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:
packages/coreindeholder REN logik — ingen browser-API'er, intet lager, ingen markup-sinks. Den er enhedstestbar i ren JavaScript og er den eneste kilde til sandhed for linkannotering, konfigurationsvalidering, matchmønster-matchning, host- sammenligning og kontrastbevidst farvelægning.packages/webextindkapsler WebExtension-API'erne omkring den kerne: indholds- livscyklussen, dynamisk content-script-registrering, indstillings-UI'et, baggrundskoblingen, værktøjslinjehandlingen og Thunderbirds beskedvisningssti.extensions/<engine>indeholder de tynde indgangspunkter pr. motor plus hver motorsmanifest.json.toolingindeholder scripts til build, versionsstempling og ikongenerering.featuresindeholder Cucumber-BDD-scenarierne og deres test-doubles.
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/core/src/config.ts—Config/SiteRule-skemaet, standardværdier og den enkelte valideringstragtnormaliseConfig.packages/core/src/matchPattern.ts— matchmønster-grammatikken, matcheren, specificitetsrangering og dækning af tildelte oprindelser.packages/core/src/linkProcessor.ts— den dokumentafgrænsede annotator.packages/core/src/hostMismatch.ts— kontrollen af uoverensstemmelse i registrerbart domæne.packages/core/src/styles.tsogpackages/core/src/contrast.ts— det indsprøjtbare stylesheet og kontrastbevidst farvelægning.packages/core/src/safeColour.ts— det sikre-farve-prædikatisSafeColour.
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:
- Den gør krav på framen synkront, før noget
await, viaclaimBootstrap, der læser/sætterBOOTSTRAP_MARKER-flaget på framenswindow. En statisk indbyggetcontent_scripts-indgang og et overlappende dynamisk brugerscript kan begge injicerecontent.jsi den samme frame; den delte isolated-world-windowgør dette til den korrekte engangsvagt, og at gøre krav før nogetawaitbetyder, at to næsten samtidige injektioner ikke begge kan passere den. - Den indlæser FØRST den persisterede konfiguration via
loadConfigWithRetry(BOOTSTRAP_CONFIG_RETRIESekstra 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 tildelerREVEAL_URLS_CSSviatextContent), så en mislykket læsning tilføjer ingen<style>, og et nyt forsøg kan ikke duplikere det. - Hvis konfigurationsindlæsningen mislykkes ved hvert forsøg, ruller
releaseBootstrapmarkøren tilbage (kun her, før annoteringen starter), så en senere geninjektion kan forsøge igen; nårstartførst har kørt, skal markøren bestå, fordi en ny annotator ikke ville eje den forrige gennemgangs noder. - Når
config.enabled, kalder denstart, og den registrerer altid enonConfigChanged-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:
- Beskærer registerposter, hvis anker ikke længere er forbundet.
- Indsamler kandidatankre med
collectAnchors(efterkommer-a[href]plus roden selv, når den er eta[href], dedupliceret). - Opløser hvert ankers destination med
resolveAnnotatableUrl, der returnerernull(spring over) for en tom/på-siden-href, en uparselbar href, ethvert andet skema endhttp:/https:eller en host lig med eller under enignoreHosts-post; den opløser relative href'er modbaseURI, så resultatet er absolut. - Tager den idempotente hurtige sti, når
isAnnotationIntactbekræ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. - Beregner uoverensstemmelse med
hostMismatchpå den rene ankertekst, respekterershowOnlyOnMismatch(efterlader ærlige links urørte) og kalderannotateAnchor.
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:
removeSite(options.ts): læsergetConfig, persisterersetConfigover etsites-array med kun den fjernede regel filtreret fra (persistér FØRST, derefterrow.remove(), derefter en betingetpermissions.removebesluttet ud fra den næste konfiguration). En persisteringsfejl kasterSiteRemovalSaveErrorog efterlader DOM'en urørt; en mislykket tilbagekaldelse kasterSitePermissionRevokeError, efter at rækken allerede er væk.addSite(options.ts): beskrevet ovenfor — validér, anmod om tilladelse, tilføj rækken, dereftersaveOptions.toggleEnabled(options.ts): den øjeblikkelige hoved-Aktivér-kontakt vender KUNenabledpå den lagrede konfiguration og skriver den tilbage, uden at gengive igen (så en igangværende redigering aldrig overskrives); ved en mislykket skrivning tilbagefører den afkrydsningsfeltet til den lagrede værdi.
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.
- Indstillingssiden (AD-1). Den leverer STATISK engelsk markup; hver oversættelig
node bærer en
data-i18n="<key>"- (tekst) ellerdata-i18n-<attr>="<key>"- (attribut) annotation. Ved runtime erpackages/webext/src/options/locale.tsapply-laget: denfetcher sidens egen pakkede_locales/<code>/messages.json(viaruntime.getURL, ingenweb_accessible_resourcesnødvendig), omskriver hver annoteret node KUN viatextContent(så formularens<code>-eksempel-hints forbliver intakte) og sætter<html lang>. Siden er FULDT lokaliseret: ved siden af den statiske prosa bærer de JS-byggede site-rækkeetiketter og Fjern-knappen endata-i18n-nøgle (med en indledendetranslate-oversat tekst), så det samme apply-lag genlokaliserer gengivne rækker uden en gengivelse, og de VIRKELIG dynamiske strenge — statuslinjen, tilføj-site-feedbacken og versionslinjen — gengives gennemlocale.ts'stranslate(<key>)(currentMessages → engelsk tilbagefald → nøgle) på kaldstidspunktet. Det engelske tilbagefald er_locales/en/messages.json-kataloget, der er IMPORTERET (bundlet) ind ioptions.jsved build-tid, såtranslateopløses til læsbar engelsk SYNKRONT fra modulindlæsning — en dynamisk streng, der gengives, før vælgeren falder på plads, eller efter at en runtime-kataloghentning mislykkes, degraderer aldrig til en rå beskednøgle.locale.tsudgiver det anvendte beskedkort og udløser sineonLocaleChange-lyttere efter hver anvendelse, såoptions.tsgengiver den aktuelt viste status/feedback/version og ompeger Online manual-linket ved hvert skift. Versionslinjen bruger enoptionsVersion-besked, hvis{version}-pladsholder erstattes med build-nummeret (token oversat, tal ordret). Manual-linket følger den aktive lokalitet til dens dok- variant (AD-9): engelsk beholder top-level-manual.html, hver anden lokalitet åbnernl/handleiding.htmlog bevarer?v=-versionsstemplet. Den aktive lokalitet er brugerens gemte valg eller, hvis det fejler, browsersproget opløst til et understøttet basissprog af kernensresolveLocale(AD-5); en ikke-understøttet eller fejlet lokalitet efterlader den forhåndsgengivne engelsk (ogtranslatedegraderer derefter til det engelske tilbagefald). Manualens Display language-vælger persisterer sit valg under en dedikeretuiLocale-nøgle istorage.local— aldrig i den synkroniseredeConfig, så den er pr. enhed og roamer aldrig. - Website-chrome (AD-2). Statisk-site-generatoren
tooling/site.mjsserver-gengiver hver chrome-node på engelsk, mens den annoterer dendata-i18n, og beholder<html lang="en-GB">(AD-4). Browser-side-indlæserensite/scripts/i18n.mjs(et afhængighedsfrit ES-modul) skifter derefter chromen til den besøgendes sprog efter rangorden — et persisteretlocalStorage-valg (revealUrlsSiteLocale) → det første understøttedenavigator.languages-basismatch (AD-5) → engelsk — idet den hentersite/i18n/<code>.json. I modsætning til options-apply-laget skifter den viainnerHTML, fordi site-chrome-værdierne bærer betroet inline-markup (<code>,<a>); en paritetstest fastlåser hver ikke-engelsk værdis tag-/attribut-/URL-sekvens byte-identisk med engelsk, så en oversættelse aldrig kan tabe et link eller bryde et tag. En manglende nøgle beholder det forhåndsgengivne engelske snapshot; en mislykket hentning efterlader den engelske side intakt (yndefuld degradering, aldrig blank). - Dokumentationssiderne (AD-8/AD-9). Dok-sider gengives PR. LOKALITET ved
build-tid, ikke runtime-skiftet. Engelske dokumenter forbliver på top-level-stierne; hver
anden lokalitet får en kopi under en
<code>/-mappe, fra en oversatdocs/<code>/<DOC>.md, når den findes, ellers den engelske kilde — et tilbagefald pr. side, sombuildSiteLOGGER frem for stiltiende at udelade. Dok-KROPPEN bærer ingendata-i18n(det er rå gengivet markdown), så runtime-chrome-indlæseren rører den aldrig; kun de delte chrome-noder skifter. Dok-sidens sprogvælger NAVIGERER til søster- lokalitetens side frem for at skifte på stedet. - Lokalisering af interne links (Finding 2). Det valgte sprog bæres med på tværs af
dok-navigation. Hvert INTERNT dok-anker (de fem sider
architecture.html,index.html,licence.html,manual.html,privacy.html) mærkesdata-doc-link="<page>"af generatoren, og i no-JavaScript-tilfældet opløses dens href allerede til den AKTUELLE sides egen lokalitetsvariant (navDocHref— de tre dok-sider pr. lokalitet lokaliseres; de kun-engelskeindex.html/licence.htmlforbliver top-level). Runtime-indlæserensrewriteDocLinksommålretter derefter hvert internt dok-anker — dem, der er mærket, OG ethvert katalog-injiceret almindeligt anker såsommanual.html#installing— til den AKTIVE lokalitets variant og bevarer enhver#hash/?queryog rører aldrig eksterne links. På en dok-side NAVIGERER et LAGRET valg, der adskiller sig fra sidens egen lokalitet, til den matchende variant; løkkevagten udløses kun ved et eksplicit lagret valg (aldrig en navigator-sprogpræference alene) og kun, når målet adskiller sig fra den allerede viste side. Katalogerne beholder selv deres engelske interne URL'er, så paritetstesten forbliver grøn — lokaliseringen er en ren runtime-gennemgang over DOM'en.
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:
- Build-tid.
copyAssetsafhænger afdescriptorFor(target).mailClient: for et mailklient-mål kører den den reneremoveSitesSection(html)-transformation (der fjerner hele<section class="sites">-host-editoren, inklusive hver tilføj-site- kontrol, og kasterMissingSourceError, hvis sektionen mangler, så en markup-ændring aldrig stiltiende kan udsende editoren) og skriver resultatet; browser-mål modtageroptions.htmlbyte-for-byte viacpSync. - Runtime. Den bundlede controller
packages/webext/src/options/options.tstolererer den fraværende sektion:renderSites/readSites/applyConfig/readFormsonderer#sites-listog no-op'er frem for at kasteOptionsFieldMissingError, ogreadFormUDELADERsites-nøglen, når sektionen er væk. Da udeladelse afsitesville ladenormaliseConfiggenudså de indbyggede standardværdier og droppe brugerens sites, genindsættersaveOptionsde lagredesitesfør normalisering, når sektionen er fraværende — det er det, der får en mailklient-gemning til at bevare de konfigurerede hosts. En runtime-vagt,guardMailClientSites, fjerner sektionen, hvis den stadig er til stede, og detekterer en mailklient ved tilstedeværelse af API/funktion (messages/messageDisplay/scripting.messageDisplaypå den injiceredebrowser), aldrig ved målnavn; den kører først iinitOptions, så den senere tilføj/liste-kobling naturligt springer de fraværende elementer over. I en browser-runtime er vagten en no-op.
Sikkerheds- og privatlivsholdning
- Intet netværk eller eksfiltrering. Intet modul udfører
fetch/XHReller noget andet netværkskald; udvidelsen læser kun den lagrede konfiguration og side-DOM'en. - Kun safe-DOM. Annotering og det injicerede stylesheet bruger udelukkende
createElement/textContent/typedestyle.*-egenskaber, aldriginnerHTML/insertAdjacentHTML/eval. For kernen håndhæves dette afpackages/core/test/purity.test.ts. - Alt eksternt/konfigurations-input normaliseret. Hver læsning og skrivning tragtes
gennem
normaliseConfig; farver passererisSafeColour; matchmønstre passererMATCH_PATTERN; content-root-selektorer afgrænses afCONTENT_ROOT_PATTERNog overdrages kun nogensinde tilquerySelectorAll/closest, aldrig interpoleret ind i markup eller CSS. - Mindste privilegium. Indbyggede webmail-oprindelser er faste
host_permissions; yderligere hosts er opt-in viaoptional_host_permissionsog en gestus-bundetpermissions.request. Værktøjslinjehandlingen erklærer ingen popup. - Gecko-dataindsamling. Firefox- og Thunderbird-manifesterne erklærer
browser_specific_settings.gecko.data_collection_permissions.required: ["none"]. - Licens. Projektet er AGPL-3.0-only (
LICENSE,package.json).
Test
- Vitest-enhedstests dækker begge pakker:
packages/core/test/*(konfiguration, kontrast, host-uoverensstemmelse, link-processor, typografi og purity-vagten) ogpackages/webext/test/*(indhold, lager, indstillinger, registrering, værktøjslinje, beskedvisning og en custom-site-ende-til-ende-test). Hver pakke kørervitest run. - Cucumber-BDD-scenarier bor i
features/(koblet afcucumber.json) og driver de rigtige@reveal-urls/webext-moduler gennem trofaste test-doubles ifeatures/support/webext.ts— særligtnormaliseReadBack, der spejler motoren, som UDELADER false-værdiede flag ved tilbagelæsning, så en eftergivende harness ikke kan skjule en geninstallationsløkke — over et jsdom-dokument opsat ifeatures/support/world.ts. - Manifest-røgtest pr. mål (
extensions/<engine>/test/manifest.smoke.test.mjs, Nodes indbyggedenode:test) fastslår hvert manifests nøjagtige form, og at den byggede dist opløser hver refereret fil.tooling/test/tilføjer build-, versions- og definition-of-done-tests. - CI (
.github/workflows/ci.yml) gater hvert push og pull request påmake test,make bddogmake lint(kørt direkte på runneren, idet Makefilens DockerRUN/IMAGE_DEPtilsidesættes). Versionsstempling og pakning er bevidst udelukket; de bor i den separate, tag-udløste.github/workflows/release.yml.