User Script can't access chrome.runtime.getURL and chrome.storage.local

52 views
Skip to first unread message

Thien Hoang

unread,
Oct 23, 2024, 4:48:54 AM (5 days ago) Oct 23
to Chromium Extensions
Hi all,

It appears to me that the user script running in USER_SCRIPT world doesn't have access to chrome.runtime.getURL or chrome.storage.local APIs. Is this intentional by design or do I need to configure somewhere?

Another topic: do you have a list of what's exposed to USER_SCRIPT world, MAIN world, and content script (ISOLATED) world? The official documentation doesn't explain this concept in depth and I'd like to learn more. At first, I thought only the MAIN world can access the `document.body`, but then I found out the USER_SCRIPT world can as well if I inject it with `runAt: 'document_end'`. I'm looking for a documented list to reduce these trials and errors.

Thank you in advance for the help.

Best,
Thien

woxxom

unread,
Oct 23, 2024, 7:31:46 AM (5 days ago) Oct 23
to Chromium Extensions, Thien Hoang
The intended goal for the userScripts API has always been to expose the messaging API only and there's no way to allow more, but I think that 1) runtime.getURL and runtime.id should be exposed too in the userscript world because this world is still a part of the extension (unlike the main world) as evidenced by the ability to send messages to the extension's scripts directly without using the externally_connectable mechanism, 2) chrome.userScripts.configureWorld should add an option to allow chrome.storage and maybe chrome.i18n, because it would allow the extension to avoid the need to instantiate an additional isolated world that consumes memory and still lacks the API for cross-world secure communication (it's being designed, but even when it's implemented it'll be still slower and more clunky than direct access to the storage API). The current secure workaround is to send a message to the extension, which will read the storage and respond with the result. For convenience you can write a simple polyfill for chrome.storage that uses messaging in this fashion.

You can suggest making these changes in WECG: https://github.com/w3c/webextensions/issues/


> do you have a list of what's exposed to USER_SCRIPT world, MAIN world, and content script (ISOLATED) world?

All worlds run in the same web page in the same JS process thread and they all have access to the same DOM. The only difference is that worlds don't share JS stuff like own variables/functions, the built-in globals like Array, and the "expando" properties on DOM nodes like document.body._foo = 1 which isn't a part of the DOM API.

> access the `document.body`, but then I found out the USER_SCRIPT world can as well if I inject it with `runAt: 'document_end'`

In the beginning (run_at: document_start) there is no `body` or `head` in the document yet, only `document.documentElement` exists (without any child nodes) that represents the root element i.e. <html> in an HTML document. After all scripts at document_start have finished running their first turn of the JS event loop, the web page parsing is resumed and the subsequent elements will be added to DOM progressively by the browser's parser and by the web page scripts. You can use MutationObserver to wait for `document.body` in its callback and add your UI when it appears. Note that you don't have to wait and just call document.documentElement.appendChild to add the UI directly to the root and it'll work just the same, even though it won't be a well-formed HTML document, but practically it doesn't matter in case of appendChild and insertBefore as only the element-based methods like .append, .prepend, .before, .after, and so on, check the validity.

When you use document_end (it's the default timing), scripts run immediately after the DOMContentLoaded event, which is fired when the entire initial html is parsed (the actual DOM may be different if the already loaded scripts modified it). At this point `document.body` is guaranteed to exist even if the initial html didn't have an explicit <body> tag: the parser automatically creates an internal `document.body` element when it encounters an html tag that according to the specification can only occur in a <body> or at DOMContentLoaded event.

Reply all
Reply to author
Forward
0 new messages