Nachricht wurde weitergegeben

Mit Messaging-APIs können Sie zwischen verschiedenen Skripts kommunizieren, die in Kontexten ausgeführt werden, die mit Ihrer Erweiterung verknüpft sind. Dazu gehört die Kommunikation zwischen Ihrem Service Worker, chrome-extension://-Seiten und Inhaltsskripts. Eine RSS-Reader-Erweiterung kann beispielsweise Inhaltsskripts verwenden, um das Vorhandensein eines RSS-Feeds auf einer Seite zu erkennen, und dann den Service Worker benachrichtigen, das Aktionssymbol für diese Seite zu aktualisieren.

Es gibt zwei APIs für die Nachrichtenübermittlung: 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, 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. Beide APIs geben ein Promise zurück, das in die Antwort eines Empfängers aufgelöst wird.

So sieht das Senden einer Anfrage von einem Inhaltsskript 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);
})();

Antworten

Verwenden Sie das Ereignis chrome.runtime.onMessage, um auf eine Nachricht zu warten:

// Event listener
function handleMessages(message, sender, sendResponse) {
  if (message !== 'get-status') return;

  fetch('https://example.com')
    .then((response) => sendResponse({statusCode: response.status}))

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

chrome.runtime.onMessage.addListener(handleMessages);

// From the sender's context...
const {statusCode} = await chrome.runtime.sendMessage('get-status');

Wenn der Ereignis-Listener aufgerufen wird, wird eine sendResponse-Funktion als dritter Parameter übergeben. Dies ist eine Funktion, die aufgerufen werden kann, um eine Antwort zu senden. Standardmäßig muss der sendResponse-Callback synchron aufgerufen werden.

Wenn Sie sendResponse ohne Parameter aufrufen, wird null als Antwort gesendet.

Wenn Sie eine Antwort asynchron senden möchten, haben Sie zwei Möglichkeiten: true zurückgeben oder ein Promise zurückgeben.

Zurückgeben true

Wenn Sie asynchron mit sendResponse() antworten möchten, geben Sie vom Ereignis-Listener ein Literal true zurück (nicht nur einen Wert, der als „truthy“ gilt). Dadurch bleibt der Nachrichtenkanal zum anderen Ende geöffnet, bis sendResponse aufgerufen wird. Sie können ihn also später aufrufen.

Promise zurückgeben

Ab Chrome 148 können Sie ein Promise von einem Nachrichten-Listener zurückgeben, um asynchron zu antworten. Dieses Update wird nach und nach eingeführt und ist daher möglicherweise noch nicht in allen Browsern der Nutzer verfügbar. Sie sollten dafür sorgen, dass Ihre Erweiterung damit umgehen kann, ob diese Funktion aktiviert ist oder nicht. Die Verwendung von return true; funktioniert weiterhin für asynchrone Antworten, unabhängig davon, ob diese Funktion aktiviert ist.

Wenn das Promise aufgelöst wird, wird der aufgelöste Wert als Antwort gesendet.

Wenn das Promise abgelehnt wird, wird der sendMessage()-Aufruf des Absenders mit der Fehlermeldung abgelehnt. Weitere Informationen und Beispiele finden Sie im Abschnitt Fehlerbehandlung.

Ein Beispiel für die Rückgabe eines Promises, das aufgelöst oder abgelehnt werden kann:

// Event listener
function handleMessages(message, sender, sendResponse) {
  // Return a promise that wraps fetch
  // If the response is OK, resolve with the status. If it's not OK then reject
  // with the network error that prevents the fetch from completing.
  return new Promise((resolve, reject) => {
    fetch('https://example.com')
      .then(response => {
        if (!response.ok) {
          reject(response);
        } else {
          resolve(response.status);
        }
      })
      .catch(error => {
        reject(error);
      });
  });
}
chrome.runtime.onMessage.addListener(handleMessages);

Sie können einen Listener auch als async deklarieren, um ein Promise zurückzugeben:

chrome.runtime.onMessage.addListener(async function(message, sender) {
  const response = await fetch('https://example.com');
  if (!response.ok) {
    // rejects the promise returned by `async function`.
    throw new Error(`Fetch failed: ${response.status}`);
  }
  // resolves the promise returned by `async function`.
  return {statusCode: response.status};
});

Promise zurückgeben: async Fallstricke bei Funktionen

Beachten Sie, dass eine async-Funktion als Listener immer ein Promise zurückgibt, auch ohne return-Anweisung. Wenn ein async-Listener keinen Wert zurückgibt, wird sein Promise implizit in undefined aufgelöst und null wird als Antwort an den Absender gesendet. Dies kann zu unerwartetem Verhalten führen, wenn mehrere Listener vorhanden sind:

// content_script.js
function handleResponse(message) {
    // The first listener promise resolves to `undefined` before the second
    // listener can respond. When a listener responds with `undefined`, Chrome
    // sends null as the response.
    console.assert(message === null);
}
function notifyBackgroundPage() {
    const sending = chrome.runtime.sendMessage('test');
    sending.then(handleResponse);
}
notifyBackgroundPage();

// background.js
chrome.runtime.onMessage.addListener(async (message) => {
    // This just causes the function to pause for a millisecond, but the promise
    // is *not* returned from the listener so it doesn't act as a response.
    await new Promise(resolve => {
        setTimeout(resolve, 1, 'OK');
    });
   // `async` functions always return promises. So once we
   // reach here there is an implicit `return undefined;`. Chrome translates
   // `undefined` responses to `null`.
});

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  return new Promise((resolve) => {
    setTimeout(resolve, 1000, 'response');
  });
});

