• JS-iframe-跨窗口通讯


    备注:补充一个额外方案:建立一个中间页面,使子窗口跳转到和主窗口同域的页面中,再使用子窗口的parent操作父窗口。

     

    跨窗口通讯

     

    “相同来源”(相同站点)策略限制了窗口和框架之间的访问。

    这样的想法是,如果用户打开了两个页面:一个来自john-smith.com,而另一个打开gmail.com,则他们将不需要脚本来john-smith.com从中读取我们的邮件gmail.com因此,“相同来源”策略的目的是保护用户免遭信息盗窃。

    同源

    如果两个URL具有相同的协议,域和端口,则称它们具有“相同的来源”。

    这些URL都具有相同的来源:

    • http://site.com
    • http://site.com/
    • http://site.com/my/page.html

    这些没有:

    • http://www.site.com(另一个领域:www.重要)
    • http://site.org(另一个领域:.org重要)
    • https://site.com(另一种协议:https
    • http://site.com:8080(另一个端口:8080

    “相同来源”政策规定:

    • 如果我们引用了另一个窗口,例如由创建的弹出window.open窗口或内部的一个窗口<iframe>,并且该窗口来自同一原点,则可以完全访问该窗口。
    • 否则,如果它来自另一个来源,那么我们将无法访问该窗口的内容:变量,文档等。唯一的例外是location:我们可以更改它(从而重定向用户)。但是我们无法读取位置(因此我们无法看到用户现在所在的位置,也不会泄漏任何信息)。

    实际应用中:iframe

    一个<iframe>标签主机单独的嵌入式窗口,有自己的独立documentwindow对象。

    我们可以使用属性访问它们:

    • iframe.contentWindow使窗户进入窗户<iframe>
    • iframe.contentDocument将文档放入的<iframe>简写iframe.contentWindow.document

    当我们访问嵌入式窗口内的内容时,浏览器会检查iframe的来源是否相同。如果不是这样,则访问被拒绝(写入location是一个例外,它仍然被允许)。

    例如,让我们尝试<iframe>从另一个来源进行读写

     
    <iframe src="https://example.com" id="iframe"></iframe>
    
    <script>
      iframe.onload = function() {
        // we can get the reference to the inner window
        let iframeWindow = iframe.contentWindow; // OK
        try {
          // ...but not to the document inside it
          let doc = iframe.contentDocument; // ERROR
        } catch(e) {
          alert(e); // Security Error (another origin)
        }
    
        // also we can't READ the URL of the page in iframe
        try {
          // Can't read URL from the Location object
          let href = iframe.contentWindow.location.href; // ERROR
        } catch(e) {
          alert(e); // Security Error
        }
    
        // ...we can WRITE into location (and thus load something else into the iframe)!
        iframe.contentWindow.location = '/'; // OK
    
        iframe.onload = null; // clear the handler, not to run it after the location change
      };
    </script>

    上面的代码显示了除以下各项以外的所有操作的错误:

    • 获取对内部窗口的引用iframe.contentWindow-允许的。
    • 写信给location

    与此相反,如果<iframe>起源相同,我们可以使用它做任何事情:

     
    <!-- iframe from the same site -->
    <iframe src="/" id="iframe"></iframe>
    
    <script>
      iframe.onload = function() {
        // just do anything
        iframe.contentDocument.body.prepend("Hello, world!");
      };
    </script>
    iframe.onload 与 iframe.contentWindow.onload

    iframe.onload(在事件<iframe>标签)是基本上相同iframe.contentWindow.onload(在嵌入窗口对象)。当嵌入式窗口完全加载所有资源时触发。

    …但是我们无法iframe.contentWindow.onload从其他来源访问iframe,因此请使用iframe.onload

    Windows在子域上:document.domain

    根据定义,两个具有不同域的URL具有不同的来源。

    但是,如果窗口共享同一个二级域名,例如john.site.competer.site.comsite.com(使他们共同的二级域名site.com),我们可以让浏览器忽略这种差别,使他们能够从“同根同源”来对待为了跨窗口交流的目的。

    为了使其正常工作,每个这样的窗口都应运行以下代码:

    document.domain = 'site.com';

    就这样。现在,他们可以不受限制地进行交互。同样,这仅适用于具有相同二级域的页面。

    iframe:错误的文档陷阱

    当iframe来自同一来源时,我们可能会访问它 document,这是一个陷阱。它与跨源事物无关,但重要的是要知道。

    创建后,iframe会立即拥有一个文档。但是该文档不同于加载到其中的文档!

    因此,如果我们立即对文档进行操作,则可能会丢失。

    在这里,看看:

     
    <iframe src="/" id="iframe"></iframe>
    
    <script>
      let oldDoc = iframe.contentDocument;
      iframe.onload = function() {
        let newDoc = iframe.contentDocument;
        // the loaded document is not the same as initial!
        alert(oldDoc == newDoc); // false
      };
    </script>

    我们不应该处理尚未加载的iframe的文档,因为那是错误的文档如果我们在其上设置了任何事件处理程序,它们将被忽略。

    如何检测文档在那里的时刻?

    iframe.onload 触发时,正确的文档肯定存在但是,只有在加载了所有资源的整个iframe时,它才会触发。

    我们可以尝试使用check in来赶上这一时刻setInterval

     
    <iframe src="/" id="iframe"></iframe>
    
    <script>
      let oldDoc = iframe.contentDocument;
    
      // every 100 ms check if the document is the new one
      let timer = setInterval(() => {
        let newDoc = iframe.contentDocument;
        if (newDoc == oldDoc) return;
    
        alert("New document is here!");
    
        clearInterval(timer); // cancel setInterval, don't need it any more
      }, 100);
    </script>

    集合:window.frames

    获取–的窗口对象的另一种方法<iframe>是从命名集合中获取它 window.frames

    • 按编号:window.frames[0]–文档中第一帧的窗口对象。
    • 按名称:window.frames.iframeName–具有的框架的窗口对象 name="iframeName"

    例如:

     
    <iframe src="/" style="height:80px" name="win" id="iframe"></iframe>
    
    <script>
      alert(iframe.contentWindow == frames[0]); // true
      alert(iframe.contentWindow == frames.win); // true
    </script>

    一个iframe可能内部还有其他iframe。相应的window对象形成层次结构。

    导航链接为:

    • window.frames –“子级”窗口的集合(用于嵌套框架)。
    • window.parent –对“父”(外部)窗口的引用。
    • window.top –对最顶层父窗口的引用。

    例如:

     
    window.frames[0].parent === window; // true

    我们可以使用该top属性来检查当前文档是否在框架内打开:

     
    if (window == top) { // current window == window.top?
      alert('The script is in the topmost window, not in a frame');
    } else {
      alert('The script runs in a frame!');
    }

    “沙盒” iframe属性

    sandbox属性允许排除<iframe>in 内部的某些操作,以防止其执行不受信任的代码。它通过将iframe视为来自其他来源和/或应用其他限制来对其进行“沙盒化”。

    有适用于的“默认设置”限制<iframe sandbox src="...">但是,如果我们提供一个以空格分隔的限制列表,可以将其放宽,这些限制不应用作属性的值,例如:<iframe sandbox="allow-forms allow-popups">

    换句话说,空"sandbox"属性会施加最严格的限制,但我们可以放置一个用空格分隔的列表,以列出要删除的属性。

    以下是限制的列表:

    allow-same-origin
    默认情况下"sandbox",对iframe强制实施“不同来源”策略。换句话说,它使浏览器将iframe视为来自另一个来源,即使其src指向同一站点也是如此。具有所有隐含的脚本限制。此选项将删除该功能。
    allow-top-navigation
    允许iframe更改parent.location
    allow-forms
    允许提交来自的表格iframe
    allow-scripts
    允许从运行脚本iframe
    allow-popups
    允许从window.open弹出窗口iframe

    有关更多信息,请参见手册

    以下示例演示了具有默认限制集的沙盒iframe <iframe sandbox src="...">它具有一些JavaScript和一种形式。

    请注意,没有任何效果。因此,默认设置确实很苛刻:

    结果
    index.html
    sandboxed.html
     
    请注意:

    "sandbox"属性的目的只是添加更多限制。它无法删除它们。特别是,如果iframe来自其他来源,则无法放宽同源限制。

    跨窗口消息传递

    postMessage界面允许Windows彼此交谈,无论它们来自哪个来源。

    因此,这是解决“相同来源”政策的一种方法。它允许从窗口john-smith.com进行对话gmail.com和交换信息,但前提是它们都同意并调用相应的JavaScript函数。这对用户来说很安全。

    该界面分为两个部分。

    postMessage

    想要发送消息的窗口调用接收窗口的postMessage方法。换句话说,如果要将消息发送到win,则应致电 win.postMessage(data, targetOrigin)

    参数:

    data
    要发送的数据。可以是任何对象,可以使用“结构化序列化算法”克隆数据。IE仅支持字符串,因此我们应该使用JSON.stringify复杂的对象来支持该浏览器。
    targetOrigin
    指定目标窗口的原点,以便只有给定原点的窗口才能获取消息。

    targetOrigin是一项安全措施。请记住,如果目标窗口来自另一个来源,我们将无法location在发送方窗口中读取它因此,我们不能确定现在正在预期的窗口中打开了哪个站点:用户可以导航离开,并且发件人窗口对此一无所知。

    指定该选项targetOrigin可确保窗口仅在正确位置时才接收数据。当数据敏感时很重要。

    例如,win仅在源头有文档的情况下,此处才接收消息http://example.com

    <iframe src="http://example.com" name="example">
    
    <script>
      let win = window.frames.example;
    
      win.postMessage("message", "http://example.com");
    </script>

    如果我们不想检查,可以将其设置targetOrigin*

    <iframe src="http://example.com" name="example">
    
    <script>
      let win = window.frames.example;
    
      win.postMessage("message", "*");
    </script>

    消息

    要接收消息,目标窗口应具有message事件处理程序触发何时postMessage调用(并targetOrigin检查成功)。

    事件对象具有特殊的属性:

    data
    来自的数据postMessage
    origin
    例如,发件人的来源http://javascript.info
    source
    对发件人窗口的引用。source.postMessage(...)如果需要,我们可以立即返回。

    要分配该处理程序,我们应该使用addEventListener,短语法window.onmessage不起作用。

    这是一个例子:

    window.addEventListener("message", function(event) {
      if (event.origin != 'http://javascript.info') {
        // something from an unknown domain, let's ignore it
        return;
      }
    
      alert( "received: " + event.data );
    
      // can message back using event.source.postMessage(...)
    });

    完整的例子:

    结果
    iframe.html
    index.html
     

    概要

    要调用方法并访问另一个窗口的内容,我们应该首先对其进行引用。

    对于弹出窗口,我们有以下参考:

    • 在打开器窗口中:window.open–打开一个新窗口并返回对该窗口的引用,
    • 从弹出窗口:window.opener–是对弹出窗口中打开器窗口的引用。

    对于iframe,我们可以使用以下方法访问父/子窗口:

    • window.frames –嵌套窗口对象的集合,
    • window.parentwindow.top是对父窗口和顶部窗口的引用,
    • iframe.contentWindow<iframe>标签内的窗口

    如果Windows共享相同的来源(主机,端口,协议),则Windows可以相互执行任何操作。

    否则,只有可能的操作是:

    • 更改location另一个窗口的(只读访问)。
    • 向其发布消息。

    例外是:

    • 共享相同二级域的Windows:a.site.comb.site.com然后document.domain='site.com'将它们都设置为“原点相同”状态。
    • 如果iframe具有sandbox属性,则除非allow-same-origin该属性值中指定,否则它将被强制置于“其他来源”状态这可用于在同一站点的iframe中运行不受信任的代码。

    postMessage界面允许两个具有任何起源的窗口进行交谈:

    1. 发件人呼叫targetWin.postMessage(data, targetOrigin)

    2. 如果targetOrigin不是'*',则浏览器检查window targetWin是否具有原点targetOrigin

    3. 如果是这样,则使用特殊属性targetWin触发message事件:

      • origin–发送者窗口的来源(如http://my.site.com
      • source –对发件人窗口的引用。
      • data –数据,除IE仅支持字符串的任何地方的任何对象。

      我们应该使用addEventListener它在目标窗口内设置此事件的处理程序。

  • 相关阅读:
    LeetCode#1047-Remove All Adjacent Duplicates In String-删除字符串中的所有相邻重复项
    LeetCode#345-Reverse Vowels of a String-反转字符串中的元音字母
    LeetCode#344-Reverse String-反转字符串
    LeetCode#232-Implement Queue using Stacks-用栈实现队列
    LeetCode#225-Implement Stack using Queues-用队列实现栈
    LeetCode#20-Valid Parentheses-有效的括号
    树的遍历
    [leetcode] 树(Ⅰ)
    二叉树图形化显示
    你错在成长于文明的边陲
  • 原文地址:https://www.cnblogs.com/lxhbky/p/13743266.html
Copyright © 2020-2023  润新知