Nachricht wurde weitergegeben

Da Inhaltsskripte im Kontext einer Webseite und nicht im Kontext einer Erweiterung ausgeführt werden, benötigen sie häufig eine Möglichkeit, mit dem Rest der Erweiterung zu kommunizieren. Eine RSS-Reader-Erweiterung kann beispielsweise Inhaltsskripte verwenden, um das Vorhandensein eines RSS-Feeds auf einer Seite zu erkennen und dann die Hintergrundseite zu benachrichtigen, damit ein Seitenaktionssymbol für diese Seite angezeigt werden kann.

Die Kommunikation zwischen Erweiterungen und ihren Inhaltsskripts erfolgt über die Nachrichtenweitergabe. Beide Seiten können Nachrichten empfangen, die von der anderen Seite gesendet werden, und auf demselben Kanal antworten. Eine Nachricht kann ein beliebiges gültiges JSON-Objekt enthalten (Null, boolescher Wert, Zahl, String, Array oder Objekt). Es gibt eine einfache API für einmalige Anfragen und eine komplexere API, mit der Sie langlebige Verbindungen für den Austausch mehrerer Nachrichten mit einem gemeinsamen Kontext haben können. Sie können eine Nachricht auch an eine andere Erweiterung senden, wenn Sie deren ID kennen. Weitere Informationen finden Sie im Abschnitt zu erweiterungsübergreifenden Nachrichten.

Einfache einmalige Anfragen

Wenn Sie nur eine einzelne Nachricht an einen anderen Teil der Erweiterung senden und optional eine Antwort erhalten müssen, sollten Sie die vereinfachte Version runtime.sendMessage oder tabs.sendMessage verwenden . Auf diese Weise können Sie eine einmalige JSON-serialisierbare Nachricht von einem Inhaltsskript an die Erweiterung senden oder umgekehrt . Mit einem optionalen Callback-Parameter können Sie die Antwort der anderen Seite verarbeiten, falls vorhanden.

Das Senden einer Anfrage von einem Inhaltsskript sieht so aus:

chrome.runtime.sendMessage({greeting: "hello"}, function(response) {
  console.log(response.farewell);
});

Das Senden einer Anfrage von der Erweiterung an ein Inhaltsskript sieht sehr ähnlich aus, mit der Ausnahme, dass Sie angeben müssen, an welchen Tab sie gesendet werden soll. Dieses Beispiel zeigt, wie auf dem ausgewählten Tab eine Nachricht an das Inhaltsskript gesendet wird.

chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
  chrome.tabs.sendMessage(tabs[0].id, {greeting: "hello"}, function(response) {
    console.log(response.farewell);
  });
});

Am empfangenden Ende müssen Sie einen runtime.onMessage-Ereignis-Listener einrichten, um die Nachricht zu verarbeiten. Dies sieht bei einem Inhaltsskript oder einer Erweiterungsseite gleich aus.

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

Im obigen Beispiel wurde sendResponse synchron aufgerufen. Wenn Sie sendResponse asynchron verwenden möchten, fügen Sie dem onMessage-Event-Handler return true; hinzu.

Hinweis:Wenn mehrere Seiten onMessage-Ereignisse überwachen, sendet nur die erste Seite, die sendResponse() für ein bestimmtes Ereignis aufruft, die Antwort. Alle anderen Antworten auf dieses Ereignis werden ignoriert.
Hinweis: Der sendResponse-Callback ist nur gültig, wenn er synchron verwendet wird oder wenn der Event-Handler true zurückgibt, um anzuzeigen, dass er asynchron reagiert. Der Callback der sendMessage-Funktion wird automatisch aufgerufen, wenn keine Handler „true“ zurückgeben oder wenn der sendResponse-Callback automatisch bereinigt wird.

Langlebige Verbindungen

Manchmal ist es nützlich, eine Unterhaltung zu haben, die länger dauert als eine einzelne Anfrage und Antwort. In diesem Fall kannst du mit runtime.connect bzw. tabs.connect einen langlebigen Kanal von deinem Inhaltsskript auf eine Erweiterungsseite öffnen oder umgekehrt. Der Kanal kann optional einen Namen haben, damit Sie zwischen verschiedenen Verbindungsarten unterscheiden können.

