1

I'm developing an extension to run on a public website I have no control over and I need access to the window variables running in the context of that host page. I wrote the code in my content script to getWindowVariables that injects a script that sets attributes on the body node so that it can be returned. It runs but I get a CSP error, as expected, I'm sure the page doesn't like this. To resolve the error it says to add a hash sha256… or a nonce, but I would expect that this needs to be added to the host page, that it would allow a foreign script to run, and of course I can't do that.

Is there a way in a chrome extension content script to get to the host page window variables that requires CSP from the chrome extension content script? I read some about user_script bypassing CSP but I'm not sure about support for that in Chrome.

The only twist I can think of is that I include the content scripts as dynamic modules for the ease of using import for other scripts, and perhaps that is causing an issue, I don't know; I would very much like to keep the use of modules.

Also note, just to be clear, I'm not trying to include external scripts into my extension, CSP is complaining because the extension is trying to execute code in the context of the host's world.

contentScript-portfolio-module-loader.js

// Dynamically load contentScript-portfolio.js as a module
(async () => {
    const contentScript = await import(chrome.runtime.getURL('contentScript-portfolio.js'));
    contentScript.main();
  })();

manifest.json

  "content_scripts": [
      {
        "matches": ["https://finance.com/portfolio/positions*"],
        "run_at": "document_idle",
        "js": ["contentScript-portfolio-module-loader.js"]
      }
  ],
  "web_accessible_resources": [
    {
      "resources": ["contentScript-portfolio.js", "contentScript-trade-options.js", "util.js"],
      "matches": ["https://finance.com/*"]
    }
  ],

contentScript-portfolio.js

import { parseObjectFromScript } from './util.js';

// Get window vars from the host context
function getWindowVariables(variables) {
    let ret = {};

    let scriptContent = "";
    for (var i = 0; i < variables.length; i++) {
        const currVariable = variables[i];
        scriptContent += "if (typeof " + currVariable + " !== 'undefined') document.body.setAttribute('tmp_" + currVariable + "', JSON.stringify(" + currVariable + "));\n"
    }

    let script = document.createElement('script');
    script.id = 'tmpScript';
    script.appendChild(document.createTextNode(scriptContent));
    (document.body || document.head || document.documentElement).appendChild(script); // <-- CSP ERROR HERE

    for (var i = 0; i < variables.length; i++) {
        const currVariable = variables[i];
        ret[currVariable] = JSON.parse(document.body.getAttribute("tmp_" + currVariable));
        document.body.removeAttribute("tmp_" + currVariable);
    }

    document.getElementById(script.id).remove();
    return ret;
}

Thanks for any help.

Posting the solution for the benefit of others.

Add getPositions.js filename to web_accessible_resources in manifest.json

getPositions.js

// Don't include circular properties
function replacerFunc() {
    const visited = new WeakSet();
    return (key, value) => {
        if (typeof value === "object" && value !== null) {
            if (visited.has(value)) {
                return;
            }
            visited.add(value);
        }
        return value;
    };
}

// The app lazy loads this window var, wait for it.
function getPositions() {
    if (window.positions) {
        document.body.setAttribute('tmp_positions', JSON.stringify(window.positions, replacerFunc()));
    } else {
        window.requestAnimationFrame(getPositions);
    }
}

window.requestAnimationFrame(getPositions);

contentScript-portfolio.js

var myPositions = null;

function getPositions() {
    return new Promise((resolve, reject) => {
        let script = document.createElement('script');
        script.id = 'tmpScript';
        script.src = chrome.runtime.getURL('getPositions.js');
        // inject and run getPositions.js in host execution context
        (document.body || document.head || document.documentElement).appendChild(script);
        // Wait for the var to show up because it is lazy loaded on the host page.
        let posInterval = setInterval(() => {
            pos = JSON.parse(document.body.getAttribute('tmp_positions'));
            if (pos) {
                clearInterval(posInterval);
                document.getElementById(script.id).remove();
                document.body.removeAttribute('tmp_positions');
                resolve(pos);
            }
        }, 500);
    });
}

document.onreadystatechange = (e) => {
    if (document.readyState === 'complete') {
        getPositions().then((r) => {
            myPositions = r;
        });
    }
};

Unfortunately, the setAttribute and getAttribute calls are in the two different files since the host page's window var is lazy loaded.

bjjer
  • 43
  • 1
  • 7
  • Okay that makes sense. It looks like you are trying to write the script dynamically and then inject it into page context. You cannot do that. You need to define the script and add it under `web_accessible_resources`, use `chrome.runtime.getURL()` and set that to the src for the script, then inject that. Store whatever variables you want to look for in a different attribute on the body element. Also, alternatively, you could use `executeScript` by adding the `scripting` permission and run that in page context. – Jridyard Nov 20 '22 at 00:22
  • Thanks @Jridyard, that did the trick. Posting the working code to give back to the community, even though the question was closed. – bjjer Nov 21 '22 at 01:33

0 Answers0