Reveal URLs — Arhitektura
Ovaj dokument opisuje kako je Reveal URLs strukturisan i kako radi njegova
osnovna logika, za saradnike i tehničke čitaoce. Kroz ceo tekst navode se
stvarne datoteke i simboli u obliku path symbol, tako da svaka tvrdnja
može da se proveri u izvornom kodu. Korisnički opis nalazi se u
manual, a koraci ručne provere u
manual test plan.
Pregled
Reveal URLs je jedna MV3 WebExtension koja se iz jednog zajedničkog, čistog
jezgra koda gradi za Chrome, Edge, Opera, Firefox i Thunderbird. Safari je
odložen za kasniju fazu: ima manifest i može da se izgradi kada se eksplicitno
navede, ali nije uključen u podrazumevani build niti u CI. Ekstenzija
prikazuje odredišni URL svakog linka pored samog linka u rendered email
sadržaju i označava link kada vidljivi tekst upućuje na drugi
registrable domen od onog iz href.
Repozitorijum je pnpm i TypeScript monorepo opisan datotekama
pnpm-workspace.yaml i package.json, a "private": true sprečava njegovo
objavljivanje kao paketa. Bundling vodi tooling/build.mjs, koji kompajlira
zajedničke pakete i tanke entry point-e svakog engine-a u učitljiv
direktorijum dist/<target>/. webextension-polyfill se bundluje u svaki
script umesto da se oslanja na runtime global.
Dva native dodatka za e-poštu proširuju isto jezgro detekcije na površine do
kojih WebExtension ne može da dođe: Outlook Add-in na Office.js i
Gmail Add-on na Apps Script CardService. Oba ponovo koriste samo PURE
analizu iz packages/core/src/findings.ts, zajedno sa analyseAnchors,
analyseHtml, neutralnim modelom Finding[], logikom hostMismatch i
izvođenjem registrable domena preko tldts. Razlikuju se samo host adapter i
prezentacija. Outlook koristi DOMParser i radi client-side. Gmail
koristi node-html-parser i radi server-side na Google Apps Script V8
infrastrukturi. Pošto nijedan od ta dva okvira ne dozvoljava menjanje DOM-a
prikazane poruke, oba prikazuju panel ili karticu sa nalazima, a ne inline
annotation. Putanja DOM mutacije iz linkProcessor.ts i REVEAL_URLS_CSS
tu se ne koriste. Relativni href razrešava se samo prema pouzdanom
<base href> unutar same poruke, a u suprotnom se preskače.
Baza koda jasno razdvaja odgovornosti:
packages/corečuva PURE logiku, bez browser API-ja, bez storage-a i bez markup sink-ova. To je jedini izvor istine za logiku annotation, validaciju config-a i poređenje hostova.packages/webextobavija to jezgro WebExtension API-jima, content lifecycle-om, dynamic registration-om, options UI-jem i background wiring-om.extensions/<engine>čuva tanke per-engine entry point-e i njihovemanifest.json.toolingčuva build, version-stamping i icon skripte.featuresčuva Cucumber BDD scenarije i test doubles.
Raspored repozitorijuma
packages/core — čisto jezgro
Nijedan modul ovde ne importuje extension API kao što su browser.*,
chrome.* ili messenger.*, i nijedan ne koristi string-to-markup DOM
sinkove kao što su innerHTML ili insertAdjacentHTML. Taj ugovor se
mehanički sprovodi u packages/core/test/purity.test.ts, koji prolazi kroz
src/**/*.ts, uklanja tela komentara pomoću stripComments i proverava da ni
EXTENSION_API_PATTERN ni UNSAFE_DOM_PATTERN ne odgovaraju nijednom
modulu. Javna površina se reeksportuje iz packages/core/src/index.ts, a
jedina runtime zavisnost je tldts.
Glavni moduli su:
packages/core/src/config.ts— šemeConfigiSiteRule, podrazumevane vrednosti i validacioni levaknormaliseConfigpackages/core/src/matchPattern.ts— gramatika pattern-a, matcher i pravila specifičnostipackages/core/src/linkProcessor.ts— annotator na nivou dokumentapackages/core/src/hostMismatch.ts— provera nepodudaranja registrable domenapackages/core/src/styles.tsipackages/core/src/contrast.ts— injectable stylesheet i logika kontrastapackages/core/src/safeColour.ts— predikatisSafeColour
packages/webext — omotači browser API-ja
Ovi moduli SMEJU da koriste browser.* i to je jedino mesto gde se dotiču
WebExtension API-ji. Javna površina se reeksportuje iz
packages/webext/src/index.ts. Tu se nalaze content.ts,
contentRegistration.ts, storage.ts, background.ts, toolbarAction.ts,
messageDisplay.ts, messageDisplayBackground.ts i options/options.ts.
extensions/<engine> — tanki entry point-i i manifesti
Svaki engine ima sopstveni manifest.json i sopstveni icons/. Chrome,
Firefox i Thunderbird dodatno imaju direktorijum src/ sa entry point-ima.
Edge, Opera i Safari imaju samo manifest i ikone i ponovo koriste Chrome
src/. Na primer, extensions/chrome/src/content.ts sadrži samo
void createContentController().bootstrap();, a
extensions/chrome/src/background.ts poziva registerBackground,
registerContentReconciliation i registerToolbarToggle.
extensions/thunderbird/src/background.ts poziva registerBackground i
registerMessageDisplay. extensions/_template je scaffold, a ne build
target.
tooling, features i smoke testovi
tooling obuhvata build.mjs, version.mjs i icons.mjs. features
obuhvata Gherkin scenarije, step definitions i verne doubles datoteke
features/support/webext.ts i features/support/world.ts. Svaki engine
takođe ima extensions/<engine>/test/manifest.smoke.test.mjs, dok
_template koristi template.smoke.test.mjs.
Tok otkrivanja
Content putanja počinje u entry point-u engine-a kao što je
extensions/chrome/src/content.ts, koji poziva
createContentController().bootstrap() iz packages/webext/src/content.ts.
Bootstrap
createContentController bootstrap radi jednom po frame-u. On preuzima
frame pre svakog await preko claimBootstrap, učitava config preko
loadConfigWithRetry, ubacuje stylesheet kroz injectStyles tek posle
uspešnog čitanja i vraća marker pomoću releaseBootstrap ako učitavanje
konačno ne uspe. Kada je config.enabled aktivan, poziva start, a u svakom
slučaju registruje listener onConfigChanged.
Određivanje aktivnog pravila za sajt
start i applyConfig koriste resolveSiteRule da odluče da li i gde treba
izvršiti annotation. Prvo se pokušava lokacija samog dokumenta preko
resolveForHref, koji filtrira config.sites na uključena pravila, a
konačan izbor prepušta funkciji selectMostSpecific. Ako se dokument ne
poklapa, a njegovo poreklo je opaque ili inherited, kako to proverava
hasOpaqueOrigin, tada sledi fallback preko readTopHref, readOpenerHref
i readReferrer, uz zaštitu od cross-origin čitanja.
Ograničavanje na koren sadržaja i praćenje promena
start pamti contentRoot, kreira annotator preko createAnnotator(config)
i obrađuje svaki koren iz selectRoots(doc, contentRoot), koji omotava
querySelectorAll u try/catch kako bi neispravan selector bezbedno vratio
[]. Posle toga posmatra doc.body koristeći OBSERVER_OPTIONS.
processMutations filtrira svaku promenu prema root-u, a processAddedNode
obuhvata čvorove unutar root-a, sam root i čvorove koji ga sadrže.
applyConfig radi teardown, restart ili reflow u zavisnosti od promene
aktivnog pravila i contentRoot.
Označavanje pojedinačnog linka
Annotator se nalazi u packages/core/src/linkProcessor.ts.
createAnnotator vraća Annotator sa Map, data-ru tokenom iz
generateToken i aktivnim config-om. Njegov process(root) čisti odspojene
zapise, skuplja a[href] elemente pomoću collectAnchors, razrešava URL
preko resolveAnnotatableUrl, koristi brzu putanju isAnnotationIntact kada
se render nije promenio i računa mismatch pomoću hostMismatch pre nego što
pozove annotateAnchor.
annotateAnchor renderuje samo SAFE-DOM. U režimu "inline" dodaje
<span class="reveal-urls-url"> sa textContent i <br>. U režimu
"title" čuva postojeći title, menja ga u URL i, kada je mismatch naglašen,
dodaje <span class="reveal-urls-warn"> sa REVEAL_URLS_WARN_LABEL.
Kreirani čvorovi dobijaju data-ru="<token>", a configFingerprint služi za
prisilni reflow. truncateUrl radi nad [...displayHref] i koristi
URL_ELLIPSIS.
Nepodudaranje hosta
packages/core/src/hostMismatch.ts hostMismatch poredi vidljivi tekst linka
sa njegovim href na nivou registrable domena, a ne sirovog hosta.
extractHostCandidates deli tekst na tokene i svodi ih pomoću
hostCandidate, koji traži tačku i public suffix koji tldts prepoznaje.
Href host i kandidati svode se preko tldts.getDomain. Svaki kandidat sa
drugim domenom proizvodi mismatch.
Stilovi i kontrast
packages/core/src/styles.ts čuva REVEAL_URLS_CSS i pomoćne funkcije
applyColour, applyFontOverrides i applyContrastBackdrop.
applyContrastBackdrop čita izračunatu boju elementa i prvu opaque pozadinu
koju pronađe kroz getComputedStyle, a zatim koristi needsWhiteBackdrop iz
packages/core/src/contrast.ts da odluči da li treba postaviti
style.backgroundColor = "white". contrast.ts pruža parseColour,
relativeLuminance i contrastRatio.
Podesivi hostovi i ograničavanje sadržaja
SiteRule u packages/core/src/config.ts određuje GDE annotation radi putem
match i allFrames, i KOJI container je ograničava kroz contentRoot,
zajedno sa enabled i oznakom builtin. DEFAULT_SITES obuhvata Gmail,
Proton sa allFrames i oba Outlook hosta. Config objedinjuje globalne
toggle-ove, colours, font overrides i listu sites.
Mehanizam match patterna
packages/core/src/matchPattern.ts sadrži restriktivnu gramatiku
MATCH_PATTERN: samo http i https, opcionalni *. wildcard host, glob path,
bez porta, bez * scheme-a i bez <all_urls>. matchesPattern poredi URL sa
validiranim pattern-om i delegira path deo na pathGlobMatches.
parsePatternParts deli pattern na host, path, scheme i
wildcardHost.
Kada se pravila preklapaju, primenjuje se pravilo "most specific wins".
compareSiteSpecificity daje prednost tačnom hostu nad wildcard hostom, zatim
dužem literal hostu, zatim literalnijem path-u preko literalPathLength, a na
kraju koristi ASCII tiebreak. selectMostSpecific vraća najspecifičnije
uključeno pravilo koje odgovara URL-u.
permissions.getAll() može da prijavi grant-ove u punoj WebExtension
gramatici, kao što su <all_urls>, *://*/*, https://*/*,
*://*.host/* i https://host/*) koje bi MATCH_PATTERN odbio.
parseGrantedOrigin ih pretvara u GrantedOriginParts, originCovers
proverava pokrivenost preko match, a matchAllowsOriginFallback govori da
li je path tačno ORIGIN_FALLBACK_PATH, odnosno /*.
Dinamička registracija
packages/webext/src/contentRegistration.ts registruje jedan dynamic content
script po svakom pravilu koje je dodao korisnik. Built-in pravila opslužuju se
preko static content_scripts unosa i nikada se ne registruju dinamički.
desiredContentScripts filtrira config.sites, koristi
isOriginGranted, originCovers i contentScriptId, te postavlja
matchOriginAsFallback: true samo kada to dozvoljava
matchAllowsOriginFallback(rule.match).
reconcileContentScriptsOnce čita dodeljena porekla, sačuvani config i
aktivne registracije te ih usklađuje kroz property-aware diff.
sameRegistration normalizuje i željeni descriptor i live read-back. Pošto ne
postoji updateContentScripts, izmenjeni script se prvo uklanja pa ponovo
registruje. reconcileContentScripts koristi promise inFlight kako bi
serijalizovao triggere koji mogu da se dogode gotovo istovremeno.
Tok eksplicitne dozvole
Static manifesti deklarišu optional_host_permissions
(http://*/*, https://*/*) kako bi korisnik u runtime-u mogao da dodeli
dozvolu za dodatni host. packages/webext/src/options/options.ts addSite
validira celo kandidat-pravilo preko normaliseSiteRule, izvodi host origin
preko matchOrigin i poziva browser.permissions.request iz click gesture-a
na Add dugmetu. Tek posle odobrenja dodaje red builtin:false.
Konfiguracija, skladištenje i kanonski obrazac
Jedinstveni validacioni levak
normaliseConfig u packages/core/src/config.ts je jedina kanonska read
putanja. On prinudno pretvara i ograničava svako polje, vraća se na
podrazumevanu vrednost kada je unos neispravan i nikada ne baca exception.
Sastavljen je od validatora po poljima kao što su normaliseBoolean,
normaliseRenderMode, normaliseMaxLength, normaliseIgnoreHosts,
normaliseMatchColour, normaliseMismatchColour, normaliseCssSize,
normaliseFontWeight, normaliseContentRoot, normaliseMatchPattern
i normaliseSites. normaliseSites izbacuje neispravne unose, uklanja
duplikate po match, prisiljava builtin: true kada se pravilo poklapa sa
built-in pravilom i vraća sve ugrađene stavke koje nedostaju.
Skladištenje
packages/webext/src/storage.ts obavija WebExtension storage API.
configArea koristi browser.storage.sync kada je dostupan, a prelazi na
browser.storage.local, na primer u Thunderbirdu.
getConfig, setConfig i onConfigChanged uvek provlače vrednosti kroz
normaliseConfig, tako da pokvareno skladištenje nikada ne može da vrati
neispravan Config. Oba add-on oblika koriste isti levak nad sopstvenim host
storage-om.
Kanonska konfiguracija i ciljane izmene
Tri write putanje čitaju kanonski config, menjaju tačno jednu stvar i upisuju ga nazad, bez čuvanja nedovršenih form edits:
removeSiteuoptions.tsaddSiteuoptions.tstoggleEnableduoptions.ts
toggleEnabled u packages/webext/src/toolbarAction.ts prati isti obrazac sa
background strane.
Build po engine-u i ugovor manifesta
tooling/build.mjs pokreće jedan zajednički esbuild build za svaki
WebExtension target. Descriptor TARGETS navodi SOURCE TARGET za svaki
target. Chrome, Firefox i Thunderbird imaju sopstveni src/. Edge, Opera i
Safari koriste chrome kao izvor. Outlook add-in ima poseban izvor.
ACTIVE_TARGETS, odnosno Chrome, Edge, Firefox, Opera i Thunderbird, jesu
oni koji se grade preko --all i --package. Safari i Outlook grade se samo
kada su eksplicitno navedeni, a Gmail koristi make build-gmail.
Gmail bundle cilja Apps Script V8 runtime, koji nema ni ES modules ni native
URL. Zato se gradi kao ESM, a zatim mu se uklanja završni export {…} kako
bi trigger funkcije ostale globalne. Mali URL polyfill uključuje se samo
kada je potreban. buildTarget čisti dist/<target>/, kopira
manifest.json i icons/, bundluje script-ove kao IIFE, options stranicu kao
ESM i kopira options.html i options.css.
tooling/version.mjs utiskuje verzije. Uzima MAJOR.MINOR iz korenskog
package.json, pronalazi najveći BUILD broj i dodaje ga svakom target
manifestu. Outlook add-in ima i manifest.json i manifest.xml.
tooling/icons.mjs, pokrenut kroz make icons, rasterizuje assets/icon.svg
u icon48.png i icon128.png.
Manifest ugovor definisan je u datotekama
extensions/<engine>/test/manifest.smoke.test.mjs, gde se proverava oblik
svakog manifesta i postojanje svakog referenced file-a.
Internacionalizacija (i18n)
Reveal URLs je lokalizovan na mnogo jezika osim engleskog. Jedini izvor za taj
skup je packages/core/src/locales.json, koji sadrži SUPPORTED_LOCALES,
lokalna imena i podrazumevani jezik. Svaki neengleski string je
machine-translated i čeka ljudsku proveru. Oznaka porekla zavisi od formata:
_locales je nose u polju description, site catalogues je beleže u
site/i18n/README.md, a prevedeni docs u prvoj liniji nose HTML komentar.
Postoje tri nezavisne lokalizacione površine:
- The options page (AD-1). Statički markup isporučuje se na engleskom, ali
svaki prevodivi čvor ima
data-i18n="<key>"ilidata-i18n-<attr>="<key>". U runtime-upackages/webext/src/options/locale.tsučitava_locales/<code>/messages.json, prepisuje tekst prekotextContenti postavlja<html lang>. - The website chrome (AD-2).
tooling/site.mjsrenderuje site chrome na engleskom sadata-i18ni<html lang="en-GB">. Zatim gasite/scripts/i18n.mjszamenjuje jezikom posetioca izsite/i18n/<code>.json. - The documentation pages (AD-8/AD-9). Dokumentacione stranice renderuju se
za svaki locale u vreme build-a i koriste lokalizovane nazive datoteka iz
packages/core/src/site-page-names.json, na primernl/handleiding.html. - Internal-link localisation (Finding 2). Interne docs veze imaju
data-doc-link="<page>", a runtime loader ih prepisuje na sibling varijantu istog locale-a, čuvajući#hashi?query.
Potiskivanje „Active sites” u klijentu e-pošte (AD-3)
Mail client target kao što je Thunderbird već vidi sve rendered messages, pa je
host-based editor za „Active sites” tu suvišan. Odluka se prenosi kroz polje
mailClient: true u tooling/build.mjs.
- Build-time.
copyAssetsproveravadescriptorFor(target).mailClienti kada je aktivan uklanja<section class="sites">prekoremoveSitesSection(html). - Runtime.
packages/webext/src/options/options.tspodnosi odsustvo tog odeljka, ne bacaOptionsFieldMissingErrori vraća sačuvanu vrednostsitespre normalisation.
Bezbednosna i privatnosna pozicija
- Bez mrežnih poziva i iznošenja podataka. Nijedan modul ne izvršava
fetch,XHRniti druge mrežne pozive. Ekstenzija čita samo sačuvani config i DOM stranice. - Samo SAFE-DOM. Annotation i injected stylesheet koriste samo
createElement,textContentistyle.*, a neinnerHTML,insertAdjacentHTMLilieval. - Sav spoljašnji unos se normalizuje. Svako čitanje i pisanje prolazi kroz
normaliseConfig. Colours se proveravaju prekoisSafeColour, pattern-i prekoMATCH_PATTERN, a content-root selectors ograničavaju se prekoCONTENT_ROOT_PATTERN. - Minimalne privilegije. Ugrađena webmail porekla su
host_permissions. Dodatni hostovi su opt-in krozoptional_host_permissionsipermissions.request. - Prikupljanje podataka u Gecko okruženju. Firefox i Thunderbird manifesti
deklarišu
browser_specific_settings.gecko.data_collection_permissions.required: ["none"]. - Licenca. Projekat je AGPL-3.0-only, kako je navedeno u datotekama
LICENSEipackage.json.
Testiranje
- Vitest unit tests pokrivaju
packages/core/test/*ipackages/webext/test/*. - Cucumber BDD scenariji žive u direktorijumu
features/i koristefeatures/support/webext.tstefeatures/support/world.ts. - Per-target manifest smoke tests u
extensions/<engine>/test/manifest.smoke.test.mjskoristenode:testi proveravaju oblik manifesta i referenced files. - CI u
.github/workflows/ci.ymlzaustavlja svaki push i pull request akomake test,make bddimake lintne prođu. Version-stamping i packaging nalaze se u.github/workflows/release.yml.