在后台上下文中的脚本之间进行通信(后台脚本,浏览器操作,页面操作,选项页面等) [英] Communicate between scripts in the background context (background script, browser action, page action, options page, etc.)

查看:127
本文介绍了在后台上下文中的脚本之间进行通信(后台脚本,浏览器操作,页面操作,选项页面等)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



在我的背景中脚本我有类似的东西:

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

其中 senderTab.tab.id onMessage 我的后台脚本中的监听器中的


$ b 在由< iframe /> 由我的内容脚本注入我有类似的内容:

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

< iframe /> 收到消息完全如预期。



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



附加到我的pageAction的HTML页面不是同一个标签的一部分吗?因为这个 tabId 使我能够激活/显示图标,我认为pageAction的JavaScript中的监听器也应该从 chrome.tabs.sendMessage(senderTab.tab.id ...






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

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

当内容脚本发送上述消息时,pageAction JavaScript正在拾取它。






如何获得后台脚本以正确地将数据发送到我的pageAction?我不想要pageAction请求/轮询,而是我希望pageAction只听和接收。例如,如果显示pageAction HTML,它应该能够在后台页面进行更改时进行实时更新。 解决方案

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



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



使用 tabs.sendMessage() MDN 不会向任何人发送消息。您需要使用 runtime.sendMessage() MDN 向他们发送消息。除了背景页面和活动页面之外,其中任何一个的范围只有在显示时才存在。显然,当代码不存在时,你不能与代码通信。当范围存在时,您可以使用以下任何一种方式与他们进行交流:




  • 直接

    从后台上下文中,您可以直接在另一个也在后台上下文中的页面(即不是内容脚本)中更改变量或调用函数,全球范围,其窗口,使用 extension.getViews() MDN extension.getBackgroundPage () MDN 其他方法 MDN

    例如,您可以致电在第一个返回的视图的页面中使用
    $ b $

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

    需要注意的是,在您从 tabs.create() 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()发送至内容脚本 >
    如果您想从后台上下文(例如后台脚本或弹出窗口)发送内容脚本,您可以使用 chrome。 tabs.sendMessage() / chrome.runtime.onMessage ,或使用 chrome.tabs.connect() MDN ) SUP> / chrome.runtime.onConnect


    仅限JSON序列化数据

    使用消息传递,您只能传递JSON序列化数据。 p>

    消息由后台的所有脚本接收,但发件人

    发送到后台上下文的消息被所有脚本在后台上下文中已经注册了一个监听器,除了发送它的脚本 3 没有办法指定它只能被特定的脚本接收。因此,如果您有多个潜在收件人,则需要创建一种方法来确保收到的消息适用于该脚本。这样做的方式通常依赖于消息中存在的特定属性(例如,使用 destination 收件人属性来指示什么脚本可以接收它,或者定义某些类型的消息总是针对一个接收者或另一个接收者),或者根据 sender (< a href =https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/runtime/onMessage#Parameters =nofollow noreferrer> MDN ) 提供给消息处理程序(例如,如果来自一个发件人的消息始终只针对特定的收件人)。没有特定的方法来做到这一点,你必须选择/创建一个方法来使用它在你的扩展中使用。

    请参阅:所有用于后台上下文中的脚本的邮件都会收到
  • StorageArea中的数据

    将数据存储到 StorageArea MDN ,并使用 chrome.storage.onChanged MDN 。可以在后台上下文和内容脚本中监听 storage.onChanged 事件。


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




在任何特定情况下最好使用哪种方法将取决于您想要传达的内容(数据类型,状态更改等)以及您的扩展的哪一部分或哪些部分想与来往交流。例如,如果您想传递不是JSON序列化的信息,则需要直接执行此操作(即不传递消息或使用StorageArea)。您可以在同一个扩展名中使用多个方法。



更多弹出窗口



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



每个Chrome窗口中一次只打开一个页面操作或浏览器操作弹出窗口。将打开的HTML文件将为当前窗口的活动选项卡定义的文件,以及用户通过单击页面/浏览器操作按钮打开的文件。通过使用 chrome,可以为不同的选项卡分配不同的HTML文档.browserAction.setPopup() MDN chrome.pageAction.setPopup() MDN ,并指定一个 tabId 。弹出窗口可能会因多种原因而被销毁,但当另一个选项卡成为弹出窗口打开的窗口中的活动选项卡时。不过,任何使用的通讯方法都只会与目前开放的通讯方式通讯,而不是通讯方式的通讯方式。如果弹出窗口一次对多个Chrome窗口打开,则它们是单独的实例,并具有各自的范围(即它们自己的窗口)。您可以将 这种类似于的内容视为在多个标签中打开了相同的网页。



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



chrome.tabs.sendMessage()无法与弹出式窗口进行通信



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

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



调用 chrome.pageAction.show() MDN 只会导致页面操作按钮成为所示。它不会导致显示任何关联的弹出。如果弹出菜单/选项页面/其他页面没有实际显示(不仅仅是按钮),则其范围不存在。当它不存在时,显然它不会收到任何消息。



而不是页面操作的能力 show() MDN hide() MDN 按钮,浏览器操作可以 enable() MDN 禁用() MDN 按钮。

以编程方式打开选项卡或窗口从你的扩展中使用HTML



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



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


  1. 提供可用数据,以便新打开的页面可以准备好时获取数据。在开始打开页面的过程之前,请执行以下操作:


    1. 如果源位于后台上下文中:将数据存储在可用的变量中全球范围的发送页面。然后打开的页面可以使用 chrome.extension.getBackgroundPage()直接读取数据。 如果数据源是在后台环境或内容脚本中:将数据放入 storage.local MDN 。打开的页面可以在运行JavaScript时读取它。例如,您可以使用名为 messageToNewExtensionPage 的键。

    2. 如果您是使用 runtime.sendMessage(),然后通过从该页面的代码发送一条消息到数据源,从新开始的页面开始传输数据(使用 runtime.sendMessage(),或 tabs.sendMessage()用于内容脚本来源)请求数据。然后,带有数据的脚本可以使用<$ c提供的 sendResponse (MDN) 函数发回数据$ c> runtime.onMessage()

    3. 等待与新打开的页面交互,直到DOM至少可用后,为页面运行。尽管可以在没有新开启的页面提供特定通知的情况下执行此操作,但是这样做更为复杂并且仅在某些特定情况下才有用(例如,您希望在运行新页面中的JavaScript之前执行某些操作) 。 2



    其他参考资料



    Chrome





    Firefox









    1. 除了一些小例外:例如使用内容脚本将内容插入到页面上下文中。
    2. 您可以使用多种方法。哪种方式最好取决于你在做什么(例如,当你需要访问与视图中执行的代码相关的视图时)。一个简单的方法就是轮询等待视图存在。下面的代码可以打开一个窗口:

        chrome.windows.create({url: myUrl},function(win){
      //轮询窗口ID的视图轮询每50ms
      //最多20次(1秒),然后执行第二轮询到
      //容纳较慢的机器在一台中等速度的机器上进行测试
      //表示视图在最多第二个50ms延迟后可用
      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);
      }
      }
      函数do2ndWaitForWinId(winId,foundCallback){
      //轮询窗口ID的视图。每500毫秒轮询最多40次(20秒)。
      waitForWindowId(winId,500,40,foundCallback,windowViewNotFound);
      }
      函数windowViewNotFound(winId,foundCallback){
      //没有找到窗口的视图。在这里做你想做的。
      //目前安静地失败。
      }
      function actOnViewFound(view){
      //当视图存在时,你希望发生什么。
      }


    3. 从MDN


      在版本51之前,runtime.onMessage侦听器将被调用以处理来自同一脚本的消息(例如后台脚本发送的消息也将被后台脚本接收)。在Firefox的这些版本中,如果您无条件地从runtime.onMessage侦听器中调用runtime.sendMessage(),您将设置一个无限循环,以最大化CPU并锁定Firefox。如果您需要从runtime.onMessage内调用runtime.sendMessage(),则需要检查sender.url属性以验证您没有发送消息来响应从相同脚本发送的消息。此bug已在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:

    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, using extension.getViews()(MDN), extension.getBackgroundPage()(MDN), or other method(MDN).
      For example, you can call a function created with function 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) or windows.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 using chrome.runtime.onMessage(MDN), 3 which were sent with chrome.runtime.sendMessage()(MDN). Each time you receive a message in a runtime.onMessage listener, there will be a sendResponse 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 to chrome.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 to return true; from your runtime.onMessage listener.

      Ports
      You can also connect ports, using chrome.runtime.connect()(MDN) and chrome.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 use chrome.tabs.sendMessage()/chrome.runtime.onMessage, or connect port(s) using chrome.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 a destination or recipient property to indicate what script is to receive it, or define that some type of messages are always for one recipient or another), or to differentiate based on the sender(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 using chrome.storage.onChanged(MDN). The storage.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:

    1. 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:

      1. 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.
      2. 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 called messageToNewExtensionPage.

    2. 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 (using runtime.sendMessage(), or tabs.sendMessage() for content script sources) requesting the data. The script with the data can then send the data back using the sendResponse(MDN) function provided by runtime.onMessage().
    3. 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


    1. With some minor exceptions: e.g. using a content script to insert content into the page context.
    2. 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.
      }
      

    3. 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