【问题标题】:Communication between tabs or windows选项卡或窗口之间的通信
【发布时间】:2015-03-29 15:08:37
【问题描述】:

我正在寻找一种方法,如何在浏览器中的多个选项卡或窗口之间进行通信(在同一个域上,而不是 CORS)而不留下任何痕迹。有几种解决方案:

  1. using the window object
  2. postMessage
  3. cookies
  4. localStorage

第一个可能是最糟糕的解决方案 - 您需要从当前窗口打开一个窗口,然后您只能在保持窗口打开的情况下进行通信。如果您在任何窗口中重新加载页面,您很可能会失去通信。

第二种方法,使用 postMessage,可能会启用跨域通信,但它遇到与第一种方法相同的问题。您需要维护一个窗口对象。

第三种方式,使用cookie,在浏览器中存储一些数据,这可以有效地看起来像向同一域中的所有窗口发送消息,但问题是你永远无法知道是否所有选项卡都读取了“消息”在清理之前已经或没有。您必须实施某种超时来定期读取 cookie。此外,您还受到最大 cookie 长度的限制,即 4 KB。

第四个方案,使用localStorage,似乎克服了cookies的限制,甚至可以使用事件监听。接受的答案中描述了如何使用它。

在 2018 年,公认的答案仍然有效,但现代浏览器有一个更新的解决方案,即使用 BroadcastChannel。有关如何使用 BroadcastChannel 在选项卡之间轻松传输消息的简单示例,请参见其他答案。

【问题讨论】:

标签: javascript html browser broadcast-channel


【解决方案1】:

为此,您最好使用 BroadcastChannel。请参阅下面的其他答案。但是,如果您仍然喜欢使用 localstorage 进行选项卡之间的通信,请这样做:

为了在标签向其他标签发送消息时得到通知,您只需绑定“存储”事件。在所有选项卡中,执行以下操作:

$(window).on('storage', message_receive);

每当您在任何其他选项卡中设置 localStorage 的任何值时,都会调用函数message_receive。事件侦听器还包含新设置为 localStorage 的数据,因此您甚至不需要解析 localStorage 对象本身。这非常方便,因为您可以在设置后立即重置该值,以有效清理任何痕迹。以下是消息传递函数:

// use local storage for messaging. Set message in local storage and clear it right away
// This is a safe way how to communicate with other tabs while not leaving any traces
//
function message_broadcast(message)
{
    localStorage.setItem('message',JSON.stringify(message));
    localStorage.removeItem('message');
}


// receive message
//
function message_receive(ev)
{
    if (ev.originalEvent.key!='message') return; // ignore other keys
    var message=JSON.parse(ev.originalEvent.newValue);
    if (!message) return; // ignore empty msg or msg reset

    // here you act on messages.
    // you can send objects like { 'command': 'doit', 'data': 'abcd' }
    if (message.command == 'doit') alert(message.data);

    // etc.
}

所以现在一旦你的标签绑定到 onstorage 事件,并且你已经实现了这两个功能,你可以简单地将消息广播到其他标签调用,例如:

message_broadcast({'command':'reset'})

请记住,两次发送完全相同的消息只会传播一次,因此如果您需要重复消息,请为其添加一些唯一标识符,例如

message_broadcast({'command':'reset', 'uid': (new Date).getTime()+Math.random()})

还请记住,广播消息的当前选项卡实际上并没有收到它,只有同一域中的其他选项卡或窗口。

您可能会问,如果用户在 setItem() 调用之后在 removeItem() 之前加载不同的网页或关闭他的选项卡,会发生什么情况。好吧,根据我自己的测试,浏览器会暂停卸载,直到整个函数 message_broadcast() 完成。我测试了在那里放了一些很长的 for() 循环,它仍然等待循环完成后再关闭。如果用户只是在两者之间杀死标签,那么浏览器将没有足够的时间将消息保存到磁盘,因此这种方法在我看来是一种安全的方式来发送消息而不会留下任何痕迹。

【讨论】:

  • 在调用 JSON.parse() 之前可以忽略 remove 事件吗?
  • 请记住事件数据限制,包括预先存在的 localStorage 数据。将存储事件仅用于消息传递而不是发送可能会更好/更安全。就像当您收到一张明信片告诉您在邮局取件时...此外,本地存储进入硬盘驱动器,因此它可能会留下无意的缓存并影响日志,这是考虑使用不同传输机制的另一个原因实际数据。
  • 不久前我做了一些相关的事情:danml.com/js/localstorageevents.js,那个有一个事件发射器库和“本地回显”,这样你就可以在任何地方使用 EE。
  • Safari 不支持 BroadcastChannel - caniuse.com/#feat=broadcastchannel
  • 另外我认为值得注意的是,使用localStorage.setItem('message',JSON.stringify(message));localStorage.removeItem('message'); 会触发两个storage 事件,因为本地存储会有两个变化——添加然后删除一些东西。
【解决方案2】:

有一个专门用于此目的的现代 API - Broadcast Channel

这很简单:

var bc = new BroadcastChannel('test_channel');

bc.postMessage('This is a test message.'); /* send */

