Reveal URLs — Arhitectură

Acest document descrie cum este structurată extensia Reveal URLs și cum funcționează logica sa de bază, pentru contribuitori și cititori tehnici. Citează fișiere și simboluri reale peste tot (în forma path symbol), astfel încât orice afirmație să poată fi verificată față de sursă. Pentru descrierea orientată către utilizator, consultați manualul; pentru pașii de verificare manuală, consultați planul de testare manuală.

Prezentare generală

Reveal URLs este o singură extensie WebExtension MV3 construită pentru mai multe motoare — Chrome, Edge, Opera, Firefox și Thunderbird — dintr-un singur nucleu de cod comun, pur. (Safari este amânat: are un manifest și este compilabil când este numit explicit, dar este exclus din build-ul implicit și din CI.) Extensia afișează URL-ul de destinație al fiecărui link lângă link într-un e-mail redat și semnalează un link al cărui text vizibil indică un domeniu înregistrabil diferit de href-ul său — indiciul clasic de phishing.

Depozitul este un monorepo pnpm/TypeScript (pnpm-workspace.yaml, package.json cu "private": true). Împachetarea este un singur script bazat pe esbuild, tooling/build.mjs, care compilează pachetele comune și punctele de intrare subțiri ale fiecărui motor într-un director încărcabil dist/<target>/. webextension-polyfill este împachetat în fiecare script, în loc să fie folosit ca global de runtime, astfel încât aceeași sursă rulează neschimbată în Chromium și Gecko.

Două suplimente native de e-mail extind același nucleu de detecție la suprafețe pe care WebExtension nu le poate atinge: un supliment Outlook (panou de activități Office.js, care ajunge la Outlook pe web/Windows/Mac/iOS/Android și Outlook.com) și un supliment Gmail (CardService Apps Script, care ajunge la aplicațiile Gmail web/Android/iOS). Amândouă reutilizează doar analiza PURĂ — un nou modul packages/core/src/findings.ts (analyseAnchors/analyseHtml, un model Finding[] neutru față de platformă) care reutilizează logica existentă hostMismatch și derivarea domeniului înregistrabil tldts. Doar adaptorul de gazdă (cum este citit și analizat corpul) și prezentarea diferă per suprafață: panoul de activități Outlook analizează corpul cu propriul DOMParser și rulează pe partea de client; suplimentul Gmail analizează cu node-html-parser și rulează pe partea de server, pe infrastructura Apps Script V8 a Google (găzduire gratuită, deoarece Google deține deja e-mailul; implementat local-interactiv prin clasp). Deoarece niciunul dintre cadre nu poate muta DOM-ul mesajului redat, în mod-citire (setAsync/prependAsync ale Office sunt doar pentru compunere; CardService redă carduri, nu HTML de mesaj), ambele suplimente prezintă un panou/card de constatări, nu o adnotare în linie — calea existentă de mutare a DOM din linkProcessor.ts și REVEAL_URLS_CSS NU sunt reutilizate de niciunul dintre suplimente. Un href relativ este rezolvat doar față de un <base href> de încredere din e-mail și altfel omis, niciodată față de originea căsuței/furnizorului (care ar fabrica o destinație).

Baza de cod separă preocupările clar:

Structura depozitului

packages/core — nucleul pur

Fiecare modul de aici nu importă niciun API de extensie (browser.*/chrome.*/messenger.*) și nu folosește niciun sink DOM de la șir la marcaj (innerHTML/insertAdjacentHTML). Acel contract este impus mecanic de packages/core/test/purity.test.ts, care parcurge fiecare src/**/*.ts, elimină corpurile de comentarii prin propriul stripComments (astfel încât docblock-urile care NUMESC legitim token-urile interzise să nu declanșeze garda) și apoi afirmă că nici EXTENSION_API_PATTERN, nici UNSAFE_DOM_PATTERN nu se potrivesc vreunui modul. Suprafața publică este reexportată din packages/core/src/index.ts. Singura dependență de runtime este tldts (pentru comparația domeniilor conștientă de sufixul public).

Modulele sunt:

packages/webext — înfășurările de API-uri de browser

