【问题标题】:Issues with Cross Document Messaging between IFrame & ParentIFrame 和父级之间的跨文档消息传递问题
【发布时间】:2016-10-12 06:39:47
【问题描述】:

我有一个应用程序在“外部”页面(不同的域等)的 iframe 内运行。为了允许 iframe 和父级之间进行一些基本的通信,我在父页面上加载了我的一些脚本并使用 postMessage 进行一些跨文档消息传递。

大多数情况下,这种通信按预期进行,但有时我会看到一些错误报告给我的错误跟踪工具,但无法弄清楚它们发生的原因。

下面是一些示例代码:

PluginOnParent.js

// ...
window.addEventListener('message', function(e) {
    // Check message origin etc...
    if (e.data.type === 'iFrameRequest') {
        e.source.postMessage({
            type: 'parentResponse',
            responseData: someInterestingData
        }, e.origin);
    }
    // ...
}, false);
// ...

AppInsideIFrame.js

// ...
var timeoutId;


try {
    if (window.self === window.top) {
        // We're not inside an IFrame, don't do anything...
        return;
    }
} catch (e) {
    // Browsers can block access to window.top due to same origin policy.
    // See http://stackoverflow.com/a/326076
    // If this happens, we are inside an IFrame...
}

function messageHandler(e) {
    if (e.data && (e.data.type === 'parentResponse')) {
        window.clearTimeout(timeoutId);
        window.removeEventListener('message', messageHandler);
        // Do some stuff with the sent data
    }
}

timeoutId = window.setTimeout(function() {
    errorTracking.report('Communication with parent page failed');
    window.removeEventListener('message', messageHandler);
}, 500);

window.addEventListener('message', messageHandler, false);
window.parent.postMessage({ type: 'iFrameRequest' }, '*');
// ...

这里发生了什么,当超时并报告错误时?

我的一些更多信息和想法:

  • 我自己无法控制父页面
  • 这似乎不是一般的“配置”问题(CORS 等),因为错误发生在大部分时间都有效的同一页面上
  • 我们根本不支持 IE
  • 我的错误报告工具报告了许多不同的浏览器,其中包括最新版本(FF 49、Android 5 上的 Chrome 43、Win 和 Android 6 上的 Chrome 53、Mobile Safari 10……)
    • 因此,这似乎不是与特定浏览器或版本相关的问题。
  • 500 毫秒的超时只是我选择的一个神奇数字,我认为它是完全安全的...

【问题讨论】:

  • 这是在测试环境中发生的还是在野外发生的?
  • 它在野外发生,我无法重现它(至少直到现在)。
  • 在这种情况下,我想知道是否有些用户最终会在 iframe 之外访问您的内页,这就是消息传递不起作用的原因?您可以添加一些代码来检查内页是否在 iframe 中,快速 Google 给出了一些关于如何执行此操作的 SO 答案。
  • @DanDef 我已经进行了这样的检查,但在我原来的问题中忽略了它。我现在在上面的示例代码中添加了一些代码。查看AppInsideIFrame.js的前几行
  • 您是否尝试过将超时提高到一个非常不切实际的值?像 5000000000 之类的东西。我只是在想,如果以某种方式加载父页面的时间比您的要长,则超时可能会失败。另外,我会将您的超时设置为脚本中的最后一件事 - 添加事件并在创建超时之前发送第一个 postMessage..

标签: javascript iframe cross-domain messaging


【解决方案1】:

问题似乎出在您的 PluginOnParent.js 中,您将在其中发送响应。而不是使用“e.origin”(在开发人员工具中检查时返回“null”)——尝试使用文字'*',正如它在以下关于 postMessage 用法的文档中所述(在 targetOrigin 的描述中):

https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage

另外,作为奖励,我刚刚在两个不同的域中对此进行了测试,并且效果也很好。我将 Parent.html 放在一个域 Web 服务器上,并将 iframe 的 src 更改为 child.html 在一个完全不同的域上,它们可以很好地通信。

Parent.html

<html>
<head>
    <script type="text/javascript">
        function parentInitialize() {
            window.addEventListener('message', function (e) {
                // Check message origin etc...
                if (e.data.type === 'iFrameRequest') {
                    var obj = {
                        type: 'parentResponse',
                        responseData: 'some response'
                    };
                    e.source.postMessage(obj, '*');
                }
                // ...
            })
		}
    </script>
