1. 9.3 跨文档消息传递
      1. 9.3.1 简介
      2. 9.3.2 安全性
        1. 9.3.2.1 作者
        2. 9.3.2.2 用户代理
      3. 9.3.3 发布消息
    2. 9.4 信道消息传递
      1. 9.4.1 简介
        1. 9.4.1.1 示例
        2. 9.4.1.2 端口作为 Web 上对象能力模型的基础
        3. 9.4.1.3 端口作为抽象服务实现的基础
      2. 9.4.2 消息信道
      3. 9.4.3 消息端口
      4. 9.4.4 端口和垃圾回收
    3. 9.5 广播到其他浏览上下文

9.3 跨文档消息传递

Window/postMessage

所有当前引擎都支持。

Firefox3+Safari4+Chrome2+
Opera9.5+Edge79+
Edge(传统)12+Internet Explorer10+
Firefox Android?Safari iOS?Chrome Android?WebView Android≤37+Samsung Internet?Opera Android10.1+

出于安全和隐私原因,Web 浏览器会阻止不同域中的文档相互影响;也就是说,不允许跨站点脚本攻击。

虽然这是一个重要的安全功能,但它会阻止来自不同域的页面进行通信,即使这些页面并不具有恶意。本节介绍了一种消息传递系统,允许文档相互通信,而无论其源域是什么,并且这种方式旨在防止跨站点脚本攻击。

postMessage() API 可以用作跟踪向量

9.3.1 简介

本节是非规范性的。

例如,如果文档 A 包含一个iframe 元素,该元素包含文档 B,并且文档 A 中的脚本调用文档 B 的Window 对象上的postMessage(),那么将在这个对象上触发一个消息事件,该事件标记为来自文档 A 的Window 对象。文档 A 中的脚本可能如下所示

var o = document.getElementsByTagName('iframe')[0];
o.contentWindow.postMessage('Hello world', 'https://b.example.org/');

要注册传入事件的事件处理程序,脚本将使用addEventListener()(或类似机制)。例如,文档 B 中的脚本可能如下所示

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

此脚本首先检查域是否为预期域,然后查看消息,它要么将其显示给用户,要么通过向最初发送消息的文档发送消息来进行响应。

9.3.2 安全性

9.3.2.1 作者

使用此 API 需要格外小心,以保护用户免受恶意实体利用网站为其自身目的的侵害。

作者应检查origin 属性,以确保只接受来自他们期望接收消息的域的消息。否则,作者消息处理代码中的错误可能会被恶意网站利用。

此外,即使在检查了origin 属性之后,作者还应检查数据是否为预期的格式。否则,如果事件的来源是通过跨站点脚本攻击漏洞进行攻击的,那么使用postMessage() 方法进一步未经检查的信息处理可能会导致攻击传播到接收方。

作者不应在包含任何机密信息的targetOrigin 参数中使用通配符(*),否则无法保证消息仅被传递给其预期接收方。


接受来自任何来源的消息的作者应考虑拒绝服务攻击的风险。攻击者可以发送大量消息;如果接收页面对每条此类消息执行昂贵的计算或导致网络流量发送,则攻击者的消息可能会被放大为拒绝服务攻击。建议作者采用限速机制(每分钟只接受一定数量的消息),使此类攻击变得不切实际。

9.3.2.2 用户代理

此 API 的完整性基于一个起源的脚本无法向其他起源(非相同起源)中的对象发布任意事件(使用dispatchEvent() 或其他方式)。

强烈建议实施者在实施此功能时格外小心。它允许作者将信息从一个域传输到另一个域,这通常出于安全原因是不允许的。它还要求 UA 谨慎地允许访问某些属性,但不允许访问其他属性。


建议用户代理考虑限制不同起源之间的消息流量,以保护天真的网站免受拒绝服务攻击。

9.3.3 发布消息

window.postMessage(message [, options ])

Window/postMessage

所有当前引擎都支持。

Firefox3+Safari4+Chrome2+
Opera9.5+Edge79+
Edge(传统)12+Internet Explorer10+
Firefox Android?Safari iOS?Chrome Android?WebView Android37+Samsung Internet?Opera Android10.1+

