Firefox 38 中的 BroadcastChannel API

最近,BroadcastChannel API 已在 Firefox 38 中发布。此 API 可用于在具有相同用户代理和来源的浏览器上下文之间进行简单消息传递。此 API 向 Windows 和 Workers 公开,并允许在 iframe、浏览器标签和 worker 线程之间进行通信。

BroadcastChannel API 的目的是提供一个 API,以促进 Web 应用程序中浏览器上下文之间简单消息的通信。例如,当用户登录应用程序中的某个页面时,它可以将用户的信息更新到所有其他上下文(例如,标签或单独的窗口),或者如果用户将照片上传到浏览器窗口,则图像可以传递到应用程序中的其他打开页面。此 API 不打算处理复杂的运算,例如共享资源锁定或将多个客户端与服务器同步。在这种情况下,共享 worker 更合适。

API 概述

可以使用以下代码创建 BroadcastChannel 对象

var myChannel = new BroadcastChannel("channelname");

channelname 区分大小写。BroadcastChannel 实例包含两个方法:postMessage 和 close。postMessage 方法用于向通道发送消息。消息内容可以是任何对象,包括复杂的对象,例如图像、数组和对象数组。发布消息时,将通知具有相同来源、用户代理和具有相同 channelname 的 BroadcastChannel 的所有浏览器上下文该消息。例如,以下代码发布了一个简单的字符串

myChannel.postMessage("Simple String");

通过使用 BroadcastChannel 实例的 onmessage 事件处理程序,任何打开了通道的页面都可以处理分派的 message。

myChannel.onmessage = function(event) {
    console.log(event.data);
}

请注意,事件的 data 属性包含消息的数据。事件的其他属性也可能有用。例如,可以使用 event.origin 属性检查 Web 应用程序来源,或者 event.target.name 属性中提供 channel name。

close 方法用于关闭特定浏览器上下文的通道,可以使用以下方法调用:

broadCastChannel.close();

Blob 消息发布

虽然发布字符串和其他原始类型非常简单,但 BroadcastChannel API 也支持更复杂的对象。例如,要将图像从一个上下文发布到另一个上下文,可以使用以下代码。

var broadcastExample = {};
broadcastExample.setupChannel = function() {
    if ("BroadcastChannel" in window) {
        if (typeof broadcastExample.channel === 'undefined' || 
           !broadcastExample.channel) {
            //create channel
            broadcastExample.channel = new BroadcastChannel("foo");
        }
    }
}
broadcastExample.pMessage = function() {
    broadcastExample.setupChannel();
    //get image element
    var img = document.getElementById("ffimg");
    //create canvas with image 
    var canvas = document.createElement("canvas");
    canvas.width = img.width;
    canvas.height = img.height;
    var ctx = canvas.getContext("2d");
    ctx.drawImage(img, 0, 0);
    //get blob of canvas
    canvas.toBlob(function(blob) {
        //broadcast blob
        broadcastExample.channel.postMessage(blob);
    });
}

上面的代码包含两个函数,并代表 BroadcastChannel 客户端的发件方。setupChannel 方法只是打开 Broadcast Channel,而 pMessage 方法从页面检索图像元素并将其转换为 Blob。最后,Blob 发布到 BroadcastChannel。接收方页面需要已经监听 BroadcastChannel 才能接收消息,并且可以类似于以下内容进行编码。

var broadcastFrame = {};

//setup broadcast channel
broadcastFrame.setup = function() {
    if ("BroadcastChannel" in window) {
        if (typeof broadcastFrame.channel === 'undefined' || !broadcastFrame.channel) {
            broadcastFrame.channel = new BroadcastChannel("foo");
        }
        //function to process broadcast messages
        function func(e) {
            if (e.data instanceof Blob) {
                //if message is a blob create a new image element and add to page
                var blob = e.data;
                var newImg = document.createElement("img"),
                    url = URL.createObjectURL(blob);
                newImg.onload = function() {
                    // no longer need to read the blob so it's revoked
                    URL.revokeObjectURL(url);
                };
                newImg.src = url;
                var content = document.getElementById("content");
                content.innerHTML = "";
                content.appendChild(newImg);
            }
        };
        //set broadcast channel message handler
        broadcastFrame.channel.onmessage = func;
    }
}
window.onload = broadcastFrame.setup();

