Safari is a supported active target with its own entry points. Built-in providers use static scripts and Safari requests no all-sites access. User-added sites receive a runtime optional permission, then the background injects content.js after navigation only for granted, enabled, non-builtin rules, with allFrames per rule. Safari settings are device-local and do not roam between devices. macOS packaging uses Xcode and the Apple Developer Program/App Store route.

Reveal URLs — Architektura

Tento dokument popisuje, jak je rozšíření Reveal URLs strukturováno a jak funguje jeho základní logika, pro přispěvatele a technické čtenáře. Cituje v celém rozsahu skutečné soubory a symboly (ve tvaru path symbol), takže lze jakékoli tvrzení ověřit oproti zdrojovému kódu. Pro popis zaměřený na uživatele viz příručku; pro kroky manuálního ověření viz plán manuálních testů.

Přehled

Reveal URLs je jediné MV3 WebExtension sestavené pro několik enginů — Chrome, Edge, Opera, Firefox, Safari a Thunderbird — z jednoho sdíleného, čistého jádra kódu. Safari používá statické skripty pouze pro vestavěné poskytovatele a je publikováno v App Storu. Rozšíření odhaluje cílovou URL každého odkazu vedle odkazu ve vykreslené zprávě a označuje odkaz, jehož viditelný text uvádí jinou registrovatelnou doménu než jeho href — klasický příznak phishingu.

Repozitář je monorepo pnpm/TypeScript (pnpm-workspace.yaml, package.json s "private": true). Bundlování je jediný skript řízený esbuildem, tooling/build.mjs, který kompiluje sdílené balíčky a tenké vstupní body každého enginu do načítatelného adresáře dist/<target>/. webextension-polyfill je zabalen do každého skriptu, spíše než aby se na něj spoléhalo jako na runtime global, takže tentýž zdroj běží beze změny napříč Chromiem a Geckem.