Modulele de aici POT folosi browser.* și sunt singurul loc unde sunt atinse API-urile WebExtension. Suprafața publică este reexportată din packages/webext/src/index.ts. Modulele sunt: content.ts (ciclul de viață al conținutului), contentRegistration.ts (înregistrare dinamică), storage.ts (persistența configurației), background.ts (comportament la instalare), toolbarAction.ts (comutator + insignă), messageDisplay.ts și messageDisplayBackground.ts (Thunderbird) și options/options.ts (controlerul paginii de setări).

extensions/<engine> — puncte de intrare subțiri și manifeste

Fiecare motor poartă propriul manifest.json și icons/. Chrome, Firefox și Thunderbird livrează suplimentar un src/ de puncte de intrare; Edge, Opera și Safari poartă doar un manifest și pictograme și REUTILIZEAZĂ src/-ul Chrome (declarat de descriptorul de build — vezi mai jos). Intrările sunt în mod deliberat minuscule: de exemplu extensions/chrome/src/content.ts este doar void createContentController().bootstrap();, iar extensions/chrome/src/background.ts apelează registerBackground, registerContentReconciliation și registerToolbarToggle. Intrarea de fundal a Firefox este identică cu cea a Chrome; cea a Thunderbird (extensions/thunderbird/src/background.ts) apelează în schimb registerBackground și registerMessageDisplay (nu are scripturi de conținut). extensions/_template este un schelet Chromium gata de copiat, nu o țintă de build.

tooling, features și testele de fum

