Opening a document with `document.open` will clear all event listeners

I've developed a Chrome extension that records user behavior while browsing web pages by adding event listeners to customers' web pages using a Chrome content script.

The code in the content script looks something like this:

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

For example, here's a snippet from a 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>

This snippet uses document.open and document.write to update the content of an iframe.

My question is this: The event listeners are attached to the window object, but when document.open is used, it removes all event listeners as shown below.

https://i.sstatic.net/DQ0fh.png

Is there a way to prevent document.open from removing event listeners? Or is there a way to monitor document.open so I can manually reattach listeners afterwards?

Answer №1

While trying to address the same issue, I stumbled upon this problem.

According to the specification, when using document.open, the current document is destroyed and replaced with a new one. I had hoped that certain events like "load" would still be retained, but unfortunately, that was not the case. Here is my code snippet for detecting this behavior:

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 the document is recreated, its childList changes (creating a new documentElement node), which I found to be the most reliable way to detect document replacement.

It's worth noting that even listeners are triggered before setTimeout(..., 0).

Answer №2

Here is an in-depth explanation of why @Viller's solution actually works. I felt the need to turn this into a separate answer since it was too lengthy for a mere comment.

The unique identifying factor here is the TestEvent event, specially designed to monitor the removal of previously set up events within a document.

Specifically, this functionality addresses scenarios like document.open, where not only the document but also window listeners are removed entirely.

To put it simply, the key strategy involves establishing a listener for a bespoke event known as TestEvent, which essentially cancels out a pending timeout. This timeout is exclusively triggered when mutations occur within the document, facilitated by a mutation observer.

By structuring the timeout to operate at least after the next tick of the event loop, it can be nullified beforehand, thus avoiding the execution of its designated callback altogether. Furthermore, given that the TestEvent handler is responsible for erasing said timeout, its termination implies the persistence of the attached listener. Conversely, if the timeout persists beyond the upcoming tick, it signifies the elimination of events, prompting the necessity for a fresh "setup".

Answer №3

As per the information provided on MDN:

When using the Document.open() method, there are certain side effects that come along with it. For example, all event listeners currently registered on the document, nodes inside the document, or the document's window are removed.

Below, a module (onGlobalListenerRemoval) is presented where users can easily register callback functions to receive notifications whenever listeners are cleared. This module follows the same principles as explained in the code from Viller's solution.

Principle of Usage:

onGlobalListenerRemoval.addListener(() => {
  alert("All event listeners have been removed!")
});

Module Script:

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);
    // execute 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)
  }
})();

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Inquiries about the jQuery Button Timer

Currently experimenting with jQuery to create a timer. Everything seems to be in place and the Stop Timer button is functioning properly. However, I'm facing issues with the Start Timer and Reset Timer buttons as they are not working as expected. Seek ...

Error encountered in jQuery Datatables: aData variable is not defined

