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 — Architektūra

Šiame dokumente aprašoma, kaip struktūruotas Reveal URLs plėtinys ir kaip veikia jo pagrindinė logika, skirta prisidedantiems ir techniniams skaitytojams. Čia nuolat remiamasi tikrais failais ir simboliais forma path symbol, kad kiekvieną teiginį būtų galima patikrinti šaltinio kode. Naudotojui skirtą aprašymą rasite manual, o rankinio tikrinimo žingsnius — manual test plan.

Apžvalga

Reveal URLs yra viena MV3 WebExtension, kuri iš vieno bendro švaraus kodo branduolio kuriama Chrome, Edge, Opera, Firefox, Safari ir Thunderbird sistemoms. Safari naudoja statinius scenarijus tik integruotiems teikėjams ir yra paskelbta „App Store“. Plėtinys rodo kiekvienos nuorodos paskirties URL šalia pačios nuorodos rendered email turinyje ir pažymi nuorodą tada, kai matomas tekstas nurodo kitą registrable domeną nei tas, kuris yra jos href.

Saugykla yra pnpm ir TypeScript monorepo, aprašyta pnpm-workspace.yaml ir package.json, o "private": true neleidžia jos publikuoti kaip paketo. Bundlingą valdo tooling/build.mjs, kuris kompiliuoja bendrus paketus ir plonus entry points kiekvienam engine į įkraunamą dist/<target>/ katalogą. webextension-polyfill įtraukiamas į kiekvieną script bundle, o ne paliekamas kaip runtime global.

Du native el. pašto priedai išplečia tą pačią aptikimo logiką į vietas, kur WebExtension nepasiekia: Outlook Add-in ant Office.js ir Gmail Add-on ant Apps Script CardService. Abu pakartotinai naudoja tik PURE analizę iš packages/core/src/findings.ts, kartu su analyseAnchors, analyseHtml, neutraliu modeliu Finding[], hostMismatch logika ir registrable domeno išvedimu per tldts. Skiriasi tik host adapteris ir pateikimas. Outlook naudoja DOMParser ir veikia client-side. Gmail naudoja node-html-parser ir veikia server-side Google Apps Script V8 infrastruktūroje. Kadangi nė viena iš šių aplinkų neleidžia keisti perskaityto pranešimo DOM, abu rodo panelį arba kortelę su radiniais, o ne inline annotation. linkProcessor.ts DOM-mutation kelias ir REVEAL_URLS_CSS ten nenaudojami. Santykinis href sprendžiamas tik prieš patikimą <base href> pačiame laiške ir kitu atveju praleidžiamas. Šis vietinių radinių kelias sąmoningai lieka skirtas tik http:/https:.

Kodo bazė aiškiai padalija atsakomybes:

Saugyklos išdėstymas

packages/core — grynasis branduolys

Nė vienas modulis čia neimportuoja extension API, pavyzdžiui, browser.*, chrome.* arba messenger.*, ir nė vienas nenaudoja string-to-markup DOM sinks, tokių kaip innerHTML ar insertAdjacentHTML. Šį susitarimą mechaniškai užtikrina packages/core/test/purity.test.ts, kuris surenka src/**/*.ts, pašalina komentarų kūnus su stripComments ir patikrina, kad nei EXTENSION_API_PATTERN, nei UNSAFE_DOM_PATTERN neatitiktų nė vieno modulio. Viešasis paviršius reeksportuojamas iš packages/core/src/index.ts, o vienintelė runtime priklausomybė yra tldts.

Pagrindiniai moduliai yra:

packages/webext — browser API apvalkalai

Šie moduliai GALI naudoti browser.*, ir tai yra vienintelė vieta, kur liečiamos WebExtension APIs. Viešasis paviršius reeksportuojamas iš packages/webext/src/index.ts. Čia yra content.ts, contentRegistration.ts, storage.ts, background.ts, toolbarAction.ts, messageDisplay.ts, messageDisplayBackground.ts ir options/options.ts.

extensions/<engine> — ploni įėjimo taškai ir manifestai

