0

Apparently, scripts in HTML template only work in HTML template itself, and not the page that the HTML template is injected into. (The scripts still execute, but they rely on jQuery, and even though its imported before the others, it spits out errors.)

To elaborate, here is my code:

function cleanDocument(names) {
    var element = document.documentElement;
    for (var i = element.attributes.length - 1; i >= 0; i--) {
        element.removeAttribute(element.attributes[i].name);
    }
    for (var i = 0; i < names.length; i++) {
        var elements = document.getElementsByTagName(names[i]);
        if (elements.length === 0)
            document.documentElement.appendChild(document.createElement(names[i]));
    }
    window.stop();
}

var documentElements = ['html'];
cleanDocument(documentElements);

var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
    // Typical action to be performed when the document is ready:
        
        var PBSKPage = xhttp.responseText;
        document.querySelector('html').innerHTML = "";
        loadPBSKPage();
        function loadPBSKPage() {
            
            document.querySelector('html').innerHTML = PBSKPage;
        }
    }
};

var actualpage = chrome.runtime.getURL('/2015/wildkratts/wk_homepage.html')
xhttp.open("GET", actualpage, true);
xhttp.send();

function insertAndExecute() {
    var scripts = Array.prototype.slice.call(document.getElementsByTagName("script"));
    var jquery = document.createElement("script");
    jquery.src = "https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js";
    document.head.appendChild(jquery);
    for (var i = 0; i < scripts.length; i++) {
        if (scripts[i].src != "") {
            var tag = document.createElement("script");
            tag.src = scripts[i].src;
            document.getElementsByTagName("head")[0].appendChild(tag);
        }
        else {
            eval(scripts[i].innerHTML);
        }
    }
}

This code applies to https://pbskids.org/wildkratts/.

Basically, what it does is it wipes the document clean, and then, using XMLHttpRequest, it injects the HTML template inside the extension onto the page. However, all the scripts inside the HTML template require jQuery, and when accessing the overriden page (pbskids.org/wildkratts/), the scripts don't fully work due to uncaught errors (i.e. Uncaught ReferenceError: $ is not defined) that would be resolved if jQuery was imported.

I then accessed the HTML template URL itself, and what do you know, the scripts actually executed no problem.

Here's the order of my script tags in the HTML template:

        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
        <script src="chrome-extension://niabfndainielhgpcjenbpodannhfofj/jquery_libraries/1.11.0/jquery.min.js"></script>
        <script>window.jQuery || document.write('<script src="chrome-extension://niabfndainielhgpcjenbpodannhfofj/2015/pbsk_resources/wildkratts/js/vendor/jquery-1.11.0.min.js"><\/script>')</script>
        <script src="chrome-extension://niabfndainielhgpcjenbpodannhfofj/2015/pbsk_resources/wildkratts/js/vendor/jquery-1.11.0.min.js"></script>
        <script src="chrome-extension://niabfndainielhgpcjenbpodannhfofj/2015/pbsk_resources/wildkratts/js/vendor/jquery.nivo.slider.pack.js" type="text/javascript"></script>
        <script src="chrome-extension://niabfndainielhgpcjenbpodannhfofj/2015/pbsk_resources/wildkratts/js/vendor/jquery.touchSwipe.min.js" type="text/javascript"></script>
        <script src="chrome-extension://niabfndainielhgpcjenbpodannhfofj/2015/pbsk_resources/wildkratts/js/vendor/retina.min.js"></script>
        <script src="chrome-extension://niabfndainielhgpcjenbpodannhfofj/2015/pbsk_resources/wildkratts/js/plugins.js"></script>
        <script type="text/javascript" src="chrome-extension://niabfndainielhgpcjenbpodannhfofj/2015/pbsk_resources/wildkratts/js/swfobject.js"></script>    
        <script type="text/javascript" src="chrome-extension://niabfndainielhgpcjenbpodannhfofj/2015/pbsk_resources/wildkratts/js/vendor/jquery.mobile.custom.min.js"></script>
        <script type="text/javascript" src="chrome-extension://niabfndainielhgpcjenbpodannhfofj/2015/pbsk_resources/wildkratts/js/vendor/jquery.nivo.slider.3.2.plus.sliding.js"></script> 
        <script src="chrome-extension://niabfndainielhgpcjenbpodannhfofj/2015/pbsk_resources/wildkratts/js/pxaudio.min.js"></script>
        <script type="text/javascript" src="https://www-tc.pbskids.org/includes/javascript/bridge.urls.js"></script>
        <script type="text/javascript" src="https://www-tc.pbskids.org/includes/javascript/bridge.js"></script>        

I would like to make it so that the scripts fuylly work correctly when on the overriden page itself, not the HTML template. I guess I could make it redirect to the HTML template, but I really don't want that.

ThePiGuy
  • 11
  • 5
  • Script tags aren't executed via innerHTML which is the intended behavior of HTML/DOM specification. Don't do it this way because such scripts would run in the unsafe page context (assuming you re-add them manually using appendChild), but instead add an iframe that points to an html file in the extension, [example](https://stackoverflow.com/a/25100953). – wOxxOm May 10 '21 at 18:29
  • @wOxxOm Well I have an additional functon that forces them to execute, but it always throws errors because they depend on jQuery, which is defined before the others but regardless they don't seem to connect or anything. Even though the jQuery script is executed, it doesn't seem to import and the other JS files don't execute. Oh well. Guess I'll have to use an iframe. – ThePiGuy May 10 '21 at 19:02
  • Well, you didn't show a full [MCVE](/help/mcve) so I have to guess what happens. Did you properly expose these scripts via web_accessible_resources? Which script says "$ is not defined"? Also why do you load jquery 4 times in the row? Note that when adding script tags manually, the script is running asynchronously so you'll have to use jQuery/$ in the script's `onload` handler! – wOxxOm May 10 '21 at 19:07
  • @wOxxOm I updated the question. Yes, I registered these scripts under web_accessible_resources. All of the scripts in the updated MCVE other than the jQuery scripts throw "$ is not defined". I loaded jQuery four times because the inline script says to write a new script. I put the script tag inside the inline tag into its own tag, to force it to execute and I left the inline tag there to preserve the original document. I loaded jQuery with a local jquery library as it would be loaded regardless, and I kept the ajax.googleapis.com one to preserve the original document. – ThePiGuy May 10 '21 at 19:48
  • Yeah so the problem is that when adding script tags manually, the script is running asynchronously so you'll have to put the code that uses the script in the onload handler. – wOxxOm May 10 '21 at 20:11
  • @wOxxOm But I didn't add the script tags manually... – ThePiGuy May 10 '21 at 20:34
  • You did it in insertAndExecute. "Manually" is probably the wrong word, the point is those scripts weren't executed by the internal parser of HTML - for example, when a page loads the browser runs script tags synchronously by default. – wOxxOm May 11 '21 at 03:38
  • @wOxxOm So what I should do is on the jQuery script import, make an onload handler and make it equal the insertAnd Execute function, and make an inline script that defines insertAndExecute() in the HTML template? – ThePiGuy May 11 '21 at 17:50

1 Answers1

1

When adding script elements individually via appendChild or similar DOM methods, each script with src is running asynchronously i.e. it doesn't wait for the previous script so it may run before jQuery runs. The solution is to wait for load event before running the next script:

async function insertAndExecute() {
  for (const orig of document.querySelectorAll('script')) {
    const copy = document.createElement('script');
    copy.textContent = orig.textContent;
    copy.src = orig.src;
    orig.replaceWith(copy);
    if (copy.src) {
      await new Promise(resolve => copy.addEventListener('load', resolve, {once: true}));
    }
  }
}
wOxxOm
  • 65,848
  • 11
  • 132
  • 136
  • It doesn't do anything. – ThePiGuy May 11 '21 at 21:05
  • It certainly does what it should in my tests. – wOxxOm May 12 '21 at 03:18
  • If it doesn't work for you then you probably didn't reload the extension after editing or you're using an ancient Chrome that doesn't have replaceWith. Anyway, open devtools, set a breakpoint in the code and see what happens. I've used a simplified version of your html e.g. document.head.innerHTML = ''; insertAndExecute(); – wOxxOm May 12 '21 at 18:41
  • I removed the extension and added the extension with the function inserted WITH a callback, and then whenever the page loaded I ran your code snippet in the devtools console and it still did nothing. Additionally, I'm using a very recent version of Chrome (although my browser is actually Brave, but it's a Chromium-based browser so I doubt that's the problem). – ThePiGuy May 12 '21 at 21:08
  • The code in the answer only defines a function so running the code won't automatically run the function. That you should do explicitly. The code is correct and it works so maybe you're not using it correctly e.g. I don't understand what you meant by "with the function inserted WITH a callback". – wOxxOm May 13 '21 at 04:00
  • I meant that I created a callback to the function: `insertAndExecute();`. I really don't understand the problem. I call the function inside the content script, it doesn't load. I run it inside the console and call it, doesn't do anything. Guess I'll have to resort to an iframe after all. – ThePiGuy May 13 '21 at 16:26
  • That's not a callback, but just a call. You didn't show when you call it in the content script so I guess you do it before the html is actually added. The correct place to call it would be inside loadPBSKPage after assigning innerHTML. Anyway, you have devtools at your disposal with a built-in debugger that exists to help you solve problems like this so use it: set a breakpoint inside the code or add `debugger;` statement inside and debug the code line by line, see what happens. – wOxxOm May 13 '21 at 16:41