Dev mode produces cross-origin worker URLs — `new Worker()` crashes the render process in Chrome 148+
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 10.5k
- Forks
- 564
- PR merge metrics
- No merged PRs in 30d
Description
Describe the bug
In dev mode, WXT serves extension page scripts from the Vite dev server, so Vite's URL helpers resolve against http://localhost:3000 while the page's own origin is chrome-extension://<id>. As a result, import workerUrl from './worker?worker&url' inside a sidepanel/popup/options entrypoint yields a cross-origin URL.
Per the HTML spec, dedicated workers must be same-origin — new Worker() with a cross-origin URL is required to throw a SecurityError DOMException. Chrome 147 and earlier didn't enforce this and the worker simply ran, so the problem went unnoticed. Starting with Chrome 148 the restriction is enforced — but instead of throwing, the render process is terminated (EXC_BREAKPOINT / SIGTRAP, consistent with an internal CHECK). The user sees the "extension has crashed, click to reload" notification and has to reload from chrome://extensions.
Filed against Chromium here: https://issues.chromium.org/issues/532577557
Chrome enforcing same-origin is correct — the crash-instead-of-throw is their bug. But it also means WXT dev mode currently generates worker URLs that are invalid per spec, and on 148+ that surfaces as a hard crash rather than a catchable error.
Applies to ?worker as well as ?worker&url — the generated WorkerWrapper calls new Worker() with the same localhost URL.
Expected: worker URLs on extension pages resolve to the extension origin in dev mode as well as production, so new Worker() is same-origin.
Workaround — patch Worker in dev to wrap cross-origin URLs in a same-origin Blob shim. The Blob URL inherits the page origin, and the import inside the worker is a plain module fetch, which is CORS-governed rather than origin-governed, so the dev server can still serve the actual module:
if (import.meta.env.DEV) {
const Native = Worker;
class PatchedWorker extends Native {
constructor(scriptUrl: string | URL, options?: WorkerOptions) {
const url = String(scriptUrl);
if (url.startsWith('http://localhost') || url.startsWith('http://127.0.0.1')) {
const shim = `import ${JSON.stringify(url)};`;
const blob = new Blob([shim], { type: 'text/javascript' });
super(URL.createObjectURL(blob), { ...options, type: 'module' });
} else {
super(scriptUrl, options);
}
}
}
(globalThis as Record<string, unknown>).Worker = PatchedWorker;
console.log('PATCHED');
}
Import before creating any worker. The block is stripped from production builds via import.meta.env.DEV. Presumably there's a cleaner fix at the framework level.
Thanks for WXT — it's a pleasure to build extensions with. Happy to test any fix.
Reproduction
https://github.com/PurpleTape/wxt-test
Steps to reproduce
npm installnpm run dev- Click the extension action to open the side panel
The sidepanel logs a http://localhost:3000/... worker URL and calls new Worker() → the extension's render process crashes and must be reloaded from chrome://extensions.
Note the background entrypoint in the repro resolves the same import to a chrome-extension://.../assets/ URL, which is same-origin and does not crash — the only difference is the origin of the URL, not the import syntax.
Requires Chrome 148 or newer. On 147 and earlier the worker is created and runs normally.
System Info
System:
OS: macOS 26.5.2
CPU: (12) arm64 Apple M2 Max
Memory: 1.14 GB / 96.00 GB
Shell: 5.3.9 - /opt/homebrew/bin/bash
Binaries:
Node: 26.4.0 - /opt/homebrew/bin/node
npm: 11.17.0 - /opt/homebrew/bin/npm
pnpm: 11.3.0 - /opt/homebrew/bin/pnpm
Browsers:
Chrome: 151.0.7922.174
Firefox: 153.0.4
Safari: 26.5.2
npmPackages:
wxt: ^0.20.25 => 0.20.27
Used Package Manager
npm
Validations
- Read the Contributing Guidelines.
- Read the docs.
- Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
- Check that this is a concrete bug. For Q&A open a GitHub Discussion or join our Discord Chat Server.
- The provided reproduction is a minimal reproducible example of the bug.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the linked wxt-test reproduction: run npm install, npm run dev, and open the side panel to observe the localhost worker URL and the generated WorkerWrapper. Trace how ?worker and ?worker&url are handled for sidepanel, popup, and options entrypoints, then verify that dev-mode worker URLs are same-origin and the reproduction no longer crashes on Chrome 148+.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript, vite
- Domain
- build-system, devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100