Ereignisse mit Service Workern verarbeiten

Tutorial zu Konzepten für Erweiterungs-Service-Worker

Übersicht

In dieser Anleitung erhalten Sie eine Einführung in Chrome-Erweiterungs-Service-Worker. In diesem Tutorial erstellen Sie eine Erweiterung, mit der Nutzer über die Omnibox schnell zu Chrome API-Referenzseiten navigieren können. Nach Abschluss können Sie:

  • Registrieren Sie Ihren Service Worker und importieren Sie Module.
  • Fehler im Service Worker Ihrer Erweiterung beheben
  • Status verwalten und Ereignisse verarbeiten
  • Regelmäßige Ereignisse auslösen
  • Mit Inhaltsskripten kommunizieren.

Vorbereitung

In dieser Anleitung wird davon ausgegangen, dass Sie grundlegende Kenntnisse in der Webentwicklung haben. Wir empfehlen Ihnen, sich die Erweiterungen 101 und Hello World anzusehen, um eine Einführung in die Entwicklung von Erweiterungen zu erhalten.

Erweiterung erstellen

Erstellen Sie zuerst ein neues Verzeichnis mit dem Namen quick-api-reference für die Erweiterungsdateien oder laden Sie den Quellcode aus unserem GitHub-Repository mit Beispielen herunter.

Schritt 1: Service Worker registrieren

Erstellen Sie die Manifestdatei im Stammverzeichnis des Projekts und fügen Sie den folgenden Code hinzu:

manifest.json:

{
  "manifest_version": 3,
  "name": "Open extension API reference",
  "version": "1.0.0",
  "icons": {
    "16": "images/icon-16.png",
    "128": "images/icon-128.png"
  },
  "background": {
    "service_worker": "service-worker.js"
  }
}

Erweiterungen registrieren ihren Service Worker im Manifest, das nur eine einzelne JavaScript-Datei benötigt. Sie müssen navigator.serviceWorker.register() nicht aufrufen, wie es bei einer Webseite der Fall wäre.

Erstellen Sie einen images-Ordner und laden Sie die Symbole in diesen Ordner herunter.

In den ersten Schritten des Tutorials zur Lesezeit erfahren Sie mehr über die Metadaten und Symbole der Erweiterung im Manifest.

Schritt 2: Mehrere Service Worker-Module importieren

Unser Service Worker implementiert zwei Funktionen. Zur besseren Wartbarkeit implementieren wir jede Funktion in einem separaten Modul. Zuerst müssen wir den Service Worker in unserem Manifest als ES-Modul deklarieren. So können wir Module in unseren Service Worker importieren:

manifest.json:

{
 "background": {
    "service_worker": "service-worker.js",
    "type": "module"
  },
}

Erstellen Sie die Datei service-worker.js und importieren Sie zwei Module:

import './sw-omnibox.js';
import './sw-tips.js';

Erstellen Sie diese Dateien und fügen Sie jeder Datei einen Konsolenlog hinzu.

sw-omnibox.js:

console.log("sw-omnibox.js");

sw-tips.js:

console.log("sw-tips.js");

Unter Scripts importieren finden Sie weitere Informationen zum Importieren mehrerer Dateien in einem Service Worker.

Optional: Service Worker debuggen

Ich erkläre Ihnen, wie Sie die Service Worker-Logs finden und wann der Service Worker beendet wurde. Folgen Sie zuerst der Anleitung zum Laden einer entpackten Erweiterung.

Nach 30 Sekunden wird „service worker (inactive)“ angezeigt. Das bedeutet, dass der Service Worker beendet wurde. Klicken Sie auf den Link „Service Worker (inaktiv)“, um ihn zu untersuchen. Das wird in der folgenden Animation veranschaulicht.

Haben Sie bemerkt, dass der Service Worker durch die Untersuchung aktiviert wurde? Wenn Sie den Service Worker in den Entwicklertools öffnen, bleibt er aktiv. Damit Ihre Erweiterung sich korrekt verhält, wenn Ihr Service Worker beendet wird, müssen Sie die Entwicklertools schließen.

