Messaggio superato

Poiché gli script di contenuti vengono eseguiti nel contesto di una pagina web, non dell'estensione che li esegue, spesso hanno bisogno di un modo per comunicare con il resto dell'estensione. Ad esempio, un'estensione RSS reader potrebbe utilizzare script di contenuti per rilevare la presenza di un feed RSS in una pagina, quindi chiedere al service worker di mostrare un'icona di azione per quella pagina.

Questa comunicazione utilizza la trasmissione dei messaggi, che consente sia alle estensioni sia agli script di contenuti di ascoltare i reciproci messaggi e di rispondere sullo stesso canale. Un messaggio può contenere qualsiasi oggetto JSON valido (nullo, booleano, numero, stringa, array o oggetto). Sono disponibili due API per la trasmissione 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 le estensioni, consulta la sezione Messaggi con più estensioni.

Richieste una tantum

Per inviare un singolo messaggio a un'altra parte dell'estensione e, facoltativamente, ricevere una risposta, chiama il numero runtime.sendMessage() o tabs.sendMessage(). Questi metodi consentono di inviare un messaggio una tantum utilizzabile in serie su JSON da uno script di contenuti all'estensione o dall'estensione a uno script di contenuti. Per gestire la risposta, usa la promessa restituita. Per la compatibilità con le versioni precedenti delle estensioni, puoi passare un callback come ultimo argomento. Non puoi utilizzare una promessa e un callback nella stessa chiamata.

Per informazioni sulla conversione dei callback in promesse e per il loro utilizzo 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);
})();

Per inviare una richiesta a uno script di contenuti, specifica a quale scheda si applica la richiesta, come illustrato di seguito. Questo esempio funziona nei service worker, nei popup e nelle 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. Utilizzano lo stesso codice sia nelle estensioni sia 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 degli eventi onMessage, solo la prima che chiama sendResponse() per un determinato evento potrà inviare la risposta. Tutte le altre risposte a quell'evento verranno ignorate.

Legami di lunga durata

Per creare un canale riutilizzabile per la trasmissione di messaggi di lunga durata, chiama runtime.connect() per trasmettere messaggi da uno script di contenuti a una pagina di estensione oppure tabs.connect() per trasmettere messaggi da una pagina di estensione a uno script di contenuti. Puoi assegnare un nome al canale per distinguere tra i diversi tipi di connessioni.

Un caso d'uso potenziale per una connessione di lunga durata è un'estensione per la compilazione automatica di 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 nella pagina per richiedere la compilazione dei dati del modulo. La connessione condivisa consente all'estensione di condividere lo stato tra i componenti dell'estensione.

Quando si stabilisce una connessione, a ciascuna estremità viene assegnato un oggetto runtime.Port per inviare e ricevere messaggi attraverso la connessione.

Utilizza il seguente codice per aprire un canale da uno script di contenuti e 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 entrata per uno script di contenuti o per la pagina di un'estensione, imposta un listener di eventi runtime.onConnect. Quando un'altra parte dell'estensione chiama connect(), attiva questo evento e l'oggetto runtime.Port. Il codice per rispondere alle connessioni in entrata è simile a questo:

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 un metodo di comunicazione bidirezionale tra parti diverse dell'estensione. Un frame di primo livello è la parte più piccola di un'estensione che può utilizzare una porta. Quando un'estensione chiama tabs.connect(), runtime.connect() o runtime.connectNative(), crea una Porta che può inviare immediatamente messaggi utilizzando postMessage().

Se una scheda contiene più frame, la chiamata a tabs.connect() richiama l'evento runtime.onConnect una volta per ogni frame nella scheda. Allo stesso modo, se viene chiamato runtime.connect(), l'evento onConnect può attivarsi una volta per ogni frame nel processo di estensione.

Potresti voler sapere quando una connessione è chiusa, ad esempio se mantieni stati separati per ogni porta aperta. Per farlo, ascolta l'evento runtime.Port.onDisconnect. Questo evento si attiva quando non esistono porte valide all'altra estremità del canale, il che può avere una delle seguenti cause:

  • Nessun listener per runtime.onConnect dall'altra parte.
  • La scheda contenente la porta viene scaricata (ad esempio, se si naviga nella scheda).
  • È stato eseguito l'unload del frame in cui è stato chiamato connect().
  • Tutti i frame che hanno ricevuto la porta (tramite runtime.onConnect) sono stati scaricati.
  • La chiamata a runtime.Port.disconnect() viene effettuata dall'altro capo. Se una chiamata connect() genera più porte all'estremità del ricevitore e la chiamata disconnect() viene chiamata su una di queste porte, l'evento onDisconnect viene attivato solo sulla porta di invio, non su altre porte.

Messaggistica su più estensioni

Oltre a inviare messaggi tra diversi componenti dell'estensione, puoi utilizzare l'API di messaggistica per comunicare con altre estensioni. Ciò consente di esporre un'API pubblica che può essere utilizzata da altre estensioni.

Per ascoltare le richieste in arrivo e le connessioni da altre estensioni, utilizza i metodi runtime.onMessageExternal o runtime.onConnectExternal. Ecco un esempio di ognuno:

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, trasmetti 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 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 Messaging viene esposto a qualsiasi pagina corrispondente 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 dalla versione 107 di Chrome, 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 potrebbe 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);
  });

Dall'estensione, ascolta i messaggi delle pagine web utilizzando le API runtime.onMessageExternal o runtime.onConnectExternal come nei messaggi su più estensioni. 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 l'argomento Messaggistica nativa.

Considerazioni sulla sicurezza

Di seguito sono riportate alcune considerazioni sulla sicurezza dei messaggi.

I copioni dei contenuti sono meno affidabili

Gli script di contenuti sono meno affidabili 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 di contenuti. Supponiamo che i messaggi di uno script di contenuti siano stati creati da un utente malintenzionato e assicurati di convalidare e sanitizzare tutti gli input. Supponiamo che i dati inviati allo script dei contenuti possano essere trasmessi alla pagina web. Limita l'ambito delle azioni con privilegi che possono essere attivate dai messaggi ricevuti dagli script di contenuti.

Cross-site scripting (XSS)

Proteggi i tuoi script da cross-site scripting. Quando ricevi dati da una fonte non attendibile, come l'input di un utente o altri siti web, tramite uno script di contenuti o un'API, evita di interpretarli come HTML o di utilizzarli in un modo che potrebbe consentire l'esecuzione di codice imprevisto.

Metodi più sicuri

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;
});
Metodi non sicuri

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