</head>
<body style="background-color: rgb(72, 222, 218);" onload="javascript: parentInitialize();">
    <iframe src="child.html" style="width: 500px; height:350px;"></iframe>
</body>
</html>

Child.html

<html>
<head>
    <script type="text/javascript">
        function childInitialize() {
            // ...
            var timeoutId;


            try {
                if (window.self === window.top) {
                    // We're not inside an IFrame, don't do anything...
                    return;
                }
            } catch (e) {
                // Browsers can block access to window.top due to same origin policy.
                // See http://stackoverflow.com/a/326076
                // If this happens, we are inside an IFrame...
            }

            function messageHandler(e) {
                if (e.data && (e.data.type === 'parentResponse')) {
                    window.clearTimeout(timeoutId);
                    window.removeEventListener('message', messageHandler);
                    // Do some stuff with the sent data
                    var obj = document.getElementById("status");
                    obj.value = e.data.responseData;
                }
            }

            timeoutId = window.setTimeout(function () {
                var obj = document.getElementById("status");
                obj.value = 'Communication with parent page failed';
                window.removeEventListener('message', messageHandler);
            }, 500);

            window.addEventListener('message', messageHandler, false);
            window.parent.postMessage({ type: 'iFrameRequest' }, '*');
            // ...
        }
    </script>
</head>
<body style="background-color: rgb(0, 148, 255);" onload="javascript: childInitialize();">
    <textarea type="text" style="width:400px; height:250px;" id="status" />
</body>
</html>

希望有帮助!

【讨论】:

  • 不幸的是,我现在无法尝试您的建议,但由于它们似乎是本主题中最有希望的建议,因此我还是给了您赏金。无论哪种方式,一旦我了解更多信息,我会及时更新此问题。
  • 谢谢——另外,如果你有兴趣,我也加入了一个与此类似的线程,但他们希望两个页面之间有一个持续的计时器通信。我向那个人提到了这个例子,并借用了你的一些代码作为例子,并将其修改为他的场景。它也可能对您有用,所以我想我会提到它:stackoverflow.com/questions/14028372/… - 如果不出意外,它会显示每秒左右的持续无错误通信。
【解决方案2】:

大多数情况下,这种交流会按预期进行,但有时我 看到一些错误报告给我的错误跟踪工具,但无法计算 找出它们发生的原因。

这里发生了什么,当超时并报告错误时?

我自己无法控制父页面

不确定函数errorTracking.report 在调用时会做什么,但似乎没有发生与message 事件相关的实际错误?

500 毫秒的超时只是我选择的一个神奇数字 以为是完全安全的……

duration 设置为500,可以在message 事件在window 触发之前调用setTimeout

timeoutId = window.setTimeout(function() {
    errorTracking.report('Communication with parent page failed');
    window.removeEventListener('message', messageHandler);
}, 500);

setTimeoutduration 调整为更长的持续时间。


或者将onerror 处理程序或window.addEventListener 替换为setTimeout

注意事项

当脚本中出现语法(?)错误时,从 different origin,语法错误的细节不上报 防止信息泄露(见bug 363897)。而是错误 报告只是“脚本错误”。这种行为可以在 一些浏览器在 上使用crossorigin 属性并具有 服务器发送适当的CORS HTTP 响应标头。一种 解决方法是隔离“脚本错误”。并处理它知道 错误详细信息只能在浏览器控制台中查看,而不是 可通过 JavaScript 访问。

例如

// handle errors
onerror = function messageErrorHandlerAtAppInsideIFrame(e) {
  console.error("Error at messageErrorIndex", e)
}

处理不同上下文或来源之间通信期间的任何实际错误。

iframeload 事件中使用postMessage 与父windowmessage 处理程序进行通信。

http://plnkr.co/edit/M85MDHF1kPPwTE2E0UGt?p=preview

【讨论】:

    猜你喜欢
    • 2011-06-09
    • 2014-10-12
    • 1970-01-01
    • 1970-01-01
    • 2012-07-31
    • 2013-05-03
    • 2014-12-13
    • 2020-02-08
    • 1970-01-01
    相关资源
    最近更新 更多