Ein Anwendungsfall könnte die automatische Verlängerung von Formularen sein. Das Inhaltsskript könnte für eine bestimmte Anmeldung einen Kanal zur Erweiterungsseite öffnen und für jedes Eingabeelement auf der Seite eine Nachricht an die Erweiterung senden, um das Ausfüllen der Formulardaten anzufordern. Die freigegebene Verbindung ermöglicht es der Erweiterung, den gemeinsamen Status beizubehalten und die verschiedenen Nachrichten aus dem Inhaltsskript miteinander zu verknüpfen.

Beim Herstellen einer Verbindung erhält jedes Ende ein runtime.Port-Objekt, das zum Senden und Empfangen von Nachrichten über diese Verbindung verwendet wird.

So öffnen Sie einen Kanal über ein Inhaltsskript und senden bzw. hören Nachrichten ab:

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

Das Senden einer Anfrage von der Erweiterung an ein Inhaltsskript sieht sehr ähnlich aus, mit der Ausnahme, dass Sie angeben müssen, zu welchem Tab eine Verbindung hergestellt werden soll. Ersetzen Sie den Anruf zum Herstellen einer Verbindung im obigen Beispiel einfach durch tabs.connect.

Zur Verarbeitung eingehender Verbindungen müssen Sie den Event-Listener runtime.onConnect einrichten. Dies sieht bei einem Content-Skript oder einer Erweiterungsseite gleich aus. Wenn ein anderer Teil der Erweiterung „connect()“ aufruft, wird dieses Ereignis zusammen mit dem Objekt runtime.Port ausgelöst, mit dem Sie Nachrichten über die Verbindung senden und empfangen können. So wird auf eingehende Verbindungen reagiert:

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

Portlebensdauer

Ports sind als bidirektionale Kommunikationsmethode zwischen verschiedenen Teilen der Erweiterung konzipiert, wobei ein Frame auf der obersten Ebene als kleinster Teil betrachtet wird. Beim Aufrufen von tabs.connect, runtime.connect oder runtime.connectNative wird ein Port erstellt. Dieser Port kann sofort verwendet werden, um Nachrichten über postMessage an die andere Seite zu senden.

Wenn ein Tab mehrere Frames enthält, führt der Aufruf von tabs.connect zu mehreren Aufrufen des Ereignisses runtime.onConnect (einmal für jeden Frame auf dem Tab). Bei Verwendung von runtime.connect kann das onConnect-Ereignis mehrmals ausgelöst werden (einmal für jeden Frame im Erweiterungsprozess).

Möglicherweise möchten Sie herausfinden, wann eine Verbindung geschlossen ist, z. B. ob Sie für jeden offenen Port einen separaten Status beibehalten. Dafür können Sie auf das Ereignis runtime.Port.onDisconnect warten. Dieses Ereignis wird ausgelöst, wenn auf der anderen Seite des Kanals keine gültigen Ports vorhanden sind. Dies geschieht in den folgenden Situationen:

  • Am anderen Ende gibt es keine Listener für runtime.onConnect.
  • Der Tab mit dem Port wird entladen, z.B. wenn auf dem Tab navigiert wird.
  • Der Frame, von dem connect aufgerufen wurde, wurde entladen.
  • Alle Frames, die den Port über runtime.onConnect empfangen haben, wurden entladen.
  • runtime.Port.disconnect wird vom anderen Ende aufgerufen. Wenn ein connect-Aufruf zu mehreren Ports auf Empfängerseite führt und disconnect() auf einem dieser Ports aufgerufen wird, wird das Ereignis onDisconnect nur am Port des Absenders ausgelöst, nicht an den anderen Ports.

Erweiterungsübergreifende Botschaft

Über die Messaging-API können Sie nicht nur Nachrichten zwischen verschiedenen Komponenten in Ihrer Erweiterung senden, sondern auch mit anderen Erweiterungen kommunizieren. So können Sie eine öffentliche API bereitstellen, die von anderen Erweiterungen genutzt werden kann.