tooling conține build.mjs, version.mjs și icons.mjs. features conține scenariile Gherkin, definițiile lor de pași și dublurile de test fidele din features/support/webext.ts și features/support/world.ts. Fiecare motor poartă și un extensions/<engine>/test/manifest.smoke.test.mjs (cel al _template este template.smoke.test.mjs) — acestea sunt contractul autoritar al manifestului (vezi „Build-ul per motor și contractul manifestului" mai jos).

Conducta de afișare

Calea de conținut începe la o intrare de motor precum extensions/chrome/src/content.ts, care apelează createContentController().bootstrap() din packages/webext/src/content.ts.

Bootstrap

createContentController bootstrap rulează o singură dată per cadru:

  1. Revendică cadrul sincron, înainte de orice await, prin claimBootstrap, care citește/setează flag-ul BOOTSTRAP_MARKER pe window-ul cadrului. O intrare content_scripts încorporată statică și un script de utilizator dinamic suprapus pot injecta ambele content.js în același cadru; window-ul izolat comun face din aceasta garda corectă de o-singură-dată, iar revendicarea înainte de orice await înseamnă că două injectări aproape simultane nu pot trece amândouă de ea.
  2. Încarcă configurația persistată MAI ÎNTÂI prin loadConfigWithRetry (BOOTSTRAP_CONFIG_RETRIES încercări suplimentare la o respingere tranzitorie), păstrând marcatorul de-a lungul reîncercărilor, astfel încât o injectare suprapusă care a devenit deja no-op să nu rămână blocată. Doar după ce citirea reușește injectează foaia de stil (injectStyles, care atribuie REVEAL_URLS_CSS prin textContent), astfel încât o citire eșuată nu adaugă niciun <style>, iar o reîncercare nu-l poate duplica.
  3. Dacă încărcarea configurației eșuează la fiecare încercare, releaseBootstrap derulează marcatorul înapoi (doar aici, înainte ca adnotarea să înceapă), astfel încât o reinjectare ulterioară să poată reîncerca; odată ce start a rulat, marcatorul trebuie să persiste, deoarece un adnotator proaspăt nu ar deține nodurile trecerii anterioare.
  4. Când config.enabled, apelează start și înregistrează întotdeauna un ascultător onConfigChanged care reaplică configurația (acoperind tranziția dezactivat→activat).

Rezolvarea regulii de site active

start (și applyConfig) apelează resolveSiteRule pentru a decide dacă — și unde — să adnoteze. resolveSiteRule încearcă mai întâi PROPRIA locație a documentului care rulează prin resolveForHref, care filtrează config.sites la regulile activate și apelează selectMostSpecific al nucleului, astfel încât cea mai specifică potrivire să câștige (o regulă încorporată specifică bate o metacaractere largă de utilizator). Când propria locație nu se potrivește cu nimic ȘI originea proprie a documentului este opacă sau moștenită — controlat de hasOpaqueOrigin, adevărat doar pentru about:blank/about:srcdoc (moștenită prin INHERITED_ORIGIN_URLS) sau blob:/data: (OPAQUE_ORIGIN_SCHEMES) — revine, în ordine, la cadrul de SUS (readTopHref, cazul iframe-ului de mesaj Proton încorporat), fereastra OPENER (readOpenerHref, fereastra de citire pop-out Outlook deschisă prin window.open("about:blank")) și, în final, document.referrer (readReferrer). Fiecare citire externă este protejată împotriva accesului cross-origin (care aruncă o excepție) și contribuie cu un candidat doar când este citibilă. Un document nepotrivit OBIȘNUIT este autoritar și nu moștenește niciodată o regulă din contextul ambiant, lăsând controlerul inert.

Limitarea la rădăcina de conținut și observarea mutațiilor

start înregistrează selectorul contentRoot al regulii potrivite, creează adnotatorul prin createAnnotator(config) și procesează fiecare rădăcină potrivită din selectRoots(doc, contentRoot) (un querySelectorAll înfășurat în try/catch, astfel încât un selector invalid să eșueze în siguranță la []). Apoi observă doc.body (un container stabil) cu OBSERVER_OPTIONS ({ childList: true, subtree: true }).

Observatorul alimentează processMutations, care controlează fiecare mutație pe rădăcina de conținut: reprocesează un target de mutație doar când este un Element din interiorul unei rădăcini (isWithinRoot, prin closest) și direcționează fiecare Element adăugat prin processAddedNode, care adnotează un nod care este în interiorul unei rădăcini, ESTE o rădăcină sau CONȚINE una (o montare SPA leneșă). Așadar, agitația din interiorul rădăcinii și un corp de mesaj frate care se montează mai târziu sunt ambele prinse, în timp ce interfața aplicației din afara rădăcinilor este lăsată neatinsă. Un adnotator nul (extensia este oprită) este un no-op.

applyConfig reconciliază o modificare de configurație live: demontează când nicio regulă nu se potrivește sau flag-ul activat este oprit; repornește (revenire + reobservare) când contentRoot-ul rezolvat s-a schimbat; și altfel refluează pe loc — annotator.setConfig apoi annotator.process peste fiecare rădăcină potrivită — fără a reporni observatorul.

Adnotarea unei singure ancore

Adnotatorul trăiește în packages/core/src/linkProcessor.ts. createAnnotator returnează un Annotator peste o stare de închidere pe care pagina nu o poate citi: un Map puternic indexat după identitatea ancorei, un token data-ru per adnotator din generateToken (crypto.getRandomValues) și configurația curentă. Metoda sa process(root):

  1. Elimină intrările de registru a căror ancoră nu mai este conectată.
  2. Colectează ancorele candidate cu collectAnchors (descendent a[href] plus rădăcina însăși când este un a[href], deduplificat).
  3. Rezolvă destinația fiecărei ancore cu resolveAnnotatableUrl, care returnează null (omite) pentru un href gol/în aceeași pagină, un href neanalizabil, orice schemă, alta decât http:/https:, sau o gazdă egală cu sau sub o intrare ignoreHosts; rezolvă href-urile relative față de baseURI, astfel încât rezultatul să fie absolut.
  4. Ia calea rapidă idempotentă când isAnnotationIntact confirmă că amprenta de randare este neschimbată și fiecare nod creat este încă conectat și pe poziție; altfel revine la adnotarea învechită și re-randează.
  5. Calculează nepotrivirea cu hostMismatch pe textul curat al ancorei, respectă showOnlyOnMismatch (lăsând linkurile cinstite neatinse) și apelează annotateAnchor.

annotateAnchor randează doar DOM-SIGUR. În modul "inline" prepune un <span class="reveal-urls-url"> al cărui URL este setat prin textContent (prefixat cu o săgeată și un spațiu neîntrerupt U+00A0, trunchiat de truncateUrl), urmat de un <br>, lăsând copiii proprii ai linkului neatinși; span-ul conectat primește apoi un fundal de contrast prin applyContrastBackdrop. În modul "title" salvează title-ul original (TitleSave), îl suprascrie cu URL-ul — singura scriere pe ancoră — și, la o nepotrivire accentuată, adaugă un frate <span class="reveal-urls-warn"> insignă care poartă REVEAL_URLS_WARN_LABEL. Fiecare nod creat este etichetat data-ru="<token>" (niciodată ancora), iar fiecare intrare înregistrează configFingerprint-ul său, astfel încât o modificare de configurație de randare să forțeze un reflux. Culorile și suprascrierile de font sunt aplicate prin proprietăți tipate element.style.* (applyColour/applyFontOverrides), niciodată interpolate într-un șir CSS. revertOwned elimină nodurile create prin referință și restabilește title-ul salvat; revertAll face acest lucru pentru fiecare ancoră deținută și golește registrul.

truncateUrl operează peste PUNCTELE DE COD Unicode ([...displayHref]), astfel încât o tăietură nu împarte niciodată o pereche surogat; păstrează originea schemă-și-gazdă și scurtează restul cu un URL_ELLIPSIS final.

Nepotrivire de gazdă

packages/core/src/hostMismatch.ts hostMismatch compară textul vizibil al linkului cu href-ul său după domeniul înregistrabil, nu după numele brut de gazdă, astfel încât un subdomeniu cinstit să nu fie semnalat, în timp ce unul care imită este. extractHostCandidates împarte textul și reduce fiecare token prin hostCandidate (care necesită un punct și un sufix public ICANN recunoscut de tldts, respingând text cu puncte obișnuit precum e.g). Atât gazda href, cât și fiecare candidat sunt reduse la domeniul lor înregistrabil cu tldts.getDomain, iar ORICE candidat al cărui domeniu diferă de cel al href-ului produce o nepotrivire — astfel încât numirea gazdei rău-intenționate reale alături de o gazdă-momeală nu poate suprima avertizarea. Nu aruncă niciodată o excepție.

Stiluri și contrast

packages/core/src/styles.ts conține REVEAL_URLS_CSS injectabil (aspect, grosime, formă fixe și un fundal de cip fix alb, complet opac — background: #ffffff, opacity: 1; culorile NU sunt NICIODATĂ interpolate în el) și ajutoarele de runtime applyColour/applyFontOverrides/applyContrastBackdrop. applyContrastBackdrop citește culoarea rezolvată a elementului și primul fundal opac găsit mergând de la elementul ÎNSUȘI în sus (propriul său fundal — albul fix implicit al cipului sau o suprascriere mai specifică a paginii gazdă — înaintea oricărui strămoș) prin getComputedStyle standard (provenit din element.ownerDocument?.defaultView, deci este sigur pe cadre și substituibil) și, când packages/core/src/contrast.ts needsWhiteBackdrop raportează că textul eșuează WCAG AA (CONTRAST_THRESHOLD) față de acel fundal ȘI albul ajută cu adevărat, setează style.backgroundColor = "white". contrast.ts oferă parseColour, relativeLuminance și contrastRatio; analizează formele rgb()/rgba()/hex pe care le returnează getComputedStyle și tratează o culoare complet transparentă ca „negăsită".

Gazde configurabile și limitarea conținutului

O SiteRule (packages/core/src/config.ts) spune UNDE rulează adnotarea (match, allFrames) și CARE container o limitează (contentRoot), plus enabled și un flag builtin. DEFAULT_SITES livrate acoperă Gmail, Proton (cu allFrames) și ambele gazde Outlook. Config agregă comutatoarele globale, culorile, suprascrierile de font și tabloul sites.

Motorul de tipare de potrivire

packages/core/src/matchPattern.ts conține o gramatică restrictivă în mod deliberat, la momentul adăugării, MATCH_PATTERN (doar http/https, metacaractere opțional de gazdă *. la început, cale glob; fără port, fără schemă *, fără <all_urls>). matchesPattern potrivește un URL față de un tipar validat, delegând calea la pathGlobMatches (fiecare * se potrivește cu orice secvență de caractere); eșuează în siguranță la false. parsePatternParts împarte un tipar validat în host/path/scheme/ wildcardHost.

Regulile suprapuse se rezolvă prin „cea mai specifică câștigă": compareSiteSpecificity clasează după (1) gazdă exactă peste gazdă cu metacaractere, (2) gazdă literală mai lungă, (3) cale mai literală (literalPathLength), apoi (4) un departajator ASCII determinist; selectMostSpecific returnează cea mai specifică regulă activată care se potrivește unui URL.

Acoperirea originii acordate este un potrivitor SEPARAT, mai LARG în mod deliberat. permissions.getAll() poate raporta acordări în gramatica WebExtension completă (<all_urls>, *://*/*, https://*/*, *://*.host/*, https://host/*) pe care MATCH_PATTERN-ul restrictiv le-ar respinge. parseGrantedOrigin le analizează în GrantedOriginParts, iar originCovers raportează dacă o origine acordată acoperă match-ul unei reguli (de exemplu, o acordare largă https://*/* acoperă cu adevărat fiecare regulă https). matchAllowsOriginFallback raportează dacă calea unei reguli este exact ORIGIN_FALLBACK_PATH (/*) — intersecția sigură a constrângerilor Chromium și Gecko/Firefox 128 asupra revenirii dinamice la originea opacă.

Înregistrare dinamică

packages/webext/src/contentRegistration.ts înregistrează un script de conținut dinamic per regulă adăugată de utilizator. Cele încorporate sunt servite de intrările statice content_scripts și nu sunt NICIODATĂ înregistrate dinamic. desiredContentScripts filtrează config.sites la regulile neîncorporate, activate, a căror origine isOriginGranted (delegând la originCovers), mapează fiecare la un RegisteredContentScript cu un id stabil (contentScriptId, un hash FNV-1a al match-ului, astfel încât id-ul să folosească doar setul de caractere sigur pentru API) și setează matchOriginAsFallback: true DOAR când matchAllowsOriginFallback(rule.match) este valabil (o cale /*). Controlarea flag-ului în acest fel previne ca o regulă cu cale neconformă să provoace respingerea întregii înregistrări în lot; o astfel de regulă se înregistrează fără acoperire pop-out prin proiectare.

reconcileContentScriptsOnce citește originile acordate, configurația stocată și înregistrările live, apoi converge printr-o diferență CONȘTIENTĂ DE PROPRIETĂȚI: sameRegistration proiectează atât descriptorul dorit, cât și citirea live pe o formă normalizată (aplicând valoarea implicită reală WebExtension a fiecărui câmp — allFrames/matchOriginAsFallback implicit false, persistAcrossSessions implicit ADEVĂRAT, runAt implicit document_idle), astfel încât un script proaspăt înregistrat să se compare EGAL cu descriptorul care l-a produs și să nu poată apărea nicio buclă de reînregistrare. Deoarece nu există updateContentScripts, un script modificat este dezînregistrat, apoi reînregistrat. reconcileContentScripts înfășoară acest lucru cu o promisiune în curs (inFlight), astfel încât două declanșatoare aproape simultane (o modificare de configurație ȘI permissions.onAdded declanșând ambele la adăugarea unui site) să fie serializate în loc să concureze. registerContentReconciliation cablează declanșatoarele (permissions.onAdded/onRemoved, onConfigChanged) și converge o dată la pornire; este importat doar de intrările de fundal Chrome și Firefox.

Fluxul de permisiune opt-in

Manifestele statice declară optional_host_permissions (http://*/*, https://*/*), astfel încât un utilizator să poată acorda o gazdă web suplimentară arbitrară la runtime. packages/webext/src/options/options.ts addSite validează ÎNTREAGA regulă candidată (match ȘI content-root) prin normaliseSiteRule ÎNAINTE de a solicita vreo permisiune, derivă originea gazdei cu matchOrigin și apelează browser.permissions.request din interiorul gestului de clic pe butonul Adaugă; doar la acordare adaugă un rând builtin:false și persistă prin calea de scriere canonică.

Configurație, stocare și tiparul canonic

Singura pâlnie de validare

normaliseConfig din packages/core/src/config.ts este singura cale de citire canonică: constrânge și limitează fiecare câmp, revenind la valoarea implicită la orice invalid și nu aruncă niciodată o excepție. Este construit din validatori per câmp — normaliseBoolean, normaliseRenderMode, normaliseMaxLength, normaliseIgnoreHosts (reducând fiecare intrare la un nume de gazdă punycode simplu prin bareHostname), normaliseMatchColour/normaliseMismatchColour (controlate de safeColour.ts isSafeColour), normaliseCssSize, normaliseFontWeight, normaliseContentRoot (limitată de CONTENT_ROOT_PATTERN/CONTENT_ROOT_MAX_LENGTH), normaliseMatchPattern (controlată de MATCH_PATTERN) și normaliseSites. normaliseSites elimină respinsele, deduplifică după match, FORȚEAZĂ builtin: true pe orice regulă al cărei match este egal cu al unei încorporate (închizând vectorul potrivire-exactă-eclipsează-încorporata / dublă-injectare), reseminează orice încorporată lipsă (astfel încât un magazin falsificat să nu poată elimina un furnizor de bază) și sortează alfanumeric după match pentru o ordine canonică. configFingerprint hashează doar câmpurile care afectează randarea (excluzând enabled, ignoreHosts și sites, care conduc pornirea/oprirea și omiterea în loc de reflux) prin fnv1a.

Stocare

packages/webext/src/storage.ts înfășoară API-ul de stocare WebExtension. configArea folosește browser.storage.sync când este disponibil (astfel încât setările să se deplaseze) și revine la browser.storage.local (de ex. pe Thunderbird); configAreaName raportează care este activ. getConfig, setConfig și onConfigChanged toate canalizează valoarea lor brută prin normaliseConfig, astfel încât un magazin malformat sau falsificat să nu poată preda niciodată o Config invalidă unui apelant. onConfigChanged ignoră suplimentar evenimentele din zona de stocare INACTIVĂ și modificările cheilor neînrudite.

Cele două suplimente reutilizează aceeași pâlnie citire -> normaliseConfig -> return peste propriile magazine gazdă. extensions/outlook/src/roamingStorage.ts al panoului de activități Outlook înfășoară Office.context.roamingSettings (get/set sincron, comis cu saveAsync); packages/gmail/src/propertiesStorage.ts al suplimentului Gmail înfășoară PropertiesService.getUserProperties() (per utilizator, deplasându-se pe dispozitivele acelui utilizator, codificat JSON sub o singură cheie și fără a necesita NICIUN domeniu OAuth suplimentar — niciodată ScriptProperties comune). Ambele expun getConfig/setConfig plus accesoarele relevante pentru card getIgnoreHosts/getHighlightMismatch și protejează o suprafață gazdă indisponibilă cu o eroare dedicată. Declanșatorul de pagină de pornire Gmail randează un card de setări CardService din getConfig, iar gestionarul de trimitere a formularului onSaveSettings îmbină ignoreHosts/highlightMismatch analizate pe configurația curentă și le persistă prin setConfig; declanșatorul contextual introduce ignoreHosts în adaptor și highlightMismatch în constructorul de card, citind defensiv, astfel încât o eroare de stocare să revină la valorile implicite în loc să întrerupă analiza mesajelor.

Tiparul configurație-canonică + actualizare-țintită

Trei căi de scriere citesc configurația canonică, schimbă exact un singur lucru și o scriu înapoi — niciodată comițând editări de formular nesalvate sau re-randând formularul:

Propriul toggleEnabled al acțiunii din bara de instrumente (packages/webext/src/toolbarAction.ts) urmează același tipar din partea de fundal.

Build-ul per motor și contractul manifestului

tooling/build.mjs conduce un singur build esbuild pentru fiecare țintă WebExtension. Descriptorul TARGETS numește TARGET-ul SURSĂ al fiecărei ținte: Chrome, Firefox și Thunderbird livrează propriul src/; Edge, Opera și Safari declară chrome ca sursa lor; suplimentul Outlook (Office.js) este propria sa sursă. ACTIVE_TARGETS (Chrome, Edge, Firefox, Opera și Thunderbird — Safari și Outlook sunt excluse) este ceea ce compilează --all/--package; Safari și Outlook se compilează doar când sunt numite explicit, iar suplimentul Gmail (Apps Script) este un pachet esbuild separat (make build-gmail) în afara buclei --all. Acel pachet Gmail țintește runtime-ul V8 al Apps Script, care nu are nici module ES, nici un URL nativ: este construit ca ESM și apoi are declarația sa finală export {…} eliminată (astfel încât funcțiile de declanșare să rămână globale de top-level pe care Apps Script le poate invoca) și împachetează un mic polyfill URL (instalat doar când URL este absent), astfel încât new URL(...) al nucleului comun să rezolve linkurile pe partea de server. buildTarget curăță dist/<target>/, copiază manifest.json per țintă și icons/ verbatim, împachetează fiecare script sursă ca IIFE (content.ts/messageDisplay.ts injectate INJECTED_SCRIPTS nu pot fi module ES, iar fundalurile sunt și ele workeri/pagini de eveniment clasice) și pagina de opțiuni comună ca ESM și copiază options.html/options.css din packages/webext/src/options/. webextension-polyfill este împachetat în fiecare script.

tooling/version.mjs ștampilează versiunile: preia MAJOR.MINOR din package.json-ul rădăcină și adaugă un număr de BUILD auto-incremental (citit din cel mai mare al treilea component existent din manifestele țintă) în fiecare manifest țintă, păstrându-le în pas. Majoritatea țintelor poartă un singur manifest.json; suplimentul Outlook poartă două manifeste versionate (manifest.json și manifest.xml) și ambele sunt ștampilate, iar scheletul _template este ștampilat în pas de asemenea. package.json-ul rădăcină este păstrat la aceeași versiune, dar rămâne semver valid (o versiune din patru părți este permisă într-un manifest, niciodată în package.json). tooling/icons.mjs (rulat prin make icons) rasterizează unica sursă vectorială assets/icon.svg în icons/icon48.png și icons/icon128.png al fiecărei ținte, încercând cairosvg, apoi inkscape, apoi ImageMagick.

Contractul manifestului — câmpurile exacte necesare per motor — este singura sursă de adevăr în fiecare extensions/<engine>/test/manifest.smoke.test.mjs (de ex. assertManifestShape al extensions/chrome/test/manifest.smoke.test.mjs, care construiește și ținta și confirmă că fiecare fișier referit se rezolvă în dist). Conform convenției de deduplicare a proiectului, forma câmp cu câmp NU este reenumerată aici; consultați acele teste de fum și nota „Contractul manifestului" din planul de testare manuală.

Internaționalizare (i18n)

Reveal URLs este localizat în multe limbi în afară de engleză. Setul este single-sourced în packages/core/src/locales.json (SUPPORTED_LOCALES, numele native și valoarea implicită), pe care atât pachetele extensiei (prin importul JSON rezolvat) cât și build-ul de website plain-Node îl citesc. Fiecare șir care nu este în engleză este tradus automat și în așteptarea revizuirii umane; marcatorul de proveniență este per format (cele din _locales folosesc description-ul fiecărui mesaj, dicționarele de interfață ale website-ului îl înregistrează în site/i18n/README.md, iar sursele de documente traduse poartă un comentariu HTML pe prima linie).

Există trei suprafețe de localizare independente, în mod deliberat necablate încrucișat, și fiecare este împărțită de-a lungul unei linii build-time / runtime.

Suprimarea „Site-urilor active" pentru clientul de e-mail (AD-3)

O țintă de client de e-mail (Thunderbird astăzi) vede deja fiecare mesaj redat, deci editorul „Site-uri active" per gazdă este redundant acolo și este eliminat. Decizia este purtată de un flag de descriptor documentat per țintă, mailClient: true, pe TARGETS.thunderbird al tooling/build.mjs — niciodată un nume de țintă hardcodat în logica de build sau de interfață — și este implementată în două jumătăți:

Postura de securitate și confidențialitate

Testare