bc.onmessage = function (ev) { console.log(ev); } /* receive */

消息不需要只是一个 DOMString。可以发送任何类型的对象。

可能,除了 API 清洁之外,这是这个 API 的主要好处 - 没有对象字符串化。

目前supported 仅在 Chrome 和 Firefox 中,但您可以找到使用 localStorage 的polyfill

【讨论】:

  • 等等,你怎么知道消息来自哪里?它会忽略来自同一标签的消息吗?
  • @zehelvion:发件人不会收到它,根据例如这个nice overview。此外,您可以在消息中添加任何您想要的内容,包括。发件人的一些 ID(如果需要)。
  • 这里有一个很好的项目将这个特性封装在一个跨浏览器库中:github.com/pubkey/broadcast-channel
  • “可以发送任何类型的对象”是不正确的。在 Firefox v78 中,我尝试发送 window 对象,并收到错误“无法克隆对象”。所以你不能用它来传递窗口引用。 (我希望能够做到这一点,以便引导直接的一对一窗口到窗口的通信)。事实上,根据规范,只有 Serializable 对象可以通过BroadcastChannel.postMessage()发送。
  • 我认为“不能发送任何类型的对象”。 postMessage 使用结构化克隆算法发送消息,因此只能发送“结构化可克隆”对象。看看这里developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/…
【解决方案3】:

对于那些搜索不基于 jQuery 的解决方案的人,这是解决方案的纯 JavaScript 版本provided by Thomas M

window.addEventListener("storage", message_receive);

function message_broadcast(message) {
    localStorage.setItem('message',JSON.stringify(message));
}

function message_receive(ev) {
    if (ev.key == 'message') {
        var message=JSON.parse(ev.newValue);
    }
}

【讨论】:

  • 为什么你省略了 removeItem 调用?
  • 我只是关注 jQuery 和 JavaScript 之间的区别。
  • 我总是使用 lib,因为 polyfill 和不支持的功能的可能性!
【解决方案4】:

Checkout AcrossTabs - 跨域浏览器选项卡之间的轻松通信。它结合使用 postMessagesessionStorage API,使通信更加轻松可靠。


有不同的方法,每一种都有自己的优点和缺点。让我们讨论一下:

  1. LocalStorage

    优点

    1. Web 存储可以简单地视为对 cookie 的改进,可提供更大的存储容量。如果您查看 Mozilla 源代码,我们可以看到 5120 KB5 MB 在 Chrome 上等于 250 万字符)是默认存储空间整个域的大小。与典型的 4 KB cookie 相比,这为您提供了更多的工作空间。
    2. 不会将每个 HTTP 请求(HTML、图像、JavaScript、CSS 等)的数据发送回服务器 - 减少客户端和服务器之间的流量。
    3. 存储在 localStorage 中的数据会一直存在,直到被明确删除。所做的更改已保存,可供当前和将来访问该网站的所有用户使用。

    缺点

    1. 它适用于same-origin policy。因此,存储的数据只能在同一来源上使用。
  2. Cookies

    优点:

    1. 与其他人相比,没有任何 AFAIK。

    缺点:

    1. 4 KB 的限制适用于整个 cookie,包括名称、值、到期日期等。要支持大多数浏览器,请将名称保持在 4000 字节以下,并将整体 cookie 大小保持在 4093 字节以下。
    2. 每个 HTTP 请求(HTML、图像、JavaScript、CSS 等)都会将数据发送回服务器 - 增加客户端和服务器之间的流量。

    通常允许以下情况:

    • 300 个 Cookie
    • 每个 cookie 4096 字节
    • 20 个 cookie 每个域
    • 每个域 81920 字节(给定 20 个最大大小为 4096 = 81920 字节的 cookie。)
  3. sessionStorage

    优点:

    1. 类似于localStorage
    2. 更改仅适用于每个窗口(或 Chrome 和 Firefox 等浏览器中的选项卡)。所做的更改被保存并可用于当前页面,以及将来在同一窗口中访问该站点。一旦窗口关闭,存储就会被删除

    缺点:

    1. 数据仅在设置它的窗口/选项卡内可用。
    2. 数据不是持久的,即一旦关闭窗口/选项卡就会丢失。
    3. localStorage 一样,tt 适用于same-origin policy。因此,存储的数据只能在同一来源上使用。
  4. PostMessage

    优点:

    1. 安全地启用cross-origin 通信。
    2. 作为一个数据点,WebKit 实现(由 Safari 和 Chrome 使用)目前不强制实施任何限制(除了因内存不足而施加的限制)。

    缺点:

    1. 需要从当前窗口打开一个窗口,然后只要保持窗口打开就可以通信。
    2. 安全问题 - 通过 postMessage 发送字符串是您将获取其他 JavaScript 插件发布的其他 postMessage 事件,因此请务必实现 targetOrigin 并进行健全性检查传递给消息侦听器的数据。
  5. PostMessage + SessionStorage 的组合

    使用 postMessage 在多个选项卡之间进行通信,同时在所有新打开的选项卡/窗口中使用 sessionStorage 来持久化正在传递的数据。只要选项卡/窗口保持打开状态,数据就会被保留。因此,即使打开的选项卡/窗口关闭,打开的选项卡/窗口即使在刷新后也将拥有全部数据。