将消息发布到给定的窗口。消息可以是结构化对象,例如嵌套对象和数组,可以包含 JavaScript 值(字符串、数字、Date 对象等),并且可以包含某些数据对象,例如File BlobFileListArrayBuffer 对象。

optionstransfer 成员中列出的对象将被传输,而不仅仅是克隆,这意味着它们在发送方将不再可用。

可以使用optionstargetOrigin 成员指定目标起源。如果未提供,则默认为“/”。此默认值将消息限制为仅相同起源的目标。

如果目标窗口的起源与给定的目标起源不匹配,则消息将被丢弃,以避免信息泄露。要将消息发送到目标,而不管起源如何,请将目标起源设置为“*”。

如果transfer 数组包含重复对象,或者message 无法克隆,则抛出"DataCloneError" DOMException

window.postMessage(message, targetOrigin [, transfer ])

这是postMessage() 的另一个版本,其中目标起源作为参数指定。调用window.postMessage(message, target, transfer) 等效于window.postMessage(message, {targetOrigin, transfer})

当将消息发布到刚刚导航到新Document浏览上下文Window 时,消息可能无法接收其预期接收方:目标浏览上下文 中的脚本必须有时间为消息设置监听器。因此,例如,在将消息发送到新创建的子iframeWindow 的情况下,建议作者让子Document 向其父级发布消息,宣布他们已准备好接收消息,并让父级等待此消息,然后再开始发布消息。

给定targetWindowmessageoptions窗口发布消息步骤 如下所示

  1. targetRealmtargetWindow领域

  2. incumbentSettings当前设置对象

  3. targetOriginoptions["targetOrigin"]。

  4. 如果targetOrigin 是单个 U+002F 斜杠字符 (/),则将targetOrigin 设置为incumbentSettings起源

  5. 否则,如果targetOrigin 不是单个 U+002A 星号字符 (*),则

    1. parsedURL 为对targetOrigin 运行URL 解析器 的结果。

    2. 如果parsedURL 为失败,则抛出"SyntaxError" DOMException

    3. targetOrigin 设置为parsedURL起源

  6. transferoptions["transfer"]。

  7. serializeWithTransferResultStructuredSerializeWithTransfer(message, transfer)。重新抛出任何异常。

  8. 在给定targetWindow发布的消息任务源排队一个全局任务,以运行以下步骤

    1. 如果targetOrigin 参数不是单个字面量 U+002A 星号字符 (*),并且targetWindow关联的 Document起源targetOrigin 不是相同起源,则返回。

    2. originincumbentSettings起源序列化

    3. source 为对应于incumbentSettings全局对象(一个Window 对象)的WindowProxy 对象。

    4. deserializeRecordStructuredDeserializeWithTransfer(serializeWithTransferResult, targetRealm)。

      如果这抛出一个异常,捕获它,触发一个事件,名为 messageerror,在 targetWindow 上,使用 MessageEvent,其 origin 属性初始化为 originsource 属性初始化为 source,然后返回。

    5. messageClonedeserializeRecord.[[Deserialized]]。

    6. newPorts 为一个新的 冻结数组,它包含 deserializeRecord.[[TransferredValues]] 中的所有 MessagePort 对象(如果有),并保持它们之间的相对顺序。

    7. 触发一个事件,名为 message,在 targetWindow 上,使用 MessageEvent,其 origin 属性初始化为 originsource 属性初始化为 sourcedata 属性初始化为 messageCloneports 属性初始化为 newPorts

Window 接口的 postMessage(message, options) 方法步骤是运行给定 thismessageoptionswindow post message 步骤

Window 接口的 postMessage(message, targetOrigin, transfer) 方法步骤是运行给定 thismessage 和 «[ "targetOrigin" → targetOrigin, "transfer" → transfer ]» 的 window post message 步骤

9.4 通道消息传递

Channel_Messaging_API

所有当前引擎都支持。

Firefox41+Safari5+Chrome2+
Opera10.6+Edge79+
Edge(传统)12+Internet Explorer10+
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android11+

Channel_Messaging_API/Using_channel_messaging

所有当前引擎都支持。

Firefox41+Safari5+Chrome2+
Opera10.6+Edge79+
Edge(传统)12+Internet Explorer10+
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android11+

9.4.1 简介

本节是非规范性的。

为了使独立的代码片段(例如,在不同的 浏览上下文中运行)能够直接通信,作者可以使用 通道消息传递

此机制中的通信通道实现为双向管道,每端都有一个端口。在一个端口发送的消息将被传递到另一个端口,反之亦然。消息作为 DOM 事件传递,不会中断或阻塞正在运行的 任务

要创建连接(两个“纠缠”的端口),调用 MessageChannel() 构造函数

var channel = new MessageChannel();

其中一个端口作为本地端口保留,另一个端口发送到远程代码,例如,使用 postMessage()

otherWindow.postMessage('hello', 'https://example.com', [channel.port2]);

要发送消息,使用端口上的 postMessage() 方法

channel.port1.postMessage('hello');

要接收消息,监听 message 事件

channel.port1.onmessage = handleMessage;
function handleMessage(event) {
  // message is in event.data
  // ...
}

通过端口发送的数据可以是结构化数据;例如,这里在一个 MessagePort 上传递字符串数组

port1.postMessage(['hello', 'world']);
9.4.1.1 例子

本节是非规范性的。

在本例中,两个 JavaScript 库使用 MessagePort 连接到彼此。这使得这些库稍后可以托管在不同的框架中,或在 Worker 对象中,而无需对 API 进行任何更改。

<script src="contacts.js"></script> <!-- exposes a contacts object -->
<script src="compose-mail.js"></script> <!-- exposes a composer object -->
<script>
 var channel = new MessageChannel();
 composer.addContactsProvider(channel.port1);
 contacts.registerConsumer(channel.port2);
</script>

"addContactsProvider()" 函数的实现可能如下所示

function addContactsProvider(port) {
  port.onmessage = function (event) {
    switch (event.data.messageType) {
      case 'search-result': handleSearchResult(event.data.results); break;
      case 'search-done': handleSearchDone(); break;
      case 'search-error': handleSearchError(event.data.message); break;
      // ...
    }
  };
};

或者,它也可以按如下方式实现

function addContactsProvider(port) {
  port.addEventListener('message', function (event) {
    if (event.data.messageType == 'search-result')
      handleSearchResult(event.data.results);
  });
  port.addEventListener('message', function (event) {
    if (event.data.messageType == 'search-done')
      handleSearchDone();
  });
  port.addEventListener('message', function (event) {
    if (event.data.messageType == 'search-error')
      handleSearchError(event.data.message);
  });
  // ...
  port.start();
};

关键区别在于,当使用 addEventListener() 时,还必须调用 start() 方法。当使用 onmessage 时,隐式调用 start()

start() 方法(无论是显式调用还是隐式调用(通过设置 onmessage))启动消息流:最初暂停在消息端口发布的消息,这样在脚本有机会设置其处理程序之前,这些消息不会被丢弃。

9.4.1.2 端口作为 Web 上对象能力模型的基础

本节是非规范性的。

端口可以被视为一种向系统中其他参与者公开有限能力(从对象能力模型意义上讲)的方式。这可以是一个弱能力系统,其中端口仅仅用作特定原点内的便捷模型,也可以是一个强能力系统,其中端口由一个原点 provider 提供,作为另一个原点 consumer 影响 provider 中的更改或从 provider 获取信息的唯一机制。

例如,考虑一个社交网站在其中嵌入用户电子邮件联系人提供程序(一个地址簿网站,来自第二个原点)的一个 iframe,并在第二个 iframe 中嵌入一个游戏(来自第三个原点)。外部社交网站和第二个 iframe 中的游戏无法访问第一个 iframe 中的任何内容;它们只能一起

联系人提供程序可以使用这些方法,尤其是第三种方法,来提供一个 API,其他原点可以访问该 API 以操作用户的地址簿。例如,它可以响应消息“add-contact Guillaume Tell <[email protected]>”,通过添加给定的人员和电子邮件地址到用户的地址簿。

为了避免 Web 上的任何网站都能操作用户的联系人,联系人提供程序可能只允许某些受信任的网站(例如社交网站)这样做。

