Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

document.open removes all window listeners

I'm writing a Chrome extension. It's used for recording users' behavior on browsing web pages. It does that by adding event listeners to customers' web pages, using Chrome content script.

Code in content script looks like:

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

Example of custom page:

<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>

It uses document.open, document.write to rewrite content of iframe.

Here is the question. My event listeners are attached to window object. And document.open removes all its event listeners. Like picture below shows.

enter image description here

Is there a way to avoid document.open removing event listeners? Or to observe document.open, so I can manually re-add listeners after it?

like image 753
zhm Avatar asked Oct 22 '25 14:10

zhm


2 Answers

I've found this issue trying to solve exactly the same problem.

Here is a spec https://html.spec.whatwg.org/multipage/webappapis.html#dom-document-open that says that on document.open current document is destroyed and replaced with a fresh one. I had a hope that some event's like "load" are still preserved, no luck. Here is my detection code:

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

When document is recreated its childList is changed(new documentElementnode), this is the best trigger I've thought of to detect document replacement.

Note that even listeners fire before setTimeout(..., 0)

like image 67
Viller Avatar answered Oct 25 '25 04:10

Viller


This is a detailed explanation of why @Viller's answer works. I'm making this a new answer as it didn't fit into a comment

The TestEvent event is a special event that monitors when the events that were previously setup in a document are removed.

In particular, this accounts for the case of document.open, which removes all listeners not only from the document but also from the window.

The general idea is to setup a listener for a custom event called TestEvent, which clears a timeout. Such timeout is setup only when the document mutates and is triggered by a mutation observer.

Since the timeout schedules the operation to happen at least during the next tick of the event loop, such timeout can be cleared before that, avoiding the execution of its callback all together. And, since the TestEvent event handler clears that timeout, the fact that the timeout is cleared implies that the listener is still attached. On the other hand, if the timeout is not cleared before the next tick, the would signify the events were removed and a new "setup" is needed.

like image 29
Miguel Sánchez Villafán Avatar answered Oct 25 '25 04:10

Miguel Sánchez Villafán