Reveal URLs — Arkitektur

Dette dokument beskriver, hvordan Reveal URLs-udvidelsen er struktureret, og hvordan dens kernelogik fungerer, for bidragydere og tekniske læsere. Det citerer rigtige filer og symboler hele vejen igennem (i formen path symbol), så enhver påstand kan kontrolleres mod kildekoden. For den brugervendte beskrivelse, se manualen; for de manuelle verifikationstrin, se den manuelle testplan.

Oversigt

Reveal URLs er en enkelt MV3-WebExtension bygget til flere motorer — Chrome, Edge, Opera, Firefox og Thunderbird — fra én delt, ren kodekerne. (Safari er udskudt: den har et manifest og kan bygges, når den navngives eksplicit, men er udelukket fra standardbuilden og fra CI.) Udvidelsen afslører hvert links destinations-URL ved siden af linket i en gengivet e-mail og markerer et link, hvis synlige tekst navngiver et andet registrerbart domæne end dets href — det klassiske phishing-tegn.

Lageret er et pnpm/TypeScript-monorepo (pnpm-workspace.yaml, package.json med "private": true). Bundlingen er et enkelt esbuild-drevet script, tooling/build.mjs, der kompilerer de delte pakker og hver motors tynde indgangspunkter til en indlæsbar dist/<target>/-mappe. webextension-polyfill bundles ind i hvert script frem for at man stoler på den som en runtime-global, så den samme kildekode kører uændret på tværs af Chromium og Gecko.

To indbyggede e-mail-tilføjelser udvider den samme detektionskerne til flader, WebExtension'en ikke kan nå: et Outlook-tilføjelsesprogram (Office.js-opgaverude, der når Outlook på web/Windows/Mac/iOS/Android og Outlook.com) og en Gmail-tilføjelse (Apps Script CardService, der når Gmail-apperne på web/Android/iOS). Begge genbruger kun den RENE analyse — et nyt packages/core/src/findings.ts-modul (analyseAnchors/analyseHtml, en platformneutral Finding[]-model), der genbruger den eksisterende hostMismatch-logik og tldts-udledning af registrerbart domæne. Kun host-adapteren (hvordan teksten læses og parses) og præsentationen adskiller sig pr. flade: Outlook-opgaveruden parser teksten med sin egen DOMParser og kører på klientsiden; Gmail-tilføjelsen parser med node-html-parser og kører på serversiden på Googles Apps Script V8-infrastruktur (gratis hosting, da Google allerede besidder mailen; udrullet lokal-interaktivt via clasp). Fordi ingen af de to frameworks kan ændre den gengivne besked-DOM i læsetilstand (Office's setAsync/prependAsync er kun til skrivning; CardService gengiver kort, ikke besked-HTML), præsenterer begge tilføjelser et panel/kort med fund, ikke inline-annotering — den eksisterende linkProcessor.ts DOM-mutationssti og REVEAL_URLS_CSS genbruges IKKE af nogen af tilføjelserne. En relativ href løses kun mod en troværdig <base href> i e-mailen og springes ellers over, aldrig mod postkasse-/udbyder-oprindelsen (hvilket ville fabrikere en destination).

Kodebasen adskiller ansvarsområder skarpt:

Repository-layout

packages/core — den rene kerne

Hvert modul her importerer ingen udvidelses-API (browser.*/chrome.*/messenger.*) og bruger ingen streng-til-markup-DOM-sink (innerHTML/insertAdjacentHTML). Den kontrakt håndhæves mekanisk af packages/core/test/purity.test.ts, der globber hver src/**/*.ts, fjerner kommentarindhold via sin egen stripComments (så de docblocks, der legitimt NAVNGIVER de forbudte tokens, ikke udløser vagten) og derefter fastslår, at hverken EXTENSION_API_PATTERN eller UNSAFE_DOM_PATTERN matcher noget modul. Den offentlige flade reeksporteres fra packages/core/src/index.ts. Den eneste runtime-afhængighed er tldts (til public-suffix-bevidst domænesammenligning).

Modulerne er:

packages/webext — browser-API-indpakningerne

Moduler her MÅ bruge browser.* og er det eneste sted, WebExtension-API'erne berøres. Den offentlige flade reeksporteres fra packages/webext/src/index.ts. Modulerne er: content.ts (indholds-livscyklus), contentRegistration.ts (dynamisk registrering), storage.ts (konfigurationspersistens), background.ts (adfærd ved installation), toolbarAction.ts (til/fra + mærke), messageDisplay.ts og messageDisplayBackground.ts (Thunderbird) samt options/options.ts (controlleren til indstillingssiden).

extensions/<engine> — tynde indgangspunkter og manifester

Hver motor bærer sin egen manifest.json og icons/. Chrome, Firefox og Thunderbird leverer desuden en src/ med indgangspunkter; Edge, Opera og Safari bærer kun et manifest og ikoner og GENBRUGER Chromes src/ (erklæret af build- deskriptoren — se nedenfor). Indgangene er bevidst ganske små: for eksempel er extensions/chrome/src/content.ts blot void createContentController().bootstrap();, og extensions/chrome/src/background.ts kalder registerBackground, registerContentReconciliation og registerToolbarToggle. Firefox' baggrundsindgang er identisk med Chromes; Thunderbirds (extensions/thunderbird/src/background.ts) kalder i stedet registerBackground og registerMessageDisplay (den har ingen content scripts). extensions/_template er et kopiklart Chromium-stillads, ikke et build-mål.

tooling, features og røgtestene

