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);