现在假设游戏想要添加一个联系人到用户的地址簿,并且社交网站愿意代表游戏这样做,本质上是“分享”联系人提供程序与社交网站之间的信任。它可以做到这一点有几种方法;最简单的方法是,它可以简单地代理游戏网站和联系人网站之间的消息。但是,这种解决方案有许多困难:它要求社交网站要么完全信任游戏网站不会滥用特权,要么要求社交网站验证每个请求以确保它不是它不想允许的请求(例如添加多个联系人、读取联系人或删除它们);如果有多个游戏同时尝试与联系人提供程序交互,它还需要一些额外的复杂性。

但是,使用消息通道和 MessagePort 对象,所有这些问题都可以消失。当游戏告诉社交网站它想要添加一个联系人时,社交网站可以请求联系人提供程序不是为它添加联系人,而是请求添加单个联系人的能力。然后,联系人提供程序创建一对 MessagePort 对象,并将其中一个对象发送回社交网站,社交网站将其转发到游戏。然后游戏和联系人提供程序之间建立了直接连接,并且联系人提供程序知道只接受一个“添加联系人”请求,而不是其他任何请求。换句话说,游戏被授予了添加单个联系人的能力。

9.4.1.3 端口作为抽象出服务实现的基础

本节是非规范性的。

继续上一节中的例子,尤其是联系人提供程序。虽然初始实现可能只是在服务 iframe 中使用了 XMLHttpRequest 对象,但服务的演变可能希望使用一个具有单个 WebSocket 连接的 共享工作线程

如果初始设计使用 MessagePort 对象来授予功能,甚至只是为了允许多个同时独立的会话,服务实现可以从 XMLHttpRequests-in-each-iframe 模型切换到共享 WebSocket 模型,而无需更改 API:服务提供者端的所有端口都可以转发到共享工作者,而不会对 API 用户造成任何影响。

9.4.2 消息通道

MessageChannel

所有当前引擎都支持。

Firefox41+Safari5+Chrome2+
Opera10.6+Edge79+
Edge(传统)12+Internet Explorer10+
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android11+
[Exposed=(Window,Worker)]
interface MessageChannel {
  constructor();

  readonly attribute MessagePort port1;
  readonly attribute MessagePort port2;
};
channel = new MessageChannel()

MessageChannel/MessageChannel

所有当前引擎都支持。

Firefox41+Safari5+Chrome2+
Opera10.6+Edge79+
Edge(传统)12+Internet Explorer10+
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android11+

返回一个新的 MessageChannel 对象,该对象包含两个新的 MessagePort 对象。

channel.port1

MessageChannel/port1

所有当前引擎都支持。

Firefox41+Safari5+Chrome2+
Opera10.6+Edge79+
Edge(传统)12+Internet Explorer10+
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android11+

返回第一个 MessagePort 对象。

channel.port2

MessageChannel/port2

所有当前引擎都支持。

Firefox41+Safari5+Chrome2+
Opera10.6+Edge79+
Edge(传统)12+Internet Explorer10+
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android11+

返回第二个 MessagePort 对象。

一个 MessageChannel 对象具有关联的 端口 1 和关联的 端口 2,它们都是 MessagePort 对象。

new MessageChannel() 构造函数的步骤如下:

  1. this端口 1 设置为在 this相关领域 中的 new MessagePort

  2. this端口 2 设置为在 this相关领域 中的 new MessagePort

  3. 纠缠 this端口 1this端口 2

port1 获取器步骤是返回 this端口 1

port2 获取器步骤是返回 this端口 2

9.4.3 消息端口

MessagePort

所有当前引擎都支持。

Firefox41+Safari5+Chrome2+
Opera10.6+Edge79+
Edge(传统)12+Internet Explorer10+
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android11+

每个通道有两个消息端口。通过一个端口发送的数据由另一个端口接收,反之亦然。

[Exposed=(Window,Worker,AudioWorklet), Transferable]
interface MessagePort : EventTarget {
  undefined postMessage(any message, sequence<object> transfer);
  undefined postMessage(any message, optional StructuredSerializeOptions options = {});
  undefined start();
  undefined close();

