feat: add WebExtension debugging support via extensionPath config - #2361
Open
noorez wants to merge 1 commit into
Open
feat: add WebExtension debugging support via extensionPath config#2361noorez wants to merge 1 commit into
noorez wants to merge 1 commit into
Conversation
noorez
marked this pull request as draft
May 19, 2026 17:59
Author
|
@microsoft-github-policy-service agree |
noorez
force-pushed
the
feat/webextension-extensionpath
branch
3 times, most recently
from
May 19, 2026 18:23
0d07209 to
1bc9a3d
Compare
noorez
commented
May 20, 2026
noorez
force-pushed
the
feat/webextension-extensionpath
branch
4 times, most recently
from
May 22, 2026 13:32
b275e42 to
e441bb7
Compare
noorez
marked this pull request as ready for review
May 25, 2026 11:36
noorez
force-pushed
the
feat/webextension-extensionpath
branch
from
June 1, 2026 12:53
b4eee88 to
437150a
Compare
noorez
force-pushed
the
feat/webextension-extensionpath
branch
2 times, most recently
from
June 11, 2026 13:08
5ee30c9 to
65a7ce1
Compare
Author
|
@kyranjamie and @lonr , I noticed you two had expressed interest in the past for this? any thoughts on what I have? |
noorez
marked this pull request as draft
July 6, 2026 19:05
noorez
marked this pull request as ready for review
July 6, 2026 19:07
noorez
force-pushed
the
feat/webextension-extensionpath
branch
from
July 9, 2026 13:38
8e75b45 to
9d0ef52
Compare
noorez
force-pushed
the
feat/webextension-extensionpath
branch
from
July 20, 2026 20:06
9d0ef52 to
5558dd1
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 The extension id is derived offline rather than guessed: Chrome hashes the manifest `key` (or, absent one, the absolute path), takes the first 16 bytes and maps each nibble onto a-p. Deriving it lets the target filter require an exact match, which matters because browsers also expose service workers for their own component extensions, and those can be reported before the extension under debug. This addresses the "non-deterministic extension ID" blocker noted on microsoft#945. An idle MV3 service worker has no CDP target at all, so there is nothing to attach to until something starts it. When no target is present the debugger now calls ServiceWorker.startWorker for the extension's scope, which is what previously required the manual "start the worker in DevTools" dance described in microsoft#1445. Also includes four robustness fixes surfaced by extension debugging, each of which produced spurious errors before: - cdp/connection: Chrome can push events (e.g. Inspector.workerScriptLoaded) on a new service worker session before createSession() completes. Such events are now dropped with a warning rather than throwing. Command *responses* on an unknown session still throw, since those would indicate a real bug. - sourceContainer: removeSource asserted when a source's reference id had been reassigned to a newer Source for the same URL, which happens routinely as service workers restart. Suppressed only for the same-URL case; other mismatches still log. - serviceWorkers: replaced a leftover `debugger;` statement, hit when a delete arrived for an unknown registration, with a verbose log. - resourceProvider: fetching a chrome-extension:// sourcemap threw an opaque "Unsupported protocol" from the HTTP client, escaping before the existing CDP fallback could run. Non-HTTP schemes now return a failed response so the browser, which understands them, can serve the resource. Adds a "Chrome: Launch Extension" configuration snippet so the option is discoverable from the Add Configuration menu. Closes microsoft#945 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
noorez
force-pushed
the
feat/webextension-extensionpath
branch
from
July 27, 2026 22:41
5558dd1 to
6c8a7ba
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