【问题标题】:Sending message from a background script to a content script, then to a injected script将消息从后台脚本发送到内容脚本,然后发送到注入脚本
【发布时间】:2014-07-16 17:13:51
【问题描述】:

我正在尝试从后台页面向内容脚本发送消息,然后从该内容脚本向注入脚本发送消息。我试过这个,但它不起作用。

这是我的代码的样子。

ma​​nifest.json

{
  "manifest_version": 2,

  "name": "NAME",
  "description": ":D",
  "version": "0.0",
  "permissions": [
    "tabs","<all_urls>"
  ],
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content_script.js"]
    }
  ],
  "web_accessible_resources": [
      "injected.js"
  ],
  "background":{
      "scripts":["background.js"]
  }
}

background.js

chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
  chrome.tabs.sendMessage(tabs[0].id, {greeting: "hello"}, function(response){});
});

content_script.js

var s = document.createElement('script');
s.src = chrome.extension.getURL('injected.js');
s.onload = function(){ 
        this.parentNode.removeChild(this);
};
(document.head||document.documentElement).appendChild(s);


chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    document.dispatchEvent(new CustomEvent('Buffer2Remote', {todo: "LOL"}));
});

injected.js

document.addEventListener('Buffer2Remote', function(e){
    alert(e.todo);
});

消息发送在第一部分背景 -> content_script 中不起作用。 我的代码有什么问题吗?

【问题讨论】:

  • 您的清单中有错字,content_scripts 中有多余的逗号
  • @Xan 我修好了,但还是不行。

标签: javascript google-chrome-extension


【解决方案1】:

由于内容脚本的注入方式,您的脚本无法运行。

问题

当您(重新)加载您的扩展程序时,与某些人的预期相反,Chrome 不会将与清单中的模式匹配的内容脚本注入现有选项卡。只有在加载扩展后,任何导航都会检查 URL 是否匹配并注入代码。

所以,时间线:

  1. 您打开了一些标签。那里没有内容脚本1
  2. 您加载了您的扩展程序。它的顶级代码被执行:它尝试将消息传递到当前选项卡。
  3. 因为那里还没有侦听器,所以它失败了。 (这可能是chrome://extensions/ 页面,无论如何您都不能在那里注入)
  4. 如果之后您尝试导航/打开一个新选项卡,侦听器会被注入,但您的顶级代码不再执行。

1 - 如果您重新加载扩展程序,也会发生这种情况。如果注入了内容脚本,它会继续处理其事件/不会被卸载,但不能再与扩展程序通信。 (详情见文末附录)

解决方案

解决方案 1:您可以先询问要发送消息的选项卡是否准备就绪,然后在静默时以编程方式注入脚本。考虑:

// Background
function ensureSendMessage(tabId, message, callback){
  chrome.tabs.sendMessage(tabId, {ping: true}, function(response){
    if(response && response.pong) { // Content script ready
      chrome.tabs.sendMessage(tabId, message, callback);
    } else { // No listener on the other end
      chrome.tabs.executeScript(tabId, {file: "content_script.js"}, function(){
        if(chrome.runtime.lastError) {
          console.error(chrome.runtime.lastError);
          throw Error("Unable to inject script into tab " + tabId);
        }
        // OK, now it's injected and ready
        chrome.tabs.sendMessage(tabId, message, callback);
      });
    }
  });
}

chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
  ensureSendMessage(tabs[0].id, {greeting: "hello"});
});

// Content script
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
  if(request.ping) { sendResponse({pong: true}); return; }
  /* Content script action */
});

解决方案 2:总是注入一个脚本,但要确保它只执行一次。

// Background
function ensureSendMessage(tabId, message, callback){
  chrome.tabs.executeScript(tabId, {file: "content_script.js"}, function(){
    if(chrome.runtime.lastError) {
      console.error(chrome.runtime.lastError);
      throw Error("Unable to inject script into tab " + tabId);
    }
    // OK, now it's injected and ready
    chrome.tabs.sendMessage(tabId, message, callback);
  });
}

// Content script
var injected;

if(!injected){
  injected = true;
  /* your toplevel code */
}