Das Warten auf eingehende Anfragen und Verbindungen ähnelt dem internen Fall, mit der Ausnahme, dass Sie die Methode runtime.onMessageExternal oder runtime.onConnectExternal verwenden. Hier ein Beispiel:

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

Das Senden einer Nachricht an eine andere Erweiterung ähnelt dem Senden einer Nachricht innerhalb Ihrer Erweiterung. Der einzige Unterschied besteht darin, dass Sie die ID der Erweiterung übergeben müssen, mit der Sie kommunizieren möchten. Beispiel:

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

Nachrichten von Webseiten senden

Ähnlich wie bei erweiterungsübergreifenden Nachrichten kann Ihre App oder Erweiterung Nachrichten von regulären Webseiten empfangen und beantworten. Wenn Sie diese Funktion verwenden möchten, müssen Sie zuerst in der Datei „manifest.json“ festlegen, mit welchen Websites Sie kommunizieren möchten. Beispiel:

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

Dadurch wird die Messaging-API für jede Seite verfügbar gemacht, die mit den von Ihnen angegebenen URL-Mustern übereinstimmt. Das URL-Muster muss mindestens eine Domain der zweiten Ebene enthalten. Hostnamenmuster wie "*", "*.com", "*.co.uk" und "*.appspot.com" sind unzulässig. Verwenden Sie auf der Webseite die APIs runtime.sendMessage oder runtime.connect, um eine Nachricht an eine bestimmte Anwendung oder Erweiterung zu senden. Beispiel:

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

Über die APIs runtime.onMessageExternal oder runtime.onConnectExternal können Sie über Ihre App oder Erweiterung Nachrichten von Webseiten anhören, ähnlich wie bei erweiterungsübergreifenden Nachrichten. Nur die Webseite kann eine Verbindung herstellen. Hier ein Beispiel:

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

Native Benachrichtigungen

Erweiterungen und Apps können Nachrichten mit nativen Anwendungen austauschen, die als nativer Nachrichtenhost registriert sind. Weitere Informationen zu dieser Funktion finden Sie unter Native Nachrichten.

Sicherheitsaspekte

Inhaltsskripte sind weniger vertrauenswürdig

Inhaltsskripts sind weniger vertrauenswürdig als die Hintergrundseite der Erweiterung. Eine schädliche Webseite könnte beispielsweise den Renderer-Prozess gefährden, auf dem die Inhaltsskripts ausgeführt werden. Davon ausgehen, dass Nachrichten von einem Inhaltsskript von einem Angreifer erstellt wurden, sollten Sie alle Eingaben validieren und bereinigen. Gehen Sie davon aus, dass Daten, die an das Inhaltsskript gesendet werden, an die Webseite gelangen könnten. Begrenzen Sie den Umfang privilegierter Aktionen, die durch Nachrichten ausgelöst werden können, die von Inhaltsskripts empfangen werden.

Cross-Site-Scripting

Achten Sie beim Erhalt einer Nachricht von einem Inhaltsskript oder einer anderen Erweiterung darauf, dass Ihre Skripts nicht dem Cross-Site-Scripting zum Opfer fallen. Dieser Hinweis gilt für Skripts, die auf der Hintergrundseite der Erweiterung ausgeführt werden, sowie für Inhaltsskripte, die innerhalb anderer Webursprünge ausgeführt werden. Vermeiden Sie insbesondere die Verwendung gefährlicher APIs wie die folgenden:

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

Stattdessen sollten Sie sicherere APIs bevorzugen, bei denen keine Skripts ausgeführt werden:

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

Beispiele

Im Verzeichnis examples/api/messaging finden Sie einfache Beispiele für die Kommunikation über Nachrichten. Das Beispiel für eine native Nachrichtenfunktion zeigt, wie eine Chrome-App mit einer nativen Anwendung kommunizieren kann. Weitere Beispiele und Hilfe zum Anzeigen des Quellcodes finden Sie unter Beispiele.