  // event handlers
  attribute EventHandler onmessage;
  attribute EventHandler onmessageerror;
  attribute EventHandler onclose;
};

dictionary StructuredSerializeOptions {
  sequence<object> transfer = [];
};
port.postMessage(message [, transfer])

MessagePort/postMessage

所有当前引擎都支持。

Firefox41+Safari5+Chrome2+
Opera10.6+Edge79+
Edge(传统)12+Internet Explorer10+
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android11+
port.postMessage(message [, { transfer }])

通过通道发布消息。列在 transfer 中的对象会被传输,而不仅仅是克隆,这意味着它们在发送端不再可用。

如果 transfer 包含重复对象或 port,或者如果 message 无法克隆,则抛出 "DataCloneError" DOMException

port.start()

MessagePort/start

所有当前引擎都支持。

Firefox41+Safari5+Chrome2+
Opera10.6+Edge79+
Edge(传统)12+Internet Explorer10+
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android11+

开始调度在端口上接收到的消息。

port.close()

MessagePort/close

所有当前引擎都支持。

Firefox41+Safari5+Chrome2+
Opera10.6+Edge79+
Edge(传统)12+Internet Explorer10+
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android11+

断开端口连接,使其不再处于活动状态。

每个 MessagePort 对象可以与另一个对象纠缠(对称关系)。每个 MessagePort 对象还具有一个名为 任务源端口消息队列,最初为空。端口消息队列可以启用或禁用,并且最初为禁用状态。启用后,端口永远无法再次禁用(虽然队列中的消息可以移动到另一个队列或完全删除,这具有相同的效果)。一个 MessagePort 还具有一个 已发货 标志,最初必须为 false。

当端口的 端口消息队列 启用时,事件循环 必须将其用作其 任务源 之一。当端口的 相关全局对象Window 时,在它的 端口消息队列 上排队的 任务 必须与端口的 相关全局对象关联的 Document 相关联。

如果文档处于 完全活动 状态,但所有事件监听器都在不处于 完全活动 状态的文档的上下文中创建,则除非文档再次处于 完全活动 状态,否则不会收到消息。

每个 事件循环 都具有一个名为 未发货端口消息队列任务源。这是一个虚拟 任务源:它必须表现得好像它包含了每个 端口消息队列 中的 任务,这些队列属于 MessagePort,其 已发货 标志为 false,其 端口消息队列 已启用,并且其 相关代理事件循环 是该 事件循环,顺序与它们添加到各自 任务源 中的顺序相同。当 任务 将从 未发货端口消息队列 中移除时,它必须改为从其 端口消息队列 中移除。

MessagePort已发货 标志为 false 时,其 端口消息队列 必须为 事件循环 的目的而被忽略。(未发货端口消息队列 替代使用。)

当端口、它的孪生兄弟或它被克隆的对象被传输或已被传输时,已发货 标志被设置为 true。当 MessagePort已发货 标志为 true 时,其 端口消息队列 充当一流的 任务源,不受任何 未发货端口消息队列 的影响。

当用户代理要 纠缠 两个 MessagePort 对象时,它必须运行以下步骤:

  1. 如果其中一个端口已经纠缠,则解开它与其纠缠的端口。

    如果这两个先前纠缠的端口是 MessageChannel 对象的两个端口,则该 MessageChannel 对象不再表示实际的通道:该对象中的两个端口不再纠缠。

  2. 将要纠缠的两个端口关联起来,使它们形成一个新通道的两个部分。(没有 MessageChannel 对象表示此通道。)

    经过此步骤的两个端口 AB 现在被称为纠缠;一个与另一个纠缠,反之亦然。

    虽然本规范将此过程描述为瞬时的,但实现更有可能通过消息传递来实现它。与所有算法一样,关键是“仅仅”最终结果在黑盒意义上与规范不可区分。

给定一个 MessagePort initiatorPort,它启动解开纠缠,解开纠缠 步骤如下:

  1. otherPort 为与 initiatorPort 纠缠的 MessagePort

  2. 断言otherPort 存在。

  3. 解开 initiatorPortotherPort 的纠缠,使它们不再纠缠或彼此关联。

  4. otherPort触发一个事件,名为 close

