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://pages und Content-Scripts. Eine RSS-Reader-Erweiterung kann beispielsweise Content-Scripts verwenden, um das Vorhandensein eines RSS-Feeds auf einer Seite zu erkennen. Anschließend wird der Service Worker benachrichtigt, 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 Verbindungen mit langer Lebensdauer, ü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 Content-Script an die Erweiterung oder von der Erweiterung an ein Content-Script senden. Beide APIs geben ein Promise zurück, das in die Antwort eines Empfängers aufgelöst wird.
So sieht das Senden einer Anfrage über ein Content-Script 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 chrome.runtime.onMessage
-Ereignis, um auf eine Nachricht zu warten:
// Event listener
function handleMessages(message, sender, sendResponse) {
fetch(message.url)
.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({
url: 'https://example.com'
});
Wenn der Event-Listener aufgerufen wird, wird eine sendResponse
-Funktion als dritter Parameter übergeben. Dies ist eine Funktion, die aufgerufen werden kann, um eine Antwort zu geben. Standardmäßig muss der sendResponse
-Callback synchron aufgerufen werden. Wenn Sie asynchrone Vorgänge ausführen möchten, um den an sendResponse
übergebenen Wert abzurufen, müssen Sie einen Literalwert true
(nicht nur einen „truthy“-Wert) vom Event-Listener zurückgeben. Dadurch bleibt der Nachrichtenkanal für das andere Ende geöffnet, bis sendResponse
aufgerufen wird.
Wenn Sie sendResponse
ohne Parameter aufrufen, wird null
als Antwort gesendet.
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
Rufen Sie Folgendes auf, um einen wiederverwendbaren, langlebigen Channel für die Nachrichtenübergabe zu erstellen:
runtime.connect()
, um Nachrichten von einem Content-Script an eine Erweiterungsseite zu übergebentabs.connect()
, um Nachrichten von einer Erweiterungsseite an ein Content-Script zu übergeben.
Sie können Ihrem Channel einen Namen geben, 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 Content-Script öffnet möglicherweise einen Channel zur Erweiterungsseite für eine bestimmte Anmeldung und sendet für jedes Eingabeelement auf der Seite eine Nachricht an die Erweiterung, um die auszufüllenden Formulardaten anzufordern. Über die gemeinsame Verbindung kann die Erweiterung den Status zwischen Erweiterungskomponenten freigeben.
Beim Herstellen einer Verbindung wird jedem Ende ein runtime.Port
-Objekt zum Senden und Empfangen von Nachrichten über diese Verbindung zugewiesen.
Verwenden Sie den folgenden Code, um einen Kanal über ein Content-Script 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 Content-Script senden möchten, ersetzen Sie den Aufruf von runtime.connect()
im vorherigen Beispiel durch tabs.connect()
.
Um eingehende Verbindungen für ein Content-Script oder eine Erweiterungsseite zu verarbeiten, 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) {
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 wird für die APIs für die Nachrichtenübergabe die JSON-Serialisierung verwendet. Das bedeutet, dass eine Nachricht (und die von Empfängern bereitgestellten Antworten) einen beliebigen gültigen JSON-Wert (null, boolescher Wert, Zahl, String, Array oder Objekt) enthalten kann. Andere Werte werden in serialisierbare Werte umgewandelt.
Dies unterscheidet sich von anderen Browsern, die dieselben APIs mit dem Algorithmus structured clone implementieren.
Port-Lebensdauer
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 es mehrere Frames auf einem Tab gibt, wird beim Aufrufen von tabs.connect()
das Ereignis runtime.onConnect
einmal für jeden Frame auf dem Tab aufgerufen. 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 für jeden geöffneten Port separate Status beibehalten. Dazu müssen Sie auf das Ereignis runtime.Port.onDisconnect
warten. 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 (über
runtime.onConnect
) empfangen haben, wurden entladen. runtime.Port.disconnect()
wird von der anderen Seite aufgerufen. Wenn einconnect()
-Anruf auf Empfängerseite zu mehreren Ports führt unddisconnect()
an einem dieser Ports aufgerufen wird, wird dasonDisconnect
-Ereignis nur am Senderport ausgelöst, 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 auch verwenden, um mit anderen Erweiterungen zu kommunizieren. So können Sie eine öffentliche API für andere Erweiterungen bereitstellen.
Verwenden Sie die Methoden runtime.onMessageExternal
oder runtime.onConnectExternal
, um auf eingehende Anfragen und Verbindungen von anderen Erweiterungen zu warten. Hier ein Beispiel für jede Option:
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 reagieren. Wenn Sie Nachrichten von einer Webseite an eine Erweiterung senden möchten, geben Sie in Ihrem manifest.json
mit dem Manifestschlüssel "externally_connectable"
an, von welchen Websites Nachrichten zulässig sein sollen. 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 Second-Level-Domain enthalten. Hostnamenmuster wie „*“, „*.com“, „*.co.uk“ und „*.appspot.com“ werden nicht unterstützt. Sie können <all_urls>
verwenden, um auf alle Domains zuzugreifen.
Verwenden Sie die APIs runtime.sendMessage()
oder runtime.connect()
, 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);
}
);
}
Hören Sie in Ihrer Erweiterung auf Nachrichten von Webseiten, indem Sie die APIs runtime.onMessageExternal
oder runtime.onConnectExternal
wie in Nachrichtenübermittlung zwischen Erweiterungen verwenden. 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 mit nativen Anwendungen austauschen, die als Host für native Nachrichten registriert sind. Weitere Informationen zu dieser Funktion finden Sie unter Native Messaging.
Sicherheitsaspekte
Hier sind einige Sicherheitsaspekte im Zusammenhang mit Messaging.
Inhaltskripte sind weniger vertrauenswürdig
Content-Scripts sind weniger vertrauenswürdig als der Service Worker der Erweiterung. Beispielsweise kann eine schädliche Webseite den Rendering-Prozess, in dem die Inhaltsskripts ausgeführt werden, beeinträchtigen. Gehen Sie davon aus, dass Nachrichten von einem Content-Script von einem Angreifer erstellt wurden, und validieren und bereinigen Sie alle Eingaben. Gehen Sie davon aus, dass alle Daten, die an das Inhaltsscript gesendet werden, an die Webseite weitergegeben werden könnten. Beschränken Sie den Umfang privilegierter Aktionen, die durch Nachrichten ausgelöst werden können, die von Inhaltsskripts empfangen werden.
Cross-Site-Scripting
Schützen Sie Ihre Skripts vor Cross-Site-Scripting. Wenn Sie Daten aus einer nicht vertrauenswürdigen Quelle wie Nutzereingaben, anderen Websites über ein Inhaltsskript oder einer API empfangen, sollten Sie darauf achten, dass diese nicht als HTML interpretiert oder auf eine Weise verwendet werden, die die Ausführung unerwarteten Codes ermöglicht.
Verwenden Sie nach Möglichkeit APIs, mit denen keine Skripts ausgeführt werden:
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; });
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; });