Messaggio superato

Poiché gli script di contenuti vengono eseguiti nel contesto di una pagina web e non dell'estensione, spesso hanno bisogno di un qualche modo per comunicare con il resto dell'estensione. Ad esempio, un'estensione per lettore RSS potrebbe usare script di contenuti per rilevare la presenza di un feed RSS su una pagina, quindi inviare una notifica alla pagina in background in modo da mostrare un'icona di azione sulla pagina per quella pagina.

La comunicazione tra le estensioni e i relativi script di contenuti funziona mediante la trasmissione dei messaggi. Entrambe le parti possono ascoltare i messaggi inviati dall'altro capo e rispondere sullo stesso canale. Un messaggio può contenere qualsiasi oggetto JSON valido (nullo, booleano, numero, stringa, array o oggetto). Esiste un'API semplice per le richieste una tantum e un'API più complessa che consente di avere connessioni di lunga durata per lo scambio di più messaggi con un contesto condiviso. Puoi anche inviare un messaggio a un'altra estensione se ne conosci l'ID, illustrato nella sezione Messaggi con più estensioni.

Richieste semplici una tantum

Se devi solo inviare un singolo messaggio a un'altra parte dell'estensione (e, facoltativamente, ricevere una risposta), devi utilizzare i metodi semplificati runtime.sendMessage o tabs.sendMessage . Questo ti consente di inviare un messaggio una tantum serializzabile in formato JSON da uno script di contenuti a un'estensione o viceversa, rispettivamente . Un parametro facoltativo di callback consente di gestire la risposta dall'altro lato, se disponibile.

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, con la differenza 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);
  });
});

Sul lato ricevente, devi configurare un listener di eventi runtime.onMessage per gestire il messaggio. Questo aspetto è simile a quello di uno script di contenuti o di una pagina 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.

Nota:se più pagine stanno ascoltando gli eventi onMessage, solo la prima che chiama sendResponse() per un determinato evento riesce a inviare la risposta. Tutte le altre risposte a quell'evento verranno ignorate.
Nota: il callback sendResponse è valido solo se utilizzato in modo sincrono oppure se il gestore di eventi restituisce true per indicare che risponderà in modo asincrono. Il callback della funzione sendMessage viene richiamato automaticamente se nessun gestore restituisce true o se il callback sendResponse viene eliminato in modo garbage.

Connessioni di lunga durata

A volte è utile avere una conversazione che dura più a lungo di una singola richiesta e risposta. In questo caso, puoi aprire un canale di lunga durata dallo script dei contenuti alla pagina di un'estensione o viceversa utilizzando rispettivamente runtime.connect o tabs.connect . Il canale può avere facoltativamente un nome che ti consente di distinguere tra diversi tipi di connessione.

Un caso d'uso potrebbe essere un'estensione per la compilazione automatica dei moduli. Lo script dei contenuti potrebbe aprire un canale alla pagina dell'estensione per un determinato accesso e inviare un messaggio all'estensione per ciascun elemento di input nella pagina per richiedere la compilazione dei dati del modulo. La connessione condivisa consente all'estensione di mantenere lo stato condiviso che collega diversi messaggi provenienti dallo script dei contenuti.

Quando viene stabilita una connessione, a ogni estremità viene assegnato un oggetto runtime.Port che viene utilizzato per inviare e ricevere messaggi attraverso quella connessione.

Ecco come aprire un canale da uno script di contenuti e 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, con la differenza che devi specificare la scheda a cui connetterti. Basta sostituire la chiamata per connetterti nell'esempio sopra con tabs.connect.

Per gestire le connessioni in entrata, devi configurare un listener di eventi runtime.onConnect. Questo aspetto è simile a quello di uno script di contenuti o di una pagina di estensione. Quando un'altra parte della tua estensione chiama "connect()", questo evento viene attivato insieme all'oggetto runtime.Port che puoi utilizzare per inviare e ricevere messaggi tramite la connessione. Ecco come si comporta la risposta 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 diverse parti dell'estensione, in cui un frame (di primo livello) viene visto come la parte più piccola. Quando chiami tabs.connect, runtime.connect o runtime.connectNative, viene creata una porta. Questa porta può essere utilizzata immediatamente per l'invio di messaggi all'altra parte tramite postMessage.

