跨文档通信(cross-document messaging)与消息通道通信(channel messaging)简介

跨文档通信

Web中由于安全与隐私的原因不允许执行跨站脚本,以免不同域的文档上下文互相影响。而跨文档通信则是在防止XSS的同时允许不同域之间文档通信的方法。

以下场景均属于跨文档通信

  • Window与其创建的WebWorker的通信
  • Window与其包含的跨域iFrame的通信
  • Window与其打开的同域popup Window的通信

可以看到参与通信的双方有:Window、Popup Window、iFrame与Web Worker。下面是简单的代码示例:

// Window <=> Popup window(same origin)
let popup = window.open('popup.html') // main window
popup.postMessage('hello popup window~') 
window.addEventListener("message", e => window.source.postMessage('hi main window', e.origin)); // popup window

// Window <=> iFrame
document.querySelector('iframe').contentWindow.postMessage('hello iframe~') // main window
window.addEventListener('message', e => window.parent.postMessage('hi main window~'))  // iframe

// Window <=> Web Worker
(new Worker('worker.js')).postMessage('hello worker~') // main window
self.onmessage = e => self.postMessage('hi main window~') // dedicated worker

安全性:通过设置targetOrigin参数来指定通信的对方。默认为'/',会限定通信对方为同域目标。

消息通道通信

如果要使用独立的代码块在不同的浏览器上下文中进行通信,可以使用消息通道进行通信(channel messaging)。

通过MessageChannelMessagePort来实现。

  • MessageChannel为消息通信类,具有两个只读的MessagePort:port1与port2,为消息通道所使用的两个端口
  • 将一个端口作为本地端口,将另一个端口传递给远程窗口使用
  • 消息会以DOM事件的方式传递,不会中断或阻塞事件循环中的task执行
// 1.创建一个连接
const channel = new MessageChannel()
// 2.主窗口发送消息与端口
window.postMessage('message', '*', [channel.port1])
// 3.使用另一个端口监听消息
channel.port2.onmessage = e => {
  console.log('get data: ', e.data)
};

postMessage 与 onmessage

port中除了onmessage与postMessage方法外,还具有EventTarget的相关方法与开启和关闭控制方法。

port.onmessage(e => {
  console.log('hello')
})

// 等价于 => 

port.addEventListener('message', e => console.log('hello'))
port.start();

window对象上的postMesagge一般有两种传参类型: window.postMessage(message[,options])window.postMessage(message,targetOrigin[,transfer])。出于安全性考虑可以传入第二个参数限定目标域。而MessagePort的postMessage则不具有这个设置。

interface Window {
  postMessage(message: any, targetOrigin: string, transfer?: Transferable[]): void;
}
interface MessagePort extends EventTarget {
  postMessage(message: any, transfer: Transferable[]): void;
  postMessage(message: any, options?: PostMessageOptions): void;
}

transfer对象

在postMessage中,除了消息本体message外,还可以传递transferable对象。transfer的transferable对象并不是被克隆的对象,而是类似于移交了控制权的对象,当transfer之后在当前的上下文中该对象则不再可用。

下面这些对象属于transferable对象:

  • OffscreenCanvas (之前的文章中介绍过)
  • ImageBitmap (也可以用在Canvas的数据传输中)
  • MessagePort (消息通道通信中传递该对象)
  • ArrayBuffer (原始二进制数据缓冲区,ws通信等的数据处理使用)