Reveal URLs — Architektúra
Ez a dokumentum leírja, hogyan épül fel a Reveal URLs bővítmény, és hogyan
működik az alaplogikája, közreműködők és technikai olvasók számára. Végig valós
fájlokra és szimbólumokra hivatkozik (path symbol formában), így bármely
állítás ellenőrizhető a forráskód alapján. A felhasználó felé néző leírásért lásd
a kézikönyvet; a kézi ellenőrzési lépésekért lásd a kézi teszttervet.
Áttekintés
A Reveal URLs egyetlen MV3 WebExtension, amely több motorra épül — Chrome,
Edge, Opera, Firefox és Thunderbird — egyetlen közös, tiszta kódmagból. (A Safari
elhalasztva: van manifesztje, és összeállítható, ha kifejezetten megnevezik, de ki
van zárva az alapértelmezett összeállításból és a CI-ből.) A bővítmény minden link
cél-URL-jét felfedi a link mellett egy megjelenített e-mailben, és megjelöl egy
olyan linket, amelynek látható szövege más regisztrálható domaint nevez meg, mint a
href-je — a klasszikus adathalász árulkodó jel.
A tároló egy pnpm/TypeScript monorepó (pnpm-workspace.yaml,
package.json "private": true értékkel). A csomagolás egyetlen esbuild-vezérelt
szkript, a tooling/build.mjs, amely lefordítja a közös csomagokat és az egyes
motorok vékony belépési pontjait egy betölthető dist/<target>/ könyvtárba.
A webextension-polyfill minden szkriptbe be van csomagolva, ahelyett, hogy
futásidejű globálisként támaszkodnánk rá, így ugyanaz a forrás változatlanul fut a
Chromiumon és a Geckón át.
Két natív e-mail-bővítmény kiterjeszti ugyanazt a felismerési magot olyan
felületekre, amelyeket a WebExtension nem érhet el: egy Outlook-bővítmény
(Office.js munkaablak, elérve az Outlookot weben/Windowson/Macen/iOS-en/Androidon
és az Outlook.com-ot) és egy Gmail-bővítmény (Apps Script CardService, elérve a
Gmail web-/Android-/iOS-alkalmazásait). Mindkettő csak a TISZTA elemzést használja
újra — egy új packages/core/src/findings.ts modul
(analyseAnchors/analyseHtml, egy platformfüggetlen Finding[] modell), amely
újrahasználja a meglévő hostMismatch logikát és a tldts regisztrálhatódomain-származtatást.
Csak a gazdagép-adapter (ahogyan a törzset beolvassa és elemzi) és a megjelenítés
tér el felületenként: az Outlook munkaablaka a törzset a saját DOMParser-ével
elemzi, és kliensoldalon fut; a Gmail-bővítmény node-html-parser-rel elemez,
és szerveroldalon a Google Apps Script V8 infrastruktúráján fut (ingyenes
hosztolás, mivel a Google már birtokolja a leveleket; helyi-interaktívan telepítve
clasp-pal). Mivel egyik keretrendszer sem tudja módosítani a megjelenített,
olvasási módú üzenet DOM-ját (az Office setAsync/prependAsync metódusai csak
szerkesztésre valók; a CardService kártyákat jelenít meg, nem üzenet-HTML-t),
mindkét bővítmény egy megállapítás-panelt/kártyát mutat, nem beágyazott jelölést
— a meglévő linkProcessor.ts DOM-módosító útvonalát és a REVEAL_URLS_CSS-t
egyik bővítmény sem használja újra. Egy relatív href csak egy megbízható,
e-mailen belüli <base href>-hez képest kerül feloldásra, egyébként kihagyásra,
soha nem a postafiók/szolgáltató origójához képest (ami hamis célhelyet gyártana).
A kódbázis élesen elkülöníti a felelősségeket:
- A
packages/coreTISZTA logikát tartalmaz — nincs böngésző-API, nincs tárolás, nincs markup-nyelő. Sima JavaScriptben egységtesztelhető, és a linkjelölés, konfigurációvalidáció, egyeztetésiminta-illesztés, gazdagép-összehasonlítás és kontrasztfüggő színezés egyetlen igazságforrása. - A
packages/webexta WebExtension API-kat burkolja e mag köré: a tartalom életciklusát, a dinamikus tartalomszkript-regisztrációt, a beállítási felületet, a háttér-bekötést, az eszköztár-műveletet és a Thunderbird üzenetmegjelenítési útvonalát. - Az
extensions/<engine>a motoronkénti vékony belépési pontokat tartalmazza az egyes motorokmanifest.json-jával együtt. - A
toolingaz összeállítási, verzióbélyegző és ikongeneráló szkripteket tartalmazza. - A
featuresa Cucumber BDD-forgatókönyveket és azok tesztduplikátumait tartalmazza.
A tároló felépítése
packages/core — a tiszta mag
Minden itteni modul semmilyen bővítmény-API-t (browser.*/chrome.*/messenger.*)
nem importál, és nem használ karakterlánc-markup DOM-nyelőt
(innerHTML/insertAdjacentHTML). Ezt a szerződést mechanikusan érvényesíti a
packages/core/test/purity.test.ts, amely minden src/**/*.ts fájlt begyűjt, a
saját stripComments-ével eltávolítja a megjegyzéstörzseket (hogy a jogszerűen a
tiltott tokeneket MEGNEVEZŐ docblockok ne buktassák meg az őrt), majd állítja, hogy
sem az EXTENSION_API_PATTERN, sem az UNSAFE_DOM_PATTERN nem illeszkedik egyik
modulra sem. A nyilvános felület a packages/core/src/index.ts-ből van
újraexportálva. Az egyetlen futásidejű függőség a tldts (a public-suffix-tudatos
domain-összehasonlításhoz).
A modulok:
packages/core/src/config.ts— aConfig/SiteRuleséma, alapértelmezések és az egyetlen validációs tölcsér, anormaliseConfig.packages/core/src/matchPattern.ts— az egyeztetésiminta-grammatika, illesztő, specifikusságrangsorolás és megadott-origó-lefedettség.packages/core/src/linkProcessor.ts— a dokumentumhatókörű jelölő.packages/core/src/hostMismatch.ts— a regisztrálhatódomain-eltérés ellenőrzése.packages/core/src/styles.tséspackages/core/src/contrast.ts— a beszúrható stíluslap és a kontrasztfüggő színezés.packages/core/src/safeColour.ts— a biztonságos-szín predikátum, azisSafeColour.
packages/webext — a böngésző-API-burkolók
Az itteni modulok HASZNÁLHATJÁK a browser.*-ot, és az egyetlen hely, ahol a
WebExtension API-kat érintik. A nyilvános felület a packages/webext/src/index.ts-ből
van újraexportálva. A modulok: content.ts (tartalom-életciklus),
contentRegistration.ts (dinamikus regisztráció), storage.ts
(konfigurációmegőrzés), background.ts (telepítéskori viselkedés),
toolbarAction.ts (váltás + jelvény), messageDisplay.ts és
messageDisplayBackground.ts (Thunderbird), valamint options/options.ts (a
beállításoldal vezérlője).
extensions/<engine> — vékony belépési pontok és manifesztek
Minden motor a saját manifest.json-ját és icons/-ját hordozza. A Chrome, a
Firefox és a Thunderbird ezenfelül szállít egy src/-t belépési pontokkal; az
Edge, az Opera és a Safari csak manifesztet és ikonokat hordoz, és ÚJRAHASZNÁLJA a
Chrome src/-jét (az összeállítási leíró deklarálja — lásd alább). A belépési
pontok szándékosan aprók: például az extensions/chrome/src/content.ts csupán
void createContentController().bootstrap();, az
extensions/chrome/src/background.ts pedig meghívja a registerBackground,
registerContentReconciliation és registerToolbarToggle metódusokat. A Firefox
háttér-belépési pontja azonos a Chrome-éval; a Thunderbirdé
(extensions/thunderbird/src/background.ts) helyette a registerBackground és
registerMessageDisplay metódusokat hívja (nincsenek tartalomszkriptjei). Az
extensions/_template egy másolásra kész Chromium-váz, nem összeállítási célpont.
tooling, features és a füsttesztek
A tooling tartalmazza a build.mjs, version.mjs és icons.mjs fájlokat. A
features tartalmazza a Gherkin-forgatókönyveket, azok lépésdefinícióit és a hű
tesztduplikátumokat a features/support/webext.ts és features/support/world.ts
fájlokban. Minden motor hordoz egy
extensions/<engine>/test/manifest.smoke.test.mjs fájlt is (a _template-é a
template.smoke.test.mjs) — ezek a mérvadó manifesztszerződés (lásd a
„Motoronkénti összeállítás és a manifesztszerződés” szakaszt alább).
A felfedési folyamat
A tartalomútvonal egy motor-belépési pontnál kezdődik, például az
extensions/chrome/src/content.ts-nél, amely meghívja a
createContentController().bootstrap()-ot a packages/webext/src/content.ts-ből.
Rendszerindítás (Bootstrap)
A createContentController bootstrap keretenként egyszer fut le:
- Szinkronban lefoglalja a keretet, bármely
awaitelőtt, aclaimBootstraprévén, amely beolvassa/beállítja aBOOTSTRAP_MARKERjelzőt a keretwindow-ján. Egy statikus, beépítettcontent_scriptsbejegyzés és egy átfedő dinamikus felhasználói szkript egyaránt beszúrhatja acontent.js-t ugyanabba a keretbe; a közös, elszigetelt-világwindowteszi ezt a helyes, egyszeri őrszemmé, és a bármelyawaitelőtti lefoglalás azt jelenti, hogy két közel egyidejű beszúrás nem juthat át rajta mindkettő. - ELŐSZÖR betölti a megőrzött konfigurációt a
loadConfigWithRetryrévén (BOOTSTRAP_CONFIG_RETRIESextra próbálkozás egy átmeneti elutasításnál), megtartva a jelzőt a próbálkozásokon át, hogy egy átfedő beszúrás, amely már nem-műveletté vált, ne ragadjon be. Csak miután az olvasás sikerül, szúrja be a stíluslapot (injectStyles, amely aREVEAL_URLS_CSS-ttextContentrévén rendeli hozzá), így egy sikertelen olvasás nem fűz hozzá<style>-t, és egy újrapróbálkozás nem duplikálhatja azt. - Ha a konfigurációbetöltés minden próbálkozásnál meghiúsul, a
releaseBootstrapvisszagörgeti a jelzőt (csak itt, a jelölés megkezdése előtt), hogy egy későbbi újrabeszúrás újrapróbálkozhasson; miután astartlefutott, a jelzőnek meg kell maradnia, mert egy friss jelölő nem birtokolná az előző menet csomópontjait. - Ha
config.enabled, meghívja astart-ot, és mindig regisztrál egyonConfigChangedfigyelőt, amely újraalkalmazza a konfigurációt (lefedve a letiltott→engedélyezett átmenetet).
Az aktív webhelyszabály feloldása
A start (és az applyConfig) meghívja a resolveSiteRule-t annak eldöntéséhez,
hogy jelöljön-e — és hol. A resolveSiteRule először a futó dokumentum SAJÁT
helyét próbálja a resolveForHref révén, amely a config.sites-ot az
engedélyezett szabályokra szűri, és a mag selectMostSpecific-jére bízza, hogy a
legspecifikusabb egyezés nyerjen (egy specifikus beépített legyőz egy tág
felhasználói helyettesítő karaktert). Amikor a saját hely semmire nem illeszkedik,
ÉS a dokumentum saját origója átlátszatlan vagy örökölt — a hasOpaqueOrigin
kapuzza, amely csak about:blank/about:srcdoc (öröklött az
INHERITED_ORIGIN_URLS révén) vagy blob:/data: (OPAQUE_ORIGIN_SCHEMES)
esetén igaz —, akkor sorrendben visszaesik a FELSŐ keretre (readTopHref, a
beépített Proton üzenet-iframe eset), a MEGNYITÓ ablakra (readOpenerHref, az
Outlook felugró olvasóablaka, amelyet a window.open("about:blank") nyit meg), és
végül a document.referrer-re (readReferrer). Minden külső olvasás védve van a
kereszt-origós hozzáférés ellen (ami hibát dob), és csak akkor járul hozzá
jelölttel, ha olvasható. Egy KÖZÖNSÉGES, nem illeszkedő dokumentum mérvadó, és soha
nem örököl szabályt a környezeti kontextusból, tétlenül hagyva a vezérlőt.
A tartalomgyökérre korlátozás és a módosítások megfigyelése
A start rögzíti az illesztett szabály contentRoot választóját, létrehozza a
jelölőt a createAnnotator(config) révén, és feldolgozza a
selectRoots(doc, contentRoot) minden illesztett gyökerét (egy try/catchbe
csomagolt querySelectorAll, így egy érvénytelen választó biztonságosan []-re
bukik). Ezután megfigyeli a doc.body-t (egy stabil tároló) az OBSERVER_OPTIONS-szal
({ childList: true, subtree: true }).
A megfigyelő a processMutations-t táplálja, amely minden módosítást a
tartalomgyökérre kapuz: csak akkor dolgoz fel újra egy módosítás-target-et,
amikor az egy Element egy gyökéren belül (isWithinRoot, closest révén), és
minden hozzáadott Elementet a processAddedNode-on át irányít, amely megjelöl egy
olyan csomópontot, amely egy gyökéren belül van, IS egy gyökér, vagy TARTALMAZ
egyet (egy lusta SPA-felcsatolás). Így az egy gyökéren belüli forgás és egy később
felcsatolódó testvér-üzenettörzs egyaránt elkapódik, míg a gyökereken kívüli
alkalmazásfelület érintetlen marad. Egy null jelölő (a bővítmény leállt)
nem-művelet.
Az applyConfig egyeztet egy élő konfigurációváltozást: lebont, amikor semmilyen
szabály nem illeszkedik, vagy az engedélyezett jelző ki van kapcsolva; újraindít
(visszavonás + újramegfigyelés), amikor a feloldott contentRoot megváltozott; és
egyébként helyben átrendez — annotator.setConfig, majd annotator.process minden
illesztett gyökér felett — a megfigyelő újraindítása nélkül.
Egyetlen horgony jelölése
A jelölő a packages/core/src/linkProcessor.ts-ben él. A createAnnotator egy
Annotator-t ad vissza olyan lezárt állapot felett, amelyet az oldal nem tud
olvasni: egy erős Map horgonyidentitás szerint kulcsozva, egy jelölőnkénti
data-ru token a generateToken-ből (crypto.getRandomValues), és az aktuális
konfiguráció. A process(root) metódusa:
- Kimetszi azokat a nyilvántartási bejegyzéseket, amelyek horgonya már nincs csatlakoztatva.
- A
collectAnchors-szal jelölt horgonyokat gyűjt (leszármazotta[href]plusz maga a gyökér, ha az egya[href], deduplikálva). - Minden horgony célhelyét a
resolveAnnotatableUrl-lel oldja fel, amelynull-t (kihagyás) ad vissza egy üres/oldalon belüli href, egy elemezhetetlen href, bármelyhttp:/https:-től eltérő séma, vagy egyignoreHostsbejegyzéssel egyenlő vagy alá tartozó gazdagép esetén; a relatív hrefeket abaseURI-hez képest oldja fel, így az eredmény abszolút. - Az idempotens gyorsútvonalat választja, amikor az
isAnnotationIntactmegerősíti, hogy a megjelenítési ujjlenyomat változatlan, és minden létrehozott csomópont még csatlakoztatva és a helyén van; egyébként visszavonja az elavult jelölést és újramegjelenít. - Az
hostMismatch-csel eltérést számít a tiszta horgonyszövegen, tiszteletben tartja ashowOnlyOnMismatch-et (érintetlenül hagyva a tisztességes linkeket), és meghívja azannotateAnchor-t.
Az annotateAnchor csak BIZTONSÁGOS-DOM-ot jelenít meg. "inline" módban egy
<span class="reveal-urls-url">-t fűz elé, amelynek URL-jét textContent révén
állítja be (egy nyíllal és egy U+00A0 nem-törő szóközzel előtaggal, a
truncateUrl-lel csonkolva), amelyet egy <br> követ, a link saját gyermekeit
érintetlenül hagyva; a csatlakoztatott span ezután kontraszthátteret kap az
applyContrastBackdrop révén. "title" módban elmenti az eredeti title-t
(TitleSave), felülírja az URL-lel — az egyetlen írás a horgonyra —, és egy
kiemelt eltérésnél egy testvér-<span class="reveal-urls-warn"> jelvényt fűz hozzá,
amely a REVEAL_URLS_WARN_LABEL-t viseli. Minden létrehozott csomópont
data-ru="<token>"-nel van címkézve (soha nem a horgony), és minden bejegyzés
rögzíti a configFingerprint-jét, így egy megjelenítéskonfiguráció-változás
átrendezésre kényszerít. A színek és a betűtípus-felülbírálások tipizált
element.style.* tulajdonságokon keresztül kerülnek alkalmazásra
(applyColour/applyFontOverrides), soha nem CSS-karakterláncba interpolálva. A
revertOwned referencia szerint távolítja el a létrehozott csomópontokat, és
visszaállítja az elmentett title-t; a revertAll ezt minden birtokolt horgonyra
elvégzi, és kiüríti a nyilvántartást.
A truncateUrl Unicode KÓDPONTOK felett működik ([...displayHref]), így egy vágás
soha nem hasít ketté egy helyettesítő párt; megőrzi a séma-és-gazdagép origót, és a
többit egy záró URL_ELLIPSIS-szel rövidíti.
Gazdagép-eltérés
A packages/core/src/hostMismatch.ts hostMismatch a link látható szövegét
hasonlítja össze a href-jével regisztrálható domain, nem nyers gazdagépnév
szerint, így egy tisztességes aldomain nincs megjelölve, míg egy megtévesztő igen.
Az extractHostCandidates szétbontja a szöveget, és minden tokent a
hostCandidate révén redukál (amely megkövetel egy pontot és egy tldts-felismert
ICANN public suffixet, elutasítva a közönséges pontozott szöveget, például az
e.g-t). Mind a href gazdagépe, mind minden jelölt a regisztrálható domainjére
redukálódik a tldts.getDomain-nel, és BÁRMELY jelölt, amelynek domainje eltér a
hrefétől, eltérést eredményez — így a valós rosszindulatú gazdagép csali gazdagép
melletti megnevezése nem tudja elnyomni a figyelmeztetést. Soha nem dob hibát.
Stílusok és kontraszt
A packages/core/src/styles.ts tartalmazza a beszúrható REVEAL_URLS_CSS-t (rögzített
elrendezés, vastagság, alak és egy rögzített fehér, teljesen átlátszatlan
chip-háttér — background: #ffffff, opacity: 1; a színek SOHA nem interpolálódnak
bele) és a futásidejű applyColour/applyFontOverrides/applyContrastBackdrop
segédfüggvényeket. Az applyContrastBackdrop beolvassa az elem feloldott színét és
az elemTŐL magától felfelé haladva talált első átlátszatlan hátteret (a saját
háttere — a chip rögzített fehér alapértelmezése vagy egy hosztoldal
specifikusabb felülbírálása — bármely őse előtt) a szabványos getComputedStyle-on
keresztül (az element.ownerDocument?.defaultView-ból származtatva, így
keret-biztonságos és csonkolható), és amikor a
packages/core/src/contrast.ts needsWhiteBackdrop jelenti, hogy a szöveg
megbukik a WCAG AA-n (CONTRAST_THRESHOLD) az adott háttérrel szemben, ÉS a fehér
valóban segít, beállítja a style.backgroundColor = "white"-t. A contrast.ts
biztosítja a parseColour, relativeLuminance és contrastRatio függvényeket;
elemzi a getComputedStyle által visszaadott rgb()/rgba()/hex formákat, és egy
teljesen átlátszó színt „nem találtként” kezel.
Konfigurálható gazdagépek és tartalomkorlátozás
Egy SiteRule (packages/core/src/config.ts) megmondja, HOL fut a jelölés
(match, allFrames), és MELYIK tároló korlátozza (contentRoot), plusz egy
enabled és egy builtin jelző. A szállított DEFAULT_SITES lefedi a Gmailt, a
Protont (allFrames-szel) és mindkét Outlook-gazdagépet. A Config összesíti a
globális kapcsolókat, színeket, betűtípus-felülbírálásokat és a sites tömböt.
Az egyeztetésiminta-motor
A packages/core/src/matchPattern.ts egy szándékosan MEGSZORÍTÓ, hozzáadáskori
grammatikát tartalmaz, a MATCH_PATTERN-t (csak http/https, opcionális vezető
*. gazdagép-helyettesítő, glob-útvonal; nincs port, nincs * séma, nincs
<all_urls>). A matchesPattern egy URL-t illeszt egy validált mintához, az
útvonalat a pathGlobMatches-re bízva (minden * bármely karaktersorozatra
illeszkedik); biztonságosan false-ra bukik. A parsePatternParts egy validált
mintát host/path/scheme/wildcardHost részekre bont.
Az átfedő szabályok a „legspecifikusabb nyer” elv szerint oldódnak fel: a
compareSiteSpecificity így rangsorol: (1) pontos gazdagép a helyettesítő gazdagép
felett, (2) hosszabb literál gazdagép, (3) literálisabb útvonal
(literalPathLength), majd (4) egy determinisztikus ASCII holtverseny-döntő; a
selectMostSpecific egy URL-re illeszkedő legspecifikusabb engedélyezett szabályt
ad vissza.
A megadott-origó-lefedettség egy KÜLÖNÁLLÓ, szándékosan TÁGABB illesztő. A
permissions.getAll() a teljes WebExtension-grammatikában
(<all_urls>, *://*/*, https://*/*, *://*.host/*, https://host/*)
jelenthet megadásokat, amelyeket a megszorító MATCH_PATTERN elutasítana. A
parseGrantedOrigin ezeket GrantedOriginParts-ra elemzi, és az originCovers
jelenti, hogy egy megadott origó lefed-e egy szabály match-ét (pl. egy tág
https://*/* megadás valóban lefed minden https-szabályt). A
matchAllowsOriginFallback jelenti, hogy egy szabály útvonala pontosan
ORIGIN_FALLBACK_PATH (/*)-e — a Chromium és a Gecko/Firefox 128 megszorításainak
biztonságos metszete a dinamikus átlátszatlan-origó visszaesésen.
Dinamikus regisztráció
A packages/webext/src/contentRegistration.ts felhasználó által hozzáadott
szabályonként egy dinamikus tartalomszkriptet regisztrál. A beépítetteket a
statikus content_scripts bejegyzések szolgálják ki, és SOHA nem regisztrálódnak
dinamikusan. A desiredContentScripts a config.sites-ot nem-beépített,
engedélyezett szabályokra szűri, amelyek origója isOriginGranted (az originCovers-re
bízva), mindegyiket egy stabil azonosítójú RegisteredContentScript-re képezi le
(contentScriptId, a match egy FNV-1a hasítása, így az azonosító csak az
API-biztos karakterkészletet használja), és a matchOriginAsFallback: true-t CSAK
akkor állítja be, amikor a matchAllowsOriginFallback(rule.match) fennáll (egy /*
útvonal). A jelző ilyen kapuzása megakadályozza, hogy egy nem konform útvonalú
szabály miatt az egész kötegregisztrációt elutasítsák; egy ilyen szabály felugró
lefedettség nélkül regisztrálódik, terv szerint.
A reconcileContentScriptsOnce beolvassa a megadott origókat, a tárolt
konfigurációt és az élő regisztrációkat, majd egy TULAJDONSÁG-TUDATOS diff révén
konvergál: a sameRegistration a kívánt leírót és az élő visszaolvasást is egy
normalizált formára vetíti (minden mező valós WebExtension-alapértelmezését
alkalmazva — az allFrames/matchOriginAsFallback alapból false, a
persistAcrossSessions alapból TRUE, a runAt alapból document_idle), így egy
frissen regisztrált szkript EGYENLŐNEK bizonyul azzal a leíróval, amely létrehozta,
és nem alakulhat ki újraregisztrációs hurok. Mivel nincs updateContentScripts, egy
megváltozott szkript deregisztrálódik, majd újraregisztrálódik. A
reconcileContentScripts ezt egy folyamatban lévő ígérettel (inFlight) burkolja,
így két közel egyidejű kiváltó (egy konfigurációváltozás ÉS a
permissions.onAdded, amely egy webhely-hozzáadáskor egyaránt kioldódik)
sorosítódik, nem versenyeznek. A registerContentReconciliation beköti a
kiváltókat (permissions.onAdded/onRemoved, onConfigChanged), és indításkor
egyszer konvergál; csak a Chrome és a Firefox háttér-belépési pontjai importálják.
A választható engedélyfolyamat
A statikus manifesztek deklarálják az optional_host_permissions-t (http://*/*,
https://*/*), így a felhasználó futásidőben megadhat egy tetszőleges további
webhosztot. A packages/webext/src/options/options.ts addSite metódusa validálja
a TELJES jelölt szabályt (match ÉS content-root) a normaliseSiteRule révén,
MIELŐTT bármely engedélyt kérne, a gazdagép-origót a matchOrigin-nal
származtatja, és a browser.permissions.request-et a Hozzáadás gomb kattintási
gesztusán belülről hívja; csak megadás esetén fűz hozzá egy builtin:false sort,
és ír a kanonikus íróútvonalon keresztül.
Konfiguráció, tárolás és a kanonikus minta
Az egyetlen validációs tölcsér
A packages/core/src/config.ts-ben lévő normaliseConfig az egyetlen kanonikus
olvasóútvonal: minden mezőt kényszerít és korlátoz, bármely érvénytelenre az
alapértelmezésre visszaesve, és soha nem dob hibát. Mezőnkénti validátorokból épül
fel — normaliseBoolean, normaliseRenderMode, normaliseMaxLength,
normaliseIgnoreHosts (minden bejegyzést egy csupasz punycode gazdagépnévre
redukálva a bareHostname révén),
normaliseMatchColour/normaliseMismatchColour (a safeColour.ts isSafeColour
által kapuzva), normaliseCssSize, normaliseFontWeight, normaliseContentRoot
(a CONTENT_ROOT_PATTERN/CONTENT_ROOT_MAX_LENGTH által korlátozva),
normaliseMatchPattern (a MATCH_PATTERN által kapuzva) és normaliseSites. A
normaliseSites eldobja az elutasítottakat, match szerint deduplikál, TRUE-ra
KÉNYSZERÍTI a builtin-t minden olyan szabályon, amelynek match-e egyenlő egy
beépítettével (bezárva a pontos-egyezés-árnyékolja-a-beépítettet / kettős-beszúrás
vektort), újravet bármely hiányzó beépítettet (így egy manipulált tároló nem tud
elejteni egy mag-szolgáltatót), és match szerint alfanumerikusan rendez egy
kanonikus sorrendhez. A configFingerprint csak a megjelenítést befolyásoló
mezőket hasítja (kizárva az enabled, ignoreHosts és sites mezőket, amelyek
inditást/leállítást és kihagyást vezérelnek, nem átrendezést) a fnv1a révén.
Tárolás
A packages/webext/src/storage.ts a WebExtension tároló API-t burkolja. A
configArea a browser.storage.sync-et használja, ha elérhető (így a beállítások
vándorolnak), és a browser.storage.local-ra esik vissza (pl. Thunderbirdön); a
configAreaName jelenti, melyik aktív. A getConfig, setConfig és
onConfigChanged mind a normaliseConfig-on tölcsérezik át a nyers értéküket, így
egy hibás vagy manipulált tároló soha nem adhat át érvénytelen Config-ot egy
hívónak. Az onConfigChanged ezenfelül figyelmen kívül hagyja az INAKTÍV
tárolóterület eseményeit és a nem kapcsolódó kulcsok változásait.
A két bővítmény ugyanazt az olvasás -> normaliseConfig -> visszatérés tölcsért
használja újra a saját gazdagép-tárolói felett. Az Outlook munkaablakának
extensions/outlook/src/roamingStorage.ts-e a Office.context.roamingSettings-t
burkolja (szinkron get/set, saveAsync-cal véglegesítve); a Gmail-bővítmény
packages/gmail/src/propertiesStorage.ts-e a PropertiesService.getUserProperties()-t
burkolja (felhasználónkénti, az adott felhasználó eszközei között vándorló,
JSON-kódolt egy kulcs alatt, és NEM igényel extra OAuth-hatókört — soha nem a
megosztott ScriptProperties). Mindkettő elérhetővé teszi a getConfig/setConfig
metódusokat plusz a kártya-releváns getIgnoreHosts/getHighlightMismatch
hozzáférőket, és egy elérhetetlen gazdagép-felületet egy dedikált hibával véd. A
Gmail kezdőlap-kiváltója egy CardService beállítási kártyát jelenít meg a
getConfig-ból, és az onSaveSettings űrlapküldő kezelője az elemzett
ignoreHosts/highlightMismatch-et az aktuális konfigurációra egyesíti, és a
setConfig-on keresztül megőrzi; a kontextuális kiváltó az ignoreHosts-ot az
adapterbe, a highlightMismatch-et pedig a kártyaépítőbe fűzi, védekezően olvasva,
így egy tárolási hiba az alapértelmezésekre esik vissza, ahelyett, hogy megtörné az
üzenetelemzést.
A kanonikus konfiguráció + célzott frissítés minta
Három íróútvonal beolvassa a kanonikus konfigurációt, pontosan egy dolgot megváltoztat, és visszaírja — soha nem véglegesítve el nem mentett űrlap-szerkesztéseket, és soha nem jelenítve meg újra az űrlapot:
removeSite(options.ts): beolvassa agetConfig-ot,setConfig-gal megőrzi egy olyansitestömb felett, amelyből csak az eltávolított szabály van kiszűrve (ELŐSZÖR megőriz, majdrow.remove(), majd egy feltételespermissions.remove, amelyet a következő konfigurációból dönt el). Egy megőrzési hibaSiteRemovalSaveError-t dob, és a DOM-ot érintetlenül hagyja; egy sikertelen visszavonásSitePermissionRevokeError-t dob, miután a sor már eltűnt.addSite(options.ts): fent leírva — validál, engedélyt kér, hozzáfűzi a sort, majdsaveOptions.toggleEnabled(options.ts): az azonnali fő Engedélyezés kapcsoló CSAK azenabled-et billenti a tárolt konfiguráción, és visszaírja, újramegjelenítés nélkül (így egy folyamatban lévő szerkesztés soha nem törlődik); egy sikertelen írásnál visszaállítja a jelölőnégyzetet a tárolt értékre.
Az eszköztár-művelet saját toggleEnabled-je
(packages/webext/src/toolbarAction.ts) ugyanezt a mintát követi a háttér
oldaláról.
Motoronkénti összeállítás és a manifesztszerződés
A tooling/build.mjs egyetlen esbuild-összeállítást vezényel minden WebExtension-célponthoz.
A TARGETS leíró megnevezi az egyes célpontok FORRÁSCÉLPONTJÁT: a Chrome, a
Firefox és a Thunderbird a saját src/-jét szállítja; az Edge, az Opera és a Safari
a chrome-ot deklarálja forrásaként; az Outlook (Office.js) bővítmény a saját
forrása. Az ACTIVE_TARGETS (Chrome, Edge, Firefox, Opera és Thunderbird — a
Safari és az Outlook kizárva) az, amit az --all/--package összeállít; a Safari
és az Outlook csak akkor épül, ha kifejezetten megnevezik, a Gmail (Apps Script)
bővítmény pedig egy különálló esbuild-köteg (make build-gmail) az --all hurkon
kívül. Az a Gmail-köteg az Apps Script V8 futtatókörnyezetét célozza, amelynek se
ES-moduljai, se natív URL-je nincs: ESM-ként épül, majd a záró export {…}
utasítását eltávolítják (így a kiváltófüggvények felső szintű globálisok maradnak,
amelyeket az Apps Script meghívhat), és becsomagol egy kis URL polyfillt (csak
akkor telepítve, ha az URL hiányzik), így a közös mag new URL(...)-je
szerveroldalon feloldja a linkeket. A buildTarget kitisztítja a dist/<target>/-t,
szó szerint másolja a célpontonkénti manifest.json-t és icons/-t, minden
forrásszkriptet IIFE-ként csomagol (a beszúrt INJECTED_SCRIPTS
content.ts/messageDisplay.ts nem lehet ES-modul, a hátterek is klasszikus
workerek/eseményoldalak), a közös beállításoldalt pedig ESM-ként, és átmásolja az
options.html/options.css-t a packages/webext/src/options/-ból. A
webextension-polyfill minden szkriptbe be van csomagolva.
A tooling/version.mjs bélyegzi a verziókat: a MAJOR.MINOR-t a gyökér
package.json-ból veszi, és egy automatikusan növekvő BUILD-számot (a
célpont-manifesztek legmagasabb létező harmadik komponenséből olvasva) fűz minden
célpont-manifesztbe, azokat lépésben tartva. A legtöbb célpont egyetlen
manifest.json-t hordoz; az Outlook-bővítmény két verziózott manifesztet hordoz
(manifest.json és manifest.xml), és mindkettő bélyegzésre kerül, és a
_template váz is lépésben van bélyegezve. A gyökér package.json ugyanazon a
verzión marad, de érvényes semver marad (egy négyrészes verzió megengedett egy
manifesztben, soha nem a package.json-ban). A tooling/icons.mjs (a make icons
révén futtatva) az egyetlen vektorforrást, az assets/icon.svg-t rasztereli
minden célpont icons/icon48.png és icons/icon128.png fájljává, előbb a
cairosvg-t, majd az inkscape-et, majd az ImageMagicket próbálva.
A manifesztszerződés — a motoronkénti pontos kötelező mezők — az egyetlen
igazságforrás minden extensions/<engine>/test/manifest.smoke.test.mjs-ben (pl. az
extensions/chrome/test/manifest.smoke.test.mjs assertManifestShape-je, amely az
összeállítást is elvégzi, és megerősíti, hogy minden hivatkozott fájl feloldódik a
distben). A projekt deduplikációs konvenciója szerint a mezőnkénti alak itt NINCS
újrafelsorolva; hivatkozzon azokra a füsttesztekre és a kézi tesztterv
„Manifest contract” megjegyzésére.
Nemzetköziesítés (i18n)
A Reveal URLs az angolon kívül sok nyelvre lokalizált. A készlet egyetlen forrásból
származik, a packages/core/src/locales.json-ból (SUPPORTED_LOCALES, natív nevek
és az alapértelmezés), amelyet mind a bővítménycsomagok (a feloldott JSON-import
révén), mind a sima Node-alapú webhely-összeállítás olvas. Minden nem angol
karakterlánc gépi fordítású, és emberi ellenőrzésre vár; az eredetjelölő
formátumonkénti (a _locales az egyes üzenetek description-jét használja, a
webhely-felület szótárai a site/i18n/README.md-ben rögzítik, a lefordított
dokumentumforrások pedig egy első soros HTML-megjegyzést hordoznak).
Három független lokalizációs felület van, szándékosan nem összekötve, és mindegyik egy összeállításidő / futásidő vonal mentén oszlik meg.
- A beállításoldal (AD-1). STATIKUS angol markupot szállít; minden fordítható
csomópont egy
data-i18n="<key>"(szöveg) vagydata-i18n-<attr>="<key>"(attribútum) annotációt hordoz. Futásidőben apackages/webext/src/options/locale.tsaz alkalmazó réteg:fetcheli az oldal saját csomagolt_locales/<code>/messages.json-ját (aruntime.getURLrévén,web_accessible_resourcesnélkül), minden annotált csomópontot CSAKtextContentrévén ír át (így az űrlap<code>példa-tippjei érintetlenek maradnak), és beállítja a<html lang>-et. Az oldal TELJESEN lokalizált: a statikus próza mellett a JS-építette webhelysor-címkék és Eltávolítás gomb egydata-i18nkulcsot hordoznak (egy kezdetitranslate-elt szöveggel), így ugyanaz az alkalmazó réteg újralokalizálja a megjelenített sorokat újramegjelenítés nélkül, és a VALÓBAN dinamikus karakterláncok — az állapotsor, a webhely-hozzáadási visszajelzés és a verziósor — alocale.tstranslate(<key>)-én keresztül jelennek meg (currentMessages → angol visszaesés → kulcs) hívási időben. Az angol visszaesés a_locales/en/messages.jsonkatalógus, amelyoptions.js-be IMPORTÁLVA (becsomagolva) van összeállítási időben, így atranslatemodultöltéstől SZINKRONBAN olvasható angolra oldódik fel — egy dinamikus karakterlánc, amely a kapcsoló letelepedése előtt jelenik meg, vagy miután egy futásidejű katalóguslehívás meghiúsult, soha nem degradálódik nyers üzenetkulcsra. Alocale.tsközzéteszi az alkalmazott üzenettérképet, és minden alkalmazás után elsüti azonLocaleChangefigyelőit, így azoptions.tsújramegjeleníti az éppen mutatott állapotot/visszajelzést/verziót, és minden váltáskor újramutatja az Online kézikönyv linket. A verziósor egyoptionsVersionüzenetet használ, amelynek{version}helyőrzője a build-számmal van behelyettesítve (a token fordítva, a szám szó szerint). A kézikönyv-link követi az aktív nyelvi beállítást a dokumentum-változatához (AD-9): az angol megtartja a felső szintűmanual.html-t, minden más nyelvi beállítás a<code>/alatti lokalizált fájlnevét nyitja meg (pl.nl/handleiding.html, a maglocalisedSitePage-ével fordítva — ugyanaz a megosztott térkép, amely alá a webhely-összeállítás kiadja a fájlt), megőrizve a?v=verzióbélyeget. Az aktív nyelvi beállítás a felhasználó mentett választása, vagy annak híján a böngészőnyelv, amelyet a magresolveLocale-je egy támogatott alapnyelvre old fel (AD-5); egy nem támogatott vagy meghiúsult nyelvi beállítás az előre megjelenített angolt hagyja (és atranslateekkor az angol visszaesésre degradál). A kézi Megjelenítési nyelv kapcsoló a választását egy dedikáltuiLocalekulcs alatt őrzi astorage.local-ban — soha nem a szinkronizáltConfig-ban, így eszközönkénti, és soha nem vándorol. - A webhely-felület (AD-2). A statikus-webhely-generátor
tooling/site.mjsminden felületcsomópontot angolul renderel szerveroldalon, miközbendata-i18n-nel annotálja, és megtartja a<html lang="en-GB">-t (AD-4). A böngészőoldali betöltő, asite/scripts/i18n.mjs(egy függőségmentes ES-modul) ezután a látogató nyelvére cseréli a felületet precedencia szerint — egy megőrzöttlocalStorageválasztás (revealUrlsSiteLocale) → az első támogatottnavigator.languagesalapegyezés (AD-5) → angol —, lehívva asite/i18n/<code>.json-t. A beállítások alkalmazó rétegétől eltérőeninnerHTMLrévén cserél, mert a webhely-felület értékei megbízható beágyazott markupot hordoznak (<code>,<a>); egy paritásteszt minden nem angol érték tag/attribútum/URL-sorozatát bájtazonosan rögzíti az angolhoz, így egy fordítás soha nem ejthet el egy linket vagy nem törhet meg egy taget. Egy hiányzó kulcs megtartja az előre megjelenített angol pillanatképet; egy meghiúsult lehívás érintetlenül hagyja az angol oldalt (kecses degradáció, soha nem üres). - A dokumentációoldalak (AD-8/AD-9). A dokumentumoldalak NYELVENKÉNT
renderelődnek összeállítási időben, nem futásidőben cserélődnek. Az angol
dokumentumok a felső szintű útvonalakon maradnak; minden más nyelv kap egy másolatot
egy
<code>/mappa alatt, egy a nyelvre LEFORDÍTOTT fájlnév alatt (nl/handleiding.html,de/datenschutz.html) a megosztottpackages/core/src/site-page-names.jsontérképből (a kezdőlapindex.htmlmarad), egy lefordítottdocs/<code>/<DOC>.md-ből, ha van, egyébként az angol forrásból — egy oldalankénti visszaesés, amelyet abuildSiteNAPLÓZ, ahelyett, hogy csendben elhagyná. Mivel a korábbi modell ezeket az angol nevük alatt tette közzé, az összeállítás minden átnevezett régi útvonalra egy vékonynoindexátirányítást is kiad (nl/manual.html→nl/handleiding.html), így egy régi könyvjelző, egy keresési találat vagy egy már telepített régebbi bővítmény kézikönyv-linkje továbbra is feloldódik. A dokumentum TÖRZSE nem hordozdata-i18n-t (nyers megjelenített markdown), így a futásidejű felületbetöltő soha nem érinti; csak a megosztott felületcsomópontok cserélődnek. A dokumentumoldal nyelvkapcsolója a testvér nyelvi beállítás oldalára NAVIGÁL, ahelyett, hogy helyben cserélne. - Belső-link-lokalizáció (Finding 2). A kiválasztott nyelv átvivődik a
dokumentumnavigáción. Minden BELSŐ dokumentumhorgony (az öt oldal
architecture.html,index.html,licence.html,manual.html,privacy.html)data-doc-link="<page>"-nel van címkézve a generátor által, és a JavaScript nélküli esetre a href-je már az AKTUÁLIS oldal saját nyelvi változatára oldódik fel, a lefordított fájlneve alatt (navDocHref— minden belső oldalnak van már nyelvenkénti változata; csak a kezdőlap tartja meg azindex.html-t minden nyelvben). A futásidejű betöltőrewriteDocLinks-e ezután minden belső dokumentumhorgonyt — a címkézetteket ÉS bármely katalógus-beszúrt sima horgonyt, például amanual.html#installing-et — az AKTÍV nyelvi beállítás változatára céloz át, megőrizve bármely#hash/?query-t, és soha nem érintve a külső linkeket. Adata-doc-linkcímke mindig a KANONIKUS angol oldalnevet hordozza; a feloldott href a nyelvi beállítás LEFORDÍTOTT fájlnevét használja (AD-9,nl/handleiding.html), így a betöltő egy lokalizált fájlnevet visszaképez a kanonikus oldalára, hogy idempotens maradjon. Egy dokumentumoldalon egy TÁROLT választás, amely eltér az oldal saját nyelvi beállításától, az egyező változatra NAVIGÁL; a hurokőr csak egy explicit tárolt választásnál sül el (soha nem pusztán egy navigátornyelv-preferenciánál), és csak akkor, amikor a célpont eltér a már megjelenített oldaltól. Maguk a katalógusok megtartják az angol belső URL-jeiket, így a paritásteszt zöld marad — a lokalizáció egy tiszta futásidejű menet a DOM felett.
Levelezőprogram – „Aktív webhelyek” elrejtése (AD-3)
Egy levelezőprogram-célpont (ma a Thunderbird) már minden megjelenített üzenetet
lát, így a gazdagépenkénti „Aktív webhelyek” szerkesztő ott felesleges, és
eltávolításra kerül. A döntést egy dokumentált célpontonkénti leíró-jelző hordozza,
a mailClient: true, a tooling/build.mjs TARGETS.thunderbird-jén — soha nem egy
bekódolt célpontnév az összeállítási vagy UI-logikában —, és két félben valósul
meg:
- Összeállítási idő. A
copyAssetsadescriptorFor(target).mailClient-re kulcsol: egy levelezőprogram-célpontnál lefuttatja a tisztaremoveSitesSection(html)transzformációt (amely leszedi az egész<section class="sites">gazdagép-szerkesztőt, minden webhely-hozzáadási vezérlővel együtt, ésMissingSourceError-t dob, ha a szakasz hiányzik, így egy markupváltozás soha nem szállíthatja csendben a szerkesztőt), és kiírja az eredményt; a böngészőcélpontok azoptions.html-t bájtról bájtra kapják acpSyncrévén. - Futásidő. A becsomagolt vezérlő
packages/webext/src/options/options.tstolerálja a hiányzó szakaszt: arenderSites/readSites/applyConfig/readFormmegvizsgálja a#sites-list-et, és nem-műveletként viselkedik, ahelyett, hogyOptionsFieldMissingError-t dobna, areadFormpedig KIHAGYJA asiteskulcsot, amikor a szakasz eltűnt. Mivel asiteskihagyása hagyná, hogy anormaliseConfigújravesse a beépített alapértelmezéseket, és eldobja a felhasználó webhelyeit, asaveOptionsvisszahelyezi a tároltsites-ot a normalizálás előtt, valahányszor a szakasz hiányzik — ez teszi, hogy egy levelezőprogram-mentés megőrzi a konfigurált gazdagépeket. Egy futásidejű őr, aguardMailClientSites, eltávolítja a szakaszt, ha az még jelen van, egy levelezőprogramot API-/funkciójelenlét szerint észlelve (messages/messageDisplay/scripting.messageDisplaya beszúrtbrowser-en), soha nem célpontnév szerint; elsőként fut azinitOptions-ban, így a későbbi hozzáadási/listázási bekötés természetesen kihagyja a hiányzó elemeket. Egy böngésző-futásidőben az őr egy nem-művelet.
Biztonsági és adatvédelmi helyzet
- Nincs hálózat vagy kiszivárogtatás. Egyetlen modul sem hajt végre
fetch/XHR-t vagy bármely más hálózati hívást; a bővítmény csak a tárolt konfigurációt és az oldal DOM-ját olvassa. - Csak biztonságos DOM. A jelölés és a beszúrt stíluslap kizárólag
createElement/textContent/tipizáltstyle.*tulajdonságokat használ, soha neminnerHTML/insertAdjacentHTML/eval-t. A mag esetében ezt apackages/core/test/purity.test.tsérvényesíti. - Minden külső/konfigurációs bemenet normalizálva. Minden olvasás és írás a
normaliseConfig-on tölcsérezik át; a színek átmennek azisSafeColour-on; az egyeztetési minták átmennek aMATCH_PATTERN-en; a tartalomgyökér-választókat aCONTENT_ROOT_PATTERNkorlátozza, és csak aquerySelectorAll/closest-nek adják át, soha nem interpolálják markupba vagy CSS-be. - Legkisebb jogosultság. A beépített webmail-origók rögzített
host_permissions; a további gazdagépek választhatóak azoptional_host_permissionsés egy gesztushoz kötöttpermissions.requestrévén. Az eszköztár-művelet nem deklarál felugrót. - Gecko-adatgyűjtés. A Firefox és a Thunderbird manifesztjei deklarálják a
browser_specific_settings.gecko.data_collection_permissions.required: ["none"]-t. - Licenc. A projekt AGPL-3.0-only (
LICENSE,package.json).
Tesztelés
- A Vitest egységtesztek mindkét csomagot lefedik:
packages/core/test/*(konfiguráció, kontraszt, gazdagép-eltérés, linkfeldolgozó, stílusok és a tisztaság-őr) éspackages/webext/test/*(tartalom, tárolás, beállítások, regisztráció, eszköztár, üzenetmegjelenítés és egy egyéni-webhely végponttól végpontig teszt). Minden csomagvitest run-t futtat. - A Cucumber BDD forgatókönyvek a
features/-ben élnek (acucumber.jsonköti be), és a valós@reveal-urls/webextmodulokat hajtják hű tesztduplikátumokon keresztül afeatures/support/webext.ts-ben — nevezetesen anormaliseReadBack, amely tükrözi, ahogyan a motor KIHAGYJA a false értékű jelzőket a visszaolvasáskor, így egy engedékeny szerelvény nem rejthet el egy újraregisztrációs hurkot — egy jsdom-dokumentum felett, amelyet afeatures/support/world.tsállít fel. - A célpontonkénti manifeszt-füsttesztek
(
extensions/<engine>/test/manifest.smoke.test.mjs, a Node beépítettnode:test-je) állítják minden manifeszt pontos alakját, és hogy a felépített dist minden hivatkozott fájlt feloldjon. Atooling/test/összeállítási, verzió- és definition-of-done teszteket ad hozzá. - A CI (
.github/workflows/ci.yml) minden push-t és pull requestet kapuz amake test,make bddésmake lint-en (közvetlenül a futtatón futtatva, felülbírálva a Makefile DockerRUN/IMAGE_DEP-jét). A verzióbélyegzés és a csomagolás szándékosan kizárt; a különálló, címkével kiváltott.github/workflows/release.yml-ben élnek.