You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.15 09:33:03