一年前,我发过一篇关于跨文档通信方案的文章 《iframe 跨域通信的通用解决方案》,提供了一种基于创建 iframe 与轮询 window.name 的方案。
一年后,很高兴地带来彻底改造的新版本。实际上新方案已经用了很久了,一直没有时间抽象出来,最近终于挤时间分享出来了!~
回望过去
第一版的方案还是有不少问题,这里统一回复与总结一下。第一次使用 MessengerJS 的同学,可以直接跳到下面的 “新版使用” 小节。
无法使用的反馈
第一版方案,在一定程度上可以解决 iframe 通信的问题,但从大家的反馈上看,还是存在一些不足。这里列举一下评论里反馈的问题:
- HTTP 与 HTTPS,无法通信
- IE6 在某些设置下,无法通信
- js 设置 document.domain 后,无法通信
性能有损
第一版方案,需要在内层的 iframe 中创建两个 iframe,并且需要跑定时器轮询 window.name,其性能必然有所损耗,更不要说在 IE6/7 下执行这样的操作。如果父窗口要与两个 iframe 通信,那么性能的问题也会成倍增长。
API 不一致
第一版方案,为父窗口和 iframe 提供了不同的 API。这样的设计并不友好,使用者应该把每个窗口对象统一对待。
多 iframe 通信?
多个 iframe 无法直接通信,需要父窗口中转才行。
问题新版都解决了?
那是必须的,上述问题全部得以解决,更重要的是,代码量还减少了 50%+!
新方案原理概述
概念上,方案的理念还是使用 “信使” 概念,即 Messenger。
对于现代浏览器,postMessage API 还是无可撼动的。IE6/7 下,使用的是一个被认为是 bug 或安全漏洞的特性,即navigator 对象在父窗口和 iframe 之间是共享的。基于这一点,我们可以在父窗口中,在 navigator 对象上注册一个消息回调函数;在 iframe 中,调用 navigator 上的这个函数并传入参数。此时可看作,iframe 往父窗口的一个函数传递了一个参数,并在父窗口的上下文中执行了,那么就相当于 iframe 向父窗口发送了一条消息。反之亦然。
原理就是这么简单(这次我连图都不用画了),好处也是很明显的:
- 该方案不依赖浏览器的各项设计,不受设置影响,同时完美支持 HTTPS
- 不用创建多余 iframe,基于接口调用,不需要轮询,性能大幅提升
- 良好的接口封装,所有窗口对象统一对待
- 多 iframe 也不怕,navigator 对象的共享,让 iframe 之间直接通信成为可能
关于安全性
有些同学认为上述方案存在安全风险,也有在 wuyun 反馈这类问题,但微软并没有去修改。
其实并不用担心,这里做个简单说明:
我们只将消息回调函数注册在 navigator 对象上,虽然任何引入的脚本或页面,都可以向 navigator 上发消息,但这其实和 postMessage 不限域的情况并无差异。这里对开发者的建议是,传递消息使用 JSON String 的形式,使用一个字段做消息有效性的验证。如果怕一个固定值(如项目名)不安全,可以使用一个简单的加密算法,并对业务脚本进行压缩混淆,此时的安全风险可以降到最低。
好处说完了,怎么用?
最新的使用方法,请参见 Github 项目主页: http://biqing.github.io/MessengerJS/
- 在需要通信的文档中 (父窗口和 iframe 们), 都确保引入 MessengerJS
- 每一个文档 (
document
), 都需要自己的Messenger
与其他文档通信. 即每一个window
对象都对应着一个, 且仅有一个Messenger
对象, 该Messenger
对象会负责当前window
的所有通信任务. 每个Messenger
对象都需要唯一的名字, 这样它们才可以知道跟谁通信.
12345678// 父窗口中 - 初始化Messenger对象var messenger = new Messenger('Parent');// iframe中 - 初始化Messenger对象var messenger = new Messenger('iframe1');// 多个iframe, 使用不同的名字var messenger = new Messenger('iframe2'); - 在发送消息前, 确保目标文档已经监听了消息事件.
12345// iframe中 - 监听消息// 回调函数按照监听的顺序执行messenger.listen(function(msg){alert("收到消息: " + msg);}); - 父窗口想给 iframe 发消息, 它怎么知道 iframe 的存在呢? 添加一个消息对象吧.
12345// 父窗口中 - 添加消息对象, 明确告诉父窗口iframe的window引用与名字messenger.addTarget(iframe1.contentWindow, 'iframe1');// 父窗口中 - 可以添加多个消息对象messenger.addTarget(iframe2.contentWindow, 'iframe2'); - 一切 ready, 发消息吧~ 发送消息有两种方式. (以父窗口向 iframe 发消息为例)
123456// 父窗口中 - 向单个iframe发消息messenger.targets['iframe1'].send(msg1);messenger.targets['iframe2'].send(msg2);// 父窗口中 - 向所有目标iframe广播消息messenger.send(msg); - 现在看到 iframe 收到消息的 alert 提示了吗?
更多
Demo: http://biqing.github.io/labs/messenger/parent.html
项目主页:http://biqing.github.io/MessengerJS/
欢迎反馈,使用中遇到问题一定要告诉我哟!
菇凉 2015 年 7 月 21 日
发现个 bug 多个 iframe 给 父级传参数的时候,messenger.listen(function(){})就会出问题
菇凉 2015 年 7 月 21 日
会打印多次,没有办法获取到对应的 iframe 传的参数吗?
大牛您好 2017 年 4 月 13 日
这个最新版的 messenger.js 里面的代码跟上一版一样吗? git 上进不去啦
跨域总结4 – messenger.js | a豹博客 2014 年 12 月 22 日
[…] 此文出自 iframe 跨域通信的通用解决方案-第二弹!(终极解决方案)messenger.js 的原理: 对于现代浏览器,postMessage API 还是无可撼动的。IE6/7 下,使用的是一个被认为是 bug 或安全漏洞的特性,即 navigator 对象在父窗口和 iframe 之间是共享的。基于这一点,我们可以在父窗口中,在 navigator 对象上注册一个消息回调函数;在 iframe 中,调用 navigator 上的这个函数并传入参数。此时可看作,iframe 往父窗口的一个函数传递了一个参数,并在父窗口的上下文中执行了,那么就相当于 iframe 向父窗口发送了一条消息。 […]
Kayson 2015 年 3 月 16 日
博主,为什么我在 IE6/7 下测试,frame 页面访问 top.navigator 会报错,提示 Access Denied.
張政叡 2014 年 11 月 19 日
想請問如果我嵌入的網頁 是他人的網頁 我不能修改他的內容 那這個方法依然可行嗎?
玄啸 2014 年 11 月 17 日
我对作者的例子做了完善地址如下:https://github.com/xuanxiao2013/f2e-practice/tree/master/cross-domain
doubledan 2014 年 10 月 29 日
IE 下完全没办法用,直接报错
跨域的一些方法 - ^styleYan 2014 年 10 月 22 日
[…] iframe 跨域通信的通用解决方案 ( 大企鹅 AlloyTeam ) […]
QQ地带 2014 年 8 月 21 日
demo 已经失效了。
iframe跨域通信的通用解决方案-第二弹!(终极解决方案) | 可乐吧 2014 年 4 月 28 日
[…] 转自:http://www.alloyteam.com/2013/11/the-second-version-universal-solution-iframe-cross-domain-communica… […]
游客 2014 年 4 月 16 日
经试用,发现 BUG。
完全跨域的情况下,iframe1 指向其他的域名下的页面。
window.parent.frames[1] 这一句报没有权限。
TAT.Johnny 2014 年 5 月 2 日
最近都在搞手机项目, 没有及时关注这边的反馈哈..
请问是什么浏览器下报错, 有没有具体的链接或 demo 可以让我看看?
游客 2014 年 4 月 16 日
非常好的解决方案,赞!!!!!!
peanuts 2014 年 3 月 17 日
很不错
webzhan 2014 年 2 月 9 日
学习了。。不过试用了一下。。发现在子窗体页面在 IE 下无法向父窗口发送消息。IE6-11 均有这个情况。。iframe 通讯无问题。
TAT.Johnny 2014 年 2 月 14 日
这个是已知 issue, https://github.com/biqing/MessengerJS/issues/6
跨窗体通信 IE 确实支持不好, 也提示开发者尽量避免前端的跨窗体通信, 可以通过后台能力来协助通信.
Prince____Yu 2014 年 1 月 16 日
我想请问一下:
window.navigator[prefix + this.name]
后面的参数是干嘛用的?我直接 window.navigator 好像也可以呢!!!
望回复!
TAT.Johnny 2014 年 1 月 20 日
上面的方法是在 navigator 上注册方法, 如果你希望在一个页面上注册多个方法, 那肯定要使用不同的名字来注册.
为了支持注册多个方法的场景, 避免注册时的方案覆盖, 所以这样设计, 不知道这样说好不好明白?
gogo 2014 年 1 月 12 日
请问一个 iframe 中有两个事件可以 send 消息给父窗口的时候,父窗口 listen 到哪一个?我在父窗口里面 new 了两个 Messenger 对象,它们能 listen 到所有的消息,请问这是本来设计的时候没处理这样的情况吗?
TAT.Johnny 2014 年 1 月 20 日
有没有 demo 或者代码可以看一下, 你在父窗口 new 的两个 Messenger 对象的名字是否相同?
iframe 要发出去消息, 需要添加消息目标 (Target), 如果父窗口都能监听到消息, 前提也是被添加到 iframe 的 Target 列表中, 所以设计上应该没问题, 具体的再看下代码交流吧 : )
aluan 2014 年 1 月 6 日
ioldfish 说 “严格的 dtd 申明后,navigator 方法失效”,但我测试并没有失效,不知道你测试过没。
TAT.Johnny 2014 年 1 月 20 日
我测试过, 没有失效.
之前也看过 oldfish 的这个结论, 还特别测试了这种情况, 还好并没有影响.
牛得草 2013 年 12 月 11 日
很棒,请问会不会对网站前端性能造成影响呢? 整个网站都要调用登录弹窗。
TAT.Johnny 2013 年 12 月 12 日
没有影响, 更高频率的通信都没问题~
wxl 2013 年 12 月 2 日
问下楼主
子窗体 可以直接通知 另一个子窗体么?
TAT.Johnny 2013 年 12 月 2 日
感谢提问, 为了回答这个问题, 我完善了 demo, 请自己试试 🙂
水货羊 2013 年 12 月 1 日
不要怕,IE 下跨域漏洞太多了,opener 和 navigator 只是九牛一毛,等被都微软封了,我再告诉你们新的,但是没有 opener 和 navigator 简单
TAT.Johnny 2013 年 12 月 1 日
太好了, 妈妈再也不用担心微软修复漏洞了.
不过我猜 IE6 不会再有安全更新了, 最近的一次更新是去年 8 月, 微软不会对中国这样仅少数的国家修复安全风险很低的漏洞啦
tmdphp 2013 年 11 月 30 日
太棒了 感谢~
iframe跨域通信的通用解决方案 | Tencent AlloyTeam 2013 年 11 月 29 日
[…] 此方案已有新版本, 请查看《iframe 跨域通信的通用解决方案-第二弹!(终极解决方案)》。本文章可做技术学习供继续交流。 […]