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 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, Safari i Thunderbird.
Safari koristi statične skripte samo za ugrađene pružatelje i objavljen je u
App Storeu. 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.
Ta putanja native nalaza namjerno ostaje ograničena na http:/https:.
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/destination.ts— klasifikacija i poređenje nepodudaranja odredišta prema shemipackages/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, 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
batchu sa jednim querySelectorAll(contentRoot), dedupliciranim pomoću
WeakSet. pendingShadowHosts ponovo provjerava ranije plain hostove dok se
ne pojavi open shadow root, tako da pokrije i shadow upgrade-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):
- Orezuje zapise registra čije sidro više nije povezano.
- Prikuplja sidra kandidate pomoću
collectAnchors(potomaka[href]i sam korijen kada jea[href], bez duplikata). - Razrješava odredište svakog sidra pomoću
resolveAnnotatableUrl, koji delegira naresolveAnnotatableHref. Vraćanull(preskoči) samo za prazan/unutar-stranice ili neparsabilan href, razrješava relativne href-ove premabaseURIi prihvata svaku parsabilnu shemu, uključujućimailto:,tel:,sms:i opaque URL-ovejavascript:,data:iblob:.ignoreHostsse primjenjuje samo nahttp:/https:linkove s hostom. - Bira idempotentnu brzu putanju kada
isAnnotationIntactpotvrdi da je render fingerprint nepromijenjen i da je svaki kreirani čvor još povezan i na svom mjestu; u suprotnom vraća zastarjelu anotaciju i ponovo renderira. - Računa nepodudaranje prema shemi s
destinationMismatchna čistom tekstu sidra:classifySchemeusmjerava web linkove nahostMismatch, poštu namailMismatch, telefon naphoneMismatch, a sve ostalo naopaqueMismatch. Zatim pozivaannotateAnchor;showOnlyOnMismatchpotiskuje samo vidljivo inline otkrivanje linka bez nepodudarnosti, zadržavajući njegov odredišnititle.
annotateAnchor renderira samo SAFE-DOM. Uvijek čuva izvorni title
(TitleSave) i zapisuje title odredišta — jedini zapis u sidro. Nema zasebnog
title režima ni badge-a upozorenja. Osim ako showOnlyOnMismatch sakrije link bez
nepodudarnosti, dodaje na početak <span class="reveal-urls-url"> i završni <br>, bez
mijenjanja vlastite djece linka. needsLeadingInlineRevealBreak dodaje vodeći
<br> kada vidljivi inline sadržaj prethodi linku u istom redu. Span prikazuje
doslovno napisani apsolutni href ili relativni href razriješen prema baseURI
preko inertnog textContent (s prefiksom strelice i neraskidivog razmaka U+00A0,
zatim skraćenog s truncateUrl); nakon povezivanja dobija kontrastnu pozadinu
preko applyContrastBackdrop. Za naglašeno nepodudaranje,
REVEAL_URLS_MISMATCH_CLASS se dodaje u REVEAL_URLS_URL_CLASS, primjenjujući
konfiguriranu boju nepodudaranja pomoću applyColour i podebljanje prema
zadanim postavkama u REVEAL_URLS_CSS; eksplicitno nadjačavanje debljine fonta
putem applyFontOverrides ima prednost. Svaki kreirani čvor označen je s
data-ru="<token>" (nikada sidro), a svaki zapis čuva svoj configFingerprint,
tako da promjena render config-a prisiljava reflow. revertOwned uklanja
kreirane čvorove po referenci i vraća sačuvani title; revertAll to radi za
svako posjedovano sidro i čisti registar.
truncateUrl radi preko Unicode CODE POINTS ([...displayHref]), tako da rez
nikada ne dijeli surrogate par; čuva porijeklo sheme i hosta te skraćuje ostatak
završnim URL_ELLIPSIS.
Nepodudaranje hosta
packages/core/src/hostMismatch.ts hostMismatch je web grana
destinationMismatch: poredi vidljivi tekst linka s njegovim href prema
registrable domeni, a ne sirovom hostu, tako da se poddomen bez nepodudarnosti ne označava,
dok se look-alike označava. extractHostCandidates dijeli tekst i svodi svaki
token preko hostCandidate (koji zahtijeva tačku i ICANN public suffix koji
tldts prepoznaje, odbijajući običan tačkasti tekst kao e.g). I href host i
svaki kandidat svode se na registrable domenu pomoću tldts.getDomain, a BILO
KOJI kandidat čija se domena razlikuje od href-ove daje nepodudaranje — zato
navođenje stvarnog zlonamjernog hosta pored hosta mamca ne može potisnuti
upozorenje. Nikada ne baca exception.
Za mail linkove mailMismatch poredi sve registrable domene primatelja,
uključujući to, cc i bcc; phoneMismatch poredi razrješive birane brojeve.
opaqueMismatch uvijek označava SCRIPT_LIKE_SCHEMES — javascript:, data:
i vbscript: — dok se druge opaque sheme označavaju samo kada vidljivi tekst
navodi konkretno web, email 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 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 FastMail, Gmail, Proton sa allFrames, Tuta, Yahoo, Zoho 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,
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 za šest stranica
architecture.html,faq.html,index.html,licence.html,manual.html,privacy.htmlimajudata-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.