Fehlerbehandlung

Ab Chrome 146 wird das von sendMessage() zurückgegebene Promise im Absender mit der Fehlermeldung abgelehnt, wenn ein onMessage-Listener einen Fehler auslöst (entweder synchron oder asynchron durch Rückgabe eines Promises, das abgelehnt wird). Dieses Update wird nach und nach eingeführt und ist daher möglicherweise noch nicht in allen Browsern der Nutzer verfügbar. Sie sollten dafür sorgen, dass Ihre Erweiterung damit umgehen kann, ob diese Änderung aktiviert ist oder nicht.

Wenn ein Listener versucht, eine Antwort zurückzugeben, die nicht serialisiert werden kann (ohne den resultierenden TypeError abzufangen), wird dies auch als Fehler des Listeners betrachtet.

Wenn ein Listener ein Promise zurückgibt, das abgelehnt wird, muss es mit einer Error-Instanz abgelehnt werden, damit der Absender diese Fehlermeldung erhält. Wenn das Promise mit einem anderen Wert (z. B. null oder undefined) abgelehnt wird, wird sendMessage() stattdessen mit einer generischen Fehlermeldung abgelehnt.

Wenn mehrere Listener für onMessage registriert sind, wirkt sich nur der erste Listener, der antwortet, ablehnt oder einen Fehler auslöst, auf den Absender aus. Alle anderen Listener werden ausgeführt, aber ihre Ergebnisse werden ignoriert.

Beispiele

Wenn ein Listener ein Promise zurückgibt, das abgelehnt wird, wird sendMessage() abgelehnt:

// sender.js
try {
  await chrome.runtime.sendMessage('test');
} catch (e) {
  console.log(e.message); // "some error"
}

// listener.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  return Promise.reject(new Error('some error'));
});

Wenn ein Listener mit einem Wert antwortet, der nicht serialisiert werden kann, wird sendMessage() abgelehnt:

// sender.js
try {
  await chrome.runtime.sendMessage('test');
} catch (e) {
  console.log(e.message); // "Error: Could not serialize message."
}

// listener.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  sendResponse(() => {}); // Functions are not serializable
  return true; // Keep channel open for async sendResponse
});

Wenn ein Listener synchron einen Fehler auslöst, bevor ein anderer Listener antwortet, wird das sendMessage()-Promise des Listeners abgelehnt:

// sender.js
try {
  await chrome.runtime.sendMessage('test');
} catch (e) {
  console.log(e.message); // "error!"
}

// listener.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  throw new Error('error!');
});

Wenn jedoch ein Listener antwortet, bevor ein anderer einen Fehler auslöst, ist sendMessage() erfolgreich:

// sender.js
const response = await chrome.runtime.sendMessage('test');
console.log(response); // "OK"

// listener.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  sendResponse('OK');
});
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  throw new Error('error!');
});

Langlebige Verbindungen

Wenn Sie einen wiederverwendbaren, langlebigen Nachrichtenkanal erstellen möchten, rufen Sie Folgendes auf:

  • runtime.connect() , um Nachrichten von einem Inhaltsskript an eine Erweiterungsseite zu senden
  • tabs.connect() , um Nachrichten von einer Erweiterungsseite an ein Inhaltsskript zu senden

Sie können Ihren Kanal benennen, indem Sie einen Optionsparameter mit einem name-Schlüssel übergeben, um zwischen verschiedenen Verbindungstypen zu unterscheiden:

const port = chrome.runtime.connect({name: "example"});

Ein möglicher Anwendungsfall für eine langlebige Verbindung ist eine Erweiterung zum automatischen Ausfüllen von Formularen. Das Inhaltsskript öffnet möglicherweise einen Kanal zur Erweiterungsseite für eine bestimmte Anmeldung und sendet für jedes Eingabeelement auf der Seite eine Nachricht an die Erweiterung, um die Formulardaten anzufordern, die ausgefüllt werden sollen. Über die gemeinsame Verbindung kann die Erweiterung den Status zwischen Erweiterungskomponenten freigeben.

Beim Herstellen einer Verbindung wird jedem Ende ein runtime.Port-Objekt zugewiesen, über das Nachrichten gesendet und empfangen werden können.

Verwenden Sie den folgenden Code, um einen Kanal von einem Inhaltsskript aus zu öffnen und Nachrichten zu senden und zu empfangen:

content-script.js:

const port = chrome.runtime.connect({name: "knockknock"});
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"});
  }
});
port.postMessage({joke: "Knock knock"});

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 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) {
  if (port.name !== "knockknock") {
    return;
  }
  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."});
    }
  });
});

Serialisierung

In Chrome verwenden die APIs für die Nachrichtenübermittlung die JSON-Serialisierung. Das unterscheidet sich von anderen Browsern, die dieselben APIs mit dem strukturierten Klonalgorithmus implementieren.

Das bedeutet, dass eine Nachricht (und Antworten von Empfängern) jeden gültigen JSON.stringify() Wert enthalten kann. Andere Werte werden in serialisierbare Werte umgewandelt (insbesondere wird undefined als null serialisiert).

Größenbeschränkungen für Nachrichten

Die maximale Größe einer Nachricht beträgt 64 MiB.

Lebensdauer von Ports

Ports sind als bidirektionaler Kommunikationsmechanismus zwischen verschiedenen Teilen einer Erweiterung konzipiert. 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 das runtime.onConnect Ereignis einmal für jeden Frame im Tab aufgerufen, wenn tabs.connect() aufgerufen wird. Wenn runtime.connect() aufgerufen wird, kann das Ereignis onConnect einmal für jeden Frame im Erweiterungsprozess ausgelöst werden.

Möglicherweise möchten Sie wissen, wann eine Verbindung geschlossen wird, z. B. wenn Sie für jeden geöffneten Port separate Status beibehalten. Warten Sie dazu auf das runtime.Port.onDisconnect Ereignis. Dieses Ereignis wird ausgelöst, wenn am anderen Ende des Kanals keine gültigen Ports vorhanden sind. Das kann folgende Ursachen haben:

  • 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, in dem connect() aufgerufen wurde, wurde entladen.
  • Alle Frames, die den Port erhalten haben (über runtime.onConnect), wurden entladen.
  • runtime.Port.disconnect() wird vom anderen Ende aufgerufen. Wenn ein connect() Aufruf zu mehreren Ports auf der Empfängerseite führt und disconnect() für einen dieser Ports aufgerufen wird , wird das onDisconnect Ereignis nur am sendenden Port ausgelöst, nicht an den anderen Ports.