Kiekvienas engine turi savo manifest.json ir savo icons/. Chrome, Firefox ir Thunderbird papildomai turi src/ katalogą su entry points. Edge, Opera ir Safari turi tik manifestus bei piktogramas ir iš naujo naudoja Chrome src/. Pavyzdžiui, extensions/chrome/src/content.ts turi tik void createContentController().bootstrap();, o extensions/chrome/src/background.ts iškviečia registerBackground, registerContentReconciliation ir registerToolbarToggle. extensions/thunderbird/src/background.ts iškviečia registerBackground ir registerMessageDisplay. extensions/_template yra scaffold, o ne build target.

tooling, features ir smoke testai

tooling apima build.mjs, version.mjs ir icons.mjs. features apima Gherkin scenarijus, step definitions ir patikimus doubles failuose features/support/webext.ts ir features/support/world.ts. Kiekvienas engine taip pat turi extensions/<engine>/test/manifest.smoke.test.mjs, o _template naudoja template.smoke.test.mjs.

Atskleidimo srautas

Content kelias prasideda engine entry point-e, pavyzdžiui, extensions/chrome/src/content.ts, kuris iškviečia createContentController().bootstrap()packages/webext/src/content.ts.

Bootstrap

createContentController bootstrap vykdomas vieną kartą kiekvienam frame. Jis perima frame dar prieš kiekvieną await per claimBootstrap, įkelia config per loadConfigWithRetry, įšvirkščia stylesheet per injectStyles tik po sėkmingo nuskaitymo ir atstato markerį per releaseBootstrap, jei įkėlimas galutinai nepavyksta. Kai config.enabled yra aktyvus, jis kviečia start, o bet kuriuo atveju užregistruoja listener onConfigChanged.

Aktyvios svetainės taisyklės nustatymas

start ir applyConfig naudoja resolveSiteRule, kad nuspręstų, ar ir kur atlikti annotation. Pirmiausia bandoma paties dokumento vieta per resolveForHref, kuris perfiltruoja config.sites iki įjungtų taisyklių ir galutinį pasirinkimą palieka selectMostSpecific. Jei dokumentas neatitinka ir jo kilmė yra opaque arba inherited, kaip tikrina hasOpaqueOrigin, tada naudojamas fallback per readTopHref, readOpenerHref ir readReferrer, saugantis nuo cross-origin skaitymų.

Apribojimas iki turinio šaknies ir pokyčių stebėjimas

start išsaugo contentRoot, sukuria annotator per createAnnotator(config) ir apdoroja kiekvieną šaknį iš selectRoots(doc, contentRoot), kuris apgaubia querySelectorAll su try/catch, kad neteisingas selector saugiai grąžintų []. Po to jis stebi doc.body su OBSERVER_OPTIONS. processMutations filtruoja kiekvieną pokytį pagal root, targetą iš naujo apdoroja tik tada, kai tai yra Element root viduje per isWithinRoot ir closest, o vėlyvą root aptikimą patiki ribotam discoverMatchedRoots() ėjimui kiekviename batch, su vienu querySelectorAll(contentRoot) ir deduplikacija per WeakSet. pendingShadowHosts iš naujo tikrina anksčiau plain buvusius hostus, kol atsiranda open shadow root, kad būtų padengti ir shadow upgrade-ai. applyConfig atlieka teardown, restart arba reflow priklausomai nuo aktyvios taisyklės ir contentRoot pasikeitimo.

Vienos nuorodos žymėjimas

