Type: bug
Does this issue occur when all extensions are disabled?: Not applicable; the repro requires a minimal webview extension.
VS Code Version: reproduced on 1.119.x; likely introduced by #311844
Steps to Reproduce
- Create a minimal extension with the files below.
- Run the extension and execute
Worker import repro: Open.
- The webview prints
starting import..., then the dynamic import never
resolves; the browser eventually surfaces
TypeError: Failed to fetch dynamically imported module.
The same setup worked before the webview service worker started streaming
resource responses through #311844.
Minimal repro:
{
"activationEvents": ["onCommand:workerImportRepro.open"],
"contributes": {
"commands": [
{
"command": "workerImportRepro.open",
"title": "Worker import repro: Open"
}
]
}
}
// extension.ts
import * as vscode from 'vscode';
export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(vscode.commands.registerCommand('workerImportRepro.open', () => {
const panel = vscode.window.createWebviewPanel(
'workerImportRepro',
'Worker import repro',
vscode.ViewColumn.One,
{
enableScripts: true,
localResourceRoots: [context.extensionUri],
},
);
const workerModuleUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, 'media', 'worker-module.js'),
);
panel.webview.html = /* html */ `<!DOCTYPE html>
<html>
<body>
<pre id="log">starting import...</pre>
<script>
const log = document.getElementById('log');
const source = \`
import(${JSON.stringify(String(workerModuleUri))})
.then(() => self.postMessage('import ok'))
.catch(error => self.postMessage('import failed: ' + error.message));
\`;
const url = URL.createObjectURL(new Blob([source], { type: 'text/javascript' }));
const worker = new Worker(url, { type: 'module' });
worker.onmessage = event => log.textContent += '\\n' + event.data;
worker.onerror = event => log.textContent += '\\nworker error: ' + event.message;
</script>
</body>
</html>`;
}));
}
// media/worker-module.js
export const loaded = true;
Diagnosis
#311844 (merged 2026-04-22) relaxed the guard in the
webview service worker so that client.type === 'worker' | 'sharedworker'
reaches processResourceRequest / processLocalhostRequest:
https://github.com/microsoft/vscode/blob/main/src/vs/workbench/contrib/webview/browser/pre/service-worker.js#L307-L313
But the dispatch block only routes when webviewId is non-null:
https://github.com/microsoft/vscode/blob/main/src/vs/workbench/contrib/webview/browser/pre/service-worker.js#L431-L450
For a blob worker created inside a webview iframe, client.url is
blob:https://<hash>.vscode-webview.net/<uuid> — no id searchParam —
so getWebviewIdForClient returns null. The relaxed guard lets that
through, but if (webviewId) is skipped, no postMessage is sent to
the host iframe, the resourceRequestStore entry is never resolved,
and the fetch hangs until requestTimeout() fires.
processLocalhostRequest (lines 460–517) has the same shape and the
same dead-end for worker clients.
Proposed direction
A worker created inside a webview iframe shares the iframe's origin
(blob URLs inherit their creator origin). Resolving the owning iframe
via origin match seems like the smallest fix for the common case, as
long as the service worker only routes when that origin maps to a single
webview id. If multiple webviews share an origin, it should fail closed
instead of sending the request to an arbitrary iframe. Draft PR coming.
Type: bug
Does this issue occur when all extensions are disabled?: Not applicable; the repro requires a minimal webview extension.
VS Code Version: reproduced on 1.119.x; likely introduced by #311844
Steps to Reproduce
Worker import repro: Open.starting import..., then the dynamic import neverresolves; the browser eventually surfaces
TypeError: Failed to fetch dynamically imported module.The same setup worked before the webview service worker started streaming
resource responses through #311844.
Minimal repro:
{ "activationEvents": ["onCommand:workerImportRepro.open"], "contributes": { "commands": [ { "command": "workerImportRepro.open", "title": "Worker import repro: Open" } ] } }Diagnosis
#311844 (merged 2026-04-22) relaxed the guard in the
webview service worker so that
client.type === 'worker' | 'sharedworker'reaches
processResourceRequest/processLocalhostRequest:https://github.com/microsoft/vscode/blob/main/src/vs/workbench/contrib/webview/browser/pre/service-worker.js#L307-L313
But the dispatch block only routes when
webviewIdis non-null:https://github.com/microsoft/vscode/blob/main/src/vs/workbench/contrib/webview/browser/pre/service-worker.js#L431-L450
For a blob worker created inside a webview iframe,
client.urlisblob:https://<hash>.vscode-webview.net/<uuid>— noidsearchParam —so
getWebviewIdForClientreturnsnull. The relaxed guard lets thatthrough, but
if (webviewId)is skipped, nopostMessageis sent tothe host iframe, the
resourceRequestStoreentry is never resolved,and the fetch hangs until
requestTimeout()fires.processLocalhostRequest(lines 460–517) has the same shape and thesame dead-end for worker clients.
Proposed direction
A worker created inside a webview iframe shares the iframe's origin
(blob URLs inherit their creator origin). Resolving the owning iframe
via origin match seems like the smallest fix for the common case, as
long as the service worker only routes when that origin maps to a single
webview id. If multiple webviews share an origin, it should fail closed
instead of sending the request to an arbitrary iframe. Draft PR coming.