我们知道chrome 插件主要是由内容脚本,后台脚本,工具栏popup脚本处理逻辑,但是内容脚本只能调用一些指定的chrome api。后台脚本不能操作dom,所以就需要一个通信系统来让他们交互起来。下面我们来看看具体通信方式吧。
主要有两大类api供我们进行通信。
一次性通信
内容脚本向popup,后台脚本通信
内容脚本向后台脚本和工具栏popup脚本发送消息我们可以使用chrome.runtime.sendMessage
API。
js
// 内容脚本
chrome.runtime.sendMessage({type: "content => background,popup"}, (response) => {
console.log("======", response) // 后台,popup脚本回复
})
js
// 后台脚本
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log("======background", request)
sendResponse("background回复")
}
);
js
// popup脚本
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log("======popup", request)
sendResponse("popup回复")
}
);
如果background和popup同时监听,那么它们都可以同时收到消息,但是只有一个可以sendResponse,一个先发送了,那么另外一个再发送就无效。
popup,后台脚本向内容脚本通信
后台脚本和工具栏popup脚本向内容脚本发送消息我们可以使用chrome.tabs.sendMessage
API。
这里需要注意一下,如果后台脚本向内容脚本发送消息可能会报Uncaught (in promise) Error: Could not establish connection. Receiving end does not exist.
错误,这个错误通常发生在尝试在内容脚本启动之前向内容脚本发送消息时。由于内容脚本和后台脚本在不同的上下文中运行,后台脚本可能在内容脚本开始运行之前就尝试发送消息,导致无法建立连接的错误。所以后台脚本需要在一定条件下触发消息。比如点击工具栏图标。
js
// 后台脚本
chrome.action.onClicked.addListener(async (tab) => {
chrome.tabs.sendMessage(tab.id, { data: 'focus-off' });
});
js
// popup脚本
chrome.tabs.query({active: true, currentWindow: true}, (tabs) => {
chrome.tabs.sendMessage(tabs[0].id, {type: 'popup => content'}, (response) => {
console.log("回复message", response)
})
})
js
// 内容脚本
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
console.log("======background, popup", message)
sendResponse("content回复消息")
})
长连接通信
如需创建可重复使用的长期消息传递通道,请调用 runtime.connect()
以将消息从内容脚本传递到扩展程序页面,或调用 tabs.connect()
以将消息从扩展程序页面传递到内容脚本。
内容脚本向popup,后台脚本通信
js
// 后台脚本
chrome.runtime.onConnect.addListener(function(port) {
// port中存在 disconnect, onDisconnect, onMessage, postMessage, sender属性和方法
port.onMessage.addListener(function(info) {
console.log('at line 52 in email_this_page/background.js:', info, port, port.sender)
// 回复消息
port.postMessage("我收到消息了")
});
});
js
// 内容脚本
const additionalInfo = {
"title": document.title,
"selection": window.getSelection().toString() // 获取选中的字符串
};
const port = chrome.runtime.connect({name: "first-connect"})
port.postMessage(additionalInfo);
port.onMessage.addListener(function(info) {
console.log('at line 14 in email_this_page/content_script.js:', info)
});
js
// popup脚本
chrome.runtime.onConnect.addListener(function(port) {
// port中存在 disconnect, onDisconnect, onMessage, postMessage, sender属性和方法
port.onMessage.addListener(function(info) {
console.log('at line 52 in email_this_page/background.js:', info, port, port.sender)
port.postMessage("oopup-我收到消息了")
});
});
如果popup,后台脚本都监听消息,并且回复消息,那么内容脚本都可以接收到,而不是向短连接一样,只能接收第一个回复消息的。这也是长连接的一种作用,持续接收消息。
popup,后台脚本向内容脚本通信
这种通信方式经过测试发现总是报Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.
错误。并且发现chrome.action.onClicked.addListener
在设置了default_popup
后,在后台脚本和popup脚本中都无法监听到点击。
js
// 后台脚本
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
const port = chrome.tabs.connect(tabs[0].id, {name: "backgroundName"});
// 发送消息
port.postMessage({type: "background => content"});
// 接收消息
port.onMessage.addListener(function getResp(response) {
console.log("background:", response)
});
// 可选:监听连接关闭事件
port.onDisconnect.addListener(() => {
console.log("Port disconnected");
});
});
js
// popup脚本
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
const port = chrome.tabs.connect(tabs[0].id, {name: "popupName"});
// 发送消息
port.postMessage({type: "popup => content"});
// 接收消息
port.onMessage.addListener(function getResp(response) {
console.log("popup:", response)
});
});
js
// 内容脚本
chrome.runtime.onConnect.addListener((port) => {
console.log('at line 113 in content/content.js:', port)
})
内容脚本与寄主环境通信
由于内容脚本默认情况下是独立于寄主环境执行的,但是可以操作dom,他们可以通过下面几种方式进行通信。
- 可以通过
window.postMessage
和window.addEventListener
来实现二者消息通讯。注意这里不能进行双向通信,因为通过e.source
指向的是发送方,而发送方也就是当前window对象,那么再通过window对象发送就会造成循环发送。
js
// 内容脚本
window.onmessage = (e) => {
console.log("寄主环境传递的消息", e)
// e.source.postMessage(
// "content回复",
// e.origin,
// );
}
js
// 注入页面
document.getElementById("mybutton").addEventListener("click", () => {
window.postMessage({type: "injected => content"}, '*');
// window.onmessage = (e) => {
// console.log("内容脚本回信", e)
// }
}, false);
- 通过自定义DOM事件来实现
js
// 注入页面
document.getElementById('mybutton').addEventListener('click', () => {
const myEvent = new CustomEvent("myEvent", {detail: {
type: "inject => content"
}})
window.dispatchEvent(myEvent)
})
js
// 内容脚本
window.addEventListener("myEvent", (e) => {
console.log("e", e)
})
- 将内容脚本和寄主环境共享执行环境。在manifest中配置内容脚本
world: "MAIN"
扩展间通信
和在同一个扩展中进行长连接一样,如果popup,background同时监听消息,并且回复消息,那么都可以监听到。但是如果当前内容脚本是向指定扩展发送的消息,当前扩展的其他脚本是不能监听到的。
js
// 当前扩展内容脚本
const additionalInfo = {
"title": document.title,
"selection": window.getSelection().toString() // 获取选中的字符串
};
// 向chromev3demo3通信,需要指定其扩展id。
const port = chrome.runtime.connect("ochkjkbancdeogkponljmdkglimpbkfa", {name: "first-connect"})
port.postMessage(additionalInfo);
port.onMessage.addListener(function(info) {
console.log('at line 14 in manifestv3demo4/content_script.js:', info)
});
js
// 其他扩展的后台脚本
chrome.runtime.onConnectExternal.addListener(function(port) {
// port中存在 disconnect, onDisconnect, onMessage, postMessage, sender属性和方法
port.onMessage.addListener(function(info) {
console.log('at line 52 in manifestv3demo3/background.js:', info, port, port.sender)
port.postMessage("background03-我收到消息了")
});
});
js
// 其他扩展的popup脚本
chrome.runtime.onConnectExternal.addListener(function(port) {
// port中存在 disconnect, onDisconnect, onMessage, postMessage, sender属性和方法
port.onMessage.addListener(function(info) {
console.log('at line 106 in manifestv3demo3/popup.js:', info, port, port.sender)
port.postMessage("popup03-我收到消息了")
});
});
js
// 内容脚本
chrome.runtime.sendMessage("ochkjkbancdeogkponljmdkglimpbkfa", additionalInfo, (msg) => {
console.log("响应的", msg)
})
js
// 后台脚本
chrome.runtime.onMessageExternal.addListener((message, sender, messageRresponse) => {
console.log("background03", message, sender)
messageRresponse("background03 => content04")
// return true
})
js
// popup脚本
chrome.runtime.onMessageExternal.addListener((message, sender, messageRresponse) => {
console.log("popup03", message, sender)
messageRresponse("popup03 => content04")
// return true
})
本质上讲,就是使用
- 一次性通信,
chrome.runtime.sendMessage, chrome.runtime.onMessage.addListener, chrome.tabs.sendMessage
- 长连接通信,
chrome.runtime.connect, chrome.runtime.onConnect.addListener, chrome.tabs.connect
, - 跨扩展通信
chrome.runtime.sendMessage, chrome.runtime.connect, chrome.runtime.onMessageExternal.addListener, chrome.runtime.onConnectExternal.addListener
进行监听,但是经过一些测试发现,总是有一些出入,这里记录一下,如果项目中遇到问题再来补充。
往期年度总结
往期文章
- 反调试吗?如何监听devtools的打开与关闭
- 因为原生,选择一家公司(前端如何防笔试作弊)
- 结合开发,带你熟悉package.json与tsconfig.json配置
- 如何优雅的在项目中使用echarts
- 如何优雅的做项目国际化
- 近三个月的排错,原来的憧憬消失喽
- 带你从0开始了解vue3核心(运行时)
- 带你从0开始了解vue3核心(computed, watch)
- 带你从0开始了解vue3核心(响应式)
- 3w+字的后台管理通用功能解决方案送给你
- 入职之前,狂补技术,4w字的前端技术解决方案送给你(vue3 + vite )