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 a Thunderbird — z jednoho sdíleného, čistého jádra kódu. (Safari je odloženo: má manifest a je sestavitelné, když je pojmenováno explicitně, ale je vyloučeno z výchozího sestavení a z CI.) 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).

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), a směruje každý přidaný Element přes processAddedNode, který anotuje uzel, který je uvnitř kořene, JE kořenem nebo kořen OBSAHUJE (líné SPA mountování). Takže churn v kořeni i sourozenecké tělo zprávy mountované později jsou oba 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ý vrací null (přeskočit) pro prázdný/vnitrostránkový href, neparsovatelný href, jakékoli schéma jiné než http:/https: nebo hostitele rovného nebo pod záznamem ignoreHosts; rozlišuje relativní hrefy oproti baseURI, takže výsledek je absolutní.
  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 pomocí hostMismatch na čistém textu kotvy, ctí showOnlyOnMismatch (poctivé odkazy ponechá nedotčené) a volá annotateAnchor.

annotateAnchor vykresluje pouze BEZPEČNÝ DOM. V režimu "inline" předsazuje <span class="reveal-urls-url">, jehož URL se nastavuje přes textContent (s předponou šipky a nezalomitelné mezery U+00A0, zkrácené truncateUrl) následované <br>, přičemž vlastní potomky odkazu ponechává nedotčené; připojený span pak dostane kontrastní pozadí přes applyContrastBackdrop. V režimu "title" uloží původní title (TitleSave), přepíše jej URL — jediný zápis do kotvy — a při zdůrazněné neshodě připojí sourozenecký odznak <span class="reveal-urls-warn"> nesoucí REVEAL_URLS_WARN_LABEL. 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í. Barvy a přepsání písma se aplikují přes typované vlastnosti element.style.* (applyColour/applyFontOverrides), nikdy interpolované do řetězce CSS. 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 porovnává viditelný text odkazu oproti jeho href podle registrovatelné domény, nikoli podle syrového názvu hostitele, takže poctivá poddoména 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.

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í Gmail, Proton (s allFrames) 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, normaliseRenderMode, 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í