~/browser-erweiterungen/aufbau-content-scripts-service-worker-popup/

Content Scripts, Service Worker, Popup: Aufbau einer Browser-Erweiterung erklärt

Welche Teile eine Erweiterung hat, in welchem Kontext jeder läuft, was er darf und wie die Teile über Nachrichten und Speicher zusammenarbeiten.

Eine Browser-Erweiterung ist kein einzelnes Programm, sondern ein Verbund aus Teilen, die in getrennten Umgebungen laufen. Der Service Worker arbeitet im Hintergrund, Content Scripts laufen in Webseiten, Popup und Optionsseite sind kleine Webseiten der Erweiterung. Weil die Teile keine Variablen teilen, kommunizieren sie über Nachrichten und über den gemeinsamen Speicher chrome.storage. Wer diese Trennung verstanden hat, vermeidet die meisten Anfängerfehler.

Die Teile im Überblick

Teil Kontext Lebensdauer Zugriff
Service Worker eigener Hintergrundkontext ohne DOM startet bei Ereignissen, endet bei Inaktivität alle genehmigten APIs
Content Script isolierte Welt innerhalb einer Webseite solange die Seite geöffnet ist DOM der Seite, wenige APIs
Popup Erweiterungsseite am Symbol nur solange geöffnet alle genehmigten APIs, DOM
Optionsseite Erweiterungsseite im Tab oder Dialog solange geöffnet alle genehmigten APIs, DOM
Seitenleiste Erweiterungsseite neben der Webseite solange geöffnet alle genehmigten APIs, DOM
Offscreen-Dokument unsichtbare Erweiterungsseite vom Service Worker gesteuert DOM, nur runtime für Nachrichten

Zusammengehalten wird alles durch die manifest.json, die jeden Teil registriert. Ein vollständiges Beispiel mit Popup und Service Worker finden Sie in der Anleitung Chrome-Erweiterung programmieren.

Der Service Worker: Ereignisse statt Dauerbetrieb

Der Service Worker ist das Gedächtnis und die Schaltzentrale der Erweiterung, allerdings eines mit Aussetzern. Chrome startet ihn, wenn ein Ereignis eintritt, etwa ein Klick auf das Symbol, eine eingehende Nachricht, ein Alarm oder ein Tab-Wechsel. Nach kurzer Inaktivität wird er beendet. Daraus ergeben sich feste Regeln:

  • Listener werden beim Start synchron auf oberster Ebene registriert.
  • Zustand liegt in chrome.storage.local (dauerhaft) oder chrome.storage.session (bis zum Browser-Neustart, nur im Arbeitsspeicher).
  • Zeitgesteuerte Aufgaben laufen über chrome.alarms, nicht über setInterval.
  • Ein Service Worker hat kein DOM. Für DOM-Aufgaben erzeugt er bei Bedarf ein Offscreen-Dokument.

Firefox nutzt unter Manifest V3 an dieser Stelle eine Ereignisseite mit DOM. Das Prinzip ist gleich: Auch sie wird bei Inaktivität entladen.

Content Scripts: Arbeiten in fremden Seiten

Content Scripts sind die einzigen Teile, die direkt mit Webseiten arbeiten. Sie werden entweder fest im Manifest für bestimmte Adressmuster eingetragen oder bei Bedarf per chrome.scripting.executeScript eingeschleust.

Content Scripts laufen in einer isolierten Welt. Sie sehen dasselbe DOM wie die Seite, aber nicht deren JavaScript-Variablen, und die Seite sieht umgekehrt nicht die Variablen des Content Scripts. Das schützt beide Seiten voreinander. Wer gezielt mit dem JavaScript der Seite interagieren muss, kann ein Skript mit world: "MAIN" ausführen. Dieses Skript verliert dann aber den Zugriff auf die Erweiterungs-APIs und ist für die Webseite sichtbar und manipulierbar.

