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 — Архитектура

Овој документ опишува како е структурирано проширувањето Reveal URLs и како работи неговата основна логика, за соработници и технички читатели. Тој постојано упатува на вистински датотеки и симболи, во форма path symbol, за секое тврдење да може да се провери во изворниот код. За описот од гледна точка на корисникот видете го manual. За чекорите за рачна проверка видете го manual test plan.

Преглед

Reveal URLs е една MV3 WebExtension, изградена за Chrome, Edge, Opera, Firefox, Safari и Thunderbird од едно заедничко чисто кодно јадро. Safari користи статични скрипти само за вградените добавувачи и е објавен на App Store. Проширувањето го открива целниот 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-от и инаку се прескокнува. Оваа патека за локални наоди намерно останува само за http:/https:.

Кодната база јасно ги одвојува одговорностите:

Распоред на складиштето

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/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-от, го обработува target-от повторно само кога е Element во root-от преку isWithinRoot и closest, а доцното откривање на root-ови го препушта на ограничениот премин discoverMatchedRoots() по batch со едно querySelectorAll(contentRoot) и дедупликација преку WeakSet. pendingShadowHosts повторно ги проверува претходно plain host-овите додека не се појави open shadow root, за да бидат покриени и shadow upgrade-ите. applyConfig прави teardown, restart или reflow, зависно од тоа дали има активен rule и дали се сменил contentRoot.

Означување на поединечна врска

Annotator-от живее во packages/core/src/linkProcessor.ts. createAnnotator враќа Annotator со Map, token data-ru од generateToken и тековниот config. Неговиот метод process(root):

  1. Ги отстранува записите од регистарот чиј anchor веќе не е поврзан.
  2. Собира candidate anchor-и со collectAnchors (потомни a[href] плус самиот root кога е a[href], без дупликати).
  3. Го resolve-ира одредиштето на секој anchor со resolveAnnotatableUrl, кој делегира на resolveAnnotatableHref. Враќа null (прескокни) само за празен / внатрешен или непарсибилен href, resolve-ира релативни href-ови наспроти baseURI и прифаќа секоја парсибилна шема, вклучувајќи mailto:, tel:, sms: и непрозирни javascript:, data: и blob: URL-адреси. ignoreHosts важи само за http:/https: врски со host.
  4. Ја користи идемпотентната брза патека кога isAnnotationIntact потврдува дека отпечатокот на render-ирањето не е променет и секој создаден јазол сè уште е поврзан и на своето место; инаку ја враќа застарената анотација и render-ира повторно.
  5. Го пресметува несовпаѓањето зависно од шемата со destinationMismatch врз чистиот текст на anchor-от: classifyScheme ги испраќа веб-врските до hostMismatch, поштата до mailMismatch, телефонот до phoneMismatch, а останатото до opaqueMismatch. Потоа го повикува annotateAnchor; showOnlyOnMismatch го потиснува само видливото внатрешно откривање на врска без несовпаѓање, а го задржува нејзиниот title за одредиштето.

annotateAnchor render-ира само SAFE-DOM. Секогаш го зачувува изворниот title (TitleSave) и го запишува title на одредиштето — единственото запишување во anchor-от. Нема посебен title режим ниту предупредувачка ознака. Освен ако showOnlyOnMismatch не скрие врска без несовпаѓање, пред неа вметнува <span class="reveal-urls-url"> и завршен <br>, без да ги допира нејзините деца. needsLeadingInlineRevealBreak додава почетен <br> кога видлива внатрешна содржина ѝ претходи на врската во истиот ред. Span прикажува апсолутен href напишан од авторот дословно, или релативен href resolve-иран наспроти baseURI, преку инертен textContent (со стрелка и U+00A0 нераскинлив простор како префикс, па скратен со truncateUrl); штом се поврзе, добива контрастна заднина преку applyContrastBackdrop. При нагласено несовпаѓање, REVEAL_URLS_MISMATCH_CLASS се додава на REVEAL_URLS_URL_CLASS, со што се применува конфигурираната боја за несовпаѓање преку applyColour и задебелување по правило во REVEAL_URLS_CSS; изречното надминување на дебелината на фонтот преку applyFontOverrides има предност. Секој создаден јазол е означен со data-ru="<token>" (никогаш самиот anchor), а секој запис го бележи својот configFingerprint, така што промена во render-конфигурацијата присилува преформатирање. revertOwned ги отстранува создадените јазли по референца и го враќа зачуваниот title; revertAll го прави тоа за секој сопствен anchor и го чисти регистарот.

truncateUrl работи со Unicode КОДНИ ТОЧКИ ([...displayHref]), така што кратењето никогаш не дели сурогатски пар; го зачувува потеклото на шемата и host-от, а остатокот го скратува со завршна URL_ELLIPSIS.

Несовпаѓање на host-от

packages/core/src/hostMismatch.ts hostMismatch е веб-гранката на destinationMismatch: го споредува видливиот текст на врската со нејзиниот href според registrable домен, не според сурово име на host, така што поддомен без несовпаѓање не се означува, а сличен домен се означува. extractHostCandidates го дели текстот и секој token го сведува преку hostCandidate (кој бара точка и ICANN јавен suffix препознаен од tldts, отфрлајќи обичен текст со точки, како e.g). И host-от на href и секој кандидат се сведуваат на нивниот registrable домен со tldts.getDomain, а СЕКОЈ кандидат чиј домен се разликува од href доменот дава несовпаѓање — така што наведувањето на вистинскиот злонамерен host покрај мамечки host не може да го потисне предупредувањето. Никогаш не фрла исклучок.

За поштенски врски, mailMismatch ги споредува сите registrable домени на примачите, вклучувајќи to, cc и bcc; phoneMismatch споредува разрешливи избрани броеви. opaqueMismatch секогаш ги означува SCRIPT_LIKE_SCHEMESjavascript:, data: и vbscript: — додека други непрозирни шеми се означуваат само кога видливиот текст именува конкретно веб-, е-пошта- или телефонско одредиште.

Стилови и контраст

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 ги покриваат FastMail, Gmail, Proton со allFrames, Tuta, Yahoo, Zoho и двата 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, 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:

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 коментар на првиот ред.

Постојат три независни површини за локализација:

Скривање на „Активни сајтови” во клиентот за пошта (AD-3)

Target mail client како Thunderbird веќе ги гледа сите rendered messages, па editor-от „Active sites” по host е вишок. Одлуката се носи преку mailClient: true во tooling/build.mjs.

Став за безбедност и приватност

Тестирање