Reveal URLs — Arkitektur
Dette dokumentet beskriver hvordan Reveal URLs-utvidelsen er strukturert og hvordan
dens kjernelogikk fungerer, for bidragsytere og tekniske lesere. Det siterer reelle
filer og symboler gjennomgående (på formen path symbol) slik at enhver påstand kan
kontrolleres mot kildekoden. For den brukervendte beskrivelsen, se håndboken;
for de manuelle verifiseringstrinnene, se manuell testplan.
Oversikt
Reveal URLs er en enkelt MV3-WebExtension bygget for flere motorer — Chrome,
Edge, Opera, Firefox og Thunderbird — fra én delt, ren kodekjerne. (Safari
er utsatt: den har et manifest og kan bygges når den navngis eksplisitt, men er
ekskludert fra standardbygget og fra CI.) Utvidelsen avslører hver lenkes
destinasjons-URL ved siden av lenken i en gjengitt e-post, og merker en lenke hvis
synlige tekst navngir et annet registrerbart domene enn dens href — det klassiske
phishing-tegnet.
Kodelageret er en pnpm/TypeScript-monorepo (pnpm-workspace.yaml,
package.json med "private": true). Bunting er et enkelt esbuild-drevet
skript, tooling/build.mjs, som kompilerer de delte pakkene og hver
motors tynne inngangspunkter til en lastbar dist/<target>/-mappe.
webextension-polyfill buntes inn i hvert skript i stedet for å baseres på som en
runtime-global, slik at den samme kildekoden kjører uendret på tvers av Chromium og Gecko.
To native e-posttillegg utvider den samme deteksjonskjernen til flater
WebExtension ikke kan nå: et Outlook-tillegg (Office.js-oppgaverute, som når
Outlook på web/Windows/Mac/iOS/Android og Outlook.com) og et Gmail-tillegg
(Apps Script CardService, som når Gmail-appene på web/Android/iOS). Begge gjenbruker kun
den RENE analysen — en ny packages/core/src/findings.ts-modul
(analyseAnchors/analyseHtml, en plattformnøytral Finding[]-modell) som gjenbruker
den eksisterende hostMismatch-logikken og tldts-utledningen av registrerbart domene. Bare
vertsadapteren (hvordan meldingsteksten leses og parses) og presentasjonen skiller seg per
flate: Outlook-oppgaveruten parser meldingsteksten med sin egen DOMParser og kjører
klientsiden; Gmail-tillegget parser med node-html-parser og kjører
serversiden på Googles Apps Script V8-infrastruktur (gratis hosting, siden
Google allerede har e-posten; distribuert lokalt-interaktivt via clasp). Fordi
ingen av rammeverkene kan endre den gjengitte meldings-DOM-en i lesemodus (Office sine
setAsync/prependAsync er kun for skriving; CardService gjengir kort, ikke meldings-HTML),
presenterer begge tilleggene et panel/kort med funn, ikke innebygd annotering —
den eksisterende linkProcessor.ts-DOM-endringsbanen og REVEAL_URLS_CSS gjenbrukes IKKE
av noen av tilleggene. En relativ href løses kun mot en pålitelig
<base href> i e-posten og hoppes ellers over, aldri mot postkassens/leverandørens
origin (som ville fabrikkere en destinasjon).
Kodebasen skiller ansvarsområder skarpt:
packages/coreinneholder REN logikk — ingen nettleser-API-er, ingen lagring, ingen markup-sluk. Den er enhetstestbar i ren JavaScript og er den eneste sannhetskilden for lenkeannotering, konfigurasjonsvalidering, samsvarsmønster-matching, verts- sammenligning og kontrastbevisst fargelegging.packages/webextpakker WebExtension-API-ene rundt den kjernen: innholds- livssyklusen, dynamisk registrering av innholdsskript, innstillingsgrensesnittet, bakgrunns- koblingen, verktøylinjehandlingen og Thunderbirds meldingsvisningsbane.extensions/<engine>inneholder de tynne inngangspunktene per motor pluss hver motorsmanifest.json.toolinginneholder skriptene for bygging, versjonsstempling og ikongenerering.featuresinneholder Cucumber-BDD-scenariene og deres testdobler.
Kodelagerstruktur
packages/core — den rene kjernen
Hver modul her importerer ingen utvidelses-API (browser.*/chrome.*/messenger.*)
og bruker ingen streng-til-markup-DOM-sluk (innerHTML/insertAdjacentHTML). Den
kontrakten håndheves mekanisk av packages/core/test/purity.test.ts, som
globber hver src/**/*.ts, fjerner kommentarinnhold via sin egen stripComments
(slik at docblocks som legitimt NAVNGIR de forbudte symbolene ikke utløser
vakten) og deretter bekrefter at verken EXTENSION_API_PATTERN eller
UNSAFE_DOM_PATTERN matcher noen modul. Den offentlige flaten re-eksporteres fra
packages/core/src/index.ts. Den eneste runtime-avhengigheten er tldts (for
domenesammenligning som tar hensyn til offentlige suffiks).
Modulene er:
packages/core/src/config.ts—Config/SiteRule-skjemaet, standardverdier og den enkle valideringstraktennormaliseConfig.packages/core/src/matchPattern.ts— samsvarsmønster-grammatikken, matcheren, spesifisitetsrangeringen og dekningen av tildelte origins.packages/core/src/linkProcessor.ts— den dokument-avgrensede annotereren.packages/core/src/hostMismatch.ts— sjekken for avvik i registrerbart domene.packages/core/src/styles.tsogpackages/core/src/contrast.ts— det injiserbare stilarket og kontrastbevisst fargelegging.packages/core/src/safeColour.ts— sikker-farge-predikatetisSafeColour.
packages/webext — nettleser-API-innpakningene
Moduler her KAN bruke browser.* og er det eneste stedet WebExtension-API-ene
berøres. Den offentlige flaten re-eksporteres fra packages/webext/src/index.ts.
Modulene er: content.ts (innholdslivssyklus), contentRegistration.ts
(dynamisk registrering), storage.ts (konfigurasjonspersistens), background.ts
(oppførsel ved installasjon), toolbarAction.ts (veksling + merke), messageDisplay.ts
og messageDisplayBackground.ts (Thunderbird), og options/options.ts (kontrolleren
for innstillingssiden).
extensions/<engine> — tynne inngangspunkter og manifester
Hver motor bærer sin egen manifest.json og icons/. Chrome, Firefox og
Thunderbird leverer i tillegg en src/ med inngangspunkter; Edge, Opera og Safari
bærer kun et manifest og ikoner og GJENBRUKER Chromes src/ (deklarert av bygge-
beskrivelsen — se nedenfor). Inngangspunktene er bevisst bittesmå: for eksempel
er extensions/chrome/src/content.ts bare
void createContentController().bootstrap();, og
extensions/chrome/src/background.ts kaller registerBackground,
registerContentReconciliation og registerToolbarToggle. Firefox' bakgrunns-
inngang er identisk med Chromes; Thunderbirds
(extensions/thunderbird/src/background.ts) kaller i stedet registerBackground og
registerMessageDisplay (den har ingen innholdsskript). extensions/_template
er et kopiklart Chromium-stillas, ikke et byggemål.
tooling, features, og røyktestene
tooling inneholder build.mjs, version.mjs og icons.mjs. features inneholder
Gherkin-scenariene, deres trinndefinisjoner og de tro testdoblene i
features/support/webext.ts og features/support/world.ts. Hver motor bærer også
en extensions/<engine>/test/manifest.smoke.test.mjs (_template sin er
template.smoke.test.mjs) — disse er den autoritative manifestkontrakten (se
«Bygg per motor og manifestkontrakten» nedenfor).
Avsløringspipelinen
Innholdsbanen begynner ved et motorinngangspunkt som
extensions/chrome/src/content.ts, som kaller createContentController().bootstrap()
fra packages/webext/src/content.ts.
Bootstrap
createContentController bootstrap kjører én gang per ramme:
- Den hevder rammen synkront, før noe
await, viaclaimBootstrap, som leser/setterBOOTSTRAP_MARKER-flagget på rammenswindow. En statisk innebygdcontent_scripts-oppføring og et overlappende dynamisk brukerskript kan begge injiserecontent.jsi den samme rammen; den delte isolerte-verden-windowgjør dette til den riktige engangsvakten, og å hevde før noeawaitbetyr at to nær-samtidige injeksjoner ikke begge kan passere den. - Den laster den lagrede konfigurasjonen FØRST via
loadConfigWithRetry(BOOTSTRAP_CONFIG_RETRIESekstra forsøk ved en forbigående avvisning), og holder markøren på tvers av forsøkene slik at en overlappende injeksjon som allerede ble en no-op ikke blir strandet. Først når lesingen lykkes injiserer den stilarket (injectStyles, som tilordnerREVEAL_URLS_CSSviatextContent), slik at en mislykket lesing ikke legger til noe<style>og et nytt forsøk ikke kan duplisere det. - Hvis konfigurasjonslasting mislykkes ved hvert forsøk, ruller
releaseBootstrapmarkøren tilbake (kun her, før annoteringen starter) slik at en senere reinjeksjon kan prøve på nytt; nårstarthar kjørt må markøren bestå, fordi en ny annoterer ikke ville eie nodene fra forrige gjennomgang. - Når
config.enabledkaller denstart, og den registrerer alltid enonConfigChanged-lytter som tar konfigurasjonen i bruk på nytt (som dekker overgangen fra deaktivert til aktivert).
Bestemme den aktive nettstedsregelen
start (og applyConfig) kaller resolveSiteRule for å avgjøre om — og hvor
— det skal annoteres. resolveSiteRule prøver først det kjørende dokumentets EGEN
plassering via resolveForHref, som filtrerer config.sites til de aktiverte reglene og
overlater til kjernens selectMostSpecific slik at det mest spesifikke samsvaret vinner (en
spesifikk innebygd regel slår et bredt bruker-jokertegn). Når den egne plasseringen ikke
matcher noe OG dokumentets egen origin er ugjennomsiktig eller arvet — styrt av hasOpaqueOrigin,
sann kun for about:blank/about:srcdoc (arvet via INHERITED_ORIGIN_URLS)
eller blob:/data: (OPAQUE_ORIGIN_SCHEMES) — faller den tilbake, i rekkefølge, til
TOPP-rammen (readTopHref, det innebygde Proton-meldings-iframe-tilfellet), ÅPNER-
vinduet (readOpenerHref, Outlooks utsprett-lesevindu åpnet via
window.open("about:blank")) og til slutt document.referrer (readReferrer).
Hver ekstern lesing er beskyttet mot kryss-origin-tilgang (som kaster) og
bidrar med en kandidat kun når den er lesbar. Et VANLIG dokument uten samsvar er
autoritativt og arver aldri en regel fra omgivelseskonteksten, slik at
kontrolleren forblir inaktiv.
Avgrensning til innholdsroten og observering av mutasjoner
start registrerer den matchede regelens contentRoot-velger, oppretter annotereren
via createAnnotator(config), og behandler hver matchede rot fra
selectRoots(doc, contentRoot) (en querySelectorAll pakket i try/catch slik at en
ugyldig velger feiler sikkert til []). Den observerer deretter doc.body (en stabil
beholder) med OBSERVER_OPTIONS ({ childList: true, subtree: true }).
Observatøren mater processMutations, som styrer hver mutasjon på innholds-
roten: den behandler en mutasjons-target på nytt kun når den er et Element innenfor en
rot (isWithinRoot, via closest), og ruter hvert tilføyde Element gjennom
processAddedNode, som annoterer en node som er inne i en rot, ER en rot, eller
INNEHOLDER en (en lat SPA-montering). Så både endringer inne i roten og en søsken-
meldingstekst som monteres senere fanges opp, mens app-rammeverk utenfor røttene forblir
urørt. En null-annoterer (utvidelsen er stoppet) er en no-op.
applyConfig forener en levende konfigurasjonsendring: den river ned når ingen regel matcher
eller aktivert-flagget er av; starter på nytt (tilbakestill + observer på nytt) når den
bestemte contentRoot endret seg; og ellers flyter den om på stedet — annotator.setConfig
deretter annotator.process over hver matchede rot — uten å starte
observatøren på nytt.
Annotere et enkelt anker
Annotereren bor i packages/core/src/linkProcessor.ts. createAnnotator
returnerer en Annotator over closure-tilstand siden ikke kan lese: et sterkt Map
nøklet på anker-identitet, et data-ru-token per annoterer fra
generateToken (crypto.getRandomValues), og den gjeldende konfigurasjonen. Dens
process(root)-metode:
- Beskjærer registeroppføringer hvis anker ikke lenger er tilkoblet.
- Samler kandidatankre med
collectAnchors(etterkommer-a[href]pluss selve roten når den er ena[href], deduplisert). - Løser hvert ankers destinasjon med
resolveAnnotatableUrl, som returnerernull(hopp over) for en tom/på-siden-href, en uparsbar href, ethvert skjema annet ennhttp:/https:, eller en vert lik eller under enignoreHosts-oppføring; den løser relative href-er motbaseURIslik at resultatet er absolutt. - Tar den idempotente raske banen når
isAnnotationIntactbekrefter at gjengivelsens fingeravtrykk er uendret og hver opprettet node fortsatt er tilkoblet og i posisjon; ellers tilbakestiller den den foreldede annoteringen og gjengir på nytt. - Beregner avvik med
hostMismatchpå den rene ankerteksten, respekterershowOnlyOnMismatch(lar ærlige lenker være urørt), og kallerannotateAnchor.
annotateAnchor gjengir kun SIKKER-DOM. I "inline"-modus setter den inn foran et
<span class="reveal-urls-url"> hvis URL settes via textContent (prefikset med
en pil og et U+00A0-hardt mellomrom, avkortet av truncateUrl) etterfulgt av
en <br>, og lar lenkens egne barn være urørt; det tilkoblede spennet får deretter en
kontrastbakgrunn via applyContrastBackdrop. I "title"-modus lagrer den den
opprinnelige title (TitleSave), overskriver den med URL-en — den eneste skrivingen til
ankeret — og, ved et fremhevet avvik, legger den til et søsken-
<span class="reveal-urls-warn">-merke som bærer REVEAL_URLS_WARN_LABEL. Hver
opprettet node er merket data-ru="<token>" (aldri ankeret), og hver oppføring
registrerer sin configFingerprint slik at en endring i gjengivelseskonfigurasjonen tvinger
en omflyt. Farger og skriftoverstyringer brukes gjennom typede element.style.*-egenskaper
(applyColour/applyFontOverrides), aldri interpolert inn i en CSS-streng. revertOwned
fjerner opprettede noder ved referanse og gjenoppretter den lagrede tittelen;
revertAll gjør dette for hvert eid anker og tømmer registeret.
truncateUrl opererer over Unicode-KODEPUNKTER ([...displayHref]), slik at et kutt
aldri deler et surrogatpar; den bevarer skjema-og-vert-originen og
forkorter resten med en avsluttende URL_ELLIPSIS.
Vertsavvik
packages/core/src/hostMismatch.ts hostMismatch sammenligner lenkens synlige
tekst mot dens href etter registrerbart domene, ikke rått vertsnavn, slik at en honest
underdomene ikke merkes mens en etterligning blir det. extractHostCandidates deler
teksten og reduserer hvert token via hostCandidate (som krever et punktum og et
tldts-gjenkjent ICANN-offentlig suffiks, og avviser vanlig punktert tekst som
e.g). Både href-verten og hver kandidat reduseres til sitt registrerbare
domene med tldts.getDomain, og ENHVER kandidat hvis domene avviker fra
href-ens gir et avvik — slik at det å navngi den ekte ondsinnede verten ved siden av en
lokkevert ikke kan undertrykke advarselen. Den kaster aldri.
Stiler og kontrast
packages/core/src/styles.ts inneholder det injiserbare REVEAL_URLS_CSS (fast layout,
vekt, form og en fast hvit, fullstendig ugjennomsiktig chip-bakgrunn — background: #ffffff,
opacity: 1; farger interpoleres ALDRI inn i det) og runtime-
hjelperne applyColour/applyFontOverrides/applyContrastBackdrop.
applyContrastBackdrop leser elementets bestemte farge og den første ugjennomsiktige
bakgrunnen funnet ved å gå fra elementet SELV og oppover (dets egen bakgrunn — chipens
faste hvite standard eller en vertssides mer spesifikke overstyring — før noen
forfedre) gjennom standard getComputedStyle (hentet fra
element.ownerDocument?.defaultView, slik at den er ramme-sikker og kan stubbes) og,
når packages/core/src/contrast.ts needsWhiteBackdrop rapporterer at teksten ikke består
WCAG AA (CONTRAST_THRESHOLD) mot den bakgrunnen OG hvitt genuint hjelper,
setter style.backgroundColor = "white". contrast.ts tilbyr parseColour,
relativeLuminance og contrastRatio; den parser rgb()/rgba()/hex-formene
getComputedStyle returnerer og behandler en fullstendig gjennomsiktig farge som «ikke funnet».
Konfigurerbare verter og innholdsavgrensning
En SiteRule (packages/core/src/config.ts) sier HVOR annotering kjører (match,
allFrames) og HVILKEN beholder som avgrenser den (contentRoot), pluss enabled og et
builtin-flagg. De medfølgende DEFAULT_SITES dekker Gmail, Proton (med
allFrames) og begge Outlook-vertene. Config samler de globale bryterne,
fargene, skriftoverstyringene og sites-tabellen.
Samsvarsmønster-motoren
packages/core/src/matchPattern.ts inneholder en bevisst RESTRIKTIV grammatikk for
innleggingstidspunkt MATCH_PATTERN (kun http/https, valgfritt innledende *.-
vertsjokertegn, glob-sti; ingen port, intet *-skjema, ingen <all_urls>). matchesPattern
matcher en URL mot et validert mønster, og delegerer stien til pathGlobMatches (hver
* matcher en hvilken som helst tegnsekvens); den feiler sikkert til false.
parsePatternParts deler et validert mønster inn i host/path/scheme/
wildcardHost.
Overlappende regler løses ved «mest spesifikke vinner»:
compareSiteSpecificity rangerer etter (1) eksakt vert over jokertegn-vert, (2) lengre
bokstavelig vert, (3) mer bokstavelig sti (literalPathLength), deretter (4) en
deterministisk ASCII-avgjørelse; selectMostSpecific returnerer den mest spesifikke
aktiverte regelen som matcher en URL.
Dekning av tildelte origins er en SEPARAT, bevisst BREDERE matcher.
permissions.getAll() kan rapportere tildelinger i den fulle WebExtension-grammatikken
(<all_urls>, *://*/*, https://*/*, *://*.host/*, https://host/*) som
det restriktive MATCH_PATTERN ville avvise. parseGrantedOrigin parser disse
til GrantedOriginParts, og originCovers rapporterer hvorvidt en tildelt origin
dekker en regels match (f.eks. dekker en bred https://*/* tildeling genuint hver
https-regel). matchAllowsOriginFallback rapporterer hvorvidt en regels sti er nøyaktig
ORIGIN_FALLBACK_PATH (/*) — det sikre snittet av Chromium- og
Gecko/Firefox 128-begrensningene på den dynamiske ugjennomsiktig-origin-reserven.
Dynamisk registrering
packages/webext/src/contentRegistration.ts registrerer ett dynamisk innholdsskript
per brukertilføyd regel. Innebygde regler betjenes av de statiske content_scripts-
oppføringene og registreres ALDRI dynamisk. desiredContentScripts filtrerer
config.sites til ikke-innebygde, aktiverte regler hvis origin er isOriginGranted
(delegerer til originCovers), mapper hver til et RegisteredContentScript med en
stabil id (contentScriptId, en FNV-1a-hash av samsvaret slik at id-en kun bruker det
API-sikre tegnsettet), og setter matchOriginAsFallback: true KUN når
matchAllowsOriginFallback(rule.match) holder (en /*-sti). Å styre flagget på denne måten
hindrer at én regel med ikke-konform sti får hele batch-registreringen til å bli
avvist; en slik regel registreres uten utsprett-dekning ved design.
reconcileContentScriptsOnce leser de tildelte originene, den lagrede konfigurasjonen
og de levende registreringene, og konvergerer deretter ved en EGENSKAPSBEVISST diff:
sameRegistration projiserer både den ønskede deskriptoren og den levende tilbakelesingen
på en normalisert form (ved å bruke hvert felts reelle WebExtension-standard —
allFrames/matchOriginAsFallback har standard false, persistAcrossSessions
har standard TRUE, runAt har standard document_idle) slik at et nyregistrert skript
sammenlignes som LIK deskriptoren som produserte det og ingen reregistreringsløkke kan
oppstå. Fordi det ikke finnes noen updateContentScripts, avregistreres et endret skript
og registreres deretter på nytt. reconcileContentScripts pakker dette med et
underveis-løfte (inFlight) slik at to nær-samtidige utløsere (en konfigurasjonsendring
OG permissions.onAdded som begge utløses ved en nettstedstilføyelse) serialiseres i stedet for å
kappløpe. registerContentReconciliation kobler utløserne
(permissions.onAdded/onRemoved, onConfigChanged) og konvergerer én gang ved
oppstart; den importeres kun av Chrome- og Firefox-bakgrunnsinngangene.
Opt-in-tillatelsesflyten
De statiske manifestene deklarerer optional_host_permissions (http://*/*,
https://*/*) slik at en bruker kan tildele en vilkårlig ekstra nettvert under kjøring.
packages/webext/src/options/options.ts addSite validerer HELE kandidatregelen
(samsvar OG innholdsrot) gjennom normaliseSiteRule FØR den ber om noen
tillatelse, utleder vertsoriginen med matchOrigin, og kaller
browser.permissions.request innenfra Legg til-knappens klikkgest; kun ved
tildeling legger den til en builtin:false-rad og persisterer gjennom den kanoniske
skrivebanen.
Konfigurasjon, lagring og det kanoniske mønsteret
Den enkle valideringstrakten
normaliseConfig i packages/core/src/config.ts er den ene kanoniske lesebanen:
den tvinger og begrenser hvert felt, faller tilbake til standarden ved alt
ugyldig, og kaster aldri. Den er bygget fra validatorer per felt —
normaliseBoolean, normaliseRenderMode, normaliseMaxLength,
normaliseIgnoreHosts (reduserer hver oppføring til et bart punycode-vertsnavn via
bareHostname), normaliseMatchColour/normaliseMismatchColour (styrt av
safeColour.ts isSafeColour), normaliseCssSize, normaliseFontWeight,
normaliseContentRoot (begrenset av CONTENT_ROOT_PATTERN/CONTENT_ROOT_MAX_LENGTH),
normaliseMatchPattern (styrt av MATCH_PATTERN) og normaliseSites.
normaliseSites forkaster avvisninger, dedupliserer etter match, TVINGER builtin: true
på enhver regel hvis samsvar er likt en innebygd regels (lukker vektoren
eksakt-samsvar-skygger-innebygd / dobbeltinjeksjon), reseder enhver manglende
innebygd regel (slik at et manipulert lager ikke kan slippe en kjerneleverandør) og sorterer
alfanumerisk etter match for en kanonisk rekkefølge. configFingerprint hasher
kun de gjengivelsespåvirkende feltene (ekskluderer enabled, ignoreHosts og sites,
som driver start/stopp og hopp i stedet for omflyt) via fnv1a.
Lagring
packages/webext/src/storage.ts pakker WebExtension-lagrings-API-et.
configArea bruker browser.storage.sync når den er tilgjengelig (slik at innstillinger roamer) og
faller tilbake til browser.storage.local (f.eks. på Thunderbird); configAreaName
rapporterer hvilken som er aktiv. getConfig, setConfig og onConfigChanged trakter alle
sin rå verdi gjennom normaliseConfig, slik at et misdannet eller manipulert lager
aldri kan overlevere en ugyldig Config til en kaller. onConfigChanged ignorerer i tillegg
hendelser fra det INAKTIVE lagringsområdet og endringer i urelaterte nøkler.
De to tilleggene gjenbruker den samme les -> normaliseConfig -> returner-trakten over sine
egne vertslagre. Outlook-oppgaverutens extensions/outlook/src/roamingStorage.ts
pakker Office.context.roamingSettings (synkron get/set, forpliktet med
saveAsync); Gmail-tilleggets packages/gmail/src/propertiesStorage.ts pakker
PropertiesService.getUserProperties() (per bruker, roamer på tvers av den brukerens
enheter, JSON-kodet under én nøkkel, og trenger INGEN ekstra OAuth-omfang — aldri den
delte ScriptProperties). Begge eksponerer getConfig/setConfig pluss de kort-relevante
getIgnoreHosts/getHighlightMismatch-aksessorene og beskytter en utilgjengelig vertsflate
med en dedikert feil. Gmail-hjemmesideutløseren gjengir et CardService-
innstillingskort fra getConfig, og onSaveSettings-skjemainnsendingshåndtereren slår
de parsede ignoreHosts/highlightMismatch sammen med den gjeldende konfigurasjonen og
persisterer den gjennom setConfig; den kontekstuelle utløseren tråder ignoreHosts inn i adapteren
og highlightMismatch inn i kortbyggeren, og leser defensivt slik at en lagrings-
feil faller tilbake til standardverdier i stedet for å bryte meldingsanalysen.
Mønsteret kanonisk-konfigurasjon + målrettet-oppdatering
Tre skrivebaner leser den kanoniske konfigurasjonen, endrer nøyaktig én ting og skriver den tilbake — uten noensinne å forplikte ulagrede skjemaredigeringer eller gjengi skjemaet på nytt:
removeSite(options.ts): lesergetConfig, persisterersetConfigover ensites-tabell med kun den fjernede regelen filtrert ut (persister FØRST, deretterrow.remove(), deretter en betingetpermissions.removebestemt ut fra neste konfigurasjon). En persisteringsfeil kasterSiteRemovalSaveErrorog lar DOM-en være urørt; en mislykket tilbakekalling kasterSitePermissionRevokeErroretter at raden er allerede borte.addSite(options.ts): beskrevet ovenfor — valider, be om tillatelse, legg til raden, derettersaveOptions.toggleEnabled(options.ts): den umiddelbare hoved-Aktiver-bryteren vender KUNenabledpå den lagrede konfigurasjonen og skriver den tilbake, uten å gjengi på nytt (slik at en pågående redigering aldri overskrives); ved en mislykket skriving tilbakestiller den avkrysningsboksen til den lagrede verdien.
Verktøylinjehandlingens egen toggleEnabled
(packages/webext/src/toolbarAction.ts) følger det samme mønsteret fra
bakgrunnssiden.
Bygg per motor og manifestkontrakten
tooling/build.mjs driver et enkelt esbuild-bygg for hvert WebExtension-mål. TARGETS-
beskrivelsen navngir hvert måls KILDEMÅL: Chrome, Firefox og
Thunderbird leverer sin egen src/; Edge, Opera og Safari deklarerer chrome som sin
kilde; Outlook-tillegget (Office.js) er sin egen kilde. ACTIVE_TARGETS (Chrome,
Edge, Firefox, Opera og Thunderbird — Safari og Outlook er ekskludert) er det
--all/--package bygger; Safari og Outlook bygges kun når de navngis eksplisitt, og
Gmail-tillegget (Apps Script) er en separat esbuild-bunt (make build-gmail)
utenfor --all-løkken. Den Gmail-bunten retter seg mot Apps Scripts V8-runtime, som
verken har ES-moduler eller en nativ URL: den bygges som ESM og får deretter sin
avsluttende export {…}-setning strippet (slik at utløserfunksjonene forblir globaler
på toppnivå som Apps Script kan kalle), og den bunter en liten URL-polyfill (installert kun
når URL er fraværende) slik at den delte kjernens new URL(...) løser lenker på
serversiden. buildTarget rydder dist/<target>/, kopierer mål-spesifikk
manifest.json og icons/ ordrett, bunter hvert kildeskript som IIFE (de injiserte
INJECTED_SCRIPTS content.ts/messageDisplay.ts kan ikke være ES-moduler, og
bakgrunner er klassiske arbeidere/hendelsessider også) og den delte innstillingssiden som
ESM, og kopierer options.html/options.css fra packages/webext/src/options/.
webextension-polyfill buntes inn i hvert skript.
tooling/version.mjs stempler versjoner: den tar MAJOR.MINOR fra rot-
package.json og legger til et auto-inkrementerende BUILD-nummer (lest fra den
høyeste eksisterende tredje komponenten på tvers av målmanifestene) inn i hvert mål-
manifest, og holder dem i takt. De fleste mål bærer et enkelt manifest.json; the
Outlook-tillegget bærer to versjonerte manifester (manifest.json og manifest.xml)
og begge stemples, og _template-stillaset stemples i takt også. Rot-package.json
holdes på samme versjon, men forblir gyldig semver (en firedelt
versjon er tillatt i et manifest, aldri i package.json). tooling/icons.mjs
(kjørt via make icons) rasteriserer den enkle vektorkilden assets/icon.svg til
hvert måls icons/icon48.png og icons/icon128.png, og prøver cairosvg, deretter
inkscape, deretter ImageMagick.
Manifestkontrakten — de nøyaktige påkrevde feltene per motor — er den eneste
sannhetskilden i hver extensions/<engine>/test/manifest.smoke.test.mjs (f.eks.
extensions/chrome/test/manifest.smoke.test.mjs sin assertManifestShape, som
også bygger målet og bekrefter at hver refererte fil løses i dist-en).
I henhold til prosjektets dedupliseringskonvensjon er ikke felt-for-felt-formen
oppregnet på nytt her; se de røyktestene og «Manifest contract»-
notatet i manuell testplan.
Internasjonalisering (i18n)
Reveal URLs er lokalisert til mange språk i tillegg til engelsk. Settet er
enkeltkildet i packages/core/src/locales.json (SUPPORTED_LOCALES, innfødte
navn og standarden), som både utvidelsen bunter (via den løste JSON-
importen) og det rene Node-nettstedbygget leser. Hver ikke-engelsk streng er
maskinoversatt og venter på menneskelig gjennomgang; opphavsmarkøren er per format
(_locales bruker hver meldings description, nettstedets chrome-ordbøker
registrerer det i site/i18n/README.md, og oversatte dokumentkilder bærer en HTML-
kommentar på første linje).
Det finnes tre uavhengige lokaliseringsflater, bevisst ikke krysskoblet, og hver er delt langs en byggetid/runtime-linje.
- Innstillingssiden (AD-1). Den leverer STATISK engelsk markup; hver oversettbar
node bærer en
data-i18n="<key>"(tekst) ellerdata-i18n-<attr>="<key>"(attributt)-annotering. Under kjøring erpackages/webext/src/options/locale.tsbruk-laget: denfetcher sidens egen pakkede_locales/<code>/messages.json(viaruntime.getURL, ingenweb_accessible_resourcesnødvendig), omskriver hver annoterte node KUN vedtextContent(slik at skjemaets<code>-eksempelhint forblir intakte), og setter<html lang>. Siden er FULLSTENDIG lokalisert: ved siden av den statiske prosaen bærer de JS-byggede nettstedsrad-etikettene og Fjern-knappen endata-i18n-nøkkel (med en innledendetranslate-t tekst) slik at det samme bruk-laget re-lokaliserer rendrede rader uten ny gjengivelse, og de VIRKELIG dynamiske strengene — statuslinjen, legg-til-nettsted- tilbakemeldingen og versjonslinjen — gjengis gjennomlocale.tssintranslate(<key>)(currentMessages → engelsk reserve → nøkkel) på kalltidspunktet. Den engelske reserven er the_locales/en/messages.json-katalogen IMPORTERT (buntet) inn ioptions.jsved byggetid, slik attranslateløser til lesbar engelsk SYNKRONT fra modullasting — en dynamisk streng gjengitt før velgeren faller på plass, eller etter at en runtime-kataloghenting mislykkes, degraderer aldri til en rå meldingsnøkkel.locale.tspubliserer det anvendte meldingskartet og utløser sineonLocaleChange-lyttere etter hver bruk, slik atoptions.tsgjengir den for øyeblikket viste statusen/tilbakemeldingen/versjonen på nytt og re-peker Nettbasert håndbok-lenken ved hver veksling. Versjonslinjen bruker enoptionsVersion-melding hvis{version}-plassholder erstattes med byggenummeret (token oversatt, tall verbatim). Håndboklenken følger den aktive lokaliteten til sin dokumentvariant (AD-9): engelsk beholdermanual.htmlpå toppnivå, hver annen lokalitet åpnernl/handleiding.html, og bevarer?v=-versjonsstempelet. Den aktive lokaliteten er brukerens lagrede valg eller, om det mislykkes, nettleserspråket løst til et støttet basisspråk av kjernensresolveLocale(AD-5); en ikke-støttet eller mislykket lokalitet lar den forhåndsgjengitte engelsken stå (ogtranslatedegraderer da til den engelske reserven). Håndbokens Visningsspråk-velger persisterer sitt valg under en dedikertuiLocale-nøkkel istorage.local— aldri i den synkroniserteConfig, slik at den er per enhet og roamer aldri. - Nettstedets chrome (AD-2). Statisk-nettsted-generatoren
tooling/site.mjsserver-gjengir hver chrome-node på engelsk mens den annoterer dendata-i18n, og beholder<html lang="en-GB">(AD-4). Nettleser-side-lasterensite/scripts/i18n.mjs(en avhengighetsfri ES-modul) bytter deretter chrome-en til den besøkendes språk etter prioritet — et lagretlocalStorage-valg (revealUrlsSiteLocale) → det første støttedenavigator.languages-basissamsvaret (AD-5) → engelsk — ved å hentesite/i18n/<code>.json. I motsetning til options-bruk-laget bytter den viainnerHTML, fordi nettstedets chrome-verdier bærer betrodd inline-markup (<code>,<a>); en paritetstest fester hver ikke-engelsk verdis tag/attributt/URL-sekvens byte-identisk med engelsk slik at en oversettelse aldri kan slippe en lenke eller ødelegge en tag. En manglende nøkkel beholder det forhåndsgjengitte engelske øyeblikksbildet; en mislykket henting lar den engelske siden være intakt (grasiøs degradering, aldri blank). - Dokumentasjonssidene (AD-8/AD-9). Dokumentsider gjengis PER LOKALITET ved
byggetid, ikke byttet under kjøring. Engelske dokumenter forblir på toppnivå-stiene; hver
annen lokalitet får en kopi under en
<code>/-mappe, fra en oversattdocs/<code>/<DOC>.mdnår den finnes, ellers den engelske kilden — en per-side-reserve sombuildSiteLOGGER i stedet for stilltiende å utelate. Dokument-KROPPEN bærer ingendata-i18n(den er rå gjengitt markdown), slik at runtime-chrome-lasteren aldri berører den; kun de delte chrome-nodene byttes. Dokumentsidens språkvelger NAVIGERER til søsken-lokalitetens side i stedet for å bytte på stedet. - Lokalisering av interne lenker (Finding 2). Det valgte språket bæres med på tvers av
dokumentnavigasjon. Hvert INTERNE dokumentanker (de fem sidene
architecture.html,index.html,licence.html,manual.html,privacy.html) merkesdata-doc-link="<page>"av generatoren, og for tilfellet uten JavaScript løses dens href allerede til DEN GJELDENDE sidens egen lokalitetsvariant (navDocHref— de tre per-lokalitet-dokumentsidene lokaliseres; de kun-engelskeindex.html/licence.htmlforblir på toppnivå). Runtime-lasterensrewriteDocLinksre-retter deretter hvert internt dokumentanker — de merkede OG ethvert katalog-injisert rent anker sommanual.html#installing— til den AKTIVE lokalitetens variant, og bevarer enhver#hash/?queryog berører aldri eksterne lenker. På en dokumentside NAVIGERER et LAGRET valg som avviker fra sidens egen lokalitet til den matchende varianten; løkkevakten utløses kun ved et eksplisitt lagret valg (aldri en navigator-språk-preferanse alene) og kun når målet avviker fra siden som allerede vises. Katalogene selv beholder sine engelske interne URL-er, slik at paritetstesten forblir grønn — lokaliseringen er en ren runtime-gjennomgang over DOM-en.
Undertrykking av «Aktive nettsteder» for e-postklient (AD-3)
Et e-postklient-mål (Thunderbird i dag) ser allerede hver gjengitt melding, så
«Aktive nettsteder»-redigeringsverktøyet per vert er overflødig der og fjernes. Beslutningen
bæres av et dokumentert beskrivelsesflagg per mål, mailClient: true, på
tooling/build.mjs sin TARGETS.thunderbird — aldri et hardkodet målnavn i bygge- eller
UI-logikk — og er implementert i to halvdeler:
- Byggetid.
copyAssetsbaserer seg pådescriptorFor(target).mailClient: for et e-postklient-mål kjører den den reneremoveSitesSection(html)-transformasjonen (som stripper hele<section class="sites">-vertsredigeringsverktøyet, inkludert hver add-site- kontroll, og kasterMissingSourceErrorhvis seksjonen er fraværende slik at en markup- endring aldri stilltiende kan levere redigeringsverktøyet) og skriver resultatet; nettlesermål mottaroptions.htmlbyte-for-byte viacpSync. - Runtime. Den buntede kontrolleren
packages/webext/src/options/options.tstolererer den fraværende seksjonen:renderSites/readSites/applyConfig/readFormsonderer#sites-listog blir en no-op i stedet for å kasteOptionsFieldMissingError, ogreadFormUTELATERsites-nøkkelen når seksjonen er borte. Fordi å utelatesitesville lanormaliseConfigrese de innebygde standardverdiene på nytt og slippe brukerens nettsteder, gjeninnsettersaveOptionsde lagredesitesfør normalisering når seksjonen er fraværende — det er det som får en e-postklient-lagring til å bevare de konfigurerte vertene. En runtime-vakt,guardMailClientSites, fjerner seksjonen hvis den fortsatt er til stede, og oppdager en e-postklient ved API-/funksjonstilstedeværelse (messages/messageDisplay/scripting.messageDisplaypå den injisertebrowser), aldri ved målnavn; den kjører først iinitOptionsslik at den senere add/list-koblingen naturlig hopper over de fraværende elementene. I en nettleser-runtime er vakten en no-op.
Sikkerhets- og personvernholdning
- Ingen nettverk eller eksfiltrering. Ingen modul utfører
fetch/XHReller noe annet nettverkskall; utvidelsen leser kun den lagrede konfigurasjonen og sidens DOM. - Kun sikker-DOM. Annotering og det injiserte stilarket bruker
utelukkende
createElement/textContent/typedestyle.*-egenskaper, aldriinnerHTML/insertAdjacentHTML/eval. For kjernen håndheves dette avpackages/core/test/purity.test.ts. - All ekstern-/konfigurasjonsinngang normalisert. Hver lesing og skriving traktes gjennom
normaliseConfig; farger passererisSafeColour; samsvarsmønstre passererMATCH_PATTERN; innholdsrot-velgere er begrenset avCONTENT_ROOT_PATTERNog overleveres kun noensinne tilquerySelectorAll/closest, aldri interpolert inn i markup eller CSS. - Minste privilegium. Innebygde webmail-origins er faste
host_permissions; ekstra verter er opt-in viaoptional_host_permissionsog en gest-bundetpermissions.request. Verktøylinjehandlingen deklarerer ingen popup. - Gecko-datainnsamling. Firefox- og Thunderbird-manifestene deklarerer
browser_specific_settings.gecko.data_collection_permissions.required: ["none"]. - Lisens. Prosjektet er AGPL-3.0-only (
LICENSE,package.json).
Testing
- Vitest-enhetstester dekker begge pakkene:
packages/core/test/*(config, kontrast, vertsavvik, lenkeprosessor, stiler, og renhetsvakten) ogpackages/webext/test/*(content, storage, options, registrering, verktøylinje, meldingsvisning og en ende-til-ende-test for egendefinert nettsted). Hver pakke kjørervitest run. - Cucumber-BDD-scenarier bor i
features/(koblet avcucumber.json) og driver de virkelige@reveal-urls/webext-modulene gjennom tro testdobler ifeatures/support/webext.ts— særlignormaliseReadBack, som speiler motoren som UTELATER false-verdsatte flagg ved tilbakelesing slik at et ettergivende testoppsett ikke kan skjule en reregistreringsløkke — over et jsdom-dokument satt opp ifeatures/support/world.ts. - Manifest-røyktester per mål (
extensions/<engine>/test/manifest.smoke.test.mjs, Nodes innebygdenode:test) bekrefter hvert manifests nøyaktige form og at den byggede dist-en løser hver refererte fil.tooling/test/legger til tester for bygg, versjon og definition-of-done. - CI (
.github/workflows/ci.yml) gatekontrollerer hver push og pull request påmake test,make bddogmake lint(kjørt direkte på løperen, og overstyrer Makefilens Docker-RUN/IMAGE_DEP). Versjonsstempling og pakking er bevisst ekskludert; de bor i den separate, tag-utløste.github/workflows/release.yml.