客户端页面的跨域跨文档通信[1]

注意

  1. 当前标准尚未确定下来,变数很多,该文档也会跟随时间有一定的变动。最后更改时间为:2012-05-08。
  2. 本文描述的环境为:Chrome 20.0.1123.4 dev & Mac Lion 10.7.3
  3. Chrome开启选项:
    1. Threaded compositing 强制开启
    2. GPU Accelerated SVG and CSS Filters 开启
    3. Enable Experimental JavaScript 开启
    4. Enable experimental task manager 开启

问题

主要是为了解决同一浏览器下面,不同域之间的跨文档(页面)交流的问题。官方的设计想法是使用某种方法,可以保证跨文档之间的交流,同时不会导致跨站脚本攻击。跨文档跨域的交流主要是可以让代码复用率增加,同时避免了麻烦的验证关系。

描述下使用场景可能更好理解一些:

当A页面是一个游戏页面,B页面是一个邮件服务商的页面的时候。这个时候当用户在A页面里面点击了增加好友,A页面的脚本通过跨文档通讯方法传递给B消息 [Add someone aaa@aaa.com],B页面接受到消息时候,可以通过自己的Add方法来将用户的信息增加到通讯录里面。

看起来有点类似 WebService 。不同的是之间的消息传递已从Server端挪动到客户端来通讯了。

window.postMessage

注意

在代码剖析和说明案例之前,应该先明白标题当中的otherWindow是什么意思。

当调用window.postMessage的时候,实际上是在触发window自身的message事件.整体过程如下:

window.postMessage => 触发 window 上的 message

也就是说,想要做跨文档通讯的时候,必须先要获取到对方文档的window,然后使用对方windowpostMessage方法触发对方的message事件。所有的循环都在对方的window里面去走,在这里JS代码只是调用了对方window上的方法罢了。

我们可以通过var otherWindow = window.open(...) 或者 ` window.frames.contentWindow 来获取otherWindow。注意尽管 window.open` 从形式上看起来是在不同的窗体之间进行通讯,但是要求这个窗体是通过Parent页面开启,性质上与iframe类似,因此讨论的时候我并没有将其归结于跨窗体通讯(或者伪跨窗体通讯)。

案例

postMessage(cross domain) by html5Demos.com。

代码剖析:

首先,你需要在 HTML 里面放置一个iframesrc 指定要你想要通讯的页面,没有同域的限制。在该例子里面,demo本身调用的是jsbin.com的内容。

然后,你需要在parent里面添加以下的代码。

// 先获取到iframe的window对象。调用的postMessage方法是在iframe的window上面的。
var win = document.getElementById("iframe").contentWindow;

addEvent(document.querySelector('form'), 'submit', function (e) {	
        //当按下按钮之后,使用iframe的window对象的postMessage方来来发送内容。
        win.postMessage(
      			//发送的message是什么。
            document.getElementById("message").value,
            
            //发送的消息的域是                
            "http://jsbin.com"
        );

        if (e.preventDefault)
            e.preventDefault();
        // FOR IE
        e.returnValue = false;
});

最后,在iframe所调用的页面里面写上:

//从自己的postMessage方法当中获取到的事件。
window.onmessage = function(e){
  //检查内容的源的网址是否符合自己的需求。如果不符合,则不允许通过。
  if ( e.origin !== "http://html5demos.com" ) {
    return;
  }
  // 如果是自己允许的域,则可以让它通过并写入。
  // 在该案例里面,这个来源应为:htt://html5demo.com
  document.getElementById("test").innerHTML = e.origin + " said: " 
                                                          + e.data;
};

这样代码方面就已经可用了。系统会触发iframe上面 message 事件,并传递进来一个 MessageEvent Object.内容如下:

说下几个比较关键的参数:

  1. origin,这个给出了消息来源的域,大部分的安全都是需要使用这个来做判断的。
  2. source,消息来源的window对象,在这个例子上,指的是 Parent 的window对象,我们可以直接使用e.source.postMessage 这个方法来回传信息给 Parent。
  3. data,所有从 Parent 传递过来的数据都在这个属性上面。

优点

  1. 允许使用iframe进行跨域。
  2. 使用上很简单,只要搞清楚使用对象即可。

问题:

在 Mozilla 上面提到了这个方法的一些安全问题1

  1. 如果你不需要进行通讯,最好不要绑定任何行为在 message 事件上面。
  2. 一定要验证origindata里面传递过来的数据。
  3. 在postMessage的时候,永远不要贪图方便的使用*这个来指定接受方的域。
  4. 这种方法必须要获取到对方的window,当你没有办法获取到window的时候(例如你并不是想通过iframe或者window.open的方式来打开页面,而是自己在浏览器里面新开一个tab来跑页面),该方法没办法执行。

待测试:

1.iframe 里面是否能获取到e.source 的其他内容?(在chrome 里面使用watch是没办法看到e.source.xxx的值的,然而MDN上却强调了检查source的属性);

参考:

Q.E.D.