Nachricht wurde weitergegeben

Da Inhaltsskripte im Kontext einer Webseite und nicht im Kontext der Erweiterung ausgeführt werden, die sie ausführt, benötigen sie häufig Möglichkeiten, mit dem Rest der Erweiterung zu kommunizieren. Eine RSS-Reader-Erweiterung kann beispielsweise Inhaltsscripts verwenden, um das Vorhandensein eines RSS-Feeds auf einer Seite zu erkennen und dann den Dienst-Worker zu benachrichtigen, ein Aktionssymbol für diese Seite anzuzeigen.

Für diese Kommunikation wird die Nachrichtenweitergabe verwendet. Dadurch können sowohl Erweiterungen als auch Inhaltsskripte auf die Nachrichten der anderen warten und auf demselben Kanal antworten. Eine Nachricht kann jedes gültige JSON-Objekt enthalten (null, boolescher Wert, Zahl, String, Array oder Objekt). Es gibt zwei APIs für die Nachrichtenweitergabe: eine für einmalige Anfragen und eine komplexere für langlebige Verbindungen, über die mehrere Nachrichten gesendet werden können. Informationen zum Senden von Nachrichten zwischen Erweiterungen finden Sie im Abschnitt Erweiterungsübergreifende Nachrichten.

Einmalige Anfragen

Wenn Sie eine einzelne Nachricht an einen anderen Teil Ihrer Erweiterung senden und optional eine Antwort erhalten möchten, drücken Sie runtime.sendMessage() oder tabs.sendMessage(). Mit diesen Methoden können Sie einmalig eine JSON-serialisierbare Nachricht von einem Inhaltsskript an die Erweiterung oder von der Erweiterung an ein Inhaltsskript senden. Verwende das zurückgegebene Versprechen, um die Antwort zu verarbeiten. Für die Abwärtskompatibilität mit älteren Erweiterungen können Sie stattdessen einen Rückruf als letztes Argument übergeben. Du kannst in einem Aufruf kein Promise und keinen Rückruf verwenden.

Informationen zum Umwandeln von Callbacks in Versprechen und zur Verwendung in Erweiterungen finden Sie im Manifest V3-Migrationsleitfaden.

Das Senden einer Anfrage über ein Inhaltsskript 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 synchron auf eine Nachricht antworten möchten, rufen Sie einfach sendResponse auf, sobald Sie die Antwort haben, und geben Sie false zurück, um anzuzeigen, dass der Vorgang abgeschlossen ist. Um asynchron zu antworten, geben Sie true zurück, damit der sendResponse-Callback aktiv bleibt, bis Sie ihn verwenden können. Async-Funktionen werden nicht unterstützt, da sie ein nicht unterstütztes Promise zurückgeben.

Wenn Sie eine Anfrage an ein Inhaltsskript senden möchten, geben Sie an, für welchen Tab die Anfrage gilt, wie im Folgenden gezeigt. Dieses Beispiel funktioniert bei 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 einen runtime.onMessage-Ereignis-Listener ein, um die Nachricht zu empfangen. Für diese wird in Erweiterungen und Inhaltsscripts derselbe Code verwendet:

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 onMessage-Event-Handler return true; hinzu.

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.

Langlebige Verbindungen

Wenn Sie einen wiederverwendbaren, langlebigen Kanal zur Nachrichtenweitergabe 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. Du kannst deinem Kanal einen Namen geben, um zwischen verschiedenen Arten von Verbindungen zu unterscheiden.

Ein potenzieller Anwendungsfall für eine langlebige Verbindung ist eine Erweiterung zum automatischen Ausfüllen von Formularen. Das Inhaltsskript kann 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 auszufüllenden Formulardaten anzufordern. Über die gemeinsam genutzte Verbindung kann die Erweiterung den Status zwischen den Erweiterungskomponenten teilen.

Beim Herstellen einer Verbindung wird jedem Ende ein runtime.Port-Objekt zugewiesen, um Nachrichten über diese Verbindung zu senden und zu empfangen.

Mit dem folgenden Code kannst du einen Kanal über ein Inhaltsscript öffnen und Nachrichten senden und empfangen:

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 Inhalts-Script oder eine Erweiterungsseite verarbeiten möchten, richten Sie einen runtime.onConnect-Ereignis-Listener ein. Wenn ein anderer Teil Ihrer Erweiterung connect() aufruft, wird dieses Ereignis und das runtime.Port-Objekt aktiviert. Der Code für die Antwort 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."});
  });
});

Portlebensdauer

Ports sind als bidirektionale Kommunikationsmethode zwischen verschiedenen Teilen der Erweiterung konzipiert. Ein Frame der obersten Ebene ist der kleinste Teil einer Erweiterung, der einen Anschluss verwenden kann. Wenn ein Teil einer Erweiterung tabs.connect(), runtime.connect() oder runtime.connectNative() aufruft, wird ein Port erstellt, der sofort Nachrichten mit postMessage() senden kann.

Wenn ein Tab mehrere Frames enthält, wird durch den Aufruf von tabs.connect() das Ereignis runtime.onConnect einmal für jeden Frame auf dem Tab ausgelöst. Wenn runtime.connect() aufgerufen wird, kann das Ereignis onConnect einmal für jeden Frame im Erweiterungsprozess ausgelöst werden.

Möglicherweise möchten Sie herausfinden, wann eine Verbindung geschlossen wird, z. B. wenn Sie einen separaten Status für jeden offenen Port verwalten. Dazu musst du das Ereignis runtime.Port.onDisconnect beobachten. Dieses Ereignis wird ausgelöst, wenn am anderen Ende des Kanals keine gültigen Ports vorhanden sind. Das kann folgende Ursachen haben:

  • Es gibt keine Listener für runtime.onConnect am anderen Ende.
  • Der Tab mit dem Port wird entladen, z. B. wenn der Tab aufgerufen 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 von der anderen Seite aufgerufen. Wenn ein connect()-Aufruf mehrere Ports am Empfängerende hat und disconnect() auf einem dieser Ports aufgerufen wird, wird das onDisconnect-Ereignis nur am Sendeport ausgelöst, nicht an den anderen Ports.

Erweiterungsübergreifende Nachrichten

Mit der 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 für andere Erweiterungen bereitstellen.

Wenn Sie auf eingehende Anfragen und Verbindungen von anderen Erweiterungen warten möchten, verwenden Sie die Methoden runtime.onMessageExternal oder runtime.onConnectExternal. Hier ein Beispiel für jede Art:

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

Wenn Sie eine Nachricht an eine andere Erweiterung senden möchten, geben Sie die ID der Erweiterung an, mit der Sie kommunizieren möchten, und gehen Sie so vor:

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, aber keine Nachrichten an Webseiten senden. Wenn Sie Nachrichten von einer Webseite an eine Erweiterung senden möchten, geben Sie in Ihrer manifest.json mithilfe des Manifestschlüssels "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 freigegeben, die mit den von Ihnen angegebenen URL-Mustern übereinstimmen. Das URL-Muster muss mindestens eine Domain der zweiten Ebene enthalten. Das heißt, 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 sich die Änderung auf alle Hosts auswirkt, kann die Überprüfung von Chrome Web Store-Erweiterungen, die sie verwenden, länger dauern.

Verwenden Sie die APIs runtime.sendMessage() oder runtime.connect(), 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 APIs runtime.onMessageExternal oder runtime.onConnectExternal können Sie in Ihrer Erweiterung Nachrichten von Webseiten empfangen, wie bei erweiterungsübergreifenden Nachrichten. 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 Messaging-Host registriert sind. Weitere Informationen zu dieser Funktion finden Sie unter Native Messaging.

Sicherheitsaspekte

Im Folgenden finden Sie einige Sicherheitsaspekte im Zusammenhang mit Messaging.

Inhaltsskripte sind weniger vertrauenswürdig

Inhaltsskripts sind weniger vertrauenswürdig als der Erweiterungs-Service-Worker. Beispielsweise kann eine schädliche Webseite den Renderingprozess beeinträchtigen, der die Inhaltsskripte ausführt. Gehen Sie davon aus, dass Nachrichten aus einem Inhaltsskript von einem Angreifer erstellt wurden, und validieren und bereinigen Sie alle Eingaben. Angenommen, alle an das Inhaltsskript gesendeten Daten können an die Webseite weitergegeben werden. Begrenzen Sie den Umfang der privilegierten Aktionen, die durch Nachrichten ausgelöst werden können, die von Inhaltsscripts empfangen werden.

Cross-Site-Scripting

Schützen Sie Ihre Skripts unbedingt vor Cross-Site-Scripting. Wenn Sie Daten von einer nicht vertrauenswürdigen Quelle wie Nutzereingaben, anderen Websites über ein Inhaltsskript oder eine API empfangen, sollten Sie diese Daten nicht als HTML interpretieren oder so verwenden, dass unerwarteter Code ausgeführt werden kann.

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, die Ihre Erweiterung angreifbar machen:

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