Nachrichten zwischen Erweiterungen

Sie können nicht nur Nachrichten zwischen verschiedenen Komponenten in Ihrer Erweiterung senden, sondern auch mit der Messaging API mit anderen Erweiterungen kommunizieren. So können Sie eine öffentliche API für andere Erweiterungen bereitstellen.

Verwenden Sie die runtime.onMessageExternal oder runtime.onConnectExternal Methoden, um auf eingehende Anfragen und Verbindungen von anderen Erweiterungen zu warten. Hier ist ein Beispiel für jede Methode:

service-worker.js

// For a single request:
chrome.runtime.onMessageExternal.addListener(
  function(request, sender, sendResponse) {
    if (sender.id !== allowlistedExtension) {
      return; // don't allow this extension access
    }
    if (request.getTargetData) {
      sendResponse({ targetData: targetData });
    } else if (request.activateLasers) {
      const 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, übergeben Sie die ID der Erweiterung, mit der Sie kommunizieren möchten, so:

service-worker.js

// The ID of the extension we want to talk to.
const 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:
const port = chrome.runtime.connect(laserExtensionId);
port.postMessage(...);

Nachrichten von Webseiten senden

Erweiterungen können auch Nachrichten von Webseiten empfangen und darauf antworten. Wenn Sie Nachrichten von einer Webseite an eine Erweiterung senden möchten, geben Sie in Ihrer manifest.json mit dem "externally_connectable" Manifestschlüssel an, von welchen Websites Nachrichten gesendet werden dürfen. Beispiel:

manifest.json

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

Dadurch wird die Messaging API für jede Seite verfügbar gemacht, die den angegebenen Übereinstimmungsmustern entspricht.

Verwenden Sie die runtime.sendMessage() oder runtime.connect() APIs, um eine Nachricht an eine bestimmte 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);
    }
  );
}

Warten Sie in Ihrer Erweiterung mit den runtime.onMessageExternal oder runtime.onConnectExternal APIs auf Nachrichten von Webseiten, wie beim Senden von Nachrichten zwischen Erweiterungen. 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);
  });

Es ist nicht möglich, eine Nachricht von einer Erweiterung an eine Webseite zu senden.

Natives Messaging

Erweiterungen 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

Hier sind einige Sicherheitsaspekte im Zusammenhang mit Messaging.

Inhaltsskripts sind weniger vertrauenswürdig

Inhaltsskripts sind weniger vertrauenswürdig als der Service Worker der Erweiterung. Beispielsweise kann eine schädliche Webseite den Renderingprozess beeinträchtigen, in dem die Inhaltsskripts ausgeführt werden. Gehen Sie davon aus, dass Nachrichten von einem Inhaltsskript von einem Angreifer erstellt wurden, und prüfen und bereinigen Sie alle Eingaben. Gehen Sie davon aus, dass alle an das Inhaltsskript gesendeten Daten an die Webseite weitergegeben werden können. Beschränken Sie den Umfang privilegierter Aktionen, die durch Nachrichten von Inhaltsskripts ausgelöst werden können.

Cross-Site-Scripting

Schützen Sie Ihre Skripts vor Cross-Site-Scripting. Wenn Sie Daten aus einer nicht vertrauenswürdigen Quelle empfangen, z. B. von Nutzereingaben, anderen Websites über ein Inhaltsskript oder einer API, vermeiden Sie es, diese als HTML zu interpretieren oder so zu 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.
  const 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 Verwendung der folgenden Methoden, die Ihre Erweiterung anfällig machen:

service-worker.js

chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
  // WARNING! Might be evaluating a malicious script!
  const 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;
});