From d5088835a78bf3f9249a172b1729e0046e2cb7ed Mon Sep 17 00:00:00 2001 From: Aaron Queen Date: Fri, 4 Sep 2026 03:06:56 -0600 Subject: [PATCH] fix(wxt): tolerate the content script reload race in MV3 dev --- .../virtual/utils/reload-content-scripts.ts | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/packages/wxt/src/virtual/utils/reload-content-scripts.ts b/packages/wxt/src/virtual/utils/reload-content-scripts.ts index 5d3b4363e..ccfbc1b5d 100644 --- a/packages/wxt/src/virtual/utils/reload-content-scripts.ts +++ b/packages/wxt/src/virtual/utils/reload-content-scripts.ts @@ -46,13 +46,30 @@ export async function reloadManifestContentScriptMv3( ]); } else { logger.debug('Registering new content script...'); - await browser.scripting.registerContentScripts([ - { - ...contentScript, + try { + await browser.scripting.registerContentScripts([ + { + ...contentScript, + id, + css: contentScript.css ?? [], + }, + ]); + } catch (err) { + // Two reload events for the same entrypoint can both read an empty + // registration above and both register; the loser throws "Duplicate + // script ID". The winner is registering this same script and reloads + // the tabs once it lands, so the loser has nothing left to do. It must + // not fall back to updateContentScripts (the winner's registration is + // not complete yet, so that throws "not fully registered") and must not + // reload tabs (they would come back before the script is registered). + const message = String((err as Error)?.message ?? err); + if (!message.includes('Duplicate script ID')) throw err; + logger.debug( + 'Lost a registration race, the concurrent register owns it', id, - css: contentScript.css ?? [], - }, - ]); + ); + return; + } } await reloadTabsForContentScript(contentScript);