Nachricht wurde weitergegeben

Da Inhaltsscripts im Kontext einer Webseite ausgeführt werden, nicht in der Erweiterung, 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.

Bei dieser Kommunikation wird die Nachrichtenweitergabe verwendet, sodass sowohl Erweiterungen als auch Inhaltsscripts auf die Nachrichten der jeweils anderen warten und auf demselben Kanal antworten können. 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 Nachrichten zwischen Erweiterungen.

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 eine einmalige 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.

Wenn Sie eine Nachricht senden, wird dem Ereignis-Listener, der die Nachricht verarbeitet, ein optionales drittes Argument, sendResponse, übergeben. Diese Funktion nimmt ein JSON-serialisierbares Objekt entgegen, das als Rückgabewert an die Funktion übergeben wird, die die Nachricht gesendet hat. Standardmäßig muss der sendResponse-Callback synchron aufgerufen werden. Wenn Sie asynchrone Aufgaben ausführen möchten, um den an sendResponse übergebenen Wert abzurufen, müssen Sie vom Ereignis-Listener einen Literalwert von true (nicht nur einen wahrheitsgemäßen Wert) zurückgeben. Dadurch bleibt der Nachrichtenkanal bis zum Aufruf von sendResponse geöffnet.

// Event listener
function handleMessages(message, sender, sendResponse) {

  fetch(message.url)
    .then((response) => sendResponse({statusCode: response.status}))

  // Since `fetch` is asynchronous, must send an explicit `true`
  return true;
}

// Message sender
  const {statusCode} = await chrome.runtime.sendMessage({
    url: 'https://example.com'
  });

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

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 anzugeben, dass die Antwort fertig ist. Wenn du asynchron antworten möchtest, gib true zurück, damit der sendResponse-Callback aktiv bleibt, bis du ihn verwendest. 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, auf welchen Tab sich die Anfrage bezieht, wie unten gezeigt. Dieses Beispiel funktioniert in Service Workers, Pop-ups und chrome-extension://-Seiten, die als Tab geöffnet werden.

(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 du sendResponse() asynchron verwenden möchtest, füge dem onMessage-Ereignis-Handler return true; hinzu.

Wenn mehrere Seiten auf onMessage-Ereignisse warten, kann die Antwort nur von der ersten Seite gesendet werden, die sendResponse() für ein bestimmtes Ereignis aufruft. Alle anderen Antworten auf dieses Ereignis werden ignoriert.

Langlebige Verbindungen

Wenn Sie einen wiederverwendbaren, langlebigen Kanal zum Übergeben 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 Ihren Kanal benennen, 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 gemeinsame 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 Reaktion 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 Top-Level-Frame 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, über den sofort Nachrichten mit postMessage() gesendet werden können.

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.

Sie möchten möglicherweise herausfinden, wann eine Verbindung geschlossen wird, z. B. wenn Sie für jeden offenen Port separate Status 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 Anschluss 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, mit der Sie kommunizieren möchten, so weiter:

service-worker.js

// The ID of the extension we want to talk to.
var laserExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";

// For a minimal 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 runtime.sendMessage()- oder runtime.connect()-APIs, um eine Nachricht an eine bestimmte App oder Erweiterung zu senden. Beispiel:

webpage.js

// The ID of the extension we want to talk to.
const editorExtensionId = 'abcdefghijklmnoabcdefhijklmnoabc';

// Check if extension is installed
if (chrome && chrome.runtime) {
  // Make a request:
  chrome.runtime.sendMessage(
    editorExtensionId,
    {
      openUrlInEditor: url
    },
    (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.

Inhaltsscripts sind weniger vertrauenswürdig

Inhaltsscripts sind weniger vertrauenswürdig als der Service Worker der Erweiterung. So kann eine schädliche Webseite beispielsweise den Rendering-Prozess manipulieren, der die Inhaltsscripts 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önnten 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 Scripts 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 Scripts 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

Verwenden Sie keine der folgenden Methoden, da sie 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;
});