How to evaluate JS in MV3?

145 views
Skip to first unread message

Tommy

unread,
Jan 22, 2024, 6:46:45 AM1/22/24
to Chromium Extensions
My extension allows users to execute little JS snippets on web pages that the user defines. For example:

document.body.style.backgroundColor = 'skyblue';

This JS snippet is stored as a string in storage.local via the chrome.storage API. In MV2 the extension uses eval() to execute the code as soon as the page loads.

However, I'm trying to update my little extension to MV3 and I can't figure how to get this JS snippet to be evaluated like in MV2. I read about the new chrome.userScripts API, but there isn't any practical examples of how to use this API to evaluate a code string stored in storage.local on every page that the user visits.

Does anyone have an MV3 solution for this using chrome.userScripts or another method? Or is this no longer possible in MV3 extensions?

Inter “internetboy” Net

unread,
Jan 22, 2024, 6:49:29 AM1/22/24
to Chromium Extensions, Tommy
There is for sure no reason for storing code as string, what you describe is for sure possible at runtime.

Deco

unread,
Jan 22, 2024, 7:00:41 AM1/22/24
to Tommy, Chromium Extensions
Yes this can be done for MV3, here's what you need to do:
  1. Store the JS Snippet: First, you store your JavaScript code as a string in chrome.storage.local.
  2. Content Script: Write a content script that fetches the stored JS snippet from chrome.storage.local.
  3. Injecting the Script: Use the chrome.scripting.executeScript function to inject your content script into the web pages.
  4. Permissions: Make sure the correct permissions are declared in your manifest.json (like "storage", "scripting", and host permissions for the pages you want your script to run on).
Since you said you cannot find any examples, here is sample code for demonstration purposes for the implementation:
manifest.json
{ "manifest_version": 3, "name": "Your Extension", "version": "1.0", "permissions": [ "storage", "scripting", "<all_urls>" // or specify specific sites ], "background": { "service_worker": "background.js" }, "content_scripts": [ { "matches": ["<all_urls>"], // or specify specific sites "js": ["contentScript.js"] } ],

You'll need a background.js (or similar) which listens for changes in your storage.local event, for example:
chrome.storage.onChanged.addListener((changes, namespace) => { if (namespace === "local" && changes.yourSnippetKey) { // Inject the content script when the stored code changes chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { chrome.scripting.executeScript({ target: { tabId: tabs[0].id }, files: ['contentScript.js'] }); }); } });
Finally, a contentScript.js (or similar) that fetches the stored JS snippet for execution:
chrome.storage.local.get(['yourSnippetKey'], function(result) { if (result.yourSnippetKey) { const script = result.yourSnippetKey; try { // Execute the script (new Function(script))(); } catch (error) { console.error('Error executing the stored script:', error); } } });

This uses new Function(script) instead of eval() to execute the string, it should work for your use case, just be wary of the dynamic execution of JS code here, handle it appropiately.

Thanks,
Deco

--
You received this message because you are subscribed to the Google Groups "Chromium Extensions" group.
To unsubscribe from this group and stop receiving emails from it, send an email to chromium-extens...@chromium.org.
To view this discussion on the web visit https://groups.google.com/a/chromium.org/d/msgid/chromium-extensions/8b7b7b77-587a-4326-9570-3a1add56ac1dn%40chromium.org.

Oliver Dunk

unread,
Jan 22, 2024, 7:17:57 AM1/22/24
to Deco, Tommy, Chromium Extensions
I would expect the Function constructor to be blocked as well - from memory that is covered by the script-src directive in the CSP.

The recommended path in this case would be the chrome.userScripts API: https://developer.chrome.com/docs/extensions/reference/api/userScripts
Oliver Dunk | DevRel, Chrome Extensions | https://developer.chrome.com/ | London, GB


Deco

unread,
Jan 22, 2024, 7:43:15 AM1/22/24
to Oliver Dunk, Tommy, Chromium Extensions
Oh yeah this is correct, it would be blocked by the CSP, my omission.

Adjusted code for OP to use userScripts:
manifest.json:
{ "manifest_version": 3, "name": "Your Extension", "version": "1.0", "permissions": [ "storage", "scripting", "userScripts", "<all_urls>" ], "background": { "service_worker": "background.js" }, ... }
background.js
chrome.runtime.onInstalled.addListener(() => { chrome.userScripts.register({ js: [{ file: 'userScript.js' }], matches: ['<all_urls>'], // Adjust this to target specific URLs if needed runAt: 'document_idle' }); });

userScript.js
chrome.storage.local.get(['yourSnippetKey'], function(result) { if (result.yourSnippetKey) { // Here, you can directly insert and execute the script // respecting the page's CSP const scriptContent = result.yourSnippetKey; const scriptElement = document.createElement('script'); scriptElement.textContent = scriptContent; (document.head || document.documentElement).appendChild(scriptElement); scriptElement.remove(); } });

Note: @OP i highly advise you read the documentation guidelines for this, the code provided is just for demonstration purposes.

Thanks,
Deco

Oliver Dunk

unread,
Jan 22, 2024, 7:47:51 AM1/22/24
to Deco, Tommy, Chromium Extensions
You should actually be able to skip a step there - userScripts.register can take a `js.code` parameter where you should be able to provide the code directly :)
Oliver Dunk | DevRel, Chrome Extensions | https://developer.chrome.com/ | London, GB

Deco

unread,
Jan 22, 2024, 7:54:47 AM1/22/24
to Oliver Dunk, Tommy, Chromium Extensions
I appear to have been outmatched. ;) 

Oliver Dunk

unread,
Jan 22, 2024, 7:58:18 AM1/22/24
to Deco, Tommy, Chromium Extensions
Still very much appreciate you sharing code snippets Deco!

I just wanted to clarify in this case since creating script tags is not something we want to encourage and may be flagged during review.
Oliver Dunk | DevRel, Chrome Extensions | https://developer.chrome.com/ | London, GB

Deco

unread,
Jan 22, 2024, 8:00:10 AM1/22/24
to Oliver Dunk, Tommy, Chromium Extensions
Haha yeah not a problem, guidance for folk is the ultimate goal, the more the merrier. :)

Cheers,
Deco

wOxxOm

unread,
Jan 22, 2024, 9:09:57 AM1/22/24
to Chromium Extensions, Deco, Tommy, Chromium Extensions, Oliver Dunk
>  I just wanted to clarify in this case since creating script tags is not something we want to encourage 

You must have meant dynamic code in script tags via textContent or a similar method, not script tags per se. Phrasing is important. 

>  and may be flagged during review.

Super popular methods like script tags + dynamic code in textContent must be explicitly stated in the rules to avoid ambiguity and fear-mongering.

Tommy

unread,
Jan 23, 2024, 5:06:01 AM1/23/24
to Chromium Extensions, wOxxOm, Deco, Tommy, Chromium Extensions, Oliver Dunk
Thank you everyone for your assistance. I was able to make the users' JS snippets execute on page load based on this example. My next challenge is to be able to allow the users to execute the JS code when they hit a keyboard combo, not page load. So far the easiest solution I've seen is to add a <script> tag, but I don't like that it might get my extension rejected or (worst scenario) banned from the store. I'm currently experimenting with using userScripts messaging based on this solution.
Reply all
Reply to author
Forward
0 new messages