Skip to content

feat(extensions): add chrome.userScripts and chrome.offscreen - #187

Open
seidnerj wants to merge 4 commits into
samuelmaddock:masterfrom
seidnerj:feat/offscreen-userscripts
Open

seidnerj wants to merge 4 commits into
samuelmaddock:masterfrom
seidnerj:feat/offscreen-userscripts

Conversation

@seidnerj

@seidnerj seidnerj commented Oct 6, 2026

Copy link
Copy Markdown

Adds two APIs that MV3-era userscript managers depend on.

chrome.userScripts: register, update, unregister, getScripts and configureWorld. Registered scripts are injected into matching frames of tracked tabs as each frame commits a navigation, honoring matches, excludeMatches, includeGlobs, excludeGlobs, allFrames and runAt.

  • USER_SCRIPT world scripts run in the extension's isolated world through tabs.executeScript, so they can message the extension. MAIN world scripts run in the page.
  • Injection requires a matching host permission, and file sources must resolve inside the extension.

chrome.offscreen: createDocument, closeDocument and hasDocument. Each extension may have one offscreen document, hosted in a hidden window. runtime.getContexts reports it as OFFSCREEN_DOCUMENT.

Known limitations

  • Messages from user scripts arrive on runtime.onMessage/onConnect; onUserScriptMessage/onUserScriptConnect are not fired.
  • configureWorld is accepted but has no effect, so CSP and messaging aren't configurable.
  • Registrations are in memory, so extensions re-register on startup, as they do in Chrome.
  • Scripts apply to navigations after registration.

Testing: new fixture extension and specs for both APIs; the full suite passes (68/68). Also checked manually with a real userscript manager (an MV2 build): its runtime registered through userScripts and a @grant none script ran in the page.

Registered user scripts are injected into matching frames of tracked tabs as each frame commits a navigation, honoring matches, excludeMatches, includeGlobs, excludeGlobs, allFrames and runAt. USER_SCRIPT world scripts run in the extension's isolated world through tabs.executeScript, so they can message the extension over runtime.onMessage/onConnect; MAIN world scripts run in the page. Injection requires a matching host permission, and file sources must resolve inside the extension.
Each extension may create one offscreen document, hosted in a hidden window that loads the extension page. runtime.getContexts reports it as an OFFSCREEN_DOCUMENT context.
…named or bundled

resolvePreloadPath falls back to __dirname, but the bundled entry lives in dist/cjs or dist/esm while the preload is built to dist/. Check both locations.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant