Zu einem Service Worker migrieren

Hintergrund- oder Ereignisseiten durch einen Service Worker ersetzen

Ein Service Worker ersetzt die Hintergrund- oder Ereignisseite der Erweiterung, um dafür zu sorgen, dass Hintergrundcode nicht im Hauptthread angezeigt wird. Dadurch 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 bieten Hintergrundseiten eine Umgebung, die von anderen Fenstern oder Tabs unabhängig 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 im Allgemeinen finden Sie in der Anleitung Ereignisse mit Service Workern verarbeiten und im Abschnitt Informationen zu Erweiterungs-Service-Workern.

Unterschiede zwischen Hintergrund-Skripts und Erweiterungsdienst-Workern

In einigen Kontexten werden Erweiterungsdienst-Worker als „Hintergrundskripts“ bezeichnet. Obwohl Extension Service Worker im Hintergrund ausgeführt werden, ist es irreführend, sie als Hintergrundscripts zu nennen, da identische Funktionen angedeutet werden. die nachfolgend beschrieben werden.

Änderungen durch Hintergrundseiten

Für Service Worker gibt es eine Reihe von Unterschieden zu Hintergrundseiten.

  • Sie funktionieren außerhalb des Hauptthreads und stören den Inhalt der Erweiterung nicht.
  • Sie bieten besondere Funktionen wie das Abfangen von Abrufereignissen vom Ursprung der Erweiterung, z. B. von einem Symbolleisten-Pop-up.
  • Sie können über die Client-Benutzeroberfläche mit anderen Kontexten kommunizieren und interagieren.

Erforderliche Änderungen

Sie müssen einige Codeanpassungen vornehmen, um Unterschiede zwischen der Funktionsweise von Hintergrund-Skripts und Service Workern zu berücksichtigen. Die Angabe eines Service Workers in der Manifestdatei unterscheidet sich zunächst von der Angabe von Hintergrundskripts. Weitere Vorteile:

  • 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 nicht sichtbares Dokument verschieben.
  • Ereignis-Listener sollten nicht als Reaktion auf zurückgegebene Promise oder innerhalb von Ereignis-Callbacks registriert werden.
  • Da sie nicht mit XMLHttpRequest() abwärtskompatibel sind, müssen Sie Aufrufe an diese Oberfläche durch Aufrufe von fetch() ersetzen.
  • Da sie bei Nichtgebrauch beendet werden, müssen Sie Anwendungsstatus beibehalten, anstatt globale Variablen zu verwenden. Durch das Beenden von Service Workern können Timer auch beendet werden, bevor sie abgeschlossen sind. Du musst sie durch einen Wecker ersetzen.

Auf dieser Seite werden diese Aufgaben ausführlich beschrieben.

Das Feld „Hintergrund“ 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 manifest.json. Beachten Sie, dass das Feld "service_worker" einen String und kein String-Array verwendet.
  • "background.persistent" aus manifest.json entfernen.
Manifest V2
{
  ...
  "background": {
    "scripts": [
      "backgroundContextMenus.js",
      "backgroundOauth.js"
    ],
    "persistent": false
  },
  ...
}
Manifest V3
{
  ...
  "background": {
    "service_worker": "service_worker.js",
    "type": "module"
  }
  ...
}

Im Feld "service_worker" wird ein einzelner String verwendet. Das Feld "type" ist nur erforderlich, wenn Sie ES-Module (mit dem Schlüsselwort import) verwenden. Der Wert ist immer "module". Weitere Informationen finden Sie unter Extension Service Worker – Grundlagen.

DOM- und Fensteraufrufe in Dokumente außerhalb des Bildschirms verschieben

Einige Erweiterungen benötigen Zugriff auf das DOM und die Fensterobjekte, ohne ein neues Fenster oder einen neuen Tab zu öffnen. Die Offscreen API unterstützt diese Anwendungsfälle, indem sie nicht angezeigte Dokumente, die mit einer Erweiterung verpackt sind, öffnet und schließt, ohne die Nutzererfahrung zu beeinträchtigen. Außer für die Nachrichtenweitergabe geben nicht sichtbare Dokumente keine APIs für andere Erweiterungskontexte frei, sondern dienen als vollständige Webseiten, mit denen Erweiterungen interagieren können.

Wenn Sie die Offscreen API verwenden möchten, erstellen Sie mithilfe des Service Workers ein nicht sichtbares Dokument.

chrome.offscreen.createDocument({
  url: chrome.runtime.getURL('offscreen.html'),
  reasons: ['CLIPBOARD'],
  justification: 'testing the offscreen API',
});

Führen Sie im nicht sichtbaren Dokument alle Aktionen aus, die Sie zuvor in einem Hintergrundskript ausgeführt hätten. Sie können beispielsweise den auf der Hostseite ausgewählten Text kopieren.

let textEl = document.querySelector('#text');
textEl.value = data;
textEl.select();
document.execCommand('copy');

Mithilfe der Nachrichtenweitergabe können Sie zwischen nicht sichtbaren Dokumenten und Mitarbeitern des Erweiterungsdienstes kommunizieren.

localStorage in einen anderen Typ konvertieren

