如何处理iframe中两次或根本不触发的IE11 localStorage事件

How to work around IE11 localStorage events firing twice or not at all in iframes?

本文关键字:localStorage IE11 事件 两次 处理 何处理 iframe      更新时间:2023-09-26

我猜这是一个bug,但我还没有找到任何关于它的讨论。

众所周知,IE10会(违反规范)在本地(即在触发事件的同一全局执行上下文中)触发存储事件,但IE11似乎偏离了规范(http://www.w3.org/TR/webstorage/)当涉及到同一域名时:

  • 如果iframe嵌入触发存储事件的页面中,则该事件将在该iframe内激发两次
  • 如果iframe嵌入的页面与触发存储事件的页面不同,则该事件在该iframe内根本不会触发
  • 如果事件是从一个iframe触发的,它将在本地触发TWICE,并在嵌入同一页面的任何其他iframe中触发TWICE

您可以通过在两个单独的选项卡中打开以下链接来测试这一点:http://hansifer.com/main.html

有人知道这种怪癖吗?

上次测试的版本:IE v11.0.9600.16476(更新2016-08-13:显然是相关的"更新版本",而不是IE的"关于"对话框中报告的"版本")

错误报告链接:https://connect.microsoft.com/IE/feedback/details/811546/ie11-localstorage-events-fire-twice-or-not-at-all-in-iframes

更新2015-10-26

我刚刚注意到,这似乎在v11.0.9600.18059中得到了修复,尽管我不知道修复是什么时候删除的,因为它似乎在最近的任何KB中都没有被引用。

不幸的是,IE11 localStorage事件在其他方面仍然是异常的(尽管这些是与本文中提出的问题不同的改进):

  • IE在调用触发事件的localStorage set或remove的窗口上下文中引发localStorage事件。localStorage事件只能在同源的外部窗口上下文中引发。(更新:使用EdgeHTML 13.10586)

  • 添加/删除存储项时,IE使用空字符串而不是null作为e.oldValue/e.newValue。(更新:EdgeHTML 13.10586中仍然存在问题)

  • IE不确定性地调用localStorage事件处理程序BEFORE或AFTER set/remove生效,而不是一致地调用AFTER。

更新2015-12-24

这个错误似乎被转移到了Edge(测试了EdgeHTML 13.10586)

更新2016-02-02

好吧,没关系。这个错误再次在IE v11.63.10586.0中被观察到(更新2016-08-13:显然是"更新版本"相关,而不是IE的"关于"对话框中报告的"版本")

更新2016-08-13

这一问题现在似乎在IE(更新版本11.0.34)中得到了修复,尽管存储事件仍然在原始窗口中触发,违反了规范(如上所述,这是一个已知的长期问题)。

我发现了这个KB,它包含在IE 2016年6月14日的安全更新中,尽管根据它的描述,它只针对上面的第二个项目符号。

至于Edge(经过测试的EdgeHTML 14.14393),这个问题现在似乎也得到了解决,但有一个新问题:存储事件不会在同一页面的相同原始帧之间触发。

我在这里单独向MS报告了。

如果您想阻止一个事件被多次调用,无论调用的原因是什么,都可以使用标志来阻止后续事件。有几种策略:

.1、基于时间的节流。假设你有一个函数"func",并且你希望它在200ms内只被调用一次:

function func(){
  if (document.func_lock) return;
  document.func_lock=true; // block future calls
  setTimeout(function(){document.func_lock=null;},300);
}

当同时触发多个事件时,您可以预期所有事件都会在300ms窗口内到达。上面的代码可以通过利用定时器手柄来简化:

function func(){
  if (document.func_lock) return;
  document.func_lock=setTimeout(function(){return;},300);
}

当计时器到期时,锁定将自动解除。

2.使用回调删除标志。这个过程很琐碎,所以我不会在这里发布示例代码。

在标志放置方面,您可以使用任何唯一的DOM对象。由于我不知道您的应用程序的上下文,所以我在这里只使用"文档"。您还可以使用应用程序特有的哈希密钥,因为您已经在处理本地存储了。概念是一样的。

我能够通过使用window.parent在iframe中以这种方式注册事件来解决这个问题:

Iframe页面

var win = window.parent || window.opener || window;
win.addEventListener('storage', handleLocalStorageEvent, false);
function handleLocalStorageEvent(e) {
    console.log('IFRAME local storage event', e);
    var sdf = document.getElementById('sdf');
    sdf.innerHTML = sdf.innerHTML + 'Storage Event => (' + e.newValue + ')<br>';
}

免责声明:请注意,此解决方案旨在修复(解决)IE11问题。无论如何,它的意图或建议,这适用于所有浏览器。