Reveal URLs — Architektūra
Šiame dokumente aprašoma, kaip struktūruotas Reveal URLs plėtinys ir kaip
veikia jo pagrindinė logika, skirta prisidedantiems ir techniniams
skaitytojams. Čia nuolat remiamasi tikrais failais ir simboliais forma
path symbol, kad kiekvieną teiginį būtų galima patikrinti šaltinio kode.
Naudotojui skirtą aprašymą rasite manual, o rankinio tikrinimo
žingsnius — manual test plan.
Apžvalga
Reveal URLs yra viena MV3 WebExtension, kuri iš vieno bendro švaraus kodo
branduolio kuriama Chrome, Edge, Opera, Firefox ir Thunderbird sistemoms.
Safari atidėta vėlesniam etapui: ji turi manifest ir gali būti build-inama
aiškiai ją nurodžius, tačiau pagal numatymą ir CI metu nėra įtraukiama.
Plėtinys rodo kiekvienos nuorodos paskirties URL šalia pačios nuorodos
rendered email turinyje ir pažymi nuorodą tada, kai matomas tekstas nurodo
kitą registrable domeną nei tas, kuris yra jos href.
Saugykla yra pnpm ir TypeScript monorepo, aprašyta
pnpm-workspace.yaml ir package.json, o "private": true neleidžia jos
publikuoti kaip paketo. Bundlingą valdo tooling/build.mjs, kuris
kompiliuoja bendrus paketus ir plonus entry points kiekvienam engine į
įkraunamą dist/<target>/ katalogą. webextension-polyfill įtraukiamas į
kiekvieną script bundle, o ne paliekamas kaip runtime global.
Du native el. pašto priedai išplečia tą pačią aptikimo logiką į vietas, kur
WebExtension nepasiekia: Outlook Add-in ant Office.js ir
Gmail Add-on ant Apps Script CardService. Abu pakartotinai naudoja tik
PURE analizę iš packages/core/src/findings.ts, kartu su
analyseAnchors, analyseHtml, neutraliu modeliu Finding[],
hostMismatch logika ir registrable domeno išvedimu per tldts.
Skiriasi tik host adapteris ir pateikimas. Outlook naudoja DOMParser ir
veikia client-side. Gmail naudoja node-html-parser ir veikia
server-side Google Apps Script V8 infrastruktūroje. Kadangi nė viena iš
šių aplinkų neleidžia keisti perskaityto pranešimo DOM, abu rodo
panelį arba kortelę su radiniais, o ne inline annotation.
linkProcessor.ts DOM-mutation kelias ir REVEAL_URLS_CSS ten
nenaudojami. Santykinis href sprendžiamas tik prieš patikimą
<base href> pačiame laiške ir kitu atveju praleidžiamas.
Kodo bazė aiškiai padalija atsakomybes:
packages/coresaugo PURE logiką, be browser APIs, be storage ir be markup sinks. Tai vienintelis tiesos šaltinis annotation, config tikrinimo ir host palyginimo logikai.packages/webextapgaubia šį branduolį WebExtension APIs, content lifecycle, dynamic registration, options UI ir background wiring sluoksniu.extensions/<engine>saugo kiekvieno engine plonus entry points ir jųmanifest.json.toolingsaugo build, version-stamping ir piktogramų skriptus.featuressaugo Cucumber BDD scenarijus ir test doubles.
Saugyklos išdėstymas
packages/core — grynasis branduolys
Nė vienas modulis čia neimportuoja extension API, pavyzdžiui,
browser.*, chrome.* arba messenger.*, ir nė vienas nenaudoja
string-to-markup DOM sinks, tokių kaip innerHTML ar insertAdjacentHTML.
Šį susitarimą mechaniškai užtikrina packages/core/test/purity.test.ts,
kuris surenka src/**/*.ts, pašalina komentarų kūnus su stripComments
ir patikrina, kad nei EXTENSION_API_PATTERN, nei UNSAFE_DOM_PATTERN
neatitiktų nė vieno modulio. Viešasis paviršius reeksportuojamas iš
packages/core/src/index.ts, o vienintelė runtime priklausomybė yra tldts.
Pagrindiniai moduliai yra:
packages/core/src/config.ts— schemosConfigirSiteRule, numatytosios reikšmės ir funnelnormaliseConfigpackages/core/src/matchPattern.ts— šablonų gramatika, matcher ir specifiškumo taisyklėspackages/core/src/linkProcessor.ts— annotator dokumento lygmeniupackages/core/src/hostMismatch.ts— registrable domeno neatitikimo tikrinimaspackages/core/src/styles.tsirpackages/core/src/contrast.ts— injectable stylesheet ir kontrasto logikapackages/core/src/safeColour.ts— predikatasisSafeColour
packages/webext — browser API apvalkalai
Šie moduliai GALI naudoti browser.*, ir tai yra vienintelė vieta, kur
liečiamos WebExtension APIs. Viešasis paviršius reeksportuojamas iš
packages/webext/src/index.ts. Čia yra content.ts,
contentRegistration.ts, storage.ts, background.ts, toolbarAction.ts,
messageDisplay.ts, messageDisplayBackground.ts ir options/options.ts.
extensions/<engine> — ploni įėjimo taškai ir manifestai
Kiekvienas engine turi savo manifest.json ir savo icons/. Chrome,
Firefox ir Thunderbird papildomai turi src/ katalogą su entry points.
Edge, Opera ir Safari turi tik manifestus bei piktogramas ir iš naujo naudoja
Chrome src/. Pavyzdžiui, extensions/chrome/src/content.ts turi tik
void createContentController().bootstrap();, o
extensions/chrome/src/background.ts iškviečia registerBackground,
registerContentReconciliation ir registerToolbarToggle.
extensions/thunderbird/src/background.ts iškviečia registerBackground ir
registerMessageDisplay. extensions/_template yra scaffold, o ne build target.
tooling, features ir smoke testai
tooling apima build.mjs, version.mjs ir icons.mjs. features
apima Gherkin scenarijus, step definitions ir patikimus doubles failuose
features/support/webext.ts ir features/support/world.ts. Kiekvienas
engine taip pat turi extensions/<engine>/test/manifest.smoke.test.mjs,
o _template naudoja template.smoke.test.mjs.
Atskleidimo srautas
Content kelias prasideda engine entry point-e, pavyzdžiui,
extensions/chrome/src/content.ts, kuris iškviečia
createContentController().bootstrap() iš packages/webext/src/content.ts.
Bootstrap
createContentController bootstrap vykdomas vieną kartą kiekvienam frame.
Jis perima frame dar prieš kiekvieną await per claimBootstrap, įkelia
config per loadConfigWithRetry, įšvirkščia stylesheet per injectStyles
tik po sėkmingo nuskaitymo ir atstato markerį per releaseBootstrap, jei
įkėlimas galutinai nepavyksta. Kai config.enabled yra aktyvus, jis kviečia
start, o bet kuriuo atveju užregistruoja listener onConfigChanged.
Aktyvios svetainės taisyklės nustatymas
start ir applyConfig naudoja resolveSiteRule, kad nuspręstų, ar ir kur
atlikti annotation. Pirmiausia bandoma paties dokumento vieta per
resolveForHref, kuris perfiltruoja config.sites iki įjungtų taisyklių ir
galutinį pasirinkimą palieka selectMostSpecific. Jei dokumentas neatitinka
ir jo kilmė yra opaque arba inherited, kaip tikrina hasOpaqueOrigin,
tada naudojamas fallback per readTopHref, readOpenerHref ir
readReferrer, saugantis nuo cross-origin skaitymų.
Apribojimas iki turinio šaknies ir pokyčių stebėjimas
start išsaugo contentRoot, sukuria annotator per
createAnnotator(config) ir apdoroja kiekvieną šaknį iš
selectRoots(doc, contentRoot), kuris apgaubia querySelectorAll su
try/catch, kad neteisingas selector saugiai grąžintų []. Po to jis stebi
doc.body su OBSERVER_OPTIONS. processMutations filtruoja kiekvieną
pokytį pagal root, o processAddedNode apima mazgus root viduje, pačią
root sritį ir mazgus, kurie ją talpina. applyConfig atlieka teardown,
restart arba reflow priklausomai nuo aktyvios taisyklės ir contentRoot
pasikeitimo.
Vienos nuorodos žymėjimas
Annotator gyvena faile packages/core/src/linkProcessor.ts.
createAnnotator grąžina Annotator su Map, data-ru tokenu iš
generateToken ir aktyviu config. process(root) išvalo atjungtus įrašus,
surenka a[href] elementus per collectAnchors, išsprendžia URL per
resolveAnnotatableUrl, naudoja fast path isAnnotationIntact, kai renderis
nepasikeitęs, ir apskaičiuoja mismatch per hostMismatch, prieš kviesdamas
annotateAnchor.
annotateAnchor renderina tik SAFE-DOM. Režimu "inline" jis prideda
<span class="reveal-urls-url"> su textContent ir <br>. Režimu
"title" jis išsaugo esamą title, pakeičia jį URL ir, kai mismatch
pabrėžiamas, prideda <span class="reveal-urls-warn"> su
REVEAL_URLS_WARN_LABEL. Sukurti mazgai gauna data-ru="<token>", o
configFingerprint naudojamas priverstiniam reflow. truncateUrl dirba
virš [...displayHref] ir naudoja URL_ELLIPSIS.
Host neatitiktis
packages/core/src/hostMismatch.ts hostMismatch lygina nuorodos matomą
tekstą su jos href registrable domeno lygmeniu, o ne pagal žalią host.
extractHostCandidates išskaido tekstą į tokenus ir sumažina juos per
hostCandidate, kuriam reikalingas taškas ir tldts atpažįstamas public
suffix. Href host ir kandidatai redukuojami per tldts.getDomain. Bet kuris
kandidatas su kitu domenu sukelia mismatch.
Stiliai ir kontrastas
packages/core/src/styles.ts saugo REVEAL_URLS_CSS ir helperius
applyColour, applyFontOverrides ir applyContrastBackdrop.
applyContrastBackdrop nuskaito elemento apskaičiuotą spalvą ir pirmą
opaque foną per getComputedStyle, tada naudoja
needsWhiteBackdrop iš packages/core/src/contrast.ts, kad nuspręstų,
ar nustatyti style.backgroundColor = "white". contrast.ts pateikia
parseColour, relativeLuminance ir contrastRatio.
Konfigūruojami host ir turinio ribojimas
SiteRule faile packages/core/src/config.ts nusako, KUR annotation
veikia per match ir allFrames, ir KOKS container jį apriboja per
contentRoot, kartu su enabled ir žyme builtin. DEFAULT_SITES
apima Gmail, Proton su allFrames ir abu Outlook host. Config sujungia
globalius toggles, colours, font overrides ir sąrašą sites.
Match pattern mechanizmas
packages/core/src/matchPattern.ts saugo restrictive gramatiką
MATCH_PATTERN: tik http ir https, pasirenkamą *. wildcard host, glob
path, be port, be * scheme ir be <all_urls>. matchesPattern lygina
URL su validuotu pattern ir path dalį perduoda pathGlobMatches.
parsePatternParts padalija pattern į host, path, scheme ir
wildcardHost.
Kai taisyklės persidengia, taikomas principas "most specific wins".
compareSiteSpecificity iškelia exact host virš wildcard host, po to ilgesnį
literal host, po to labiau literal path per literalPathLength, o galiausiai
ASCII tiebreak. selectMostSpecific grąžina specifiškiausią įjungtą taisyklę,
atitinkančią URL.
permissions.getAll() gali pranešti grants visoje WebExtension gramatikoje
(<all_urls>, *://*/*, https://*/*, *://*.host/*, https://host/*),
kuriuos MATCH_PATTERN atmestų. parseGrantedOrigin paverčia juos į
GrantedOriginParts, originCovers tikrina padengimą per match, o
matchAllowsOriginFallback nurodo, ar path yra tiksliai
ORIGIN_FALLBACK_PATH, tai yra /*.
Dinaminė registracija
packages/webext/src/contentRegistration.ts užregistruoja po vieną dynamic
content script kiekvienai naudotojo pridėtai taisyklei. Built-ins aptarnaujami
per static content_scripts entries ir niekada neregistruojami dinamiškai.
desiredContentScripts perfiltruoja config.sites, naudoja
isOriginGranted, originCovers ir contentScriptId, ir nustato
matchOriginAsFallback: true tik tada, kai tai leidžia
matchAllowsOriginFallback(rule.match).
reconcileContentScriptsOnce nuskaito suteiktas kilmes, išsaugotą config ir
live registrations bei sulygina jas property-aware diff būdu.
sameRegistration normalizuoja tiek pageidaujamą descriptor, tiek live
read-back. Kadangi nėra updateContentScripts, pakeistas script pašalinamas
ir užregistruojamas iš naujo. reconcileContentScripts naudoja promise
inFlight, kad nuosekliai vykdytų triggerius, kurie gali nutikti beveik
vienu metu.
Aiškaus leidimo srautas
Static manifestai deklaruoja optional_host_permissions
(http://*/*, https://*/*), kad naudotojas runtime metu galėtų suteikti
leidimą papildomam host. packages/webext/src/options/options.ts addSite
validuoja visą kandidato rule per normaliseSiteRule, išveda origin per
matchOrigin ir kviečia browser.permissions.request iš Add mygtuko click
gesture. Tik po patvirtinimo pridedama eilutė builtin:false.
Config, saugykla ir kanoninis modelis
Vienintelis validavimo piltuvas
normaliseConfig faile packages/core/src/config.ts yra vienintelis
kanoninis skaitymo kelias. Jis konvertuoja ir apriboja kiekvieną lauką,
neteisingų duomenų atveju grąžina numatytąją reikšmę ir niekada nemeta
exception. Jis sudarytas iš validatorių, tokių kaip normaliseBoolean,
normaliseRenderMode, normaliseMaxLength, normaliseIgnoreHosts,
normaliseMatchColour, normaliseMismatchColour, normaliseCssSize,
normaliseFontWeight, normaliseContentRoot, normaliseMatchPattern
ir normaliseSites. normaliseSites išmeta neteisingus entries,
pašalina dublikatus pagal match, priverčia builtin: true ten, kur taisyklė
persidengia su built-in, ir atkuria trūkstamus built-ins.
Saugojimas
packages/webext/src/storage.ts apgaubia WebExtension storage API.
configArea naudoja browser.storage.sync, kai jis prieinamas, ir pereina
prie browser.storage.local, pavyzdžiui, Thunderbird aplinkoje.
getConfig, setConfig ir onConfigChanged visada perleidžia reikšmes per
normaliseConfig, todėl sugadinta saugykla negali grąžinti neteisingo
Config. Abi add-on formos naudoja tą patį piltuvą ant savo host storage.
Kanoninis config modelis ir tiksliniai atnaujinimai
Trys write path perskaito kanoninį config, pakeičia tik vieną dalyką ir įrašo jį atgal, nesaugodami neužbaigtų form edits:
removeSitefaileoptions.tsaddSitefaileoptions.tstoggleEnabledfaileoptions.ts
toggleEnabled faile packages/webext/src/toolbarAction.ts seka tuo pačiu
modeliu ir background pusėje.
Build pagal engine ir manifesto sutartis
tooling/build.mjs valdo vieną bendrą esbuild build kiekvienam
WebExtension target. Descriptor TARGETS nustato SOURCE TARGET kiekvienam
target. Chrome, Firefox ir Thunderbird turi savo src/. Edge, Opera ir Safari
naudoja chrome kaip šaltinį. Outlook add-in turi atskirą šaltinį.
ACTIVE_TARGETS, tai yra Chrome, Edge, Firefox, Opera ir Thunderbird, yra
tie, kurie build-inami per --all ir --package. Safari ir Outlook
build-inami tik tada, kai aiškiai įvardijami, o Gmail naudoja
make build-gmail.
Gmail bundle taikomas Apps Script V8 runtime, kuriame nėra nei ES modules,
nei native URL. Todėl jis build-inamas kaip ESM, o po to jam pašalinamas
export {…}, kad trigger funkcijos liktų globalios. Nedidelis URL
polyfill įtraukiamas tik prireikus. buildTarget išvalo dist/<target>/,
nukopijuoja manifest.json ir icons/, supakuoja scripts kaip IIFE,
options puslapį kaip ESM ir nukopijuoja options.html bei options.css.
tooling/version.mjs stamp-ina versijas. Jis paima MAJOR.MINOR iš root
package.json, suranda didžiausią BUILD number ir prideda jį kiekvienam
target manifest. Outlook add-in turi tiek manifest.json, tiek
manifest.xml. tooling/icons.mjs, paleistas su make icons,
rasterises assets/icon.svg į icon48.png ir icon128.png.
Manifesto sutartis apibrėžta failuose
extensions/<engine>/test/manifest.smoke.test.mjs, kur tikrinama kiekvieno
manifesto forma ir kiekvieno referenced file buvimas.
Internacionalizacija (i18n)
Reveal URLs lokalizuotas į daugelį kalbų be anglų. Vienintelis šio rinkinio
šaltinis yra packages/core/src/locales.json, kuriame yra
SUPPORTED_LOCALES, vietiniai pavadinimai ir numatytoji kalba. Kiekvienas
neangliškas string yra machine-translated ir laukia žmogaus peržiūros.
Kilmės žymeklis skiriasi pagal formatą: _locales jį neša lauke
description, site catalogues jį įrašo faile site/i18n/README.md, o
išversti docs pirmoje eilutėje turi HTML komentarą.
Yra trys nepriklausomi lokalizacijos paviršiai:
- The options page (AD-1). Statinis markup pateikiamas anglų kalba, bet
kiekvienas verčiamas mazgas turi
data-i18n="<key>"arbadata-i18n-<attr>="<key>". Runtime metupackages/webext/src/options/locale.tsįkelia_locales/<code>/messages.json, perrašo tekstą pertextContentir nustato<html lang>. - The website chrome (AD-2).
tooling/site.mjsrenderina svetainės chrome anglų kalba sudata-i18nir<html lang="en-GB">. Tadasite/scripts/i18n.mjsjį pakeičia lankytojo kalba išsite/i18n/<code>.json. - The documentation pages (AD-8/AD-9). Dokumentacijos puslapiai
renderinami kiekvienam locale build metu ir naudoja lokalizuotus
failų pavadinimus iš
packages/core/src/site-page-names.json, pavyzdžiui,nl/handleiding.html. - Internal-link localisation (Finding 2). Vidinės docs nuorodos turi
data-doc-link="<page>", o runtime loader jas perrašo į to paties locale sibling variantą, išsaugodamas#hashir?query.
„Aktyvių svetainių“ slėpimas pašto kliente (AD-3)
Mail client target, pavyzdžiui, Thunderbird, jau mato visus rendered
messages, todėl host pagrindu veikiantis „Active sites“ redaktorius ten yra
nereikalingas. Sprendimas perduodamas per mailClient: true lauką faile
tooling/build.mjs.
- Build-time.
copyAssetspatikrinadescriptorFor(target).mailClientir, jam esant aktyviam, pašalina<section class="sites">perremoveSitesSection(html). - Runtime.
packages/webext/src/options/options.tstoleruoja skilties nebuvimą, nemetaOptionsFieldMissingErrorir atstato išsaugotąsitesreikšmę prieš normalisation.
Saugumo ir privatumo laikysena
- Be tinklo užklausų ir duomenų išnešimo. Nė vienas modulis neatlieka
fetch,XHRar kitų tinklo iškvietimų. Plėtinys skaito tik išsaugotą config ir puslapio DOM. - Tik SAFE-DOM. Annotation ir įšvirkštas stylesheet naudoja tik
createElement,textContentirstyle.*, o neinnerHTML,insertAdjacentHTMLareval. - Visa išorinė įvestis normalizuojama. Kiekvienas skaitymas ir įrašymas
pereina per
normaliseConfig. Spalvos tikrinamos perisSafeColour, rakstai — perMATCH_PATTERN, o content-root selectors ribojami perCONTENT_ROOT_PATTERN. - Minimalios privilegijos. Įmontuoti webmail origin yra
host_permissions. Papildomi host yra opt-in peroptional_host_permissionsirpermissions.request. - Duomenų rinkimas Gecko aplinkoje. Firefox ir Thunderbird manifestai
deklaruoja
browser_specific_settings.gecko.data_collection_permissions.required: ["none"]. - Licencija. Projektas yra AGPL-3.0-only, kaip nurodyta failuose
LICENSEirpackage.json.
Testavimas
- Vitest unit tests apima
packages/core/test/*irpackages/webext/test/*. - Cucumber BDD scenarijai gyvena kataloge
features/ir naudojafeatures/support/webext.tsbeifeatures/support/world.ts. - Per-target manifest smoke tests kataloge
extensions/<engine>/test/manifest.smoke.test.mjsnaudojanode:testir tikrina manifestų formą bei referenced files. - CI faile
.github/workflows/ci.ymlsustabdo kiekvieną push ir pull request, jeimake test,make bddirmake lintnepraeina. Version-stamping ir packaging gyvena faile.github/workflows/release.yml.