Zu einem Service Worker migrieren

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. Einfach ausgedrückt bieten Hintergrundseiten 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 Erweiterungs-Service Workern finden Sie im Tutorial Ereignisse mit Service Workern verarbeiten und im Abschnitt Über Erweiterungs-Service Worker.

Unterschiede zwischen Hintergrundskripten und Erweiterungs-Service Workern

In einigen Kontexten werden Erweiterungs-Service Worker als „Hintergrundskripte“ bezeichnet. Erweiterungs-Service Worker werden zwar im Hintergrund ausgeführt, aber die Bezeichnung als Hintergrundskripte ist etwas irreführend, da sie identische Funktionen impliziert. Die Unterschiede werden unten beschrieben.

Änderungen gegenüber Hintergrundseiten

Service Worker unterscheiden sich in einigen Punkten von Hintergrundseiten.

  • Sie werden außerhalb des Hauptthreads ausgeführt, sodass sie den Inhalt der Erweiterung nicht beeinträchtigen.
  • Sie haben spezielle Funktionen wie 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 unterscheidet sich die Art und Weise, wie ein Service Worker in der Manifestdatei angegeben wird, von der Art und Weise, wie Hintergrundskripte angegeben werden. 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 Promises oder in Ereignis-Callbacks registriert werden.
  • Da sie nicht abwärtskompatibel mit XMLHttpRequest() sind, müssen Sie Aufrufe dieser Schnittstelle durch Aufrufe von fetch() ersetzen.
  • Da sie beendet werden, wenn sie nicht verwendet werden, müssen Sie Anwendungsstatus beibehalten, anstatt sich auf globale Variablen zu verlassen. Wenn Service Worker beendet werden, können auch Timer beendet werden, bevor sie abgeschlossen sind. Sie müssen durch Alarme ersetzt werden.

Auf dieser Seite werden diese Aufgaben im Detail 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 in der manifest.json "background.scripts" durch "background.service_worker". Beachten Sie, dass das "service_worker" Feld einen String und kein Array von Strings akzeptiert.
  • Entfernen Sie "background.persistent" aus der manifest.json.
Manifest V2
{
  ...
  "background": {
    "scripts": [
      "backgroundContextMenus.js",
      "backgroundOauth.js"
    ],
    "persistent": false
  },
  ...
}
Manifest V3
{
  ...
  "background": {
    "service_worker": "service_worker.js",
    "type": "module"
  }
  ...
}

Das "service_worker" Feld akzeptiert einen einzelnen String. Das Feld "type" ist nur erforderlich, wenn Sie ES-Module verwenden (mit dem import Schlüsselwort). Der Wert ist immer "module". Weitere Informationen finden Sie unter Grundlagen von Erweiterungs-Service Workern.

DOM- und Fensteraufrufe in ein Offscreen-Dokument verschieben

Einige Erweiterungen benötigen Zugriff auf die DOM- und Fensterobjekte, ohne ein neues Fenster oder einen neuen Tab visuell zu öffnen. Die Offscreen API unterstützt diese Anwendungsfälle, indem sie nicht angezeigte Dokumente öffnet und schließt, die mit der Erweiterung verpackt sind, ohne die Nutzererfahrung 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.

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 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 Erweiterungs-Service Workern über die 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. Erstens können Sie sie durch Aufrufe eines anderen Speichermechanismus ersetzen. Der chrome.storage.local Namespace ist für die meisten Anwendungsfälle geeignet, aber andere Optionen sind 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:

  1. Erstellen Sie ein Offscreen-Dokument mit einer Konvertierungsroutine und einem runtime.onMessage-Handler.
  2. Fügen Sie dem Offscreen-Dokument eine Konvertierungsroutine hinzu.
  3. Prüfen Sie im Erweiterungs-Service Worker chrome.storage auf Ihre Daten.
  4. Wenn Ihre Daten nicht gefunden werden, erstellen Sie ein Offscreen-Dokument und rufen Sie runtime.sendMessage() auf, um die Konvertierungsroutine zu starten.
  5. Rufen Sie im runtime.onMessage-Handler, den Sie dem Offscreen-Dokument hinzugefügt haben, die Konvertierungsroutine auf.

Es gibt auch einige Nuancen 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 nicht garantiert. Sehen Sie sich 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 und nie neu initialisiert wird. In Manifest V3 wird der Service Worker neu initialisiert, wenn das Ereignis gesendet wird. Wenn das Ereignis ausgelöst wird, sind die Listener also nicht registriert (da sie asynchron hinzugefügt werden) und das Ereignis wird verpasst.

Verschieben Sie stattdessen die Registrierung des Ereignis-Listeners auf die oberste Ebene Ihres Skripts. So kann Chrome den Klick-Handler Ihrer Aktion sofort finden und aufrufen, auch wenn die Erweiterung die Startlogik noch nicht vollständig ausgeführt hat.

chrome.action.onClicked.addListener(handleActionClick);

chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
  chrome.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 XMLHttpRequest() in Ihrem Hintergrundskript durch Aufrufe von global fetch().

XMLHttpRequest()
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);
fetch()
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 einer 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 beendet wurde. Verwenden Sie stattdessen Storage APIs als Single 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 im Laufe einer Browsersitzung eines Nutzers mehrmals zurückgesetzt werden.

Hintergrundskript für Manifest V2
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 für Manifest V3 die globale Variable durch einen Aufruf der Storage API.

Service Worker für Manifest V3
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 Alarme konvertieren

Verzögerte oder periodische Vorgänge werden häufig mit den Methoden setTimeout() oder setInterval() verwendet. Diese APIs können jedoch in Service Workern fehlschlagen, da die Timer beendet werden, wenn der Service Worker beendet wird.

Hintergrundskript für Manifest V2
// 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 Alarm-Listener auf der obersten Ebene Ihres Skripts registriert werden.

Service Worker für Manifest V3
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 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 Lebenszyklus von Service Workern. In Ausnahmefällen sind möglicherweise zusätzliche Maßnahmen erforderlich, um sicherzustellen, dass 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, wird der Service Worker möglicherweise während des Vorgangs beendet. 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 Service Workers in diesen Fällen zu verlängern, können Sie regelmäßig eine einfache Erweiterungs-API aufrufen, um den Timeout-Zähler zurückzusetzen. Beachten Sie, dass dies nur für Ausnahmefälle vorgesehen ist. In den meisten Situationen gibt es in der Regel eine bessere, plattformspezifische Möglichkeit, dasselbe Ergebnis zu erzielen.

Im folgenden Beispiel wird eine waitUntil()-Helferfunktion gezeigt, die den Service Worker 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 dauerhaft 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. Unter diesen außergewöhnlichen Umständen 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 diese Erweiterungen zu ergreifen.

Verwenden Sie das folgende Code-Snippet, um den 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 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'];
}