Poiché gli script di contenuti vengono eseguiti nel contesto di una pagina web e non nell'estensione che li esegue, spesso hanno bisogno di modi per comunicare con il resto dell'estensione. Ad esempio, un'estensione del visualizzatore di RSS potrebbe utilizzare script di contenuti per rilevare la presenza di un feed RSS in una pagina, quindi informare il servizio worker di visualizzare un'icona di azione per quella pagina.
Questa comunicazione utilizza il passaggio di messaggi, che consente sia alle estensioni sia agli script di contenuti di ascoltare i messaggi l'uno dell'altro e di rispondere sullo stesso canale. Un messaggio può contenere qualsiasi oggetto JSON valido (null, booleano, numero, stringa, array o oggetto). Esistono due API di passaggio dei messaggi: una per le richieste una tantum e una più complessa per le connessioni di lunga durata che consentono l'invio di più messaggi. Per informazioni sull'invio di messaggi tra estensioni, consulta la sezione Messaggi tra estensioni.
Richieste una tantum
Per inviare un singolo messaggio a un'altra parte dell'estensione e, facoltativamente, ricevere una risposta, chiama runtime.sendMessage()
o tabs.sendMessage()
.
Questi metodi ti consentono di inviare un messaggio una tantum serializzabile in JSON da uno script di contenuti all'estensione o dall'estensione a uno script di contenuti. Per gestire la risposta, utilizza la promessa restituita. Per la compatibilità con le versioni precedenti con le estensioni precedenti, puoi passare un callback come ultimo argomento. Non puoi utilizzare una promessa e un callback nella stessa chiamata.
Per informazioni su come convertire i callback in promesse e su come utilizzarli nelle estensioni, consulta la guida alla migrazione di Manifest V3.
L'invio di una richiesta da uno script dei contenuti ha il seguente aspetto:
content-script.js:
(async () => {
const response = await chrome.runtime.sendMessage({greeting: "hello"});
// do something with response here, not outside the function
console.log(response);
})();
Se vuoi rispondere in modo sincrono a un messaggio, chiama sendResponse
una volta che hai la risposta e restituisci false
per indicare che hai finito. Per rispondere in modo asincrono, restituisci true
per mantenere attivo il callback sendResponse
finché non è tutto pronto per l'utilizzo. Le funzioni asincrone non sono supportate perché restituiscono una promessa, che non è supportata.
Per inviare una richiesta a uno script di contenuti, specifica la scheda a cui si applica la richiesta come mostrato di seguito. Questo esempio funziona in service worker, popup e pagine chrome-extension:// aperte come scheda.
(async () => {
const [tab] = await chrome.tabs.query({active: true, lastFocusedWindow: true});
const response = await chrome.tabs.sendMessage(tab.id, {greeting: "hello"});
// do something with response here, not outside the function
console.log(response);
})();
Per ricevere il messaggio, configura un listener di eventi runtime.onMessage
. Queste
utilizzano lo stesso codice nelle estensioni e negli script di contenuti:
content-script.js o service-worker.js:
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. Per utilizzare sendResponse()
in modo asincrono, aggiungi return true;
al gestore di eventi onMessage
.
Se più pagine sono in ascolto per gli eventi onMessage
, solo la prima che chiama sendResponse()
per un determinato evento riuscirà a inviare la risposta. Tutte le altre risposte a quell'evento verranno ignorate.
Connessioni di lunga durata
Per creare un canale di trasmissione di messaggi riutilizzabile e di lunga durata, chiama runtime.connect()
per trasmettere messaggi da uno script di contenuti a una pagina di estensione o tabs.connect()
per trasmettere messaggi da una pagina di estensione a uno script di contenuti. Puoi assegnare un nome al canale per distinguere i diversi tipi di collegamenti.
Un potenziale caso d'uso per una connessione a lungo termine è un'estensione di compilazione automatica dei moduli. Lo script dei contenuti potrebbe aprire un canale alla pagina dell'estensione per un accesso specifico e inviare un messaggio all'estensione per ogni elemento di input sulla pagina per richiedere i dati del modulo da compilare. La connessione condivisa consente all'estensione di condividere lo stato tra i componenti dell'estensione.
Quando viene stabilita una connessione, a ogni estremità viene assegnato un oggetto runtime.Port
per l'invio e la ricezione di messaggi tramite la connessione.
Utilizza il seguente codice per aprire un canale da uno script dei contenuti, inviare e ascoltare i messaggi:
content-script.js:
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"});
});
Per inviare una richiesta dall'estensione a uno script di contenuti, sostituisci la chiamata a runtime.connect()
nell'esempio precedente con tabs.connect()
.
Per gestire le connessioni in arrivo per uno script di contenuti o una pagina di estensione, configura un listener di eventi runtime.onConnect
. Quando un'altra parte della tua estensione chiama connect()
, attiva questo evento e l'oggetto runtime.Port
. Il codice per rispondere alle connessioni in arrivo è il seguente:
service-worker.js:
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. Un frame di primo livello è la parte più piccola di un'estensione che può utilizzare una porta.
Quando parte di un'estensione chiama tabs.connect()
, runtime.connect()
o runtime.connectNative()
, viene creata una porta che può inviare immediatamente messaggi utilizzando postMessage()
.
Se in una scheda sono presenti più frame, la chiamata a tabs.connect()
invoca
l'evento runtime.onConnect
una volta per ogni frame della scheda. Allo stesso modo, se viene chiamato runtime.connect()
, l'evento onConnect
può essere attivato una volta per ogni frame nel processo di estensione.
Potresti voler sapere quando una connessione è chiusa, ad esempio se gestisci stati distinti per ogni porta aperta. Per farlo, ascolta l'evento runtime.Port.onDisconnect
. Questo
evento viene attivato quando non sono presenti porte valide all'altra estremità del canale, il che può essere causato da una delle seguenti situazioni:
- Non ci sono ascoltatori per
runtime.onConnect
dall'altra parte. - La scheda contenente la porta viene scaricata (ad esempio, se viene navigata nella scheda).
- Il frame in cui è stato chiamato
connect()
è stato scaricato. - Tutti i frame che hanno ricevuto la porta (tramite
runtime.onConnect
) sono stati scaricati. runtime.Port.disconnect()
viene chiamato dall'altra parte. Se una chiamataconnect()
genera più porte sul lato del destinatario e viene chiamatadisconnect()
su una di queste porte, l'eventoonDisconnect
viene attivato solo sulla porta di invio, non sulle altre porte.
Messaggistica tra estensioni
Oltre a inviare messaggi tra componenti diversi dell'estensione, puoi utilizzare l'API di messaggistica per comunicare con altre estensioni. In questo modo puoi esporre un'API pubblica da utilizzare per altre estensioni.
Per ascoltare le richieste e le connessioni in arrivo da altre estensioni, utilizza i metodi
runtime.onMessageExternal
o runtime.onConnectExternal
. Ecco un esempio di ciascuno:
service-worker.js
// For a single request:
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.
});
});
Per inviare un messaggio a un'altra estensione, passa l'ID dell'estensione con cui vuoi comunicare come segue:
service-worker.js
// The ID of the extension we want to talk to.
var laserExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";
// For a simple request:
chrome.runtime.sendMessage(laserExtensionId, {getTargetData: true},
function(response) {
if (targetInRange(response.targetData))
chrome.runtime.sendMessage(laserExtensionId, {activateLasers: true});
}
);
// For a long-lived connection:
var port = chrome.runtime.connect(laserExtensionId);
port.postMessage(...);
Invia messaggi da pagine web
Le estensioni possono anche ricevere e rispondere ai messaggi provenienti da altre pagine web, ma non possono inviare messaggi alle pagine web. Per inviare messaggi da una pagina web a un'estensione,
specifica in manifest.json
i siti web con cui vuoi comunicare utilizzando
la chiave manifest "externally_connectable"
. Ad esempio:
manifest.json
"externally_connectable": {
"matches": ["https://*.example.com/*"]
}
In questo modo, l'API di messaggistica viene esposta a qualsiasi pagina che corrisponda ai pattern URL specificati. Il pattern URL deve contenere almeno un dominio di secondo livello; ovvero i pattern dei nomi host come "*", "*.com", "*.co.uk" e "*.appspot.com" non sono supportati. A partire da Chrome 107, puoi utilizzare
<all_urls>
per accedere a tutti i domini. Tieni presente che, poiché interessa tutti gli host,
le revisioni del Chrome Web Store per le estensioni che la utilizzano potrebbero richiedere più tempo.
Utilizza le API runtime.sendMessage()
o runtime.connect()
per inviare un messaggio a un'app o un'estensione specifica. Ad esempio:
webpage.js
// 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);
});
Dalla tua estensione, ascolta i messaggi dalle pagine web utilizzando le API runtime.onMessageExternal
o runtime.onConnectExternal
come nella messaggistica con estensioni incrociate. Ecco un esempio:
service-worker.js
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);
});
Messaggistica nativa
Le estensioni possono scambiare messaggi con applicazioni native registrate come host di messaggistica nativi. Per saperne di più su questa funzionalità, consulta Messaggistica nativa.
Considerazioni sulla sicurezza
Di seguito sono riportate alcune considerazioni sulla sicurezza relative ai messaggi.
I testi dei contenuti sono meno affidabili
Gli script di contenuti sono meno attendibili del service worker dell'estensione. Ad esempio, una pagina web dannosa potrebbe essere in grado di compromettere il processo di rendering che esegue gli script dei contenuti. Dai per scontato 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. Supponiamo che tutti i dati inviati allo script dei contenuti potrebbero essere divulgati nella pagina web. Limita l'ambito delle azioni privilegiate che possono essere attivate dai messaggi ricevuti dagli script dei contenuti.
Cross-site scripting (XSS)
Assicurati di proteggere i tuoi script dal cross-site scripting. Quando ricevi dati da una fonte non attendibile, come input utente, altri siti web tramite uno script di contenuti o un'API, evita di interpretarlo come HTML o di utilizzarlo in un modo che potrebbe consentire l'esecuzione di codice imprevisto.
Se possibile, utilizza API che non eseguono script:
service-worker.js
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) { // JSON.parse doesn't evaluate the attacker's scripts. var resp = JSON.parse(response.farewell); });
service-worker.js
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) { // innerText does not let the attacker inject HTML elements. document.getElementById("resp").innerText = response.farewell; });
Evita di utilizzare i seguenti metodi che rendono vulnerabile la tua estensione:
service-worker.js
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) { // WARNING! Might be evaluating a malicious script! var resp = eval(`(${response.farewell})`); });
service-worker.js
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) { // WARNING! Might be injecting a malicious script! document.getElementById("resp").innerHTML = response.farewell; });