Chrome扩展多标签打开时service worker的onMessage.addListener触发两次问题
问题描述
当打开2个及以上扩展标签页时,chrome.runtime.sendMessage与chrome.runtime.onMessage.addListener会运行两次,仅单个标签页时功能正常。曾尝试用chrome.storage.local存储布尔值判断请求执行状态但无效,希望保留Service Worker以支持弹窗关闭后后台继续处理请求。
相关代码
Main.tsx
const generate = async (type: dalleReqTypes): Promise<void> => { try { await chrome.runtime .sendMessage({ action: "dalle-fetch", type: type, request: { n: numToGen, prompt: prompt, image: selectedImg, }, }) .catch((err) => { console.log(err); }); } catch (err) { console.log(err); } };
Service_worker.ts
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.action === "dalle-fetch") { const dalleReq = message.request as DalleRequest; getDalleGenerations(dalleReq) .then(() => { sendResponse({ result: "success" }); }) .catch((error) => { sendResponse({ result: "error", dalleReq: error.toString() }); }); });
Manifest.json
{ "version": "0.0.1", "manifest_version": 3, "name": "App", "action": { "default_popup": "index.html", "default_title": "App", "default_icon": { "16": "images/camera.png", "48": "images/camera.png", "128": "images/camera.png" } }, "background": { "service_worker": "service_worker.js", "type": "module" }, "minimum_chrome_version": "92", "host_permissions": ["<all_urls>"], "permissions": ["contextMenus", "desktopCapture", "storage", "activeTab"], "icons": { "16": "images/camera.png", "48": "images/camera.png", "128": "images/camera.png" } }
App.tsx
function App() { return ( <HashRouter> <Suspense fallback={<div></div>}> <Routes> <Route index path="/" element={<Main isPopup={true} />} /> <Route path="/browser" element={<Main isPopup={false} />} /> <Route path="/cropper" element={<Cropper />} /> <Route path="/settings" element={<Settings />} /> </Routes> </Suspense> </HashRouter> ); }
问题成因
- Service Worker监听器重复注册:Manifest V3的Service Worker是事件驱动的,闲置时会被Chrome终止,新事件触发时会重启并重新执行整个脚本。如果监听器注册写在Service Worker顶层,每次重启都会重复添加监听器,导致单条消息被多个监听器处理。
- 消息广播到多上下文:
chrome.runtime.sendMessage会向扩展所有活跃上下文(标签页、弹窗、Service Worker)广播消息,若标签页/弹窗中也注册了相同的onMessage监听器,每个上下文都会独立处理消息。 - 异步响应未保持通道:Service Worker的
onMessage监听器中存在异步操作,但未返回true告知Chrome保持消息通道开放,可能引发异常触发重复执行。
修复方案
1. 避免Service Worker监听器重复注册
用标志变量控制监听器仅注册一次,同时补充异步响应的返回值:
// service_worker.ts let listenerRegistered = false; if (!listenerRegistered) { chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.action === "dalle-fetch") { const dalleReq = message.request as DalleRequest; getDalleGenerations(dalleReq) .then(() => sendResponse({ result: "success" })) .catch((error) => sendResponse({ result: "error", dalleReq: error.toString() })); // 返回true保持消息通道开放,支持异步响应 return true; } }); listenerRegistered = true; }
2. 限制消息仅发送给Service Worker
直接向Service Worker发送消息,避免广播到其他上下文:
// Main.tsx const generate = async (type: dalleReqTypes): Promise<void> => { try { const registration = await chrome.serviceWorker.getRegistration(); if (registration?.active) { registration.active.postMessage({ action: "dalle-fetch", type: type, request: { n: numToGen, prompt: prompt, image: selectedImg, }, }); } } catch (err) { console.log(err); } };
同时修改Service Worker的消息监听方式:
// service_worker.ts let listenerRegistered = false; if (!listenerRegistered) { self.addEventListener('message', (event) => { const message = event.data; if (message.action === "dalle-fetch") { const dalleReq = message.request as DalleRequest; getDalleGenerations(dalleReq) .then(() => event.source?.postMessage({ result: "success" })) .catch((error) => event.source?.postMessage({ result: "error", dalleReq: error.toString() })); } }); listenerRegistered = true; }
3. 排查前端重复发送逻辑
检查前端代码是否存在多标签页场景下,每个标签页都触发generate函数的情况(如全局storage事件监听导致批量发送),添加条件判断仅让触发操作的标签页发送消息。
内容的提问来源于stack exchange,提问作者Sean
相关产品推荐
相关产品推荐

