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

IT技术 javascript iframe google-chrome-extension firefox-addon-webextensions
2021-02-11 15:45:21

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

在我的后台脚本中,我有类似的东西:

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

我的后台脚本senderTab.tab.idonMessageListener 中的“发件人”在哪里

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

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

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

我在我的 .js 中放置了相同的 JavaScript page_action.js,但它没有从后台脚本接收任何数据。pageActionchrome.pageAction.show(senderTab.tab.id);在我打电话之前被激活chrome.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,它应该能够在后台页面进行更改时实时更新。

1个回答

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

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

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

  • 直接
    从后台上下文中,您可以直接在另一个页面中更改变量或调用函数,该页面也在后台上下文中(即不是内容脚本),在获得对其全局范围,其Window的引用后,使用MDN , ( 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.onMessagechrome.runtime.sendMessage()runtime.onMessagesendResponsechrome.runtime.sendMessage()browser.runtime.sendMessage()return true;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消息始终是给一个或另一个收件人的),或者根据( MDN )sender提供给消息处理程序(例如,如果来自一个发件人的消息始终仅适用于特定收件人)。没有固定的方法可以做到这一点,您必须选择/创建一种方法来做到这一点,以便在您的扩展中使用。

    有关此问题的更详细讨论,请参阅:所有人都收到了用于后台上下文中的一个脚本的消息

  • StorageArea 中的
    数据 将数据存储到StorageArea ( MDN )并使用( MDN )通知其他脚本中的更改可以在后台上下文和内容脚本中收听事件。chrome.storage.onChangedstorage.onChanged

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

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

更多关于弹出窗口

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

每个 Chrome 窗口一次只打开一个页面操作或浏览器操作弹出窗口。将打开的 HTML 文件将是为当前窗口的活动选项卡定义并由用户通过单击页面/浏览器操作按钮打开的文件通过使用( MDN )( MDN )并指定一个. 弹出窗口可以/将由于多种原因被销毁,但肯定是当另一个选项卡成为打开弹出窗口的窗口中的活动选项卡时。chrome.browserAction.setPopup()chrome.pageAction.setPopup()tabId

但是,使用的任何通信方法都只会与当前打开的那些通信,而不是未打开的通信。如果一次为多个 Chrome 窗口打开弹出窗口,则它们是单独的实例,具有自己的范围(即自己的窗口)。您可以认为这类似于在多个选项卡中打开相同的网页。

如果您有后台脚本,则后台脚本上下文在整个 Chrome 实例中都是持久的。如果您没有后台脚本,则上下文可能会在需要时创建(例如显示弹出窗口),并在不再需要时销毁。

chrome.tabs.sendMessage()无法弹出窗口通信

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

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

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

取而代之的是页面动作的能力MDNMDN按钮,浏览器操作可MDNMDN按钮。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.
    }
    
  3. 来自 MDN

    在版本 51 之前的 Firefox 版本中,runtime.onMessage 侦听器将被调用从同一脚本发送的消息(例如,后台脚本发送的消息也将被后台脚本接收)。在这些版本的 Firefox 中,如果您从 runtime.onMessage 侦听器中无条件地调用 runtime.sendMessage(),您将设置一个无限循环,这将最大化 CPU 并锁定 Firefox。如果您需要从 runtime.onMessage 中调用 runtime.sendMessage(),您将需要检查 sender.url 属性以验证您没有发送消息以响应从同一脚本发送的消息。此错误已从 Firefox 51 开始解决。

@EricG,后台脚本上下文在 Chrome 的整个实例中都是持久的。如果您包含的脚本文件与您定义为用于其他用途的后台脚本相同,那么您可能会在同一上下文(并且可能在多个上下文中)的多个范围内运行该脚本。请确保在您的新问题中包含您的manifest.json,因为这可能对理解您的问题至关重要。还请在此处发表评论,并附上指向您的新问题的链接。无论如何,我可能会看到它,但如果您能直接通知我,我将不胜感激。
2021-03-17 15:45:21
谢谢,我可能会在本周晚些时候创建新问题,因为我需要将我的扩展归结为最小的部分。如果我使用chrome.pageAction.setPopup()相同的 HTML 页面,它仍然是共享实例还是每个选项卡都有自己的实例化?
2021-03-19 15:45:21
我将提出另一个问题来扩展这一点。也许这与在后台脚本中重用有关。后台脚本是在所有选项卡中持久存在还是每个选项卡都有自己的后台脚本版本?
2021-03-28 15:45:21
谢谢@makyen,但是只有当前页面的 pageAction 接收更新的最佳策略是什么?在我的后台脚本中,我function(request, sender, sendResponse) { sendResponse(foo); }用来捕获来自pageAction的请求,然后将数据发送回调用pageAction,但是,这会转到打开选项卡中的每个页面操作。需要对上述内容进行哪些修改,以便仅将更新发送到正确的 pageAction?
2021-04-08 15:45:21
@EricG,一次只打开一个 pageAction弹出窗口:为当前窗口的活动选项卡定义并由用户通过单击页面操作按钮打开的弹出窗口通过使用chrome.pageAction.setPopup()和指定一个tabId. 但是,使用的任何通信方法都只会与当前打开的那个通信,而不是多个。从您的评论来看,您似乎在谈论实际pageAction弹出窗口以外的其他内容请更详细地描述。
2021-04-10 15:45:21