Na papierze współczesne rozszerzenie przeglądarkowe powinno być produktem wieloplatformowym niemal z definicji. Chrome, Firefox, Edge i Safari wspierają WebExtensions, korzystają z podobnego pliku manifest.json i udostępniają zbliżone mechanizmy: content scripts, storage, komunikację między kontekstami, przycisk w pasku narzędzi oraz strony ustawień.
To prawda, ale tylko do pewnego momentu.
Po wydaniu i portowaniu dziewięciu rozszerzeń POLPROG wiemy już, że wspólny kod nie oznacza wspólnego produktu. Większość logiki biznesowej można współdzielić, lecz zachowanie przeglądarki, cykl życia procesu w tle, interfejs, uprawnienia, pakowanie i recenzja sklepu pozostają osobnymi problemami.
Portfolio obejmuje:
- AutoScroll,
- ClickClean,
- DevScope,
- LeakLens,
- LoopIt,
- Loudly,
- OneMoreJump,
- TabZoo,
- TubePilot.
Nie są to dziewięć kopii tego samego typu dodatku. W zestawie są narzędzia do wideo, wzmacniania dźwięku, prywatności, czyszczenia danych, programowania, zarządzania kartami i lekka gra. Dzięki temu problemy nie ograniczały się do jednego API. Dotyczyły między innymi service workerów, popupów, content scripts, audio, storage, ikon, uprawnień i procesu publikacji.
POLPROG publicznie utrzymuje dziewięć produktów rozszerzeniowych, pierwotnie wydawanych przede wszystkim dla Chrome i Firefoksa, a kolejne porty objęły również Edge oraz Safari. [1][2]
Ten artykuł nie jest laboratoryjnym benchmarkiem. Nie mierzyliśmy od pierwszego commita każdej minuty spędzonej na konkretnym sklepie, dlatego nie będziemy twierdzić, że Safari kosztuje dokładnie 2,7 raza więcej niż Chrome albo że 94% kodu jest wspólne. To byłaby pozorna precyzja.
Możemy natomiast pokazać coś bardziej użytecznego: które założenia okazały się błędne, gdzie pojawiały się rzeczywiste awarie i jak dziś zaprojektowalibyśmy cały proces od początku.
Ostatnia weryfikacja źródeł: 17 lipca 2026 roku.
Cztery przeglądarki, cztery różne role
| Platforma | Rola w naszym procesie | Największa zaleta | Najczęstsza pułapka |
|---|---|---|---|
| Chrome | Główna baza zgodności z Manifest V3 | Najszersza dokumentacja i rynek rozszerzeń | Usypiany service worker i surowe wymagania dotyczące uprawnień |
| Edge | Port oparty na Chromium | Niewielka różnica w kodzie względem Chrome | Osobny Partner Center, metadane i certyfikacja |
| Firefox | Niezależna implementacja WebExtensions | Dobrze ujawnia założenia zależne od Chromium | Różnice API, manifestu, podpisywanie i wymagania dotyczące źródeł |
| Safari | Osobny produkt w ekosystemie Apple | Dostęp do użytkowników macOS, iOS i iPadOS | Xcode, aplikacja-kontener, podpisywanie i inne zachowanie runtime |
MDN podkreśla, że celem WebExtensions jest uruchamianie dodatków w głównych przeglądarkach po minimalnych zmianach, ale jednocześnie wskazuje istotne różnice między Chrome, Firefoksem i Safari w dostępności API, manifestach oraz obsłudze operacji asynchronicznych. [7][8]
To dobrze opisuje nasze doświadczenie. „Minimalne zmiany” są realne dla prostego dodatku. Im bardziej rozszerzenie ingeruje w stronę, media, cykl życia kart albo proces w tle, tym mniej przydatne staje się słowo „minimalne”.
Lekcja 1: jeden codebase tak, jeden manifest niekoniecznie
Najlepszym punktem wyjścia jest wspólna logika, ale oddzielne konfiguracje kompilacji.
W praktyce wygodniejsza okazała się struktura, w której współdzielone są:
- logika funkcji,
- modele danych,
- moduły storage,
- walidacja,
- większość interfejsu popupu i ustawień,
- lokalizacje,
- testy jednostkowe.
Oddzielne pozostają natomiast:
- warianty manifestu,
- identyfikatory dodatków,
- minimalne wersje przeglądarek,
- konfiguracja backgroundu,
- część uprawnień,
- ikony i zasoby sklepowe,
- pakowanie,
- podpisywanie,
- skrypty publikacyjne.
Nie chodzi o utrzymywanie czterech ręcznie kopiowanych katalogów. To szybko prowadzi do rozjazdów. Lepszy model to wspólne źródła oraz generator albo warstwa konfiguracji przygotowująca osobny artefakt dla każdej platformy.
Przykładowo Firefox wymaga własnych ustawień w browser_specific_settings. W Manifest V3 identyfikator rozszerzenia jest potrzebny do podpisania, a od listopada 2025 roku nowe dodatki wysyłane do AMO muszą również deklarować ustawienia dotyczące zbierania danych. [9]
Safari wymaga z kolei projektu Xcode i aplikacji zawierającej rozszerzenie. Apple dostarcza konwerter WebExtension, ale wynik nadal trzeba traktować jak projekt natywny przeznaczony do podpisania i dystrybucji w App Store. [3][4]
Wspólny manifest byłby więc sztuczną oszczędnością. Łatwiej utrzymać kilka małych, jawnych różnic niż jeden plik wypełniony wyjątkami i warunkami.
Lekcja 2: Manifest V3 nie ujednolicił zachowania przeglądarek
Manifest V3 poprawił część zgodności i uporządkował model rozszerzeń, ale nie stworzył jednego identycznego runtime.
Najbardziej widoczna zmiana w Chrome to zastąpienie trwałych stron działających w tle przez extension service workera. Google opisuje go jako centralny handler zdarzeń uruchamiany wtedy, gdy jest potrzebny. Taki proces może zostać zakończony, a zmienne przechowywane wyłącznie w pamięci znikają wraz z nim. [5][6]
To ma bezpośrednie konsekwencje.
Kod w rodzaju:
let activeSessions = new Map();
let currentTab = null;
let isEnabled = false;
nie może być jedynym źródłem prawdy, jeżeli te dane są potrzebne po ponownym uruchomieniu service workera.
Stan trzeba podzielić na trzy grupy:
- chwilowy, który może bezpiecznie zniknąć;
- odtwarzalny z aktywnych kart lub DOM;
- trwały, który musi zostać zapisany w
storage.local,storage.sessionalbo innym właściwym miejscu.
Równie ważna jest rejestracja listenerów. Chrome zaleca deklarowanie handlerów zdarzeń w globalnym zakresie skryptu, aby były zarejestrowane synchronicznie, gdy service worker zostanie obudzony. Listener tworzony dopiero po asynchronicznej inicjalizacji może przegapić zdarzenie, które właśnie uruchomiło worker. [10]
W testach deweloperskich łatwo tego nie zauważyć. Otwarty DevTools może wpływać na cykl życia service workera, a błąd pojawia się dopiero podczas normalnego używania rozszerzenia. Google opisywał podobne problemy w analizie migracji rozszerzeń do MV3: usypianie procesu może przerywać timery i usuwać stan przechowywany w pamięci. [11]
Najważniejsza zasada brzmi więc:
Proces w tle rozszerzenia powinien być projektowany jak przerywany handler zdarzeń, a nie mały serwer Node.js działający przez cały czas.
Lekcja 3: Chrome jest dobrym punktem startowym, ale złym jedynym środowiskiem testowym
Chrome jest naturalną bazą dla projektu Manifest V3. Ma rozbudowaną dokumentację, przykłady, narzędzia deweloperskie i największy ekosystem dodatków.
Jednocześnie testowanie wyłącznie w Chrome tworzy fałszywe poczucie zgodności.
Jeżeli kod działa w Chrome i Edge, potwierdza przede wszystkim zgodność z Chromium. Nie potwierdza:
- poprawności w Gecko,
- poprawności w WebKit,
- odporności na inny cykl życia popupu,
- działania przy innym modelu uprawnień,
- właściwego skalowania ikon,
- poprawności zachowania audio,
- tego, że API ma taką samą semantykę.
Chrome Web Store wymaga od rozszerzenia jednego, wąskiego i łatwego do zrozumienia celu. Wymaga też minimalnego zestawu uprawnień, uzasadnienia każdej z nich i zgodnych deklaracji dotyczących danych. Zbyt szerokie uprawnienia mogą prowadzić do odrzucenia. W Manifest V3 nie można również ładować i wykonywać zdalnie hostowanego kodu. [12]
Te wymagania wpływają na architekturę wcześniej, niż może się wydawać. Przykładowo dodanie „na zapas” dostępu do wszystkich stron nie jest neutralne. Trzeba później uzasadnić go przed recenzentem i użytkownikiem. Lepszym rozwiązaniem może być activeTab, opcjonalne host permissions albo dostęp ograniczony do konkretnych domen.
Chrome nauczył nas więc dwóch przeciwnych rzeczy:
- dobrze nadaje się do wyznaczenia podstawowego wariantu MV3;
- nie powinien definiować wszystkich założeń wspólnej warstwy.
Lekcja 4: Edge jest najłatwiejszym portem technicznym, ale nie jest darmowym kanałem dystrybucji
Microsoft oficjalnie opisuje portowanie istniejącego rozszerzenia Chrome do Edge jako osobny, wspierany scenariusz. Ponieważ obie przeglądarki korzystają z Chromium, różnice w podstawowym kodzie bywają niewielkie. [13]
W naszych projektach Edge zwykle wymagał najmniej zmian technicznych. Nie oznaczało to jednak publikacji „jednym kliknięciem”.
Rozszerzenie trafia do osobnego Partner Center, gdzie należy:
- przesłać własny pakiet ZIP,
- ustawić dostępność i rynki,
- wybrać kategorię,
- opisać pojedynczy cel,
- uzasadnić każdą zgodę,
- zadeklarować wykorzystanie zdalnego kodu,
- opisać praktyki dotyczące danych,
- przygotować listingi dla języków,
- przesłać osobne grafiki i screenshoty,
- dodać uwagi dla zespołu certyfikacji. [14]
Microsoft, podobnie jak Google, wymaga jasno określonego celu i najmniejszego potrzebnego zestawu uprawnień. Partner Center zaznacza również, że niepełne albo niespójne deklaracje mogą wydłużyć certyfikację lub zakończyć się odrzuceniem. [14]
Edge jest więc dobrym przykładem różnicy między kosztem portu a kosztem kanału.
Kod może być niemal identyczny, ale nadal trzeba utrzymywać:
- osobny rekord produktu,
- osobny status recenzji,
- osobne metadane,
- osobne wersje grafik,
- osobny proces aktualizacji,
- zgodność deklaracji prywatności.
Dla jednego rozszerzenia jest to umiarkowany narzut. Dla dziewięciu produktów staje się regularnym procesem operacyjnym.
Lekcja 5: Firefox najlepiej wykrywa kod napisany „przypadkiem tylko dla Chrome”
Firefox korzysta z WebExtensions, ale nie jest Chromium. Właśnie dlatego jest tak wartościowym środowiskiem testowym.
Najbardziej znana różnica dotyczy namespace i kodu asynchronicznego. Chrome historycznie korzystał z chrome i callbacków, Firefox oraz Safari z browser i promises. W Manifest V3 Chrome i Edge dodały obsługę promises dla wielu metod, ale nie każda różnica zniknęła. Mozilla rekomenduje pisanie kodu z browser oraz promises i używanie WebExtension browser API Polyfill tam, gdzie jest potrzebny. [8][15]
Nie warto rozrzucać po projekcie warunków:
if (isFirefox) {
// ...
} else {
// ...
}
przy każdym wywołaniu API. Lepiej utworzyć małą warstwę adaptera:
export const extensionApi = {
storageGet: (keys) => browser.storage.local.get(keys),
storageSet: (value) => browser.storage.local.set(value),
sendMessage: (message) => browser.runtime.sendMessage(message),
};
i rozwiązywać różnice w jednym miejscu.
Firefox wymusza też większą dyscyplinę publikacyjną. Rozszerzenia muszą być podpisane, nawet jeżeli są dystrybuowane samodzielnie w standardowej konfiguracji. AMO wykonuje walidację pakietu, a kod zminifikowany lub trudny do odczytania może wymagać przesłania kompletnego pakietu źródłowego. Mozilla może także przeprowadzić ręczny przegląd już po publikacji i odrzucić bieżącą albo wcześniejszą wersję. [16][17][18]
Z punktu widzenia jakości jest to zaleta. Proces zmusza do tego, aby build był:
- odtwarzalny,
- opisany,
- wolny od niepotrzebnej obfuskacji,
- zgodny z deklarowanym działaniem,
- możliwy do sprawdzenia bez wiedzy autora.
Firefox ujawniał również problemy w miejscach, w których Chromium było bardziej pobłażliwe. Dotyczyły one obsługi komunikatów, oczekiwania na odpowiedź promise, różnic w popupach i założeń dotyczących obecności wybranych właściwości API.
Najważniejsza lekcja:
Firefox nie jest dodatkowym checkboxem po zakończeniu projektu. Jest drugim niezależnym runtime, który powinien wejść do testów wcześnie.
Lekcja 6: Safari to nie port sklepu, tylko osobny etap produktu
Apple wspiera WebExtensions i dostarcza narzędzie do konwersji rozszerzeń z Chrome, Firefoksa lub Edge. Mimo tego proces Safari jest zasadniczo inny. [3][4]
Rozszerzenie staje się częścią aplikacji tworzonej w Xcode. Trzeba zarządzać:
- projektem aplikacji,
- targetem rozszerzenia,
- bundle identifier aplikacji,
- osobnym bundle identifier rozszerzenia,
- podpisywaniem,
- profilami i certyfikatami,
- ikoną aplikacji,
- uprawnieniami,
- archiwizacją,
- App Store Connect,
- recenzją App Store.
Apple wymaga, aby rozszerzenie działało na aktualnej wersji Safari dla odpowiedniego systemu, nie ingerowało w interfejs Safari i nie żądało dostępu do większej liczby witryn, niż jest to konieczne. [19]
Właśnie tutaj pojawiły się najbardziej konkretne problemy w naszym portfolio.
Loudly: interfejs działał, ale dźwięk się nie wzmacniał
Loudly jest rozszerzeniem ingerującym w audio odtwarzane na stronie. W wersji Safari można było zobaczyć interfejs, zmieniać ustawienia i odnieść wrażenie, że produkt działa. Rzeczywisty sygnał audio nie był jednak wzmacniany.
To gorszy typ błędu niż oczywisty crash. Smoke test „popup się otwiera” przechodzi, ale podstawowa obietnica produktu nie jest spełniona.
Wniosek: dla rozszerzeń multimedialnych test funkcjonalny musi mierzyć efekt końcowy, a nie jedynie stan interfejsu.
TabZoo: biała strona zamiast aplikacji
W TabZoo wersja Safari potrafiła wyświetlić pusty biały ekran. Taki problem może wynikać z błędu inicjalizacji, ścieżek zasobów, niewłaściwego kontekstu albo wyjątku występującego przed renderem.
W Chromium podobny błąd może pozostać niewidoczny, jeżeli kolejność uruchomienia lub dostępność API jest inna.
Wniosek: każdy ekran rozszerzenia powinien mieć minimalny mechanizm diagnostyczny i obsługę błędu inicjalizacji. Pusta strona nie może być końcowym stanem produktu.
LoopIt: jeden klik otwierał dwa interfejsy
W LoopIt kliknięcie ikony w Safari powodowało pojawienie się jednego popupu oraz dodatkowego panelu rozszerzenia. Był to przykład konfliktu między deklaratywnie ustawionym popupem a ręczną obsługą kliknięcia.
W jednej przeglądarce zachowanie wydawało się poprawne, w drugiej prowadziło do podwójnej reakcji.
Wniosek: trzeba jednoznacznie ustalić, kto jest właścicielem kliknięcia przycisku. Albo przycisk ma default_popup, albo zdarzenie jest obsługiwane programowo. Łączenie obu mechanizmów wymaga bardzo świadomego projektu.
Te trzy przypadki dobrze pokazują, dlaczego samo uruchomienie konwertera Apple nie kończy portu. Konwerter przenosi strukturę. Nie potwierdza semantyki działania.
Lekcja 7: rozszerzenia wideo i audio są trudniejsze niż sugeruje popup
Cztery z naszych produktów mocno zależą od multimediów:
- AutoScroll,
- LoopIt,
- Loudly,
- TubePilot.
Każde z nich musi skoordynować kilka światów:
- DOM strony,
- element audio lub video,
- content script,
- popup,
- proces w tle,
- zapisane ustawienia,
- nawigację SPA,
- zmianę aktywnej karty.
Najwięcej błędów powstaje nie w pojedynczej funkcji, lecz na granicy tych kontekstów.
Przykładowy scenariusz:
- użytkownik otwiera popup;
- popup pyta content script o bieżący stan;
- strona właśnie zmieniła film bez pełnego przeładowania;
- stary element video został usunięty;
- content script posiada jeszcze referencję do poprzedniego elementu;
- popup pokazuje poprawną wartość, ale modyfikuje nieaktywny odtwarzacz.
Rozwiązaniem nie jest dodanie kolejnego setTimeout. Potrzebny jest jawny model stanu:
- identyfikacja aktywnego elementu,
- obserwacja zmian DOM,
- ponowne podłączanie handlerów,
- potwierdzenie wykonania komendy,
- synchronizacja UI dopiero po odpowiedzi,
- odtwarzanie stanu po restarcie procesu w tle.
W przypadku serwisów typu YouTube trzeba dodatkowo pamiętać o nawigacji SPA. Zmiana filmu nie zawsze powoduje pełne przeładowanie dokumentu, dlatego jednorazowa inicjalizacja content scriptu jest niewystarczająca.
Lekcja 8: ikona i popup są częścią implementacji, nie tylko brandingu
Wieloplatformowy build może być poprawny technicznie, a jednocześnie wyglądać źle.
W naszych testach pojawiały się ikony:
- zbyt duże w stosunku do pola,
- pozbawione bezpiecznego marginesu,
- dotykające krawędzi,
- optycznie mniejsze od ikon innych rozszerzeń mimo identycznego rozmiaru pliku,
- poprawne w sklepie, ale nieczytelne w pasku przeglądarki.
Rozmiar PNG nie rozwiązuje problemu. Liczy się rozmiar wizualny symbolu wewnątrz płótna.
To samo dotyczy popupów. Ta sama szerokość CSS może być wyświetlana inaczej ze względu na:
- font rendering,
- domyślne style formularzy,
- zachowanie scrollbara,
- obliczanie wysokości,
- marginesy systemowe,
- skalowanie ekranu,
- tryb jasny i ciemny.
Dlatego assets przygotowujemy obecnie jako zestaw, nie jeden obraz:
- źródłowy SVG,
- warianty rastrowe wymagane przez manifest,
- kontrolowany safe area,
- ikona aplikacji Safari,
- grafiki listingowe,
- screenshoty sklepu.
UI rozszerzenia powinno mieć również minimalne i maksymalne wymiary oraz zachowywać czytelność po zmianie długości tłumaczeń.
Lekcja 9: publikacja jest osobnym produktem operacyjnym
Największym błędem organizacyjnym byłoby uznanie, że praca kończy się po wygenerowaniu ZIP-a.
Każdy sklep ma własny model zaufania.
Chrome Web Store
Chrome wymaga jasnego, pojedynczego celu, minimalnych uprawnień, deklaracji danych i polityki prywatności. Zdalnie wykonywany kod jest zabroniony w Manifest V3. [12]
Opis produktu musi odpowiadać rzeczywistemu działaniu. Jeżeli listing obiecuje funkcję, której recenzent nie może uruchomić, problem nie jest marketingowy, lecz certyfikacyjny.
Firefox Add-ons
AMO automatycznie waliduje pakiet, może wymagać źródeł dla zbudowanego kodu i zachowuje możliwość późniejszej ręcznej kontroli. Ostrzeżenia bezpieczeństwa i prywatności warto poprawiać nawet wtedy, gdy technicznie można przejść do następnego kroku. [16][17]
Microsoft Edge Add-ons
Partner Center wymaga osobnych danych dla dostępności, prywatności i listingów językowych. Każda zgoda powinna mieć uzasadnienie, a informacje muszą być spójne z manifestem i zachowaniem rozszerzenia. [14]
Safari i App Store
Safari dodaje warstwę aplikacji, podpisywania, metadanych App Store oraz wytycznych obowiązujących aplikacje Apple. Rozszerzenie musi działać na aktualnej wersji Safari i ograniczać dostęp do stron do niezbędnego minimum. [19]
W rezultacie dziewięć rozszerzeń nie oznacza dziewięciu listingów. Przy pełnym pokryciu czterech platform powstaje do 36 kombinacji produkt-przeglądarka, nie licząc wersji językowych i osobnych wariantów dla urządzeń Apple.
To wystarczający powód, aby metadane trzymać w repozytorium.
Lekcja 10: potrzebna jest matryca testów, a nie lista „sprawdzić rozszerzenie”
Dla każdego rozszerzenia utrzymujemy ten sam rdzeń testów, a następnie dokładamy przypadki związane z jego funkcją.
Minimalny smoke test
Po każdym buildzie sprawdzamy:
- instalację czystej wersji;
- aktualizację z poprzedniego wydania;
- pojawienie się ikony;
- otwarcie popupu;
- brak błędów przy inicjalizacji;
- zapis i ponowne odczytanie ustawień;
- działanie po zamknięciu oraz ponownym uruchomieniu przeglądarki;
- działanie po uśpieniu backgroundu;
- zachowanie przy braku uprawnienia do strony;
- zachowanie na stronie nieobsługiwanej.
Test komunikacji
Osobno testujemy:
- popup → background,
- popup → content script,
- content script → background,
- odpowiedź asynchroniczną,
- brak aktywnej karty,
- przeładowanie karty,
- nawigację SPA,
- ponowne wstrzyknięcie content scriptu,
- zamknięcie popupu przed odpowiedzią.
Test konkretnej funkcji
Dla Loudly nie wystarczy przesunąć suwaka. Trzeba potwierdzić zmianę poziomu audio.
Dla LoopIt trzeba sprawdzić, czy odtwarzanie rzeczywiście wraca do wybranego zakresu.
Dla AutoScroll trzeba przetestować różne układy strony, pełny ekran, zmianę filmu i ręczne przewinięcie.
Dla ClickClean trzeba sprawdzić efekt czyszczenia na prawdziwych danych testowych, a nie tylko komunikat „gotowe”.
Dla DevScope i LeakLens trzeba potwierdzić poprawność raportu na stronach o różnej polityce CSP, liczbie ramek oraz zestawie nagłówków.
Test platformowy
Na końcu dochodzą przypadki specyficzne dla przeglądarki:
- restart service workera w Chromium,
- podpisany pakiet Firefoksa,
- uprawnienia hostów w Firefox,
- instalacja i aktywacja rozszerzenia w Safari,
- wersja macOS oraz iOS, jeżeli produkt je wspiera,
- aktualizacja przez sklep, a nie tylko lokalny sideload.
Lekcja 11: testy automatyczne nie zastąpią rzeczywistego browser QA
Automatyzacja jest konieczna, ale ma ograniczenia.
Testy jednostkowe dobrze obejmują:
- logikę biznesową,
- walidację,
- migracje storage,
- formatowanie danych,
- reguły włączania funkcji.
Testy integracyjne mogą sprawdzić:
- manifest,
- obecność plików,
- komunikację między modułami,
- podstawowe wywołania API,
- poprawność lokalizacji.
Testy E2E w Chromium wychwytują sporą część regresji interfejsu i content scripts.
Nie potwierdzają jednak, że:
- Firefox interpretuje wszystkie uprawnienia identycznie,
- Safari poprawnie modyfikuje audio,
- App Store uruchomi właściwy target,
- popup nie zostanie otwarty dwukrotnie,
- ikona będzie wyglądała poprawnie w systemowym pasku.
Najbardziej kosztowne błędy z naszych portów nie były błędami, które łatwo wykryć samym testem jednostkowym. Były błędami integracji między rozszerzeniem, przeglądarką, stroną i systemem operacyjnym.
Lekcja 12: warstwa zgodności powinna opisywać możliwości, nie nazwy przeglądarek
Na początku projektu kuszący jest warunek:
if (browserName === "safari") {
// specjalne zachowanie
}
Czasem jest nieunikniony, ale nie powinien być podstawowym modelem architektury.
Lepsze pytania brzmią:
- czy środowisko obsługuje dane API?
- czy popup jest deklaratywny?
- czy tło jest service workerem?
- czy dostępna jest konkretna metoda?
- czy dana funkcja wymaga natywnego kontenera?
- czy strona udostępnia element multimedialny, którego potrzebujemy?
Kod oparty na możliwościach jest odporniejszy na zmiany wersji i łatwiejszy do testowania.
Przykładowa warstwa platformowa może udostępniać:
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",
};
Nie eliminuje to różnic. Sprawia jednak, że są jawne i skupione w jednym miejscu.
Lekcja 13: prywatność-first realnie upraszcza publikację
Rozszerzenia POLPROG są projektowane bez analityki, śledzenia i zewnętrznego backendu tam, gdzie nie jest on potrzebny.
To nie tylko decyzja wizerunkowa. Upraszcza:
- opis przepływu danych,
- politykę prywatności,
- uzasadnienie uprawnień,
- code review,
- ryzyko wycieku,
- zgodność między listingiem a kodem.
Nie oznacza to, że prywatne rozszerzenie nie potrzebuje dokumentacji. Jeżeli dodatek czyta zawartość strony, historię, karty albo cookies, trzeba jasno wyjaśnić, po co to robi, nawet jeśli niczego nie wysyła na serwer.
Chrome i Edge wymagają uzasadniania zgód oraz deklarowania praktyk dotyczących danych. Firefox od nowych dodatków wymaga informacji o uprawnieniach związanych ze zbieraniem danych. Safari zaleca ograniczanie dostępu do stron do niezbędnego minimum. [9][12][14][19]
Projektowanie z minimalnymi uprawnieniami od pierwszego dnia jest znacznie łatwiejsze niż usuwanie nadmiarowych zgód po odrzuceniu.
Lekcja 14: release pipeline powinien budować produkty, nie archiwum „final-final.zip”
Przy dziewięciu rozszerzeniach ręczne pakowanie szybko przestaje być bezpieczne.
Dobry pipeline powinien:
- sprawdzić wersję;
- zwalidować manifest bazowy;
- wygenerować manifest platformowy;
- zbudować kod;
- skopiować właściwe ikony;
- sprawdzić brak zdalnego kodu;
- uruchomić testy;
- wygenerować osobne artefakty;
- utworzyć sumy kontrolne;
- przygotować changelog i informacje sklepowe.
Przykładowy wynik:
dist/
loudly/
chrome/loudly-1.4.0.zip
edge/loudly-1.4.0.zip
firefox/loudly-1.4.0.xpi
safari/Loudly.xcarchive
Wersja powinna pochodzić z jednego źródła prawdy, ale artefakty nie muszą być identyczne.
Warto też automatycznie sprawdzać:
- niedozwolone pliki,
- sourcemapy,
- sekrety,
- nieużywane uprawnienia,
- brakujące lokalizacje,
- złą ścieżkę ikony,
- niezgodność wersji między manifestami.
Automatyzacja nie usuwa recenzji sklepów. Usuwa natomiast błędy, które nie powinny nigdy do nich dotrzeć.
Która przeglądarka okazała się najłatwiejsza?
Odpowiedź zależy od tego, co mierzymy.
Najłatwiejszy początek: Chrome
Najwięcej materiałów, przykładów i narzędzi. Dobry wybór do zbudowania podstawowej wersji MV3.
Najłatwiejszy port kodu: Edge
Jeżeli wersja Chrome jest poprawna, różnice techniczne często są niewielkie. Pozostaje jednak osobna praca publikacyjna.
Najlepszy test niezależności: Firefox
Nie zawsze jest najprostszy, ale daje dużo wartości. Ujawnia kod przypadkowo związany z Chromium i wymusza porządek w buildzie.
Największy całkowity nakład: Safari
Nie tylko z powodu API. Dochodzą Xcode, aplikacja, podpisywanie, App Store oraz testy na urządzeniach Apple. Problemy z Loudly, TabZoo i LoopIt pokazały też, że działający interfejs nie oznacza działającej funkcji.
Co zrobilibyśmy inaczej, zaczynając dziś?
1. Safari byłoby w planie od pierwszego sprintu
Nie musi od razu otrzymać pełnego zestawu funkcji. Powinien jednak istnieć działający skeleton projektu Xcode i regularny test podstawowego przepływu.
2. Firefox byłby częścią CI wcześniej
Nie tylko build, lecz także testy uruchamiane w prawdziwym Firefoxie.
3. Każda funkcja otrzymałaby właściciela stanu
Przed implementacją ustalalibyśmy, czy stan należy do content scriptu, backgroundu, popupu czy storage. Wiele błędów komunikacji wynika z niejasnej odpowiedzi na to pytanie.
4. Uprawnienia byłyby projektowane razem z funkcją
Każdy nowy permission powinien mieć:
- uzasadnienie techniczne,
- tekst dla sklepu,
- scenariusz odmowy,
- test zachowania bez zgody.
5. Listing byłby częścią repozytorium
Opis, krótkie podsumowanie, release notes, search terms, polityka prywatności i grafiki powinny mieć historię zmian.
6. Mierzylibyśmy dane od pierwszego wydania
Kolejny etap raportu powinien obejmować:
- czas od wysłania do publikacji,
- liczbę odrzuceń,
- przyczynę każdego odrzucenia,
- liczbę zmian platformowych,
- liczbę regresji wykrytych dopiero poza Chrome,
- czas przygotowania aktualizacji,
- rozmiar wspólnego i platformowego kodu.
Tych danych nie rekonstruujemy dziś z pamięci, ponieważ chcemy oddzielać doświadczenie od statystyki. W przyszłości pozwolą one publikować jeszcze bardziej precyzyjne raporty.
Rekomendowana architektura rozszerzenia wieloplatformowego
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/
Najważniejszy jest podział odpowiedzialności:
corenie zna konkretnej przeglądarki;platformtłumaczy WebExtensions na wspólny interfejs;- powierzchnie UI są możliwie współdzielone;
- manifesty i store assets są jawnie platformowe;
- testy ręczne mają zapisane scenariusze, a nie istnieją wyłącznie w pamięci autora.
Werdykt
Najważniejsza lekcja z dziewięciu rozszerzeń jest prosta:
WebExtensions pozwala utrzymywać wspólny produkt, ale nie zwalnia z tworzenia czterech poprawnych wydań.
Chrome daje najlepszy punkt startowy. Edge jest zwykle najłatwiejszym portem technicznym. Firefox jest najcenniejszym testem prawdziwej zgodności. Safari wymaga potraktowania rozszerzenia jak elementu aplikacji Apple, a nie kolejnego wariantu ZIP-a.
Największe problemy nie powstawały tam, gdzie brakowało odpowiednika pojedynczej metody API. Powstawały na styku:
- cyklu życia procesu,
- stanu,
- strony internetowej,
- interfejsu,
- uprawnień,
- pakowania,
- recenzji sklepu.
Dlatego skuteczny zespół nie zaczyna od pytania: „jak skopiować rozszerzenie Chrome do trzech innych sklepów?”. Zaczyna od pytań:
- co jest wspólnym rdzeniem produktu?
- które możliwości są zależne od platformy?
- gdzie przechowywany jest stan?
- jak potwierdzimy rzeczywisty efekt funkcji?
- jak ograniczymy uprawnienia?
- jak odtworzymy każdy build?
- jak sprawdzimy aktualizację, a nie tylko czystą instalację?
Po tych doświadczeniach nadal uważamy jeden codebase za właściwą strategię. Nie próbujemy jednak udawać, że cztery przeglądarki są jednym środowiskiem.
To właśnie zaakceptowanie różnic pozwala współdzielić kod bez obniżania jakości.
Przeczytaj dalej
Źródła
- POLPROG, strona główna i informacja o portfolio rozszerzeń
- POLPROG, profil wydawcy na 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 i Kapravelos, Manifest V3 Unveiled
- Polčák i inni, Developers Insight on Manifest V3