Nun müssen Sie die Erweiterung aufteilen, um herauszufinden, wo sich die Fehler befinden. Eine Möglichkeit dazu ist, „.js“ aus dem './sw-omnibox.js'-Import in der Datei service-worker.js zu löschen. Chrome kann den Service Worker nicht registrieren.

Kehren Sie zu chrome://extensions zurück und aktualisieren Sie die Erweiterung. Es werden zwei Fehler angezeigt:

Service worker registration failed. Status code: 3.

An unknown error occurred when fetching the script.

Weitere Möglichkeiten zum Debuggen des Service Workers der Erweiterung finden Sie unter Erweiterungen debuggen.

Schritt 4: Status initialisieren

Chrome beendet Service Worker, wenn sie nicht benötigt werden. Wir verwenden die browser.storage API, um den Status über Service Worker-Sitzungen hinweg beizubehalten. Für den Speicherzugriff müssen wir die Berechtigung im Manifest anfordern:

manifest.json:

{
  ...
  "permissions": ["storage"],
}

Speichern Sie zuerst die Standardvorschläge. Wir können den Status initialisieren, wenn die Erweiterung zum ersten Mal installiert wird, indem wir auf das Ereignis runtime.onInstalled() warten:

sw-omnibox.js:

...
// Save default API suggestions
browser.runtime.onInstalled.addListener(({ reason }) => {
  if (reason === 'install') {
    browser.storage.local.set({
      apiSuggestions: ['tabs', 'storage', 'scripting']
    });
  }
});

Service Worker haben keinen direkten Zugriff auf das window-Objekt und können daher nicht window.localStorage verwenden, um Werte zu speichern. Außerdem sind Service Worker kurzlebige Ausführungsumgebungen. Sie werden während der Browsersitzung eines Nutzers wiederholt beendet, was sie mit globalen Variablen inkompatibel macht. Verwenden Sie stattdessen browser.storage.local, um Daten auf dem lokalen Computer zu speichern.

Unter Daten beibehalten, anstatt globale Variablen zu verwenden finden Sie weitere Speicheroptionen für Service Worker von Erweiterungen.

Schritt 5: Ereignisse registrieren

Alle Event-Listener müssen statisch im globalen Bereich des Service Workers registriert werden. Mit anderen Worten: Event-Listener sollten nicht in asynchronen Funktionen verschachtelt werden. So kann Chrome dafür sorgen, dass alle Event-Handler im Falle eines Neustarts des Service Workers wiederhergestellt werden.

In diesem Beispiel verwenden wir die browser.omnibox API. Zuerst müssen wir jedoch den Omnibox-Schlüsselwort-Trigger im Manifest deklarieren:

manifest.json:

{
  ...
  "minimum_chrome_version": "102",
  "omnibox": {
    "keyword": "api"
  },
}

Registrieren Sie die Omnibox-Ereignis-Listener jetzt auf der obersten Ebene des Scripts. Wenn der Nutzer das Omnibox-Keyword (api) in die Adressleiste eingibt und dann die Tabulatortaste oder die Leertaste drückt, zeigt Chrome eine Liste mit Vorschlägen an, die auf den Keywords im Speicher basieren. Das Ereignis onInputChanged(), das die aktuelle Nutzereingabe und ein suggestResult-Objekt verwendet, ist für das Einblenden dieser Vorschläge verantwortlich.

sw-omnibox.js:

...
const URL_CHROME_EXTENSIONS_DOC =
  'https://developer.chrome.com/docs/extensions/reference/';
const NUMBER_OF_PREVIOUS_SEARCHES = 4;

// Display the suggestions after user starts typing
browser.omnibox.onInputChanged.addListener(async (input, suggest) => {
  await browser.omnibox.setDefaultSuggestion({
    description: 'Enter a Chrome API or choose from past searches'
  });
  const { apiSuggestions } = await browser.storage.local.get('apiSuggestions');
  const suggestions = apiSuggestions.map((api) => {
    return { content: api, description: `Open browser.${api} API` };
  });
  suggest(suggestions);
});

