Chrome扩展manifest v3中tabs.onUpdated.addListener报错排查
问题描述
开发Chrome扩展(Manifest V3)时,使用chrome.tabs.onUpdated.addListener频繁触发错误:Uncaught (in promise) Error: Could not establish connection. Receiving end does not exist.,对background.js与content.js的消息通信机制存疑,附上相关代码寻求问题分析及优化建议。
问题原因
消息发送时机与content script注入不同步
即使判断了changeInfo.status === 'complete',页面加载完成的瞬间,浏览器可能还没完成content.js的注入,此时调用chrome.tabs.sendMessage会找不到接收端,直接报错。非匹配页面也触发消息发送
content script仅配置了特定域名的匹配规则,但chrome.tabs.onUpdated会监听所有标签页的更新事件。当打开非目标域名的页面时,background依然发送消息,而这些页面没有注入content.js,自然会出现接收端不存在的错误。Manifest V3 Service Worker的特性影响
background采用Service Worker实现,浏览器会在闲置时将其休眠或销毁,重新激活时可能重复绑定监听,但核心问题还是发送时机与接收端存在性不匹配。
优化方案
方案一:精准匹配目标页面+捕获发送错误
在发送消息前,先判断当前标签页的URL是否匹配content script的规则,同时用try-catch包裹发送逻辑,避免错误抛出:
chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => { // 定义content script匹配的域名规则 const targetPatterns = [ "https://prisjakt.prismic.io/*", "https://www.prisjakt.nu/*", "https://www.prisjakt.no/*", "https://www.prisjagt.dk/*", "https://hintaopas.fi/*", "https://ledenicheur.fr/*", "https://pricespy.co.uk/*", "https://pricespy.co.nz/*" ]; // 检查当前URL是否匹配目标规则 const isTargetPage = targetPatterns.some(pattern => new URLPattern(pattern).test(tab.url)); if (changeInfo.status === 'complete' && tab.status === 'complete' && isTargetPage) { try { await chrome.tabs.sendMessage(tabId, { type: "RELOADED" }); } catch (err) { console.log("消息发送失败(content script未就绪):", err); } } });
注:
URLPattern是Chrome 95+支持的API,可准确校验URL是否符合manifest中的匹配规则。
方案二:让content script主动处理逻辑(推荐)
放弃background主动推送消息的模式,改为content script注入后直接执行页面逻辑——因为content script仅在匹配页面注入,无需跨页面通信,从根源避免接收端不存在的问题:
function handlePageLogic() { const prismicUrl = document.location.href; const prismicUrlMustExist = /~b/; const prismicUrlMustExist2 = /&b=/; const articleUrlMustExist = /--ec/; if (prismicUrlMustExist.test(prismicUrl) || prismicUrlMustExist2.test(prismicUrl)) { console.info("Hittade artikel. Väntar en sekund."); setTimeout(() => initiate(), 1000); console.info("Försöker hitta sidebar."); } else if (articleUrlMustExist.test(prismicUrl)) { console.info("Hittaden artikel på sajten."); setTimeout(() => initiate_article(), 1000); } else { console.warn("Error: hittade ingen kompatibel sida."); } } // content script默认在document_idle时机注入,此时页面已加载完成,直接执行逻辑 handlePageLogic(); // 监听页面刷新,确保逻辑重新执行 window.addEventListener('load', handlePageLogic);
方案三:调整content script注入时机
在manifest.json的content_scripts配置中显式声明run_at为document_idle(默认值),确保content script在页面完全加载后注入,减少时机不匹配的概率:
"content_scripts": [ { "js": ["content.js"], "matches": [/* 你的匹配规则 */], "run_at": "document_idle" } ]
此方案可靠性不如方案二,页面complete状态与content script注入仍可能存在细微时间差。
原代码参考
background.js
chrome.tabs.onUpdated.addListener ( function (tabId, changeInfo, tab) { if (changeInfo.status === 'complete' && tab.status === 'complete' && tab.url) { chrome.tabs.sendMessage(tab.id, { type: "RELOADED" }); } });
content.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.type === "RELOADED") { let prismicUrl = document.location.href; const prismicUrlMustExist = /~b/; // Normal const prismicUrlMustExist2 = /&b=/; // Efter sökning const articleUrlMustExist = /--ec/; if (prismicUrlMustExist.test(prismicUrl) || prismicUrlMustExist2.test(prismicUrl)) { console.info("Hittade artikel. Väntar en sekund."); setTimeout(() => { initiate(); }, 1000); console.info("Försöker hitta sidebar."); } else if (articleUrlMustExist.test(prismicUrl)) { console.info("Hittaden artikel på sajten."); setTimeout(() => { initiate_article(); }, 1000); } else { console.warn("Error: hittade ingen kompatibel sida."); } } });
manifest.json
{ "name": "Links in Prismic", "action": {}, "manifest_version": 3, "version": "0.7.5", "description": "Adds useful functions to Prismic when working with articles and products.", "permissions": [ "activeTab", "scripting", "contextMenus", "tabs" ], "icons": { "16": "/images/icon16.png", "48": "/images/icon48.png", "128": "/images/icon128.png" }, "background": { "service_worker": "background.js", "type": "module" }, "options_page": "options.html", "content_scripts": [ { "js": [ "content.js" ], "matches": [ "https://prisjakt.prismic.io/*", "https://www.prisjakt.nu/*", "https://www.prisjakt.no/*", "https://www.prisjagt.dk/*", "https://hintaopas.fi/*", "https://ledenicheur.fr/*", "https://pricespy.co.uk/*", "https://pricespy.co.nz/*" ] } ] }
内容的提问来源于stack exchange,提问作者Daniel

