上面说到了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
属性示例:// 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
方法来发送消息。(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
的函数,调用这个函数即可发送回复。
// 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 不是马上发送,而是过一段时间才发送,如何也能正常地获取到返回的结果呢?
// 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
以指示它将异步响应。如果没有处理程序返回true
或sendResponse()
回调被垃圾回收,系统会自动调用sendMessage()
函数的回调。
这个GPT直译说得比较糙,总结一下就是:sendResponse
只在同步使用时有效,如果需要在异步响应时使用,需要在消息处理回调函数中 return true
来告诉请求方这是一个异步响应。
那我们是不是这样改就可以了呢?
// 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 方法作为消息处理回调函数时,也能正常运行吗?
// 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
也能正常发送并被接收。示例代码如下:
// background.js
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
(async () => {
// 在这里面写异步的代码
// ...
const result = await getSomething();
sendResponse(result);
})();
// 重点!返回 true 表示要异步发送响应
return true;
});
原文链接:https://juejin.cn/post/7349087888049766435 作者:CrabSAMA