这更简单,但在扩展重新加载时有复杂性。重新加载扩展后,旧脚本仍然存在1,但它不再是“你的”上下文 - 所以injected 将是未定义的。请注意可能会执行两次脚本的副作用。


解决方案 3:在初始化时不加选择地注入您的内容脚本。只有在两次运行相同的内容脚本或在页面完全加载后运行它是安全的情况下,这样做才是安全的。

chrome.tabs.query({}, function(tabs) {
  for(var i in tabs) {
    // Filter by url if needed; that would require "tabs" permission
    // Note that injection will simply fail for tabs that you don't have permissions for
    chrome.tabs.executeScript(tabs[i].id, {file: "content_script.js"}, function() {
      // Now you can use normal messaging
    });
  }
}); 

我还怀疑您希望它在某些操作上运行,而不是在扩展加载时运行。例如,您可以使用 Browser Action 并将您的代码包装在 chrome.browserAction.onClicked 侦听器中。


关于孤立内容脚本的附录

当重新加载扩展程序时,人们会期望 Chrome 清理所有内容脚本。但显然情况并非如此。内容脚本的侦听器未被禁用。但是,任何带有父分机的消息都会失败。 这可能应该被认为是一个错误,并且可能会在某个时候被修复。我将把这个状态称为“孤立”

这两种情况都不是问题:

  1. 内容脚本对页面上的事件没有监听器(例如,只执行一次,或者只监听来自后台的消息)
  2. 内容脚本不对页面做任何事情,只向背景发送有关事件的消息。

但是,如果不是这种情况,您就会遇到问题:内容脚本可能正在做某事,但失败或干扰了另一个非孤立的自身实例。

解决方案是:

  1. 跟踪可以由页面触发的所有事件侦听器
  2. 在对这些事件采取行动之前,向后台发送“心跳”消息。 3a。如果后台响应,我们很好,应该执行该操作。 3b。如果消息传递失败,我们就是孤儿,应该停止;忽略该事件并取消注册所有侦听器。

代码、内容脚本:

function heartbeat(success, failure) {
  chrome.runtime.sendMessage({heartbeat: true}, function(reply){
    if(chrome.runtime.lastError){
      failure();
    } else {
      success();
    }
  });
}

function handler() {
  heartbeat(
    function(){ // hearbeat success
      /* Do stuff */
    }, 
    function(){ // hearbeat failure
      someEvent.removeListener(handler);
      console.log("Goodbye, cruel world!");
    }
  );
}
someEvent.addListener(handler);

后台脚本:

chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
  if(request.heartbeat) { sendResponse(request); return; }
  /* ... */
});    

【讨论】:

  • 实际上现在我想我遇到了你提到的关于在 content_script 上杀死听众的问题。你能解释一下吗?
  • Chrome 不允许通过 executeScript 注入内容脚本:“无法访问 chrome:// URL”。有没有其他注入方式?
  • @Paras 不在chrome:// 页中,没有。你不能碰这些。
  • 哦,所以在其他页面中,应该没有错误。对吗?
  • 您可以简单地检查chrome.runtime.id,而不是检查心跳,如果是孤儿院,它将是undefined。 if(chrome.runtime.id == undefined) return;
【解决方案2】:

在我的 background.js 中

chrome.tabs.onUpdated.addListener(function(tabId, info, tab) {
  if (tab.url !== undefined && info.status == "complete") {

    chrome.tabs.query({active: true, currentWindow: true, status: "complete"}, function (tabs) {
      console.log(tabs);
      chrome.tabs.sendMessage(tabs[0].id, {greeting: "hello"}, function (response) {
        console.log(response.farewell);
      });
    });
  }
});

我的 manifest.json

"content_scripts": [
{
  "matches": ["http://*/*", "https://*/*"],
  "js": [
    "content_script.js"
  ],
  "run_at": "document_end"
}

我的“content_sciprt.js”在“background.js”之后工作。所以我收不到回复。

但是在我添加之后

  1. info.status=="complete", status: "complete"
  2. "run_at": "document_end" 在我的 manifest.json 中

效果很好

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-27
    • 1970-01-01
    • 1970-01-01
    • 2014-11-23
    • 2013-06-26
    相关资源
    最近更新 更多