I'm working on rewriting a Chrome extension to manifest v3 (damn you, Google monopoly!)
It's targeted towards a certain website, adding a bunch of cool features. This means I have to inject script that can access the page context. The website uses PageSpeed Insights (presumably an older version because I doubt they use eval nowadays), which fetches stringified code and evaluates it as such:
<script src="https://cdn.tanktrouble.com/RELEASE-2021-07-06-01/js/red_infiltration.js+messages.js.pagespeed.jc.vNQfCE2Wzx.js"></script>
<!-- ^ Contains the variables `mod_pagespeed_nEcHBi$9_H = 'function foo(){...}'` and `mod_pagespeed_gxZ81E5yX8 = 'function bar(){...}'` and the scripts below evaluate them -->
<script>eval(mod_pagespeed_nEcHBi$9_H);</script>
<script>eval(mod_pagespeed_gxZ81E5yX8);</script>
Now, I've come up with a method where I override the native eval function, take the string that it being evaluated, I generate a hash and compare it with some stored hashes. If they match, I stop the script from being evaluated and inject mine instead. Works good in theory, and acts as a failsafe if the site updates any code.
The problem lies in the fact that a good 50% of the time, the evaluation happens before I override eval, and it seems to relate to cache. If I do a hard reset (Ctrl+Shift+R) it works most of the time and injects my scripts instead as intended. However, a normal reload/site load, and it won't work.
manifest.json
    ...
    "content_scripts": [{
            "run_at": "document_start",
            "js": ["js/content.js"],
            "matches": [ "*://*.tanktrouble.com/*" ]
        }
    ], ...
content.js (content script)
class GameLoader {
    constructor() {
        // Preload scripts, why not?
        this.hasherScript = Object.assign(document.createElement('script'), {
            'src': chrome.runtime.getURL('js/hasher.js'),
            'type': 'module' // So we can import some utils and data later on
        });
        // These custom elements are a hacky method to get my chrome runtime URL into the site with element datasets.
        this.extensionURL = document.createElement('tanktroubleaddons-url');
        this.extensionURL.dataset.url = chrome.runtime.getURL('');
    }
    observe() {
        return new Promise(resolve => {
            const observer = new MutationObserver((mutations, observer) => {
                for (const mutation of mutations) {
                    for (const node of mutation.addedNodes) {
                        if (node.tagName === 'HEAD') {
                            node.insertBefore(this.extensionURL, node.firstChild); // extensionURL is used in hasherScript.js.
                            node.insertBefore(this.hasherScript, node.firstChild); // Inject the hash module
                            resolve(), observer.disconnect();
                        }
                    }
                }
            })
            .observe(document.documentElement, { childList: true, subtree: true });
        });
    }
}
const loader = new GameLoader();
loader.observe();
hasher.js (this.hasherScript)
import ScriptHashes from '/config/ScriptHashes.js'; // The script hashes previously mentioned. The format is { '7pqp95akl2s': 'game/gamemanager.js' ... }
import Hasher from '/js/utils/HashAlgorithm.js'; // Quick hash algorithm
/**
Here we implement the aforementioned hacky method for the extension URL.
 */
const nodeData = document.querySelector('tanktroubleaddons-url'),
extensionURL = nodeData.dataset.url;
window.t_url = function(url) {
    return extensionURL + url;
}
// Change the native eval function and generate a hash of the script being evaluated.
const proxied = eval;
const hashLength = ScriptHashes.length;
window.eval = function(code) {
    if (typeof code === 'string') {
        const codeHash = Hasher(code),
        match = ScriptHashes.hashes[codeHash]; // Check the newly generated hash against my list of hashes. If match, it should return a path to use be used in script fetching.
        if (match) {
            // Synchronous script fetching with jQuery. When done, return null so original script won't be evaluated.
            $.getScript(t_url('js/injects/' + match), () => {
                return null;
            });
        }
    }
    return proxied.apply(this, arguments);
}
Weird behaviour
I have noticed weird behaviour when changing hasherScript to a regular script rather than a module. If I exclude the type paramter and paste the imports directly into hasher.js, things load and work perfectly. Scripts get evaluated every time et cetera. It makes me wonder if it's a synchronous/asynchronous issue.
I haven't been able to find anything on this, unfortunately.
Thanks in advance! :)
 
    