一种通用的iframe跨域通信方法
同源策略简介
如果两个页面的协议、端口(如果指明了的话)和主机名都相同则两个页面拥有相同的源。同源策略阻止从一个源加载的文档或脚本获取或设置另一个源加载的文档的属性。这个策略可以追溯到 Netscape Navigator 2.0。
但很多时候两个不同域的页面之间需要进行通信,这就产生了跨域通信的问题了,关于跨域的文章非常多,可以参考10种方式实现跨域资源的共享,下面和大家分享一下我们项目中是如何进行双向跨域通信的,工大家参考。
解决方案
对于双向跨域通信来说,选择的方案也不是很多,综合各种因素,我们选择FIM + window.postMessage的组合方式来解决跨域通信问题。因为这两种方式各有优缺点,window.postMessage,使用非常简单,但是由于是个比较新的方法,IE6和IE7等老式浏览器不支持。FIM方法支持所有的浏览器,但比较繁琐,而且容易产生浏览记录,因此如果浏览器支持window.postMessage就采用window.postMessage,否则就采用FIM技术。
window.postMessage解决方案
window.postMessage是HTML5定义的一个很新的方法,这个方法可以很方便地跨window通信。由于它是一个很新的方法,所以在很旧和比较旧的浏览器中都无法使用,比如IE6和IE7,IE8已经支持这个方法了。
window.postMessage的使用方法比较简单,只有两个参数,第一个参数是要传输的消息,第二个参数是接收消息的域,可以用“×”来表示所有的域。发送消息的代码如下:
var o = document.getElementsByTagName('iframe')[0];
o.contentWindow.postMessage('Hello world', 'http://b.example.org/');
接收消息页面的代码如下:
window.addEventListener('message', receiver, false);
function receiver(e) {
if (e.origin == 'http://example.com') {
if (e.data == 'Hello world') {
e.source.postMessage('Hello', e.origin);
} else {
alert(e.data);
}
}
}
注意这里的绑定事件方式只针对非IE浏览器,IE浏览器需要用attachEvent方式来绑定事件。在上面的代码里你可以决定是否要判断消息的来源,这里是从安全角度考虑,防止不安全的消息。
FIM (Fragment Identitier Messaging)
FIM (Fragment Identitier Messaging)的原理是基于父窗口可以对iframe进行URL读写,iframe也可以写父窗口的URL(注意,跨域时iframe是不可以读取父窗口的URL的,但可以修改父窗口的URL),URL有一部分被称 为frag,就是#号及其后面的字符,它一般用于浏览器锚点定位,Server端并不关心这部分,应该说HTTP请求过程中不会携带frag,所以这部分的修改不会产生HTTP请求,也就是页面不会刷新,但是会产生浏览器历史记录。FIM的原理就是改变URL的frag部分来进行双向通信。每个window通过改变其它window的location来发送消息,并通过监听自己的URL的变化来接收消息。这个方式的通信会造成一些不必要的浏览器历史记录,而且有些浏览器 不支持onhashchange事件,需要轮询来获知URL的改变,最后,URL在浏览器下有长度限制,这个制约了每次传送的数据量。
先说父窗口向iframe发送消息,代码如下:
var o = document.getElementById('iframe');
o.href = 'http://www.weakweb.com#name=boris';
这样我们就把值传到iframe里面了,下面就需要考虑iframe如何监听何时有消息传递过来。这里有两个方法:1,在iframe里使用setInterval()轮询来判断当前的iframe的URL是否发生了变化,缺点是轮询会产生性能消耗;2,在父窗口里改变iframe的大小来触发iframe的window.onresize事件,缺点是要改变iframe窗口的大小。当iframe发现有消息传送过来时就可以通过locaiton.hash来读取了。
iframe向父窗口发送消息
parent.location.href = 'http://www.company.com#height=100';
上面提过,跨域时iframe可以写父窗口的URL,但是不可以读取的,所以这里我们就通过frag的方式把消息传递到父窗口了,同样,父窗口也需要监听啥时候有消息传进来,这里只能用轮询的方式了。
对FIM的一点改进
当父窗口采用修改iframe窗口大小的方式告诉iframe有消息传递方式时,这种方式有一个非常明显的缺点,那就是iframe窗口的大小改变会严重影响用户体验。因此这里采用的代理机制来处理。
我们额外创建了代理iframe叫proxyIframe吧,proxyIframe和之前的contentIframe是同一个域的,同时proxyIframe是隐藏不可见的,这样我们先把消息传递到proxyIframe里,然后在由proxyIframe发送到contentIframe,因为这两个iframe是同域的,所以可以很方便的传递。这样做的好处是我们只修改proxyIframe的大小而不用修改contentIframe的大小,这样不会影响用户体验,缺点是比之前复杂了一些。
浏览器的URL长度是有限制的,尤其是IE,所以我们可以通过分段传输来解决这个问题。
示例代码
我写了一个测试代码,其中包含了三个页面,一个父页面和两个iframe页面,点击下载
一个如何在本地测试运行上面的示例代码的简单方法,在C:\Windows\System32\drivers\etc\hosts文件,在里面添加两个域名指向127.0.0.1,这样就可以测试跨域了。我的hosts文件如下:
127.0.0.1 a.com
127.0.0.1 b.com
补充:web前端 , JavaScript ,