将数据从执行的脚本传递到chrome扩展中的背景

时间:2015-04-03 09:29:14

标签: javascript google-chrome-extension

我有这个想法,用于将数据从注入的脚本(getDOM.js)传递到我的background.js

bacgkround.js

chrome.contextMenus.onClicked.addListener(function(info, tab){
  chrome.tabs.executeScript(tab.id, {file: "getDOM.js"})
});


chrome.contextMenus.onClicked.addListener(function(info, tab){
  chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
            chrome.tabs.sendMessage(tabs[0].id, {greeting: "GetURL"}, function(response) {
            alert(response.navURL);
      });
      });
});

getDOM.js

chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {

    if (request.greeting === "GetURL")
      sendResponse({navURL:'test'});
  });

你可以看到我用按摩功能来传递数据,但是有一个问题。我无法在正确的时间获取数据,我将传递background.js以前的数据

每次它都会提醒以前的数据,

内容必须是动态的(不是特定的“测试”)。想象getDOM.js将传递选定的文本,使用此代码将传递先前选定的文本。我该如何解决这个问题?

我的动态数据示例:

function getHTMLOfSelection () {
      var range;
      if (document.selection && document.selection.createRange) {
        range = document.selection.createRange();
        return range.htmlText;
      }
      else if (window.getSelection) {
        var selection = window.getSelection();
        if (selection.rangeCount > 0) {
          range = selection.getRangeAt(0);
          var clonedSelection = range.cloneContents();
          var div = document.createElement('div');
          div.appendChild(clonedSelection);
          return div.innerHTML;
        }
        else {
          return '';
        }
      }
      else {
        return '';
      }
}

var dom = getHTMLOfSelection();
chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {

    if (request.greeting === "getDom")
      sendResponse({DOM:dom});
  });

它会将选定的dom传递给background.js

1 个答案:

答案 0 :(得分:0)

这里有很多问题。我们来看看。

  1. 在注入脚本时执行getHTMLOfSelection()没有意义。您可能应该将其放在消息处理程序中:在被要求时获取选择

  2. 更大的问题是每次注入脚本时都会这样。这与1一起导致各种有趣。让我们看一下更详细的信息!

    • 用户触发上下文菜单。
    • 您的第一个contextMenu.onClicked处理程序运行。脚本注入计划。它是异步的,所以除非你使用回调,否则你不知道它什么时候会完成。你没有。
    • 您的第二个contextMenu.onClicked处理程序运行。它向可能尚未执行的脚本发送消息。如果你很幸运,你会收到回复。
    • 用户在同一页面上再次触发上下文菜单。
    • 您的第一个contextMenu.onClicked处理程序再次运行。该脚本将再次注入 ,为将与第一个竞争的消息创建第二个侦听器。它又是异步的,所以当你的消息到达时,可能是 dom是最新的。也许不是。
    • 您的第二个contextMenu.onClicked处理程序再次运行。确实有一个消息监听器(可能是两个!),它返回可能最新数据。

    等等。你看到了问题吗?

  3. 此外,您无法通过sendResponse传递DOM对象。该对象需要是JSON可序列化的,并且DOM对象包含循环引用,这是禁止的。您需要在内容脚本端提取所需的数据,并仅传递该数据。

  4. 所以,让我们试着解决这些问题。

    有两种方法可以解决这个问题。我会介绍两者,选择你喜欢的。两者都将解决问题1和2。

    第一种方式是通过包含某种保护变量来确保您的消息处理程序只添加一次:

    // getDOM.js
    
    if(!getDOM_ready) { // This will be undefined the first time around
      getDOM_ready = true;
      chrome.runtime.onMessage.addListener(
        function(request, sender, sendResponse) {
          // If only you knew how I hate this "greeting" example copied over
          if (request.command === "GetURL") {
            var dom = getHTMLOfSelection();
            sendResponse({DOM: dom});
          }
        }
      );
    }
    
    function getHTMLOfSelection() {
      /* ... */
    }
    

    然后在后台,我们需要确保在脚本完成执行后才发送消息:

    chrome.contextMenus.onClicked.addListener(function(info, tab){
      chrome.tabs.executeScript(tab.id, {file: "getDOM.js"}, function() {
        // This only executes after the content script runs
        // Oh, and you most certainly don't need to query for tabs,
        //   you already have a tab and its id in `onClicked`
        chrome.tabs.sendMessage(tab.id, {command: "GetURL"}, function(response) {
          alert(response.navURL);
        });
    });
    

    第二种方式是完全删除Messaging。 executeScript实际上返回内容脚本评估的最后一个值。这使得内容脚本变得微不足道,并且不会留下消息监听器:

    // getDOM.js
    function getHTMLOfSelection() {
      /* ... */
    }
    
    getHTMLOfSelection(); // Yes, that's it
    

    在背景方面,您需要调整听众:

    chrome.contextMenus.onClicked.addListener(function(info, tab){
      chrome.tabs.executeScript(tab.id, {file: "getDOM.js"}, function(results) {
        // results is an array, because it can be executed in more than one frame
        alert(results[0]);
    });
    

    这里的代码要简单得多,并且它不会留下活跃的事件监听器。

    对于问题3,您需要从选择中提取所需的信息(例如,链接),并仅传递而不是对象。


    最后,这不是一个完整的解决方案。问题是,您的选择可以在iframe内,并且此解决方案仅将代码注入顶部框架。解决这个问题留给读者一个练习;在内容脚本选项中使用all_frames: true将注入所有帧,其中一个将具有非空选择。你只需看看哪个。