我为此编写了一个名为 AcrossTabs 的 JavaScript 库,它使用 postMessage API 在跨域选项卡/窗口和 sessionStorage 之间进行通信,以保持打开的选项卡/窗口身份,只要它们存在。

【讨论】:

  • 使用AcrossTabs,是否可以在另一个选项卡中打开不同的网站,并从中获取数据到父选项卡?我将获得另一个网站的身份验证详细信息。
  • 是的,你可以@MadhurBhaiya
  • cookie 的最大优点是它允许跨域同域,这在你有一组来源时通常很有用,例如“a.target.com”、“b.target.com”等.
【解决方案5】:

我创建了一个库sysend.js for sending messages between browser tabs and windows。该库没有任何外部依赖项。

您可以使用它在同一浏览器和域中的选项卡/窗口之间进行通信。该库使用 BroadcastChannel(如果支持)或来自 localStorage 的存储事件。

API 非常简单:

sysend.on('foo', function(data) {
    console.log(data);
});
sysend.broadcast('foo', {message: 'Hello'});
sysend.broadcast('foo', "hello");
sysend.broadcast('foo', ["hello", "world"]);
sysend.broadcast('foo'); // empty notification

当您的浏览器支持 BroadcastChannel 时,它会发送一个文字对象(但实际上它是由浏览器自动序列化的),如果不支持,它会先序列化为 JSON,然后在另一端反序列化。

最近的版本还有一个帮助 API 来创建跨域通信的代理(它需要目标域上的单个 HTML 文件)。

这里是a demo

新版本还支持跨域通信,如果你在目标域中包含一个特殊的proxy.html文件并从源域调用proxy函数:

sysend.proxy('https://target.com');

(proxy.html 是一个非常简单的 HTML 文件,库中只有一个 script 标签)。

如果您想要双向通信,则需要在其他域上执行相同操作。

注意:如果您将使用 localStorage 实现相同的功能,则 Internet Explorer 中存在问题。存储事件被发送到同一个窗口,触发该事件,对于其他浏览器,它只为其他选项卡/窗口调用。

【讨论】:

  • 只是想给你一些荣誉。不错的可爱小补充,很简单,让我可以在我的标签之间进行交流,以防止注销警告软件将人们赶走。不错的工作。如果您想要一个简单易用的消息传递解决方案,我强烈推荐这个。
【解决方案6】:

人们应该考虑使用的另一种方法是共享工作者。我知道这是一个前沿概念,但您可以在共享工作器上创建一个中继,它比本地存储快很多,并且不需要父/子窗口之间的关系,只要你们是同源的。

请参阅我的回答 here,了解我对此进行的一些讨论。

【讨论】:

    【解决方案7】:

    a tiny open-source component 用于在基于 localStorage 的同一来源的选项卡/窗口之间同步和通信(免责声明 - 我是贡献者之一!)。

    TabUtils.BroadcastMessageToAllTabs("eventName", eventDataString);
    
    TabUtils.OnBroadcastMessage("eventName", function (eventDataString) {
        DoSomething();
    });
    
    TabUtils.CallOnce("lockname", function () {
        alert("I run only once across multiple tabs");
    });
    

    P.S.:我冒昧地在这里推荐它,因为当事件几乎同时发生时,大多数“锁定/互斥/同步”组件在 websocket 连接上都会失败。

    【讨论】:

      【解决方案8】:

      我创建了一个与官方 Broadcastchannel 相同的模块,但它具有基于 localstorage、indexeddb 和 unix-sockets 的后备功能。这确保了它即使与 Webworkers 或 Node.js 也始终有效。见pubkey:BroadcastChannel

      【讨论】:

        【解决方案9】:

        这是针对 Chrome 的 Tomas M's answer 的开发 storage 的一部分。我们必须添加一个监听器:

        window.addEventListener("storage", (e)=> { console.log(e) } );
        

        在存储中加载/保存项目不会运行此事件 - 我们必须手动触发它

        window.dispatchEvent( new Event('storage') ); // THIS IS IMPORTANT ON CHROME
        

        现在,所有打开的标签都将收到该事件。

        【讨论】:

        • 你说的“这次事件不矮”是什么意思(似乎难以理解)?
        【解决方案10】:

        我在我的博客上写了一篇关于此的文章:Sharing sessionStorage data across browser tabs

        使用库,我创建了storageManager。您可以按如下方式实现:

        storageManager.savePermanentData('data', 'key'): //saves permanent data
        storageManager.saveSyncedSessionData('data', 'key'); //saves session data to all opened tabs
        storageManager.saveSessionData('data', 'key'); //saves session data to current tab only
        storageManager.getData('key'); //retrieves data
        

        还有其他方便的方法来处理其他场景。

        【讨论】:

          猜你喜欢
          • 2011-06-26
          • 2015-10-13
          • 2011-01-15
          • 2011-01-15
          • 1970-01-01
          • 2017-02-28
          • 2011-05-04
          相关资源
          最近更新 更多