FireFox扩展报错Uncaught(in Promise) Error:接收端不存在问题排查
扩展调试连接错误分析与修复
问题现象
在FireFox的about:debugging页面调试扩展时,多次触发Uncaught(in Promise) Error: Could not establish connection. Receiving end does not exist错误,多次报错后扩展可正常显示根证书信息。
相关代码
background.js
const securityInfo = await browser.webRequest.getSecurityInfo( details.requestId, { certificateChain: true } ); if (securityInfo.state === "insecure" || securityInfo.state === "broken") { browser.runtime.sendMessage("no"); } else if (!securityInfo.isUntrusted && securityInfo.certificates.length > 0) { //if Root Info exists //"subject" property from CertificateInfo Object const root = securityInfo.certificates[securityInfo.certificates.length - 1].issuer; //substring to only include the root CA name (comma seperated list) let rootCA = root.substring(3, root.indexOf(",")); browser.runtime.sendMessage({ rootCA }); //send to popup.js }
popup.js
document.addEventListener("DOMContentLoaded", () => { // ... var faviconImage = document.getElementById('faviconImage'); //Favicon (Logo) var websiteUrlElement = document.getElementById('websiteUrl'); //URL // get the information on the extension browser.tabs.query({ active: true, currentWindow: true }, function (tabs) { const url = tabs[0].url; const favicon = tabs[0].favIconUrl; websiteUrlElement.textContent = url; faviconImage.src = favicon; // Receive message from background.js for CA Info and update html browser.runtime.onMessage.addListener((request) => { if (request.rootCA) { // Check if root CA exists in the request caInfo = request.rootCA; document.getElementById("rootCAInfo").textContent = caInfo; checkCA(url, caInfo); } if (request.secure) { if (request.secure === "no") { siteStatusDivs.notMarked.style.display = "none"; siteStatusDivs.unsecure.style.display = "block"; } } }); });
错误原因
- 消息发送时机不匹配:background.js在web请求触发时立即发送消息,但此时popup可能尚未打开,或者popup的消息监听还未完成注册,导致没有接收端处理消息,触发连接错误。
- 消息格式不一致:background发送的"no"是字符串,而popup里判断的是
request.secure,两者不匹配,同时无接收端时未捕获Promise错误,导致报错抛出。 - popup监听注册滞后:popup的
onMessage监听被嵌套在tabs.query的回调中,进一步延迟了监听注册时间,增加了消息发送时无接收端的概率。
修复方案
方案1:捕获消息发送错误,统一消息格式
修改background.js,封装消息发送逻辑,捕获无接收端的错误,同时统一消息为对象格式,避免格式不匹配:
const securityInfo = await browser.webRequest.getSecurityInfo( details.requestId, { certificateChain: true } ); // 封装消息发送,捕获错误避免抛出未处理Promise异常 const sendPopupMessage = (msg) => { browser.runtime.sendMessage(msg) .catch(err => { // 仅做日志记录,不抛出错误 console.debug("消息发送失败(popup未打开):", err); }); }; if (securityInfo.state === "insecure" || securityInfo.state === "broken") { sendPopupMessage({ secure: "no" }); // 统一用对象格式 } else if (!securityInfo.isUntrusted && securityInfo.certificates.length > 0) { const root = securityInfo.certificates[securityInfo.certificates.length - 1].issuer; let rootCA = root.substring(3, root.indexOf(",")); sendPopupMessage({ rootCA }); }
方案2:由popup主动请求数据(推荐)
这种方式从根源上避免时机问题,只有当popup打开后才发起数据请求:
调整background.js,添加请求监听与数据缓存
// 缓存每个标签的最新安全信息 const tabSecurityCache = {}; // 监听web请求,缓存安全信息 browser.webRequest.onHeadersReceived.addListener(async (details) => { const securityInfo = await browser.webRequest.getSecurityInfo( details.requestId, { certificateChain: true } ); tabSecurityCache[details.tabId] = securityInfo; }, { urls: ["<all_urls>"] }, ["blocking"]); // 响应popup的请求 browser.runtime.onMessage.addListener(async (request, sender, sendResponse) => { if (request.action === "fetchRootCA") { const [currentTab] = await browser.tabs.query({ active: true, currentWindow: true }); const securityInfo = tabSecurityCache[currentTab.id]; if (!securityInfo) { sendResponse({ error: "无可用安全信息" }); return; } if (securityInfo.state === "insecure" || securityInfo.state === "broken") { sendResponse({ secure: "no" }); } else if (!securityInfo.isUntrusted && securityInfo.certificates.length > 0) { const root = securityInfo.certificates[securityInfo.certificates.length - 1].issuer; let rootCA = root.substring(3, root.indexOf(",")); sendResponse({ rootCA }); } return true; // 标记为异步响应 } });
修改popup.js,主动发起请求
document.addEventListener("DOMContentLoaded", () => { var faviconImage = document.getElementById('faviconImage'); var websiteUrlElement = document.getElementById('websiteUrl'); browser.tabs.query({ active: true, currentWindow: true }, async (tabs) => { const url = tabs[0].url; const favicon = tabs[0].favIconUrl; websiteUrlElement.textContent = url; faviconImage.src = favicon; // 主动向background请求根CA信息 try { const response = await browser.runtime.sendMessage({ action: "fetchRootCA" }); if (response.rootCA) { const caInfo = response.rootCA; document.getElementById("rootCAInfo").textContent = caInfo; checkCA(url, caInfo); } if (response.secure === "no") { siteStatusDivs.notMarked.style.display = "none"; siteStatusDivs.unsecure.style.display = "block"; } } catch (err) { console.debug("请求根CA信息失败:", err); } }); });
方案3:提前注册popup的消息监听
将popup的onMessage监听移到tabs.query回调外,提前完成注册:
document.addEventListener("DOMContentLoaded", () => { var faviconImage = document.getElementById('faviconImage'); var websiteUrlElement = document.getElementById('websiteUrl'); let currentTabUrl = ""; // 提前注册消息监听 browser.runtime.onMessage.addListener((request) => { if (request.rootCA) { const caInfo = request.rootCA; document.getElementById("rootCAInfo").textContent = caInfo; checkCA(currentTabUrl, caInfo); } if (request.secure === "no") { siteStatusDivs.notMarked.style.display = "none"; siteStatusDivs.unsecure.style.display = "block"; } }); browser.tabs.query({ active: true, currentWindow: true }, (tabs) => { const url = tabs[0].url; const favicon = tabs[0].favIconUrl; websiteUrlElement.textContent = url; faviconImage.src = favicon; currentTabUrl = url; }); });
内容的提问来源于stack exchange,提问作者Armina Parvaresh Rizi
相关产品推荐
相关产品推荐

