Hintergrund- oder Ereignisseiten durch einen Service Worker ersetzen
Ein Service Worker ersetzt die Hintergrund- oder Ereignisseite der Erweiterung, damit der Hintergrundcode nicht im Hauptthread ausgeführt wird. So können Erweiterungen nur bei Bedarf ausgeführt werden, was Ressourcen spart.
Hintergrundseiten sind seit der Einführung von Erweiterungen ein grundlegender Bestandteil davon. Einfach ausgedrückt: Hintergrundseiten bieten eine Umgebung, die unabhängig von anderen Fenstern oder Tabs ist. So können Erweiterungen Ereignisse beobachten und darauf reagieren.
Auf dieser Seite werden die Aufgaben zum Konvertieren von Hintergrundseiten in Erweiterungs-Service-Worker beschrieben. Weitere Informationen zu Service Workern für Erweiterungen finden Sie in der Anleitung Ereignisse mit Service Workern verarbeiten und im Abschnitt Informationen zu Service Workern für Erweiterungen.
Unterschiede zwischen Hintergrundskripts und Service Workern für Erweiterungen
In einigen Kontexten werden Erweiterungs-Service-Worker als „Hintergrundskripts“ bezeichnet. Erweiterungs-Service-Worker werden zwar im Hintergrund ausgeführt, sie als Hintergrundskripts zu bezeichnen, ist jedoch etwas irreführend, da dies impliziert, dass sie dieselben Funktionen haben. die nachfolgend beschrieben werden.
Änderungen von Hintergrundseiten
Service Worker unterscheiden sich in einigen Punkten von Hintergrundseiten.
- Sie funktionieren außerhalb des Hauptthreads, d. h., sie beeinträchtigen den Inhalt der Erweiterung nicht.
- Sie haben spezielle Funktionen, z. B. das Abfangen von Abrufereignissen am Ursprung der Erweiterung, z. B. von einer Symbolleiste.
- Sie können über die Clients-Schnittstelle mit anderen Kontexten kommunizieren und interagieren.
Erforderliche Änderungen
Sie müssen einige Codeanpassungen vornehmen, um die Unterschiede zwischen der Funktionsweise von Hintergrundskripts und Service Workern zu berücksichtigen. Zuerst einmal unterscheidet sich die Angabe eines Service Workers in der Manifestdatei von der Angabe von Hintergrundskripts. Außerdem:
- Da sie nicht auf das DOM oder die
window-Schnittstelle zugreifen können, müssen Sie solche Aufrufe in eine andere API oder in ein Offscreen-Dokument verschieben. - Ereignis-Listener sollten nicht als Antwort auf zurückgegebene Promises oder innerhalb von Ereignis-Callbacks registriert werden.
- Da sie nicht mit
XMLHttpRequest()abwärtskompatibel sind, müssen Sie Aufrufe dieser Schnittstelle durch Aufrufe vonfetch()ersetzen. - Da sie beendet werden, wenn sie nicht verwendet werden, müssen Sie den Anwendungsstatus beibehalten, anstatt sich auf globale Variablen zu verlassen. Durch das Beenden von Service Workern können auch Timer beendet werden, bevor sie abgelaufen sind. Du musst sie durch Wecker ersetzen.
Auf dieser Seite werden diese Aufgaben ausführlich beschrieben.
Feld „background“ im Manifest aktualisieren
In Manifest V3 werden Hintergrundseiten durch einen Service Worker ersetzt. Die Änderungen am Manifest sind unten aufgeführt.
- Ersetzen Sie
"background.scripts"durch"background.service_worker"inmanifest.json. Beachten Sie, dass das Feld"service_worker"einen String und kein Stringarray akzeptiert. - Entfernen Sie
"background.persistent"aus demmanifest.json.
{ ... "background": { "scripts": [ "backgroundContextMenus.js", "backgroundOauth.js" ], "persistent": false }, ... }
{ ... "background": { "service_worker": "service_worker.js", "type": "module" } ... }
Das Feld "service_worker" akzeptiert einen einzelnen String. Das Feld "type" ist nur erforderlich, wenn Sie ES-Module verwenden (mit dem Schlüsselwort import). Der Wert ist immer "module". Weitere Informationen finden Sie unter Grundlagen zu Service Workern für Erweiterungen.
DOM- und Fensteraufrufe in ein Offscreen-Dokument verschieben
Einige Erweiterungen benötigen Zugriff auf das DOM und Fensterobjekte, ohne dass ein neues Fenster oder ein neuer Tab geöffnet wird. Die Offscreen API unterstützt diese Anwendungsfälle, indem sie nicht angezeigte Dokumente, die mit der Erweiterung verpackt sind, öffnet und schließt, ohne die Nutzererfahrung zu beeinträchtigen. Mit Ausnahme der Nachrichtenweitergabe teilen Offscreen-Dokumente keine APIs mit anderen Erweiterungskontexten, sondern fungieren als vollständige Webseiten, mit denen Erweiterungen interagieren können.
Wenn Sie die Offscreen API verwenden möchten, erstellen Sie ein Offscreen-Dokument über den Service Worker.
browser.offscreen.createDocument({
url: browser.runtime.getURL('offscreen.html'),
reasons: ['CLIPBOARD'],
justification: 'testing the offscreen API',
});
Führen Sie im Offscreen-Dokument alle Aktionen aus, die Sie zuvor in einem Hintergrundskript ausgeführt hätten. Sie können beispielsweise Text kopieren, der auf der Hostseite ausgewählt wurde.
let textEl = document.querySelector('#text');
textEl.value = data;
textEl.select();
document.execCommand('copy');
Kommunikation zwischen Offscreen-Dokumenten und Service Workern von Erweiterungen mithilfe der Nachrichtenweitergabe
localStorage in einen anderen Typ konvertieren
Die Storage-Schnittstelle der Webplattform (zugänglich über window.localStorage) kann nicht in einem Service Worker verwendet werden. Dafür gibt es zwei Möglichkeiten. Erstens können Sie sie durch Aufrufe eines anderen Speichermechanismus ersetzen. Der Namespace browser.storage.local ist für die meisten Anwendungsfälle geeignet, es sind jedoch auch andere Optionen verfügbar.
Sie können die Aufrufe auch in ein Offscreen-Dokument verschieben. So migrieren Sie beispielsweise Daten, die zuvor in localStorage gespeichert wurden, zu einem anderen Mechanismus:
- Erstellen Sie ein Offscreen-Dokument mit einer Konvertierungsroutine und einem
runtime.onMessage-Handler. - Fügen Sie dem Offscreen-Dokument eine Konvertierungsroutine hinzu.
- Prüfen Sie im Service Worker der Erweiterung
browser.storageauf Ihre Daten. - Wenn Ihre Daten nicht gefunden werden, erstellen Sie ein Offscreen-Dokument und rufen Sie
runtime.sendMessage()auf, um die Konvertierungsroutine zu starten. - Rufen Sie im
runtime.onMessage-Handler, den Sie dem Offscreen-Dokument hinzugefügt haben, die Konvertierungsroutine auf.
Es gibt auch einige Besonderheiten bei der Funktionsweise von Web Storage APIs in Erweiterungen. Weitere Informationen finden Sie unter Speicher und Cookies.
Listener synchron registrieren
Die asynchrone Registrierung eines Listeners (z. B. in einem Promise oder Callback) funktioniert in Manifest V3 möglicherweise nicht. Sehen Sie sich den folgenden Code an.
browser.storage.local.get(["badgeText"], ({ badgeText }) => {
browser.browserAction.setBadgeText({ text: badgeText });
browser.browserAction.onClicked.addListener(handleActionClick);
});
Das funktioniert mit einer persistenten Hintergrundseite, da die Seite ständig ausgeführt und nie neu initialisiert wird. In Manifest V3 wird der Service Worker neu initialisiert, wenn das Ereignis gesendet wird. Das bedeutet, dass die Listener beim Auslösen des Ereignisses nicht registriert werden, da sie asynchron hinzugefügt werden, und das Ereignis verpasst wird.
Verschieben Sie die Registrierung des Event-Listeners stattdessen auf die oberste Ebene Ihres Scripts. So kann Chrome den Klick-Handler Ihrer Aktion sofort finden und aufrufen, auch wenn die Startlogik Ihrer Erweiterung noch nicht vollständig ausgeführt wurde.
browser.action.onClicked.addListener(handleActionClick);
browser.storage.local.get(["badgeText"], ({ badgeText }) => {
browser.action.setBadgeText({ text: badgeText });
});
XMLHttpRequest() durch globales fetch() ersetzen
XMLHttpRequest() kann nicht von einem Service Worker, einer Erweiterung oder anderweitig aufgerufen werden. Ersetzen Sie Aufrufe von XMLHttpRequest() aus Ihrem Hintergrundskript durch Aufrufe von global fetch().
const xhr = new XMLHttpRequest(); console.log('UNSENT', xhr.readyState); xhr.open('GET', '/api', true); console.log('OPENED', xhr.readyState); xhr.onload = () => { console.log('DONE', xhr.readyState); }; xhr.send(null);
const response = await fetch('https://www.example.com/greeting.json'') console.log(response.statusText);
Status beibehalten
Service Worker sind flüchtig. Das bedeutet, dass sie während der Browsersitzung eines Nutzers wahrscheinlich wiederholt gestartet, ausgeführt und beendet werden. Außerdem sind Daten nicht sofort in globalen Variablen verfügbar, da der vorherige Kontext gelöscht wurde. Verwenden Sie stattdessen Storage APIs als „Source of Truth“. Anhand eines Beispiels wird gezeigt, wie das geht.
Im folgenden Beispiel wird eine globale Variable verwendet, um einen Namen zu speichern. In einem Service Worker kann diese Variable im Laufe der Browsersitzung eines Nutzers mehrmals zurückgesetzt werden.
let savedName = undefined; browser.runtime.onMessage.addListener(({ type, name }) => { if (type === "set-name") { savedName = name; } }); browser.browserAction.onClicked.addListener((tab) => { browser.tabs.sendMessage(tab.id, { name: savedName }); });
Ersetzen Sie bei Manifest V3 die globale Variable durch einen Aufruf der Storage API.
browser.runtime.onMessage.addListener(({ type, name }) => { if (type === "set-name") { browser.storage.local.set({ name }); } }); browser.action.onClicked.addListener(async (tab) => { const { name } = await browser.storage.local.get(["name"]); browser.tabs.sendMessage(tab.id, { name }); });
Timer in Wecker umwandeln
Es ist üblich, verzögerte oder regelmäßige Vorgänge mit den Methoden setTimeout() oder setInterval() zu verwenden. Diese APIs können jedoch in Service Workern fehlschlagen, da die Timer abgebrochen werden, wenn der Service Worker beendet wird.
// 3 minutes in milliseconds const TIMEOUT = 3 * 60 * 1000; setTimeout(() => { browser.action.setIcon({ path: getRandomIconPath(), }); }, TIMEOUT);
Verwenden Sie stattdessen die Alarms API. Wie andere Listener sollten auch Alarm-Listener auf der obersten Ebene des Scripts registriert werden.
async function startAlarm(name, duration) { await browser.alarms.create(name, { delayInMinutes: 3 }); } browser.alarms.onAlarm.addListener(() => { browser.action.setIcon({ path: getRandomIconPath(), }); });
Service Worker aktiv halten
Service Worker sind per Definition ereignisgesteuert und werden bei Inaktivität beendet. So kann Chrome die Leistung und den Arbeitsspeicherverbrauch Ihrer Erweiterung optimieren. Weitere Informationen finden Sie in unserer Dokumentation zum Lebenszyklus von Service Workern. In Ausnahmefällen können zusätzliche Maßnahmen erforderlich sein, damit ein Service Worker länger aktiv bleibt.
Service Worker aktiv halten, bis ein Vorgang mit langer Ausführungszeit abgeschlossen ist
Bei lang andauernden Service Worker-Vorgängen, bei denen keine Erweiterungs-APIs aufgerufen werden, kann der Service Worker während des Vorgangs beendet werden. Beispiele:
- Eine
fetch()-Anfrage, die möglicherweise länger als fünf Minuten dauert (z.B. ein großer Download über eine potenziell schlechte Verbindung). - Eine komplexe asynchrone Berechnung, die länger als 30 Sekunden dauert.
Wenn Sie die Lebensdauer des Service Workers in diesen Fällen verlängern möchten, können Sie regelmäßig eine einfache Erweiterungs-API aufrufen, um den Zeitlimit-Zähler zurückzusetzen. Dies ist jedoch nur für Ausnahmefälle vorgesehen. In den meisten Situationen gibt es in der Regel eine bessere, plattformspezifische Möglichkeit, dasselbe Ergebnis zu erzielen.
Das folgende Beispiel zeigt eine waitUntil()-Hilfsfunktion, die den Service Worker so lange aktiv hält, bis ein bestimmtes Promise aufgelöst wird:
async function waitUntil(promise) {
const keepAlive = setInterval(browser.runtime.getPlatformInfo, 25 * 1000);
try {
await promise;
} finally {
clearInterval(keepAlive);
}
}
waitUntil(someExpensiveCalculation());
Service Worker kontinuierlich aktiv halten
In seltenen Fällen ist es erforderlich, die Lebensdauer unbegrenzt zu verlängern. Wir haben Unternehmen und Bildungseinrichtungen als die größten Anwendungsfälle identifiziert und erlauben dies dort ausdrücklich, unterstützen es aber nicht allgemein. In diesen Ausnahmefällen kann ein Service Worker aktiv gehalten werden, indem regelmäßig eine einfache Erweiterungs-API aufgerufen wird. Diese Empfehlung gilt nur für Erweiterungen, die auf verwalteten Geräten für Unternehmen oder Bildungseinrichtungen ausgeführt werden. In anderen Fällen ist dies nicht zulässig und das Chrome-Erweiterungsteam behält sich das Recht vor, in Zukunft Maßnahmen gegen solche Erweiterungen zu ergreifen.
Verwenden Sie das folgende Code-Snippet, um Ihren Service Worker aktiv zu halten:
/**
* Tracks when a service worker was last alive and extends the service worker
* lifetime by writing the current time to extension storage every 20 seconds.
* You should still prepare for unexpected termination - for example, if the
* extension process crashes or your extension is manually stopped at
* chrome://serviceworker-internals.
*/
let heartbeatInterval;
async function runHeartbeat() {
await browser.storage.local.set({ 'last-heartbeat': new Date().getTime() });
}
/**
* Starts the heartbeat interval which keeps the service worker alive. Call
* this sparingly when you are doing work which requires persistence, and call
* stopHeartbeat once that work is complete.
*/
async function startHeartbeat() {
// Run the heartbeat once at service worker startup.
runHeartbeat().then(() => {
// Then again every 20 seconds.
heartbeatInterval = setInterval(runHeartbeat, 20 * 1000);
});
}
async function stopHeartbeat() {
clearInterval(heartbeatInterval);
}
/**
* Returns the last heartbeat stored in extension storage, or undefined if
* the heartbeat has never run before.
*/
async function getLastHeartbeat() {
return (await browser.storage.local.get('last-heartbeat'))['last-heartbeat'];
}