在后台上下文中的脚本之间进行通信(后台脚本、浏览器动作、页面动作、选项页面等) [英] Communicate between scripts in the background context (background script, browser action, page action, options page, etc.)
问题描述
我遇到了将数据从我的后台脚本发送到我的 pageAction
脚本的问题.我的内容脚本添加了一个 <iframe/>
并且 <iframe/>
中的 JavaScript 正在从我的后台脚本接收数据,但它似乎没有在我的 pageAction
中检索.
在我的后台脚本中,我有类似的内容:
chrome.tabs.sendMessage(senderTab.tab.id,{富:酒吧});
其中 senderTab.tab.id
是我的后台脚本中 onMessage
侦听器中的发送者".
在由我的内容脚本注入的 <iframe/>
加载的 JavaScript 中,我有类似的内容:
chrome.runtime.onMessage.addListener(功能(请求,发件人,sendResponse){console.log("在 iframe 中收到:", request);}});
完全按预期接收消息.
我在我的 page_action.js
中放置了相同的 JavaScript,但它没有从后台脚本接收任何数据.在我调用 chrome.tabs.sendMessage(senderTab.tab.id ...
附加到我的 pageAction 的 HTML 页面是否不是同一选项卡的一部分?由于这个 tabId
使我能够激活/显示"图标,我认为 pageAction 的 JavaScript 中的侦听器也应该从 chrome.tabs.sendMessage(senderTab.tab.id...
在我的内容脚本中,我使用以下内容将数据发送到后台脚本:
chrome.runtime.sendMessage({富:酒吧});
当内容脚本发送上述消息时,pageAction JavaScript 正在接收它.
<小时>如何让后台脚本正确地将数据发送到我的 pageAction?我不想有 pageAction 请求/轮询,而是希望 pageAction 只是侦听和接收.例如,如果它显示的 pageAction HTML,它应该能够在后台页面发生变化时实时更新.
在后台上下文中与页面通信
在后台上下文中打开的页面包括:
- 背景页面/脚本(MDN)
- 事件页面(Firefox 不支持事件页面.所有manifest.json
背景
页面 始终保持加载状态.) - 浏览器操作弹出窗口(MDN)
- 页面操作弹出窗口(MDN)
- 选项页面(MDN1, MDN2)(在弹出窗口、选项卡或窗口中)
- 侧边栏操作页面 (NDN)(在 Chrome 中不可用)
- 您的扩展程序中包含的任何 HTML 内容在选项卡、窗口(例如面板)中正常打开,或框架.1
使用tabs.sendMessage()
(MDN) 不会向其中任何一个发送消息.您需要使用 runtime.sendMessage()
(MDN) 向他们发送消息.其中任何一个的作用域,除了背景页面和事件页面,只在它被显示时存在.显然,当代码不存在时,您无法与它进行通信.当范围存在时,您可以使用以下方式与其中任何一个进行通信:
直接
在后台上下文中,您可以在另一个也在后台上下文中(即不是内容脚本)的页面中直接更改变量或调用函数,在获得对其全局范围的引用后,它的窗口,使用extension.getViews()
(MDN),extension.getBackgroundPage()
(MDN) 或 其他方法(MDN).
例如,您可以在第一个返回的视图的页面中调用一个由function myFunction
创建的函数,方法如下:winViews = chrome.extension.getViews();winViews[0].myFunction(foo);
应该注意的是,在您从
tabs.create()<的回调中/code>
(MDN) 或 <代码>windows.create()(MDN) 新打开的选项卡或窗口的视图可能还不存在.您将需要使用某种方法来等待视图存在.2请参阅下文,了解与新打开的选项卡或窗口进行通信的推荐方式.直接操作其他页面范围内的值允许您交流您想要的任何类型的数据.
消息
使用chrome.runtime.onMessage
(MDN), 3 与chrome.runtime.sendMessage()
(MDN).每次您在runtime.onMessage
侦听器中收到消息时,都会提供一个sendResponse
函数作为第三个参数,它允许您直接响应消息.如果原始发件人在调用chrome.runtime.sendMessage()
时未提供回调以接收此类响应,则响应将丢失.如果使用 Promises(例如 Firefox 中的browser.runtime.sendMessage()
),则当 Promise 完成时,响应将作为参数传递.如果您想异步发送响应,您需要从您的runtime.onMessage
侦听器return true;
.端口
您还可以使用chrome.runtime.connect()
连接端口(MDN) 和chrome.runtime.onConnect
(MDN) 用于长期消息传递.使用
chrome.tabs.sendMessage()
发送到内容脚本
如果您想from 发送背景上下文(例如背景脚本或弹出窗口)到 内容脚本,您可以使用chrome.tabs.sendMessage()
/chrome.runtime.onMessage
,或使用chrome.tabs.connect()
(MDN)/chrome.runtime.onConnect
.仅限 JSON 可序列化数据
使用消息传递,您只能传递 JSON 可序列化的数据.消息在后台被所有脚本接收,除了发件人
发送到后台上下文的消息会被后台上下文中所有注册了监听器的脚本接收,除了发送它的脚本.3 没有办法指定它只能被特定的脚本接收.因此,如果您有多个潜在收件人,您将需要创建一种方法来确保收到的消息是针对该脚本的.这样做的方法通常依赖于消息中存在的特定属性(例如,使用destination
或recipient
属性来指示接收它的脚本,或者定义一些type
消息总是针对一个收件人或另一个收件人),或者根据sender
(MDN) 提供给消息处理程序(例如,如果来自一个发件人的消息总是只用于特定的收件人).没有固定的方法来执行此操作,您必须选择/创建一种方法来执行此操作以在您的扩展程序中使用.有关此问题的更详细讨论,请参阅:所有人都收到用于后台上下文中的一个脚本的消息一个>
存储区域中的数据
将数据存储到 StorageArea(MDN) 并使用chrome.storage.onChanged
(MDN).storage.onChanged
事件可以在后台上下文和内容脚本中监听.您只能将可通过 JSON 序列化的数据存储到 StorageArea 中.
在任何特定情况下最好使用哪种方法取决于您想要传达的内容(数据类型、状态更改等),以及您想要传达的扩展程序的哪一部分从和到.例如,如果您想传达不可 JSON 序列化的信息,您需要直接这样做(即不发送消息或使用 StorageArea).您可以在同一个扩展中使用多种方法.
更多关于弹出窗口
没有任何弹出窗口(例如浏览器操作或页面操作)与活动选项卡直接关联.每个选项卡没有共享或单独实例的概念.但是,用户可以在每个 Chrome 窗口中打开一个弹出窗口.如果打开了多个弹出窗口(每个 Chrome 窗口最多一个),那么每个弹出窗口都在一个单独的实例中(单独的范围;有自己的窗口),但处于相同的上下文中.当弹出窗口实际可见时,它存在于背景上下文中.
每个 Chrome 窗口一次只能打开一个页面操作或浏览器操作弹出窗口.将打开的 HTML 文件将是为当前窗口的活动选项卡定义的并且用户通过单击页面/浏览器操作按钮打开.可以使用 chrome.browserAction.setPopup 为不同的选项卡分配不同的 HTML 文档()
(MDN) 或 chrome.pageAction.setPopup()
(MDN),并指定 tabId
.弹出窗口可以/将由于多种原因被销毁,但肯定是当另一个选项卡成为打开弹出窗口的窗口中的活动选项卡时.
但是,使用的任何通信方法都只会与当前打开的那些通信,而不是未打开的.如果一次为多个 Chrome 窗口打开弹出窗口,则它们是单独的实例,具有自己的范围(即自己的窗口).您可以认为这类似于在多个标签页中打开相同的网页.
如果您有后台脚本,则后台脚本上下文在整个 Chrome 实例中都是持久的.如果您没有后台脚本,则上下文可能会在需要时创建(例如显示弹出窗口),并在不再需要时销毁.
chrome.tabs.sendMessage()
无法到弹出窗口
如上所述,即使弹出窗口确实存在,它也会存在于后台上下文中.调用 chrome.tabs.sendMessage()
向注入标签/框架的内容脚本发送消息,而不是发送到背景上下文.因此,它不会像弹出窗口那样向非内容脚本发送消息.
操作按钮:启用/禁用(浏览器操作)与显示/隐藏(页面操作)
调用 chrome.pageAction.show()
(MDN) 只会显示页面操作按钮.它不会导致显示任何相关的弹出窗口.如果弹出/选项页面/其他页面实际上并未显示(不仅仅是按钮),则其范围不存在.当它不存在时,它显然不能接收任何消息
代替页面操作的show()
(MDN) 或 hide()
(MDN) 按钮,浏览器动作可以enable()
(MDN) 或 disable()
(MDN) 按钮.
使用扩展程序中的 HTML 以编程方式打开选项卡或窗口
您可以使用tabs.create()
(MDN) 或 windows.create()
(MDN) 从扩展程序中打开包含 HTML 页面的选项卡或窗口.但是,这两个 API 调用的回调在页面的 DOM 存在之前执行,因此在与页面关联的任何 JavaScript 之前执行.因此,您无法立即访问由该页面的内容创建的 DOM,也无法与该页面的 JavaScript 进行交互.非常明确:不会添加 runtime.onMessage()
侦听器,因此新打开的页面不会收到当时发送的消息.
解决此问题的最佳方法是:
- 准备好数据,以便新打开的页面可以获取数据.在开始打开页面的过程之前,通过以下方式执行此操作:
- 如果源在后台上下文中:将数据存储在发送页面全局范围可用的变量中.然后打开页面可以使用
chrome.extension.getBackgroundPage()
直接读取数据. - 如果数据源在后台上下文或内容脚本中:将数据放入
storage.local
(MDN).当 JavaScript 运行时,打开的页面可以读取它.例如,您可以使用名为messageToNewExtensionPage
的键.
- 如果源在后台上下文中:将数据存储在发送页面全局范围可用的变量中.然后打开页面可以使用
- 如果您正在使用
runtime.sendMessage()
,则通过从该页面的代码向数据源发送消息(使用runtime.sendMessage()
或tabs.sendMessage()
用于内容脚本源)请求数据.然后,带有数据的脚本可以使用runtime.onMessage()<提供的
sendResponse
(MDN) 函数将数据发送回/code>. - 等待至少在 DOM 可用之后与新打开的页面进行交互,如果不是在页面的 JavaScript 运行之后.虽然可以在新打开的页面不提供它已启动并运行的特定通知的情况下执行此操作,但这样做更复杂并且仅在某些特定情况下有用(例如,您想在运行新页面中的 JavaScript 之前执行某些操作).2
其他参考资料
铬
火狐
<小时>- 除了一些小的例外:例如使用内容脚本将内容插入到页面上下文中.
您可以使用多种方法.哪种方式最好取决于您正在做什么(例如,当您需要访问与视图中正在执行的代码相关的视图时).一个简单的方法就是轮询等待视图存在.以下代码用于打开窗口:
chrome.windows.create({url: myUrl},function(win){//轮询窗口ID的视图.每 50 毫秒轮询一次//最多 20 次(1 秒).然后进行第二组轮询//适应较慢的机器.在一台中等速度的机器上测试//表示视图在最多第二个 50 毫秒延迟后可用.waitForWindowId(win.id,50,20,actOnViewFound,do2ndWaitForWinId);});函数 waitForWindowId(id,delay,maxTries,foundCallback,notFoundCallback) {if(maxTries--<=0){if(typeof notFoundCallback === '函数'){notFoundCallback(id,foundCallback);}返回;}让视图 = chrome.extension.getViews({windowId:id});if(views.length > 0){if(typeof foundCallback === '函数'){foundCallback(views[0]);}} 别的 {setTimeout(waitForWindowId,delay,id,delay,maxTries,foundCallback,notFoundCallback);}}函数 do2ndWaitForWinId(winId,foundCallback){//轮询窗口ID的视图.每 500 毫秒轮询一次,最多 40 次(20 秒).waitForWindowId(winId,500,40,foundCallback,windowViewNotFound);}函数 windowViewNotFound(winId,foundCallback){//没有找到窗口的视图.在这里做你想做的.//目前安静地失败.}功能actOnViewFound(视图){//当视图存在时,您希望视图发生什么.}
- 来自 MDN:<块引用>
在版本 51 之前的 Firefox 版本中,runtime.onMessage 侦听器将被调用从同一脚本发送的消息(例如,后台脚本发送的消息也将被后台脚本接收).在这些版本的 Firefox 中,如果您从 runtime.onMessage 侦听器中无条件地调用 runtime.sendMessage(),您将设置一个无限循环,这将最大化 CPU 并锁定 Firefox.如果您需要从 runtime.onMessage 中调用 runtime.sendMessage(),您将需要检查 sender.url 属性以验证您没有发送消息以响应从同一脚本发送的消息.此错误已从 Firefox 51 开始解决.
I am running into an issue sending data from my background script to the script for my pageAction
. My content script adds an <iframe />
and the JavaScript in the <iframe />
is receiving the data from my background script, but it does not seem to be retrieved in my pageAction
.
In my background script I have something like:
chrome.tabs.sendMessage(senderTab.tab.id,
{
foo:bar
});
where senderTab.tab.id
is the "sender" in onMessage
Listener in my background script.
In the JavaScript loaded by the <iframe />
injected by my content script I have something like:
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log("received in iframe:", request);
}
});
The <iframe />
receives the message exactly as expected.
I put the same JavaScript in my page_action.js
, but it does not receive any data from the background script. The pageAction is activated with chrome.pageAction.show(senderTab.tab.id);
before I call chrome.tabs.sendMessage(senderTab.tab.id ...
Is the HTML page attached to my pageAction not part of the same tab? Since this tabId
enabled me to activate/"show" the icon, I would think the listener in the JavaScript for the pageAction should also receive from chrome.tabs.sendMessage(senderTab.tab.id ...
In my content script I use the following to send data to the background script:
chrome.runtime.sendMessage({
foo: bar
});
When the content script sends the above message, the pageAction JavaScript is picking it up.
How do I get the background script to properly send data to my pageAction? I do not want to have pageAction request/poll, instead I want pageAction to just listen and receive. E.g., if the pageAction HTML it shown, it should be able to update in real time as the background page makes changes.
Communicating with a page in the background context
Pages which are open in the background context include:
- background pages/scripts(MDN)
- event pages (Firefox does not support event pages. All manifest.json
background
pages remain loaded at all times.) - browser action popups(MDN)
- page action popups(MDN)
- options pages(MDN1, MDN2) (in a popup, a tab, or window)
- sidebar action pages (NDN) (not available in Chrome)
- Any HTML content contained within your extension which is opened normally in a tab, window (e.g. a panel), or frame.1
Using tabs.sendMessage()
(MDN) will not send a message to any of them. You would need to use runtime.sendMessage()
(MDN) to send a message to them. The scope for any of them, except background pages and event pages, only exists when it is being displayed. Obviously, you can not communicate with the code when it does not exist. When the scope exists, you can communicate with any of them using:
Directly
From the background context, you can directly change variables, or call functions, in another page that is also in the background context (i.e. not content scripts), after having gotten a reference to its global scope, its Window, usingextension.getViews()
(MDN),extension.getBackgroundPage()
(MDN), or other method(MDN).
For example, you can call a function created withfunction myFunction
in the page of the first returned view by using something like:winViews = chrome.extension.getViews(); winViews[0].myFunction(foo);
It should be noted that in your callback from
tabs.create()
(MDN) orwindows.create()
(MDN) the view for the newly opened tab or window will probably not yet exist. You will need to use some methodology to wait for the view to exist.2 See below for recommended ways to communicate with newly opened tabs or windows.Directly manipulating values in the other page's scope allows you to communicate any type of data you desire.
Messaging
Receive messages usingchrome.runtime.onMessage
(MDN), 3 which were sent withchrome.runtime.sendMessage()
(MDN). Each time you receive a message in aruntime.onMessage
listener, there will be asendResponse
function provided as the third argument which allows you to directly respond to the message. If the original sender has not supplied a callback to receive such a response in their call tochrome.runtime.sendMessage()
, then the response is lost. If using Promises (e.g.browser.runtime.sendMessage()
in Firefox), the response is passed as an argument when the Promise is fulfilled. If you want to send the response asynchronously, you will need toreturn true;
from yourruntime.onMessage
listener.Ports
You can also connect ports, usingchrome.runtime.connect()
(MDN) andchrome.runtime.onConnect
(MDN) for longer term messaging.Use
chrome.tabs.sendMessage()
to send to content scripts
If you want to send from the background context (e.g. background script or popup) to a content script you would usechrome.tabs.sendMessage()
/chrome.runtime.onMessage
, or connect port(s) usingchrome.tabs.connect()
(MDN)/chrome.runtime.onConnect
.JSON-serializable data only
Using messaging, you can only pass data which is JSON-serializable.Messages are received by all scripts in the background, except the sender
Messages sent to the background context are received by all scripts in the background context which have registered a listener, except the script which sent it.3 There is no way to specify that it is only to be received by a specific script. Thus, if you have multiple potential recipients, you will need to create a way to be sure that the message received was intended for that script. The ways to do so usually rely on specific properties existing in the message (e.g. use adestination
orrecipient
property to indicate what script is to receive it, or define that sometype
of messages are always for one recipient or another), or to differentiate based on thesender
(MDN) supplied to the message handler (e.g. if messages from one sender are always only for a specific recipient). There is no set way to do this, you must choose/create a way to do it for use in your extension.For a more detailed discussion of this issue, please see: Messages intended for one script in the background context are received by all
Data in a StorageArea
Store data to a StorageArea(MDN) and be notified of the change in other scripts usingchrome.storage.onChanged
(MDN). Thestorage.onChanged
event can be listened to in both the background context and content scripts.You can only store data which is JSON-serializable into a StorageArea.
Which method is best to use in any particular situation will depends on what you are wanting to communicate (type of data, state change, etc.), and to which portion, or portions, of your extension you are wanting to communicate from and to. For instance, if you want to communicate information which is not JSON-serializable, you would need to do so directly (i.e. not messaging or using a StorageArea). You can use multiple methods in the same extension.
More on popups
None of the popups (e.g. browser action, or page action) are directly associated with the active tab. There is no concept of a shared or separate instance per tab. However, the user can open one popup in each Chrome window. If more than one popup is open (a maximum of one per Chrome window), then each is in a separate instance (separate scope; has its own Window), but are in the same context. When a popup is actually visible, it exists in the background context.
There is only ever one page action or browser action popup open at a time per Chrome window. The HTML file which will be open will be whichever one has been defined for the active tab of the current window and opened by the user by clicking on the page/browser action button. This can be assigned a different HTML document for different tabs by using chrome.browserAction.setPopup()
(MDN), or chrome.pageAction.setPopup()
(MDN), and specifying a tabId
. The popup can/will be destroyed for multiple reasons, but definitely when another tab becomes the active tab in the window in which the popup is open.
However, any method of communication used will only communicate to the one(s) which is/are currently open, not ones which are not open. If popups are open for more than one Chrome window at a time, then they are separate instances, with their own scope (i.e. their own Window). You can think of this something like having the same web page open in more than one tab.
If you have a background script, the background script context is persistent across the entire instance of Chrome. If you do not have a background script the context may be created when needed (e.g. a popup is shown) and destroyed when no longer needed.
chrome.tabs.sendMessage()
can not communicate to popups
As mentioned above, even if the popup did exist, it will exist in the background context. Calling chrome.tabs.sendMessage()
sends a message to content scripts injected into a tab/frame, not to the background context. Thus, it will not send a message to a non-content script like a popup.
Action button: enable/disable (browser action) vs. show/hide (page action)
Calling chrome.pageAction.show()
(MDN) just causes the page action button to be shown. It does not cause any associated popup to be shown. If the popup/options page/other page is not actually being shown (not just the button), then its scope does not exist. When it does not exist, it, obviously, can not receive any message
Instead of the page action's ability to show()
(MDN) or hide()
(MDN) the button, browser actions can enable()
(MDN) or disable()
(MDN) the button.
Programmatically opening a tab or window with HTML from your extension
You can use tabs.create()
(MDN) or windows.create()
(MDN) to open a tab or window containing an HTML page from within your extension. However, the callback for both of those API calls is executed prior to the page's DOM existing and thus prior to any JavaScript associated with the page existing. Thus, you can not immediately access the DOM created by the contents of that page, nor interact with the JavaScript for the page. Very specifically: no runtime.onMessage()
listeners will have been added, so no messages sent at that time will be received by the newly opening page.
The best ways to resolve this issue are:
- Have the data available so the newly opening page can get the data when it is ready for. Do this by, prior to beginning the process of opening the page:
- If the source is in the background context: store the data in a variable available to the global scope of the sending page. The opening page can then use
chrome.extension.getBackgroundPage()
to read the data directly. - If the source of the data is in either the background context or a content script: place the data into
storage.local
(MDN). The opening page can then read it when its JavaScript is run. For example, you could use a key calledmessageToNewExtensionPage
.
- If the source is in the background context: store the data in a variable available to the global scope of the sending page. The opening page can then use
- If you are using
runtime.sendMessage()
, then initiate the transfer of the data from your newly opening page by sending a message from the that page's code to the source of the data (usingruntime.sendMessage()
, ortabs.sendMessage()
for content script sources) requesting the data. The script with the data can then send the data back using thesendResponse
(MDN) function provided byruntime.onMessage()
. - Wait to interact with the newly opening page until after at least the DOM is available, if not until after the JavaScript for the page has run. While it's possible to do this without the newly opening page providing specific notification that it's up and running, doing so is more complex and only useful in some specific cases (e.g. you want to do something prior to the JavaScript in the new page being run).2
Additional references
Chrome
Firefox
- With some minor exceptions: e.g. using a content script to insert content into the page context.
There are multiple methods which you can use. Which way is best will depend on exactly what you are doing (e.g. when you need to access the view with respect to the code being executed in the view). A simple method would be just to poll waiting for the view to exist. The following code does that for opening a window:
chrome.windows.create({url: myUrl},function(win){ //Poll for the view of the window ID. Poll every 50ms for a // maximum of 20 times (1 second). Then do a second set of polling to // accommodate slower machines. Testing on a single moderately fast machine // indicated the view was available after, at most, the second 50ms delay. waitForWindowId(win.id,50,20,actOnViewFound,do2ndWaitForWinId); }); function waitForWindowId(id,delay,maxTries,foundCallback,notFoundCallback) { if(maxTries--<=0){ if(typeof notFoundCallback === 'function'){ notFoundCallback(id,foundCallback); } return; } let views = chrome.extension.getViews({windowId:id}); if(views.length > 0){ if(typeof foundCallback === 'function'){ foundCallback(views[0]); } } else { setTimeout(waitForWindowId,delay,id,delay,maxTries,foundCallback ,notFoundCallback); } } function do2ndWaitForWinId(winId,foundCallback){ //Poll for the view of the window ID. Poll every 500ms for max 40 times (20s). waitForWindowId(winId,500,40,foundCallback,windowViewNotFound); } function windowViewNotFound(winId,foundCallback){ //Did not find the view for the window. Do what you want here. // Currently fail quietly. } function actOnViewFound(view){ //What you desire to happen with the view, when it exists. }
- From MDN:
In Firefox versions prior to version 51, the runtime.onMessage listener will be called for messages sent from the same script (e.g. messages sent by the background script will also be received by the background script). In those versions of Firefox, if you unconditionally call runtime.sendMessage() from within a runtime.onMessage listener, you will set up an infinite loop which will max-out the CPU and lock-up Firefox. If you need to call runtime.sendMessage() from within a runtime.onMessage, you will need to check the sender.url property to verify you are not sending a message in response to a message which was sent from the same script. This bug was resolved as of Firefox 51.
这篇关于在后台上下文中的脚本之间进行通信(后台脚本、浏览器动作、页面动作、选项页面等)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!