我认为 chrome 扩展程序总体上非常简单且非常强大,但总是让我感到困惑的一件事是尝试在可能运行代码的各种脚本之间进行通信。从浏览器操作的“default_popup”页面引用时运行的代码,“背景”的“脚本”属性中的代码和内容脚本。
这些类别中的脚本在什么上下文中运行,每个脚本如何与其他脚本通信?
我认为 chrome 扩展程序总体上非常简单且非常强大,但总是让我感到困惑的一件事是尝试在可能运行代码的各种脚本之间进行通信。从浏览器操作的“default_popup”页面引用时运行的代码,“背景”的“脚本”属性中的代码和内容脚本。
这些类别中的脚本在什么上下文中运行,每个脚本如何与其他脚本通信?
作为 Chrome 扩展程序开发人员,您可以区分三种不同的环境。
请注意,<iframe src="chrome-extension://EXTENSIONID/page.htm">
在非扩展页面中,曾经被视为案例 2(内容脚本),因为框架是在非特权选项卡进程中加载的。由于在 Chrome 56 中为扩展程序启动了进程外 iframe,因此这些页面由扩展程序进程处理,因此它们可能使用相同的全套扩展程序 API。这种行为变化(允许扩展框架使用特权扩展 API)是有意的。
window
在扩展进程中访问对象因为所有扩展代码都运行在同一个进程中,所以它们可以互相访问全局window
对象。这一特性并不为人所知,但它允许在同一个扩展进程中直接操作 JavaScript 和 DOM 对象。通常最好不要使用此方法,而是使用消息传递API。
// To access the `window` of a background page, use
var bgWindowObject = chrome.extension.getBackgroundPage();
// To access the `window` of an event or background page, use:
chrome.runtime.getBackgroundPage(function(bgWindowObject) {
// Do something with `bgWindow` if you want
});
// To access the `window` of the badge's popup page (only if it's open!!!), use
var popupWindowObject = chrome.extension.getViews({type:'popup'})[0];
// To access the `window` of the options page (called /options.html), use
var allWindowObjects = chrome.extension.getViews({type:'tab'});
var popupWindowObjects = allWindowObjects.filter(function(windowObject) {
return windowObject.location.pathname == '/options.html';
});
// Example: Get the `window` object of the first options page:
var popupWindowObject = popupWindowObjects[0];
为了使本节简短,我有意将代码示例限制为访问其他全局window
对象的演示。您可以使用这些方法来定义全局方法、设置全局变量、调用全局函数等
……前提是页面是打开的。有人认为弹出窗口window
始终可用。这不是真的,当弹出窗口关闭时,全局对象被释放!
消息通道总是有两端:发送者和接收者。
要成为接收者,请使用该chrome.runtime.onMessage.addListener
方法绑定事件侦听器。这可以通过扩展代码和内容脚本来完成。
要在扩展程序中传递消息,请使用chrome.runtime.sendMessage
. 如果您想将消息发送到另一个选项卡,请调用chrome.tabs.sendMessage
。目标选项卡通过包含一个整数 ( tabId
) 作为其第一个参数来指定。请注意,背景页面只能向一个选项卡发送消息。要访问所有选项卡,必须为每个选项卡调用该方法。例如:
chrome.tabs.query({}, function(tabs) {
for (var i=0; i<tabs.length; i++) {
chrome.tabs.sendMessage(tabs[i].id, "some message");
}
});
内容脚本只能调用chrome.runtime.sendMessage
以向扩展代码发送消息。如果您想从一个内容脚本向另一个内容脚本发送消息,则需要一个背景/事件页面,它接收消息并将其发送到所需的选项卡。有关示例,请参见此答案。
这些sendMessage
方法接受一个可选函数,该函数作为onMessage
事件的第三个参数接收。
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
if (message === 'message') sendResponse('the response');
});
chrome.runtime.sendMessage('message', function(response) {
console('sendResponse was called with: ' + response);
});
前面的示例显示了明显的行为。当您想要异步发送响应时,事情会变得更加棘手,例如,如果您想要执行 AJAX 请求来获取一些数据。当onMessage
函数没有调用就返回时sendResponse
,Chrome 会立即调用sendResponse
。由于sendResponse
只能调用一次,您将收到以下错误:
无法发送响应:如果您想在侦听器返回后发送响应,则 chrome.runtime.onMessage 侦听器必须返回 true(消息由扩展EXTENSION ID HERE发送)
按照错误提示进行操作,return true;
在 onMessage 事件侦听器中添加:
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
setTimeout(function() { // Example: asynchronous invocation of sendResponse
sendResponse('async response');
}, 200);
return true;
});
我已经在本节中解释了简单一次性消息传递的实际应用。如果您想了解更多关于长寿命消息通道或跨扩展消息的信息,请阅读官方文档中的教程。
消息传递 API 经历了几次名称更改。如果您阅读旧示例,请记住这一点。历史和兼容性说明可以在这里找到。
可以与页面进行通信。Apsillers 创建了一个很好的答案,解释了如何在(非扩展)页面和内容脚本之间建立通信通道。在站点可以调用浏览器扩展程序吗?.
与文档中的方法相比,apsiller 方法的优势在于使用了自定义事件。该文档用于window.postMessage
向页面发送消息,但这可能会导致与不期望消息事件的错误编码页面发生冲突。
Google 文档包含所有内容,但很难将所有信息汇总在一起。脚本主要有两种类型:
1. 后台脚本可以完全访问 Chrome api,但不能与目标网页交互。
2. 内容脚本可以相互交互,也可以与网页的 DOM 交互(但不能与网页的 DOM 交互),但只能有限地访问 Chrome api。
每当您加载新页面时,两者都会运行(除非您使用“匹配”来限制内容脚本的运行位置)。
您可以通过消息传递在两者之间进行通信。内容脚本比后台脚本更容易做到这一点,因为您需要知道后者的选项卡 ID。
其他脚本 ( browserAction.js
, pageAction.js
, optionsPage.js
) 仅在打开相应的 html 页面时运行(就像您在浏览器窗口中打开网页一样,这就是您真正在做的事情)。它们在限制和能力方面类似于背景脚本。
尽量避免与页面脚本交互的需要。我知道的最好方法是通过共享 DOM 进行交互(字面意思是在 html 注释中编写 javascript 代码)。但是您的扩展程序的目标不是为此而设计的,因此您必须在网页中包含您自己的脚本来执行此操作。使用内容脚本将脚本元素写入文档(它src
是
chrome.extension.getURL("myscript.js")
,
并且您需要
"web_accessible_resources": ["myscript.js"]
在清单中包含。
自从我不得不处理 chrome 的扩展以来已经有一段时间了。我记得在我了解事情的运作方式之前,这是一场相当大的斗争。为了让您的扩展程序与浏览器进行通信,您可以轻松地使用您的 javascript/background 文件,并与您需要使用的网页进行通信,chrome.tabs.executeScript
但这确实很棘手,而且可能会让人头疼。我建议您参加google 的扩展之旅,并给他们的 api 一个非常好的俯瞰,一切都在那里!祝你好运,希望这个答案对你有所帮助!:P