Safari is a supported active target with its own entry points. Built-in providers use static scripts and Safari requests no all-sites access. User-added sites receive a runtime optional permission, then the background injects content.js after navigation only for granted, enabled, non-builtin rules, with allFrames per rule. Safari settings are device-local and do not roam between devices. macOS packaging uses Xcode and the Apple Developer Program/App Store route.
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, Safari i Thunderbird.
Safari koristi statične skripte samo za ugrađene provajdere i objavljen je u
App Storeu. 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. Ta putanja
izvornih nalaza namerno ostaje ograničena na http:/https:.
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/destination.ts— klasifikacija i poređenje nepodudaranja odredišta prema šemipackages/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, ponovo obrađuje
target samo kada je Element unutar root-a preko isWithinRoot i closest,
a kasno otkrivanje root-a vodi ograničeni prolaz discoverMatchedRoots() po
batch-u sa jednim querySelectorAll(contentRoot), dedupliciranim pomoću
WeakSet. pendingShadowHosts ponovo proverava ranije plain hostove dok se
ne pojavi open shadow root, tako da pokrije i shadow upgrade-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, tokenom data-ru iz
generateToken i aktivnom konfiguracijom. Njegov metod process(root):
- Uklanja stavke registra čije sidro više nije povezano.
- Prikuplja kandidatska sidra pomoću
collectAnchors(potomkea[href]i sam koren kada jea[href], bez duplikata). - Razrešava odredište svakog sidra pomoću
resolveAnnotatableUrl, koji delegira naresolveAnnotatableHref. Vraćanull(preskoči) samo za prazan, unutarstranični ili neraščlanjiv href, razrešava relativne href-ove premabaseURIi prihvata svaku raščlanjivu šemu, uključujućimailto:,tel:,sms:i neprozirne URL-ovejavascript:,data:iblob:.ignoreHostsvaži samo za vezehttp:/https:koje imaju host. - Koristi idempotentnu brzu putanju kada
isAnnotationIntactpotvrdi da je otisak prikaza nepromenjen i da je svaki stvoreni čvor još povezan i na mestu; inače vraća zastarelu anotaciju i ponovo prikazuje sadržaj. - Izračunava nepodudarnost zavisnu od šeme pomoću
destinationMismatchnad čistim tekstom sidra:classifySchemešalje veb-veze uhostMismatch, poštu umailMismatch, telefon uphoneMismatch, a ostale uopaqueMismatch. Potom pozivaannotateAnchor;showOnlyOnMismatchskriva samo vidljivo umetnuto otkrivanje za vezu bez nepodudaranja, ali zadržava njentitleodredišta.
annotateAnchor prikazuje samo BEZBEDAN DOM. Uvek čuva izvorni title
(TitleSave) i upisuje title odredišta — jedini upis u sidro. Ne postoji
zaseban režim naslova ni oznaka upozorenja. Osim kada showOnlyOnMismatch
sakrije vezu bez nepodudaranja, dodaje ispred
<span class="reveal-urls-url"> i završni <br>, ostavljajući sopstvenu decu
veze netaknutom.
needsLeadingInlineRevealBreak dodaje vodeći <br> kada vidljivi umetnuti
sadržaj prethodi vezi u istom redu. Span prikazuje autorov apsolutni href
doslovno, ili relativni href razrešen prema baseURI, preko inertnog
textContent (sa strelicom i neprekidnim razmakom U+00A0 kao prefiksom, pa
skraćen pomoću truncateUrl); kada se poveže, dobija kontrastnu pozadinu preko
applyContrastBackdrop. Kod naglašene nepodudarnosti,
REVEAL_URLS_MISMATCH_CLASS se dodaje u REVEAL_URLS_URL_CLASS, primenjujući
podešenu boju nepodudarnosti preko applyColour i podrazumevano podebljan tekst
u REVEAL_URLS_CSS; izričito preinačenje težine fonta preko
applyFontOverrides ima prednost. Svaki stvoreni čvor dobija
data-ru="<token>" (nikada sidro), a svaka stavka beleži svoj
configFingerprint, pa promena konfiguracije prikaza nameće ponovno
prelamanje. revertOwned uklanja stvorene čvorove po referenci i vraća sačuvani
title; revertAll to čini za svako sidro u vlasništvu i prazni registar.
truncateUrl radi nad [...displayHref] i koristi URL_ELLIPSIS.
Nepodudaranje hosta
packages/core/src/hostMismatch.ts hostMismatch je veb grana funkcije
destinationMismatch: poredi vidljivi tekst veze sa njenim href po
registrabilnom domenu, a ne po sirovom imenu hosta, pa se poddomen bez
nepodudaranja ne označava, dok se imitacija označava. extractHostCandidates
razdvaja tekst i
svodi svaki token preko hostCandidate (koji zahteva tačku i ICANN javni sufiks
što ga tldts prepoznaje, odbacujući običan tekst sa tačkama kao e.g). I host
href-a i svaki kandidat svode se na svoj registrovani domen pomoću
tldts.getDomain, a BILO KOJI kandidat čiji se domen razlikuje od href-ovog
daje nepodudarnost — zato navođenje stvarnog zlonamernog hosta uz host-mamac ne
može potisnuti upozorenje. Nikada ne baca izuzetak.
Za poštanske veze mailMismatch poredi sve registrovane domene primalaca,
uključujući to, cc i bcc; phoneMismatch poredi razrešive birane brojeve.
opaqueMismatch uvek označava SCRIPT_LIKE_SCHEMES — javascript:, data: i
vbscript: — dok se druge neprozirne šeme označavaju samo kada vidljiv tekst
navodi konkretno veb, e-poštansko ili telefonsko odredište.
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,
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. - Lokalizacija internih linkova (Finding 2). Izabrani jezik se prenosi kroz
navigaciju dokumentacijom. Svako INTERNO sidro dokumenta (šest stranica
architecture.html,faq.html,index.html,licence.html,manual.html,privacy.html) označeno je sadata-doc-link="<page>", a runtime loader ga 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.