Annotator gyvena faile packages/core/src/linkProcessor.ts. createAnnotator grąžina Annotator su Map, data-ru tokenu iš generateToken ir aktyviu config. Jo metodas process(root):

  1. Išvalo registro įrašus, kurių nuoroda nebėra prijungta.
  2. Surenka kandidatines nuorodas per collectAnchors (palikuonių a[href] ir pačią šaknį, jei ji yra a[href], pašalinant dublikatus).
  3. Išsprendžia kiekvienos nuorodos paskirtį per resolveAnnotatableUrl, kuris deleguoja į resolveAnnotatableHref. Jis grąžina null (praleisti) tik tuščiam / puslapio vidiniam ar neišanalizuojamam href, santykinius href išsprendžia pagal baseURI ir priima kiekvieną išanalizuojamą schemą, įskaitant mailto:, tel:, sms: bei neskaidrius javascript:, data: ir blob: URL. ignoreHosts taikomas tik host turinčioms http:/https: nuorodoms.
  4. Pasirenka idempotentinį greitą kelią, kai isAnnotationIntact patvirtina, kad renderio pirštų atspaudas nepasikeitė ir kiekvienas sukurtas mazgas vis dar prijungtas bei savo vietoje; kitaip atšaukia pasenusią anotaciją ir renderina iš naujo.
  5. Apskaičiuoja nuo schemos priklausančią neatitiktį su destinationMismatch pagal švarų nuorodos tekstą: classifyScheme žiniatinklio nuorodas perduoda hostMismatch, pašto — mailMismatch, telefono — phoneMismatch, o kitas — opaqueMismatch. Tada iškviečia annotateAnchor; showOnlyOnMismatch paslepia tik nuorodos be neatitikčių matomą įterptinį atskleidimą, bet išlaiko jos paskirties title.

annotateAnchor renderina tik SAFE-DOM. Jis visada išsaugo pradinį title (TitleSave) ir įrašo paskirties title — tai vienintelis rašymas į nuorodą. Atskiro title režimo ar įspėjimo ženklelio nėra. Jei showOnlyOnMismatch nepaslepia nuorodos be neatitikčių, jis prieš ją įterpia <span class="reveal-urls-url"> ir galinį <br>, nepalikdamas jos vaikų nepakeistų. needsLeadingInlineRevealBreak prideda pradinį <br>, kai toje pačioje eilutėje prieš nuorodą yra matomas įterptinis turinys. Span rodo autoriaus nurodytą absoliutų href pažodžiui arba pagal baseURI išspręstą santykinį href, naudodamas inertinį textContent (su rodykle ir U+00A0 nepertraukiamu tarpu prieš jį, paskui sutrumpintą per truncateUrl); prijungus jam per applyContrastBackdrop suteikiamas kontrastinis fonas. Esant pabrėžtai neatitikčiai, REVEAL_URLS_MISMATCH_CLASS pridedama prie REVEAL_URLS_URL_CLASS, todėl per applyColour pritaikoma sukonfigūruota neatitikties spalva ir REVEAL_URLS_CSS numatytasis pusjuodis šriftas; aiškus šrifto storio pakeitimas per applyFontOverrides turi pirmenybę. Visi sukurti mazgai pažymimi data-ru="<token>" (niekada ne pati nuoroda), o kiekvienas įrašas saugo savo configFingerprint, kad pasikeitus renderio konfigūracijai būtų priverstinai performuota. revertOwned pašalina sukurtus mazgus pagal nuorodą ir atkuria išsaugotą title; revertAll tai padaro su kiekviena valdoma nuoroda ir išvalo registrą.

truncateUrl dirba su Unicode KODO TAŠKAIS ([...displayHref]), todėl trumpinimas niekada nepadalija surogatinės poros; jis išsaugo schemos ir host kilmę, o likusią dalį sutrumpina galine URL_ELLIPSIS.

Host neatitiktis

packages/core/src/hostMismatch.ts hostMismatch yra žiniatinklio šaka iš destinationMismatch: jis lygina nuorodos matomą tekstą su jos href pagal registrable domeną, o ne žalią host pavadinimą, todėl subdomenas be neatitikčių nepažymimas, o panašus domenas pažymimas. extractHostCandidates išskaido tekstą ir sumažina kiekvieną tokeną per hostCandidate (kuriam reikia taško ir tldts atpažįstamo ICANN viešojo sufikso, atmetant įprastą taškuotą tekstą, pvz., e.g). Tiek href host, tiek kiekvienas kandidatas per tldts.getDomain sumažinami iki registrable domeno, ir BET KURIS kandidatas, kurio domenas skiriasi nuo href domeno, sukelia neatitiktį — taigi tikro kenkėjiško host paminėjimas šalia jauko host negali nuslopinti įspėjimo. Jis niekada nemeta išimties.

