Отправка сообщения из фона script в контент script, затем до введенного script

Я пытаюсь отправить сообщения с фоновой страницы в контент script, а затем отправить сообщение из этого содержимого script на введенный script. Я пробовал это, но он не работает.

Вот как выглядит мой код.

manifest.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);
});

Посылка сообщения не работает из первой части, background → content_script. Что-то не так с моим кодом?

Ответ 1

Ваш script не работает из-за того, как вставляются скрипты содержимого.

Проблема

Когда вы (повторно) загружаете свое расширение, вопреки ожиданиям некоторых людей, Chrome не будет добавлять сценарии содержания в существующие вкладки, которые соответствуют шаблонам из манифеста. Только после того, как расширение будет загружено, любая навигация будет проверять URL-адрес для сопоставления и будет вводить код.

Итак, временная шкала:

  • Вы открываете некоторые вкладки. Нет сценариев содержания 1.
  • Вы загружаете расширение. Выполняется его код верхнего уровня: он пытается передать сообщение на текущую вкладку.
  • Так как там еще нет слушателя, он терпит неудачу. (Это, вероятно, страница chrome://extensions/, и вы все равно не можете вставлять туда)
  • Если после этого вы попытаетесь перейти/открыть новую вкладку, слушатель будет введен, но ваш код верхнего уровня больше не будет выполнен.

1 - Это также происходит, если вы перезагрузите расширение. Если был добавлен контент script, он продолжает обрабатывать свои события/не выгружается, но больше не может связываться с расширением. (подробности см. в дополнении в конце)

Решение

Решение 1: вы можете сначала запросить вкладку, по которой вы отправляете сообщение, готово ли оно, и после молчания введите программный код script. Рассмотрим:

// 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 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: всегда вводит script, но убедитесь, что он выполняется только один раз.

// 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 injected and ready
    chrome.tabs.sendMessage(tabId, message, callback);
  });
}

и

// Content script
var injected;

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

Это проще, но имеет сложности с перезагрузкой. После того, как расширение будет перезагружено, старый script все еще существует 1 но это не "ваш" контекст больше, поэтому injected будет undefined. Остерегайтесь побочных эффектов потенциального выполнения вашего script дважды.


Решение 3: просто без разницы вводит ваш контент script (s) при инициализации. Это безопасно, если можно безопасно запускать один и тот же контент script дважды, или запустить его после полной загрузки страницы.

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

Я также подозреваю, что вы хотите, чтобы он работал на каком-то мероприятии, а не на нагрузке расширения. Например, вы можете использовать Действие браузера и обернуть свой код в прослушиватель chrome.browserAction.onClicked.


Добавление к сценариям с сиротским контентом

Когда расширение будет перезагружено, можно ожидать, что Chrome очистит все скрипты содержимого. Но, по-видимому, это не так; слушатели контент-скриптов не отключены. Тем не менее, любая передача сообщений с родительским расширением не будет выполнена. Это, вероятно, следует считать ошибкой и в какой-то момент можно исправить. Я собираюсь назвать это состояние "осиротевшим"

Это не проблема ни в одном из двух случаев:

  • Содержимое script не имеет прослушивателей для событий на странице (например, выполняется только один раз или только прослушивает сообщения из фона)
  • Содержимое script ничего не делает со страницей и только сообщения о событиях.

Однако, если это не так, у вас возникла проблема: контент script может что-то делать, но не работает или не вмешивается в другой, сам по себе несигналированный экземпляр.

Решение этого будет:

  • Следите за всеми прослушивателями событий, которые могут запускаться на странице
  • Прежде чем действовать на эти события, отправьте сообщение "heartbeat" на задний план. 3a. Если фон отвечает, мы хороши и должны выполнить действие. 3b. Если передача сообщения завершается с ошибкой, мы становимся сиротами и должны воздерживаться; игнорировать событие и отменять регистрацию всех слушателей.

Код, содержимое 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);

Фон script:

chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
  if(request.heartbeat) { sendResponse(request); 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);
      });
    });
  }
});

Мой манифест .json

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

Мой "content_sciprt.js" работал после "background.js". поэтому я не могу получить ответ.

Но после того, как я добавил

  • info.status=="complete", status: "complete"
  • "run_at": "document_end" в моем manifest.json

Он отлично работает