Chrome扩展主世界内容脚本与弹窗脚本的消息通信问题
Chrome扩展主世界内容脚本与弹窗脚本的消息通信问题
这个场景确实很棘手——主世界(MAIN)的内容脚本能拿到页面的window.someCustomProperty,但偏偏没有Chrome扩展的chrome.runtime API;而隔离世界的脚本有完整的扩展API,却碰不到页面自定义的window属性。要解决这个问题,我们得在两个世界之间搭个"通信桥",具体方案如下:
步骤1:修改Manifest配置,添加两个内容脚本
我们需要同时配置隔离世界脚本和主世界脚本:隔离世界脚本负责和弹窗通信,主世界脚本负责抓取页面的自定义属性。修改后的manifest.json如下:
{ "manifest_version": 3, "name": "Plugin", "version": "1.0", "description": "Plugin", "permissions": [ "cookies", "activeTab", "storage", "scripting", "tabs" ], "host_permissions": [ "<list of urls>" ], "content_scripts": [ // 隔离世界脚本:负责和弹窗通信,接收主世界的数据 { "js": ["bridge.js"], "matches": [ "<list of urls>" ] }, // 主世界脚本:负责获取页面window的自定义属性 { "world": "MAIN", "js": ["content-main.js"], "matches": [ "<list of urls>" ], "run_at": "document_idle" // 确保页面脚本加载完成后再执行 } ], "action": { "default_popup": "popup.html" }, "icons": { "16": "icon_16x16.png", "48": "icon_48x48.png", "128": "icon_128x128.png" } }
步骤2:编写主世界脚本(content-main.js)
这个脚本运行在主世界,能直接访问window.someCustomProperty。我们通过自定义DOM事件把数据传递给隔离世界的脚本:
// 等待页面的自定义属性加载完成(如果需要的话) window.addEventListener('load', () => { // 创建自定义事件,携带页面的自定义属性数据 const dataEvent = new CustomEvent('extension-main-world-data', { detail: { someCustomProperty: window.someCustomProperty } }); // 向全局派发事件,隔离世界的脚本能监听到 window.dispatchEvent(dataEvent); });
步骤3:编写隔离世界的桥接脚本(bridge.js)
这个脚本运行在隔离世界,拥有完整的chrome.runtime API。它需要做两件事:一是监听弹窗的连接请求,二是监听主世界发来的自定义事件,然后把数据转发给弹窗:
// 存储从主世界获取到的自定义属性数据 let pageCustomData = null; // 监听主世界发来的自定义事件,保存数据 window.addEventListener('extension-main-world-data', (event) => { pageCustomData = event.detail.someCustomProperty; }); // 监听弹窗的连接请求 chrome.runtime.onConnect.addListener(port => { if (port.name === 'test') { // 如果已经拿到了数据,直接发送给弹窗 if (pageCustomData !== null) { port.postMessage({ type: 'test', status: pageCustomData }); } else { // 如果还没拿到数据,可以等待主世界的事件,或者告知弹窗暂无数据 port.postMessage({ type: 'test', status: 'waiting for page data' }); // 也可以在这里重新监听一次事件,拿到数据后再发送 const sendDataOnce = (event) => { port.postMessage({ type: 'test', status: event.detail.someCustomProperty }); window.removeEventListener('extension-main-world-data', sendDataOnce); }; window.addEventListener('extension-main-world-data', sendDataOnce); } // 处理断开连接的情况 port.onDisconnect.addListener(() => { console.log('Popup disconnected'); }); } });
步骤4:弹窗脚本保持原有逻辑即可
你的弹窗脚本不需要大改,还是和之前一样建立连接,接收消息:
const port = chrome.tabs.connect(activeTabId, {name: 'test'}); port.onMessage.addListener((message) => { resolve(message.status); console.log('MESSAGE', message); }); port.onDisconnect.addListener(() => { reject(); console.log('DISCONNECTED'); });
为什么这个方案可行?
隔离世界和主世界共享同一个DOM环境,所以可以通过自定义DOM事件来传递数据——主世界脚本把数据塞进事件的detail里派发,隔离世界脚本监听这个事件就能拿到数据;而隔离世界脚本又能正常使用chrome.runtime API和弹窗通信,这样就完美解决了两边的限制问题。
额外注意事项
- 确保主世界脚本的
run_at设置合理,如果window.someCustomProperty是页面后期加载的(比如第三方脚本动态注入的),可能需要调整run_at为document_idle,或者在脚本里等待特定的页面事件。 - 自定义事件的命名尽量独特,避免和页面本身的事件冲突,比如用
extension-前缀。
备注:内容来源于stack exchange,提问作者Furman
相关产品推荐
相关产品推荐

