Migrating Chrome extension to Manifest V3 from V2

2,033 views
Skip to first unread message

Activity Recorder

unread,
Nov 15, 2022, 8:22:18 AM11/15/22
to Chromium Extensions
Steps to reproduce the problem:
1. In manifest file specify background.js as service worker.
"background": {
"service_worker": "background.js"
}
2. In "background.js" file use document.write() to inject the script into the current tab.
3. This gives error "Error handling response: ReferenceError: document is not defined".

Problem Description:
Trying to migrate our chrome extension to Manifest V3 from V2.
In the console seeing the error "Error handling response: ReferenceError: document is not defined".
Tried scenarios:
1. We did updated getCurrentTab().
2. We did "run_at": "document_end" in content_scripts of manifest.json
3. Moved entire code in background.js file to script.js and tried to access it using
chrome.scripting.executeScript({
target: {tabId: tab.id},
files: ['script.js']
});
4. Tried to load the file which needed to be added with document.wirte() like
chrome.scripting.executeScript({
target: {tabId: tab.id},
files: ["cookieManagement.js","events.js","contentScript.js","jszip.min.js","filesaver.min.js"]
});

Please suggest an alternative which can be used in service worker in the same way we can use document.write() in the background page. 

Ryan Guilbault

unread,
Nov 15, 2022, 9:11:43 AM11/15/22
to Chromium Extensions, app.s...@gmail.com
do you need to use document.write() or can you inject the script using another technique, e.g. inserting a script tag with a src pointing to a url to your extension package (https://developer.chrome.com/docs/extensions/reference/runtime/#method-getURL)?

Activity Recorder

unread,
Nov 16, 2022, 5:48:05 AM11/16/22
to Chromium Extensions, rguil...@meditech.com, Activity Recorder
Thanks for the response.

We actually injecting the script like below in the method which will run on extension is clicked.
document.write('<script src="events.js"></script>');
This approach is followed in MV2. Now post converting to MV3, this implementation giving error.

Ryan Guilbault

unread,
Nov 16, 2022, 9:25:22 AM11/16/22
to Activity Recorder, Chromium Extensions
so the service worker/background script should be communicating to the content script to do DOM things, such as interacting with the document object. but this (and its variants):

chrome.scripting.executeScript({
target: {tabId: tab.id},
files: ['script.js']
});

is certainly a proper way to get your content script injected into the page and should be able to interact with the document object.

at minimum, it is not inappropriate to check the document load state, e.g.:

    if (document.readyState === DOC_STATE.complete) {
        onDocumentLoad(new Event('_loaded'))
    } else {
        document.addEventListener('DOMContentLoaded', onDocumentLoad)
    }

which means document should not issue a reference error.

could you provide clipped details of your manifest file and the service worker script that is calling chrome.scripting.executeScript?
                
Subscribe to receive emails from MEDITECH or to change email preferences.

Activity Recorder

unread,
Nov 23, 2022, 7:34:45 AM11/23/22
to Chromium Extensions, rguil...@meditech.com, Chromium Extensions, Activity Recorder
The document is getting added from the page that is being visited and showing an error like below

Refused to load the script 'https://<SITE_URL>/cookieManagement.js' because it violates the following Content Security Policy directive: "script-src 'self' 'wasm-unsafe-eval'". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.

Ryan Guilbault

unread,
Nov 23, 2022, 9:15:25 AM11/23/22
to Activity Recorder, Chromium Extensions
so there you go -- all is revealed in the error message:

Refused to load the script 'https://<SITE_URL>/cookieManagement.js' because it violates the following Content Security Policy directive: "script-src 'self' 'wasm-unsafe-eval'". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.

the server is setting a Content Security Policy (or its not and Chrome's default is kicking in) that precludes the inclusion of unidentified scripts. I'm actually unfamiliar with 'script-src-elem' -- but that is what it is stating is lacking:


looks like it's been around for a while.

do you control the server side as well? note: this variant seems like the goal posts are moving...if this javascript is needed for your extension, you should package it with the extension and pull it in via:


or by specifying it to be injected by Chrome (which I know you previously stated that you tried, but that should be workable -- just keep in mind that you have to account for asynchronous loading, so it is plausible if you're relying on a strict order to the javascript getting loaded, you may need to add event handlers to trigger proper initialization ordering).

thus far, what you are trying to do does not appear to be a deficiency with MV3, but MV3 constraints may be impacting how your application behaves. I faced the same problems where prior to the service worker I could rest assured that once the extension was loaded it wouldn't be unloaded unless the page went away, the tab/browser went away or the extension was disabled/removed. with the service worker, you have to use tricks to keep it up and running *or* make your extension resilient to the service worker going away and (hopefully, as far as I know it is still buggy) coming back. point is: the problem you have presented thus far should be workable in MV3 using standard techniques.

Activity Recorder

unread,
Nov 28, 2022, 6:07:31 AM11/28/22
to Chromium Extensions, rguil...@meditech.com, Chromium Extensions, Activity Recorder
Adding the following line to manifest.json didn't helped in resolving the issue.
    "content_security_policy": {
        "extension_pages": "default-src 'self' 'wasm-unsafe-eval'"
      }

Extension will be activated by clicking on the extension icon. The line is:
chrome.action.onClicked.addListener(onActionClicked);

onActionClicked() will call the method onActionClick()
    chrome.storage.local.get({
        debugWinKey: false
    }, function (items) {
        var debugWinKey = items.debugWinKey;
        if (null != debugWinKey) {
            debugWin = debugWinKey;
        }
        onActionClick();
    });

onActionClick() has the code
        if (debugWin && !isDebugWinAlreadyLauched) {
            isDebugWinAlreadyLauched = true;
            tabStartRecordingDebugWin(tab);
        } else {

            chrome.scripting.executeScript({
                target: {tabId: tab.id},
                files: ['script.js']
            });
            /*document.write('<script src="cookieManagement.js"></script>');

            document.write('<script src="events.js"></script>');
            document.write('<script src="contentScript.js"></script>');
            document.write('<script src="jszip.min.js" type="text/javascript"></script>');
            document.write('<script src="filesaver.min.js"></script>');*/
            resetForNewRecording();
            tabStartRecording(tab);
        }
Commented(/* */) code is being used earlier and moved to 'script.js' to load the files.

Snap of manifest.json:
Capture.PNG

On investigating deeply found that these files are getting added to background script itself in MV2 based on a condition. The idea is to achieve the same using service workers.

Ryan Guilbault

unread,
Nov 28, 2022, 9:36:14 AM11/28/22
to Activity Recorder, Chromium Extensions
what if you try:

    "content_security_policy": {
        "extension_pages": "script-src-elem 'unsafe-inline'"
      }

just for the sake of proving you can get past the CSP block. I do not recommend that for a solution, but it will put you on a track to sort out the proper solution to use. for example, since you control injecting the script, you can generate a nonce attribute for it that you allow list, e.g.:

            document.write('<script src="cookieManagement.js" nonce="ABBA"></script>');
            document.write('<script src="events.js" nonce="ABBA"></script>');
            document.write('<script src="contentScript.js" nonce="ABBA"></script>');
            document.write('<script src="jszip.min.js" type="text/javascript" nonce="ABBA"></script>');
            document.write('<script src="filesaver.min.js" nonce="ABBA"></script>');
            resetForNewRecording();
            tabStartRecording(tab);
        }

then pair it with the CSP:

    "content_security_policy": {
        "extension_pages": "script-src-elem 'nonce-ABBA'"
      }

this would be a bit better, though using the hash method *may* be slightly better since you would have to pick a nonce-to-rule-them-all given the permissive injection...


Reply all
Reply to author
Forward
0 new messages