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

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

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

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

Отправка сообщения не работает с первой части, фон -> content_script. Что-то не так с моим кодом?

2 ответа

Решение

Ваш сценарий не работает из-за того, как вводятся сценарии содержимого.

проблема

Когда вы (повторно) загружаете свое расширение, вопреки ожиданиям некоторых пользователей, 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
    });
  }
}); 

Я также подозреваю, что вы хотите, чтобы он выполнялся при некоторых действиях, а не при загрузке расширений. Например, вы можете использовать действие браузера и обернуть ваш код в 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; }
  /* ... */
});    

В моем 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". поэтому я не могу получить ответ.

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

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

Работает нормально

Другие вопросы по тегам