tooling indeholder build.mjs, version.mjs og icons.mjs. features indeholder Gherkin-scenarierne, deres trindefinitioner og de trofaste test-doubles i features/support/webext.ts og features/support/world.ts. Hver motor bærer også en extensions/<engine>/test/manifest.smoke.test.mjs (_template's er template.smoke.test.mjs) — disse er den autoritative manifestkontrakt (se "Build pr. motor og manifestkontrakten" nedenfor).

Afslørings-pipelinen

Indholdsstien begynder ved en motorindgang såsom extensions/chrome/src/content.ts, der kalder createContentController().bootstrap() fra packages/webext/src/content.ts.

Bootstrap

createContentController bootstrap kører én gang pr. frame:

  1. Den gør krav på framen synkront, før noget await, via claimBootstrap, der læser/sætter BOOTSTRAP_MARKER-flaget på framens window. En statisk indbygget content_scripts-indgang og et overlappende dynamisk brugerscript kan begge injicere content.js i den samme frame; den delte isolated-world- window gør dette til den korrekte engangsvagt, og at gøre krav før noget await betyder, at to næsten samtidige injektioner ikke begge kan passere den.
  2. Den indlæser FØRST den persisterede konfiguration via loadConfigWithRetry (BOOTSTRAP_CONFIG_RETRIES ekstra forsøg ved en forbigående afvisning), idet den holder markøren på tværs af forsøgene, så en overlappende injektion, der allerede blev no-op'et, ikke efterlades strandet. Først når læsningen lykkes, injicerer den stylesheetet (injectStyles, der tildeler REVEAL_URLS_CSS via textContent), så en mislykket læsning tilføjer ingen <style>, og et nyt forsøg kan ikke duplikere det.
  3. Hvis konfigurationsindlæsningen mislykkes ved hvert forsøg, ruller releaseBootstrap markøren tilbage (kun her, før annoteringen starter), så en senere geninjektion kan forsøge igen; når start først har kørt, skal markøren bestå, fordi en ny annotator ikke ville eje den forrige gennemgangs noder.
  4. Når config.enabled, kalder den start, og den registrerer altid en onConfigChanged-lytter, der genanvender konfigurationen (dækkende overgangen deaktiveret→aktiveret).

Opløsning af den aktive site-regel

start (og applyConfig) kalder resolveSiteRule for at afgøre, om — og hvor — der skal annoteres. resolveSiteRule prøver først det kørende dokuments EGEN placering via resolveForHref, der filtrerer config.sites til de aktiverede regler og overlader til kernens selectMostSpecific, så det mest specifikke match vinder (en specifik indbygget slår et bredt bruger-wildcard). Når den egne placering ikke matcher noget OG dokumentets egen oprindelse er ugennemsigtig eller nedarvet — styret af hasOpaqueOrigin, sand kun for about:blank/about:srcdoc (nedarvet via INHERITED_ORIGIN_URLS) eller blob:/data: (OPAQUE_ORIGIN_SCHEMES) — falder den tilbage, i rækkefølge, til TOP-framen (readTopHref, det indbyggede Proton-besked- iframe-tilfælde), OPENER-vinduet (readOpenerHref, Outlook-pop-out-læsevinduet åbnet via window.open("about:blank")) og endelig document.referrer (readReferrer). Hver ekstern læsning er beskyttet mod cross-origin-adgang (som kaster) og bidrager kun med en kandidat, når den er læsbar. Et ALMINDELIGT ikke-matchet dokument er autoritativt og nedarver aldrig en regel fra omgivende kontekst, hvilket efterlader controlleren inaktiv.

Afgrænsning til indholdsroden og observation af mutationer

start registrerer den matchede regels contentRoot-selektor, opretter annotatoren via createAnnotator(config) og behandler hver matchet rod fra selectRoots(doc, contentRoot) (et querySelectorAll pakket ind i try/catch, så en ugyldig selektor fejler sikkert til []). Den observerer derefter doc.body (en stabil container) med OBSERVER_OPTIONS ({ childList: true, subtree: true }).

Observatøren føder processMutations, der gater hver mutation på indholdsroden: den genbehandler kun et mutations-target, når det er et Element inden i en rod (isWithinRoot, via closest), og dirigerer hvert tilføjet Element gennem processAddedNode, der annoterer en node, som er inde i en rod, ER en rod eller INDEHOLDER en (en doven SPA-montering). Så både churn inde i roden og en søsker- beskedtekst, der monteres senere, fanges, mens app-chrome uden for rødderne efterlades urørt. En null-annotator (udvidelsen er stoppet) er en no-op.

applyConfig forener en live konfigurationsændring: den river ned, når ingen regel matcher, eller aktiveret-flaget er slået fra; genstarter (tilbagefør + genobservér), når den opløste contentRoot ændrede sig; og omflyder ellers på stedet — annotator.setConfig derefter annotator.process over hver matchet rod — uden at genstarte observatøren.

Annotering af et enkelt anker

Annotatoren bor i packages/core/src/linkProcessor.ts. createAnnotator returnerer en Annotator over closure-tilstand, siden ikke kan læse: et stærkt Map indekseret efter anker-identitet, et data-ru-token pr. annotator fra generateToken (crypto.getRandomValues) og den aktuelle konfiguration. Dens process(root)-metode:

  1. Beskærer registerposter, hvis anker ikke længere er forbundet.
  2. Indsamler kandidatankre med collectAnchors (efterkommer-a[href] plus roden selv, når den er et a[href], dedupliceret).
  3. Opløser hvert ankers destination med resolveAnnotatableUrl, der returnerer null (spring over) for en tom/på-siden-href, en uparselbar href, ethvert andet skema end http:/https: eller en host lig med eller under en ignoreHosts-post; den opløser relative href'er mod baseURI, så resultatet er absolut.
  4. Tager den idempotente hurtige sti, når isAnnotationIntact bekræfter, at render- fingeraftrykket er uændret, og hver oprettet node stadig er forbundet og på position; ellers tilbageføres den forældede annotering og gengives igen.
  5. Beregner uoverensstemmelse med hostMismatch på den rene ankertekst, respekterer showOnlyOnMismatch (efterlader ærlige links urørte) og kalder annotateAnchor.

annotateAnchor gengiver kun SAFE-DOM. I "inline"-tilstand sætter den et <span class="reveal-urls-url"> foran, hvis URL sættes via textContent (med en pil og et U+00A0 ikke-brydende mellemrum foran, afkortet af truncateUrl) efterfulgt af et <br>, idet linkets egne børn efterlades urørte; det forbundne span får derefter en kontrast-baggrund via applyContrastBackdrop. I "title"-tilstand gemmer den den oprindelige title (TitleSave), overskriver den med URL'en — den eneste skrivning til ankeret — og tilføjer, ved en fremhævet uoverensstemmelse, et søsker- <span class="reveal-urls-warn">-mærke, der bærer REVEAL_URLS_WARN_LABEL. Hver oprettet node mærkes data-ru="<token>" (aldrig ankeret), og hver post registrerer sit configFingerprint, så en render-konfigurationsændring fremtvinger en omflydning. Farver og skrift-tilsidesættelser anvendes gennem typede element.style.*-egenskaber (applyColour/applyFontOverrides), aldrig interpoleret ind i en CSS-streng. revertOwned fjerner oprettede noder via reference og gendanner den gemte title; revertAll gør dette for hvert ejet anker og rydder registret.

truncateUrl arbejder over Unicode-KODEPUNKTER ([...displayHref]), så et snit aldrig deler et surrogatpar; den bevarer skema-og-host-oprindelsen og forkorter resten med en afsluttende URL_ELLIPSIS.

Host-uoverensstemmelse

packages/core/src/hostMismatch.ts hostMismatch sammenligner linkets synlige tekst mod dets href efter registrerbart domæne, ikke rå hostname, så et ærligt underdomæne ikke markeres, mens en look-alike gør. extractHostCandidates opdeler teksten og reducerer hvert token via hostCandidate (der kræver et punktum og et tldts-anerkendt ICANN public suffix, idet almindelig prikket tekst som e.g afvises). Både href-hosten og hver kandidat reduceres til deres registrerbare domæne med tldts.getDomain, og ENHVER kandidat, hvis domæne afviger fra href'ens, giver en uoverensstemmelse — så det at navngive den rigtige ondsindede host ved siden af en lokke-host kan ikke undertrykke advarslen. Den kaster aldrig.

Typografi og kontrast

packages/core/src/styles.ts indeholder det indsprøjtbare REVEAL_URLS_CSS (fast layout, vægt, form og en fast hvid, fuldt ugennemsigtig chip-baggrund — background: #ffffff, opacity: 1; farver interpoleres ALDRIG ind i det) og runtime-hjælperne applyColour/applyFontOverrides/applyContrastBackdrop. applyContrastBackdrop læser elementets opløste farve og den første ugennemsigtige baggrund, der findes ved at gå fra elementet SELV og opefter (dets egen baggrund — chippens faste hvide standard eller en host-sides mere specifikke tilsidesættelse — før nogen forfader) gennem standard-getComputedStyle (hentet fra element.ownerDocument?.defaultView, så det er frame-sikkert og kan stubbes), og, når packages/core/src/contrast.ts needsWhiteBackdrop rapporterer, at teksten ikke består WCAG AA (CONTRAST_THRESHOLD) mod den baggrund OG hvid reelt hjælper, sætter den style.backgroundColor = "white". contrast.ts leverer parseColour, relativeLuminance og contrastRatio; den parser de rgb()/rgba()/hex-former, getComputedStyle returnerer, og behandler en fuldt gennemsigtig farve som "ikke fundet".

Konfigurerbare hosts og indholdsafgrænsning

En SiteRule (packages/core/src/config.ts) angiver, HVOR annotering kører (match, allFrames), og HVILKEN container der afgrænser den (contentRoot), plus enabled og et builtin-flag. De leverede DEFAULT_SITES dækker Gmail, Proton (med allFrames) og begge Outlook-hosts. Config aggregerer de globale til/fra-kontakter, farver, skrift-tilsidesættelser og sites-arrayet.

Matchmønster-motoren

packages/core/src/matchPattern.ts indeholder en bevidst RESTRIKTIV grammatik MATCH_PATTERN ved tilføjelsestidspunktet (kun http/https, valgfrit indledende *.-host- wildcard, glob-sti; ingen port, intet *-skema, intet <all_urls>). matchesPattern matcher en URL mod et valideret mønster og delegerer stien til pathGlobMatches (hvert * matcher enhver tegnsekvens); den fejler sikkert til false. parsePatternParts opdeler et valideret mønster i host/path/scheme/ wildcardHost.

Overlappende regler opløses efter "mest specifik vinder": compareSiteSpecificity rangerer efter (1) eksakt host over wildcard-host, (2) længere literal host, (3) mere literal sti (literalPathLength), derefter (4) en deterministisk ASCII-afgørelse; selectMostSpecific returnerer den mest specifikke aktiverede regel, der matcher en URL.

Dækning af tildelte oprindelser er en SEPARAT, bevidst BREDERE matcher. permissions.getAll() kan rapportere tildelinger i den fulde WebExtension-grammatik (<all_urls>, *://*/*, https://*/*, *://*.host/*, https://host/*), som det restriktive MATCH_PATTERN ville afvise. parseGrantedOrigin parser dem til GrantedOriginParts, og originCovers rapporterer, om en tildelt oprindelse dækker en regels match (f.eks. dækker en bred https://*/* tildeling reelt enhver https-regel). matchAllowsOriginFallback rapporterer, om en regels sti er nøjagtigt ORIGIN_FALLBACK_PATH (/*) — den sikre fællesmængde af Chromium- og Gecko/Firefox 128-begrænsningerne på det dynamiske opaque-origin-tilbagefald.

Dynamisk registrering

packages/webext/src/contentRegistration.ts registrerer ét dynamisk content script pr. brugertilføjet regel. Indbyggede betjenes af de statiske content_scripts-indgange og registreres ALDRIG dynamisk. desiredContentScripts filtrerer config.sites til ikke-indbyggede, aktiverede regler, hvis oprindelse isOriginGranted (delegerer til originCovers), mapper hver til en RegisteredContentScript med en stabil id (contentScriptId, en FNV-1a-hash af matchet, så id'et kun bruger det API-sikre tegnsæt) og sætter matchOriginAsFallback: true KUN når matchAllowsOriginFallback(rule.match) holder (en /*-sti). At gate flaget på denne måde forhindrer, at én regel med en ikke-overensstemmende sti får hele batch- registreringen til at blive afvist; en sådan regel registreres uden pop-out-dækning efter design.

reconcileContentScriptsOnce læser de tildelte oprindelser, den lagrede konfiguration og de aktive registreringer og konvergerer derefter via en EGENSKABSBEVIDST diff: sameRegistration projicerer både den ønskede deskriptor og den aktive tilbagelæsning ned på en normaliseret form (ved at anvende hvert felts reelle WebExtension-standard — allFrames/matchOriginAsFallback har standard false, persistAcrossSessions har standard TRUE, runAt har standard document_idle), så et nyligt registreret script sammenlignes som LIG MED den deskriptor, der frembragte det, og ingen geninstallationsløkke kan opstå. Da der ikke er noget updateContentScripts, afregistreres et ændret script og registreres derefter igen. reconcileContentScripts indkapsler dette med et igangværende løfte (inFlight), så to næsten samtidige udløsere (en konfigurationsændring OG permissions.onAdded, der begge udløses ved en site-tilføjelse) serialiseres frem for at kappes. registerContentReconciliation kobler udløserne (permissions.onAdded/onRemoved, onConfigChanged) og konvergerer én gang ved opstart; den importeres kun af Chrome- og Firefox-baggrundsindgangene.

Opt-in-tilladelsesflowet

De statiske manifester erklærer optional_host_permissions (http://*/*, https://*/*), så en bruger kan tildele en vilkårlig yderligere web-host ved runtime. packages/webext/src/options/options.ts addSite validerer HELE kandidatreglen (match OG content-root) gennem normaliseSiteRule, FØR der anmodes om nogen tilladelse, udleder host-oprindelsen med matchOrigin og kalder browser.permissions.request inden for Tilføj-knappens klik-gestus; kun ved tildeling tilføjer den en builtin:false-række og persisterer gennem den kanoniske skrivesti.

Konfiguration, lager og det kanoniske mønster

Den enkelte valideringstragt

normaliseConfig i packages/core/src/config.ts er den ene kanoniske læsesti: den tvinger og afgrænser hvert felt, falder tilbage til standardværdien ved alt ugyldigt og kaster aldrig. Den er bygget af validatorer pr. felt — normaliseBoolean, normaliseRenderMode, normaliseMaxLength, normaliseIgnoreHosts (reducerer hver post til et bart punycode-hostname via bareHostname), normaliseMatchColour/normaliseMismatchColour (styret af safeColour.ts isSafeColour), normaliseCssSize, normaliseFontWeight, normaliseContentRoot (afgrænset af CONTENT_ROOT_PATTERN/CONTENT_ROOT_MAX_LENGTH), normaliseMatchPattern (styret af MATCH_PATTERN) og normaliseSites. normaliseSites kasserer afviste, deduplikerer efter match, TVINGER builtin: true på enhver regel, hvis match svarer til en indbyggets (lukker exact-match-shadows- built-in-/dobbeltinjektions-vektoren), genudsår enhver manglende indbygget (så et manipuleret lager ikke kan droppe en kerneudbyder) og sorterer alfanumerisk efter match for en kanonisk rækkefølge. configFingerprint hasher kun de render-påvirkende felter (eksklusive enabled, ignoreHosts og sites, der driver start/stop og spring frem for omflydning) via fnv1a.

Lager

packages/webext/src/storage.ts indkapsler WebExtension-lager-API'et. configArea bruger browser.storage.sync, når den er tilgængelig (så indstillinger roamer), og falder tilbage til browser.storage.local (f.eks. på Thunderbird); configAreaName rapporterer, hvilken der er aktiv. getConfig, setConfig og onConfigChanged tragter alle deres rå værdi gennem normaliseConfig, så et misdannet eller manipuleret lager aldrig kan overdrage en ugyldig Config til en kalder. onConfigChanged ignorerer desuden hændelser fra det INAKTIVE lagerområde og ændringer af ikke-relaterede nøgler.

De to tilføjelser genbruger den samme læs -> normaliseConfig -> returnér-tragt over deres egne host-lagre. Outlook-opgaverudens extensions/outlook/src/roamingStorage.ts indkapsler Office.context.roamingSettings (synkron get/set, committet med saveAsync); Gmail-tilføjelsens packages/gmail/src/propertiesStorage.ts indkapsler PropertiesService.getUserProperties() (pr. bruger, roamende på tværs af den brugers enheder, JSON-kodet under én nøgle og uden behov for et ekstra OAuth-scope — aldrig den delte ScriptProperties). Begge eksponerer getConfig/setConfig plus de kort-relevante getIgnoreHosts/getHighlightMismatch-accessorer og beskytter en utilgængelig host- flade med en dedikeret fejl. Gmail-forsidens trigger gengiver et CardService- indstillingskort fra getConfig, og onSaveSettings-formularsendelseshåndteringen fletter de parsede ignoreHosts/highlightMismatch ind på den aktuelle konfiguration og persisterer den gennem setConfig; den kontekstuelle trigger fører ignoreHosts ind i adapteren og highlightMismatch ind i kortbyggeren og læser defensivt, så en lagerfejl falder tilbage til standardværdier frem for at bryde beskedanalysen.

Mønstret kanonisk konfiguration + målrettet opdatering

Tre skrivestier læser den kanoniske konfiguration, ændrer præcis én ting og skriver den tilbage — uden nogensinde at committe ugemte formularredigeringer eller gengive formularen igen:

Værktøjslinjehandlingens egen toggleEnabled (packages/webext/src/toolbarAction.ts) følger det samme mønster fra baggrundssiden.

Build pr. motor og manifestkontrakten

tooling/build.mjs driver en enkelt esbuild-build for hvert WebExtension-mål. TARGETS- deskriptoren navngiver hvert måls KILDE-MÅL: Chrome, Firefox og Thunderbird leverer deres egen src/; Edge, Opera og Safari erklærer chrome som deres kilde; Outlook-tilføjelsen (Office.js) er sin egen kilde. ACTIVE_TARGETS (Chrome, Edge, Firefox, Opera og Thunderbird — Safari og Outlook er udelukket) er, hvad --all/--package bygger; Safari og Outlook bygger kun, når de navngives eksplicit, og Gmail-tilføjelsen (Apps Script) er en separat esbuild-bundle (make build-gmail) uden for --all-løkken. Den Gmail-bundle målretter Apps Scripts V8-runtime, der hverken har ES-moduler eller et indbygget URL: den bygges som ESM og får derefter sin afsluttende export {…}-sætning fjernet (så trigger-funktionerne forbliver top-level- globaler, som Apps Script kan kalde), og den bundler en lille URL-polyfill (kun installeret, når URL er fraværende), så den delte kernes new URL(...) opløser links på serversiden. buildTarget rydder dist/<target>/, kopierer mål-specifik manifest.json og icons/ ordret, bundler hvert kildescript som IIFE (de injicerede INJECTED_SCRIPTS content.ts/messageDisplay.ts kan ikke være ES-moduler, og baggrunde er også klassiske workers/event pages) og den delte indstillingsside som ESM, og kopierer options.html/options.css fra packages/webext/src/options/. webextension-polyfill bundles ind i hvert script.

tooling/version.mjs stempler versioner: den tager MAJOR.MINOR fra roden package.json og tilføjer et auto-inkrementerende BUILD-nummer (læst fra den højeste eksisterende tredje komponent på tværs af mål-manifesterne) i hvert mål- manifest, så de holdes i takt. De fleste mål bærer et enkelt manifest.json; Outlook-tilføjelsen bærer to versionerede manifester (manifest.json og manifest.xml) og begge stemples, og _template-stilladset stemples også i takt. Roden package.json holdes på samme version, men forbliver gyldig semver (en firdelt version er tilladt i et manifest, aldrig i package.json). tooling/icons.mjs (kørt via make icons) rasteriserer den enkelte vektorkilde assets/icon.svg til hvert måls icons/icon48.png og icons/icon128.png og prøver cairosvg, derefter inkscape, derefter ImageMagick.

Manifestkontrakten — de nøjagtige påkrævede felter pr. motor — er den eneste kilde til sandhed i hver extensions/<engine>/test/manifest.smoke.test.mjs (f.eks. extensions/chrome/test/manifest.smoke.test.mjs's assertManifestShape, der også bygger målet og bekræfter, at hver refereret fil opløses i dist'en). Ifølge projektets de-dupliceringskonvention er feltet-for-felt-formen IKKE genopregnet her; se de røgtest og "Manifest contract"-noten i den manuelle testplan.

Internationalisering (i18n)

Reveal URLs er lokaliseret til mange sprog ud over engelsk. Sættet er enkelt-kildet i packages/core/src/locales.json (SUPPORTED_LOCALES, indfødte navne og standarden), som både udvidelsen bundler (via den opløste JSON-import) og den rene Node-website-build læser. Hver ikke-engelsk streng er maskinoversat og afventer menneskelig gennemgang; herkomstmarkøren er pr. format (_locales bruger hver beskeds description, website-chrome-ordbøgerne registrerer den i site/i18n/README.md, og oversatte dok-kilder bærer en HTML-kommentar på første linje).

Der er tre uafhængige lokaliseringsflader, bevidst ikke kryds-koblet, og hver er opdelt langs en build-time-/runtime-linje.

Undertrykkelse af "Aktive sites" på mailklienter (AD-3)

Et mailklient-mål (Thunderbird i dag) ser allerede hver gengivet besked, så den per-host "Aktive sites"-editor er overflødig dér og fjernes. Beslutningen bæres af et dokumenteret deskriptor-flag pr. mål, mailClient: true, på tooling/build.mjs's TARGETS.thunderbird — aldrig et hardkodet målnavn i build- eller UI-logik — og er implementeret i to halvdele:

Sikkerheds- og privatlivsholdning

Test