Von den Erweiterungs-APIs stehen Content Scripts nur wenige zur Verfügung, vor allem chrome.runtime für Nachrichten, chrome.storage und chrome.i18n. Alles andere, etwa Tabs verwalten oder Downloads starten, erledigen sie über eine Nachricht an den Service Worker.

Diese drei sind gewöhnliche HTML-Seiten, die aus dem Erweiterungspaket geladen werden. Sie haben ein DOM und Zugriff auf alle APIs, für die die Erweiterung Berechtigungen hat. Zwei Eigenheiten sollten Sie kennen:

  • Das Popup ist flüchtig. Es wird bei jedem Öffnen neu geladen und beim Schließen komplett verworfen. Langlaufende Aufgaben gehören deshalb in den Service Worker.
  • Keine Inline-Skripte. Die Content Security Policy für Erweiterungsseiten verbietet <script>-Blöcke im HTML und onclick-Attribute. Skripte werden als Dateien eingebunden.

Die Seitenleiste (Side Panel) bleibt dagegen geöffnet, während Nutzer zwischen Seiten wechseln, und eignet sich für Werkzeuge, die neben dem Inhalt sichtbar sein sollen.

Messaging: wie die Teile miteinander sprechen

Einmalige Nachrichten

Für einzelne Anfragen mit Antwort gibt es chrome.runtime.sendMessage. Ein Content Script schickt damit eine Nachricht an den Service Worker:

// content.js
async function auswahlSenden() {
  const antwort = await chrome.runtime.sendMessage({
    typ: "markieren",
    text: window.getSelection().toString(),
  });
  console.log(antwort.gespeichert);
}
document.addEventListener("mouseup", auswahlSenden);
// background.js
chrome.runtime.onMessage.addListener((nachricht, absender, antworten) => {
  if (nachricht.typ !== "markieren") return;
  chrome.storage.local
    .set({ [absender.tab.url]: nachricht.text })
    .then(() => antworten({ gespeichert: true }));
  return true; // Antwort folgt asynchron
});

Das return true ist wichtig: Es signalisiert Chrome, dass die Antwort später kommt. Ohne diese Zeile wird der Kanal sofort geschlossen und der Absender erhält keine Antwort.

In die andere Richtung, vom Service Worker oder Popup zu einem Content Script, nutzen Sie chrome.tabs.sendMessage mit der ID des Tabs. Voraussetzung ist, dass im Tab bereits ein Content Script läuft, das zuhört.

Dauerhafte Verbindungen

Für viele Nachrichten hintereinander, etwa einen Fortschrittsbalken, ist ein Port effizienter. chrome.runtime.connect öffnet eine Verbindung, über die beide Seiten mit postMessage senden, bis eine Seite sie schließt. Ein offener Port hält den Service Worker in Chrome eine Zeit lang aktiv, ersetzt aber keine saubere Zustandsverwaltung.

Gemeinsamer Speicher als Kanal

Oft ist gar keine Nachricht nötig. Alle Teile können chrome.storage lesen und schreiben, und über chrome.storage.onChanged erfahren sie von Änderungen. Das ist der robusteste Weg, um Einstellungen zwischen Optionsseite, Popup und Content Scripts aktuell zu halten.

Von Nach Weg
Content Script Service Worker runtime.sendMessage
Service Worker oder Popup Content Script tabs.sendMessage
Popup Service Worker runtime.sendMessage
beliebig beliebig, auch später storage und storage.onChanged
viele Nachrichten in Folge Gegenstelle runtime.connect (Port)

Ein Ablauf von Anfang bis Ende

