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

标签 javascript iframe google-chrome-extension firefox-webextensions

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

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

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

其中senderTab.tab.id是后台脚本中onMessage侦听器中的“发送者”。

在内容脚本注入(inject)的<iframe />加载的JavaScript中,我有类似以下内容:
chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
      console.log("received in iframe:", request);
    }   
});
<iframe />完全按预期接收消息。

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

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

在我的内容脚本中,我使用以下命令将数据发送到后台脚本:
chrome.runtime.sendMessage({
  foo: bar
});  

当内容脚本发送上述消息时,pageAction JavaScript会对其进行接收。

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

最佳答案

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

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

  • background pages/scripts(MDN)
  • event pages(Firefox不支持事件页面。所有manifest.json background 页面remain loaded at all times。)
  • browser action popups(MDN)
  • page action popups(MDN)
  • options pages(MDN1MDN2)(在弹出窗口,选项卡或窗口中)
  • sidebar action pages (NDN)(在Chrome中不可用)
  • 扩展程序中包含的任何HTML内容,即opened normally in a tab, window (e.g. a panel)或frame.1

  • 使用 tabs.sendMessage() (MDN)不会向任何一个发送消息。您将需要使用 runtime.sendMessage() (MDN)向他们发送消息。除背景页面和事件页面外,其中任何一个的作用域仅在显示时存在。显然,当代码不存在时,您将无法与其进行通信。存在作用域时,您可以使用以下方法与其中任何一个进行通信:
  • 直接
    从后台上下文中,您可以使用Window( extension.getViews() )对其全局范围MDN进行引用,然后直接在也位于后台上下文中的另一个页面(即,不是内容脚本)中更改变量或调用函数。 , extension.getBackgroundPage() (MDN)或other method(MDN)。
    例如,您可以使用以下方法在返回的第一个 View 的页面中调用用function myFunction创建的函数:

    winViews = chrome.extension.getViews();
    winViews[0].myFunction(foo); 
    

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

    在其他页面范围内直接操作值可让您交流所需的任何类型的数据。
  • 消息
    使用 chrome.runtime.onMessage (MDN)发送的 chrome.runtime.sendMessage() (MDN)接收消息3。每次您在runtime.onMessage侦听器中收到消息时,都会提供sendResponse函数作为第三个参数,使您可以直接响应消息。如果原始发件人未在其对chrome.runtime.sendMessage()的调用中提供回调以接收此类响应,则该响应将丢失。如果使用Promises(例如Firefox中的browser.runtime.sendMessage()),则在实现Promise时将响应作为参数传递。如果要异步发送响应,则需要从return true;侦听器中输入runtime.onMessage

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

    使用chrome.tabs.sendMessage()发送到内容脚本
    如果要从后台上下文(例如,后台脚本或弹出窗口)发送到内容脚本,则可以使用chrome.tabs.sendMessage() / chrome.runtime.onMessage,或使用 chrome.tabs.connect() (MDN)/ chrome.runtime.onConnect连接端口。

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

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

    有关此问题的更详细讨论,请参见:Messages intended for one script in the background context are received by all
  • 存储区域中的数据
    将数据存储到StorageArea(MDN),并使用 chrome.storage.onChanged (MDN)通知其他脚本中的更改。 storage.onChanged事件可以在后台上下文和内容脚本中侦听。

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

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

    有关弹出式窗口的更多信息

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

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

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

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

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

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

    调用 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())来请求数据,从而启动从新打开页面的数据传输。然后,包含数据的脚本可以使用sendResponse提供的runtime.onMessage()(MDN)函数将数据发送回去。
  • 等待与新打开的页面进行交互,直到至少有DOM可用为止;否则,直到页面的JavaScript运行之后才进行交互。尽管可以在不重新打开页面的情况下提供启动和运行的特定通知的情况下执行此操作,但是这样做更为复杂,并且仅在某些特定情况下才有用(例如,您要在运行新页面中的JavaScript之前执行一些操作) .2

  • 其他引用

    Chrome
  • Message Passing
  • Chrome extension overview
  • architecture
  • Communication between pages

  • 火狐浏览器
  • WebExtensions
  • Anatomy of a WebExtension


  • 除了一些小小的异常(exception):使用内容脚本将内容插入页面上下文。
  • 您可以使用多种方法。哪种方法最好取决于您正在执行的操作(例如,何时需要根据 View 中执行的代码访问 View )。一个简单的方法就是轮询等待该 View 存在。以下代码用于打开窗口:

    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.

  • 关于javascript - 在后台上下文中的脚本之间进行通信(后台脚本,浏览器操作,页面操作,选项页面等),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41420528/

    相关文章:

    javascript - 如何将过滤器和网格应用于 D3 Choropleth

    javascript - 将新值推送到 React 钩子(Hook)中的嵌套数组

    javascript 使用严格的文档类型创建一个 iframe

    iframe - 来自不同域的 SSL iframe 和 SSL 页面

    javascript - 带有异步等待的 chrome.runtime.onMessage 响应

    javascript - 如何从 chrome 扩展中的多个 HTML 文件访问 chrome 存储

    javascript - Chrome 扩展,如何延迟脚本直到页面完全加载

    java - 成功执行servlet后如何在jsp中生成成功警报?

    javascript - 下拉菜单不会在第二次单击时关闭

    javascript - iframe问题和覆盖