Dvě nativní e-mailová rozšíření rozšiřují totéž detekční jádro na povrchy, na které WebExtension nedosáhne: doplněk pro Outlook (podokno úloh Office.js, dosahující na Outlook na webu/Windows/Macu/iOS/Androidu a Outlook.com) a doplněk pro Gmail (CardService Apps Script, dosahující na aplikace Gmail web/Android/iOS). Oba znovu používají pouze ČISTOU analýzu — nový modul packages/core/src/findings.ts (analyseAnchors/analyseHtml, platformově neutrální model Finding[]), který znovu používá stávající logiku hostMismatch a odvození registrovatelné domény tldts. Per povrch se liší pouze adaptér hostitele (jak se čte a parsuje tělo) a prezentace: podokno úloh Outlooku parsuje tělo vlastním DOMParser a běží na straně klienta; doplněk pro Gmail parsuje pomocí node-html-parser a běží na straně serveru na infrastruktuře V8 Apps Script Googlu (bezplatný hosting, protože Google již poštu má; nasazováno lokálně-interaktivně přes clasp). Protože ani jeden framework nemůže mutovat vykreslený DOM zprávy v režimu čtení (Office'ovské setAsync/prependAsync jsou pouze pro psaní; CardService vykresluje karty, nikoli HTML zprávy), oba doplňky prezentují panel/kartu zjištění, nikoli vloženou anotaci — stávající cesta mutace DOM linkProcessor.ts a REVEAL_URLS_CSS NEjsou žádným z doplňků znovu použity. Relativní href se rozlišuje pouze oproti důvěryhodnému <base href> v e-mailu a jinak se přeskakuje, nikdy oproti původu mailboxu/poskytovatele (což by cíl vyfabrikovalo). Tato cesta nativních zjištění záměrně zůstává omezená na http:/https:.

Kódová základna ostře odděluje odpovědnosti:

Rozvržení repozitáře

packages/core — čisté jádro

Každý modul zde neimportuje žádné rozhraní API rozšíření (browser.*/chrome.*/messenger.*) a nepoužívá žádný DOM sink typu řetězec-do-značkování (innerHTML/insertAdjacentHTML). Tento kontrakt je mechanicky vynucen packages/core/test/purity.test.ts, který globuje každý src/**/*.ts, odstraňuje těla komentářů přes vlastní stripComments (takže docblocky, které legitimně JMENUJÍ zakázané tokeny, nespustí strážce) a poté tvrdí, že ani EXTENSION_API_PATTERN, ani UNSAFE_DOM_PATTERN neodpovídá žádnému modulu. Veřejný povrch se znovu exportuje z packages/core/src/index.ts. Jedinou runtime závislostí je tldts (pro porovnání domén s ohledem na veřejné přípony).

Moduly jsou:

packages/webext — obaly rozhraní API prohlížeče

Moduly zde MOHOU používat browser.* a jsou jediným místem, kde se dotýká rozhraní API WebExtension. Veřejný povrch se znovu exportuje z packages/webext/src/index.ts. Moduly jsou: content.ts (životní cyklus obsahu), contentRegistration.ts (dynamická registrace), storage.ts (perzistence konfigurace), background.ts (chování při instalaci), toolbarAction.ts (přepínač + odznak), messageDisplay.ts a messageDisplayBackground.ts (Thunderbird) a options/options.ts (řadič stránky nastavení).

extensions/<engine> — tenké vstupní body a manifesty

Každý engine nese svůj vlastní manifest.json a icons/. Chrome, Firefox a Thunderbird navíc dodávají src/ vstupních bodů; Edge, Opera a Safari nesou pouze manifest a ikony a ZNOVU POUŽÍVAJÍ Chromovský src/ (deklarováno deskriptorem sestavení — viz níže). Vstupy jsou záměrně nepatrné: například extensions/chrome/src/content.ts je jen void createContentController().bootstrap(); a extensions/chrome/src/background.ts volá registerBackground, registerContentReconciliation a registerToolbarToggle. Firefoxův vstup na pozadí je totožný s Chromovým; Thunderbirdův (extensions/thunderbird/src/background.ts) volá místo toho registerBackground a registerMessageDisplay (nemá žádné obsahové skripty). extensions/_template je Chromium lešení připravené ke kopírování, nikoli cíl sestavení.

tooling, features a kouřové testy

tooling obsahuje build.mjs, version.mjs a icons.mjs. features obsahuje Gherkin scénáře, jejich definice kroků a věrné testovací dvojníky v features/support/webext.ts a features/support/world.ts. Každý engine rovněž nese extensions/<engine>/test/manifest.smoke.test.mjs (_templateovský je template.smoke.test.mjs) — to jsou autoritativní kontrakt manifestu (viz „Sestavení per engine a kontrakt manifestu“ níže).

Odhalovací kanál

Obsahová cesta začíná u vstupu enginu jako extensions/chrome/src/content.ts, který volá createContentController().bootstrap() z packages/webext/src/content.ts.

Bootstrap

createContentController bootstrap běží jednou na rámec:

  1. Nárokuje rámec synchronně, před jakýmkoli await, přes claimBootstrap, který čte/nastavuje příznak BOOTSTRAP_MARKER na window rámce. Statický vestavěný záznam content_scripts a překrývající se dynamický uživatelský skript mohou oba vložit content.js do téhož rámce; sdílené izolované window z toho činí správnou strážku pouze-jednou a nárokování před jakýmkoli await znamená, že dvě téměř současná vložení jí nemohou obě projít.
  2. Načte perzistovanou konfiguraci NEJPRVE přes loadConfigWithRetry (BOOTSTRAP_CONFIG_RETRIES dodatečných pokusů při přechodném odmítnutí), přičemž drží marker napříč opakováními, takže překrývající se vložení, které již provedlo no-op, není osiřelé. Až poté, co čtení uspěje, vloží stylopis (injectStyles, který přiřazuje REVEAL_URLS_CSS přes textContent), takže neúspěšné čtení nepřipojí žádný <style> a opakování jej nemůže duplikovat.
  3. Pokud načtení konfigurace selže při každém pokusu, releaseBootstrap marker vrátí zpět (jen zde, před začátkem anotace), takže pozdější opětovné vložení může opakovat; jakmile start proběhl, marker musí přetrvat, protože čerstvý anotátor by nevlastnil uzly předchozího průchodu.
  4. Když config.enabled, volá start a vždy registruje posluchač onConfigChanged, který znovu aplikuje konfiguraci (pokrývaje přechod zakázáno→povoleno).

Rozlišení aktivního pravidla webu

start (a applyConfig) volá resolveSiteRule, aby rozhodl, zda — a kde — anotovat. resolveSiteRule nejprve zkouší VLASTNÍ umístění běžícího dokumentu přes resolveForHref, který filtruje config.sites na povolená pravidla a odkládá se na jádrové selectMostSpecific, takže vyhraje nejspecifičtější shoda (specifický vestavěný poráží širokou uživatelskou zástupnou masku). Když vlastní umístění neodpovídá ničemu A původ vlastního dokumentu je neprůhledný nebo zděděný — řízeno hasOpaqueOrigin, pravda pouze pro about:blank/about:srcdoc (zděděné přes INHERITED_ORIGIN_URLS) nebo blob:/data: (OPAQUE_ORIGIN_SCHEMES) — vrací se, v pořadí, na HORNÍ rámec (readTopHref, vestavěný případ message-iframe Protonu), okno OPENER (readOpenerHref, vyskakovací čtecí okno Outlooku otevřené přes window.open("about:blank")) a nakonec document.referrer (readReferrer). Každé externí čtení je chráněno proti přístupu napříč původy (který vyhazuje) a přispívá kandidátem pouze tehdy, když je čitelné. OBYČEJNÝ dokument bez shody je autoritativní a nikdy nedědí pravidlo z okolního kontextu, takže řadič zůstává netečný.

Omezení na kořen obsahu a pozorování mutací

start zaznamenává selektor contentRoot odpovídajícího pravidla, vytváří anotátor přes createAnnotator(config) a zpracovává každý odpovídající kořen z selectRoots(doc, contentRoot) (querySelectorAll zabalený v try/catch, takže neplatný selektor selže bezpečně na []). Poté pozoruje doc.body (stabilní kontejner) s OBSERVER_OPTIONS ({ childList: true, subtree: true }).

Pozorovatel napájí processMutations, který každou mutaci gatuje na kořeni obsahu: znovu zpracovává target mutace pouze tehdy, když je Element v rámci kořene (isWithinRoot, přes closest), zatímco pozdní objevování kořenů zajišťuje omezený průchod discoverMatchedRoots() pro každý batch (jedno querySelectorAll(contentRoot) na batch, deduplikované přes WeakSet). pendingShadowHosts levně znovu kontroluje dříve plain hosty, dokud se neobjeví open shadow root, takže jsou pokryty i shadow upgrady. Takže churn v kořeni i shadow-host upgrady jsou zachyceny, zatímco rozhraní aplikace mimo kořeny zůstává nedotčené. Nulový anotátor (rozšíření je zastaveno) je no-op.

applyConfig uvádí do souladu živou změnu konfigurace: bourá, když neodpovídá žádné pravidlo nebo je příznak povoleno vypnutý; restartuje (vrácení + opětovné pozorování), když se rozlišený contentRoot změnil; a jinak přeteče na místě — annotator.setConfig a poté annotator.process přes každý odpovídající kořen — bez restartování pozorovatele.

Anotování jediné kotvy

Anotátor žije v packages/core/src/linkProcessor.ts. createAnnotator vrací Annotator nad stavem uzávěru, který stránka nemůže číst: silná Map klíčovaná identitou kotvy, token data-ru na anotátor z generateToken (crypto.getRandomValues) a aktuální konfigurace. Jeho metoda process(root):

  1. Prořezává záznamy registru, jejichž kotva již není připojena.
  2. Sbírá kandidátské kotvy pomocí collectAnchors (potomek a[href] plus samotný kořen, když je a[href], deduplikováno).
  3. Rozlišuje cíl každé kotvy pomocí resolveAnnotatableUrl, který deleguje na resolveAnnotatableHref. null (přeskočit) vrací jen pro prázdný/vnitrostránkový nebo neparsovatelný href, relativní hrefy rozlišuje oproti baseURI a přijímá každé parsovatelné schéma včetně mailto:, tel:, sms: a neprůhledných URL javascript:, data: a blob:. ignoreHosts platí jen pro http:/https: odkazy s hostitelem.
  4. Bere idempotentní rychlou cestu, když isAnnotationIntact potvrdí, že otisk vykreslení je nezměněný a každý vytvořený uzel je stále připojen a na místě; jinak vrátí zastaralou anotaci a znovu vykreslí.
  5. Vypočítá neshodu podle schématu pomocí destinationMismatch na čistém textu kotvy: classifyScheme posílá webové odkazy na hostMismatch, poštu na mailMismatch, telefon na phoneMismatch a ostatní na opaqueMismatch. Pak volá annotateAnchor; showOnlyOnMismatch potlačuje pouze viditelné vložené odhalení odkazu bez nesouladu a zachovává jeho cílový title.

annotateAnchor vykresluje pouze BEZPEČNÝ DOM. Vždy uloží původní title (TitleSave) a zapíše cílový title — jediný zápis do kotvy. Neexistuje samostatný title režim ani varovný odznak. Pokud showOnlyOnMismatch neskryje odkaz bez nesouladu, předsadí <span class="reveal-urls-url"> a následující <br>, přičemž vlastní potomky odkazu ponechá nedotčené. needsLeadingInlineRevealBreak přidá vedoucí <br>, když odkazu na stejném řádku předchází viditelný vložený obsah. Span zobrazuje autorem napsaný absolutní href doslovně nebo relativní href rozlišený oproti baseURI přes inertní textContent (s předponou šipky a nezalomitelné mezery U+00A0, pak zkrácený truncateUrl); po připojení získá kontrastní pozadí přes applyContrastBackdrop. Při zdůrazněné neshodě se REVEAL_URLS_MISMATCH_CLASS přidá do REVEAL_URLS_URL_CLASS, což použije nastavenou barvu neshody přes applyColour a výchozí tučné písmo v REVEAL_URLS_CSS; výslovné přepsání tloušťky písma přes applyFontOverrides má přednost. Každý vytvořený uzel je označen data-ru="<token>" (nikdy kotva) a každý záznam zaznamenává svůj configFingerprint, takže změna konfigurace vykreslení vynutí přetečení. revertOwned odstraňuje vytvořené uzly podle reference a obnovuje uložený title; revertAll to dělá pro každou vlastněnou kotvu a čistí registr.

truncateUrl operuje nad kódovými body Unicode ([...displayHref]), takže řez nikdy nerozdělí surrogátový pár; zachovává původ schématu a hostitele a zkracuje zbytek s koncovým URL_ELLIPSIS.

Neshoda hostitele

packages/core/src/hostMismatch.ts hostMismatch je webová větev destinationMismatch: porovnává viditelný text odkazu oproti jeho href podle registrovatelné domény, nikoli podle syrového názvu hostitele, takže poddoména bez nesouladu se neoznačí, zatímco napodobenina ano. extractHostCandidates rozdělí text a redukuje každý token přes hostCandidate (který vyžaduje tečku a veřejnou příponu ICANN rozpoznanou tldts, odmítaje běžný tečkovaný text jako e.g). Jak hostitel href, tak každý kandidát se redukují na svou registrovatelnou doménu pomocí tldts.getDomain a JAKÝKOLI kandidát, jehož doména se liší od domény href, dává neshodu — takže pojmenování skutečného škodlivého hostitele vedle lákacího hostitele nemůže varování potlačit. Nikdy nevyhazuje.

Pro mailové odkazy mailMismatch porovnává všechny registrovatelné domény příjemců včetně to, cc a bcc; phoneMismatch porovnává rozlišitelná vytáčená čísla. opaqueMismatch vždy označí SCRIPT_LIKE_SCHEMESjavascript:, data: a vbscript: — zatímco ostatní neprůhledná schémata označí jen tehdy, když viditelný text uvádí konkrétní webový, e-mailový nebo telefonní cíl.

Styly a kontrast

packages/core/src/styles.ts obsahuje vkládatelné REVEAL_URLS_CSS (pevné rozvržení, tloušťka, tvar a pevné bílé, plně neprůhledné pozadí čipu — background: #ffffff, opacity: 1; barvy se do něj NIKDY neinterpolují) a runtime pomocníky applyColour/applyFontOverrides/applyContrastBackdrop. applyContrastBackdrop čte rozlišenou barvu prvku a první neprůhledné pozadí nalezené při procházení od prvku SAMOTNÉHO nahoru (jeho vlastní pozadí — pevné bílé výchozí čipu nebo specifičtější přepsání hostitelské stránky — před jakýmkoli předkem) přes standardní getComputedStyle (získané z element.ownerDocument?.defaultView, takže je bezpečné vůči rámcům a stubovatelné) a když packages/core/src/contrast.ts needsWhiteBackdrop hlásí, že text selhává WCAG AA (CONTRAST_THRESHOLD) oproti tomuto pozadí A bílá skutečně pomáhá, nastaví style.backgroundColor = "white". contrast.ts poskytuje parseColour, relativeLuminance a contrastRatio; parsuje formy rgb()/rgba()/hex, které getComputedStyle vrací, a plně průhlednou barvu považuje za „nenalezenou“.

Konfigurovatelní hostitelé a omezování obsahu

SiteRule (packages/core/src/config.ts) říká, KDE anotace běží (match, allFrames) a KTERÝ kontejner ji omezuje (contentRoot), plus enabled a příznak builtin. Dodávané DEFAULT_SITES pokrývají FastMail, Gmail, Proton (s allFrames), Tuta, Yahoo, Zoho a oba hostitele Outlooku. Config agreguje globální přepínače, barvy, přepsání písem a pole sites.

Engine vzorů shody

packages/core/src/matchPattern.ts obsahuje záměrně RESTRIKTIVNÍ gramatiku pro dobu přidání MATCH_PATTERN (pouze http/https, volitelná úvodní zástupná maska hostitele *., glob cesty; žádný port, žádné schéma *, žádné <all_urls>). matchesPattern porovnává URL oproti validovanému vzoru, přičemž cestu deleguje na pathGlobMatches (každý * odpovídá libovolné sekvenci znaků); selhává bezpečně na false. parsePatternParts rozdělí validovaný vzor na host/path/scheme/ wildcardHost.

Překrývající se pravidla se rozlišují podle „nejspecifičtější vyhrává“: compareSiteSpecificity řadí podle (1) přesný hostitel nad zástupným hostitelem, (2) delší doslovný hostitel, (3) doslovnější cesta (literalPathLength), poté (4) deterministický ASCII tiebreak; selectMostSpecific vrací nejspecifičtější povolené pravidlo odpovídající URL.

Pokrytí udělených původů je SAMOSTATNÝ, záměrně ŠIRŠÍ matcher. permissions.getAll() může hlásit udělení v plné gramatice WebExtension (<all_urls>, *://*/*, https://*/*, *://*.host/*, https://host/*), které by restriktivní MATCH_PATTERN odmítl. parseGrantedOrigin je parsuje do GrantedOriginParts a originCovers hlásí, zda udělený původ pokrývá match pravidla (např. široké udělení https://*/* skutečně pokrývá každé pravidlo https). matchAllowsOriginFallback hlásí, zda je cesta pravidla přesně ORIGIN_FALLBACK_PATH (/*) — bezpečný průnik omezení Chromia a Gecko/Firefox 128 na dynamickém záložním řešení neprůhledného původu.

Dynamická registrace

packages/webext/src/contentRegistration.ts registruje jeden dynamický obsahový skript na uživatelem přidané pravidlo. Vestavěné jsou obsluhovány statickými záznamy content_scripts a NIKDY se dynamicky neregistrují. desiredContentScripts filtruje config.sites na nevestavěná, povolená pravidla, jejichž původ isOriginGranted (delegováno na originCovers), mapuje každé na RegisteredContentScript se stabilním id (contentScriptId, hash FNV-1a shody, takže id používá jen znakovou sadu bezpečnou pro API) a nastavuje matchOriginAsFallback: true POUZE tehdy, když platí matchAllowsOriginFallback(rule.match) (cesta /*). Gatování příznaku tímto způsobem brání jednomu pravidlu s nekonformní cestou způsobit odmítnutí celé dávkové registrace; takové pravidlo se registruje bez pokrytí vyskakovacího okna, záměrně.

reconcileContentScriptsOnce čte udělené původy, uloženou konfiguraci a živé registrace, poté konverguje diffem VĚDOMÝM VLASTNOSTÍ: sameRegistration promítá jak požadovaný deskriptor, tak živé zpětné čtení na normalizovaný tvar (aplikuje reálnou výchozí hodnotu WebExtension každého pole — allFrames/matchOriginAsFallback výchozí false, persistAcrossSessions výchozí TRUE, runAt výchozí document_idle), takže čerstvě zaregistrovaný skript se porovnává STEJNĚ s deskriptorem, který jej vyprodukoval, a nemůže vzniknout žádná smyčka opětovné registrace. Protože neexistuje žádné updateContentScripts, změněný skript se odregistruje a poté znovu zaregistruje. reconcileContentScripts to obaluje probíhajícím příslibem (inFlight), takže dva téměř současné spouštěče (změna konfigurace A permissions.onAdded obojí spuštěné při přidání webu) jsou serializovány spíše než v závodu. registerContentReconciliation propojuje spouštěče (permissions.onAdded/onRemoved, onConfigChanged) a konverguje jednou při spuštění; je importováno pouze vstupy na pozadí Chrome a Firefoxu.

Přihlašovací tok oprávnění

Statické manifesty deklarují optional_host_permissions (http://*/*, https://*/*), takže uživatel může za běhu udělit libovolného dalšího webového hostitele. packages/webext/src/options/options.ts addSite validuje CELÉ kandidátské pravidlo (shoda A kořen obsahu) přes normaliseSiteRule PŘED požádáním o jakékoli oprávnění, odvozuje původ hostitele pomocí matchOrigin a volá browser.permissions.request z gesta kliknutí na tlačítko Přidat; jen při udělení připojí řádek builtin:false a perzistuje přes kanonickou cestu zápisu.

Konfigurace, úložiště a kanonický vzor

Jediný validační trychtýř

normaliseConfig v packages/core/src/config.ts je jediná kanonická cesta čtení: koeruje a omezuje každé pole, přičemž se u čehokoli neplatného vrací na výchozí hodnotu, a nikdy nevyhazuje. Je sestaven z validátorů per pole — normaliseBoolean, normaliseMaxLength, normaliseIgnoreHosts (redukuje každý záznam na holý název hostitele punycode přes bareHostname), normaliseMatchColour/normaliseMismatchColour (gatováno safeColour.ts isSafeColour), normaliseCssSize, normaliseFontWeight, normaliseContentRoot (omezeno CONTENT_ROOT_PATTERN/CONTENT_ROOT_MAX_LENGTH), normaliseMatchPattern (gatováno MATCH_PATTERN) a normaliseSites. normaliseSites zahazuje odmítnutí, deduplikuje podle match, VYNUCUJE builtin: true u jakéhokoli pravidla, jehož shoda se rovná shodě vestavěného (uzavíraje vektor přesná-shoda-zastíní-vestavěný / dvojité vložení), znovu naseje jakýkoli chybějící vestavěný (takže zmanipulované úložiště nemůže vypustit jádrového poskytovatele) a řadí abecedně-numericky podle match pro kanonické pořadí. configFingerprint hashuje pouze pole ovlivňující vykreslení (vyjma enabled, ignoreHosts a sites, které řídí start/stop a přeskočení spíše než přetečení) přes fnv1a.

Úložiště

packages/webext/src/storage.ts obaluje rozhraní API úložiště WebExtension. configArea používá browser.storage.sync, když je k dispozici (takže nastavení putují), a vrací se na browser.storage.local (např. na Thunderbirdu); configAreaName hlásí, které je aktivní. getConfig, setConfig a onConfigChanged všechny prohánějí svou syrovou hodnotu přes normaliseConfig, takže poškozené nebo zmanipulované úložiště nemůže volajícímu nikdy předat neplatný Config. onConfigChanged navíc ignoruje události z NEAKTIVNÍ oblasti úložiště a změny nesouvisejících klíčů.

Oba doplňky znovu používají tentýž trychtýř čtení -> normaliseConfig -> návrat nad svými vlastními hostitelskými úložišti. extensions/outlook/src/roamingStorage.ts podokna úloh Outlooku obaluje Office.context.roamingSettings (synchronní get/set, potvrzené saveAsync); packages/gmail/src/propertiesStorage.ts doplňku pro Gmail obaluje PropertiesService.getUserProperties() (per uživatel, putující napříč zařízeními tohoto uživatele, kódováno JSON pod jedním klíčem a nevyžadující ŽÁDNÝ dodatečný rozsah OAuth — nikdy sdílené ScriptProperties). Oba vystavují getConfig/setConfig plus přístupové prvky relevantní pro kartu getIgnoreHosts/getHighlightMismatch a chrání nedostupný hostitelský povrch vyhrazenou chybou. Domovský spouštěč Gmailu vykresluje CardService kartu nastavení z getConfig a obsluha odeslání formuláře onSaveSettings sloučí parsované ignoreHosts/highlightMismatch do aktuální konfigurace a perzistuje ji přes setConfig; kontextový spouštěč provléká ignoreHosts do adaptéru a highlightMismatch do stavitele karty, čtouc obranně, takže selhání úložiště se vrátí k výchozím hodnotám spíše než by rozbilo analýzu zprávy.

Vzor kanonická-konfigurace + cílená-aktualizace

Tři cesty zápisu čtou kanonickou konfiguraci, mění přesně jednu věc a zapisují ji zpět — nikdy nepotvrzují neuložené úpravy formuláře ani znovu nevykreslují formulář:

Vlastní toggleEnabled akce panelu nástrojů (packages/webext/src/toolbarAction.ts) sleduje tentýž vzor ze strany pozadí.

Sestavení per engine a kontrakt manifestu

tooling/build.mjs řídí jediné sestavení esbuild pro každý cíl WebExtension. Deskriptor TARGETS jmenuje ZDROJOVÝ CÍL každého cíle: Chrome, Firefox a Thunderbird dodávají svůj vlastní src/; Edge, Opera a Safari deklarují chrome jako svůj zdroj; doplněk Outlook (Office.js) je svým vlastním zdrojem. ACTIVE_TARGETS (Chrome, Edge, Firefox, Opera a Thunderbird — Safari a Outlook jsou vyloučeny) je to, co --all/--package sestavuje; Safari a Outlook se sestavují jen když jsou pojmenovány explicitně, a doplněk Gmail (Apps Script) je samostatný balíček esbuild (make build-gmail) mimo smyčku --all. Tento balíček Gmailu cílí na runtime V8 Apps Script, který nemá ani ES moduly, ani nativní URL: je sestaven jako ESM a poté mu je odstraněn koncový příkaz export {…} (takže spouštěcí funkce zůstávají top-level globals, které Apps Script může vyvolat), a zabaluje malý polyfill URL (instalovaný jen když URL chybí), takže new URL(...) sdíleného jádra rozlišuje odkazy na straně serveru. buildTarget čistí dist/<target>/, kopíruje manifest.json a icons/ per cíl doslovně, zabaluje každý zdrojový skript jako IIFE (vkládané INJECTED_SCRIPTS content.ts/messageDisplay.ts nemohou být ES moduly a pozadí jsou také klasické workery/event pages) a sdílenou stránku možností jako ESM a kopíruje options.html/options.css z packages/webext/src/options/. webextension-polyfill je zabalen do každého skriptu.

tooling/version.mjs otiskuje verze: bere MAJOR.MINOR z kořenového package.json a připojuje automaticky se zvyšující číslo BUILD (čtené z nejvyšší stávající třetí komponenty napříč cílovými manifesty) do každého cílového manifestu, udržuje je v zámku. Většina cílů nese jediný manifest.json; doplněk Outlook nese dva verzované manifesty (manifest.json a manifest.xml) a oba se otiskují a lešení _template se otiskuje v zámku také. Kořenový package.json je udržován na téže verzi, ale zůstává platným semverem (čtyřdílná verze je povolena v manifestu, nikdy v package.json). tooling/icons.mjs (spouštěno přes make icons) rasterizuje jediný vektorový zdroj assets/icon.svg do icons/icon48.png a icons/icon128.png každého cíle, zkoušeje cairosvg, poté inkscape, poté ImageMagick.

Kontrakt manifestu — přesná požadovaná pole per engine — je jediným zdrojem pravdy v každém extensions/<engine>/test/manifest.smoke.test.mjs (např. assertManifestShape z extensions/chrome/test/manifest.smoke.test.mjs, který také sestaví cíl a potvrdí, že se každý odkazovaný soubor v distu rozliší). Per konvence deduplikace projektu se tvar pole po poli zde NEuvádí znovu; odkažte se na tyto kouřové testy a na poznámku „Kontrakt manifestu“ v plánu manuálních testů.

Internacionalizace (i18n)

Reveal URLs je lokalizováno do mnoha jazyků kromě angličtiny. Sada je jednozdrojová v packages/core/src/locales.json (SUPPORTED_LOCALES, nativní názvy a výchozí hodnota), kterou čtou jak balíčky rozšíření (přes rozlišený import JSON), tak plainové Node sestavení webu. Každý neanglický řetězec je strojově přeložený a čeká na lidskou revizi; marker provenience je per formát (_locales používají description každé zprávy, slovníky rozhraní webu jej zaznamenávají v site/i18n/README.md a přeložené zdroje dokumentů nesou první řádek HTML komentář).

Existují tři nezávislé lokalizační povrchy, záměrně nepropojené, a každý je rozdělen podél čáry doba-sestavení / runtime.

Potlačení „Aktivních webů“ e-mailového klienta (AD-3)

Cíl e-mailového klienta (dnes Thunderbird) již vidí každou vykreslenou zprávu, takže editor „Aktivní weby“ per hostitel je tam nadbytečný a je odstraněn. Rozhodnutí je neseno dokumentovaným deskriptorem per cíl, příznakem mailClient: true, na TARGETS.thunderbird z tooling/build.mjs — nikdy pevně zakódovaným názvem cíle v logice sestavení nebo UI — a je implementováno ve dvou polovinách:

Postoj k bezpečnosti a soukromí

Testování