此代码从广播消息获取 blob 并创建一个图像元素,该元素将添加到接收方页面。

Workers 和 BroadcastChannel 消息

BroadcastChannel API 也适用于专用和共享的 workers。作为一个简单的示例,假设启动了一个 worker,并且当通过主脚本向 worker 发送消息时,worker 然后将消息广播到 Broadcast Channel。

var broadcastExample = {};
broadcastExample.setupChannel = function() {
    if ("BroadcastChannel" in window) {
        if (typeof broadcastExample.channel === 'undefined' || 
           !broadcastExample.channel) {
            //create channel
            broadcastExample.channel = new BroadcastChannel("foo");
        }
    }
}

function callWorker() {
    broadcastExample.setupChannel();
    //verify compat
    if (!!window.Worker) {
        var myWorker = new Worker("worker.js");
        //ping worker to post to Broadcast Channel
        myWorker.postMessage("Broadcast from Worker");
    }
}


//Worker.js
onmessage = function(e) {
    //Broadcast Channel Message with Worker
    if ("BroadcastChannel" in this) {
        var workerChannel = new BroadcastChannel("foo");
        workerChannel.postMessage("BroadcastChannel Message Sent from Worker");
        workerChannel.close();
    }
}

可以通过与本文前面部分中描述的相同方式在主页面中处理该消息。
workerbroadcast
使用 Worker 将消息广播到 iframe

更多信息

除了演示如何在 iframe 中使用外,本文中的代码还提供另一个示例,该示例介绍了如何在 github 上使用该代码。有关 BroadcastChannel API 的更多信息,请参阅 规范bugzilla 条目MDN。该 API 的代码和测试页面位于 gecko 源代码 中。


