遗留应用程序 - 处理 IFRAME 混乱 (Window.postMessage)
遗留应用程序 - 处理 IFRAME 混乱 (Window.postMessage)
现在是 2018 年 10 月,我应该写点关于 React 16.5、Angular 7.0 或 Blazor 0.6 的东西……但是……不如来点有趣的,用充满 iframe 的遗留应用程序来代替?;)
简而言之
你可以向嵌入的 iframe
传递消息
someIframe.contentWindow.postMessage({ a: 'aaa', b: 'bbb' }, '*');
以及向 iframe
的父级传递消息,如下所示:
window.parent.postMessage({ a: 'aaa', b: 'bbb' }, '*');
这是接收消息的方式
window.addEventListener('message', function (e) {
// Do something with e.data...
});
请注意安全!查看我的实时示例及其GitHub 上的代码,或者如果您想要更多功能,可以考虑framebus 库。
iframe
除非您在创业公司工作,否则您的部分职责可能是维护一些内部 Web 应用程序的生命,而这些应用程序还记得 Internet Explorer 6 的辉煌时代。它之所以叫做工作,是有原因的,对吧?;) 过去,iframe
被大量使用。不仅仅是嵌入来自其他站点的内容、跨域 Ajax 或用 hack 来覆盖 select 的叠加层,还用于提供页面区域之间的边界或模拟类似桌面的窗口布局……
因此,我们假设您有一个包含嵌套 iframe
的站点,您需要在其中一个 iframe
中iframe
中的操作。
在上面的示例中,顶层的 iframe 0
包含一个文本字段,如果点击了更新用户名按钮,我们应该修改嵌套的 iframe 1a
和 iframe 1b
中的用户名标签。当点击更新账号按钮时,深度嵌套的 iframe 2a
中的账号应该改变。点击更新新闻应该修改 iframe 2b
中的文本。最后一个 iframe
包含一个清除新闻按钮,当它被点击时,应该将通知传递给顶层的 iframe 0
……
直接访问(错误的方式)
实现 iframe
之间交互的一种方法是通过直接访问嵌套/父 iframe
的 DOM 元素(如果同源策略允许)。嵌套 iframe
中的元素状态可以通过此类代码进行修改:
document.getElementById('someIframe').contentWindow.document.getElementById('someInput').value = 'test';
并且可以通过以下方式访问父级中的元素:
window.parent.document.getElementById('someInput').value = 'test';
这种方法的问题在于它iframe
,这是不幸的,因为 iframe
可能被用来提供某种程度的封装。直接 DOM 访问还有另一个缺点:在深度嵌套的情况下,它会变得非常糟糕:window.parent.parent.parent.document
……
消息传递(正确的方式)
Window.postMessage 方法被引入浏览器是为了在 Window
对象之间实现安全的跨域通信。该方法可用于在 iframe
之间传递数据。在本文中,我假设带有 iframe
的应用程序很旧,但可以在 Internet Explorer 11 中运行,这是微软发布的最后一个版本(在 2013 年)。据我所知,通常需要支持 Internet Explorer,但至少它是最新的版本。如果这个假设不适用于您,我很抱歉,我已经受够了支持旧版 Internet Explorer 的痛苦……
借助 postMessage
方法,可以非常轻松地创建一个小型消息总线,这样在一个 iframe
中触发的事件就可以在另一个 iframe
中处理,前提是目标 iframe
iframe
之间的耦合
请看一个可以向所有直接嵌套的 iframe 发送消息的示例函数:
const sendMessage = function (type, value) {
console.log('[iframe0] Sending message down, type: ' + type + ', value: ' + value);
var iframes = document.getElementsByTagName('iframe');
for (var i = 0; i < iframes.length; i++) {
iframes[i].contentWindow.postMessage({ direction: 'DOWN', type: type, value: value }, '*');
}
};
在上面的代码中,通过 document.getElementsByTagName
找到 iframe
,然后通过 contentWindow.postMessage
调用向每个 iframe
发送消息。postMessage
方法的第一个参数是我们想要传递的消息(数据)。浏览器将负责其序列化,由您决定需要传递什么。我选择传递一个包含 3 个属性的对象:第一个指定消息应该在哪个方向发送(UP 或 DOWN),第二个指定消息类型(例如 UPDATE_USER
),最后一个包含消息的负载。在本示例应用程序中,它将是用户在输入框中输入的文本,该文本应影响嵌套 iframe 中的元素。传递给 contentWindow
方法的'*' 值决定了浏览器如何分发事件。星号表示没有限制 - 对于我们的代码示例来说是可以的,但在现实世界中,您应该考虑提供一个 URI 作为参数值,以便浏览器能够根据方案、主机名和端口号限制事件。如果您需要传递敏感数据(您不希望任何加载到 iframe
的网站看到它!),这是必须的!
这就是 sendMessage
函数如何用于通知嵌套 iframe
更新用户信息的需求
document.getElementById('updateUserName').addEventListener('click', function (event) {
sendMessage('UPDATE_USER', document.getElementById('textToSend').value);
});
上面显示的代码属于 iframe 0
,其中包含两个嵌套的 iframe:1a
和 1b
。下面是来自 iframe 1b
的代码,它可以做两件事:处理它感兴趣的消息,或者直接将其传递 UP
或 DOWN
。
window.addEventListener('message', function (e) {
console.log('[iframe1b] Message received');
if (e.data.type === 'UPDATE_USER') {
console.log('[iframe1b] Handling message - updating user name to: ' + e.data.value);
document.getElementById('userName').innerText = e.data.value;
} else {
if (e.data.direction === 'UP') {
console.log('[iframe1b] Passing message up');
window.parent.postMessage(e.data, '*');
} else {
console.log('[iframe1b] Passing message down');
document.getElementById('iframe2b').contentWindow.postMessage(e.data, '*');
}
}
});
您可以看到,可以通过监听 window
对象上的 message
事件来捕获消息。传递的消息可在事件的 data
字段中找到,因此会检查 e.data.type
以查看代码是应该处理消息还是只是传递它。通过 window.parent.postMessage
来传递 UP
,通过在 iframe
元素上调用 contentWindow.postMessage
来传递 DOWN
。
iframe 2b
包含一个按钮,其 click
处理程序如下:
document.getElementById('clearNews').addEventListener('click', function () {
document.getElementById('news').innerText = '';
console.log('[iframe2b] News cleared, sending message up, type: NEWS_CLEARED');
window.parent.postMessage({ direction: 'UP', type: 'NEWS_CLEARED' }, '*');
);
它清除 news
文本并将通知发送到父 window
(iframe
)。此消息将由 iframe 1b
接收,然后向上传递给 iframe 0
,iframe 0
将通过显示“新闻已清除
”文本来处理它。
window.addEventListener('message', function (e) {
console.log('[iframe0] Message received');
if (e.data.type === 'NEWS_CLEARED') {
console.log('[iframe0] Handling message - notifying about news clear');
document.getElementById('newsClearedNotice').innerText = 'News cleared!';
}
});
请注意,这次 message
处理程序非常简单。这是因为在顶层 iframe 0
的情况下,我们不希望传递收到的消息。
示例
就是这样!这是一个工作的 iframe
“丰富”页面示例。打开浏览器控制台,查看消息是如何飞来飞去的。查看仓库以查看代码,它是纯原生 JS,没有任何花哨的功能,因为我们假设 Internet Explorer 11 需要直接支持(也在 Firefox 62、Chrome 69 和 Edge 42 中进行了测试)。
在我最初的帖子中,有一个演示 iframe 在这里,但似乎 CodeProject 不喜欢这样的嵌入。请到这里查看示例: https://en.morzel.net/post/legacy-apps-dealing-with-iframe-mess-window-postmessage#example