即使端口未明确关闭,close 事件也会被触发。触发此事件的案例包括:

我们只在 otherPort 上触发事件,因为 initiatorPort 明确触发了关闭,它的 Document 不再存在,或者它已经被垃圾回收,如上所述。


MessagePort 对象是 可传输对象。它们在给定 valuedataHolder 时的 传输步骤 如下:

  1. value已发货 标志设置为 true。

  2. dataHolder.[[PortMessageQueue]] 设置为 value端口消息队列

  3. 如果 value 与另一个端口 remotePort 纠缠在一起,则

    1. remotePort已发货 标志设置为 true。

    2. dataHolder.[[RemotePort]] 设置为 remotePort

  4. 否则,将 dataHolder.[[RemotePort]] 设置为 null。

它们在给定 dataHoldervalue 的情况下,其 传输接收步骤

  1. value已发货 标志设置为 true。

  2. 将所有将在 dataHolder.[[PortMessageQueue]] 中触发 message 事件的 任务 移动到 value端口消息队列(如果有),将 value端口消息队列 保持在初始禁用状态,并且,如果 value相关全局对象 是一个 Window,则将移动的 任务value相关全局对象关联的 Document 关联起来。

  3. 如果 dataHolder.[[RemotePort]] 不为 null,则将 dataHolder.[[RemotePort]] 和 value 纠缠。(这将使 dataHolder.[[RemotePort]] 与最初被传输的端口解缠。)


给定 sourcePorttargetPortmessageoptions端口消息发布步骤 如下

  1. transferoptions["transfer"]。

  2. 如果 transfer 包含 sourcePort,则抛出一个 "DataCloneError" DOMException

  3. doomed 为 false。

  4. 如果 targetPort 不为 null 且 transfer 包含 targetPort,则将 doomed 设置为 true,并可选地向开发者控制台报告目标端口已发布到自身,导致通信通道丢失。

  5. serializeWithTransferResultStructuredSerializeWithTransfer(message, transfer)。重新抛出任何异常。

  6. 如果 targetPort 为 null 或 doomed 为 true,则返回。

  7. targetPort端口消息队列 添加一个运行以下步骤的 任务

    1. finalTargetPort 为现在在其中找到任务的 MessagePort端口消息队列

      如果 targetPort 本身已被传输,因此所有任务都随之移动,则这可能与 targetPort 不同。

    2. targetRealmfinalTargetPort相关领域

    3. deserializeRecordStructuredDeserializeWithTransfer(serializeWithTransferResult, targetRealm)。

      如果这抛出一个异常,则捕获它,使用 MessageEventfinalTargetPort触发一个名为 messageerror 的事件,然后返回。

    4. messageClonedeserializeRecord.[[Deserialized]]。

    5. newPorts 为一个新的 冻结数组,它包含 deserializeRecord.[[TransferredValues]] 中的所有 MessagePort 对象(如果有),并保持其相对顺序。

    6. 使用 MessageEventfinalTargetPort触发一个名为 message 的事件,其 data 属性初始化为 messageCloneports 属性初始化为 newPorts

给定 postMessage(message, options) 方法的步骤为

  1. targetPort 为与 this 纠缠的端口(如果有);否则令它为 null。

  2. 运行 端口消息发布步骤,提供 thistargetPortmessageoptions

给定 postMessage(message, transfer) 方法的步骤为

  1. targetPort 为与 this 纠缠的端口(如果有);否则令它为 null。

  2. options 为 «[ "transfer" → transfer ]»。

  3. 运行 端口消息发布步骤,提供 thistargetPortmessageoptions


给定 start() 方法的步骤为:启用 this端口消息队列(如果它尚未启用)。


给定 close() 方法的步骤为

  1. this[[Detached]] 内部插槽值设置为 true。

  2. 如果 this 纠缠,则将其 解缠


以下是所有实现 MessagePort 接口的对象必须支持的 事件处理程序(及其相应的 事件处理程序事件类型),作为 事件处理程序 IDL 属性

事件处理程序 事件处理程序事件类型
onmessage

MessagePort/message_event

所有当前引擎都支持。

