feat: add WebExtension debugging support via extensionPath config#2361
Draft
noorez wants to merge 2 commits into
Draft
feat: add WebExtension debugging support via extensionPath config#2361noorez wants to merge 2 commits into
noorez wants to merge 2 commits into
Conversation
Author
|
@microsoft-github-policy-service agree |
0d07209 to
1bc9a3d
Compare
noorez
commented
May 20, 2026
Author
There was a problem hiding this comment.
hmm...on second thought, is this too lax now? initial testing showed that a real error wasnt allowed to slip through but perhaps some more aggressive timing checks are warranted?
e326e5d to
9ee05fd
Compare
Adds an `extensionPath` property to Chrome/Edge launch and attach configs that enables debugging unpacked browser extensions (MV2/MV3) without manual target picking or workarounds. When set: - Launch: auto-injects --load-extension and filters CDP targets to the extension's service worker or background page - Attach: same target filter, works with web-ext or any externally launched Chrome - Source maps: chrome-extension://<id>/path resolves to extensionPath/path using a UUID-ignoring regex (same pattern as vscode-firefox-debug) After the main target is attached, the resolved extension ID is pinned in the path resolver so subsequent source URLs are matched only against that exact extension origin, preventing any other loaded extensions from being incorrectly resolved or attached to. Also fixes a race condition where Inspector.workerScriptLoaded arrives on the service worker's CDP session before createSession() is called, which previously threw an unhandled error. Adds a "Chrome: Launch Extension" configuration snippet for discoverability in the Add Configuration menu. Closes microsoft#945 (WebExtension debugging support) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous fix suppressed all messages for unregistered sessions. A command *response* arriving on an unknown session would indicate a real bug (we only send commands after createSession()) and should still throw so it isn't silently swallowed. Only CDP events (object.method is set) are safe to drop — they are fire-and-forget with no waiting caller.
9ee05fd to
b275e42
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an
extensionPathproperty to Chrome/Edge launch and attach configurations, enabling debugging of unpacked browser extensions (MV2 background pages, MV3 service workers) without manual target picking or workarounds.Closes #945
Background
Issue #945 was closed as out-of-scope citing two blockers:
page-type CDP targets; service workers were silently skipped (noted as "easy to fix")chrome-extension://<id>/...where<id>changes each session with a fresh profile, making static path mapping configuration impossible (noted as the "hard" blocker)vscode-firefox-debug solves the equivalent Firefox problem (
moz-extension://<uuid>/...) with a UUID-ignoring regex. The same approach works for Chrome.Changes
src/configuration.tsextensionPath: string | nullonIChromiumBaseConfiguration(Chrome + Edge, launch + attach)src/targets/browser/browserLauncher.ts--load-extensioninto Chrome args; overridesgetFilterForTargetto match extension targets; pins discovered extension ID post-attachsrc/targets/browser/browserAttacher.tsgetTargetFiltersrc/targets/browser/browserPathResolver.tschrome-extension://<id>/path→extensionPath/pathresolution;pinExtensionId()to lock to discovered IDsrc/targets/sourcePathResolverFactory.tsextensionPaththrough to resolver optionssrc/build/generate-contributions.tsextensionPathin shared Chromium base schema; adds "Chrome: Launch Extension" config snippetsrc/cdp/connection.tsthrow→warn+return— Chrome sendsInspector.workerScriptLoadedon the service worker session beforecreateSession()completes, causing a noisy unhandled errorpackage.nls.jsonHow It Works
Target filtering: When
extensionPathis set, the CDP target filter acceptsservice_workerandpagetargets whose URL starts withchrome-extension://, rejecting all others (normal tabs, devtools, etc.).Source map resolution: A UUID-ignoring regex initially matches any 32-character extension ID. Once the main target attaches, the real ID is extracted from the target URL and pinned — all subsequent
chrome-extension://source URLs are matched only against that exact origin, so other loaded extensions cannot interfere.page-type CDP targets; service workers were silently skipped (noted as "easy to fix")chrome-extension://<id>/...where<id>changes each session with a fresh profile, making static path mapping configuration impossible (noted as the "hard" blocker)vscode-firefox-debug solves the equivalent Firefox problem (
moz-extension://<uuid>/...) with a UUID-ignoring regex. The same approach works for Chrome.Changes
src/configuration.tsextensionPath: string | nullonIChromiumBaseConfiguration(Chrome + Edge, launch + attach)src/targets/browser/browserLauncher.ts--load-extensioninto Chrome args; overridesgetFilterForTargetto match extension targets; pins discovered extension ID post-attachsrc/targets/browser/browserAttacher.tsgetTargetFiltersrc/targets/browser/browserPathResolver.tschrome-extension://<id>/path→extensionPath/pathresolution;pinExtensionId()to lock to discovered IDsrc/targets/sourcePathResolverFactory.tsextensionPaththrough to resolver optionssrc/build/generate-contributions.tsextensionPathin shared Chromium base schema; adds "Chrome: Launch Extension" config snippetsrc/cdp/connection.tsthrow→warn+return— Chrome sendsInspector.workerScriptLoadedon the service worker session beforecreateSession()completes, causing a noisy unhandled errorpackage.nls.jsonHow It Works
Target filtering: When
extensionPathis set, the CDP target filter acceptsservice_workerandpagetargets whose URL starts withchrome-extension://, rejecting all others (normal tabs, devtools, etc.).Source map resolution: A UUID-ignoring regex initially matches any 32-character extension ID. Once the main target attaches, the real ID is extracted from the target URL and pinned — all subsequent
chrome-extension://source URLs are matched only against that exact origin, so other loaded extensions cannot interfere.Usage
Launch — VS Code owns Chrome, no external tooling needed:
{ "type": "pwa-chrome", "request": "launch", "name": "Debug Extension", "extensionPath": "${workspaceFolder}/dist", "webRoot": "${workspaceFolder}", "sourceMapPathOverrides": { "webpack://<name>/./src/*": "${workspaceFolder}/src/*" } } --load-extension is injected automatically. The debugger attaches to the extension's background service worker or page without any manual target selection. Attach — bring your own Chrome launcher (web-ext, a shell script, etc.): { "type": "pwa-chrome", "request": "attach", "port": 9222, "name": "Debug Extension (attach)", "extensionPath": "${workspaceFolder}/dist", "webRoot": "${workspaceFolder}", "sourceMapPathOverrides": { "webpack://<name>/./src/*": "${workspaceFolder}/src/*" } } --- Not Included - Auto-reload on file change — vscode-firefox-debug implements this via reloadDescriptor. The Chrome equivalent requires calling chrome.runtime.reload() via Runtime.evaluate then re-attaching to the new service worker target. Left as a follow-up; web-ext run --target=chromium covers this in the interim. - Content script first-class support — content scripts run in normal page targets and are already reachable. extensionPath handles source map resolution for them as well when chrome-extension:// URLs appear in their sourcemaps. --- Testing the Branch git clone https://github.com/noorez/vscode-js-debug.git cd vscode-js-debug git checkout feat/webextension-extensionpath npm install && npm run compile Then open an extension workspace with the dev build loaded: code --extensionDevelopmentPath="$(pwd)/dist" \ --enable-proposed-api=ms-vscode.js-debug \ /path/to/your/extension Add a pwa-chrome launch config with "extensionPath": "${workspaceFolder}/dist" (pointing to your built extension) and press F5. The debugger should attach directly to the service worker — no manual target picking. Community testing reports very welcome, especially on: - MV2 background pages - MV3 service workers - Microsoft Edge (pwa-msedge) - Extensions with TypeScript + sourcemaps