4

I'm migration my extension from V2 to V3. Now all is working fine except for one thing. In my V2 version I did

const actualCode = '(' + function () { 'console.log("demo");' } + `)();`;
const script = document.createElement('script');
script.textContent = actualCode;
(document.head || document.documentElement).appendChild(script);
script.remove();

Note that the console.log("demo") is a simplification of what I need to inject :)

I need to inject some javascript for my chrome-extension-magic to take place.

Now, in V3 this doesn't work anymore. I get the following error in my devtools-console

content.js:23114 
    
   Refused to execute inline script because it violates the following 
   ContentSecurity Policy directive: "script-src 'self'". Either the 
   'unsafe-inline' keyword, a hash ('sha256-tN52+5...6d2I/Szq8='), or a nonce
   ('nonce-...') is required to enable inline execution.

In the migration guide I noticed this section

"content_security_policy": {
   "extension_pages": "...",
   "sandbox": "..."
}

but there is not much description there, so this is magic to me. So I hope someone know can help me with this?

wOxxOm
  • 65,848
  • 11
  • 132
  • 136
Jeanluca Scaljeri
  • 26,343
  • 56
  • 205
  • 333
  • 2
    Use a separate file as shown in [method 1 here](https://stackoverflow.com/a/9517879). It runs asynchronously so it may run later than some scripts of the page. In the future chrome.scripting.registerContentScripts will allow specifying `world`. – wOxxOm Dec 24 '21 at 16:46
  • Thats it, thnx. I've tested it, but in my case this solution doesn't work unfortunately. In my case I need the injected script to run before the page's scripts runs. I've tested this solution and noticed that the injected script now runs too late :( – Jeanluca Scaljeri Dec 24 '21 at 20:50
  • You'll have to keep using MV2. – wOxxOm Dec 24 '21 at 20:55
  • Yes, I came to the exact same conclusion – Jeanluca Scaljeri Dec 24 '21 at 20:57
  • 1
    I see you use it to override XHR/fetch so here's an alternative (in case the site doesn't use the deprecated synchronous XHR): override XMLHttpRequest.prototype.response getter (also responseText) and Response.prototype.text getter (also json, blob, arrayBuffer, formData) via Object.getOwnPropertyDescriptor + Object.defineProperty. These getters are used after the remote server responds so your script should always run earlier. – wOxxOm Dec 24 '21 at 21:28
  • Thats a very interesting suggestion, and might be the solution to my probleem. I will investigate, because it should also work, for example, if the endpoint being called not yet even exists! Thnx for the suggestion! – Jeanluca Scaljeri Dec 25 '21 at 13:30
  • Do you think it would still be possible, if you consider the fact that I have to retrieve the response from `chrome.strorage` (async). It means that to be able to set a value for responseText I need to dispatch a message to the content script, asking for the data. – Jeanluca Scaljeri Dec 28 '21 at 12:09
  • Since chrome.storage is always queried locally (even for sync storage) it should be much faster than a remote request by the site so unless there's a bug in Chrome it should be fine. – wOxxOm Dec 28 '21 at 14:16
  • True, but if I replace `XMLHttpRequest.prototype.response` with my own getter, now as soon my getter is called, it means the server's response has already arrived and it will be to late for me to make that chrome.storage call – Jeanluca Scaljeri Dec 28 '21 at 14:24
  • This is where [MCVE](/help/mcve) is necessary. There may be a solution depending on what the actual workflow and the actual data are e.g. querying the [entire] storage when the content script starts. – wOxxOm Dec 28 '21 at 14:26
  • yes, all this is also way off topic :) Thanks for the help! – Jeanluca Scaljeri Dec 28 '21 at 14:42

2 Answers2

11

Refer to Access variables and functions defined in page context using a content script

Since content scripts are executed in an "isolated world" environment, we can't do some special dom operations in content_script js.

This example will show you how to inject inject.js to web page before document start:

// document_start.js
var s = document.createElement('script');
s.src = chrome.runtime.getURL('inject.js');
s.onload = function() {
    this.remove();
};
(document.head || document.documentElement).appendChild(s);

manifest.json example for ManifestV3

"content_scripts": [
      {
            "matches": ["<all_urls>"],
            "js": ["document_start.js"],
            "run_at": "document_start" //default document end
      }
]
"web_accessible_resources": [{
  "resources": ["inject.js"],
  "matches": ["<all_urls>"]
}]

Updated: 2023-03-08

Support es6 module import

If you want es6 module import, just add s.type="module"

function injectScript(src) {
    const s = document.createElement('script');
    s.src = chrome.runtime.getURL(src);
    s.type = "module"
    s.onload = function() {
        this.remove();
    };
    (document.head || document.documentElement).appendChild(s);
}

injectScript('inject/main.js')

Then you could use es6 import like this:

// inject/main.js
import { initColor } from './init.js'; //import inject/init.js

initColor()
ahuigo
  • 2,929
  • 2
  • 25
  • 45
  • do we need to add any csp line/directive for this `appendChild`? – user5858 May 18 '22 at 19:14
  • @user5858 We should put the path “intject.js” to "web_accessible_resources" lik my example. – ahuigo May 20 '22 at 09:23
  • Dont we need csp policy? Why about if we are injecting inline js code? – user5858 May 21 '22 at 10:43
  • I have this working to inject the script, I can see the tag however objects that are defined in my script are not defined. Any ideas – user1735894 Aug 31 '22 at 19:17
  • This saves me, thanks very much. I was migrating from v2 to v3, originally we are using injected script directly as `script.textContent = content`, in v3 this no longer works, changed to `script.src = chrome.runtime.getURL('test.js');` – Yuefei Ma Mar 08 '23 at 01:57
0

There's a safer way to inject JS instead of using script which is dynamic import. It'd be something like this below:

(async () => {
  const src = chrome.extension.getURL('src/js/main.js');
  const contentScript = await import(src);
  contentScript.main();
})();
Dave Lee
  • 316
  • 3
  • 9