Firefox41+Safari5+Chrome2+
Opera10.6+Edge79+
Edge(传统)12+Internet Explorer10+
Firefox Android?Safari iOS?Chrome Android?WebView Android37+Samsung Internet?Opera Android11.5+
message
onmessageerror

MessagePort/messageerror_event

所有当前引擎都支持。

Firefox57+Safari16.4+Chrome60+
Opera?Edge79+
Edge (Legacy)18Internet ExplorerNo
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android47+
messageerror
onclose close

第一次设置 MessagePort 对象的 onmessage IDL 属性时,必须启用端口的 端口消息队列,就好像已调用 start() 方法一样。

9.4.4 端口和垃圾回收

MessagePort 对象 o 被垃圾回收时,如果 o 纠缠,则用户代理必须 解缠 o

MessagePort 对象 o 纠缠且注册了 messagemessageerror 事件监听器时,用户代理必须表现得好像 o 的纠缠的 MessagePort 对象对 o 具有强引用。

此外,如果在 任务队列 中存在一个由要在该 MessagePort 对象上分派的 任务 引用的事件,或者 MessagePort 对象的 端口消息队列 已启用且不为空,则 MessagePort 对象不得被垃圾回收。

因此,在给定一个事件监听器的情况下,可以接收一个消息端口,然后将其遗忘,只要该事件监听器可以接收消息,该通道就会保持。

当然,如果这种情况发生在通道的双方,那么两个端口都可能被垃圾回收,因为它们将无法从实时代码中访问,尽管它们彼此具有强引用。但是,如果一个消息端口有一个待处理的消息,则它不会被垃圾回收。

强烈建议作者显式关闭 MessagePort 对象以将其解缠,以便可以回收其资源。创建许多 MessagePort 对象并将其丢弃而不关闭它们会导致高瞬态内存使用率,因为垃圾回收不一定立即执行,特别是对于 MessagePort,其中垃圾回收可能涉及跨进程协调。

9.5 广播到其他浏览上下文

BroadcastChannel

所有当前引擎都支持。

Firefox38+Safari15.4+Chrome54+
Opera?Edge79+
Edge (Legacy)?Internet ExplorerNo
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?

广播通道 API

所有当前引擎都支持。

Firefox38+Safari15.4+Chrome54+
Opera?Edge79+
Edge (Legacy)?Internet ExplorerNo
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?

在同一个用户代理中,由同一用户打开的同一个 上的页面,但在不同的无关 浏览上下文 中,有时需要互相发送通知,例如“嘿,用户在这里登录了,请重新检查您的凭据”。

对于复杂的用例,例如管理共享状态的锁定、管理服务器和多个本地客户端之间的资源同步、与远程主机共享 WebSocket 连接,等等,共享工作线程 是最合适的解决方案。

但是,对于简单的用例,使用共享工作线程会造成不必要的开销,作者可以使用本节中描述的简单通道广播机制。

[Exposed=(Window,Worker)]
interface BroadcastChannel : EventTarget {
  constructor(DOMString name);

  readonly attribute DOMString name;
  undefined postMessage(any message);
  undefined close();
  attribute EventHandler onmessage;
  attribute EventHandler onmessageerror;
};
broadcastChannel = new BroadcastChannel(name)

BroadcastChannel/BroadcastChannel

所有当前引擎都支持。

Firefox38+Safari15.4+Chrome54+
Opera?Edge79+
Edge (Legacy)?Internet ExplorerNo
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?

返回一个新的 BroadcastChannel 对象,通过该对象可以发送和接收给定通道名称的消息。

broadcastChannel.name

BroadcastChannel/name

所有当前引擎都支持。

Firefox38+Safari15.4+Chrome54+
Opera?Edge79+
Edge (Legacy)?Internet ExplorerNo
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?

返回通道名称(与构造函数中传递的名称相同)。

broadcastChannel.postMessage(message)

BroadcastChannel/postMessage

所有当前引擎都支持。

Firefox38+Safari15.4+Chrome54+
Opera?Edge79+
Edge (Legacy)?Internet ExplorerNo
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?

将给定的消息发送到为该通道设置的其它 BroadcastChannel 对象。消息可以是结构化对象,例如嵌套对象和数组。

