Auf dem Papier sollte eine moderne Browser-Erweiterung beinahe automatisch ein plattformübergreifendes Produkt sein. Chrome, Firefox, Edge und Safari unterstützen WebExtensions, verwenden eine ähnliche manifest.json und stellen vergleichbare Mechanismen bereit: Content Scripts, Storage, Kommunikation zwischen Kontexten, eine Schaltfläche in der Symbolleiste und Einstellungsseiten.
Das stimmt, aber nur bis zu einem gewissen Punkt.
Nach der Veröffentlichung und Portierung von neun POLPROG-Erweiterungen wissen wir: Gemeinsamer Code bedeutet nicht automatisch ein gemeinsames Produkt. Der Großteil der Geschäftslogik lässt sich teilen, doch Browserverhalten, Lebenszyklus des Hintergrundprozesses, Benutzeroberfläche, Berechtigungen, Paketierung und Store-Prüfung bleiben eigenständige Probleme.
Zum Portfolio gehören:
- AutoScroll,
- ClickClean,
- DevScope,
- LeakLens,
- LoopIt,
- Loudly,
- OneMoreJump,
- TabZoo,
- TubePilot.
Es handelt sich nicht um neun Kopien desselben Add-on-Typs. Die Gruppe umfasst Werkzeuge für Video, Audioverstärkung, Datenschutz, Datenbereinigung, Entwicklung, Tab-Verwaltung sowie ein leichtgewichtiges Spiel. Entsprechend beschränkten sich die Probleme nicht auf eine einzelne API. Sie betrafen Service Worker, Pop-ups, Content Scripts, Audio, Storage, Icons, Berechtigungen und den Veröffentlichungsprozess.
POLPROG betreut öffentlich neun Erweiterungsprodukte, die zunächst vor allem für Chrome und Firefox veröffentlicht wurden. Spätere Portierungen umfassten auch Edge und Safari. [1][2]
Dieser Artikel ist kein Labor-Benchmark. Wir haben nicht ab dem ersten Commit jede Minute pro Store gemessen. Deshalb behaupten wir weder, dass Safari exakt 2,7-mal so teuer wie Chrome sei, noch dass 94 % des Codes gemeinsam genutzt würden. Das wäre nur scheinbare Präzision.
Wir können jedoch etwas Nützlicheres zeigen: Welche Annahmen sich als falsch erwiesen haben, wo reale Fehler auftraten und wie wir den gesamten Prozess heute von Grund auf aufbauen würden.
Quellen zuletzt geprüft: 17. Juli 2026.
Vier Browser, vier unterschiedliche Rollen
| Plattform | Rolle in unserem Prozess | Größter Vorteil | Häufigste Falle |
|---|---|---|---|
| Chrome | Hauptreferenz für Manifest-V3-Kompatibilität | Umfangreichste Dokumentation und größter Erweiterungsmarkt | Suspendierter Service Worker und strenge Berechtigungsanforderungen |
| Edge | Chromium-basierte Portierung | Geringe Codeabweichung gegenüber Chrome | Eigenes Partner Center, eigene Metadaten und Zertifizierung |
| Firefox | Unabhängige WebExtensions-Implementierung | Deckt Chromium-abhängige Annahmen sehr gut auf | Unterschiede bei API und Manifest, Signierung und Quellcodeanforderungen |
| Safari | Eigenständiges Produkt im Apple-Ökosystem | Zugang zu Nutzern von macOS, iOS und iPadOS | Xcode, Container-App, Signierung und anderes Laufzeitverhalten |
MDN betont, dass WebExtensions Add-ons nach möglichst kleinen Änderungen in den wichtigsten Browsern lauffähig machen soll. Gleichzeitig dokumentiert MDN wichtige Unterschiede zwischen Chrome, Firefox und Safari bei API-Verfügbarkeit, Manifesten und asynchronen Operationen. [7][8]
Das entspricht unserer Erfahrung. „Kleine Änderungen“ sind bei einem einfachen Add-on realistisch. Je stärker eine Erweiterung mit Seite, Medien, Tab-Lebenszyklus oder Hintergrundprozess interagiert, desto weniger hilfreich wird das Wort „klein“.
Lektion 1: Eine Codebasis ja, ein Manifest nicht unbedingt
Der beste Ausgangspunkt ist gemeinsame Logik mit getrennten Build-Konfigurationen.
In der Praxis hat sich eine Struktur bewährt, in der Folgendes gemeinsam genutzt wird:
- Funktionslogik,
- Datenmodelle,
- Storage-Module,
- Validierung,
- der Großteil von Pop-up- und Einstellungsoberfläche,
- Lokalisierungen,
- Unit-Tests.
Getrennt bleiben dagegen:
- Manifest-Varianten,
- Erweiterungskennungen,
- minimale Browserversionen,
- Hintergrundkonfiguration,
- ein Teil der Berechtigungen,
- Icons und Store-Assets,
- Paketierung,
- Signierung,
- Veröffentlichungsskripte.
Das bedeutet nicht, vier manuell kopierte Verzeichnisse zu pflegen. Das führt schnell zu Abweichungen. Besser ist gemeinsamer Quellcode plus Generator oder Konfigurationsschicht, die für jede Plattform ein eigenes Artefakt erzeugt.
Firefox benötigt beispielsweise eigene Einstellungen unter browser_specific_settings. Bei Manifest V3 ist eine Erweiterungs-ID für die Signierung notwendig. Seit November 2025 müssen neue bei AMO eingereichte Add-ons zudem Angaben zur Datenerfassung deklarieren. [9]
Safari benötigt dagegen ein Xcode-Projekt und eine App, die die Erweiterung enthält. Apple stellt einen WebExtension-Konverter bereit, doch das Ergebnis muss weiterhin wie ein natives Projekt behandelt werden, das signiert und über den App Store verteilt wird. [3][4]
Ein gemeinsames Manifest wäre daher eine künstliche Ersparnis. Einige kleine, explizite Unterschiede sind leichter zu pflegen als eine einzige Datei voller Ausnahmen und Bedingungen.
Lektion 2: Manifest V3 hat das Browserverhalten nicht vereinheitlicht
Manifest V3 hat einen Teil der Kompatibilität verbessert und das Erweiterungsmodell geordnet, aber keine einheitliche Laufzeit geschaffen.
Die sichtbarste Änderung in Chrome ist der Ersatz dauerhaft laufender Background Pages durch einen Extension Service Worker. Google beschreibt ihn als zentralen Ereignis-Handler, der bei Bedarf gestartet wird. Der Prozess kann beendet werden, und ausschließlich im Arbeitsspeicher gehaltene Variablen verschwinden mit ihm. [5][6]
Das hat direkte Konsequenzen.
Code wie:
let activeSessions = new Map();
let currentTab = null;
let isEnabled = false;
kann nicht die einzige Quelle der Wahrheit sein, wenn die Daten nach einem Neustart des Service Workers wieder benötigt werden.
Zustand sollte in drei Gruppen aufgeteilt werden:
- kurzfristiger Zustand, der sicher verschwinden darf;
- aus aktiven Tabs oder dem DOM rekonstruierbarer Zustand;
- dauerhafter Zustand, der in
storage.local,storage.sessionoder an einem anderen geeigneten Ort gespeichert werden muss.
Ebenso wichtig ist die Registrierung von Listenern. Chrome empfiehlt, Event Handler im globalen Bereich des Skripts zu deklarieren, damit sie beim Aufwecken des Service Workers synchron registriert werden. Ein Listener, der erst nach asynchroner Initialisierung entsteht, kann genau das Ereignis verpassen, das den Worker aufgeweckt hat. [10]
In Entwicklungstests ist das leicht zu übersehen. Geöffnete DevTools können den Lebenszyklus des Service Workers beeinflussen, während der Fehler erst bei normaler Nutzung auftritt. Google beschrieb ähnliche Probleme in einer Analyse von MV3-Migrationen: Das Suspendieren des Prozesses kann Timer unterbrechen und im Speicher gehaltenen Zustand entfernen. [11]
Die wichtigste Regel lautet daher:
Der Hintergrundprozess einer Erweiterung sollte wie ein unterbrechbarer Ereignis-Handler entworfen werden, nicht wie ein kleiner Node.js-Server, der permanent läuft.
Lektion 3: Chrome ist ein guter Startpunkt, aber ein schlechtes einziges Testumfeld
Chrome ist die natürliche Basis für ein Manifest-V3-Projekt. Es bietet umfangreiche Dokumentation, Beispiele, Entwicklerwerkzeuge und das größte Add-on-Ökosystem.
Gleichzeitig erzeugt ausschließliches Testen in Chrome ein falsches Sicherheitsgefühl.
Wenn Code in Chrome und Edge funktioniert, bestätigt das vor allem die Kompatibilität mit Chromium. Es bestätigt nicht:
- korrektes Verhalten in Gecko,
- korrektes Verhalten in WebKit,
- Robustheit gegenüber einem anderen Pop-up-Lebenszyklus,
- Funktion unter einem anderen Berechtigungsmodell,
- korrekte Icon-Skalierung,
- korrektes Audioverhalten,
- identische Semantik der API.
Der Chrome Web Store verlangt einen einzigen, engen und leicht verständlichen Zweck. Er verlangt zudem den minimalen Berechtigungssatz, eine Begründung für jede Berechtigung und konsistente Angaben zur Datennutzung. Zu weitreichende Berechtigungen können zu einer Ablehnung führen. In Manifest V3 darf außerdem kein entfernt gehosteter Code geladen und ausgeführt werden. [12]
Diese Anforderungen beeinflussen die Architektur früher, als man zunächst denkt. Zugriff auf alle Websites „auf Vorrat“ hinzuzufügen, ist nicht neutral. Er muss später gegenüber Prüfer und Nutzer begründet werden. Besser können activeTab, optionale Host-Berechtigungen oder auf konkrete Domains beschränkter Zugriff sein.
Chrome hat uns daher zwei gegensätzliche Dinge gelehrt:
- Es eignet sich gut, um die grundlegende MV3-Variante festzulegen.
- Es sollte nicht alle Annahmen der gemeinsamen Schicht bestimmen.
Lektion 4: Edge ist die einfachste technische Portierung, aber kein kostenloser Vertriebskanal
Microsoft beschreibt die Portierung einer vorhandenen Chrome-Erweiterung zu Edge offiziell als eigenen, unterstützten Ablauf. Da beide Browser Chromium verwenden, sind Unterschiede im Kerncode häufig gering. [13]
In unseren Projekten erforderte Edge meist die wenigsten technischen Änderungen. Das bedeutete trotzdem keine Veröffentlichung „mit einem Klick“.
Die Erweiterung gelangt in ein separates Partner Center. Dort muss man:
- ein eigenes ZIP-Paket hochladen,
- Verfügbarkeit und Märkte festlegen,
- eine Kategorie auswählen,
- den einzelnen Zweck beschreiben,
- jede Berechtigung begründen,
- die Nutzung entfernten Codes deklarieren,
- Datenpraktiken beschreiben,
- Listings für mehrere Sprachen vorbereiten,
- separate Grafiken und Screenshots hochladen,
- Hinweise für das Zertifizierungsteam hinzufügen. [14]
Wie Google verlangt auch Microsoft einen klar definierten Zweck und den kleinstmöglichen Berechtigungssatz. Partner Center weist außerdem darauf hin, dass unvollständige oder widersprüchliche Angaben die Zertifizierung verlängern oder zu einer Ablehnung führen können. [14]
Edge zeigt damit gut den Unterschied zwischen Portierungskosten und Kanalkosten.
Der Code kann nahezu identisch sein, dennoch müssen gepflegt werden:
- ein separater Produkteintrag,
- ein separater Prüfstatus,
- separate Metadaten,
- separate Grafikvarianten,
- ein eigener Aktualisierungsprozess,
- konsistente Datenschutzerklärungen.
Bei einer Erweiterung ist dieser Aufwand überschaubar. Bei neun Produkten wird daraus ein regelmäßiger operativer Prozess.
Lektion 5: Firefox erkennt am besten Code, der „versehentlich nur für Chrome“ geschrieben wurde
Firefox verwendet WebExtensions, ist aber kein Chromium. Genau deshalb ist er als Testumgebung so wertvoll.
Der bekannteste Unterschied betrifft Namespace und asynchronen Code. Chrome verwendete historisch chrome und Callbacks, Firefox und Safari dagegen browser und Promises. In Manifest V3 haben Chrome und Edge bei vielen Methoden Promise-Unterstützung ergänzt, aber nicht jede Differenz ist verschwunden. Mozilla empfiehlt, mit browser und Promises zu arbeiten und bei Bedarf das WebExtension Browser API Polyfill einzusetzen. [8][15]
Es lohnt sich nicht, Bedingungen wie:
if (isFirefox) {
// ...
} else {
// ...
}
über jedes API-Calling zu verteilen. Besser ist eine kleine Adapter-Schicht:
export const extensionApi = {
storageGet: (keys) => browser.storage.local.get(keys),
storageSet: (value) => browser.storage.local.set(value),
sendMessage: (message) => browser.runtime.sendMessage(message),
};
und die Unterschiede an einer Stelle zu lösen.
Firefox erzwingt außerdem mehr Disziplin bei der Veröffentlichung. Erweiterungen müssen signiert sein, selbst wenn sie in der Standardkonfiguration unabhängig verteilt werden. AMO validiert das Paket, und minifizierter oder schwer lesbarer Code kann die Einreichung des vollständigen Quellpakets erfordern. Mozilla kann auch nach der Veröffentlichung eine manuelle Prüfung durchführen und die aktuelle oder eine frühere Version ablehnen. [16][17][18]
Aus Qualitätssicht ist das ein Vorteil. Der Prozess zwingt dazu, dass der Build:
- reproduzierbar,
- dokumentiert,
- frei von unnötiger Obfuskation,
- mit dem deklarierten Verhalten konsistent,
- ohne internes Wissen des Autors prüfbar ist.
Firefox deckte außerdem Probleme an Stellen auf, an denen Chromium toleranter war. Dazu gehörten Nachrichtenverarbeitung, Warten auf Promise-Antworten, Unterschiede bei Pop-ups und Annahmen über das Vorhandensein einzelner API-Eigenschaften.
Die wichtigste Lektion:
Firefox ist kein zusätzlicher Haken nach Projektabschluss. Er ist eine zweite unabhängige Laufzeit, die früh in die Tests aufgenommen werden sollte.
Lektion 6: Safari ist keine Store-Portierung, sondern eine eigene Produktphase
Apple unterstützt WebExtensions und stellt ein Tool zur Konvertierung aus Chrome, Firefox oder Edge bereit. Trotzdem ist der Safari-Prozess grundlegend anders. [3][4]
Die Erweiterung wird Teil einer in Xcode erstellten Anwendung. Verwalten muss man:
- das App-Projekt,
- das Erweiterungs-Target,
- die Bundle-ID der App,
- eine eigene Bundle-ID der Erweiterung,
- Signierung,
- Profile und Zertifikate,
- das App-Icon,
- Entitlements,
- Archivierung,
- App Store Connect,
- App-Store-Prüfung.
Apple verlangt, dass die Erweiterung mit der aktuellen Safari-Version des jeweiligen Systems funktioniert, die Safari-Oberfläche nicht stört und nicht mehr Website-Zugriff anfordert als nötig. [19]
Hier traten die konkretesten Probleme unseres Portfolios auf.
Loudly: Die Oberfläche funktionierte, aber der Ton wurde nicht verstärkt
Loudly verändert den auf einer Webseite abgespielten Ton. In der Safari-Version ließ sich die Oberfläche anzeigen, Einstellungen konnten geändert werden und das Produkt wirkte funktionsfähig. Das tatsächliche Audiosignal wurde jedoch nicht verstärkt.
Das ist eine problematischere Fehlerklasse als ein offensichtlicher Absturz. Der Smoke Test „Pop-up öffnet sich“ besteht, aber das zentrale Produktversprechen wird nicht erfüllt.
Schlussfolgerung: Bei Multimedia-Erweiterungen muss der Funktionstest das Endergebnis messen, nicht nur den Zustand der Oberfläche.
TabZoo: Weiße Seite statt Anwendung
Die Safari-Version von TabZoo konnte eine leere weiße Seite anzeigen. Ursache können ein Initialisierungsfehler, Asset-Pfade, ein falscher Kontext oder eine Ausnahme vor dem Rendern sein.
In Chromium kann derselbe Fehler verborgen bleiben, wenn Startreihenfolge oder API-Verfügbarkeit anders sind.
Schlussfolgerung: Jede Ansicht einer Erweiterung sollte einen minimalen Diagnosemechanismus und Fehlerbehandlung für die Initialisierung besitzen. Eine leere Seite darf kein endgültiger Produktzustand sein.
LoopIt: Ein Klick öffnete zwei Oberflächen
In LoopIt führte ein Klick auf das Icon in Safari dazu, dass ein Pop-up und zusätzlich ein Erweiterungspanel erschienen. Das war ein Konflikt zwischen deklarativ konfiguriertem Pop-up und manueller Klickbehandlung.
In einem Browser wirkte das Verhalten korrekt, in einem anderen entstand eine doppelte Reaktion.
Schlussfolgerung: Es muss eindeutig festgelegt werden, wer den Klick auf die Schaltfläche besitzt. Entweder hat sie ein default_popup, oder das Ereignis wird programmatisch verarbeitet. Beide Mechanismen zu kombinieren, erfordert sehr bewusstes Design.
Diese drei Fälle zeigen, warum der Apple-Konverter die Portierung nicht abschließt. Der Konverter überträgt Struktur. Er bestätigt nicht die Semantik des Verhaltens.
Lektion 7: Video- und Audio-Erweiterungen sind schwieriger, als das Pop-up vermuten lässt
Vier unserer Produkte hängen stark von Medien ab:
- AutoScroll,
- LoopIt,
- Loudly,
- TubePilot.
Jedes muss mehrere Welten koordinieren:
- das DOM der Seite,
- das Audio- oder Videoelement,
- den Content Script,
- das Pop-up,
- den Hintergrundprozess,
- gespeicherte Einstellungen,
- SPA-Navigation,
- Wechsel des aktiven Tabs.
Die meisten Fehler entstehen nicht in einer einzelnen Funktion, sondern an den Grenzen zwischen diesen Kontexten.
Ein Beispielszenario:
- Der Nutzer öffnet das Pop-up.
- Das Pop-up fragt den Content Script nach dem aktuellen Zustand.
- Die Seite hat gerade das Video ohne vollständiges Neuladen gewechselt.
- Das alte Videoelement wurde entfernt.
- Der Content Script hält noch eine Referenz auf das vorherige Element.
- Das Pop-up zeigt einen korrekten Wert, verändert aber einen inaktiven Player.
Ein weiteres setTimeout ist keine Lösung. Nötig ist ein explizites Zustandsmodell:
- aktives Element identifizieren,
- DOM-Änderungen beobachten,
- Handler erneut verbinden,
- Ausführung des Befehls bestätigen,
- UI erst nach einer Antwort synchronisieren,
- Zustand nach Neustart des Hintergrundprozesses wiederherstellen.
Bei Diensten wie YouTube muss zusätzlich SPA-Navigation berücksichtigt werden. Ein Video-Wechsel lädt nicht immer das gesamte Dokument neu, daher reicht eine einmalige Initialisierung des Content Scripts nicht aus.
Lektion 8: Icon und Pop-up sind Teil der Implementierung, nicht nur des Brandings
Ein plattformübergreifender Build kann technisch korrekt sein und dennoch falsch aussehen.
In unseren Tests erschienen Icons:
- zu groß für die verfügbare Fläche,
- ohne Sicherheitsabstand,
- an den Rändern anliegend,
- optisch kleiner als andere Erweiterungsicons trotz identischer Dateigröße,
- im Store korrekt, aber in der Browser-Symbolleiste kaum lesbar.
Die PNG-Abmessungen lösen das Problem nicht. Entscheidend ist die visuelle Größe des Symbols innerhalb der Fläche.
Dasselbe gilt für Pop-ups. Dieselbe CSS-Breite kann unterschiedlich dargestellt werden aufgrund von:
- Font-Rendering,
- Standardstilen von Formularfeldern,
- Scrollbar-Verhalten,
- Höhenberechnung,
- Systemrändern,
- Display-Skalierung,
- Hell- und Dunkelmodus.
Deshalb bereiten wir Assets heute als Set vor, nicht als einzelnes Bild:
- Quell-SVG,
- vom Manifest verlangte Rastervarianten,
- kontrollierte Safe Area,
- Safari-App-Icon,
- Listing-Grafiken,
- Store-Screenshots.
Die Erweiterungsoberfläche sollte zudem minimale und maximale Maße besitzen und bei längeren Übersetzungen lesbar bleiben.
Lektion 9: Veröffentlichung ist ein eigenes operatives Produkt
Der größte organisatorische Fehler wäre die Annahme, dass die Arbeit nach der Erzeugung der ZIP-Datei endet.
Jeder Store besitzt ein eigenes Vertrauensmodell.
Chrome Web Store
Chrome verlangt einen klaren, einzelnen Zweck, minimale Berechtigungen, Angaben zur Datennutzung und eine Datenschutzrichtlinie. Remote ausgeführter Code ist in Manifest V3 verboten. [12]
Die Produktbeschreibung muss dem tatsächlichen Verhalten entsprechen. Wenn das Listing eine Funktion verspricht, die der Prüfer nicht ausführen kann, ist das kein Marketingproblem, sondern ein Zertifizierungsproblem.
Firefox Add-ons
AMO validiert das Paket automatisch, kann Quellcode für gebauten Code verlangen und behält die Möglichkeit einer späteren manuellen Prüfung. Sicherheits- und Datenschutzhinweise sollten auch dann behoben werden, wenn man technisch fortfahren könnte. [16][17]
Microsoft Edge Add-ons
Partner Center verlangt separate Angaben zu Verfügbarkeit, Datenschutz und Sprach-Listings. Jede Berechtigung sollte begründet werden, und die Informationen müssen mit Manifest und Verhalten übereinstimmen. [14]
Safari und App Store
Safari ergänzt eine App-Schicht, Signierung, App-Store-Metadaten und die für Apple-Apps geltenden Richtlinien. Die Erweiterung muss mit der aktuellen Safari-Version funktionieren und den Website-Zugriff auf das notwendige Minimum begrenzen. [19]
Neun Erweiterungen bedeuten daher nicht neun Listings. Bei vollständiger Abdeckung von vier Plattformen entstehen bis zu 36 Produkt-Browser-Kombinationen, noch ohne Sprachversionen und separate Varianten für Apple-Geräte.
Das ist Grund genug, Metadaten im Repository zu halten.
Lektion 10: Man braucht eine Testmatrix, keine Notiz „Erweiterung prüfen“
Für jede Erweiterung pflegen wir denselben Testkern und ergänzen anschließend funktionsspezifische Fälle.
Minimaler Smoke Test
Nach jedem Build prüfen wir:
- saubere Installation;
- Aktualisierung von der vorherigen Version;
- Anzeige des Icons;
- Öffnen des Pop-ups;
- keine Fehler bei der Initialisierung;
- Speichern und erneutes Lesen der Einstellungen;
- Funktion nach Schließen und Neustart des Browsers;
- Funktion nach Suspendierung des Hintergrundprozesses;
- Verhalten ohne Berechtigung für die Seite;
- Verhalten auf einer nicht unterstützten Seite.
Kommunikationstest
Separat testen wir:
- Pop-up → Background,
- Pop-up → Content Script,
- Content Script → Background,
- asynchrone Antwort,
- kein aktiver Tab,
- Neuladen des Tabs,
- SPA-Navigation,
- erneute Injektion des Content Scripts,
- Schließen des Pop-ups vor Eingang der Antwort.
Funktionsspezifischer Test
Bei Loudly reicht es nicht, den Regler zu bewegen. Die Änderung des Audiopegels muss bestätigt werden.
Bei LoopIt muss geprüft werden, ob die Wiedergabe tatsächlich in den gewählten Bereich zurückkehrt.
Bei AutoScroll müssen unterschiedliche Layouts, Vollbildmodus, Video-Wechsel und manuelles Scrollen getestet werden.
Bei ClickClean muss der tatsächliche Effekt auf vorbereiteten Testdaten geprüft werden, nicht nur die Meldung „Fertig“.
Bei DevScope und LeakLens muss der Bericht auf Seiten mit unterschiedlichen CSP-Regeln, Frame-Anzahlen und Header-Sätzen überprüft werden.
Plattformtest
Am Ende kommen browserbezogene Fälle hinzu:
- Neustart des Service Workers in Chromium,
- signiertes Firefox-Paket,
- Host-Berechtigungen in Firefox,
- Installation und Aktivierung der Erweiterung in Safari,
- macOS- und iOS-Versionen, sofern unterstützt,
- Aktualisierung über den Store statt nur lokales Sideloading.
Lektion 11: Automatisierte Tests ersetzen keine echte Browser-QA
Automatisierung ist notwendig, hat aber Grenzen.
Unit-Tests eignen sich gut für:
- Geschäftslogik,
- Validierung,
- Storage-Migrationen,
- Datenformatierung,
- Regeln zur Aktivierung von Funktionen.
Integrationstests können prüfen:
- das Manifest,
- Vorhandensein von Dateien,
- Kommunikation zwischen Modulen,
- grundlegende API-Aufrufe,
- Vollständigkeit der Lokalisierung.
E2E-Tests in Chromium erkennen einen großen Teil der UI- und Content-Script-Regressionen.
Sie bestätigen jedoch nicht, dass:
- Firefox alle Berechtigungen identisch interpretiert,
- Safari Audio korrekt verändert,
- der App Store das richtige Target startet,
- das Pop-up nicht doppelt geöffnet wird,
- das Icon in der Systemsymbolleiste korrekt aussieht.
Die teuersten Fehler unserer Portierungen waren nicht solche, die sich leicht mit Unit-Tests finden ließen. Es waren Integrationsfehler zwischen Erweiterung, Browser, Webseite und Betriebssystem.
Lektion 12: Die Kompatibilitätsschicht sollte Fähigkeiten beschreiben, nicht Browsernamen
Am Anfang eines Projekts ist eine Bedingung wie diese verlockend:
if (browserName === "safari") {
// spezielles Verhalten
}
Manchmal ist sie unvermeidbar, sollte aber nicht das grundlegende Architekturmodell sein.
Bessere Fragen sind:
- Unterstützt die Umgebung die jeweilige API?
- Ist das Pop-up deklarativ?
- Ist der Hintergrund ein Service Worker?
- Ist eine bestimmte Methode verfügbar?
- Benötigt die Funktion einen nativen Container?
- Stellt die Seite das benötigte Medienelement bereit?
Fähigkeitsbasierter Code ist robuster gegenüber Versionsänderungen und leichter zu testen.
Eine Plattformschicht kann zum Beispiel Folgendes bereitstellen:
export const capabilities = {
supportsSessionStorage: Boolean(browser.storage?.session),
supportsSidePanel: Boolean(browser.sidePanel),
usesServiceWorker: import.meta.env.BACKGROUND_MODE === "service-worker",
isSafariContainer: import.meta.env.TARGET === "safari",
};
Das beseitigt Unterschiede nicht. Es macht sie explizit und konzentriert sie an einer Stelle.
Lektion 13: Privacy-first vereinfacht die Veröffentlichung tatsächlich
POLPROG-Erweiterungen werden ohne Analytics, Tracking und externes Backend entwickelt, wenn diese nicht benötigt werden.
Das ist nicht nur eine Markenentscheidung. Es vereinfacht:
- Beschreibung der Datenflüsse,
- Datenschutzrichtlinie,
- Begründung von Berechtigungen,
- Code Review,
- Risiko eines Datenlecks,
- Konsistenz zwischen Listing und Code.
Das bedeutet nicht, dass eine datenschutzfreundliche Erweiterung keine Dokumentation benötigt. Wenn ein Add-on Seiteninhalte, Verlauf, Tabs oder Cookies liest, muss klar erklärt werden, warum, selbst wenn nichts an einen Server gesendet wird.
Chrome und Edge verlangen Begründungen für Berechtigungen und Angaben zur Datennutzung. Firefox verlangt bei neuen Add-ons Informationen zu Berechtigungen im Zusammenhang mit Datenerfassung. Safari empfiehlt, Website-Zugriff auf das notwendige Minimum zu beschränken. [9][12][14][19]
Von Anfang an mit minimalen Berechtigungen zu entwerfen, ist deutlich einfacher, als überflüssige Rechte nach einer Ablehnung zu entfernen.
Lektion 14: Die Release-Pipeline sollte Produkte bauen, nicht ein Archiv namens final-final.zip
Bei neun Erweiterungen ist manuelles Paketieren schnell nicht mehr sicher.
Eine gute Pipeline sollte:
- die Version prüfen;
- das Basis-Manifest validieren;
- das plattformspezifische Manifest generieren;
- den Code bauen;
- die richtigen Icons kopieren;
- nach Remote-Code prüfen;
- Tests ausführen;
- getrennte Artefakte erzeugen;
- Prüfsummen erstellen;
- Changelog und Store-Informationen vorbereiten.
Beispielausgabe:
dist/
loudly/
chrome/loudly-1.4.0.zip
edge/loudly-1.4.0.zip
firefox/loudly-1.4.0.xpi
safari/Loudly.xcarchive
Die Version sollte aus einer einzigen Quelle der Wahrheit stammen, die Artefakte müssen jedoch nicht identisch sein.
Automatisch geprüft werden sollten außerdem:
- unzulässige Dateien,
- Source Maps,
- Secrets,
- ungenutzte Berechtigungen,
- fehlende Lokalisierungen,
- falsche Icon-Pfade,
- Versionsabweichungen zwischen Manifesten.
Automatisierung entfernt die Store-Prüfung nicht. Sie entfernt Fehler, die einen Prüfer niemals erreichen sollten.
Welcher Browser war am einfachsten?
Die Antwort hängt davon ab, was gemessen wird.
Einfachster Start: Chrome
Die meiste Dokumentation, die meisten Beispiele und Werkzeuge. Eine gute Wahl für die grundlegende MV3-Version.
Einfachste Code-Portierung: Edge
Wenn die Chrome-Version korrekt ist, sind die technischen Unterschiede häufig klein. Die separate Veröffentlichungsarbeit bleibt.
Bester Unabhängigkeitstest: Firefox
Nicht immer am einfachsten, aber sehr wertvoll. Er deckt versehentlich an Chromium gebundenen Code auf und erzwingt Ordnung beim Build.
Höchster Gesamtaufwand: Safari
Nicht nur wegen der API. Xcode, App, Signierung, App Store und Tests auf Apple-Geräten kommen hinzu. Die Probleme bei Loudly, TabZoo und LoopIt zeigten außerdem, dass eine funktionierende Oberfläche noch keine funktionierende Funktion bedeutet.
Was würden wir heute anders machen?
1. Safari wäre ab dem ersten Sprint Teil des Plans
Safari müsste nicht sofort den vollständigen Funktionsumfang besitzen. Es sollte jedoch ein funktionsfähiges Xcode-Grundprojekt und regelmäßige Tests des Basisablaufs geben.
2. Firefox würde früher Teil der CI
Nicht nur der Build, sondern auch Tests in einem echten Firefox.
3. Jede Funktion hätte einen eindeutigen Besitzer des Zustands
Vor der Implementierung würden wir festlegen, ob der Zustand zum Content Script, Background, Pop-up oder Storage gehört. Viele Kommunikationsfehler entstehen aus einer unklaren Antwort auf diese Frage.
4. Berechtigungen würden gemeinsam mit der Funktion entworfen
Jede neue Berechtigung sollte besitzen:
- technische Begründung,
- Text für den Store,
- Szenario bei Ablehnung,
- Test des Verhaltens ohne Zustimmung.
5. Das Listing läge im Repository
Beschreibung, Kurztext, Release Notes, Suchbegriffe, Datenschutzrichtlinie und Grafiken sollten eine Änderungshistorie haben.
6. Wir würden Daten ab der ersten Veröffentlichung messen
Die nächste Stufe des Berichts sollte enthalten:
- Zeit vom Einreichen bis zur Veröffentlichung,
- Anzahl der Ablehnungen,
- Grund jeder Ablehnung,
- Anzahl plattformspezifischer Änderungen,
- Anzahl der erst außerhalb von Chrome gefundenen Regressionen,
- Zeit für die Vorbereitung eines Updates,
- Größe des gemeinsamen und plattformspezifischen Codes.
Wir rekonstruieren diese Werte heute nicht aus dem Gedächtnis, weil wir Erfahrung von Statistik trennen möchten. Künftig ermöglichen sie noch präzisere Berichte.
Empfohlene Architektur einer plattformübergreifenden Erweiterung
src/
core/
domain/
storage/
validation/
platform/
api.ts
capabilities.ts
messaging.ts
background/
content/
popup/
options/
locales/
manifests/
chrome.json
edge.json
firefox.json
safari.json
store/
chrome/
edge/
firefox/
safari/
tests/
unit/
integration/
e2e/
manual/
Entscheidend ist die Aufteilung der Verantwortlichkeiten:
corekennt keinen konkreten Browser;platformübersetzt WebExtensions in eine gemeinsame Schnittstelle;- UI-Flächen werden soweit sinnvoll geteilt;
- Manifeste und Store-Assets sind ausdrücklich plattformspezifisch;
- manuelle Tests besitzen dokumentierte Szenarien und existieren nicht nur im Gedächtnis des Autors.
Fazit
Die wichtigste Erkenntnis aus neun Erweiterungen ist einfach:
WebExtensions ermöglicht die Pflege eines gemeinsamen Produkts, entbindet aber nicht davon, vier korrekte Releases zu erstellen.
Chrome bietet den besten Startpunkt. Edge ist meist die einfachste technische Portierung. Firefox ist der wertvollste Test echter Kompatibilität. Safari verlangt, die Erweiterung als Teil einer Apple-App zu behandeln und nicht als weitere ZIP-Variante.
Die größten Probleme entstanden nicht dort, wo für eine einzelne API-Methode kein Gegenstück existierte. Sie entstanden an der Schnittstelle von:
- Prozesslebenszyklus,
- Zustand,
- Webseite,
- Oberfläche,
- Berechtigungen,
- Paketierung,
- Store-Prüfung.
Ein effektives Team beginnt daher nicht mit der Frage: „Wie kopieren wir die Chrome-Erweiterung in drei weitere Stores?“ Es beginnt mit:
- Was ist der gemeinsame Produktkern?
- Welche Fähigkeiten sind plattformspezifisch?
- Wo wird Zustand gespeichert?
- Wie bestätigen wir den tatsächlichen Effekt einer Funktion?
- Wie begrenzen wir Berechtigungen?
- Wie reproduzieren wir jeden Build?
- Wie testen wir ein Update und nicht nur eine saubere Installation?
Nach diesen Erfahrungen halten wir eine gemeinsame Codebasis weiterhin für die richtige Strategie. Wir tun jedoch nicht so, als wären vier Browser eine einzige Umgebung.
Gerade die Akzeptanz der Unterschiede ermöglicht es, Code zu teilen, ohne Qualität zu verlieren.
Lesen Sie als Nächstes
Quellen
- POLPROG, Startseite und Informationen zum Erweiterungsportfolio
- POLPROG, Herausgeberprofil bei Firefox Add-ons
- Apple Developer, Safari Extensions
- Apple Developer, Meet Safari Web Extensions
- Chrome for Developers, Migrate to a service worker
- Chrome for Developers, Extension service worker lifecycle
- MDN, Chrome incompatibilities
- MDN, Differences between API implementations
- MDN,
browser_specific_settings - Chrome for Developers, Events in extension service workers
- Chrome for Developers, Testing MV3 service worker suspension
- Chrome for Developers, Chrome Web Store privacy fields and permissions
- Microsoft Learn, Port a Chrome extension to Microsoft Edge
- Microsoft Learn, Publish a Microsoft Edge extension
- MDN, Build a cross-browser extension
- Mozilla Extension Workshop, Submitting an add-on
- Mozilla Extension Workshop, Signing and distribution overview
- Mozilla Extension Workshop, Add-on Policies
- Apple Developer, App Review Guidelines, Safari extensions
- Mozilla, Approach to Manifest V3
- Pantelaios und Kapravelos, Manifest V3 Unveiled
- Polčák et al., Developers Insight on Manifest V3

