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 ihrer Einführung ein grundlegender Bestandteil von Erweiterungen. 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 Aufgaben zum Konvertieren von Hintergrundseiten in Erweiterungs-Service-Worker beschrieben. Weitere Informationen zu Service Workern für Erweiterungen finden Sie im Tutorial zu Service Workern und im Abschnitt Service Worker für Erweiterungen.
Unterschiede zwischen Hintergrundskripts und Service Workern für Erweiterungen
In einigen Kontexten werden Service Worker von Erweiterungen als „Hintergrundskripts“ bezeichnet. Obwohl Service Worker für Erweiterungen im Hintergrund ausgeführt werden, ist die Bezeichnung als Hintergrundskript etwas irreführend, da sie identische Funktionen impliziert. die nachfolgend beschrieben werden.
Änderungen auf Hintergrundseiten
Service Worker unterscheiden sich in einigen Punkten von Hintergrundseiten.
- Sie werden außerhalb des Haupt-Threads ausgeführt und beeinträchtigen daher nicht den Inhalt der Erweiterung.
- Sie haben spezielle Funktionen, z. B. das Abfangen von Fetch-Ereignissen im Ursprung der Erweiterung, z. B. von einem Symbolleisten-Pop-up.
- Über die Clients-Schnittstelle können sie mit anderen Kontexten kommunizieren und interagieren.
Erforderliche Änderungen
Sie müssen einige Codeanpassungen vornehmen, um die Unterschiede zwischen der Funktionsweise von Hintergrundskripten und Service Workern zu berücksichtigen. Zunächst einmal wird ein Service Worker in der Manifestdatei anders angegeben als 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. - Event-Listener sollten nicht als Reaktion auf zurückgegebene Promises oder innerhalb von Event-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 Anwendungsstatus beibehalten, anstatt sich auf globale Variablen zu verlassen. Durch das Beenden von Service Workern können auch Timer beendet werden, bevor sie abgeschlossen sind. Sie müssen 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 Manifeständerungen sind unten aufgeführt.
- Ersetzen Sie
"background.scripts"durch"background.service_worker"in dermanifest.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 (mit dem Keyword import) verwenden. 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 die 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 in der Erweiterung enthalten sind, öffnet und schließt, ohne die Nutzerfreundlichkeit zu beeinträchtigen. Mit Ausnahme der Nachrichtenübermittlung verwenden 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 aus dem 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 haben. 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');
Kommunizieren Sie zwischen Offscreen-Dokumenten und Service Workern von Erweiterungen mithilfe von Nachrichtenübermittlung.
„localStorage“ in einen anderen Typ konvertieren
Die Storage-Schnittstelle der Webplattform (über window.localStorage zugänglich) kann nicht in einem Service Worker verwendet werden. Sie haben zwei Möglichkeiten, dieses Problem zu beheben. Zuerst können Sie sie durch Aufrufe eines anderen Speichermechanismus ersetzen. Der Namespace browser.storage.local deckt die meisten Anwendungsfälle ab, es sind aber auch andere Optionen verfügbar.
Sie können die Anrufe auch in ein Dokument auf einem anderen Bildschirm verschieben. Beispiel: So migrieren Sie Daten, die zuvor in localStorage gespeichert wurden, zu einem anderen Mechanismus:
- Erstellen Sie ein Offscreen-Dokument mit einer Conversion-Routine und einem
runtime.onMessage-Handler. - Fügen Sie dem Offscreen-Dokument eine Conversion-Routine hinzu.
- Suchen Sie im Service Worker der Erweiterung nach
browser.storage. - Wenn Ihre Daten nicht gefunden werden, erstellen Sie ein Offscreen-Dokument und rufen Sie
runtime.sendMessage()auf, um die Konvertierungsroutine zu starten. - Rufen Sie die Konvertierungsroutine im
runtime.onMessage-Handler auf, den Sie dem Offscreen-Dokument hinzugefügt haben.
Es gibt auch einige Besonderheiten bei der Funktionsweise von Web Storage APIs in Erweiterungen. Weitere Informationen zu Speicher und Cookies
Listener synchron registrieren
Die asynchrone Registrierung eines Listeners (z. B. in einem Promise oder Callback) funktioniert in Manifest V3 nicht garantiert. 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, weil 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 sind, da sie asynchron hinzugefügt werden. Das Ereignis wird also nicht erfasst.
Verschieben Sie die Registrierung des Event-Listeners stattdessen auf die oberste Ebene Ihres Skripts. 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 global fetch() ersetzen
XMLHttpRequest() kann nicht von einem Service Worker, einer Erweiterung oder auf andere Weise aufgerufen werden. Ersetzen Sie Aufrufe von Ihrem Hintergrundskript an XMLHttpRequest() durch Aufrufe an 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);
Zustände beibehalten
Service Worker sind kurzlebig. Das bedeutet, dass sie während der Browsersitzung eines Nutzers wahrscheinlich wiederholt gestartet, ausgeführt und beendet werden. Das bedeutet auch, dass Daten nicht sofort in globalen Variablen verfügbar sind, da der vorherige Kontext beendet wurde. Um dieses Problem zu umgehen, verwenden Sie Speicher-APIs als „Source of Truth“. Ein Beispiel zeigt, wie das geht.
Im folgenden Beispiel wird eine globale Variable zum Speichern eines Namens verwendet. In einem Service Worker könnte 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 für 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, sobald der Service Worker beendet wird.
// 3 minutes in milliseconds const TIMEOUT = 3 * 60 * 1000; setTimeout(() => { browser.action.setIcon({ path: getRandomIconPath(), }); }, TIMEOUT);
Verwende stattdessen die Alarms API. Wie andere Listener sollten auch Alarm-Listener auf der obersten Ebene Ihres 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 der Dokumentation zum Service Worker-Lebenszyklus. In Ausnahmefällen sind möglicherweise zusätzliche Maßnahmen erforderlich, damit ein Service Worker länger aktiv bleibt.
Service Worker bis zum Abschluss eines Vorgangs mit langer Ausführungszeit aktiv halten
Bei Service Worker-Vorgängen mit langer Ausführungszeit, bei denen keine Erweiterungs-APIs aufgerufen werden, kann es passieren, dass der Service Worker während des Vorgangs heruntergefahren wird. Beispiele:
- Eine
fetch()-Anfrage, die möglicherweise länger als fünf Minuten dauert (z.B. ein großer Download über eine möglicherweise schlechte Verbindung). - Eine komplexe asynchrone Berechnung, die länger als 30 Sekunden dauert.
Um die Lebensdauer des Service Workers in diesen Fällen zu verlängern, können Sie regelmäßig eine triviale Erweiterungs-API aufrufen, um den Timeout-Zähler zurückzusetzen. Das ist nur in Ausnahmefällen möglich. In den meisten Situationen gibt es eine bessere, plattformspezifische Möglichkeit, dasselbe Ergebnis zu erzielen.
Das folgende Beispiel zeigt eine waitUntil()-Hilfsfunktion, die Ihren 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 im Allgemeinen. In diesen Ausnahmefällen kann ein Service Worker aktiv gehalten werden, indem regelmäßig eine triviale Erweiterungs-API aufgerufen wird. Diese Empfehlung gilt nur für Erweiterungen, die auf verwalteten Geräten für Unternehmens- oder Bildungsanwendungsfälle ausgeführt werden. In anderen Fällen ist dies nicht zulässig. 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'];
}