3

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.

Gary
  • 2,393
  • 12
  • 31
  • What happens to your javascript event handlers etc., – vvg Aug 27 '20 at 02:52
  • I can see in the inspector that the script tag came with the HTML document but the events are not working in my little test file. Thanks for pointing that out. I guess I need to figure out how to make the script run or still inject it through the extension. – Gary Aug 27 '20 at 03:14
  • The script tag shows up in the inspector but the source data is still the old html and script. – Gary Aug 27 '20 at 03:19

1 Answers1

3

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.

vvg
  • 1,010
  • 7
  • 25
  • Thank you for the example. That's very interesting and saved me a lot of trouble, probably several wasted hours of thinking I was just doing something wrong within the extension. Would it be safe to use fetch to get the other DOM elements content only, that which would be provided through the template elements in the background page, and then add the CSS and JS through the browser.tab methods? – Gary Aug 27 '20 at 03:34
  • I have been using `insertAdjacentHTML` to add the template as a string because cannot pass a DOM node from the background to the page. Perhaps those templates can be separate HTML files in the extension such that they could be fetched, parsed, and used to replace the body element or the content of the body element, after which the browser tabs methods can add the CSS and JS. This would still allow for reduction in size or removal of a background HTML page. – Gary Aug 27 '20 at 03:36
  • Yes. insertAdjacentHTML if you want to insert components that need to obey global context and the Shadow DOM if you want to use self-contained components (See: https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM). – vvg Aug 27 '20 at 03:43