跨浏览器 HTML5 postMessage 方法以及 message 事件模拟实现

postMessage 是 HTML5 新方法,它可以实现跨域窗口之间通讯。到目前为止,只有 IE8+, Firefox 3, Opera 9, Chrome 3和 Safari 4 支持,而本篇文章主要讲述 postMessage 方法与 message 事件跨浏览器实现。

postMessage 方法 JSONP 技术不一样,前者是前端擅长跨域文档数据即时通讯,后者擅长针对跨域服务端数据通讯,postMessage 应用场景能说明这个区别:

应用场景举例

  1. webOS 使用 iframe 嵌入第三方应用,此时 webOS 与应用需要实时接收/发送各自的消息与响应事件。

  2. 页面弹出一个由 iframe 层,嵌入第三方提供的图片上传页面,文件上传完毕后需要获取返回图片地址插入到编辑器。

  3. iframe 跨域高度自适应。

HTML5 postMessage 方法

postMessage 可以实现跨域文档的消息传输(Cross Document Messaging)。

向外界窗口发送消息:

otherWindow.postMessage(message, targetOrigin);

otherWindow: 指目标窗口,是 window.frames 属性的成员或者由 window.open 方法创建的窗口

参数说明:

  • _message:_是要发送的消息,类型为 String、Object (IE8、9 不支持)

  • _targetOrigin:_是限定消息接收范围,不限制请使用 ‘*’

HTML5 message 事件

绑定消息事件:

window.addEventListener('message', receiver, false); 
function receiver (event) { 
  if (event.origin === 'http://example.com') { 
    if (event.data === 'Hello world') { 
      event.source.postMessage('Hello', event.origin); 
    } else { 
      alert(event.data); 
    }; 
  }; 
};

调函数第一个参数接收 Event 对象,有三个常用属性:

  • _data:_消息

  • _origin:_消息来源地址

  • _source:_源 DOMWindow 对象

message 事件在低版本浏览器下模拟实现

对于支持 postMessage 方法的浏览器直接使用它;而对于 IE6、7 采用了比较成熟的 window.name 保存数据以及跨域 iframe 静态代理动态传输方案,下面简称 Cross Frame。

Cross Frame

假设在域 www.a.com 上有页面 a.html 和代理页面 proxy-a.html , 另一个域 www.b.com 上有个页面 b.html 和代理页面 proxy-b.html,a.html 需要向 b.html 中发送消息时,页面会创建一个隐藏的 iframe 指向 proxy-b.html ,并把消息赋予 iframe.name 属性,此时 proxy-b.html 可以通过 window.name 获取到消息,由于 proxy-b.html 与 b.html 是同域,proxy-b.html 可以把消息赋予 b.html。 b.html 要给 a.html 发送消息时,原理一样。

自动捕获代理 URL

在 Cross Frame 方案中,通信双方必须确切的知道静态代理文件的 URL,显然这个极大的限制了应用范围,我们可以通过一些约定改善:静态代理文件必须置于通信页面所在域根目录,且文件名必须保持一致,如 messageEvent-proxy.html。

有了上述约定,接下来可以用一些巧妙的方法让双方自动捕获代理 URL。以 http://www.a.com/a.html 通过 iframe 嵌入 http://www.b.com/b.html 保持数据交换为例进行说明:

b.html 的静态代理路径可以通过正则分析 iframe.src 后得知;而从框架 b.html 内获取父页面就比较麻烦了,因为跨域后的 parent.location.href 属性只可写入不可读取,不过还可以借用 document.referrer 属性来分析来路地址得知父页面 url。document.referrer 是一个不稳定的属性,我们可以利用 iframe 中 window.name 刷新也不会变化的特性,用此来保存父页面 a.html 的地址。

持续跟踪 URL

a.html 第一次通过提取 iframe.src 路径可得知 b.html 的地址,假若 b.html 跳转到其他域名的时候,此时就会失去对 iframe 内静态代理的联络。 好在新页面由于能够获取父页面 a.html 保存在 window.name 的静态代理,所以我们可以在新页面初始化的时候向 a.html 传递消息告诉它新的地址,这样就能持续跟踪 iframe 中的 URL。

开源事件库 messageEvent.js

“messageEvent.js”是针对上述方案封装的 message 事件与 postMessage 方法库,它让各个浏览器之间 message 的 Event 对象成员属性统一,event.data 属性能传递多达 2MB 的文本信息,并且能让 IE6-9 浏览器像其他现代浏览一样支持 Object 类型数据进行传递 (内部使用深拷贝方式)。

若应用双方页面都采用 messageEvent.js,即可轻松实现跨域通信。

接口

  • _add(callback)_添加 message 事件

  • _remove(callback)_卸载 message 事件

  • _postMessage(otherWindow, message, targetOrigin)_向外部窗口发送消息

通过 jQuery 使用它

jQuery 是一个应用比较广泛的 DOM 库,它的事件机制非常强大而精妙,可以实现自定义事件。若页面引用了 jQuery, messageEvent.js 会为为它提供支持,你可以用熟悉的jQuery api 风格编程,如:

jQuery(window).bind('message', function (event) { 
      alert(event.data) 
}); 
jQuery(window).message(function (event) { 
      alert(event.data) 
}); 
jQuery.postMessage(iframe.contentWindow, 'hello world', '*'); 
jQuery(window).unbind('message');

由于 jQuery 把包装后的 Event 对象用 data 属性来保存 bind 方法传入的额外数据,导致与 message 事件自身的 event.data 属性冲突——这是一个设计错误。为了让 message 事件能够正确获取 event.data,messageEvent.js 通过操作 jQuery 底层缓存强制覆盖了 bind 方法传入的附加数据 (只针对 message 类型)。当然,我仍然期待 jQuery 未来版本能够取消掉 bind 方法的鸡肋特性。

项目地址

项目主页: http://code.google.com/p/message-event/
在线阅读最新版源码: messageEvent.js| messageEvent-proxy.html