Pašto nuorodoms mailMismatch lygina visus gavėjų registrable domenus, įskaitant to, cc ir bcc; phoneMismatch lygina išsprendžiamus surinktus numerius. opaqueMismatch visada pažymi SCRIPT_LIKE_SCHEMESjavascript:, data: ir vbscript: — o kitos neskaidrios schemos pažymimos tik kai matomas tekstas nurodo konkretų žiniatinklio, el. pašto ar telefono paskirties adresą.

Stiliai ir kontrastas

packages/core/src/styles.ts saugo REVEAL_URLS_CSS ir helperius applyColour, applyFontOverrides ir applyContrastBackdrop. applyContrastBackdrop nuskaito elemento apskaičiuotą spalvą ir pirmą opaque foną per getComputedStyle, tada naudoja needsWhiteBackdroppackages/core/src/contrast.ts, kad nuspręstų, ar nustatyti style.backgroundColor = "white". contrast.ts pateikia parseColour, relativeLuminance ir contrastRatio.

Konfigūruojami host ir turinio ribojimas

SiteRule faile packages/core/src/config.ts nusako, KUR annotation veikia per match ir allFrames, ir KOKS container jį apriboja per contentRoot, kartu su enabled ir žyme builtin. DEFAULT_SITES apima FastMail, Gmail, Proton su allFrames, Tuta, Yahoo, Zoho ir abu Outlook host. Config sujungia globalius toggles, colours, font overrides ir sąrašą sites.

Match pattern mechanizmas

packages/core/src/matchPattern.ts saugo restrictive gramatiką MATCH_PATTERN: tik http ir https, pasirenkamą *. wildcard host, glob path, be port, be * scheme ir be <all_urls>. matchesPattern lygina URL su validuotu pattern ir path dalį perduoda pathGlobMatches. parsePatternParts padalija pattern į host, path, scheme ir wildcardHost.

Kai taisyklės persidengia, taikomas principas "most specific wins". compareSiteSpecificity iškelia exact host virš wildcard host, po to ilgesnį literal host, po to labiau literal path per literalPathLength, o galiausiai ASCII tiebreak. selectMostSpecific grąžina specifiškiausią įjungtą taisyklę, atitinkančią URL.