broadcastChannel.close()

BroadcastChannel/close

所有当前引擎都支持。

Firefox38+Safari15.4+Chrome54+
Opera?Edge79+
Edge (Legacy)?Internet ExplorerNo
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?

关闭 BroadcastChannel 对象,使其可以被垃圾回收。

一个 BroadcastChannel 对象具有一个 通道名称 和一个 关闭标志

构造函数 new BroadcastChannel(name) 的步骤是

  1. this通道名称 设置为 name

  2. this关闭标志 设置为 false。

获取器 name 的步骤是返回 this通道名称

BroadcastChannel 对象的 相关全局对象 为以下两种情况时,则称该对象为 可进行消息传递的

方法 postMessage(message) 的步骤是

  1. 如果 this 不是 可进行消息传递的,则返回。

  2. 如果 this关闭标志 为 true,则抛出 "InvalidStateError" DOMException

  3. serialized 为使用 StructuredSerialize(message) 获得的值。重新抛出任何异常。

  4. sourceOriginthis相关设置对象

  5. sourceStorageKey 为使用 为非存储目的获取存储密钥 方法,并使用 this相关设置对象 获得的值。

  6. destinations 为匹配以下条件的 BroadcastChannel 对象列表:

  7. destinations 中移除 source

  8. destinations 进行排序,使所有 相关代理 相同的 BroadcastChannel 对象按创建顺序排序,最早的排在最前面。(这并没有定义一个完整的排序。在这个约束下,用户代理可以以任何 实现定义 的方式对列表进行排序。)

  9. 对于 destinations 中的每一个 destination在全局任务队列 上,使用给定 destination相关全局对象DOM 操作任务源,执行以下步骤:

    1. 如果 destination关闭标志 为 true,则中止这些步骤。

    2. targetRealmdestination相关领域

    3. data 为使用 StructuredDeserialize(serialized, targetRealm) 获得的值。

      如果这会抛出异常,则捕获该异常,destination 上触发名为 messageerror 的事件,使用 MessageEvent,将 origin 属性初始化为 sourceOrigin序列化,然后中止这些步骤。

    4. destination 上触发名为 message 的事件,使用 MessageEvent,将 data 属性初始化为 data,将 origin 属性初始化为 sourceOrigin序列化

BroadcastChannel 对象的 关闭标志 为 false 且注册了 messagemessageerror 事件的事件监听器时,BroadcastChannel 对象的 相关全局对象 必须对 BroadcastChannel 对象本身有一个强引用。

方法 close() 的步骤是将 this关闭标志 设置为 true。

强烈建议作者在不再需要 BroadcastChannel 对象时显式关闭它们,以便它们可以被垃圾回收。创建许多 BroadcastChannel 对象并丢弃它们,同时保留它们的事件监听器而不关闭它们,会导致明显的内存泄漏,因为这些对象将一直存活,直到它们拥有一个事件监听器(或直到它们的页面或工作线程被关闭)。


以下列出了所有实现 BroadcastChannel 接口的对象必须支持的 事件处理程序(及其对应的 事件处理程序事件类型),作为 事件处理程序 IDL 属性

事件处理程序 事件处理程序事件类型
onmessage

BroadcastChannel/message_event

所有当前引擎都支持。

Firefox38+Safari15.4+Chrome54+
Opera?Edge79+
Edge (Legacy)?Internet ExplorerNo
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?
message
onmessageerror

BroadcastChannel/messageerror_event

所有当前引擎都支持。

Firefox57+Safari15.4+Chrome60+
Opera?Edge79+
Edge (Legacy)?Internet ExplorerNo
Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android47+
messageerror

假设一个页面想要知道用户何时注销,即使用户是在同一网站的另一个标签页上注销的

var authChannel = new BroadcastChannel('auth');
authChannel.onmessage = function (event) {
  if (event.data == 'logout')
    showLogout();
}

function logoutRequested() {
  // called when the user asks us to log them out
  doLogout();
  showLogout();
  authChannel.postMessage('logout');
}

function doLogout() {
  // actually log the user out (e.g. clearing cookies)
  // ...
}

function showLogout() {
  // update the UI to indicate we're logged out
  // ...
}