通信
Chrome Extension JS 对比
JS 种类 | 可访问的 API | DOM 访问情况 | JS 访问情况 | 直接跨域 |
---|---|---|---|---|
background js | 可以访问绝大部分的 API,除了 devtools 系列 | 不能直接访问 | 不可以 | 可以 |
content scripts | 只能访问 extension、runtime 等部分 API | 可以访问 | 不可以 | 不可以 |
browser action | 可以访问绝大部分的 API,除了 devtools 系列 | 不能直接访问 | 不可以 | 可以 |
inject script | 和普通 JS 无任何差别,不能访问任何扩展 API | 可以访问 | 可以访问 | 不可以 |
通信
content script 向扩展程序通信
// content script
chrome.runtime.sendMessage({ greeting: "您好" }, function (response) {
console.log("收到来自后台的回复:" + response)
})
// background.js 或者 popup.js(browser action)
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
console.log("收到来自content-script的消息:")
console.log(request, sender, sendResponse)
sendResponse("我是后台,我已收到你的消息:" + JSON.stringify(request))
})
注意: 如果多个页面都监听 onMessage 事件,对于某一次事件只有第一次调用 sendResponse() 能成功发出回应,所有其他回应将被忽略。
在 content script 中 使用 sendMessage 发送消息,在接收方使用 runtime.onMessage 或者 runtime.onMessageExternal ,向自己的扩展程序发送消息就接受方使用 onMessage,如果是向另一个扩展程序发送则使用 onMessageExternal 。
扩展程序向 coentent script 发送信息使用的就是 tabs.sendMessage
// 唯一的区别是您需要指定发送至哪一个标签页
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
chrome.tabs.sendMessage(
tabs[0].id,
{ greeting: "您好" },
function (response) {
console.log(response.farewell)
}
)
})
注意事项: content_scripts 向 popup 主动发消息的前提是 popup 必须打开!否则需要利用 background 作中转; 如果 background 和 popup 同时监听,那么它们都可以同时收到消息,但是只有一个可以 sendResponse,一个先发送了,那么另外一个再发送就无效;
popup 与 background 的通信
popup 可以直接调用 background 中的 JS 方法,也可以直接访问 background 的 DOM:
// background.js
function test() {
alert("我是background!")
}
// popup.js
var bg = chrome.extension.getBackgroundPage()
bg.test() // 访问bg的函数
alert(bg.document.body.innerHTML) // 访问bg的DOM
注意:不能有语法报错,不然会看不到输出
background 访问 popup 如下(前提是 popup 已经打开)
var views = chrome.extension.getViews({ type: "popup" })
console.log("---views---", views)
if (views.length > 0) {
console.log(views[0].location.href)
}
调试步骤:
- 先打开背景页,console tab 下会输出
---views--- []
- 点击你的扩展,弹出 popup 页面
- 刷新背景页面的控制台,就可以看到输出的内容了
popup/background 与 content 通信
// background.js 或者 popup.js
function sendMessageToContentScript(message, callback) {
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
chrome.tabs.sendMessage(tabs[0].id, message, function (response) {
if (callback) callback(response)
})
})
}
sendMessageToContentScript(
{ cmd: "test", value: "你好,我是popup!" },
function (response) {
console.log("来自content的回复:" + response)
}
)
// content script
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
// console.log(sender.tab ?"from a content script:" + sender.tab.url :"from the extension");
if (request.cmd == "test") alert(request.value)
sendResponse("我收到了你的消息!")
})
inject script 和 content script 通信
content-script 和页面内的脚本(injected-script 自然也属于页面内的脚本)之间唯一共享的东西就是页面的 DOM 元素,有 2 种方法可以实现二者通讯:
- 可以通过 window.postMessage 和 window.addEventListener 来实现二者消息通讯(推荐)
// inject script
window.postMessage({ test: "你好!" }, "*")
// content script
window.addEventListener(
"message",
function (e) {
console.log(e.data)
},
false
)
- 通过自定义 DOM 事件来实现;
// inject script
var customEvent = document.createEvent("Event")
customEvent.initEvent("myCustomEvent", true, true)
function fireCustomEvent(data) {
hiddenDiv = document.getElementById("myCustomEventDiv")
hiddenDiv.innerText = data
hiddenDiv.dispatchEvent(customEvent)
}
fireCustomEvent("你好,我是普通JS!")
// content script
var hiddenDiv = document.getElementById("myCustomEventDiv")
if (!hiddenDiv) {
hiddenDiv = document.createElement("div")
hiddenDiv.style.display = "none"
document.body.appendChild(hiddenDiv)
}
hiddenDiv.addEventListener("myCustomEvent", function () {
var eventData = document.getElementById("myCustomEventDiv").innerText
console.log("收到自定义事件消息:" + eventData)
})
长连接
上面讲的都是属于短连接,一次性的。
要使用长连接,您可以分别使用 runtime.connect 或 tabs.connect 从您的内容脚本建立到扩展程序(或者反过来)的长时间连接。建立的通道可以有一个可选的名称,让您区分不同类型的连接。
建立连接时,两端都将获得一个 runtime.Port 对象,用来通过建立的连接发送和接收消息。
content script 中建立连接,发送并监听消息
var port = chrome.runtime.connect({ name: "敲门" })
port.postMessage({ joke: "敲门" })
port.onMessage.addListener(function (msg) {
if (msg.question == "是谁?") port.postMessage({ answer: "女士" })
else if (msg.question == "哪位女士?")
port.postMessage({ answer: "Bovary 女士" })
})
在接收端通过 onConnect 事件监听
chrome.runtime.onConnect.addListener(function (port) {
console.assert(port.name == "敲门")
port.onMessage.addListener(function (msg) {
if (msg.joke == "敲门") port.postMessage({ question: "是谁?" })
else if (msg.answer == "女士")
port.postMessage({ question: "哪位女士?" })
else if (msg.answer == "Bovary 女士")
port.postMessage({ question: "我没听清楚。" })
})
})
您可能想知道连接何时关闭,例如您需要为每一个打开的端口单独保留状态。这种情况下您可以监听 runtime.Port.onDisconnect 事件,当连接的另一端调用 runtime.Port.disconnect 或包含该端口的页面已结束(例如标签页转到了另一个页面)时,对于每一个端口确保都会发生一次该事件。
总结
- content script 是依附于当前打开的页面的,所以由它向 background/popup 发送消息直接 runtime.sendMessage 就行
- bacground/popup 分别是扩展的后台页面和扩展展示页,它们是不知道往哪个打开页面(即 tab)的 content script 发送信息的,因此这里就通过 tabs.sendMessage
- injected script 和 content script 都属于页面内的脚本,由于 content script 不能获取页面自己的 js ,但是 inject script 是可以获取到页面 js ,而 content script 和 inject script 之间共享的东西就是页面的 DOM 元素,window 就是页面元素,所以可以用来传递消息
- background 和 popup 都是属于扩展自己的 js,这两者的通信就更简单了。