Reveal URLs — Архитектура
Овој документ опишува како е структурирано проширувањето Reveal URLs и како
работи неговата основна логика, за соработници и технички читатели. Тој
постојано упатува на вистински датотеки и симболи, во форма path
symbol, за секое тврдење да може да се провери во изворниот код. За
описот од гледна точка на корисникот видете го manual. За
чекорите за рачна проверка видете го
manual test plan.
Преглед
Reveal URLs е една MV3 WebExtension, изградена за Chrome, Edge, Opera,
Firefox и Thunderbird од едно заедничко чисто кодно јадро. Safari е одложен
за подоцнежна фаза: има manifest и може да се build-ира кога ќе се побара
изрично, но е исклучен од стандардниот build и од CI. Проширувањето го
открива целниот URL на секоја врска покрај самата врска во rendered email и
означува врска кога видливиот текст именува различен registrable домен од
оној во нејзиниот href.
Складиштето е monorepo за pnpm и TypeScript, опишано во
pnpm-workspace.yaml и package.json, додека "private": true го држи
надвор од објавување како пакет. Bundling се води преку tooling/build.mjs,
кој ги компилира заедничките пакети и тенките entry points за секој engine
во вчитлива директорија dist/<target>/. webextension-polyfill се
вклучува во секој script bundle, наместо да остане runtime global.
Два native додатоци за email ја прошируваат истата логика за откривање на
површини до кои WebExtension не може да стигне: Outlook Add-in на
Office.js и Gmail Add-on на Apps Script CardService. И двата користат
само PURE анализа од packages/core/src/findings.ts, со analyseAnchors,
analyseHtml, неутралниот модел Finding[], логиката hostMismatch и
изведувањето на registrable домен преку tldts. Се разликуваат само
adapter-от на host-от и презентацијата. Outlook користи DOMParser и работи
client-side. Gmail користи node-html-parser и работи server-side на
Apps Script V8 инфраструктурата на Google. Бидејќи ниту една од тие рамки
не дозволува менување на прочитаниот DOM на пораката, и двата додатока
прикажуваат панел или картичка со наоди, а не inline annotation.
Патеката за DOM mutation во linkProcessor.ts и REVEAL_URLS_CSS не се
користат таму. Релативен href се resolve-ира само наспроти доверлив
<base href> во рамките на email-от и инаку се прескокнува.
Кодната база јасно ги одвојува одговорностите:
packages/coreја содржи PURE логиката, без browser APIs, без storage и без markup sinks. Таа е единствениот извор на вистина за annotation на врски, валидација на config, споредување на host-ови и работа со contrast.packages/webextго обвиткува тоа јадро со WebExtension APIs, lifecycle-от на content-от, dynamic registration, UI-то за options и background wiring-от.extensions/<engine>ги држи тенките entry points по engine и нивнитеmanifest.json.toolingги држи скриптите за build, version-stamping и икони.featuresги држи Cucumber BDD сценаријата и test doubles.
Распоред на складиштето
packages/core — чистото јадро
Ниту еден модул тука не import-ира extension API како browser.*,
chrome.* или messenger.*, и ниту еден не користи string-to-markup DOM
sinks како innerHTML или insertAdjacentHTML. Ова правило се спроведува
механички преку packages/core/test/purity.test.ts, кој ги собира
src/**/*.ts, ги отстранува телата на коментарите со stripComments и
проверува дека ниту EXTENSION_API_PATTERN ниту UNSAFE_DOM_PATTERN не
се совпаѓаат со некој модул. Јавната површина се преекспортира од
packages/core/src/index.ts, а единствената runtime зависност е tldts.
Главните модули се:
packages/core/src/config.ts— шемитеConfigиSiteRule, стандардните вредности и funnel-отnormaliseConfigpackages/core/src/matchPattern.ts— граматиката на шаблоните, matcher-от и правилата за specificitetpackages/core/src/linkProcessor.ts— annotator-от на ниво документpackages/core/src/hostMismatch.ts— проверката за несовпаѓање на registrable доменpackages/core/src/styles.tsиpackages/core/src/contrast.ts— injectable stylesheet и логиката за contrastpackages/core/src/safeColour.ts— предикатотisSafeColour
packages/webext — обвивките на browser API
Модулите тука МОЖАТ да користат browser.* и тоа е единственото место каде
се допираат WebExtension APIs. Јавната површина се преекспортира од
packages/webext/src/index.ts. Тука се наоѓаат content.ts,
contentRegistration.ts, storage.ts, background.ts, toolbarAction.ts,
messageDisplay.ts, messageDisplayBackground.ts и options/options.ts.
extensions/<engine> — тенки влезни точки и manifest-и
Секој engine носи сопствен manifest.json и сопствен icons/. Chrome,
Firefox и Thunderbird имаат и src/ со entry points. Edge, Opera и Safari
носат само manifest и икони и повторно го користат src/ на Chrome. На
пример extensions/chrome/src/content.ts содржи само
void createContentController().bootstrap();, а
extensions/chrome/src/background.ts ги повикува registerBackground,
registerContentReconciliation и registerToolbarToggle.
extensions/thunderbird/src/background.ts ги повикува registerBackground и
registerMessageDisplay. extensions/_template е scaffold, не build target.
tooling, features и smoke tests
tooling ги вклучува build.mjs, version.mjs и icons.mjs. features
ги вклучува Gherkin сценаријата, step definitions и верните doubles во
features/support/webext.ts и features/support/world.ts. Секој engine има
и extensions/<engine>/test/manifest.smoke.test.mjs, а _template
користи template.smoke.test.mjs.
Текот на откривање
Патот на content-от започнува од entry point на engine, како
extensions/chrome/src/content.ts, кој го повикува
createContentController().bootstrap() од packages/webext/src/content.ts.
Bootstrap
createContentController bootstrap се извршува еднаш по секој frame. Тој
го присвојува frame-от пред секое await преку claimBootstrap, го вчитува
config преку loadConfigWithRetry, го инјектира stylesheet-от со
injectStyles дури по успешно читање и го враќа marker-от преку
releaseBootstrap ако вчитувањето целосно не успее. Кога config.enabled
е активен, го повикува start, а во секој случај регистрира listener
onConfigChanged.
Одредување на правилото за активен сајт
start и applyConfig го користат resolveSiteRule за да решат дали и
каде да направат annotation. Најпрво се проверува сопствениот URL на
документот преку resolveForHref, кој го филтрира config.sites до
вклучените правила и го остава изборот на selectMostSpecific. Ако
документот не се совпаѓа и неговото потекло е opaque или inherited, како
што проверува hasOpaqueOrigin, следи fallback преку readTopHref,
readOpenerHref и readReferrer, заштитен од cross-origin пристап.
Ограничување на коренот на содржината и набљудување на промените
start го зачувува contentRoot, создава annotator со
createAnnotator(config) и ја обработува секоја совпаѓачка root област од
selectRoots(doc, contentRoot), кој го обвиткува querySelectorAll со
try/catch, така што невалиден selector безбедно завршува во []. Потоа
го набљудува doc.body со OBSERVER_OPTIONS. processMutations ја филтрира
секое изменение според root-от, а processAddedNode ги покрива јазлите во
root-от, самиот root и јазлите што го содржат. applyConfig прави
teardown, restart или reflow, зависно од тоа дали има активен rule и дали
се сменил contentRoot.
Означување на поединечна врска
Annotator-от живее во packages/core/src/linkProcessor.ts.
createAnnotator враќа Annotator со Map, token data-ru од
generateToken и тековниот config. process(root) ги чисти веќе
неповрзаните entries, собира a[href] со collectAnchors, ги resolve-ира
URL-ите со resolveAnnotatableUrl, го користи fast path-от
isAnnotationIntact кога render-от е недопрен и го пресметува mismatch-от
со hostMismatch, пред да го повика annotateAnchor.
annotateAnchor render-ира само SAFE-DOM. Во режим "inline" додава
<span class="reveal-urls-url"> со textContent и <br>. Во режим
"title" го зачувува стариот title, го заменува со URL-от и при
нагласен mismatch додава <span class="reveal-urls-warn"> со
REVEAL_URLS_WARN_LABEL. Создадените јазли добиваат data-ru="<token>", а
configFingerprint се користи за reflow. truncateUrl работи врз
[...displayHref] и користи URL_ELLIPSIS.
Несовпаѓање на host-от
packages/core/src/hostMismatch.ts hostMismatch го споредува видливиот
текст на врската со нејзиниот href на ниво на registrable домен, не на
суров host. extractHostCandidates го дели текстот и ги сведува token-ите
преку hostCandidate, кој бара точка и public suffix што го препознава
tldts. Host-от на href и кандидатите се редуцираат со tldts.getDomain.
Секој кандидат со различен домен произведува mismatch.
Стилови и контраст
packages/core/src/styles.ts го држи REVEAL_URLS_CSS и helper-ите
applyColour, applyFontOverrides и applyContrastBackdrop.
applyContrastBackdrop ја чита пресметаната боја на елементот и првата
opaque позадина нагоре со getComputedStyle, а потоа го користи
needsWhiteBackdrop од packages/core/src/contrast.ts за да одлучи дали да
постави style.backgroundColor = "white". contrast.ts ги дава
parseColour, relativeLuminance и contrastRatio.
Конфигурирачки host-ови и ограничување на содржината
Еден SiteRule во packages/core/src/config.ts одредува КАДЕ се извршува
annotation преку match и allFrames и КОЈ container го ограничува преку
contentRoot, заедно со enabled и ознаката builtin. DEFAULT_SITES ги
покриваат Gmail, Proton со allFrames и двата Outlook host-а. Config
ги обединува глобалните toggles, colours, font overrides и листата sites.
Механизмот на match pattern
packages/core/src/matchPattern.ts ја држи restrictive граматиката
MATCH_PATTERN: само http и https, опционален *. wildcard host, glob path,
без port, без * scheme и без <all_urls>. matchesPattern споредува URL
со валидиран pattern и ја препушта path логиката на pathGlobMatches.
parsePatternParts го дели pattern-от на host, path, scheme и
wildcardHost.
Кога правилата се преклопуваат, важи принципот "most specific wins".
compareSiteSpecificity го рангира exact host пред wildcard host, потоа
подолгиот literal host, потоа полiteralниот path преку
literalPathLength, а на крај ASCII tiebreak. selectMostSpecific го
враќа најспецифичното вклучено правило што се совпаѓа со URL-от.
permissions.getAll() може да пријави grants во целосната граматика на
WebExtension (<all_urls>, *://*/*, https://*/*, *://*.host/*,
https://host/*) што MATCH_PATTERN би ги одбил. parseGrantedOrigin
ги претвора во GrantedOriginParts, originCovers проверува покривање врз
match, а matchAllowsOriginFallback кажува дали path-от е точно
ORIGIN_FALLBACK_PATH, односно /*.
Динамична регистрација
packages/webext/src/contentRegistration.ts регистрира по еден dynamic
content script за секое правило додадено од корисникот. Built-ins се
опслужуваат од static content_scripts entries и никогаш не се регистрираат
динамично. desiredContentScripts го филтрира config.sites, ги користи
isOriginGranted, originCovers и contentScriptId, и поставува
matchOriginAsFallback: true само кога тоа е дозволено од
matchAllowsOriginFallback(rule.match).
reconcileContentScriptsOnce ги чита granted origins, зачуваниот config и
live registrations и ги усогласува со property-aware diff. sameRegistration
ги нормализира саканиот descriptor и live read-back-от. Бидејќи не постои
updateContentScripts, променет script се отстранува и повторно се
регистрира. reconcileContentScripts користи promise inFlight за да
серијализира trigger-и што може да се случат блиску еден до друг.
Текот на изречната дозвола
Static manifest-ите декларираат optional_host_permissions (http://*/*,
https://*/*), така што корисникот може да даде дозвола за дополнителен
host за време на runtime. packages/webext/src/options/options.ts addSite
го валидира целиот кандидатен rule преку normaliseSiteRule, го извлекува
origin-от преку matchOrigin и го повикува browser.permissions.request
од click gesture-от на копчето Add. Дури по одобрување додава ред
builtin:false.
Config, storage и канонскиот модел
Единствениот funnel за валидација
normaliseConfig во packages/core/src/config.ts е единствениот канонски
пат за читање. Тој го преобразува и ограничува секое поле, се враќа на
стандардната вредност кога наидува на невалидни податоци и никогаш не фрла
exception. Изграден е од validators како normaliseBoolean,
normaliseRenderMode, normaliseMaxLength, normaliseIgnoreHosts,
normaliseMatchColour, normaliseMismatchColour, normaliseCssSize,
normaliseFontWeight, normaliseContentRoot, normaliseMatchPattern и
normaliseSites. normaliseSites ги отстранува невалидните entries,
ги отстранува дупликатите по match, наметнува builtin: true кога има
совпаѓање со built-in и ги враќа missing built-ins.
Чување
packages/webext/src/storage.ts го обвиткува WebExtension storage API.
configArea користи browser.storage.sync кога е достапен и прави fallback
кон browser.storage.local, на пример во Thunderbird. getConfig,
setConfig и onConfigChanged секогаш ја поминуваат вредноста низ
normaliseConfig, така што расипан store никогаш не може да врати невалиден
Config. Двете add-on форми го користат истиот funnel врз сопствените
host storage-и.
Канонскиот модел на config и целните ажурирања
Три write path-а го читаат канонскиот config, менуваат точно една работа и го запишуваат повторно, без да зачувуваат недовршени form edits:
removeSiteвоoptions.tsaddSiteвоoptions.tstoggleEnabledвоoptions.ts
toggleEnabled во packages/webext/src/toolbarAction.ts го следи истиот
модел и од страната на background-от.
Build по engine и договорот на manifest-от
tooling/build.mjs управува со единствен esbuild build за секој target на
WebExtension. Descriptor-от TARGETS го одредува SOURCE TARGET за секој
target. Chrome, Firefox и Thunderbird имаат сопствен src/. Edge, Opera и
Safari го користат chrome како source. Outlook add-in има сопствен source.
ACTIVE_TARGETS, односно Chrome, Edge, Firefox, Opera и Thunderbird, се
оние што се build-ираат со --all и --package. Safari и Outlook се
build-ираат само кога се именувани изрично, а Gmail користи
make build-gmail.
Bundle-от за Gmail го таргетира Apps Script V8 runtime, кој нема ES modules
и нема native URL. Затоа се build-ира како ESM, а потоа му се отстранува
export {…}, за trigger функциите да останат глобални. Мал polyfill за
URL се вклучува само кога е потребен. buildTarget го чисти
dist/<target>/, ги копира manifest.json и icons/, ги пакува scripts како
IIFE и страницата со options како ESM и ги копира options.html и
options.css.
tooling/version.mjs ги stamp-ира верзиите. Тој зема MAJOR.MINOR од root
package.json, го наоѓа највисокиот BUILD number и го додава на секој
target manifest. Outlook add-in ги носи и manifest.json и manifest.xml.
tooling/icons.mjs, стартуван преку make icons, rasterises
assets/icon.svg во icon48.png и icon128.png.
Договорот на manifest-от е дефиниран во
extensions/<engine>/test/manifest.smoke.test.mjs, каде што се проверуваат
точната форма на секој manifest и постоењето на секој referenced file.
Интернационализација (i18n)
Reveal URLs е локализиран на многу јазици освен англискиот. Единствениот
извор на овој список е packages/core/src/locales.json, кој содржи
SUPPORTED_LOCALES, домашните имиња и стандардниот јазик. Секој
неанглиски string е machine-translated и чека човечки преглед. Marker-от за
потекло се разликува по формат: _locales го носат во description,
site catalogues го бележат во site/i18n/README.md, а преведените docs
имаат HTML коментар на првиот ред.
Постојат три независни површини за локализација:
- The options page (AD-1). Статичкиот markup е на англиски, но секој
преводлив јазол носи
data-i18n="<key>"илиdata-i18n-<attr>="<key>". Во runtimepackages/webext/src/options/locale.tsго вчитува_locales/<code>/messages.json, го препишува текстот прекуtextContentи поставува<html lang>. - The website chrome (AD-2).
tooling/site.mjsго render-ира chrome-от на сајтот на англиски соdata-i18nи<html lang="en-GB">. Потоаsite/scripts/i18n.mjsго заменува со јазикот на посетителот одsite/i18n/<code>.json. - The documentation pages (AD-8/AD-9). Документациските страници се
render-ираат за секој locale за време на build и користат локализирани
имиња на датотеки од
packages/core/src/site-page-names.json, какоnl/handleiding.html. - Internal-link localisation (Finding 2). Внатрешните doc врски носат
data-doc-link="<page>", а runtime loader-от ги пренасочува кон sibling варијантата на истиот locale, задржувајќи#hashи?query.
Скривање на „Активни сајтови” во клиентот за пошта (AD-3)
Target mail client како Thunderbird веќе ги гледа сите rendered messages,
па editor-от „Active sites” по host е вишок. Одлуката се носи преку
mailClient: true во tooling/build.mjs.
- Build-time.
copyAssetsпроверуваdescriptorFor(target).mailClientи, кога е активен, го отстранува<section class="sites">прекуremoveSitesSection(html). - Runtime.
packages/webext/src/options/options.tsја толерира отсуството на секцијата, не фрлаOptionsFieldMissingErrorи ја враќа зачуваната вредност наsitesпред normalisation.
Став за безбедност и приватност
- Без мрежни повици и без извлекување податоци. Ниту еден модул не прави
fetch,XHRили друг мрежен повик. Проширувањето го чита само зачуваниот config и DOM-от на страницата. - Само SAFE-DOM. Annotation и инјектираниот stylesheet користат само
createElement,textContentиstyle.*, а неinnerHTML,insertAdjacentHTMLилиeval. - Секој надворешен влез се нормализира. Секое читање и запис оди низ
normaliseConfig. Боите одат прекуisSafeColour, шаблоните прекуMATCH_PATTERN, а content-root selector-ите се ограничуваат прекуCONTENT_ROOT_PATTERN. - Минимални привилегии. Вградените webmail origin-и се во
host_permissions. Дополнителните host-ови се opt-in прекуoptional_host_permissionsиpermissions.request. - Собирање податоци во Gecko. Firefox и Thunderbird manifest-ите
декларираат
browser_specific_settings.gecko.data_collection_permissions.required: ["none"]. - Лиценца. Проектот е AGPL-3.0-only, како што е наведено во
LICENSEиpackage.json.
Тестирање
- Vitest unit tests ги покриваат
packages/core/test/*иpackages/webext/test/*. - Cucumber BDD сценаријата живеат во
features/и ги користатfeatures/support/webext.tsиfeatures/support/world.ts. - Per-target manifest smoke tests во
extensions/<engine>/test/manifest.smoke.test.mjsкористатnode:testи ја проверуваат формата на manifest-ите и referenced files. - CI во
.github/workflows/ci.ymlго блокира секој push и pull request акоmake test,make bddиmake lintне поминат. Version-stamping и packaging живеат во.github/workflows/release.yml.