Die Schnittstelle Storage der Webplattform (Zugriff über window.localStorage) kann nicht in einem Service Worker verwendet werden. Sie haben zwei Möglichkeiten, das Problem zu beheben. Zunächst können Sie sie durch Aufrufe eines anderen Speichermechanismus ersetzen. Der chrome.storage.local-Namespace wird für die meisten Anwendungsfälle verwendet, es sind aber weitere Optionen verfügbar.

Sie können die Anrufe auch in ein offenes Dokument verschieben. So migrieren Sie beispielsweise zuvor in localStorage gespeicherte Daten zu einem anderen Mechanismus:

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

Es gibt auch einige Besonderheiten 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. innerhalb eines Promise oder Callbacks) funktioniert in Manifest V3 nicht zwangsläufig. Betrachten Sie den folgenden Code.

chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
  chrome.browserAction.setBadgeText({ text: badgeText });
  chrome.browserAction.onClicked.addListener(handleActionClick);
});

Dies funktioniert mit einer dauerhaften 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 nicht registriert werden, wenn das Ereignis ausgelöst wird, da sie asynchron hinzugefügt werden, und das Ereignis wird nicht erfasst.

Verschieben Sie stattdessen die Ereignis-Listener-Registrierung auf die oberste Ebene Ihres Skripts. So kann Chrome den Klick-Handler Ihrer Aktion sofort finden und aufrufen, auch wenn die Startlogik der 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 globalen Abruf() ersetzen

XMLHttpRequest() kann nicht von einem Service Worker, einer Erweiterung oder anderweitig aufgerufen werden. Ersetzen Sie Aufrufe von Ihrem Hintergrundskript an XMLHttpRequest() durch Aufrufe des globalen 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 sitzungsspezifisch. Das heißt, sie werden während der Browsersitzung eines Nutzers wahrscheinlich wiederholt gestartet, ausgeführt und beendet. Es bedeutet auch, dass Daten in globalen Variablen nicht sofort verfügbar sind, seit der vorherige Kontext entfernt wurde. Verwenden Sie Storage APIs, um dieses Problem zu umgehen. Ein Beispiel zeigt, wie dies funktioniert.

Im folgenden Beispiel wird eine globale Variable zum Speichern eines Namens verwendet. In einem Service Worker kann diese Variable während der Browsersitzung eines Nutzers mehrmals zurückgesetzt werden.

Hintergrundskript von 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 bei Manifest V3 die globale Variable durch einen Aufruf der Storage API.

Manifest V3 Service Worker
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

Häufig werden verzögerte oder regelmäßige Vorgänge mit den Methoden setTimeout() oder setInterval() verwendet. Diese APIs können jedoch in Service-Workern fehlschlagen, da die Timer bei jeder Beendigung des Service Workers abgebrochen werden.

Hintergrundskript von 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 auch bei anderen Listenern sollten Alarm-Listener auf der obersten Ebene Ihres Skripts registriert werden.

Manifest V3 Service Worker
async function startAlarm(name, duration) {
  await chrome.alarms.create(name, { delayInMinutes: 3 });
}

chrome.alarms.onAlarm.addListener(() => {
  chrome.action.setIcon({
    path: getRandomIconPath(),
  });
});

Service Worker offen halten

Service Worker sind Definition ereignisgesteuert und werden bei Inaktivität beendet. So kann Chrome die Leistung und den Arbeitsspeicherverbrauch der Erweiterung optimieren. Weitere Informationen finden Sie in der Dokumentation zum Service Worker-Lebenszyklus. In Ausnahmefällen sind möglicherweise zusätzliche Maßnahmen erforderlich, um sicherzustellen, dass ein Service Worker länger aktiv bleibt.

Service Worker bis zum Abschluss eines lang andauernden Vorgangs aktiv halten

Bei lange laufenden Service Worker-Vorgängen, die keine Erweiterungs-APIs aufrufen, 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 potenziell schlechter Verbindung)
  • Eine komplexe asynchrone Berechnung, die mehr als 30 Sekunden dauert.

Wenn Sie die Service Worker-Lebensdauer in diesen Fällen verlängern möchten, können Sie in regelmäßigen Abständen eine einfache Erweiterungs-API aufrufen, um den Zeitüberschreitungszähler zurückzusetzen. Dies ist jedoch nur in Ausnahmefällen möglich. In den meisten Fällen gibt es normalerweise eine bessere, plattform-idiomatische Methode, um dasselbe Ergebnis zu erzielen.

Das folgende Beispiel zeigt eine waitUntil()-Hilfsfunktion, die Ihren 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 kontinuierlich am Leben halten

In seltenen Fällen muss die Lebensdauer auf unbestimmte Zeit verlängert werden. Wir haben festgestellt, dass Unternehmen und Bildungseinrichtungen die wichtigsten Anwendungsfälle darstellen, und wir lassen dies dort ausdrücklich zu, unterstützen dies jedoch im Allgemeinen nicht. Unter diesen außergewöhnlichen Umständen kann ein Service Worker durch regelmäßiges Aufrufen einer einfachen Erweiterungs-API am Leben gehalten werden. Beachten Sie, dass diese Empfehlung nur für Erweiterungen gilt, die auf verwalteten Geräten für Anwendungsfälle in 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, in Zukunft Maßnahmen gegen diese 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 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'];
}