Nachdem der Nutzer einen Vorschlag ausgewählt hat, wird mit onInputEntered() die entsprechende Chrome-API-Referenzseite geöffnet.

sw-omnibox.js:

...
// Open the reference page of the chosen API
browser.omnibox.onInputEntered.addListener((input) => {
  browser.tabs.create({ url: URL_CHROME_EXTENSIONS_DOC + input });
  // Save the latest keyword
  updateHistory(input);
});

Die Funktion updateHistory() übernimmt die Omnibox-Eingabe und speichert sie in storage.local. So kann der letzte Suchbegriff später als Omnibox-Vorschlag verwendet werden.

sw-omnibox.js:

...
async function updateHistory(input) {
  const { apiSuggestions } = await browser.storage.local.get('apiSuggestions');
  apiSuggestions.unshift(input);
  apiSuggestions.splice(NUMBER_OF_PREVIOUS_SEARCHES);
  return browser.storage.local.set({ apiSuggestions });
}

Schritt 6: Wiederkehrenden Termin einrichten

Die Methoden setTimeout() oder setInterval() werden häufig verwendet, um verzögerte oder regelmäßige Aufgaben auszuführen. Diese APIs können jedoch fehlschlagen, da der Scheduler die Timer abbricht, wenn der Service Worker beendet wird. Stattdessen können Erweiterungen die browser.alarms API verwenden.

Fordern Sie zuerst die Berechtigung "alarms" im Manifest an:

manifest.json:

{
  ...
  "permissions": ["storage"],
  "permissions": ["storage", "alarms"],
}

Die Erweiterung ruft alle Tipps ab, wählt einen zufällig aus und speichert ihn. Wir erstellen einen Alarm, der einmal täglich ausgelöst wird, um den Tipp zu aktualisieren. Alarme werden nicht gespeichert, wenn Sie Chrome schließen. Wir müssen also prüfen, ob der Wecker vorhanden ist, und ihn gegebenenfalls erstellen.

sw-tips.js:

// Fetch tip & save in storage
const updateTip = async () => {
  const response = await fetch('https://chrome.dev/f/extension_tips/');
  const tips = await response.json();
  const randomIndex = Math.floor(Math.random() * tips.length);
  return browser.storage.local.set({ tip: tips[randomIndex] });
};

const ALARM_NAME = 'tip';

// Check if alarm exists to avoid resetting the timer.
// The alarm might be removed when the browser session restarts.
async function createAlarm() {
  const alarm = await browser.alarms.get(ALARM_NAME);
  if (typeof alarm === 'undefined') {
    browser.alarms.create(ALARM_NAME, {
      delayInMinutes: 1,
      periodInMinutes: 1440
    });
    updateTip();
  }
}

createAlarm();

// Update tip once a day
browser.alarms.onAlarm.addListener(updateTip);

Schritt 7: Mit anderen Kontexten kommunizieren

Erweiterungen verwenden Content-Scripts, um den Inhalt der Seite zu lesen und zu ändern. Wenn ein Nutzer eine Chrome-API-Referenzseite aufruft, wird die Seite durch das Content-Script der Erweiterung mit dem Tipp des Tages aktualisiert. Es sendet eine Nachricht, um den Tipp des Tages vom Service Worker anzufordern.

Deklarieren Sie zuerst das Content-Script im Manifest und fügen Sie das entsprechende Muster für den Abgleich gemäß der Chrome-API-Referenzdokumentation hinzu.

manifest.json:

{
  ...
  "content_scripts": [
    {
      "matches": ["https://developer.chrome.com/docs/extensions/reference/*"],
      "js": ["content.js"]
    }
  ]
}

Erstellen Sie eine neue Inhaltsdatei. Mit dem folgenden Code wird eine Nachricht an den Service Worker gesendet, in der der Tipp angefordert wird. Anschließend wird ein Button hinzugefügt, über den ein Pop-over mit dem Tipp zur Erweiterung geöffnet wird. In diesem Code wird die neue Popover API der Webplattform verwendet.

