javascriptgoogle-chromedomgoogle-chrome-extensionfirefox-addon

Chrome Extension: Run custom JS with access to window before any other JS in webpage


I'm writing Chrome extension (manifest v3), the main aim is to override some of the Window native functions and replace them before any other webpages scripts can access them.

I have contentscript.js that has run_at = document_start. The contentscript.js successfully injects my script.js into the webpage DOM either into <html> or <head> tag (by mutation observer).But (!) i cannot force and guarantee my script.js will run first before any other website scripts.

When i debug contentscript.js, the DOM of the page does contain only <html> and my newly injected <script> element that loads script.js. The run_at thus works fine. But, when i step with debugger after last line, instead of stepping into script.js, i'm inside websites script (even though my script.js reference is the first in header above all other referenecs). All would be solved easily if contentscript.js could write directly into the webpages window, but it cannot.

I've tried to add mutator observer inside contentscript.js where i'm able to detect addition of tags into DOM and i'm able to edit/remove them without (?) letting them execute, but i need the page working.

What i've tried:

  1. add debugger; command into any <script> so i can see which script runs first -- everytime it is the websites script, not mine.
  2. adding async=false, defer=false to all scripts -- failed,
  3. disable any <script> by rewriting or smart-disabling them and then re-running them after my script.js registers as read. I'm re-enabling the scripts in order the scripts (a) were observed by observer or (b) by order in DOM from document.querySelectorAll. This approach works for some websites, but some sites throw errors on "not-existing object references" which are then all defined when i check them in console -- for me this seems odd as i evaluate the original scripts in order and look like some race condition by authors of the website (?).

Edit: I've also tried to add a custom <script> with direct script.text = ... code ... but this is refused due to CSP needing either hash, nonce or unsafe-inline (however, i'll try to edit the HTTP header to contain unsafe-inline now).

Is there any hope to have my script running first with a guarantee? Thank you


Solution

  • The problem is that your injector script is loaded via DOM script element's src, so it loads asynchronously and runs after the other scripts loaded by the page.

    The solution is to register the injected script at document_start directly in the context of the page:

    "content_scripts": [
        {
          "matches": ["<all_urls>"],
          "js": ["injected-script.js"],
          "run_at": "document_start",
          "world": "MAIN"
        }
      ]