Invio di messaggi da uno script in background a uno script di contenuto, quindi a uno script immesso

Sto cercando di inviare messaggi dalla pagina di sfondo a uno script di contenuto, quindi di inviare un messaggio da quello script di contenuto a uno script inserito. Ho provato questo, ma non funziona.

Ecco come appare il mio codice.

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

L’invio del messaggio non funziona dalla prima parte, background -> content_script. C’è qualcosa di sbagliato nel mio codice?

Lo script non funziona a causa dell’iniezione di script di contenuto.

Problema

Quando carichi (ri) la tua estensione, contrariamente a quanto alcuni si aspettano, Chrome non inserirà gli script di contenuto nelle tabs esistenti che corrispondono ai modelli del manifest. Solo dopo aver caricato l’estensione, qualsiasi navigazione controllerà l’URL per la corrispondenza e inietterà il codice.

Quindi, la cronologia:

  1. Apri alcune tabs. Nessun script di contenuto lì 1 .
  2. Carichi la tua estensione. Il codice di primo livello viene eseguito: tenta di passare un messaggio alla scheda corrente.
  3. Dal momento che non ci può essere ancora nessun ascoltatore, fallisce. (Il che è probabilmente il chrome://extensions/ page e non è ansible inserirli comunque)
  4. Se, in seguito, provi a navigare / aprire una nuova scheda, il listener viene iniettato, ma il tuo codice di primo livello non viene più eseguito.

1 – Questo succede anche se ricarichi la tua estensione. Se è stato iniettato uno script di contenuto, continua a gestire i suoi eventi / non viene scaricato, ma non può più comunicare con l’estensione. (per i dettagli, vedere l’appendice alla fine)

soluzioni

Soluzione 1: è ansible prima chiedere al tab che si sta inviando un messaggio se è pronto e, al silenzio, iniettare lo script a livello di programmazione. Prendere in considerazione:

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

Soluzione 2: iniettare sempre uno script, ma assicurarsi che venga eseguito solo una volta.

 // 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 */ } 

Questo è più semplice, ma ha delle complicazioni sull’aggiunta della ricarica. Dopo che un’estensione è stata ricaricata, il vecchio script è ancora lì 1 ma non è più il “tuo” contesto – quindi l’ injected sarà indefinita. Fai attenzione agli effetti collaterali di potenzialmente eseguire lo script due volte.


Soluzione 3: iniettare indiscriminatamente gli script di contenuto durante l’inizializzazione . Questo è sicuro solo se è ansible eseguire lo stesso script di contenuto due volte o eseguirlo dopo che la pagina è stata caricata completamente.

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

Sospetto anche che tu voglia che venga eseguito su qualche azione, e non sul carico di estensione. Ad esempio, puoi utilizzare un’azione del browser e avvolgere il codice in un listener chrome.browserAction.onClicked .


Addendum su script di contenuti orfani

Quando un’estensione viene ricaricata, ci si aspetterebbe che Chrome pulisse tutti gli script di contenuto. Ma a quanto pare questo non è il caso; gli ascoltatori degli script di contenuto non sono disabilitati. Tuttavia, qualsiasi messaggio con l’estensione genore fallirà. Questo dovrebbe probabilmente essere considerato un bug e potrebbe a un certo punto essere risolto. Chiamerò questo stato “orfano”

Questo non è un problema in nessuno dei due casi:

  1. Lo script di contenuto non ha ascoltatori per gli eventi nella pagina (ad esempio esegue solo una volta o ascolta solo i messaggi dallo sfondo)
  2. Lo script di contenuto non fa nulla con la pagina e solo i messaggi sullo sfondo degli eventi.

Tuttavia, se questo non è il caso, hai un problema: lo script di contenuto potrebbe fare qualcosa ma fallire o interferire con un’altra istanza di se stesso non orfana.

Una soluzione a questo sarebbe:

  1. Tieni traccia di tutti i listener di eventi che possono essere triggersti ​​dalla pagina
  2. Prima di agire su quegli eventi, invia un messaggio “heartbeat” allo sfondo. 3a. Se lo sfondo risponde, siamo a posto e dovremmo eseguire l’azione. 3b. Se il passaggio del messaggio fallisce, siamo orfani e dovremmo desistere; ignora l’evento e cancella tutti gli ascoltatori.

Codice, contenuto dello 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 di sfondo:

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

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

Il mio manifest.json

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

Il mio “content_sciprt.js” ha funzionato dopo “background.js”. quindi non posso ricevere la risposta.

Ma dopo ho aggiunto

  1. info.status=="complete" , status: "complete"
  2. "run_at": "document_end" nel mio manifest.json

Funziona bene