Poiché gli script di contenuti vengono eseguiti nel contesto di una pagina web e non dell'estensione, spesso hanno bisogno di un modo per comunicare con il resto dell'estensione. Ad esempio, un'estensione per la lettura di feed RSS potrebbe utilizzare script di contenuti per rilevare la presenza di un feed RSS su una pagina, quindi notificare la pagina di sfondo per visualizzare un'icona di azione della pagina per quella pagina.
La comunicazione tra le estensioni e i relativi script di contenuti avviene tramite il passaggio di messaggi. Entrambe le parti possono rimanere in ascolto dei messaggi inviati dall'altra parte e rispondere sullo stesso canale. Un messaggio può contenere qualsiasi oggetto JSON valido (null, booleano, numero, stringa, array o oggetto). Esiste una semplice API per richieste una tantum e un'API più complessa che consente di avere connessioni a lunga durata per lo scambio di più messaggi con un contesto condiviso. È anche possibile inviare un messaggio a un'altra estensione se ne conosci l'ID, argomento trattato nella sezione Messaggi tra estensioni.
Richieste una tantum semplici
Se devi inviare un solo messaggio a un'altra parte dell'estensione (e, facoltativamente, ricevere una risposta), devi utilizzare runtime.sendMessage o tabs.sendMessage semplificati . In questo modo, puoi inviare un messaggio serializzabile JSON una tantum da uno script di contenuti all'estensione o viceversa. Un parametro di callback facoltativo consente di gestire la risposta dell'altra parte, se presente.
L'invio di una richiesta da uno script di contenuti ha il seguente aspetto:
chrome.runtime.sendMessage({greeting: "hello"}, function(response) {
console.log(response.farewell);
});
L'invio di una richiesta dall'estensione a uno script di contenuti è molto simile, tranne per il fatto che devi specificare la scheda a cui inviarla. Questo esempio mostra l'invio di un messaggio allo script di contenuti nella scheda selezionata.
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {greeting: "hello"}, function(response) {
console.log(response.farewell);
});
});
Dalla parte ricevente, devi configurare un listener di eventi runtime.onMessage per gestire il messaggio. L'aspetto è lo stesso da una pagina di script di contenuti o di estensione.
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log(sender.tab ?
"from a content script:" + sender.tab.url :
"from the extension");
if (request.greeting == "hello")
sendResponse({farewell: "goodbye"});
}
);
Nell'esempio precedente, sendResponse è stato chiamato in modo sincrono. Se vuoi utilizzare sendResponse in modo asincrono, aggiungi return true; al gestore di eventi onMessage.
sendResponse è valido solo se utilizzato in modo sincrono o se il gestore di eventi restituisce true per indicare che risponderà in modo asincrono. Il callback della funzione sendMessage verrà richiamato automaticamente se nessun gestore restituisce true o se il callback sendResponse viene sottoposto a garbage collection.Connessioni a lunga durata
A volte è utile avere una conversazione che duri più di una singola richiesta e risposta. In questo caso, puoi aprire un canale a lunga durata dallo script di contenuti a una pagina di estensione o viceversa, utilizzando runtime.connect o tabs.connect, rispettivamente . Il canale può avere un nome facoltativo, che consente di distinguere tra diversi tipi di connessioni.
Un caso d'uso potrebbe essere un'estensione di compilazione automatica dei moduli. Lo script di contenuti potrebbe aprire un canale alla pagina di estensione per un determinato accesso e inviare un messaggio all'estensione per ogni elemento di input della pagina per richiedere i dati del modulo da compilare. La connessione condivisa consente all'estensione di mantenere uno stato condiviso che collega i diversi messaggi provenienti dallo script di contenuti.
Quando viene stabilita una connessione, a ogni estremità viene assegnato un oggetto runtime.Port utilizzato per inviare e ricevere messaggi tramite la connessione.
Ecco come aprire un canale da uno script di contenuti, inviare e ascoltare i messaggi:
var port = chrome.runtime.connect({name: "knockknock"});
port.postMessage({joke: "Knock knock"});
port.onMessage.addListener(function(msg) {
if (msg.question == "Who's there?")
port.postMessage({answer: "Madame"});
else if (msg.question == "Madame who?")
port.postMessage({answer: "Madame... Bovary"});
});
L'invio di una richiesta dall'estensione a uno script di contenuti è molto simile, tranne per il fatto che devi specificare la scheda a cui connetterti. Basta sostituire la chiamata a connect nell'esempio precedente con tabs.connect.
Per gestire le connessioni in entrata, devi configurare un listener di eventi runtime.onConnect. L'aspetto è lo stesso da una pagina di script di contenuti o di estensione. Quando un'altra parte della tua estensione chiama "connect()", viene attivato questo evento, insieme all'oggetto runtime.Port che puoi utilizzare per inviare e ricevere messaggi tramite la connessione. Ecco come rispondere alle connessioni in entrata:
chrome.runtime.onConnect.addListener(function(port) {
console.assert(port.name == "knockknock");
port.onMessage.addListener(function(msg) {
if (msg.joke == "Knock knock")
port.postMessage({question: "Who's there?"});
else if (msg.answer == "Madame")
port.postMessage({question: "Madame who?"});
else if (msg.answer == "Madame... Bovary")
port.postMessage({question: "I don't get it."});
});
});
Durata della porta
Le porte sono progettate come metodo di comunicazione bidirezionale tra le diverse parti dell'estensione, in cui un frame (di primo livello) è considerato la parte più piccola. Quando chiami tabs.connect, runtime.connect o runtime.connectNative, viene creata una Port. Questa porta può essere utilizzata immediatamente per inviare messaggi all'altra estremità tramite postMessage.
Se in una scheda sono presenti più frame, la chiamata a tabs.connect comporta più chiamate all'evento runtime.onConnect (una per ogni frame nella scheda). Allo stesso modo, se runtime.connect viene utilizzato, l'evento onConnect potrebbe essere attivato più volte (una per ogni frame nel processo di estensione).
Potresti voler sapere quando una connessione viene chiusa, ad esempio se mantieni uno stato separato per ogni porta aperta. Per questo, puoi rimanere in ascolto dell'evento runtime.Port.onDisconnect. Questo evento viene attivato quando non sono presenti porte valide dall'altra parte del canale. Ciò si verifica nelle seguenti situazioni:
- Non sono presenti listener per runtime.onConnect dall'altra parte.
- La scheda contenente la porta viene scaricata (ad es. se la scheda viene spostata).
- Il frame da cui è stata chiamata
connectè stato scaricato. - Tutti i frame che hanno ricevuto la porta (tramite runtime.onConnect) sono stati scaricati.
- runtime.Port.disconnect viene chiamato da l'altra parte. Tieni presente che se una chiamata
connectgenera più porte all'estremità del destinatario e viene chiamatadisconnect()su una di queste porte, allora l'eventoonDisconnectviene attivato solo sulla porta del mittente e non sulle altre porte.
Messaggi tra estensioni
Oltre a inviare messaggi tra i diversi componenti dell'estensione, puoi utilizzare l'API Messaging per comunicare con altre estensioni. In questo modo, puoi esporre un'API pubblica di cui altre estensioni possono usufruire.
L'ascolto delle richieste e delle connessioni in entrata è simile al caso interno, tranne per il fatto che utilizzi i runtime.onMessageExternal o runtime.onConnectExternal. Ecco un esempio di ciascuno:
// For simple requests:
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (sender.id == blocklistedExtension)
return; // don't allow this extension access
else if (request.getTargetData)
sendResponse({targetData: targetData});
else if (request.activateLasers) {
var success = activateLasers();
sendResponse({activateLasers: success});
}
});
// For long-lived connections:
chrome.runtime.onConnectExternal.addListener(function(port) {
port.onMessage.addListener(function(msg) {
// See other examples for sample onMessage handlers.
});
});
Allo stesso modo, l'invio di un messaggio a un'altra estensione è simile all'invio di un messaggio all'interno dell'estensione. L'unica differenza è che devi passare l'ID dell'estensione con cui vuoi comunicare. Ad esempio:
// The ID of the extension we want to talk to.
var laserExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";
// Make a simple request:
chrome.runtime.sendMessage(laserExtensionId, {getTargetData: true},
function(response) {
if (targetInRange(response.targetData))
chrome.runtime.sendMessage(laserExtensionId, {activateLasers: true});
}
);
// Start a long-running conversation:
var port = chrome.runtime.connect(laserExtensionId);
port.postMessage(...);
Invio di messaggi da pagine web
Analogamente ai messaggi tra estensioni, l'app o l'estensione può ricevere e rispondere ai messaggi provenienti da pagine web normali. Per utilizzare questa funzionalità, devi prima specificare in manifest.json i siti web con cui vuoi comunicare. Ad esempio:
"externally_connectable": {
"matches": ["*://*.example.com/*"]
}
In questo modo, l'API Messaging verrà esposta a qualsiasi pagina che corrisponda ai pattern URL specificati. Il pattern URL deve contenere almeno un dominio di secondo livello, ovvero i pattern hostname come "*", "*.com", "*.co.uk" e "*.appspot.com" sono vietati. Dalla pagina web, utilizza le runtime.sendMessage o runtime.connect per inviare un messaggio a un'app o un'estensione specifica. Ad esempio:
// The ID of the extension we want to talk to.
var editorExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";
// Make a simple request:
chrome.runtime.sendMessage(editorExtensionId, {openUrlInEditor: url},
function(response) {
if (!response.success)
handleError(url);
});
Dall'app o dall'estensione, puoi rimanere in ascolto dei messaggi provenienti dalle pagine web tramite le runtime.onMessageExternal o runtime.onConnectExternal, in modo simile ai messaggi tra estensioni. Solo la pagina web può avviare una connessione. Ecco un esempio:
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (sender.url == blocklistedWebsite)
return; // don't allow this web page access
if (request.openUrlInEditor)
openUrl(request.openUrlInEditor);
});
Messaggi nativi
Le estensioni e le app possono scambiare messaggi con le applicazioni native registrate come host di messaggistica nativa. Per saperne di più su questa funzionalità, consulta Messaggi nativi.
Considerazioni sulla sicurezza
Gli script di contenuti sono meno affidabili
Gli script di contenuti sono meno affidabili della pagina di sfondo dell'estensione (ad es. una pagina web dannosa potrebbe essere in grado di compromettere il processo di rendering in cui vengono eseguiti gli script di contenuti). Supponi che i messaggi provenienti da uno script di contenuti possano essere stati creati da un utente malintenzionato e assicurati di convalidare e sanificare tutti gli input. Supponi che tutti i dati inviati allo script di contenuti possano essere divulgati alla pagina web. Limita l'ambito delle azioni privilegiate che possono essere attivate dai messaggi ricevuti dagli script di contenuti.
Cross-site scripting (XSS)
Quando ricevi un messaggio da uno script di contenuti o da un'altra estensione, gli script devono fare attenzione a non essere vittime di cross-site scripting. Questo consiglio si applica agli script eseguiti all'interno della pagina di sfondo dell'estensione, nonché agli script di contenuti eseguiti all'interno di altre origini web. In particolare, evita di utilizzare API pericolose come quelle riportate di seguito:
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
// WARNING! Might be evaluating an evil script!
var resp = eval("(" + response.farewell + ")");
});
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
// WARNING! Might be injecting a malicious script!
document.getElementById("resp").innerHTML = response.farewell;
});
In alternativa, preferisci API più sicure che non eseguono script:
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
// JSON.parse does not evaluate the attacker's scripts.
var resp = JSON.parse(response.farewell);
});
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
// innerText does not let the attacker inject HTML elements.
document.getElementById("resp").innerText = response.farewell;
});
Esempi
Puoi trovare semplici esempi di comunicazione tramite messaggi nella directory examples/api/messaging. L'esempio di messaggi nativi mostra come un'app di Chrome può comunicare con un' app nativa. Per altri esempi e per assistenza nella visualizzazione del codice sorgente, consulta Esempi.