Nachricht wurde weitergegeben

Da Inhaltsskripte im Kontext einer Webseite und nicht in der Erweiterung ausgeführt werden, von der sie ausgeführt werden, benötigen sie oft eine Möglichkeit, mit dem Rest der Erweiterung zu kommunizieren. Beispielsweise kann eine RSS-Reader-Erweiterung Inhaltsskripte verwenden, um das Vorhandensein eines RSS-Feeds auf einer Seite zu erkennen, und dann den Service Worker auffordern, ein Aktionssymbol für diese Seite anzuzeigen.

Für diese Kommunikation wird die Nachrichtenweitergabe verwendet. Dadurch können sowohl Erweiterungen als auch Inhaltsskripte auf Nachrichten der jeweils anderen zugreifen 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 zwei APIs zur Nachrichtenweitergabe: eine für einmalige Anfragen und eine komplexere für langlebige Verbindungen, mit denen mehrere Nachrichten gesendet werden können. Informationen zum Senden von Nachrichten zwischen Erweiterungen finden Sie im Abschnitt Erweiterungsübergreifende Nachrichten.

Einmalige Anfragen

Um eine einzelne Nachricht an einen anderen Teil der Erweiterung zu senden und optional eine Antwort zu erhalten, rufen Sie runtime.sendMessage() oder tabs.sendMessage() auf. Mit diesen Methoden können Sie eine einmalige JSON-serialisierbare Nachricht von einem Inhaltsskript an die Erweiterung oder von der Erweiterung an ein Inhaltsskript senden. Verwende zum Verarbeiten der Antwort das zurückgegebene Promise. Für die Abwärtskompatibilität mit älteren Erweiterungen können Sie stattdessen einen Callback als letztes Argument übergeben. Ein Promise und ein Callback können nicht in einem Anruf verwendet werden.

Informationen zum Konvertieren von Callbacks in Promise und zu ihrer Verwendung in Erweiterungen finden Sie im Leitfaden zur Migration von Manifest V3.

Das Senden einer Anfrage von einem Content-Script sieht so aus:

content-script.js:

(async () => {
  const response = await chrome.runtime.sendMessage({greeting: "hello"});
  // do something with response here, not outside the function
  console.log(response);
})();

Wenn Sie eine Anfrage an ein Inhaltsskript senden möchten, geben Sie an, für welchen Tab sich die Anfrage bezieht, wie im Folgenden gezeigt. Dieses Beispiel funktioniert in Service Workern, Pop-ups und chrome-extension://-Seiten, die als Tab geöffnet sind.

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

Richten Sie zum Empfangen der Nachricht einen runtime.onMessage-Event-Listener ein. Diese verwenden denselben Code in Erweiterungen und Inhaltsskripten:

content-script.js oder 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"});
  }
);

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

Wenn mehrere Seiten onMessage-Ereignisse überwachen, sendet nur die erste Seite, die sendResponse() für ein bestimmtes Ereignis aufruft, die Antwort erfolgreich. Alle anderen Antworten auf diesen Termin werden ignoriert.

Langlebige Verbindungen

Wenn Sie einen wiederverwendbaren Kanal zur dauerhaften Übermittlung von Nachrichten erstellen möchten, rufen Sie runtime.connect() auf, um Nachrichten von einem Inhaltsskript an eine Erweiterungsseite zu übergeben, oder tabs.connect(), um Nachrichten von einer Erweiterungsseite an ein Inhaltsskript zu übergeben. Sie können Ihrem Channel einen Namen geben, um zwischen verschiedenen Verbindungstypen zu unterscheiden.

Ein möglicher Anwendungsfall für eine langlebige Verbindung ist das automatische Ausfüllen von Formularen. Das Inhaltsskript kann 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 die Formulardaten anzufordern. Durch die gemeinsame Verbindung kann die Erweiterung den Status zwischen Erweiterungskomponenten teilen.

Beim Herstellen einer Verbindung wird jedem Ende ein runtime.Port-Objekt zum Senden und Empfangen von Nachrichten über diese Verbindung zugewiesen.

Mit dem folgenden Code kannst du über ein Inhaltsskript einen Kanal öffnen und Nachrichten senden und abhören:

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

Wenn Sie eine Anfrage von der Erweiterung an ein Inhaltsskript senden möchten, ersetzen Sie den Aufruf von runtime.connect() im vorherigen Beispiel durch tabs.connect().

Wenn Sie eingehende Verbindungen für ein Inhaltsskript oder eine Erweiterungsseite verarbeiten möchten, richten Sie einen runtime.onConnect-Event-Listener ein. Wenn ein anderer Teil der Erweiterung connect() aufruft, werden dieses Ereignis und das Objekt runtime.Port aktiviert. Der Code zum Antworten auf eingehende Verbindungen sieht so aus:

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

Port-Lebensdauer

Ports sind als bidirektionale Kommunikation zwischen verschiedenen Teilen der Erweiterung vorgesehen. Ein Frame auf oberster Ebene ist der kleinste Teil einer Erweiterung, der einen Port verwenden kann. Wenn ein Teil einer Erweiterung tabs.connect(), runtime.connect() oder runtime.connectNative() aufruft, wird ein Port erstellt, von dem sofort Nachrichten mit postMessage() gesendet werden können.

