Envio de mensagem de um script de segundo plano para um script de conteúdo e, em seguida, para um script injetado

Estou tentando enviar mensagens da página de plano de fundo para um script de conteúdo e, em seguida, enviar uma mensagem desse script de conteúdo para um script injetado. Eu tentei isso, mas não está funcionando.

Aqui está o meu código se parece.

manifest.json

{ "manifest_version": 2, "name": "NAME", "description": ":D", "version": "0.0", "permissions": [ "tabs","" ], "content_scripts": [ { "matches": [""], "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); }); 

O envio da mensagem não funciona na primeira parte, background -> content_script. Há algo de errado com o meu código?

Seu script não funciona devido a como os scripts de conteúdo são injetados.

Problema

Quando você (re) carrega sua extensão, ao contrário do que algumas pessoas esperam, o Google Chrome não injetará scripts de conteúdo em guias existentes que correspondam aos padrões do manifesto. Somente após o carregamento da extensão, qualquer navegação verificará a URL para correspondência e injetará o código.

Então, a linha do tempo:

  1. Você abre algumas abas. Nenhum script de conteúdo existe 1 .
  2. Você carrega sua extensão. Seu código de nível superior é executado: ele tenta passar uma mensagem para a guia atual.
  3. Como não pode haver ouvinte ainda, ele falha. (Que é provavelmente o chrome://extensions/ page e você não pode injetar lá de qualquer maneira)
  4. Se, depois, você tentar navegar / abrir uma nova guia, o ouvinte será injetado, mas o código de nível superior não será mais executado.

1 – Isso também acontece se você recarregar sua extensão. Se houver um script de conteúdo injetado, ele continuará a manipular seus events / não será descarregado, mas não poderá mais se comunicar com a extensão. (para detalhes, ver adendo no final)

Soluções

Solução 1: você pode primeiro perguntar à guia que está enviando uma mensagem se está pronta e, após o silêncio, injetar o script programaticamente. Considerar:

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

e

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

Solução 2: sempre injetar um script, mas certifique-se de que ele seja executado apenas uma vez.

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

e

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

Isso é mais simples, mas tem complicações na recarga de extensões. Depois que uma extensão é recarregada, o script antigo ainda está lá, mas não é mais o contexto “seu” – então, o injected será indefinido. Cuidado com os efeitos colaterais de potencialmente executar seu script duas vezes.


Solução 3: injete indiscriminadamente seu (s) script (s) de conteúdo na boot . Isso só é seguro se for seguro executar o mesmo script de conteúdo duas vezes ou executá-lo depois que a página estiver totalmente carregada.

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

Eu também suspeito que você quer que ele seja executado em alguma ação, e não na carga de extensão. Por exemplo, você pode empregar uma Ação do navegador e envolver seu código em um ouvinte chrome.browserAction.onClicked .


Adendo em scripts de conteúdo órfão

Quando uma extensão é recarregada, espera-se que o Chrome limpe todos os scripts de conteúdo. Mas aparentemente este não é o caso; os ouvintes dos scripts de conteúdo não estão desabilitados. No entanto, qualquer mensagem com extensão pai falhará. Isso provavelmente deve ser considerado um bug e pode, em algum momento, ser corrigido. Eu vou chamar esse estado de “órfão”

Isso não é um problema em nenhum dos dois casos:

  1. O script de conteúdo não tem ouvintes de events na página (por exemplo, executa apenas uma vez ou apenas escuta mensagens de segundo plano)
  2. O script de conteúdo não faz nada com a página e apenas envia mensagens sobre os events.

No entanto, se esse não for o caso, você tem um problema: o script de conteúdo pode estar fazendo alguma coisa, mas falhando ou interferindo em outra instância não órfã de si mesmo.

Uma solução para isso seria:

  1. Acompanhe todos os ouvintes de events que podem ser acionados pela página
  2. Antes de agir nesses events, envie uma mensagem “heartbeat” para o segundo plano. 3a. Se o plano de fundo responder, estamos bem e devemos executar a ação. 3b. Se a passagem da mensagem falhar, seremos órfãos e desistiremos; ignore o evento e cancele todos os ouvintes.

Código, script de conteúdo:

 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 de fundo:

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

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

Meu manifest.json

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

Meu “content_sciprt.js” funcionou depois de “background.js”. então não posso receber a resposta.

Mas depois eu adicionei

  1. info.status=="complete" , status: "complete"
  2. "run_at": "document_end" no meu manifest.json

Funciona bem