Reveal URLs — Arhitektura
Ovaj dokument opisuje kako je Reveal URLs strukturiran i kako radi njegova
osnovna logika, za saradnike i tehničke čitatelje. Kroz cijeli tekst navode
se stvarne datoteke i simboli u obliku path symbol, tako da se svaka
tvrdnja može provjeriti u izvornom kodu. Korisnički opis nalazi se u
manual, a koraci ručne provjere u
manual test plan.
Pregled
Reveal URLs je jedna MV3 WebExtension koja se iz jedne zajedničke, čiste
jezgre koda gradi za Chrome, Edge, Opera, Firefox i Thunderbird. Safari je
odgođen za kasniju fazu: ima manifest i može se izgraditi kada se eksplicitno
navede, ali nije uključen u podrazumijevani build ni u CI. Proširenje
prikazuje odredišni URL svakog linka pored samog linka u rendered email
sadržaju i označava link kada vidljivi tekst upućuje na drugu
registrable domenu od one iz href.
Repozitorij je pnpm i TypeScript monorepo opisan datotekama
pnpm-workspace.yaml i package.json, a "private": true sprječ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čitljivi
direktorij dist/<target>/. webextension-polyfill se bundla u svaki script
umjesto da se oslanja na runtime global.
Dva native dodatka za e-poštu proširuju istu jezgru detekcije na površine do
kojih WebExtension ne može doći: 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 mijenjanje DOM-a
prikazane poruke, oba prikazuju panel ili karticu s nalazima, a ne inline
annotation. Putanja DOM mutacije iz linkProcessor.ts i REVEAL_URLS_CSS
tu se ne koriste. Relativni href razrješava se samo prema pouzdanom
<base href> unutar same poruke, a u suprotnom se preskače.
Kodna baza 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 tu jezgru 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 repozitorija
packages/core — čista jezgra
Nijedan modul ovdje 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 provodi u packages/core/test/purity.test.ts, koji prolazi kroz
src/**/*.ts, uklanja tijela komentara pomoću stripComments i provjerava
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— shemeConfigiSiteRule, zadane vrijednosti i validacijski lijevaknormaliseConfigpackages/core/src/matchPattern.ts— gramatika pattern-a, matcher i pravila specifičnostipackages/core/src/linkProcessor.ts— annotator na nivou dokumentapackages/core/src/hostMismatch.ts— provjera 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 SMIJU koristiti browser.* i to je jedino mjesto gdje 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 vlastiti manifest.json i vlastiti icons/. Chrome,
Firefox i Thunderbird dodatno imaju direktorij src/ s entry point-ima.
Edge, Opera i Safari imaju samo manifest i ikone i ponovo koriste Chrome
src/. Na primjer, 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 vjerne doubles datoteke
features/support/webext.ts i features/support/world.ts. Svaki engine
također 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 prije svakog await preko claimBootstrap, učitava config preko
loadConfigWithRetry, ubacuje stylesheet kroz injectStyles tek nakon
uspješnog čitanja i vraća marker pomoću releaseBootstrap ako učitavanje
konačno ne uspije. 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 gdje
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 porijeklo je opaque ili inherited, kako to provjerava
hasOpaqueOrigin, tada slijedi fallback preko readTopHref,
readOpenerHref i readReferrer, uz zaštitu od cross-origin čitanja.
Ograničavanje na korijen sadržaja i praćenje promjena
start pamti contentRoot, kreira annotator preko createAnnotator(config)
i obrađuje svaki korijen iz selectRoots(doc, contentRoot), koji omotava
querySelectorAll u try/catch kako bi neispravan selector sigurno vratio
[]. Nakon toga posmatra doc.body koristeći OBSERVER_OPTIONS.
processMutations filtrira svaku promjenu prema root-u, a
processAddedNode obuhvata čvorove unutar root-a, sam root i čvorove koji ga
sadrže. applyConfig radi teardown, restart ili reflow zavisno od promjene
aktivnog pravila i contentRoot.
Označavanje pojedinačnog linka
Annotator se nalazi u packages/core/src/linkProcessor.ts.
createAnnotator vraća Annotator s Map, data-ru tokenom iz
generateToken i aktivnim config-om. Njegov process(root) čisti odspojene
zapise, skuplja a[href] elemente pomoću collectAnchors, razrješava URL
putem resolveAnnotatableUrl, koristi brzu putanju isAnnotationIntact kada
se render nije promijenio i računa mismatch pomoću hostMismatch prije nego
što pozove annotateAnchor.
annotateAnchor renderira 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, mijenja 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
s njegovim href na nivou registrable domene, a ne sirovog hosta.
extractHostCandidates dijeli 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
drugom 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 treba li 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 GDJE annotation radi
putem match i allFrames, te KOJI container je ograničava kroz
contentRoot, zajedno s 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 s
validiranim pattern-om i delegira path dio na pathGlobMatches.
parsePatternParts dijeli pattern na host, path, scheme i
wildcardHost.
Kada se pravila preklapaju, primjenjuje 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 prijaviti 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
provjerava 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 dodijeljena porijekla, pohranjeni config i
aktivne registracije te ih usklađuje kroz property-aware diff.
sameRegistration normalizira i željeni descriptor i live read-back. Pošto ne
postoji updateContentScripts, izmijenjeni script se prvo uklanja pa ponovo
registruje. reconcileContentScripts koristi promise inFlight kako bi
serijalizirao triggere koji se mogu dogoditi gotovo istovremeno.
Tok eksplicitne dozvole
Static manifesti deklarišu optional_host_permissions
(http://*/*, https://*/*) kako bi korisnik u runtime-u mogao dodijeliti
dozvolu za dodatni host. packages/webext/src/options/options.ts addSite
validira cijelo kandidat-pravilo preko normaliseSiteRule, izvodi host origin
preko matchOrigin i poziva browser.permissions.request iz click gesture-a
na Add dugmetu. Tek nakon odobrenja dodaje red builtin:false.
Konfiguracija, pohrana i kanonski obrazac
Jedinstveni validacijski lijevak
normaliseConfig u packages/core/src/config.ts je jedina kanonska read
putanja. On prisilno pretvara i ograničava svako polje, vraća se na zadanu
vrijednost 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 s
built-in pravilom i vraća sve ugrađene stavke koje nedostaju.
Pohrana
packages/webext/src/storage.ts obavija WebExtension storage API.
configArea koristi browser.storage.sync kada je dostupan, a prelazi na
browser.storage.local, na primjer u Thunderbirdu.
getConfig, setConfig i onConfigChanged uvijek provlače vrijednosti kroz
normaliseConfig, tako da pokvarena pohrana nikada ne može vratiti neispravan
Config. Oba add-on oblika koriste isti lijevak nad vlastitim host
storage-om.
Kanonska konfiguracija i ciljane izmjene
Tri write putanje čitaju kanonski config, mijenjaju tačno jednu stvar i zapisuju ga nazad, bez spremanja nedovršenih form edits:
removeSiteuoptions.tsaddSiteuoptions.tstoggleEnableduoptions.ts
toggleEnabled u packages/webext/src/toolbarAction.ts slijedi 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 vlastiti 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/, bundla script-ove kao IIFE, options stranicu kao
ESM i kopira options.html i options.css.
tooling/version.mjs utiskuje verzije. Uzima MAJOR.MINOR iz korijenskog
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, rasterizira assets/icon.svg
u icon48.png i icon128.png.
Manifest ugovor definiran je u datotekama
extensions/<engine>/test/manifest.smoke.test.mjs, gdje se provjerava oblik
svakog manifesta i postojanje svakog referenced file-a.
Internacionalizacija (i18n)
Reveal URLs je lokaliziran na mnogo jezika osim engleskog. Jedini izvor za taj
skup je packages/core/src/locales.json, koji sadrži SUPPORTED_LOCALES,
lokalna imena i zadani jezik. Svaki neengleski string je machine-translated i
čeka ljudsku provjeru. Oznaka porijekla zavisi od formata: _locales je nose
u polju description, site catalogues je bilježe u site/i18n/README.md, a
prevedeni docs u prvoj liniji nose HTML komentar.
Postoje tri nezavisne lokalizacijske 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.mjsrenderira site chrome na engleskom sadata-i18ni<html lang="en-GB">. Zatim gasite/scripts/i18n.mjszamjenjuje jezikom posjetioca izsite/i18n/<code>.json. - The documentation pages (AD-8/AD-9). Dokumentacijske stranice renderiraju
se za svaki locale u vrijeme build-a i koriste lokalizirane nazive datoteka
iz
packages/core/src/site-page-names.json, na primjernl/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.
copyAssetsprovjeravadescriptorFor(target).mailClienti kada je aktivan uklanja<section class="sites">prekoremoveSitesSection(html). - Runtime.
packages/webext/src/options/options.tspodnosi odsustvo tog odjeljka, ne bacaOptionsFieldMissingErrori vraća pohranjenu vrijednostsitesprije normalisation.
Sigurnosna i privatnosna pozicija
- Bez mrežnih poziva i iznošenja podataka. Nijedan modul ne izvršava
fetch,XHRniti druge mrežne pozive. Proširenje čita samo pohranjeni config i DOM stranice. - Samo SAFE-DOM. Annotation i injected stylesheet koriste samo
createElement,textContentistyle.*, a neinnerHTML,insertAdjacentHTMLilieval. - Sav vanjski unos se normalizira. Svako čitanje i pisanje prolazi kroz
normaliseConfig. Colours se provjeravaju prekoisSafeColour, pattern-i prekoMATCH_PATTERN, a content-root selectors ograničavaju se prekoCONTENT_ROOT_PATTERN. - Minimalne privilegije. Ugrađena webmail porijekla 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. Projekt 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 direktoriju
features/i koristefeatures/support/webext.tstefeatures/support/world.ts. - Per-target manifest smoke tests u
extensions/<engine>/test/manifest.smoke.test.mjskoristenode:testi provjeravaju oblik manifesta te 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.