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.
> 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.