I am having an issue with a jQuery datatable that is using an Ajax data source. This is how the table is set up: $("#tblNotes").DataTable({ "ajax" : { "url": "/projects/ajaxGetProjectNotes/", "type" : "post", ...

Include characteristics in JSX.Element following its declaration

Suppose I have an item in a dictionary with the following structure: let item = { element: <myElement/>, color: "#0e76a8" } Is it possible to add a style attribute to the item.element within the render() method? I tried the following appro ...

Storing chrome identity api responses in a Vue.js component can be achieved by creating a function

When passing the response from the Chrome Identity API to the tab running my Vue-powered Chrome extension, I encountered an issue in storing this information inside my Vue instance for use within a component. Despite trying to assign the info to a variable ...

Is it possible to incorporate additional sections by utilizing map and props in the given code snippet?

I have a component named CardItems.jsx which specifically defines the appearance of a card. Then, I also have Gotocart.jsx where there is a welcome section (similar to welcoming someone to their cart) and an order section at the end (for initiating an orde ...

Error: Unexpected TypeError occurred stating that 'map' cannot be read from undefined, although the map method is not being used in the code

I have recently developed an Ethereum application for conducting transactions using React and the ethers module. Below, you can see a snippet of my code, specifically focusing on the function sendTransactions: import {ethers} from 'ethers'; impor ...

How come my form isn't functioning properly on mobile devices?

I recently downloaded a form from the website and integrated it within my desktop site successfully. However, when accessed on mobile devices, specifically iOS, the submit button changes to "sending ..." and the form gets stuck without displaying any erro ...

What is the optimal method for verifying two distinct conditions simultaneously using Javascript?

Hey there, I'm working on a code snippet to check the status of a Rails model. Here's what I have so far: var intervalCall = setInterval(function(){ $.post("getstatus", {id:id}); var finished = "<%= @sentence.finished%>"; // CONDI ...

The width of Material UI Grid decreases every time it is re-rendered

I'm attempting to display a list of 25 words in a 5x5 grid using the MUI Grid component. The grid is structured as a <Grid container direction="column"> with five <Grid item> elements. Each <Grid item> contains: <Grid co ...

The validation feature in 1000hz Bootstrap seems to be malfunctioning

I've been working on implementing validation using the 1000hz bootstrap validation plugin. Most things are going smoothly, but I'm encountering two issues: 1). The data-match attribute doesn't seem to be working even when I enter the same p ...

Creating PopUp Windows with PHP and JavaScript

There is a function created on my page that opens a pop-up window when clicking on a game-mod name: <SCRIPT language="javascript" type="text/javascript"> function popModData( modName ) { var url = "./modList.php?mod=" + modName; ...

Trouble with JavaScript loading in HTML webpage

<!DOCTYPE html> <html> <head> <title>Breakout Game</title> <link rel="stylesheet" href="css/style.css"> </head> <body> <canvas width="900" height="450" class="canvas"></canvas> ...

Issue with Component not displaying properly upon refreshing

I'm currently using react.js and I have a button with an onClick event. My goal is to reload the page after clicking the button and then display a specific component on the page. However, I've encountered an issue where the component doesn't ...

Using Regex to replace special characters in TypeScript

I need assistance in removing the characters "?" and "/" from my inner HTML string. Can you guide me on how to achieve this using regex? For example, I would like to replace "?" with a space in the following content. "Hello?How are you?<a href="http:/ ...

Simple code styling tool

Seeking guidance to create a basic syntax highlighter using JavaScript or ClojureScript. While aware of existing projects like Codemirror and rainbow.js, I'm interested in understanding how they are constructed and looking for a simple example. Do th ...

The custom created THREE.Curve is not rendering correctly

Following advice from this solution, I have implemented a linearly interpolated curve in the code below: THREE.Linear3 = THREE.Curve.create( function ( points, label /* array of Vector3 */) { this.points = (points == undefined) ? [] : points; ...

Error: The function SomeFunction is not recognized as a valid function (Mongoose library)

Help needed! I'm encountering an error stating "TypeError: User.getUserByUsername is not a function at Strategy._verify (.../routes/users.js:65:10) var User = require('../models/user'); passport.use(new LocalStrategy( function(username, ...

Can you explain the distinction between using <router-view/> and <router-view></router-view>?

In various projects, I have encountered both of these. Are they just "syntactic sugar" or do they hold unique distinctions? ...

`Combining Promises and yields for seamless functionality`

I have been struggling to incorporate yield with a created Promise. Despite extensively researching, I am still unable to understand where I am going wrong in my implementation. Based on my understanding, when calling the generator function, I need to use ...

How to effectively utilize JSON responses with jQuery Mobile?

I've been facing an issue with working on my JSON result in JavaScript. Can anyone provide some insight? Even though the JSON call returns a success status code (200) and I can view the data in Firebug, no alert is being displayed. $(document).on(& ...