一尘不染

在后台上下文中的脚本之间进行通信(后台脚本,浏览器操作,页面操作,选项页面等)

javascript

我遇到了将数据从后台脚本发送到的脚本的问题pageAction。我的内容脚本添加了一个,<iframe />并且中的JavaScript
<iframe />正在从我的后台脚本接收数据,但是似乎未在中检索到它pageAction

在我的后台脚本中,我有类似以下内容:

chrome.tabs.sendMessage(senderTab.tab.id, 
{
   foo:bar
});

我的后台脚本senderTab.tab.id中的onMessage侦听器中的“发送者”在哪里?

<iframe />内容脚本注入的JavaScript中,我有类似以下内容:

chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
      console.log("received in iframe:", request);
    }   
});

<iframe />接收完全一样的预期消息。

我将相同的JavaScript放入了page_action.js,但它没有从后台脚本接收任何数据。chrome.pageAction.show(senderTab.tab.id);在我致电之前激活pageActionchrome.tabs.sendMessage(senderTab.tab.id ...

附加到我的pageAction的HTML页面不是同一选项卡的一部分吗?由于这tabId使我能够激活/“显示”图标,因此我认为pageAction的JavaScript中的侦听器也应从chrome.tabs.sendMessage(senderTab.tab.id ...


在我的内容脚本中,我使用以下命令将数据发送到后台脚本:

chrome.runtime.sendMessage({
  foo: bar
});

当内容脚本发送上述消息时,pageAction JavaScript会将其拾取。


如何获取后台脚本以将数据正确发送到pageAction?我不想有pageAction请求/投票,而是希望pageAction仅监听和接收。例如,如果显示了pageAction
HTML,则它应该能够在后台页面进行更改时实时更新。


阅读 220

收藏
2020-05-01

共1个答案

一尘不染

在后台上下文中与页面进行通信

在后台上下文中打开的页面包括:

  • 后台页面/脚本MDN
  • 事件页面Firefox不支持事件页面。所有manifest.json_background页面始终保持加载状态
  • 浏览器操作弹出窗口MDN
  • 页面操作弹出窗口MDN
  • 选项页MDN1,MDN2(在弹出窗口,选项卡或窗口中)
  • 侧边栏操作页面(NDN)(在Chrome中不可用)
  • 扩展程序中包含的任何HTML内容,通常在选项卡,窗口

使用MDN不会向其中任何一个发送消息。您将需要使用MDN向他们发送消息。除背景页面和事件页面外,其中任何一个的作用域仅在显示时存在。显然,当代码不存在时,您将无法与代码进行通信。存在作用域时,您可以使用以下方法与其中任何一个进行通信:tabs.sendMessage()``runtime.sendMessage()

  • 直接
    从后台上下文中,在使用MDN引用其全局范围,其Window之后,也可以直接在 也位于后台上下文
    中的另一个页面中更改变量或调用函数(即,不是内容脚本)。,MDN或其他方法MDN。
    例如,您可以使用以下方法调用在第一个返回的视图的页面中创建的函数:extension.getViews()``extension.getBackgroundPage()``function myFunction
    winViews = chrome.extension.getViews();
    

    winViews[0].myFunction(foo);

请注意,在您从MDN或MDN进行的回调中,新打开的选项卡或窗口的视图可能将不存在。您将需要使用某种方法来等待视图存在。2请参阅以下有关与新打开的选项卡或窗口进行通信的推荐方法。tabs.create()``windows.create()

在其他页面范围内直接操作值可让您交流所需的任何类型的数据。

  • 消息
    接收使用消息MDN,3将其用发送MDN。每次您在侦听器中收到消息时,都会提供一个函数作为第三个参数,使您可以直接响应消息。如果原始发件人未在其对的调用中提供回调以接收此类响应,则该响应将丢失。如果使用Promises(例如在Firefox中),则在实现Promise时将响应作为参数传递。如果要异步发送响应,则需要从侦听器发送响应。chrome.runtime.onMessage``chrome.runtime.sendMessage()``runtime.onMessage``sendResponse``chrome.runtime.sendMessage()``browser.runtime.sendMessage()``returntrue;``runtime.onMessage

端口
您还可以使用MDN和MDN连接端口以进行长期消息传递。chrome.runtime.connect()``chrome.runtime.onConnect

使用chrome.tabs.sendMessage()发送到内容脚本
如果你想发送 背景环境(例如,背景脚本或弹出式) 你会使用内容脚本chrome.tabs.sendMessage()/
chrome.runtime.onMessage或连接端口使用(S)MDN。chrome.tabs.connect()``chrome.runtime.onConnect

仅JSON可序列化的数据
使用消息传递,您只能传递JSON可序列化的数据。

消息被后台中的所有脚本接收,但发件人除外
发送到后台上下文中的消息被已注册侦听的后台上下文中的所有脚本(发送脚本的接收者除外)接收。3无法指定仅由特定脚本接收。因此,如果您有多个潜在收件人,则需要创建一种方法来确保收到的邮件是针对该脚本的。这样做的方式通常取决于消息中存在的特定属性(例如,使用destinationrecipient属性来指示要接收什么脚本的脚本,或者定义某些type消息始终用于某个收件人或另一个收件人),或者根据MDNsender提供给消息处理程序(例如,如果来自一个发件人的消息始终仅用于特定收件人)。没有固定的方法来执行此操作,您必须选择/创建一种在扩展程序中使用的方法。

  • StorageArea中的
    数据将 数据 存储到StorageAreaMDN,并使用MDN)通知其他脚本中的更改。该事件可以同时在后台背景和内容的脚本可以听了。chrome.storage.onChanged``storage.onChanged

您只能将可JSON序列化的数据存储到StorageArea中。

在任何特定情况下哪种方法最适合使用取决于您要交流的内容(数据类型,状态更改等),以及您要与之通信的扩展的哪一部分或哪一部分。
例如,如果您想传达不可JSON序列化的信息,则需要直接进行传达(即,不发送消息或使用StorageArea)。您可以在同一扩展中使用多种方法。

有关弹出窗口的更多信息

没有任何弹出窗口(例如,浏览器操作或页面操作)直接与活动选项卡相关联。每个选项卡没有共享或单独实例的概念。但是,用户可以在每个Chrome窗口中打开一个弹出窗口。如果打开了多个弹出窗口(每个Chrome窗口最多一个弹出窗口),则每个弹出窗口都在单独的实例中(单独的作用域;具有自己的Window),但处于相同的上下文中。当弹出窗口实际上是可见的时,它存在于后台上下文中。

每个Chrome窗口一次只能打开一个页面操作或浏览器操作弹出窗口。将为打开的HTML文件将是为当前窗口的活动标签定义的文件,
并由用户通过单击页面/浏览器操作按钮来打开 。通过使用(MDN)或(MDN)并指定一个,可以为不同的选项卡分配不同的HTML文档。可以/将破坏弹出窗口有多种原因,但是肯定是在打开弹出窗口的窗口中另一个选项卡变为活动选项卡时。chrome.browserAction.setPopup()chrome.pageAction.setPopup()tabId

但是,所使用的任何通信方法都只会与当前处于打开状态的通信,而不会与尚未打开的通信。如果一次打开多个Chrome窗口的弹出窗口,则它们是具有各自范围(即各自的Window)的单独实例。您可以想到
类似 在多个标签中打开同一网页的方法。

如果您有后台脚本,则后台脚本上下文将在整个Chrome实例中保持不变。如果您没有后台脚本,则可以在需要时创建上下文(例如,显示弹出窗口),并在不再需要时将其删除。

chrome.tabs.sendMessage()无法 弹出窗口进行通讯

如上所述,即使弹出窗口确实存在,它也将存在于后台上下文中。调用chrome.tabs.sendMessage()会将消息发送到
插入到选项卡/框架中的内容脚本 ,而不是发送到后台上下文。因此,它不会将消息发送到非内容脚本(如弹出窗口)。

操作按钮:启用/禁用(浏览器操作)与显示/隐藏(页面操作)

调用(MDN)仅导致显示页面操作 按钮 。它不会导致显示任何关联的 弹出窗口
。如果实际上未显示弹出窗口/选项页面/其他页面(不仅仅是按钮),则其范围不存在。当它不存在时,显然无法接收任何消息chrome.pageAction.show()

取而代之的是页面动作的能力(MDN)或(MDN)按钮,浏览器操作可(MDN)或(MDN)按钮。show()hide()enable()disable()

通过扩展程序中的HTML以编程方式打开选项卡或窗口

您可以使用(MDN)或(MDN)从扩展程序中打开包含HTML页面的选项卡或窗口。但是,这两个API调用的回调都是在页面DOM存在之前执行的,因此是在与页面相关联的任何JavaScript之前执行的。因此,您无法立即访问该页面内容创建的DOM,也无法与该页面的JavaScript进行交互。具体来说:不会添加任何侦听器,因此新打开的页面不会接收到那时发送的消息。tabs.create()windows.create()runtime.onMessage()

解决此问题的最佳方法是:

  1. 有可用的数据,以便新打开的页面可以在准备就绪时获取数据。在开始打开页面之前,请执行以下操作:
    1. 如果源位于后台上下文中:将数据存储在发送页面的全局范围可用的变量中。然后,打开的页面可用于chrome.extension.getBackgroundPage()直接读取数据。
    2. 如果数据源在后台上下文或内容脚本中:将数据放入(MDN)中。然后,打开的页面可以在运行JavaScript时读取。例如,您可以使用名为的键。storage.localmessageToNewExtensionPage
  2. 如果您使用runtime.sendMessage(),则通过从该页面的代码向该数据源(使用runtime.sendMessage(),或tabs.sendMessage()对于内容脚本源使用)发送一条消息来请求数据,从而开始从新打开的页面传输数据。然后,包含数据的脚本可以使用提供的sendResponse(MDN)功能将数据发送回去runtime.onMessage()
  3. 等待与新打开的页面进行交互,直到至少有DOM可用为止,否则,直到运行页面的JavaScript之后才进行交互。尽管可以在不重新打开页面的情况下提供启动和运行的特定通知的情况下执行此操作,但是这样做更为复杂,并且仅在某些特定情况下才有用(例如,您想在运行新页面中的JavaScript之前执行一些操作) 。2

其他参考

火狐浏览器


  1. 除了一些次要的例外:例如,使用内容脚本将内容插入页面上下文。
  2. 您可以使用多种方法。哪种方法最好取决于您在做什么(例如,何时需要根据视图中执行的代码访问视图)。一个简单的方法就是轮询等待视图存在。以下代码用于打开窗口:
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.
}
  1. 从MDN

在版本51之前的Firefox版本中,将为同一脚本发送的消息调用runtime.onMessage侦听器(例如,由后台脚本发送的消息也将由后台脚本接收)。在那些版本的Firefox中,如果从runtime.onMessage侦听器中无条件调用runtime.sendMessage(),则将设置一个无限循环,该循环将最大程度地占用CPU并锁定Firefox。如果需要从runtime.onMessage内调用runtime.sendMessage(),则需要检查sender.url属性以确认您没有在响应同一脚本发送的消息时发送消息。自Firefox
51起,此错误已解决。

2020-05-01