Hintergrund- oder Ereignisseiten durch einen Dienst-Worker ersetzen
Ein Dienst-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 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-Dienstworker 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. Erweiterungs-Service Worker werden zwar im Hintergrund ausgeführt, aber sie als Hintergrundscripts zu bezeichnen, ist etwas irreführend, da dies impliziert, dass sie dieselben Funktionen haben. die nachfolgend beschrieben werden.
Änderungen von Hintergrundseiten
Dienstprogramme unterscheiden sich in einer Reihe von Punkten von 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.
- Sie können über die Clientoberfläche 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 bei Nichtgebrauch beendet werden, müssen Sie Anwendungsstatus beibehalten, anstatt sich auf globale Variablen zu verlassen. Wenn Sie Dienstprogramme beenden, können auch Timer vorzeitig beendet werden. Sie müssen sie durch Wecker ersetzen.
Auf dieser Seite werden diese Aufgaben ausführlich beschrieben.
Aktualisieren Sie das Feld „background“ im Manifest.
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 String-Array verwendet werden kann. - 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 Erweiterungs-Dienstworkern.
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, die mit einer Erweiterung gepackt sind, geöffnet und geschlossen werden, ohne die Nutzerfreundlichkeit zu beeinträchtigen. 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 Offscreen-Dokument über den 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');
Kommunikation zwischen Offscreen-Dokumenten und Erweiterungs-Dienst-Workern mithilfe von Nachrichtenübertragung
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: Sie können sie zuerst 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 Erweiterungs-Dienst-Worker unter
chrome.storage
, ob Ihre Daten vorhanden sind. - Wenn Ihre Daten nicht gefunden werden, create 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.
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 });
});
XMLHttpRequest() durch globale fetch() ersetzen
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);
Zustände beibehalten
Service Worker sind sitzungsbasiert, d. h., sie werden während der Browsersitzung eines Nutzers wahrscheinlich wiederholt gestartet, ausgeführt und beendet. Außerdem sind Daten nicht sofort in globalen Variablen verfügbar, da 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 der Browser-Sitzung eines Nutzers 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. Diese APIs können in Service Workern jedoch fehlschlagen, da die Timer immer dann abgebrochen werden, wenn der Service Worker beendet wird.
// 3 minutes in milliseconds const TIMEOUT = 3 * 60 * 1000; setTimeout(() => { chrome.action.setIcon({ path: getRandomIconPath(), }); }, TIMEOUT);
Verwenden Sie stattdessen die Alarms API. Wie bei anderen Listenern sollten auch Wecker-Listener 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. Hinweis: Diese Methode ist nur für Ausnahmefälle vorgesehen. In den meisten Fällen gibt es eine bessere, plattformspezifische Möglichkeit, dasselbe Ergebnis zu erzielen.
Das folgende Beispiel zeigt eine waitUntil()
-Hilfsfunktion, die Ihren Dienstarbeiter aktiv hält, bis ein bestimmtes Versprechen erfüllt ist:
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 Ausnahmefällen kann ein Service Worker durch regelmäßiges Aufrufen einer einfachen Erweiterungs-API aktiv 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. Das Chrome-Erweiterungs-Team behält sich das Recht vor, in Zukunft 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'];
}