nikitaeverywhere
nikitaeverywhere

Reputation: 1343

Using document.write within MutationObserver results in browser being stuck and document.readyState always 'loading'

Disclaimer: I am aware of why should I avoid using document.write and document.write clears page.

Let's say I need to inject a synchronous (blocking) script into the DOM when it's being loaded (document.readyState = 'loading'), but right before another specific existing sync script.

If I have access to HTML code, I can do this by writing something like:

<html>
  <head>
    <title>It's okay</title>
    <script>
      document.write(
        '<script src="https://www.googletagmanager.com/gtm.js?id=GTM-WZNT8QT" onload="console.log(1)"><'+'/script>'
      );
    </script>
    <script src="https://code.jquery.com/jquery-3.6.3.min.js" onload="console.log(2)"></script>
  </head>
  <body>
    <script>console.log('DOM is parsed')</script>
  </body>
</html>

It correctly loads both JS files, and outputs 1, 2, DOM is parsed to the console.

Now, if I try to do it using MutationObserver (that is, detecting when a new script node is inserted, but before the script is actually invoked),

<html>
  <head>
    <title>Why not loading?</title>
    <script>
      // Say, this JS is invoked by the extension before even parsing HTML
      new MutationObserver((mutationList) => {
        for (const mutation of mutationList) {
          for (const node of mutation.addedNodes) {
            if (node.tagName !== 'SCRIPT' || !node.src.includes('jquery')) {
              continue;
            }
            document.write(
              '<script src="https://www.googletagmanager.com/gtm.js?id=GTM-WZNT8QT" onload="console.log(1)"><'+'/script>'
            );
          }
        }
      }).observe(document, { childList: true, subtree: true });
    </script>
    <script src="https://code.jquery.com/jquery-3.6.3.min.js" onload="console.log(2)"></script>
  </head>
  <body>
    <script>console.log('Now DOM is parsed!')</script>
  </body>
</html>

...not only it makes the browser "always loading" something, but also never prints 2 and Now DOM is parsed! to the console. Upon checking the actual HTML code, there's only that "googletagmanager" script in the head. JavaScript thread is not blocked. And actually document.readyState always stays 'loading' from that moment, effectively stuck and broken.

Always loading

Questions:

  1. Why is this happening? How can you explain it? My expectation was that both scripts would be invoked.
  2. Is there any way to load a synchronous piece of code before the specific script (already present in HTML code) is executed? (with not having access to editing HTML but having an ability to inject JS before HTML is even parsed, say, browser extension etc).

With MutationObserver actually, I can at least prevent the jquery code from running by saying node.setAttribute('src', 'text/prevented') in the mutation handler, so it doesn't matter whether googletagmanager script is inserted before or after it. I am trying to understand why it doesn't work.

Upvotes: 1

Views: 129

Answers (0)

Related Questions