Safari扩展:弹出页向内容脚本发送消息异常的排查与修复
首先,你的消息传递异常主要是由异步时序不匹配以及Chrome和Safari扩展API的细微行为差异导致的,咱们拆解来看:
核心原因
异步执行顺序冲突
你同时使用了await处理chrome.scripting.executeScript的Promise,又附加了回调函数。虽然await会等待脚本注入完成,但回调的执行时机并不一定和Promise的resolved完全同步,这就可能导致chrome.runtime.sendMessage在内容脚本的chrome.runtime.onMessage监听器还没完成注册时就发送了消息,自然收不到预期的响应。Safari扩展的API行为差异
虽然Safari通过转换器兼容Chrome扩展API,但在内容脚本注入后的初始化时机上,可能和Chrome有细微差别。Chrome中脚本注入完成后通常会立即执行,但Safari可能需要极短的时间窗口完成脚本加载和监听器注册,直接发送消息容易错过时机。循环闭包的潜在问题
你的for循环中使用index变量,虽然用了let(块级作用域),但如果inboxTabId是在循环外定义的变量,可能会出现预期外的取值问题(不过这不是你当前消息异常的核心原因,但也是需要优化的点)。
修复步骤
1. 调整异步逻辑,确保脚本注入完成后再发消息
移除executeScript的回调函数,利用await的特性,在脚本注入完成后直接发送消息:
const buttonList = document.querySelectorAll(".app-button-inbox"); // 用for...of循环避免闭包问题 for (const button of buttonList) { button.addEventListener("click", async () => { try { console.log('点击触发,目标tab:', inboxTabId); // 等待脚本注入完成 await chrome.scripting.executeScript({ target: { tabId: inboxTabId, allFrames: true}, files: ['contentScript.js'] }); // 脚本注入完成后再发送消息 chrome.runtime.sendMessage({message: 'hello'}); } catch (error) { console.log('注入脚本出错:', error); } }); }
2. 增加内容脚本的"就绪"确认(可选但更可靠)
为了彻底避免时序问题,可以让内容脚本加载完成后主动给Popup发送一个"就绪"信号,Popup收到后再发送目标消息:
修改contentScript.js:
console.log('内容脚本加载完成'); // 主动通知Popup脚本已就绪 chrome.runtime.sendMessage({type: 'scriptReady'}); chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) { console.log('收到消息:', message, sender); // 如果需要响应,可以调用sendResponse sendResponse({status: 'received'}); });
修改Popup中的点击事件逻辑:
const buttonList = document.querySelectorAll(".app-button-inbox"); // 监听内容脚本的就绪信号 const waitForScriptReady = () => { return new Promise(resolve => { const listener = (message) => { if (message.type === 'scriptReady') { chrome.runtime.onMessage.removeListener(listener); resolve(); } }; chrome.runtime.onMessage.addListener(listener); }); }; for (const button of buttonList) { button.addEventListener("click", async () => { try { console.log('点击触发,目标tab:', inboxTabId); await chrome.scripting.executeScript({ target: { tabId: inboxTabId, allFrames: true}, files: ['contentScript.js'] }); // 等待内容脚本就绪 await waitForScriptReady(); // 发送目标消息 const response = await chrome.runtime.sendMessage({message: 'hello'}); console.log('收到响应:', response); } catch (error) { console.log('注入脚本出错:', error); } }); }
3. 检查Safari扩展的权限配置
确保你的manifest.json(或Safari扩展的配置文件)中已经声明了必要的权限:
{ "permissions": ["scripting", "activeTab"], "host_permissions": ["<all_urls>"] // 根据你的需求调整匹配规则 }
额外提示
- 在Safari中测试扩展时,建议开启开发菜单 -> 显示扩展构建器,可以更方便地查看扩展的日志和调试信息。
- 如果仍然有问题,可以在内容脚本的
onMessage监听器中加入console.log确认监听器是否被注册,同时在Popup中检查sendMessage的返回值(如果用await的话可以捕获响应)。
内容的提问来源于stack exchange,提问作者Ceren Keklik