content.js:

(async () => {
  // Sends a message to the service worker and receives a tip in response
  const { tip } = await browser.runtime.sendMessage({ greeting: 'tip' });

  const nav = document.querySelector('.upper-tabs > nav');
  
  const tipWidget = createDomElement(`
    <button type="button" popovertarget="tip-popover" popovertargetaction="show" style="padding: 0 12px; height: 36px;">
      <span style="display: block; font: var(--devsite-link-font,500 14px/20px var(--devsite-primary-font-family));">Tip</span>
    </button>
  `);

  const popover = createDomElement(
    `<div id='tip-popover' popover style="margin: auto;">${tip}</div>`
  );

  document.body.append(popover);
  nav.append(tipWidget);
})();

function createDomElement(html) {
  const dom = new DOMParser().parseFromString(html, 'text/html');
  return dom.body.firstElementChild;
}

Im letzten Schritt fügen wir unserem Service Worker einen Nachrichtentyp hinzu, der eine Antwort mit dem täglichen Tipp an das Content-Script sendet.

sw-tips.js:

...
// Send tip to content script via messaging
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.greeting === 'tip') {
    browser.storage.local.get('tip').then(sendResponse);
    return true;
  }
});

Funktionsweise testen

Prüfen Sie, ob die Dateistruktur Ihres Projekts so aussieht:

Der Inhalt des Erweiterungsordners: „images“-Ordner, „manifest.json“, „service-worker.js“, „sw-omnibox.js“, „sw-tips.js“ und „content.js“

Erweiterung lokal laden

Wenn Sie eine entpackte Erweiterung im Entwicklermodus laden möchten, folgen Sie der Anleitung unter Hello World.

Referenzseite öffnen

  1. Geben Sie das Keyword „api“ in die Adressleiste des Browsers ein.
  2. Drücken Sie die Tabulatortaste oder die Leertaste.
  3. Geben Sie den vollständigen Namen der API ein.
    • ODER wählen Sie aus einer Liste mit früheren Suchanfragen aus.
  4. Eine neue Seite mit der Chrome API-Referenz wird geöffnet.

Das sollte so aussehen:

Schnelle API-Referenz zum Öffnen der Runtime API-Referenz
Schnelle API-Erweiterung zum Öffnen der Runtime API.

Tipp des Tages öffnen

Klicken Sie in der Navigationsleiste auf die Schaltfläche „Tipp“, um den Erweiterungstipp zu öffnen.

Täglichen Tipp öffnen in
Schnelle API-Erweiterung, die den Tipp des Tages öffnet.

🎯 Mögliche Verbesserungen

Versuchen Sie, auf Grundlage des heutigen Lerninhalts Folgendes zu erreichen:

  • Eine andere Möglichkeit zur Implementierung der Omnibox-Vorschläge
  • Erstellen Sie ein eigenes benutzerdefiniertes Modal, um den Erweiterungstipp anzuzeigen.
  • Öffnet eine zusätzliche Seite mit den MDN-Referenz-API-Seiten für Web Extensions.

Mach weiter so!

Herzlichen Glückwunsch zum Abschluss dieser Anleitung 🎉. Sie können Ihre Kenntnisse weiter ausbauen, indem Sie andere Anleitungen für Anfänger durcharbeiten:

Erweiterung Lerninhalte
Lesezeit Ein Element automatisch auf einer bestimmten Gruppe von Seiten einfügen
Tabs Manager So erstellen Sie ein Pop-up, mit dem Browser-Tabs verwaltet werden.
Konzentrationsmodus Code auf der aktuellen Seite ausführen, nachdem auf die Erweiterungsaktion geklickt wurde.

Weiter erkunden

Wenn Sie Ihren Lernpfad für Service Worker für Erweiterungen fortsetzen möchten, empfehlen wir Ihnen die folgenden Artikel: