chrome extension 系列:第七章——消息传递

上面说到了popup、content、background这几个概念,我们可以发现,这几个模块看起来都是相互独立的,但我们会有模块间通信的需求。

举个例子,我们需要在popup中配置一些页面上的功能,比如在popup中打开一个开关,就将页面上的某个元素移除掉。但popup无法访问页面的DOM,此时我们在popup中操作时,就需要去通知content,执行content中的一些逻辑对页面进行处理。此时我们就会接触到chrome extension中的消息传递相关的内容。

消息传递在extension中可以简单地分为以下两种情况:

  • 扩展程序内消息传递(如popup -> background或sidePanel -> background)

    扩展程序内消息传递 通过 chrome.runtime.sendMessage 发送消息,使用 chrome.runtime.onMessage.addEventListener来监听消息。

    如果需要判断消息来源,可以通过监听消息回调函数中的 sender 属性来判断,以下是 popup -> background 的 sender 属性示例:

    json 复制代码
    // chrome.runtime.onMessage.addEventListener 中 sender 属性
    {
        "id": "dmhihmcmfmkhdncmdaojodiifmpdgdje",
        "url": "chrome-extension://dmhihmcmfmkhdncmdaojodiifmpdgdje/src/pages/popup/index.html",
        "origin": "chrome-extension://dmhihmcmfmkhdncmdaojodiifmpdgdje"
    }
  • 内容脚本与扩展程序间消息传递(即content -> background与background -> content)

    内容脚本往扩展程序发消息 依旧是通过 chrome.runtime.sendMessage 实现,但扩展程序往内容脚本发消息会相对复杂一点。

    在发消息时,需要先找到对应的 tab。在 chrome 浏览器中,一个浏览器可能会有许多不同的 tab,而我们的内容脚本也有可能会注入到不同的 tab 中,因此我们发消息之前需要先找到发往的地址。我们可以通过 chrome.tabs.query 这个方法找到对应的 tab 后,调用 chrome.tabs.sendMessage 方法来发送消息。

    javascript 复制代码
    (async () => {
      const [tab] = await chrome.tabs.query({active: true, lastFocusedWindow: true});
      const response = await chrome.tabs.sendMessage(tab.id, {greeting: "hello"});
      // do something with response here, not outside the function
      console.log(response);
    })();

异步消息回调

有去了解过API的同学可能会发现,chrome.runtime.sendMessage 会返回一个 Promise,Promise的结果是返回的内容。而接收方是如何发送这个回复的呢?在 chrome.runtime.onMessage 的回调函数中,有一个 sendResponse 的函数,调用这个函数即可发送回复。

javascript 复制代码
// content.js
const response = await chrome.runtime.sendMessage('hello'); //response: 'nice to meet you'
​
// background.js
chrome.runtime.onMessage.addEventListener(function(request, sender, sendResponse) {
  console.log(request);
  sendResponse('nice to meet you');
})

上面说的是理想的情况,但看到返回的结果用一个 Promise 包裹,我第一时间就想到了如果 sendResponse 不是马上发送,而是过一段时间才发送,如何也能正常地获取到返回的结果呢?

javascript 复制代码
// content.js
const response = await chrome.runtime.sendMessage('fetch something'); //response: undefined
​
// background.js
chrome.runtime.onMessage.addEventListener(function(request, sender, sendResponse) {
  console.log(request);
  fetch('https://something').then((res) => res.json()).then((res) => {
    sendResponse('I got it!');
  })
})

运行了上述代码后,我们会发现 response 居然是 undefined,而不是我们预期的内容,这是为什么呢?

在查看了官方文档后,我们可以看到这一段话:

注意 :如果您使用回调,则 sendResponse() 回调仅在以下情况下有效:同步使用,或事件处理脚本返回 true 以指示它将异步响应。如果没有处理程序返回 truesendResponse() 回调被垃圾回收,系统会自动调用 sendMessage() 函数的回调。

这个GPT直译说得比较糙,总结一下就是:sendResponse只在同步使用时有效,如果需要在异步响应时使用,需要在消息处理回调函数中 return true 来告诉请求方这是一个异步响应。

那我们是不是这样改就可以了呢?

javascript 复制代码
// content.js
const response = await chrome.runtime.sendMessage('fetch something'); //response: 'I got it!'
​
// background.js
chrome.runtime.onMessage.addEventListener(function(request, sender, sendResponse) {
  console.log(request);
  fetch('https://something').then((res) => res.json()).then((res) => {
    sendResponse('I got it!');
  })
  return true;
})

好消息,这样是可以的!在使用同步方法作为消息处理回调函数时,这样是可以的。但我们平时经常会使用 async/await 写法来处理异步操作,那如果我们使用 async 方法作为消息处理回调函数时,也能正常运行吗?

javascript 复制代码
// content.js
const response = await chrome.runtime.sendMessage('fetch something'); //response: undefined
​
// background.js
chrome.runtime.onMessage.addEventListener(async function(request, sender, sendResponse) {
  console.log(request);
  const fetchResponse = await fetch('https://something');
  sendResponse('I got it!');
  return true;
})

此时我们发现,response 怎么又是 undefined 了?

这里面有一个知识点,async 函数的返回值其实是一个 Promise,这个处理函数的返回值被包装在 Promise 决议的结果中,因此 chrome 并没有接收到返回的 true 值,自然也不会把这个当成一个异步消息回调。

解决办法也很简单,我们无法使用 async 函数作为消息回调处理函数,那我们可以在同步函数内部定义一个 async 函数,并直接使用 IIFE 的方式执行,此时返回的 true 值会被 chrome 接收到,sendResponse 也能正常发送并被接收。示例代码如下:

javascript 复制代码
// background.js
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  (async () => {
    // 在这里面写异步的代码
    // ...
    const result = await getSomething();
    sendResponse(result);
  })();
​
  // 重点!返回 true 表示要异步发送响应
  return true;
});

参考资料:stackoverflow.com/questions/4...

相关推荐
excel4 小时前
前端加密的作用与使用场景
前端
计算机魔术师5 小时前
纽约时报版权诉讼披露:微软高管内部称训练 AI 是人类历史上最大规模劳动窃取
前端
去伪存真6 小时前
大模型的参数量为什么那么大?
前端·人工智能
IT_陈寒6 小时前
Java空指针这次真把我坑惨了
前端·人工智能·后端
八荒启·交互动画7 小时前
# Web特效020—让 Web 特效真正动起来:时间循环应该怎么接
前端·webgl·网页特效·八荒启-交互动画·八荒启
动恰客流统计7 小时前
线下零售数字化浪潮下,客流统计的3个核心发展趋势
大数据·前端·人工智能
用户938515635078 小时前
从小米前端面试题,彻底搞懂闭包、作用域链与 useState 惰性初始化
前端·面试
粥里有勺糖9 小时前
视野修炼第133期 | Native 回春了?
前端·github·agent
aichitang20249 小时前
前端小skill
前端·人工智能·算法·ai·前端框架
前端·柱子10 小时前
q-floodfill白边锯齿?一招搞定抗锯齿边缘问题
前端·html·canva可画