Can fetch be used to replace the entire document or html element?

Viewed 1115

In a web extension, I've been passing template elements from the background page as strings to an empty local HTML file and using browser.tabs.insertCSS and browser.tabs.executeScript to pass CSS and JS. That has been working well but I recently learned that fetch can be used to retrieve the files from the extension directly, which would reduce the size of the background page or eliminate it altogether, leaving just the background script and reducing RAM usage.

I've seen some examples in this SO question that write the HTML string to the body element using innerHTML and one that uses a DOMParser. But if you try it, the HTML is added to the body element.

Another SO question, ten years old, had a five-year-old last response that was interesting, to a novice like me anyway; and I tried this, which appears to work.

fetch('file.html')
.then( response => response.text() )
.then( result => {
        let parser = new DOMParser(),
            doc = parser.parseFromString( result, 'text/html' );
        document.replaceChild( doc.documentElement, document.documentElement );
    } );

My question is, is this a valid and reliable method of replacing the html element completely?

Thank you.

1 Answers

No. This will have unintended side-effects in some cases.

Here is a simple example demonstrating this:

index.html

<html>
<head>
  <title>DOM Parser test</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <h1>This is the original file</h1>
  <button id="btn1" type="button">Click to reload</button>

  <script>
    var btn1 = document.getElementById('btn1');
    function onClick() {
      fetch('file.html')
       .then( response => response.text() )
       .then( result => {
          let parser = new DOMParser();
          doc = parser.parseFromString( result, 'text/html' );
          document.replaceChild( doc.documentElement, document.documentElement );
        } );
    }

    btn1.addEventListener('click', onClick);
  </script>
</body>
</html>

file.html

<html>
<head>
  <title>DOM Parser test</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <h1>This is the second file</h1>
  <button id="btn1" type="button">Click to reload</button>

  <script>
    var btn1 = document.getElementById('btn1');
    function onClick() {
      fetch('file.html')
       .then( response => response.text() )
       .then( result => {
          let parser = new DOMParser();
          doc = parser.parseFromString( result, 'text/html' );
          document.replaceChild( doc.documentElement, document.documentElement );
        } );
    }

    btn1.addEventListener('click', onClick);
  </script>
</body>
</html>

style.css

html body {
      background: #00003f;
}

I served these static files using node JS http-server ./ and accessed index.html

enter image description here

Now click the "Click to Reload" button. This should fetch file.html and render it.

enter image description here

As you can see in the source code of index.html and file.html, they both reference style.css stylesheet. index.html rendered the styles referenced whereas file.html which was fetched and used to update the documentElement did not honor the stylesheet.

This is one example of how things can break.

Related