Se in una scheda sono presenti più frame, la chiamata a tabs.connect comporta più chiamate dell'evento runtime.onConnect (una volta per ogni frame nella scheda). Allo stesso modo, se viene utilizzato runtime.connect, l'evento onConnect può essere attivato più volte (una volta per ogni frame nel processo di estensione).

Potresti voler sapere quando una connessione è chiusa, ad esempio se stai mantenendo uno stato separato per ogni porta aperta. Per farlo, puoi rimanere in ascolto dell'evento runtime.Port.onDisconnect. Questo evento viene attivato quando non ci sono porte valide sull'altro lato del canale. Ciò si verifica nelle seguenti situazioni:

  • All'altra estremità non sono presenti listener per runtime.onConnect.
  • La scheda contenente la porta è scaricata (ad esempio se si naviga nella scheda).
  • È stato eseguito l'unload del frame da cui è stata chiamata l'app connect.
  • È stato eseguito l'unload di tutti i frame che hanno ricevuto la porta (tramite runtime.onConnect).
  • runtime.Port.disconnect viene chiamata dall'altra estremità. Tieni presente che se una chiamata connect genera più porte sul lato del destinatario e la chiamata disconnect() viene richiamata su una di queste porte, l'evento onDisconnect viene attivato solo sulla porta del mittente e 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. In questo modo puoi esporre un'API pubblica che può essere utilizzata da altre estensioni.

L'ascolto delle richieste e delle connessioni in arrivo è simile a quello delle richieste interne, ad eccezione del fatto che utilizzi i metodi runtime.onMessageExternal o runtime.onConnectExternal. Ecco un esempio di ognuna:

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

Analogamente, 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 alla messaggistica in più estensioni, la tua app o estensione può ricevere e rispondere ai messaggi provenienti da normali pagine web. Per utilizzare questa funzionalità, devi innanzitutto specificare nel file manifest.json con quali siti web vuoi comunicare. Ad esempio:

"externally_connectable": {
  "matches": ["*://*.example.com/*"]
}

In questo modo, l'API di messaggistica verrà esposta a qualsiasi pagina corrispondente ai pattern URL che hai specificato. Il pattern URL deve contenere almeno un dominio di secondo livello, vale a dire che i pattern di nomi host come "*", "*.com", "*.co.uk" e "*.appspot.com" sono vietati. Dalla pagina web, utilizza le API runtime.sendMessage o runtime.connect per inviare un messaggio a un'app o 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 ascoltare i messaggi delle pagine web tramite le API runtime.onMessageExternal o runtime.onConnectExternal, come nel caso del messaggio con cross-extension. 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);
  });

Messaggistica nativa

Le estensioni e le app possono scambiare messaggi con applicazioni native registrate come host di messaggistica nativi. Per scoprire di più su questa funzionalità, consulta la sezione Messaggi nativi.

Considerazioni sulla sicurezza

Gli script dei contenuti sono meno affidabili

Gli script di contenuti sono meno affidabili della pagina in background dell'estensione (ad esempio, una pagina web dannosa potrebbe compromettere il processo di rendering in cui vengono eseguiti 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 cancellare 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)

Quando ricevi un messaggio da uno script di contenuti o da un'altra estensione, fai attenzione a non subire attacchi di cross-site scripting. Questo consiglio si applica agli script eseguiti all'interno della pagina in background dell'estensione e agli script di contenuti in esecuzione all'interno di altre origini web. In particolare, evita di utilizzare API pericolose come quelle elencate 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;
});

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 messaggistica nativa mostra come un'app di Chrome può comunicare con un'app nativa. Per ulteriori esempi e per assistenza per la visualizzazione del codice sorgente, vedi Esempi.