Chrome扩展程序中浏览器操作、后台脚本和内容脚本之间的通信上下文和方法是什么?

30

我认为 Chrome 扩展总体上相当简单而且非常强大,但有一件事情总是困扰着我,那就是试图在各种脚本之间进行通信。这些脚本包括浏览器操作的 "default_popup" 页面中运行的代码、"background" 的 "scripts" 属性中的代码以及内容脚本。

这些类别中的脚本在什么情况下运行,并且如何使它们之间进行通信?


3个回答

80

三种不同的上下文环境

作为Chrome扩展程序开发者,你需要区分三种不同的环境。

以下是翻译:
  1. 扩展程序代码,运行在您的Chrome扩展程序中的进程中
  2. 内容脚本,在选项卡的进程中运行。
  3. 非扩展程序代码 在选项卡进程中运行(由内容脚本注入)。
请注意,非扩展页面中的<iframe src="chrome-extension://EXTENSIONID/page.htm">曾被视为第2种情况(内容脚本),因为该框架是在未受特权的选项卡进程中加载的。自从Chrome 56为扩展推出out-of-process iframes以来,这些页面由扩展进程处理,因此它们可以使用完整集合的扩展API。这个行为变化(允许扩展帧使用特权扩展API)是有意义的

在扩展进程内访问window对象

因为所有扩展代码运行在同一进程中,它们可以访问彼此的全局window对象。这个特性不是很出名,但允许直接在同一扩展进程中操作JavaScript和DOM对象。通常最好不要使用这种方法,而应该使用消息传递API。
// To access the `window` of a background page, use
var bgWindowObject = <a rel="nofollow noreferrer" href="https://developer.chrome.com/extensions/extension.html#method-getBackgroundPage">chrome.extension.getBackgroundPage()</a>;
// To access the `window` of an event or background page, use:
<a rel="nofollow noreferrer" href="https://developer.chrome.com/extensions/runtime.html#method-getBackgroundPage">chrome.runtime.getBackgroundPage</a>(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 = <a rel="nofollow noreferrer" href="https://developer.chrome.com/extensions/extension.html#method-getViews">chrome.extension.getViews({type:'popup'})[0]</a>;

// To access the `window` of the options page (called /options.html), use
var allWindowObjects = <a rel="nofollow noreferrer" href="https://developer.chrome.com/extensions/extension.html#method-getViews">chrome.extension.getViews({type:'tab'})</a>;
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)作为其第一个参数来指定。请注意,后台页面只能向一个选项卡发送消息。要达到所有选项卡,必须为每个选项卡调用该方法。例如:
<a rel="nofollow noreferrer" href="https://developer.chrome.com/extensions/tabs.html#method-query">chrome.tabs.query</a>({}, function(tabs) {
    for (var i=0; i<tabs.length; i++) {
        <a rel="nofollow noreferrer" href="https://developer.chrome.com/extensions/tabs.html#method-sendMessage">chrome.tabs.sendMessage</a>(tabs[i].id, "some message");
    }
});

Content scripts只能调用 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 发送)

按照错误提示的做法,在你的 onMessage 事件监听器中添加 return true;

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向页面发送消息,但这可能会与编写不良的页面发生冲突,这些页面不会预期消息事件。

5
哇,这个回答值得采纳并且获得更多点赞。讲解得非常清楚易懂。 - Sachin Jain

3
谷歌文档包含了所有内容,但很难将所有信息整合起来。有两种主要类型的脚本:
1. 后台脚本可以完全访问Chrome API,但无法与目标网页交互。
2. 内容脚本可以相互交互并与网页的DOM(但不是它的脚本)进行交互,但对Chrome API的访问仅受到限制。
两者都会在加载新页面时运行(除非您使用“匹配”来限制内容脚本的运行范围)。

您可以通过消息传递在两者之间进行通信。这在内容脚本中比后台脚本更容易实现,因为您需要了解后者的选项卡ID。

其他脚本(browserAction.jspageAction.jsoptionsPage.js)仅在打开其对应的HTML页面时运行(就像您在浏览器窗口中打开网页一样)。它们与后台脚本在限制和功能方面类似。

尽量避免与页面的脚本进行交互。我所知道的最好方法是通过共享的DOM进行交互(在HTML注释中直接编写JavaScript代码)。但您的扩展程序的目标并不是为此而设计的,因此您将不得不将自己的脚本包含到网页中。使用内容脚本将脚本元素写入文档(其srcchrome.extension.getURL("myscript.js")),并且您需要在清单中有
"web_accessible_resources": ["myscript.js"]


-1

我已经有一段时间没有处理Chrome扩展了。我记得在我理解它们的工作原理之前,这真是一场艰苦的斗争。为了让您的扩展与浏览器通信,只需使用JavaScript /后台文件即可轻松实现。要与网页通信,您需要使用chrome.tabs.executeScript,但这确实很棘手,可能会让您感到非常烦恼。我建议您查看Google关于扩展的介绍,并仔细研究其API,所有内容都在其中!祝您好运,希望这个答案能帮助到您!:P


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接