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 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 delle estensioni, puoi invece passare un callback come ultimo argomento. Non puoi utilizzare una promessa e un callback nella stessa chiamata.

Quando invii un messaggio, al gestore di eventi che lo gestisce viene passato un terzo argomento facoltativo, sendResponse. Si tratta di una funzione che riceve un oggetto serializzabile in JSON che viene utilizzato come valore restituito alla funzione che ha inviato il messaggio. Per impostazione predefinita, il callback sendResponse deve essere chiamato in modo sincrono. Se vuoi eseguire un'operazione asincrona per ottenere il valore passato a sendResponse, devi restituire un valore letterale true (non solo un valore vero) dall'ascoltatore di eventi. In questo modo, il canale di messaggistica rimarrà aperto all'altra estremità finché non viene chiamato sendResponse.

// Event listener
function handleMessages(message, sender, sendResponse) {

  fetch(message.url)
    .then((response) => sendResponse({statusCode: response.status}))

  // Since `fetch` is asynchronous, must send an explicit `true`
  return true;
}

// Message sender
  const {statusCode} = await chrome.runtime.sendMessage({
    url: 'https://example.com'
  });

Per informazioni sulla conversione dei callback in promesse e sul loro utilizzo nelle estensioni, consulta la guida alla migrazione a 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. Questi 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 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 dei messaggi riutilizzabile e di lunga durata, chiama runtime.connect() per trasmettere i messaggi da uno script di contenuti a una pagina di estensione o tabs.connect() per trasmettere i 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 dell'estensione chiama connect(), attiva questo evento e l'oggetto runtime.Port. Il codice per rispondere alle connessioni in entrata è 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. Analogamente, se viene chiamato runtime.connect(), l'evento onConnect può essere attivato una volta per ogni frame del 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 sono presenti ascoltatori per runtime.onConnect dall'altra parte.
  • La scheda contenente la porta viene scaricata (ad esempio, se viene visualizzata la scheda).
  • Il frame in 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 dall'altra parte. Se una chiamata connect() genera più porte sul lato del destinatario e disconnect() viene chiamato su una di queste porte, l'evento onDisconnect viene attivato solo sulla porta di invio, non sulle altre porte.

Messaggistica tra estensioni

Oltre a inviare messaggi tra diversi componenti 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 minimal 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(...);

Inviare messaggi dalle 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 di nome 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 lo 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.
const editorExtensionId = 'abcdefghijklmnoabcdefhijklmnoabc';

// Check if extension is installed
if (chrome && chrome.runtime) {
  // Make a request:
  chrome.runtime.sendMessage(
    editorExtensionId,
    {
      openUrlInEditor: url
    },
    (response) => {
      if (!response.success) handleError(url);
    }
  );
}

Dall'estensione, ascolta i messaggi provenienti dalle pagine web utilizzando le API runtime.onMessageExternal o runtime.onConnectExternal come per i messaggi tra estensioni. 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 nativa. Per saperne di più su questa funzionalità, consulta Messaggistica nativa.

Considerazioni sulla sicurezza

Ecco alcune considerazioni sulla sicurezza relative alla messaggistica.

Gli script di 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. Supponiamo che i messaggi di uno script di contenuti possano essere stati creati da un 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 l'input utente, altri siti web tramite uno script di contenuti o un'API, fai attenzione a non interpretarli come HTML o a 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;
});