8 条评论

  1. Hubert SABLONNIERE

    此 BroadcastChannel 规范似乎很有趣……我想了解更多信息。它来自哪里?WHATWG 中谁编辑它?其他浏览器供应商的意见是什么?

    我在 Paris-Web 上发表了关于跨窗口/标签消息传递和多屏幕体验的演讲,并且感谢这篇文章,我已经开始尝试使用该 API。

    http://www.paris-web.fr/2014/conferences/les-recettes-du-web-multi-ecran.php

    2015 年 2 月 3 日 下午 1:05

  2. Jason Weathersby

    请查看:WHATWG 常见问题解答以获取规范编辑器。目前,我认为只有 Mozilla 实现了此 API。感谢发布演讲的链接。

    2015 年 2 月 4 日 上午 8:24

  3. Luke

    听起来很有趣,但这与 ServiceWorker 相比有什么优势?从草案规范中可以看出:“服务工作者通过其与事件的关系,而不是通过文档来启动和保持活动状态。”
    “用户代理可能会在没有附加文档的情况下启动服务工作者,并且用户代理几乎可以在任何时候将其终止”(https://slightlyoff.github.io/ServiceWorker/spec/service_worker/)

    “它们还将允许访问推送通知和后台同步 API”
    https://mdn.org.cn/en-US/docs/Web/API/ServiceWorker_API

    我刚开始研究 ServiceWorker 的可能性,并且看到 Chrome 40 已经可以使用了。令人遗憾的是,Firefox 还没有在标准 Firefox 中可以使用它,尤其是在我理解正确的情况下,这实际上将是 FirefoxOS 中唯一可以使用推送通知的方式吗?

    2015 年 2 月 4 日 下午 9:36

  4. Hubert SABLONNIERE

    您好,Luke

    您说
    “我看到 Chrome 40 已经可以使用了。令人遗憾的是,Firefox 还没有在标准 Firefox 中可以使用它,尤其是在我理解正确的情况下,这实际上将是 FirefoxOS 中唯一可以使用推送通知的方式吗?”。

    实际上并没有那么简单……根本不简单!

    首先,Chrome 在 40 中发布了规范的某些部分,但它不是 100% 兼容的。它正在开发中,就像规范一样。如果您尝试使用它,您会发现某些细节仍然缺失或仍然引用以前的 API 设计。

    Jake Archibald 跟踪了此处实现的一些部分
    https://jakearchibald.github.io/isserviceworkerready/

    关于 Firefox 的状态和对 SW 的参与,我会说它很好。Mozilla 已经像 Google 和其他人一样长期致力于规范。某些部分已在 Nightly 中实现,并且将在未来的版本中发布。

    SW 中的推送通知是单独的规范,将在之后发布。现在要求稳定的实现还为时过早。

    您问
    “但这与 ServiceWorker 相比有什么优势”。

    就像我在第一条评论中所说,我在 FF、Chrome、Opera、IE 和 Safari 中进行了许多关于跨标签/窗口通信的实验。我使用带有 SharedWorker、MessageChannel、iframe 和弹出窗口的 postMessage()。我还尝试使用“StorageEvent 技巧”。

    以从标签向源代码的某些特定标签发送消息为例。

    使用 SharedWorker,所有标签都必须与其打开通信,并且 SharedWorker 脚本必须跟踪所有连接的标签以“广播”消息。如果您不想让某些标签接收消息,则必须在 SharedWorker 脚本或所有标签消息侦听器中实现一些逻辑。这可能会很麻烦。

    使用 ServiceWorker,您无需跟踪连接的标签。您可以在 ServiceWorker 脚本中遍历连接的标签,并使用其位置定位要使用的页面(Chrome 中尚未提供)。但是您仍然必须实现一个“中间人”脚本以允许您的页面进行通信。并且调试起来并不容易……

    使用这些技术,标签通过全局 window.onmessage 接收消息,并且所有内容都混合在一起。来自其他上下文的所有消息都混合在一起。您可以使用一个或多个 MessageChannel 来分离不同的类型或用法,但是同样,支持和易用性并不理想。

    我认为 BroadcastChannel 在“一个标签到某些特定标签的消息传递”方面具有的优势是
    * 您无需编写“中间人”脚本来传输消息。标签可以直接与其他标签进行通信。编写和调试起来更容易。
    * 您可以使用简单的消息侦听器,而无需“if 语句地狱”进行分离,因为您只需监听命名通道。

    我认为 BroadcastChannel 就像 Socket.IO 中的消息室或命名空间。我在多用户目的中使用它们,这是一个很好的比喻。

    所有这些内容都不容易在帖子回复中解释 :-(
    如果您想了解更多信息,请告诉我。

    用于跨标签通信的 StorageEvent
    http://truongtx.me/2014/06/16/cross-tab-communication-using-html5-dom-storage/

    2015年2月5日 上午8:32

  5. Nick Desaulniers

    这个API很重要,因为它不再需要维护对要与其通信的iframe或worker的引用。它们可以通过构造BroadcastChannel来简单地“订阅”特定频道,并进行全双工(双向)通信(“发布”)。从理论上讲,你甚至可以在同一个JS上下文中使用它来实现具有自己实例的BroadcastChannel订阅同一个频道的对象之间的消息传递。这很有用,因为在浏览器中实现来自Node.js的EventEmitter接口并不是最直接的事情,而且只有DOM元素可以是EventTargets。我以前从未实现过它们,但我不知道为什么这不能导致在JavaScript中实现Actors。

    2015年2月5日 下午12:27

  6. 匿名

    这是否是指以下规范,但使用了重命名/“供应商前缀”的BOM对象?
    http://www.w3.org/TR/webmessaging/#channel-messaging

    2015年2月5日 下午1:50

    1. jperrier@mozilla.com

      不,这是不同的东西。广播频道API在HTML规范中定义:https://html.whatwg.com.cn/multipage/comms.html#broadcasting-to-other-browsing-contexts

      2015年2月6日 上午1:09

  7. Jason Weathersby

    消息频道很快就会发布,请查看这个bug,它使用这个规范.

    2015年2月6日 上午5:49

本文的评论已关闭。