Nachricht wurde weitergegeben

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

Die Kommunikation zwischen Erweiterungen und ihren Content-Scripts erfolgt über die Nachrichtenübermittlung. Beide Seiten können auf Nachrichten warten, 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 herstellen können. Es ist auch möglich, eine Nachricht an eine andere Erweiterung zu senden, wenn Sie ihre ID kennen. Dies wird im Abschnitt Nachrichten zwischen Erweiterungen behandelt.

Einfache einmalige Anfragen

Wenn Sie nur eine einzelne Nachricht an einen anderen Teil Ihrer Erweiterung senden und optional eine Antwort erhalten möchten, sollten Sie die vereinfachte runtime.sendMessage oder tabs.sendMessage verwenden . So können Sie eine einmalige JSON-serialisierbare Nachricht von einem Content-Script an die Erweiterung oder umgekehrt senden. Mit einem optionalen Callback-Parameter können Sie die Antwort von der anderen Seite verarbeiten, falls vorhanden.

So senden Sie eine Anfrage von einem Content-Script:

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

Das Senden einer Anfrage von der Erweiterung an ein Content-Script sieht sehr ähnlich aus. Sie müssen jedoch angeben, an welchen Tab sie gesendet werden soll. In diesem Beispiel wird eine Nachricht an das Content-Script im ausgewählten Tab gesendet.

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

Auf der Empfängerseite müssen Sie einen runtime.onMessage-Ereignis-Listener einrichten, um die Nachricht zu verarbeiten. Dies sieht von einem Content-Script oder einer Erweiterungsseite aus 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 return true; zum onMessage-Event-Handler hinzu.

Hinweis:Wenn mehrere Seiten auf onMessage-Ereignisse warten, kann nur die erste Seite, die sendResponse() für ein bestimmtes Ereignis aufruft, die Antwort senden. 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 anzugeben, dass er asynchron antworten wird. Der Callback der Funktion sendMessage wird automatisch aufgerufen, wenn keine Handler „true“ zurückgeben oder wenn der sendResponse-Callback per Garbage Collection entfernt wird.

Langlebige Verbindungen

Manchmal ist es nützlich, eine Unterhaltung zu führen, die länger als eine einzelne Anfrage und Antwort dauert. In diesem Fall können Sie mit runtime.connect oder tabs.connect einen langlebigen Kanal von Ihrem Content-Script zu einer Erweiterungsseite oder umgekehrt öffnen . Der Kanal kann optional einen Namen haben, sodass Sie zwischen verschiedenen Verbindungstypen unterscheiden können.

Ein Anwendungsfall könnte eine Erweiterung zum automatischen Ausfüllen von Formularen sein. Das Content-Script könnte einen Kanal zur Erweiterungsseite für eine bestimmte Anmeldung öffnen und für jedes Eingabeelement auf der Seite eine Nachricht an die Erweiterung senden, um die Formulardaten zum Ausfüllen anzufordern. Über die gemeinsame Verbindung kann die Erweiterung einen gemeinsamen Status beibehalten, der die verschiedenen Nachrichten aus dem Content-Script verknüpft.

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 von einem Content-Script aus und senden und empfangen Nachrichten:

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 Content-Script sieht sehr ähnlich aus. Sie müssen jedoch angeben, mit welchem Tab eine Verbindung hergestellt werden soll. Ersetzen Sie im obigen Beispiel einfach den Aufruf von „connect“ durch tabs.connect.

Um eingehende Verbindungen zu verarbeiten, müssen Sie einen runtime.onConnect Ereignis Listener einrichten. Dies sieht von einem Content-Script oder einer Erweiterungsseite aus gleich aus. Wenn ein anderer Teil Ihrer Erweiterung „connect()“ aufruft, wird dieses Ereignis zusammen mit dem runtime.Port-Objekt ausgelöst, mit dem Sie Nachrichten über die Verbindung senden und empfangen können. So sieht die Antwort auf eingehende Verbindungen aus:

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

Lebensdauer des Ports

Ports sind als bidirektionale Kommunikationsmethode zwischen verschiedenen Teilen der Erweiterung konzipiert, wobei ein Frame 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 das andere Ende zu senden.

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