Wenn sich auf einem Tab mehrere Frames befinden, wird beim Aufrufen von tabs.connect() das runtime.onConnect-Ereignis einmal für jeden Frame auf dem Tab aufgerufen. Wenn runtime.connect() aufgerufen wird, kann das Ereignis onConnect ebenfalls einmal für jeden Frame im Erweiterungsprozess ausgelöst werden.

Sie können herausfinden, wann eine Verbindung geschlossen ist, z. B. ob Sie für jeden offenen Port einen separaten Status beibehalten. Dazu warten Sie auf das Ereignis runtime.Port.onDisconnect. Dieses Ereignis wird ausgelöst, wenn am anderen Ende des Kanals keine gültigen Ports vorhanden sind. Dies kann folgende Ursachen haben:

  • Am anderen Ende gibt es keine Listener für runtime.onConnect.
  • Der Tab mit dem Anschluss wird entladen, z. B. wenn der Tab navigiert wird.
  • Der Frame, in 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 auf Empfängerseite zu mehreren Ports führt und disconnect() für einen dieser Ports aufgerufen wird, wird das Ereignis onDisconnect nur am Sendeport ausgelöst, nicht an den anderen Ports.

Erweiterungsübergreifende Kommunikation

Über die Messaging-API können Sie nicht nur Nachrichten zwischen verschiedenen Komponenten in Ihrer Erweiterung senden, sondern auch mit anderen Erweiterungen kommunizieren. Auf diese Weise können Sie eine öffentliche API für andere Erweiterungen verfügbar machen.

Verwenden Sie die Methode runtime.onMessageExternal oder runtime.onConnectExternal, um auf eingehende Anfragen und Verbindungen von anderen Erweiterungen zu warten. Hier jeweils ein Beispiel:

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

Um eine Nachricht an eine andere Erweiterung zu senden, geben Sie die ID der Erweiterung, mit der Sie kommunizieren möchten, wie folgt weiter:

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

Nachrichten von Webseiten senden

Erweiterungen können auch Nachrichten von anderen Webseiten empfangen und beantworten, jedoch keine Nachrichten an Webseiten senden. Wenn Sie Nachrichten von einer Webseite an eine Erweiterung senden möchten, geben Sie in der manifest.json mit dem Manifestschlüssel "externally_connectable" an, mit welchen Websites Sie kommunizieren möchten. Beispiel:

manifest.json

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

Dadurch wird die Messaging-API für alle Seiten verfügbar gemacht, die den von Ihnen angegebenen URL-Mustern entsprechen. Das URL-Muster muss mindestens eine Domain der zweiten Ebene enthalten. Hostnamenmuster wie "*", "*.com", "*.co.uk" und "*.appspot.com" werden nicht unterstützt. Ab Chrome 107 können Sie mit <all_urls> auf alle Domains zugreifen. Da die Überprüfung aller Hosts von dieser Änderung betroffen ist, dauert die Überprüfung von Erweiterungen, die sie verwenden, im Chrome Web Store möglicherweise länger.

Verwenden Sie die runtime.sendMessage() oder runtime.connect() API, um eine Nachricht an eine bestimmte App oder Erweiterung zu senden. Beispiel:

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

Über die Erweiterung können Sie Nachrichten von Webseiten mithilfe der APIs runtime.onMessageExternal oder runtime.onConnectExternal wie erweiterungsübergreifendes Messaging abrufen. Beispiel:

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

Natives Messaging

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

Sicherheitsaspekte

Im Folgenden finden Sie einige Sicherheitsaspekte im Zusammenhang mit Nachrichten.

Content-Scripts sind weniger vertrauenswürdig

Inhaltsskripts sind weniger vertrauenswürdig als der Service Worker der Erweiterung. Beispielsweise kann eine schädliche Webseite den Renderingprozess gefährden, der die Inhaltsskripte ausführt. Davon ausgehen, dass Nachrichten von einem Inhaltsskript von einem Angreifer erstellt wurden, müssen Sie alle Eingaben validieren und bereinigen. Sie gehen davon aus, dass an das Inhaltsskript gesendete Daten in die Webseite gelangen könnten. Begrenzen Sie den Umfang der privilegierten Aktionen, die durch Nachrichten ausgelöst werden können, die von Inhaltsskripts empfangen werden.

Cross-Site-Scripting

Schützen Sie Ihre Skripts unbedingt vor Cross-Site-Scripting. Wenn Sie Daten von einer nicht vertrauenswürdigen Quelle erhalten, z. B. von Nutzereingaben, anderen Websites über ein Content-Script oder eine API, achten Sie darauf, dies nicht als HTML zu interpretieren oder auf eine Weise zu verwenden, die die Ausführung von unerwartetem Code verursachen könnte.

Sicherere Methoden

Verwenden Sie nach Möglichkeit APIs, die keine Skripts ausführen:

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

Vermeiden Sie die folgenden Methoden, durch die Ihre Erweiterung angreifbar wird:

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