document.open会删除所有窗口监听器。

4

我正在编写一个Chrome扩展程序。它用于记录用户在浏览网页时的行为。通过使用Chromecontent script,将事件监听器添加到客户的网页中来实现这一点。

content script中的代码如下:

var recordingEvents = ['click', 'input', 'change'];
recordingEvents.forEach(function (e) {
    window.addEventListener(e, handler, true);
});

自定义页面示例:

<script>
function reload() {
    var ifrw = document.getElementById("iframeResult").contentWindow;
    ifrw.document.open();
    ifrw.document.write("<div>abc</div>");  
    ifrw.document.close();
}
</script>
<body>
<input type="submit" onclick="reload();" value="Reload" />
<iframe id="iframeResult"></iframe>
</body>

它使用document.opendocument.write来重写iframe的内容。

问题在于,我的事件监听器附加到window对象上。而document.open会删除所有事件监听器。如下图所示。

enter image description here

有没有办法避免document.open删除事件监听器?或者观察document.open,这样我就可以手动重新添加监听器了呢?


请勿调用 document.open - SLaks
我正在编写一个Chrome扩展程序,用于向自定义网页添加事件监听器。因此,我无法确定自定义是否使用document.write()。 - zhm
为什么不在加载时重写iframe的内容,或者使用window.setTimeoutdocument.write呢? - Jonathan Gray
这很令人困惑。问题实际上并不是很清楚。我的评论已经导致了两次误解。 - Jonathan Gray
直到你最后一次编辑,我才明白document.open被调用在客户的页面上,这一点并不清楚。现在我想知道你尝试让代码工作的哪一部分,它应该具体做什么? - Jonathan Gray
显示剩余2条评论
3个回答

3
我发现这个问题是在尝试解决完全相同的问题时发现的。
这里有一个规范https://html.spec.whatwg.org/multipage/webappapis.html#dom-document-open,它说当使用document.open时,当前文档会被销毁并替换为一个新的文档。我希望一些事件,如“load”事件仍然被保留,但没有运气。以下是我的检测代码:
const testEventName = 'TestEvent';
let tm;

function onTestEvent() {
    clearTimeout(tm);
}

function listenToTestEvent() {
    document.addEventListener(testEventName, onTestEvent);
}

listenToTestEvent();

function onLostEvents() {
    console.log('events are lost');
    listenToTestEvent();
    // DO THING HERE
}


function checkIfEventsAreLost() {
    document.dispatchEvent(new CustomEvent(testEventName));
    tm = setTimeout(onLostEvents);
}

new MutationObserver(checkIfEventsAreLost).observe(document, { childList: true });

当文档被重新创建时,它的childList会改变(新的documentElement节点),这是我想到的最好的触发器来检测文档替换。
请注意,即使监听器在setTimeout(..., 0)之前触发。

1
这是关于为什么@Viller's answer 起作用的详细说明。我将其作为新答案进行,因为它不适合评论。 TestEvent事件是一种特殊事件,用于监视先前在文档中设置的事件何时被删除。
特别是这适用于document.open的情况,它不仅从文档中删除所有侦听器,还从窗口中删除所有侦听器。
总体思路是为名为TestEvent的自定义事件设置侦听器,该侦听器清除超时。只有在文档发生变化并由变异观察者触发时才会设置此类超时。
由于超时安排操作至少在事件循环的下一个刻度期间发生,因此可以在那之前清除超时,从而避免执行其回调。而且,由于TestEvent事件处理程序清除了该超时,因此清除超时意味着仍然附加了侦听器。另一方面,如果超时在下一个刻度周期之前未被清除,则表示已删除事件,并且需要进行新的“设置”。

1
根据MDN:

Document.open() 方法[...] 会带来一些副作用。例如: 当前在文档、文档节点或文档窗口上注册的所有事件侦听器都会被删除。

下面我提供一个模块(onGlobalListenerRemoval),用户可以轻松地注册某些回调函数,以便在侦听器被清除时得到通知。这使用与Viller答案中代码相同的工作原理。 使用方法原则:
onGlobalListenerRemoval.addListener(() => {
  alert("All event listeners got removed!")
});

模块代码:
const onGlobalListenerRemoval = (() => {
  const callbacks = new Set();
  const eventName = "listenerStillAttached";

  window.addEventListener(eventName, _handleListenerStillAttached);

  new MutationObserver((entries) => {
    const documentReplaced = entries.some(entry =>
      Array.from(entry.addedNodes).includes(document.documentElement)
    );
    if (documentReplaced) {
      const timeoutId = setTimeout(_handleListenerDetached);
      window.dispatchEvent(new CustomEvent(eventName, {detail: timeoutId}));
    }
  }).observe(document, { childList: true });

  function _handleListenerDetached() {
    // reattach event listener
    window.addEventListener(eventName, _handleListenerStillAttached);
    // run registered callbacks
    callbacks.forEach((callback) => callback());
  }

  function _handleListenerStillAttached(event) {
    clearTimeout(event.detail);
  }

  return  {
    addListener: c => void callbacks.add(c),
    hasListener: c =>  callbacks.has(c),
    removeListener: c => callbacks.delete(c)
  }
})();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接