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

使用window对象 postMessage 饼干 localStorage

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

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

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

第四种解决方案,使用localStorage,似乎克服了cookie的限制,甚至可以使用事件监听。如何使用它在公认的答案中有描述。


当前回答

为此,您最好使用BroadcastChannel。请看下面的其他答案。然而,如果你仍然喜欢使用localstorage在选项卡之间进行通信,可以这样做:

为了在一个选项卡向其他选项卡发送消息时得到通知,你只需要绑定一个'storage'事件。在所有选项卡中,这样做:

$(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()})

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

You may ask what happens if the user loads a different webpage or closes his tab just after the setItem() call before the removeItem(). Well, from my own testing the browser puts unloading on hold until the entire function message_broadcast() is finished. I tested to put some very long for() cycle in there and it still waited for the cycle to finish before closing. If the user kills the tab just in-between, then the browser won't have enough time to save the message to disk, thus this approach seems to me like safe way how to send messages without any traces.

其他回答

有一个基于localStorage的小开源组件,可以在相同来源的选项卡/窗口之间同步和通信(免责声明-我是贡献者之一!)

TabUtils.BroadcastMessageToAllTabs("eventName", eventDataString);

TabUtils.OnBroadcastMessage("eventName", function (eventDataString) {
    DoSomething();
});

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

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

为此,您最好使用BroadcastChannel。请看下面的其他答案。然而,如果你仍然喜欢使用localstorage在选项卡之间进行通信,可以这样做:

为了在一个选项卡向其他选项卡发送消息时得到通知,你只需要绑定一个'storage'事件。在所有选项卡中,这样做:

$(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()})

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

You may ask what happens if the user loads a different webpage or closes his tab just after the setItem() call before the removeItem(). Well, from my own testing the browser puts unloading on hold until the entire function message_broadcast() is finished. I tested to put some very long for() cycle in there and it still waited for the cycle to finish before closing. If the user kills the tab just in-between, then the browser won't have enough time to save the message to disk, thus this approach seems to me like safe way how to send messages without any traces.

这是一个开发存储部分的Tomas M的答案Chrome。我们必须添加一个监听器:

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

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

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

现在,所有打开的选项卡都将接收该事件。

对于那些寻找不基于jQuery的解决方案的人来说,这是一个由Thomas M提供的纯JavaScript版本的解决方案:

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);
    }
}

我在我的博客上写过一篇文章:跨浏览器选项卡共享sessionStorage数据。

使用一个库,我创建了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

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