browser.tabs.sendMessage Chrome正常Firefox返回undefined问题
问题根因
Chrome和Firefox对扩展消息接口runtime.onMessage的异步响应逻辑存在实现差异:
- Chrome环境下,只要消息监听器同步返回
true,就会永久保持消息通信端口开放,直到sendResponse被调用,哪怕sendResponse放在setTimeout这类跨事件循环的宏任务里延迟执行,端口也不会被回收。 - Firefox环境下,同步返回
true仅能保持端口开放到当前事件循环的微任务队列执行完毕,一旦sendResponse在setTimeout这类下一轮事件循环才执行的宏任务中被调用,端口会被提前静默回收,不会抛出任何runtime.lastError,直接导致sendMessage立即返回undefined。
另外你的代码还存在一个隐藏的时序问题:onInstalled回调中调用browser.scripting.executeScript时没有加await,动态注入内容脚本的操作是异步执行的,极端情况下会出现消息发送时内容脚本还没注入完成的问题,只是这个问题在Chrome测试时恰好没触发。
修复方案
不要使用return true+宏任务内调用sendResponse的旧写法,这是Chrome早期的非标准实现,跨浏览器兼容性差。直接使用WebExtension标准规定的「监听器返回Promise」写法即可,Chrome和Firefox全版本兼容:
- 修改
inject.js中的消息监听逻辑,去掉sendResponse参数和return true,直接返回Promise,将需要返回的内容放在Promise的resolve中:
//inject.js (async () => { if (typeof browser === "undefined") { var browser = chrome; } browser.runtime.onMessage.addListener((msg, sender) => { console.log(msg); // 直接返回Promise,不需要手动调用sendResponse return new Promise((resolve) => { setTimeout(() => { let pageObject = {a:1}; resolve(pageObject); },1000) }) }); })();
- 修复动态注入的时序问题,给
executeScript加上await,确保内容脚本注入完成后再执行后续逻辑:
browser.runtime.onInstalled.addListener(async () => { let manifest = browser.runtime.getManifest(); for (const cs of manifest.content_scripts) { for (const tab of await browser.tabs.query({ url: cs.matches })) { // 等待注入完成 await browser.scripting.executeScript({ target: { tabId: tab.id }, files: cs.js, }); } } });
补充说明:如果你的异步逻辑本身就是Promise-based的(比如
fetch、浏览器API调用),直接return这个Promise即可,不需要额外套一层Promise包装。
内容的提问来源于stack exchange,提问作者sanjihan
相关产品推荐
相关产品推荐