Möglicherweise möchten Sie wissen, wann eine Verbindung geschlossen wird, z. B. wenn Sie für jeden geöffneten Port einen separaten Status verwalten. Dazu können Sie auf das runtime.Port.onDisconnect-Ereignis warten. Dieses Ereignis wird ausgelöst, wenn auf der anderen Seite des Kanals keine gültigen Ports vorhanden sind. Das kann in folgenden Situationen passieren:

  • Auf der anderen Seite gibt es keine Listener für runtime.onConnect.
  • Der Tab mit dem Port wird entladen (z.B. wenn der Tab navigiert wird).
  • Der Frame, von dem aus connect aufgerufen wurde, wurde entladen.
  • Alle Frames, die den Port erhalten haben (über runtime.onConnect), wurden entladen.
  • runtime.Port.disconnect wird von der anderen Seite aufgerufen. Beachten Sie, dass, wenn ein connect Aufruf auf der Empfängerseite zu mehreren Ports führt und disconnect() für einen dieser Ports aufgerufen wird, das onDisconnect Ereignis nur am Port des Absenders ausgelöst wird, nicht an den anderen Ports.

Nachrichten zwischen Erweiterungen

Neben dem Senden von Nachrichten zwischen verschiedenen Komponenten in Ihrer Erweiterung können Sie die Messaging API verwenden, um mit anderen Erweiterungen zu 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 dem Unterschied, dass Sie die runtime.onMessageExternal oder runtime.onConnectExternal Methoden verwenden. Hier ein Beispiel für jede Methode:

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

Ebenso ähnelt das Senden einer Nachricht an eine andere Erweiterung 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 der Nachrichtenübermittlung zwischen Erweiterungen kann Ihre App oder Erweiterung Nachrichten von regulären Webseiten empfangen und darauf antworten. Damit Sie diese Funktion verwenden können, müssen Sie zuerst in Ihrer manifest.json angeben, 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 Second-Level-Domain enthalten. Hostname-Muster wie „*“, „*.com“, „*.co.uk“ und „*.appspot.com“ sind nicht zulässig. Verwenden Sie auf der Webseite die runtime.sendMessage oder runtime.connect APIs, um eine Nachricht an eine bestimmte App 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);
  });

In Ihrer App oder Erweiterung können Sie über die APIs runtime.onMessageExternal oder runtime.onConnectExternal auf Nachrichten von Webseiten warten, ähnlich wie bei der Nachrichtenübermittlung zwischen Erweiterungen. Nur die Webseite kann eine Verbindung initiieren. 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);
  });

Natives Messaging

Erweiterungen und Apps können Nachrichten austauschen mit nativen Anwendungen, die als Host für natives Messaging registriert sind. Weitere Informationen zu dieser Funktion finden Sie unter Natives Messaging.

Sicherheitsaspekte

Content-Scripts sind weniger vertrauenswürdig

Content-Scripts sind weniger vertrauenswürdig als die Hintergrundseite der Erweiterung. Eine schädliche Web seite kann beispielsweise den Renderer-Prozess kompromittieren, in dem die Content-Scripts ausgeführt werden. Gehen Sie davon aus, dass Nachrichten von einem Content-Script von einem Angreifer erstellt wurden, und stellen Sie sicher, dass Sie alle Eingaben validieren und bereinigen. Gehen Sie davon aus, dass alle an das Content-Script gesendeten Daten an die Webseite weitergegeben werden können. Beschränken Sie den Umfang privilegierter Aktionen, die durch Nachrichten von Content-Scripts ausgelöst werden können.

Cross-Site-Scripting

Wenn Sie eine Nachricht von einem Content-Script oder einer anderen Erweiterung erhalten, sollten Ihre Skripts darauf achten, nicht Opfer von Cross-Site-Scripting zu werden. Dieser Hinweis gilt sowohl für Skripts, die auf der Hintergrundseite der Erweiterung ausgeführt werden, als auch für Content-Scripts, die in anderen Web-Ursprüngen ausgeführt werden. Vermeiden Sie insbesondere die Verwendung gefährlicher APIs wie der 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;
});

Verwenden Sie stattdessen sicherere APIs, die keine Skripts ausführen:

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

Einfache Beispiele für die Kommunikation über Nachrichten finden Sie im Verzeichnis examples/api/messaging. Im Beispiel für natives Messaging wird gezeigt, wie eine Chrome-App mit einer systemeigenen App kommunizieren kann. Weitere Beispiele und Hilfe beim Anzeigen des Quellcodes finden Sie unter Beispiele.