在同一域上的不同窗口之间进行通信

Sys*_*ral 5 javascript postmessage

我正在构建一个执行大量客户端数据下载和处理的应用程序.通过在驻留在子域上的iframe中处理,数据处理与主应用程序隔离.这是iframe下载数据.通过postMessage进行通信.

一切都很好,除了它可能会更好.

如果用户打开额外的选项卡/窗口,应用程序当前会重新加载所有数据,甚至可能会执行重复的处理工作,这不是一个问题,除了它减慢了所有内容并且页面加载时间更长.

我想要做的是让每个顶级选项卡/窗口只与一个处理iframe通信,如果原始窗口关闭,可以恢复.问题是,这些不是通过javascript打开,而是通过普通的浏览器方法打开标签中的链接,所以我无法获得发送消息所需的iframe的引用.

无论如何我可以将iframe的窗口引用传递给其他选项卡,以便它们可以通过postMessage与它通信吗?这可以通过共享工作者实现吗?

我意识到我可以将共享工作者用于整个处理任务,但这会产生问题,因为数据来自第三方域,无法从工作人员中访问.

只需要与所有主流浏览器的最新版本兼容.

编辑:我刚刚发现在Firefox中尚未实现SharedWorker,所以我想这不会起作用.我可以通过其他方式实现这一目标吗

编辑2:我发现你可以使用:

var win = window.open('', 'my_window_name'); 
Run Code Online (Sandbox Code Playgroud)

从任何其他窗口捕获对iframe的引用.但是,如果iframe尚不存在,则会将其作为窗口打开.即使它立即关闭,也会导致闪烁并导致"弹出窗口被阻止"消息,使其无法使用.

Sys*_*ral 3

万一其他人发现了这一点,我已经想出了一个解决方案。它有点 hacky,需要进一步测试。但到目前为止它正在发挥作用。如果需要的话,它可以跨域工作。

它结合使用了两种技巧。

第一个是使用

remote_window = window.open("", "remote_window_name");
Run Code Online (Sandbox Code Playgroud)

获取对窗口的引用。这是有效的,因为如果已经使用给定名称打开了一个窗口,那么将返回对该窗口的引用,而不是打开一个新窗口。

但它确实存在一个问题,如果 iframe 不存在,则会弹出一个新窗口。使用本地存储是为了防止这种情况发生。当窗口/选项卡加载时,它会检查 localStorage 以查看是否有另一个页面已具有共享 iframe。如果没有,它会插入 iframe 并在本地存储中设置一个标志来表明它可用。

作为最后的手段,如果窗口仍然打开,则使用 try 块来关闭新打开的窗口。try 块可以防止跨域错误。这意味着最糟糕的情况是用户看到一个窗口弹出然后消失,或者他们会看到“启用弹出窗口”消息。我还没有在测试中触发这个——这只是一个边缘情况的回退。

try {
    if(store_window.location.href === "about:blank" ){
        remote_window.close();
        remote_window = insertIfame();
    }
} catch(err) {
}
Run Code Online (Sandbox Code Playgroud)

添加了 onunload 事件,该事件会在页面关闭时删除该标志。

还创建了一个 setInterval 来不断刷新超时标志。我让它每秒运行 4 次;当加载第二个窗口/选项卡时,它会在尝试与其通信之前检查 iframe 标志是否未超时。这是一个很小的开销,但远远低于我加载第二个 iframe 的成本。这样做的目的是在浏览器崩溃或 onunload 因任何原因未触发时防止数据过时。我在检查超时时留有一点余地(当前为 1 秒),以防主窗口陷入循环。余地仅在于超时,而不是完全删除标志的卸载事件。

每次发送消息时都需要检查该标志,以防带有 iframe 的原始窗口已关闭。发生这种情况时,iframe 会重新插入到第一个需要它的打开窗口中,并且标志会重置。

发回消息很容易。只需使用 receiveMessage 的 event.source 属性 - 这指向发送窗口。

需要考虑的最后一个边缘情况是,如果主窗口关闭,而其 iframe 正在处理辅助窗口。理论上,可以通过使用 iframe 中的 onunload 事件将消息发送回任何正在处理数据的窗口来解决此问题。但我还没有实现它,它可能无法在页面卸载之前完成。处理它的另一种方法是在辅助窗口中设置超时,检查标志并重试,我可能会走这条路线,因为消息已经附加了超时。