permissions.getAll() gali pranešti grants visoje WebExtension gramatikoje (<all_urls>, *://*/*, https://*/*, *://*.host/*, https://host/*), kuriuos MATCH_PATTERN atmestų. parseGrantedOrigin paverčia juos į GrantedOriginParts, originCovers tikrina padengimą per match, o matchAllowsOriginFallback nurodo, ar path yra tiksliai ORIGIN_FALLBACK_PATH, tai yra /*.

Dinaminė registracija

packages/webext/src/contentRegistration.ts užregistruoja po vieną dynamic content script kiekvienai naudotojo pridėtai taisyklei. Built-ins aptarnaujami per static content_scripts entries ir niekada neregistruojami dinamiškai. desiredContentScripts perfiltruoja config.sites, naudoja isOriginGranted, originCovers ir contentScriptId, ir nustato matchOriginAsFallback: true tik tada, kai tai leidžia matchAllowsOriginFallback(rule.match).

reconcileContentScriptsOnce nuskaito suteiktas kilmes, išsaugotą config ir live registrations bei sulygina jas property-aware diff būdu. sameRegistration normalizuoja tiek pageidaujamą descriptor, tiek live read-back. Kadangi nėra updateContentScripts, pakeistas script pašalinamas ir užregistruojamas iš naujo. reconcileContentScripts naudoja promise inFlight, kad nuosekliai vykdytų triggerius, kurie gali nutikti beveik vienu metu.

Aiškaus leidimo srautas

Static manifestai deklaruoja optional_host_permissions (http://*/*, https://*/*), kad naudotojas runtime metu galėtų suteikti leidimą papildomam host. packages/webext/src/options/options.ts addSite validuoja visą kandidato rule per normaliseSiteRule, išveda origin per matchOrigin ir kviečia browser.permissions.request iš Add mygtuko click gesture. Tik po patvirtinimo pridedama eilutė builtin:false.

Config, saugykla ir kanoninis modelis

Vienintelis validavimo piltuvas

normaliseConfig faile packages/core/src/config.ts yra vienintelis kanoninis skaitymo kelias. Jis konvertuoja ir apriboja kiekvieną lauką, neteisingų duomenų atveju grąžina numatytąją reikšmę ir niekada nemeta exception. Jis sudarytas iš validatorių, tokių kaip normaliseBoolean, normaliseMaxLength, normaliseIgnoreHosts, normaliseMatchColour, normaliseMismatchColour, normaliseCssSize, normaliseFontWeight, normaliseContentRoot, normaliseMatchPattern ir normaliseSites. normaliseSites išmeta neteisingus entries, pašalina dublikatus pagal match, priverčia builtin: true ten, kur taisyklė persidengia su built-in, ir atkuria trūkstamus built-ins.

Saugojimas

packages/webext/src/storage.ts apgaubia WebExtension storage API. configArea naudoja browser.storage.sync, kai jis prieinamas, ir pereina prie browser.storage.local, pavyzdžiui, Thunderbird aplinkoje. getConfig, setConfig ir onConfigChanged visada perleidžia reikšmes per normaliseConfig, todėl sugadinta saugykla negali grąžinti neteisingo Config. Abi add-on formos naudoja tą patį piltuvą ant savo host storage.

Kanoninis config modelis ir tiksliniai atnaujinimai

Trys write path perskaito kanoninį config, pakeičia tik vieną dalyką ir įrašo jį atgal, nesaugodami neužbaigtų form edits:

toggleEnabled faile packages/webext/src/toolbarAction.ts seka tuo pačiu modeliu ir background pusėje.

Build pagal engine ir manifesto sutartis

tooling/build.mjs valdo vieną bendrą esbuild build kiekvienam WebExtension target. Descriptor TARGETS nustato SOURCE TARGET kiekvienam target. Chrome, Firefox ir Thunderbird turi savo src/. Edge, Opera ir Safari naudoja chrome kaip šaltinį. Outlook add-in turi atskirą šaltinį. ACTIVE_TARGETS, tai yra Chrome, Edge, Firefox, Opera ir Thunderbird, yra tie, kurie build-inami per --all ir --package. Safari ir Outlook build-inami tik tada, kai aiškiai įvardijami, o Gmail naudoja make build-gmail.

Gmail bundle taikomas Apps Script V8 runtime, kuriame nėra nei ES modules, nei native URL. Todėl jis build-inamas kaip ESM, o po to jam pašalinamas export {…}, kad trigger funkcijos liktų globalios. Nedidelis URL polyfill įtraukiamas tik prireikus. buildTarget išvalo dist/<target>/, nukopijuoja manifest.json ir icons/, supakuoja scripts kaip IIFE, options puslapį kaip ESM ir nukopijuoja options.html bei options.css.

tooling/version.mjs stamp-ina versijas. Jis paima MAJOR.MINOR iš root package.json, suranda didžiausią BUILD number ir prideda jį kiekvienam target manifest. Outlook add-in turi tiek manifest.json, tiek manifest.xml. tooling/icons.mjs, paleistas su make icons, rasterises assets/icon.svg į icon48.png ir icon128.png.

Manifesto sutartis apibrėžta failuose extensions/<engine>/test/manifest.smoke.test.mjs, kur tikrinama kiekvieno manifesto forma ir kiekvieno referenced file buvimas.

Internacionalizacija (i18n)

Reveal URLs lokalizuotas į daugelį kalbų be anglų. Vienintelis šio rinkinio šaltinis yra packages/core/src/locales.json, kuriame yra SUPPORTED_LOCALES, vietiniai pavadinimai ir numatytoji kalba. Kiekvienas neangliškas string yra machine-translated ir laukia žmogaus peržiūros. Kilmės žymeklis skiriasi pagal formatą: _locales jį neša lauke description, site catalogues jį įrašo faile site/i18n/README.md, o išversti docs pirmoje eilutėje turi HTML komentarą.

Yra trys nepriklausomi lokalizacijos paviršiai:

„Aktyvių svetainių“ slėpimas pašto kliente (AD-3)

Mail client target, pavyzdžiui, Thunderbird, jau mato visus rendered messages, todėl host pagrindu veikiantis „Active sites“ redaktorius ten yra nereikalingas. Sprendimas perduodamas per mailClient: true lauką faile tooling/build.mjs.

Saugumo ir privatumo laikysena

Testavimas