Wie die Teile zusammenspielen, zeigt ein typischer Fall: Eine Erweiterung soll markierten Text auf einer Webseite speichern und in einer Liste anzeigen.

  1. Das Content Script läuft auf der Webseite und bemerkt, dass Text markiert wurde. Es kann den Text lesen, aber keine Liste dauerhaft verwalten.
  2. Es schickt den Text per runtime.sendMessage an den Service Worker. War der Worker beendet, startet Chrome ihn für diese Nachricht neu.
  3. Der Service Worker prüft die Nachricht, ergänzt Datum und Adresse und legt den Eintrag in chrome.storage.local ab. Danach setzt er die Zahl der Einträge als Badge auf das Symbol.
  4. Öffnet die Nutzerin das Popup, liest es die Liste aus dem Speicher und zeigt sie an. Eine Nachricht an den Service Worker ist dafür nicht nötig.
  5. Ändert sie auf der Optionsseite die Sortierung, schreibt die Seite den neuen Wert in den Speicher. Ein geöffnetes Popup oder die Seitenleiste erfahren davon über storage.onChanged.

Das Muster lässt sich verallgemeinern: Content Scripts sammeln und zeigen an, der Service Worker entscheidet und speichert, die Oberflächen lesen aus dem Speicher. Wer sich an diese Arbeitsteilung hält, muss sich kaum Gedanken darüber machen, ob ein Teil gerade läuft.

Sicherheit in der Architektur

Content Scripts laufen in Seiten, die Sie nicht kontrollieren. Eine bösartige Seite kann das DOM so manipulieren, dass Ihr Content Script falsche Daten liest und weiterreicht. Behandeln Sie Nachrichten aus Content Scripts im Service Worker deshalb wie Eingaben von außen: prüfen, nicht blind ausführen. Ein paar Grundsätze:

  • Nur genau definierte Nachrichtentypen annehmen, alles andere ignorieren.
  • Niemals Code oder Adressen aus Nachrichten ungeprüft ausführen oder aufrufen.
  • Inhalte aus Webseiten mit textContent statt innerHTML in eigene Oberflächen übernehmen.
  • Berechtigungen und Adressmuster eng fassen. Jedes zusätzliche Recht vergrößert den Schaden, falls etwas schiefgeht.

Aus Nutzersicht sind es genau diese Rechte, die über das Risiko einer Erweiterung entscheiden. Welche davon heikel sind, beschreibt der Beitrag Welche Berechtigungen von Browser-Erweiterungen sind gefährlich?.

Häufige Missverständnisse

  • „Das Popup kann im Hintergrund weiterarbeiten.“ Nein, es endet beim Schließen.
  • „Content Scripts sehen die Variablen der Seite.“ Nur in der Hauptwelt, und dann ohne Erweiterungs-APIs.
  • „Der Service Worker läuft immer.“ Er läuft nur bei Bedarf.
  • „Nachrichten erreichen jeden Tab.“ tabs.sendMessage erreicht nur Tabs mit aktivem Content Script.

Begriffe wie isolierte Welt, Manifest oder Host-Berechtigung sind im Glossar kurz erklärt.

faq

Fragen und Antworten

Kann ein Content Script auf Variablen der Webseite zugreifen?
Standardmäßig nicht. Content Scripts laufen in einer isolierten Welt und teilen nur das DOM mit der Seite. Für den Zugriff auf Seitenvariablen kann ein Skript in der Hauptwelt (world MAIN) ausgeführt werden, hat dort dann aber keinen Zugriff auf die Erweiterungs-APIs.
Warum kommt meine Nachricht an das Popup nicht an?
Das Popup existiert nur, solange es geöffnet ist. Schließt es sich, gibt es keinen Empfänger mehr. Legen Sie Ergebnisse besser in chrome.storage ab, das Popup liest sie beim nächsten Öffnen.
Wie lange läuft der Service Worker einer Erweiterung?
Nur so lange, wie er Ereignisse verarbeitet. Nach kurzer Inaktivität beendet Chrome ihn und startet ihn beim nächsten Ereignis neu. Planen Sie deshalb jeden Ablauf so, dass er einen Neustart übersteht.
Welcher Teil darf welche APIs nutzen?
Service Worker, Popup und Optionsseite haben Zugriff auf alle APIs, für die die Erweiterung Berechtigungen hat. Content Scripts sehen nur einen kleinen Teil, vor allem runtime für Nachrichten, storage und i18n. Alles andere erledigen sie über eine Nachricht an den Service Worker.