Search code examples
javascripthtmlgoogle-chromedomgoogle-chrome-extension

How do I scripts work in HTML template on injection on the overriden page? (Importing jQuery)


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.


Solution

  • 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}));
        }
      }
    }