Hintergrund- oder Ereignisseiten durch einen Dienst-Worker ersetzen
Ein Service Worker ersetzt den Hintergrund oder die Ereignisseite der Erweiterung, um sicherzustellen, dass der Hintergrundcode aus dem Hauptthread entfernt bleibt. Dadurch können Erweiterungen nur bei Bedarf ausgeführt werden, wodurch Ressourcen gespart werden.
Hintergrundseiten sind seit ihrer Einführung ein wesentlicher 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 allgemeine Informationen zu Erweiterungs-Dienstworkern finden Sie im Tutorial Ereignisse mit Dienstworkern verarbeiten und im Abschnitt Erweiterungs-Dienstworker.
Unterschiede zwischen Hintergrundscripts und Dienst-Workern von Erweiterungen
In einigen Kontexten werden Erweiterungs-Dienstworker als „Hintergrundscripts“ bezeichnet. Obwohl die Erweiterungs-Service-Worker im Hintergrund ausgeführt werden, ist der Aufruf von Hintergrundskripts irreführend, da identische Funktionen impliziert werden. die nachfolgend beschrieben werden.
Änderungen von Hintergrundseiten
Service Worker haben eine Reihe von Unterschieden zu Hintergrundseiten.
- Sie funktionieren unabhängig vom Haupt-Thread und beeinträchtigen daher nicht die Erweiterungsinhalte.
- Sie haben spezielle Funktionen wie das Abfangen von Abruf-Ereignissen am Ursprung der Erweiterung, z. B. von einem Symbolleisten-Pop-up.
- Über die Client-Benutzeroberfläche können sie mit anderen Kontexten kommunizieren und interagieren.
Erforderliche Änderungen
Sie müssen einige Codeanpassungen vornehmen, um die Unterschiede zwischen Hintergrundscripts und Dienstarbeitern zu berücksichtigen. Zunächst unterscheidet sich die Angabe eines Service Workers in der Manifestdatei von der Angabe von Hintergrundscripts. Weitere Schritte:
- 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 Reaktion auf zurückgegebene Versprechen oder innerhalb von Ereignis-Callbacks registriert werden.
- Da sie nicht abwärtskompatibel mit
XMLHttpRequest()
sind, müssen Sie Aufrufe dieser Schnittstelle durch Aufrufe vonfetch()
ersetzen. - Da sie beendet werden, wenn sie nicht verwendet werden, müssen Sie die Anwendungsstatus beibehalten, anstatt sich auf globale Variablen zu verlassen. Wenn Sie Dienstprogramme beenden, können auch Timer vorzeitig beendet werden. In diesem Fall musst du sie durch Alarme ersetzen.
Auf dieser Seite werden diese Aufgaben ausführlich beschrieben.
Feld „background“ im Manifest aktualisieren
In Manifest V3 werden Hintergrundseiten durch einen Dienst-Worker ersetzt. Die Manifeständerungen sind unten aufgeführt.
- Ersetzen Sie
"background.scripts"
durch"background.service_worker"
inmanifest.json
. Beachten Sie, dass für das Feld"service_worker"
ein String und kein Array mit Strings angegeben wird. - 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"
kann nur einen einzelnen String enthalten. Das Feld "type"
ist nur erforderlich, wenn Sie ES-Module verwenden (mit dem Keyword import
). Der Wert ist immer "module"
. Weitere Informationen finden Sie unter Grundlagen von Extension Service Workers
DOM- und Fensteraufrufe in ein Offscreen-Dokument verschieben
Einige Erweiterungen benötigen Zugriff auf das DOM und die Fensterobjekte, ohne dass ein neues Fenster oder ein neuer Tab geöffnet wird. Die Offscreen API unterstützt diese Anwendungsfälle, indem nicht angezeigte Dokumente mit Erweiterung geöffnet und geschlossen werden, ohne dass die Nutzererfahrung beeinträchtigt wird. Außer bei der Nachrichtenweitergabe teilen Offscreen-Dokumente keine APIs mit anderen Erweiterungskontexten, sondern funktionieren als vollständige Webseiten, mit denen Erweiterungen interagieren können.
Wenn Sie die Offscreen API verwenden möchten, erstellen Sie ein nicht sichtbares Dokument vom Service Worker.
chrome.offscreen.createDocument({
url: chrome.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');
Mithilfe der Nachrichtenweitergabe zwischen nicht sichtbaren Dokumenten und Erweiterungs-Service-Workern kommunizieren
localStorage in einen anderen Typ konvertieren
Die Storage
-Benutzeroberfläche der Webplattform (über window.localStorage
zugänglich) kann nicht in einem Dienstworker verwendet werden. Sie haben zwei Möglichkeiten, dieses Problem zu beheben: Erstens können Sie sie durch Aufrufe eines anderen Speichermechanismus ersetzen. Der Namespace chrome.storage.local
eignet sich für die meisten Anwendungsfälle. Es gibt aber auch andere Optionen.
Sie können die Aufrufe auch in ein Offscreen-Dokument verschieben. So migrieren Sie beispielsweise Daten, die zuvor in localStorage
gespeichert waren, 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 Konvertierungsroutine hinzu.
- Prüfen Sie im Service Worker der Erweiterung unter
chrome.storage
, ob Ihre Daten vorhanden sind. - Wenn Ihre Daten nicht gefunden werden, create Sie ein nicht sichtbares Dokument und rufen Sie
runtime.sendMessage()
auf, um den Conversion-Ablauf zu starten. - Rufen Sie im
runtime.onMessage
-Handler, den Sie dem Offscreen-Dokument hinzugefügt haben, die Konvertierungsroutine auf.
Außerdem gibt es einige Nuancen bei der Funktionsweise von Webspeicher-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 nicht garantiert. Sehen wir uns den folgenden Code an.
chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
chrome.browserAction.setBadgeText({ text: badgeText });
chrome.browserAction.onClicked.addListener(handleActionClick);
});
Dies funktioniert mit einer persistenten Hintergrundseite, da die Seite ständig ausgeführt wird und nie neu initialisiert wird. In Manifest V3 wird der Dienst-Worker beim Senden des Ereignisses neu initialisiert. Das bedeutet, dass die Listener beim Auslösen des Ereignisses nicht registriert werden, da sie asynchron hinzugefügt werden, und das Ereignis somit verpasst wird.
Verschieben Sie die Registrierung des Ereignisempfängers stattdessen auf die oberste Ebene des Scripts. So kann Chrome den Klickhandler Ihrer Aktion sofort finden und aufrufen, auch wenn die Startlogik Ihrer Erweiterung noch nicht vollständig ausgeführt wurde.
chrome.action.onClicked.addListener(handleActionClick);
chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
chrome.action.setBadgeText({ text: badgeText });
});
Ersetzen Sie XMLHttpRequest() mit globalemfetch() .
XMLHttpRequest()
kann nicht von einem Service Worker, einer Erweiterung oder anderweitig aufgerufen werden. Ersetzen Sie Aufrufe von XMLHttpRequest()
in 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 sitzungsspezifisch. Das bedeutet, dass sie während der Browsersitzung eines Nutzers wahrscheinlich wiederholt gestartet, ausgeführt und beendet werden. Es bedeutet auch, dass Daten in globalen Variablen nicht sofort verfügbar sind, seit der vorherige Kontext entfernt wurde. Verwenden Sie stattdessen Speicher-APIs als „Source of Truth“. Ein Beispiel zeigt, wie das geht.
Im folgenden Beispiel wird eine globale Variable verwendet, um einen Namen zu speichern. In einem Service Worker kann diese Variable während einer Browsersitzung mehrmals zurückgesetzt werden.
let savedName = undefined; chrome.runtime.onMessage.addListener(({ type, name }) => { if (type === "set-name") { savedName = name; } }); chrome.browserAction.onClicked.addListener((tab) => { chrome.tabs.sendMessage(tab.id, { name: savedName }); });
Ersetzen Sie bei Manifest V3 die globale Variable durch einen Aufruf der Storage API.
chrome.runtime.onMessage.addListener(({ type, name }) => { if (type === "set-name") { chrome.storage.local.set({ name }); } }); chrome.action.onClicked.addListener(async (tab) => { const { name } = await chrome.storage.local.get(["name"]); chrome.tabs.sendMessage(tab.id, { name }); });
Timer in Wecker umwandeln
Es ist üblich, verzögerte oder periodische Vorgänge mit den Methoden setTimeout()
oder setInterval()
auszuführen. Bei diesen APIs können jedoch Fehler in Service Workern auftreten, da die Timer bei jedem Beenden des Service Workers abgebrochen werden.
// 3 minutes in milliseconds const TIMEOUT = 3 * 60 * 1000; setTimeout(() => { chrome.action.setIcon({ path: getRandomIconPath(), }); }, TIMEOUT);
Verwende stattdessen die Alarms API. Wie bei anderen Listenern sollten auch Wecker auf der obersten Ebene des Scripts registriert werden.
async function startAlarm(name, duration) { await chrome.alarms.create(name, { delayInMinutes: 3 }); } chrome.alarms.onAlarm.addListener(() => { chrome.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 Speicherverbrauch 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 Dienstarbeiter länger aktiv bleibt.
Service Worker aktiv halten, bis ein langwieriger Vorgang abgeschlossen ist
Bei lang laufenden Service Worker-Vorgängen, bei denen keine Erweiterungs-APIs aufgerufen werden, wird der Service Worker möglicherweise während des Vorgangs heruntergefahren. Beispiele:
- Eine
fetch()
-Anfrage, die möglicherweise länger als fünf Minuten dauert (z.B. ein großer Download bei einer möglicherweise schlechten Verbindung). - Eine komplexe asynchrone Berechnung, die länger als 30 Sekunden dauert.
Um die Lebensdauer des Dienstarbeiters in diesen Fällen zu verlängern, können Sie regelmäßig eine triviale Erweiterungs-API aufrufen, um den Zeitüberschreitungszähler zurückzusetzen. Bitte beachten Sie, dass dies nur in Ausnahmefällen möglich ist. In den meisten Fällen gibt es in der Regel eine bessere, plattformbasierte idiomatische Methode, um dasselbe Ergebnis zu erzielen.
Das folgende Beispiel zeigt die Hilfsfunktion waitUntil()
, die Ihren Service Worker so lange aktiv hält, bis ein bestimmtes Promise aufgelöst wird:
async function waitUntil(promise) = {
const keepAlive = setInterval(chrome.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 auf unbestimmte Zeit zu verlängern. Wir haben Unternehmen und Bildungseinrichtungen als die größten Anwendungsfälle identifiziert und erlauben dies dort ausdrücklich, aber wir unterstützen dies nicht allgemein. In diesen außergewöhnlichen Umständen kann ein Service Worker durch regelmäßiges Aufrufen einer trivialen Erweiterungs-API am Leben gehalten werden. 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 Team für Chrome-Erweiterungen behält sich das Recht vor, zukünftig Maßnahmen gegen diese Erweiterungen zu ergreifen.
Mit dem folgenden Code-Snippet können Sie Ihren Dienst-Worker aktiv 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 chrome.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 chrome.storage.local.get('last-heartbeat'))['last-heartbeat'];
}