javascript 将消息从后台脚本发送到内容脚本,然后发送到注入脚本

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/23895377/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-28 01:45:33  来源:igfitidea点击:

Sending message from a background script to a content script, then to a injected script

javascriptgoogle-chrome-extension

提问by user3653206

I'm trying to send messages from the background page to a content script, then send a message from that content script to an injected script. I've tried this, but it isn't working.

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

Here's what my code looks like.

这是我的代码的样子。

manifest.json

清单文件.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

背景.js

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

content_script.js

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

注入.js

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

The message sending doesn't work from the first part, background -> content_script. Is there anything wrong with my code?

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

回答by Xan

Your script doesn't work because of how content scripts are injected.

由于内容脚本的注入方式,您的脚本不起作用。

Problem

问题

When you (re)load your extension, contrary to what some people expect, Chrome will not inject content scripts into existing tabsthat match patterns from the manifest. Only after the extension is loaded, any navigation will check the URL for matching and will inject the code.

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

So, the timeline:

所以,时间线:

  1. You open some tabs. No content scripts there1.
  2. You load your extension. Its top level code gets executed: it tries to pass a message to the current tab.
  3. Since there can be no listener there yet, it fails. (Which is probably the chrome://extensions/page and you can't inject there anyway)
  4. If, afterwards, you try to navigate/open a new tab, the listener gets injected, but your top level code no longer gets executed.
  1. 您打开一些选项卡。没有内容脚本有1
  2. 你加载你的扩展。它的顶级代码被执行:它尝试将消息传递给当前选项卡。
  3. 由于那里还没有侦听器,所以它失败了。(这可能是chrome://extensions/页面,无论如何你不能在那里注入)
  4. 如果之后您尝试导航/打开一个新选项卡,侦听器将被注入,但您的顶级代码不再被执行。

1- This also happens if you reload your extension. If there was a content script injected, it continues to handle its events / doesn't get unloaded, but can no longer communicate with the extension. (for details, see addendum at the end)

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

Solutions

解决方案

Solution 1: you can first ask the tab you're sending a message to whether it's ready, and upon silence inject the script programmatically. Consider:

解决方案 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"});
});

and

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


Solution 2: always inject a script, but make sure it only executes once.

解决方案 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);
  });
}

and

// Content script
var injected;

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

This is simpler, but has complications on extension reload. After an extension is reloaded, the old script is still there1but it's not "your" context anymore - so injectedwill be undefined. Beware of side effects of potentially executing your script twice.

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



Solution 3: just indiscriminately inject your content script(s) on initialization. This is only safe to do if it's safe to run the same content script twice, or run it after the page is fully loaded.

解决方案 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
    });
  }
}); 


I also suspect that you want it to run on some action, and not on extension load. For example, you can employ a Browser Actionand wrap your code in a chrome.browserAction.onClickedlistener.

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



Addendum on orphaned content scripts

关于孤立内容脚本的附录

When an extension gets reloaded, one would expect Chrome to clean up all content scripts. But apparently this is not the case; content scripts' listeners are not disabled. However, any messaging with parent extension will fail. This should probably be considered a bug and may at some point be fixed.I'm going to call this state "orphaned"

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

This is not a problem in either of two cases:

在以下两种情况下,这都不是问题:

  1. Content script has no listeners for events on the page (e.g. only executes once, or only listens to messages from background)
  2. Content script does not do anything with the page, and only messages the background about events.
  1. 内容脚本没有页面事件的监听器(例如只执行一次,或者只监听来自后台的消息)
  2. 内容脚本对页面不做任何事情,只向有关事件的背景发送消息。

However, if that's not the case, you've got a problem: the content script might be doing something but failing or interfering with another, non-orphaned instance of itself.

但是,如果情况并非如此,您就会遇到问题:内容脚本可能正在执行某些操作,但会失败或干扰其自身的另一个非孤立实例。

A solution to this would be:

对此的解决方案是:

  1. Keep track of all event listeners that can be triggered by the page
  2. Before acting on those events, send a "heartbeat" message to background. 3a. If the background responds, we're good and should execute the action. 3b. If the message passing fails, we're orphaned and should desist; ignore the event and deregister all listeners.
  1. 跟踪所有可以被页面触发的事件监听器
  2. 在对这些事件采取行动之前,向后台发送“心跳”消息。3a. 如果后台响应,我们很好,应该执行操作。3b. 如果消息传递失败,我们就是孤儿,应该停止;忽略该事件并注销所有侦听器。

Code, content script:

代码、内容脚本:

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);

Background script:

后台脚本:

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

回答by user3629377

In my background.js

在我的 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);
      });
    });
  }
});

My manifest.json

我的 manifest.json

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

My "content_sciprt.js" worked after "background.js". so I can't receive the response.

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

But after i added

但是在我添加之后

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

It works fine

它工作正常