Reveal URLs — Architettura

Questo documento descrive come è strutturata l'estensione Reveal URLs e come funziona la sua logica di base, per i contributori e i lettori tecnici. Cita file e simboli reali in tutto il testo (nella forma path symbol) così che ogni affermazione possa essere verificata rispetto al codice sorgente. Per la descrizione rivolta all'utente consulta il manuale; per i passaggi di verifica manuale consulta il piano di test manuale.

Panoramica

Reveal URLs è una singola WebExtension MV3 costruita per diversi motori — Chrome, Edge, Opera, Firefox e Thunderbird — a partire da un unico core di codice puro e condiviso. (Safari è rinviato: ha un manifest ed è compilabile quando nominato esplicitamente, ma è escluso dalla build predefinita e dalla CI.) L'estensione rivela l'URL di destinazione di ogni link accanto al link in un'email visualizzata, e contrassegna un link il cui testo visibile indica un dominio registrabile diverso dal suo href — il classico segnale di phishing.

Il repository è un monorepo pnpm/TypeScript (pnpm-workspace.yaml, package.json con "private": true). Il bundling è un unico script guidato da esbuild, tooling/build.mjs, che compila i pacchetti condivisi e i sottili punti di ingresso di ciascun motore in una directory caricabile dist/<target>/. webextension-polyfill è incluso in ogni script anziché essere usato come variabile globale a runtime, così lo stesso codice sorgente gira immutato su Chromium e Gecko.

Due add-on email nativi estendono lo stesso core di rilevamento a superfici che la WebExtension non può raggiungere: un add-in per Outlook (riquadro attività Office.js, che raggiunge Outlook su web/Windows/Mac/iOS/Android e Outlook.com) e un add-on per Gmail (CardService di Apps Script, che raggiunge le app Gmail web/Android/iOS). Entrambi riutilizzano solo l'analisi PURA — un nuovo modulo packages/core/src/findings.ts (analyseAnchors/analyseHtml, un modello Finding[] neutrale rispetto alla piattaforma) che riutilizza la logica hostMismatch esistente e la derivazione del dominio registrabile di tldts. Solo l'adattatore host (come il corpo viene letto e analizzato) e la presentazione differiscono per superficie: il riquadro attività di Outlook analizza il corpo con il proprio DOMParser e gira lato client; l'add-on per Gmail analizza con node-html-parser e gira lato server sull'infrastruttura V8 di Apps Script di Google (hosting gratuito, dato che Google detiene già la posta; distribuito in modo locale-interattivo tramite clasp). Poiché nessuno dei due framework può modificare il DOM del messaggio visualizzato in modalità lettura (i metodi setAsync/prependAsync di Office sono solo per la composizione; CardService rende delle schede, non l'HTML del messaggio), entrambi gli add-on presentano un riquadro/scheda di risultati, non un'annotazione in linea — il percorso di modifica del DOM linkProcessor.ts esistente e REVEAL_URLS_CSS NON sono riutilizzati da nessuno dei due add-on. Un href relativo viene risolto solo rispetto a un affidabile <base href> interno all'email e altrimenti saltato, mai rispetto all'origine della casella/provider (che fabbricherebbe una destinazione).

Il codebase separa nettamente le responsabilità:

Struttura del repository

packages/core — il core puro

Ogni modulo qui non importa alcuna API di estensione (browser.*/chrome.*/messenger.*) e non usa alcun sink DOM da stringa a markup (innerHTML/insertAdjacentHTML). Quel contratto è applicato meccanicamente da packages/core/test/purity.test.ts, che esegue il glob di ogni src/**/*.ts, rimuove i corpi dei commenti tramite il proprio stripComments (così che i docblock che legittimamente NOMINANO i token vietati non facciano scattare la guardia) e poi verifica che né EXTENSION_API_PATTERNUNSAFE_DOM_PATTERN corrispondano ad alcun modulo. La superficie pubblica è riesportata da packages/core/src/index.ts. L'unica dipendenza a runtime è tldts (per il confronto dei domini consapevole dei suffissi pubblici).

I moduli sono:

packages/webext — i wrapper delle API del browser

I moduli qui POSSONO usare browser.* e sono l'unico luogo in cui si toccano le API WebExtension. La superficie pubblica è riesportata da packages/webext/src/index.ts. I moduli sono: content.ts (ciclo di vita del contenuto), contentRegistration.ts (registrazione dinamica), storage.ts (persistenza della configurazione), background.ts (comportamento all'installazione), toolbarAction.ts (interruttore + badge), messageDisplay.ts e messageDisplayBackground.ts (Thunderbird), e options/options.ts (il controller della pagina delle impostazioni).

extensions/<engine> — punti di ingresso sottili e manifest

Ogni motore porta il proprio manifest.json e icons/. Chrome, Firefox e Thunderbird forniscono inoltre un src/ di punti di ingresso; Edge, Opera e Safari portano solo un manifest e le icone e RIUTILIZZANO il src/ di Chrome (dichiarato dal descrittore di build — vedi sotto). I punti di ingresso sono deliberatamente minuscoli: per esempio extensions/chrome/src/content.ts è solo void createContentController().bootstrap();, e extensions/chrome/src/background.ts chiama registerBackground, registerContentReconciliation e registerToolbarToggle. Il punto di ingresso di background di Firefox è identico a quello di Chrome; quello di Thunderbird (extensions/thunderbird/src/background.ts) chiama invece registerBackground e registerMessageDisplay (non ha content script). extensions/_template è uno scaffold Chromium pronto da copiare, non un target di build.

tooling, features e gli smoke test

tooling contiene build.mjs, version.mjs e icons.mjs. features contiene gli scenari Gherkin, le loro definizioni dei passaggi e i fedeli test double in features/support/webext.ts e features/support/world.ts. Ogni motore porta anche un extensions/<engine>/test/manifest.smoke.test.mjs (quello di _template è template.smoke.test.mjs) — questi sono il contratto autorevole del manifest (vedi «Build per motore e il contratto del manifest» sotto).

La pipeline di rivelazione

Il percorso del contenuto inizia da un punto di ingresso del motore come extensions/chrome/src/content.ts, che chiama createContentController().bootstrap() da packages/webext/src/content.ts.

Bootstrap

createContentController bootstrap viene eseguito una volta per frame:

  1. Rivendica il frame in modo sincrono, prima di qualsiasi await, tramite claimBootstrap, che legge/imposta il flag BOOTSTRAP_MARKER sul window del frame. Una voce statica integrata content_scripts e uno script utente dinamico sovrapposto possono entrambi iniettare content.js nello stesso frame; il window condiviso del mondo isolato rende questa la corretta guardia una-volta-sola, e rivendicare prima di qualsiasi await significa che due iniezioni quasi simultanee non possono superarla entrambe.
  2. Carica PRIMA la configurazione persistita tramite loadConfigWithRetry (BOOTSTRAP_CONFIG_RETRIES tentativi aggiuntivi su un rifiuto transitorio), mantenendo il marcatore durante i tentativi così che un'iniezione sovrapposta che ha già eseguito un no-op non rimanga bloccata. Solo una volta che la lettura riesce inietta il foglio di stile (injectStyles, che assegna REVEAL_URLS_CSS tramite textContent), così una lettura fallita non aggiunge alcun <style> e un nuovo tentativo non può duplicarlo.
  3. Se il caricamento della configurazione fallisce a ogni tentativo, releaseBootstrap riporta indietro il marcatore (solo qui, prima che inizi l'annotazione) così che una successiva re-iniezione possa riprovare; una volta che start è stato eseguito il marcatore deve persistere, perché un nuovo annotatore non possiederebbe i nodi del passaggio precedente.
  4. Quando config.enabled chiama start, e registra sempre un listener onConfigChanged che riapplica la configurazione (coprendo la transizione disabilitato→abilitato).

Risolvere la regola del sito attiva

start (e applyConfig) chiama resolveSiteRule per decidere se — e dove — annotare. resolveSiteRule prova prima la posizione PROPRIA del documento in esecuzione tramite resolveForHref, che filtra config.sites alle regole abilitate e si affida a selectMostSpecific del core così che la corrispondenza più specifica vinca (un built-in specifico batte un ampio wildcard utente). Quando la posizione propria non corrisponde a nulla E l'origine propria del documento è opaca o ereditata — controllato da hasOpaqueOrigin, vero solo per about:blank/about:srcdoc (ereditate tramite INHERITED_ORIGIN_URLS) o blob:/data: (OPAQUE_ORIGIN_SCHEMES) — ripiega, nell'ordine, sul frame SUPERIORE (readTopHref, il caso integrato dell'iframe del messaggio di Proton), sulla finestra che lo ha APERTO (readOpenerHref, la finestra di lettura a comparsa di Outlook aperta tramite window.open("about:blank")) e infine su document.referrer (readReferrer). Ogni lettura esterna è protetta contro l'accesso cross-origin (che genera un'eccezione) e fornisce un candidato solo quando leggibile. Un documento ORDINARIO senza corrispondenza è autorevole e non eredita mai una regola dal contesto ambientale, lasciando il controller inerte.

Limitare l'ambito alla radice del contenuto e osservare le mutazioni

start registra il selettore contentRoot della regola corrispondente, crea l'annotatore tramite createAnnotator(config), ed elabora ogni radice corrispondente da selectRoots(doc, contentRoot) (un querySelectorAll racchiuso in try/catch così che un selettore non valido fallisca in modo sicuro a []). Osserva poi doc.body (un contenitore stabile) con OBSERVER_OPTIONS ({ childList: true, subtree: true }).

L'observer alimenta processMutations, che vincola ogni mutazione alla radice del contenuto: rielabora un target di mutazione solo quando è un Element all'interno di una radice (isWithinRoot, tramite closest), e instrada ogni Element aggiunto attraverso processAddedNode, che annota un nodo che è dentro una radice, È una radice, o ne CONTIENE una (un mount SPA pigro). Così sia il rimescolamento all'interno della radice sia un corpo del messaggio fratello montato più tardi vengono entrambi catturati, mentre l'interfaccia dell'app al di fuori delle radici resta intatta. Un annotatore nullo (l'estensione è ferma) è un no-op.

applyConfig riconcilia una modifica della configurazione in tempo reale: smonta tutto quando nessuna regola corrisponde o il flag enabled è disattivato; riavvia (revert + ri-osserva) quando il contentRoot risolto è cambiato; e altrimenti riscorre sul posto — annotator.setConfig poi annotator.process su ogni radice corrispondente — senza riavviare l'observer.

Annotare una singola ancora

L'annotatore risiede in packages/core/src/linkProcessor.ts. createAnnotator restituisce un Annotator su uno stato di chiusura che la pagina non può leggere: una Map forte indicizzata per identità dell'ancora, un token data-ru per annotatore da generateToken (crypto.getRandomValues), e la configurazione corrente. Il suo metodo process(root):

  1. Pota le voci del registro la cui ancora non è più connessa.
  2. Raccoglie le ancore candidate con collectAnchors (i discendenti a[href] più la radice stessa quando è un a[href], deduplicati).
  3. Risolve la destinazione di ogni ancora con resolveAnnotatableUrl, che restituisce null (salta) per un href vuoto/interno alla pagina, un href non analizzabile, qualsiasi schema diverso da http:/https:, o un host uguale o sottostante a una voce di ignoreHosts; risolve gli href relativi rispetto a baseURI così che il risultato sia assoluto.
  4. Prende il percorso veloce idempotente quando isAnnotationIntact conferma che l'impronta di rendering è invariata e ogni nodo creato è ancora connesso e in posizione; altrimenti annulla l'annotazione obsoleta e ri-renderizza.
  5. Calcola la discrepanza con hostMismatch sul testo pulito dell'ancora, rispetta showOnlyOnMismatch (lasciando intatti i link onesti), e chiama annotateAnchor.

annotateAnchor rende solo DOM-SICURO. In modalità "inline" antepone un <span class="reveal-urls-url"> il cui URL è impostato tramite textContent (preceduto da una freccia e uno spazio unificatore U+00A0, troncato da truncateUrl) seguito da un <br>, lasciando intatti i figli propri del link; lo span connesso ottiene poi uno sfondo di contrasto tramite applyContrastBackdrop. In modalità "title" salva il title originale (TitleSave), lo sovrascrive con l'URL — l'unica scrittura sull'ancora — e, su una discrepanza enfatizzata, aggiunge un badge fratello <span class="reveal-urls-warn"> recante REVEAL_URLS_WARN_LABEL. Ogni nodo creato è contrassegnato con data-ru="<token>" (mai l'ancora), e ogni voce registra il proprio configFingerprint così che una modifica della configurazione di rendering forzi una riscorsa. I colori e le sovrascritture dei caratteri sono applicati tramite proprietà tipizzate element.style.* (applyColour/applyFontOverrides), mai interpolati in una stringa CSS. revertOwned rimuove i nodi creati per riferimento e ripristina il titolo salvato; revertAll fa questo per ogni ancora posseduta e svuota il registro.

truncateUrl opera sui PUNTI DI CODICE Unicode ([...displayHref]), così che un taglio non divida mai una coppia surrogata; preserva l'origine schema-e-host e accorcia il resto con un URL_ELLIPSIS finale.

Discrepanza dell'host

packages/core/src/hostMismatch.ts hostMismatch confronta il testo visibile del link con il suo href per dominio registrabile, non per hostname grezzo, così che un sottodominio onesto non venga contrassegnato mentre un sosia sì. extractHostCandidates divide il testo e riduce ogni token tramite hostCandidate (che richiede un punto e un suffisso pubblico ICANN riconosciuto da tldts, rifiutando testo puntato ordinario come e.g). Sia l'host dell'href sia ogni candidato vengono ridotti al loro dominio registrabile con tldts.getDomain, e QUALSIASI candidato il cui dominio differisce da quello dell'href produce una discrepanza — così nominare il vero host malevolo accanto a un host esca non può sopprimere l'avviso. Non genera mai eccezioni.

Stili e contrasto

packages/core/src/styles.ts contiene il REVEAL_URLS_CSS iniettabile (layout, spessore, forma fissi e uno sfondo del chip bianco fisso e completamente opaco — background: #ffffff, opacity: 1; i colori non vi sono MAI interpolati) e gli helper a runtime applyColour/applyFontOverrides/applyContrastBackdrop. applyContrastBackdrop legge il colore risolto dell'elemento e il primo sfondo opaco trovato risalendo dall'elemento STESSO verso l'alto (il suo stesso sfondo — il bianco predefinito fisso del chip o una sovrascrittura più specifica della pagina host — prima di qualsiasi antenato) attraverso lo standard getComputedStyle (ottenuto da element.ownerDocument?.defaultView, così è sicuro rispetto ai frame e sostituibile con stub) e, quando packages/core/src/contrast.ts needsWhiteBackdrop segnala che il testo non supera il WCAG AA (CONTRAST_THRESHOLD) rispetto a quello sfondo E il bianco aiuta davvero, imposta style.backgroundColor = "white". contrast.ts fornisce parseColour, relativeLuminance e contrastRatio; analizza le forme rgb()/rgba()/esadecimali che getComputedStyle restituisce e tratta un colore completamente trasparente come «non trovato».

Host configurabili e ambito del contenuto

Una SiteRule (packages/core/src/config.ts) dice DOVE viene eseguita l'annotazione (match, allFrames) e QUALE contenitore ne delimita l'ambito (contentRoot), più enabled e un flag builtin. I DEFAULT_SITES forniti coprono Gmail, Proton (con allFrames) ed entrambi gli host di Outlook. Il Config aggrega gli interruttori globali, i colori, le sovrascritture dei caratteri e l'array sites.

Il motore dei match-pattern

packages/core/src/matchPattern.ts contiene una grammatica MATCH_PATTERN deliberatamente RESTRITTIVA al momento dell'aggiunta (solo http/https, wildcard host iniziale *. opzionale, percorso glob; nessuna porta, nessuno schema *, nessun <all_urls>). matchesPattern confronta un URL con un pattern convalidato, delegando il percorso a pathGlobMatches (ogni * corrisponde a qualsiasi sequenza di caratteri); fallisce in modo sicuro a false. parsePatternParts divide un pattern convalidato in host/path/scheme/wildcardHost.

Le regole sovrapposte si risolvono con «vince la più specifica»: compareSiteSpecificity classifica per (1) host esatto rispetto a host wildcard, (2) host letterale più lungo, (3) percorso più letterale (literalPathLength), poi (4) un tiebreak ASCII deterministico; selectMostSpecific restituisce la regola abilitata più specifica che corrisponde a un URL.

La copertura delle origini concesse è un matcher SEPARATO, deliberatamente PIÙ AMPIO. permissions.getAll() può segnalare concessioni nella grammatica WebExtension completa (<all_urls>, *://*/*, https://*/*, *://*.host/*, https://host/*) che il restrittivo MATCH_PATTERN rifiuterebbe. parseGrantedOrigin le analizza in GrantedOriginParts, e originCovers segnala se un'origine concessa copre il match di una regola (per esempio un'ampia concessione https://*/* copre genuinamente ogni regola https). matchAllowsOriginFallback segnala se il percorso di una regola è esattamente ORIGIN_FALLBACK_PATH (/*) — l'intersezione sicura dei vincoli di Chromium e Gecko/Firefox 128 sul fallback dinamico a origine opaca.

Registrazione dinamica

packages/webext/src/contentRegistration.ts registra un content script dinamico per ogni regola aggiunta dall'utente. I built-in sono serviti dalle voci statiche content_scripts e non sono MAI registrati dinamicamente. desiredContentScripts filtra config.sites alle regole non-builtin e abilitate la cui origine isOriginGranted (delegando a originCovers), mappa ciascuna a un RegisteredContentScript con un id stabile (contentScriptId, un hash FNV-1a del match così che l'id usi solo l'insieme di caratteri sicuri per l'API), e imposta matchOriginAsFallback: true SOLO quando matchAllowsOriginFallback(rule.match) è vero (un percorso /*). Vincolare il flag in questo modo impedisce che una regola con percorso non conforme provochi il rifiuto dell'intera registrazione in batch; una tale regola si registra senza copertura delle finestre a comparsa per scelta progettuale.

reconcileContentScriptsOnce legge le origini concesse, la configurazione memorizzata e le registrazioni attive, poi converge con un diff CONSAPEVOLE DELLE PROPRIETÀ: sameRegistration proietta sia il descrittore desiderato sia la rilettura attiva su una forma normalizzata (applicando il vero valore predefinito WebExtension di ogni campo — allFrames/matchOriginAsFallback predefiniti a false, persistAcrossSessions predefinito a TRUE, runAt predefinito a document_idle) così che uno script appena registrato risulti UGUALE al descrittore che lo ha prodotto e non possa sorgere alcun loop di ri-registrazione. Poiché non esiste updateContentScripts, uno script modificato viene deregistrato e poi ri-registrato. reconcileContentScripts lo avvolge con una promise in volo (inFlight) così che due trigger quasi simultanei (una modifica della configurazione E permissions.onAdded che scattano entrambi su un'aggiunta di sito) siano serializzati anziché in concorrenza. registerContentReconciliation collega i trigger (permissions.onAdded/onRemoved, onConfigChanged) e converge una volta all'avvio; è importato solo dai punti di ingresso di background di Chrome e Firefox.

Il flusso di consenso ai permessi

I manifest statici dichiarano optional_host_permissions (http://*/*, https://*/*) così che un utente possa concedere un host web aggiuntivo arbitrario a runtime. addSite di packages/webext/src/options/options.ts convalida l'INTERA regola candidata (match E content-root) tramite normaliseSiteRule PRIMA di richiedere qualsiasi permesso, deriva l'origine dell'host con matchOrigin, e chiama browser.permissions.request all'interno del gesto di clic del pulsante Aggiungi; solo alla concessione aggiunge una riga builtin:false e persiste attraverso il percorso di scrittura canonico.

Configurazione, archiviazione e il pattern canonico

L'unico imbuto di convalida

normaliseConfig in packages/core/src/config.ts è l'unico percorso di lettura canonico: coerce e delimita ogni campo, ripiegando sul valore predefinito per qualsiasi cosa non valida, e non genera mai eccezioni. È costruito a partire da validatori per campo — normaliseBoolean, normaliseRenderMode, normaliseMaxLength, normaliseIgnoreHosts (riducendo ogni voce a un semplice hostname punycode tramite bareHostname), normaliseMatchColour/normaliseMismatchColour (controllati da safeColour.ts isSafeColour), normaliseCssSize, normaliseFontWeight, normaliseContentRoot (delimitato da CONTENT_ROOT_PATTERN/CONTENT_ROOT_MAX_LENGTH), normaliseMatchPattern (controllato da MATCH_PATTERN) e normaliseSites. normaliseSites scarta i rifiuti, deduplica per match, FORZA builtin: true su qualsiasi regola il cui match è uguale a quello di un built-in (chiudendo il vettore corrispondenza-esatta-oscura-built-in / doppia-iniezione), reinserisce ogni built-in mancante (così che un archivio manomesso non possa eliminare un provider di base) e ordina alfanumericamente per match per un ordine canonico. configFingerprint calcola l'hash solo dei campi che influenzano il rendering (escludendo enabled, ignoreHosts e sites, che guidano avvio/arresto e salto anziché la riscorsa) tramite fnv1a.

Archiviazione

packages/webext/src/storage.ts avvolge l'API di archiviazione WebExtension. configArea usa browser.storage.sync quando disponibile (così le impostazioni si spostano tra dispositivi) e ripiega su browser.storage.local (per esempio su Thunderbird); configAreaName segnala quale è attiva. getConfig, setConfig e onConfigChanged incanalano tutti il loro valore grezzo attraverso normaliseConfig, così che un archivio malformato o manomesso non possa mai consegnare una Config non valida a un chiamante. onConfigChanged ignora inoltre gli eventi provenienti dall'area di archiviazione INATTIVA e le modifiche a chiavi non correlate.

I due add-on riutilizzano lo stesso imbuto lettura -> normaliseConfig -> restituzione sui propri archivi host. extensions/outlook/src/roamingStorage.ts del riquadro attività di Outlook avvolge Office.context.roamingSettings (get/set sincroni, salvati con saveAsync); packages/gmail/src/propertiesStorage.ts dell'add-on per Gmail avvolge PropertiesService.getUserProperties() (per utente, in roaming tra i dispositivi di quell'utente, codificato in JSON sotto un'unica chiave, e che NON richiede alcun ambito OAuth aggiuntivo — mai le ScriptProperties condivise). Entrambi espongono getConfig/setConfig più gli accessor rilevanti per la scheda getIgnoreHosts/getHighlightMismatch e proteggono una superficie host non disponibile con un errore dedicato. Il trigger della homepage di Gmail rende una scheda di impostazioni CardService da getConfig, e il gestore di invio del modulo onSaveSettings unisce ignoreHosts/highlightMismatch analizzati sulla configurazione corrente e la persiste tramite setConfig; il trigger contestuale infila ignoreHosts nell'adattatore e highlightMismatch nel costruttore della scheda, leggendo in modo difensivo così che un guasto dell'archiviazione ripieghi sui valori predefiniti anziché interrompere l'analisi del messaggio.

Il pattern configurazione-canonica + aggiornamento-mirato

Tre percorsi di scrittura leggono la configurazione canonica, modificano esattamente una cosa e la riscrivono — senza mai salvare modifiche del modulo non salvate né ri-renderizzare il modulo:

Il toggleEnabled proprio dell'azione della barra degli strumenti (packages/webext/src/toolbarAction.ts) segue lo stesso pattern dal lato del background.

Build per motore e il contratto del manifest

tooling/build.mjs guida un'unica build esbuild per ogni target WebExtension. Il descrittore TARGETS nomina il TARGET SORGENTE di ciascun target: Chrome, Firefox e Thunderbird forniscono il proprio src/; Edge, Opera e Safari dichiarano chrome come loro sorgente; l'add-in per Outlook (Office.js) è la sorgente di se stesso. ACTIVE_TARGETS (Chrome, Edge, Firefox, Opera e Thunderbird — Safari e Outlook sono esclusi) è ciò che --all/--package compilano; Safari e Outlook compilano solo quando nominati esplicitamente, e l'add-on per Gmail (Apps Script) è un bundle esbuild separato (make build-gmail) al di fuori del ciclo --all. Quel bundle di Gmail ha come target il runtime V8 di Apps Script, che non ha né moduli ES né un URL nativo: è compilato come ESM e poi gli viene rimossa l'istruzione finale export {…} (così che le funzioni trigger rimangano variabili globali di primo livello che Apps Script può invocare), e include un piccolo polyfill di URL (installato solo quando URL è assente) così che il new URL(...) del core condiviso risolva i link lato server. buildTarget pulisce dist/<target>/, copia il manifest.json e icons/ per target alla lettera, raggruppa ogni script sorgente come IIFE (gli INJECTED_SCRIPTS iniettati content.ts/messageDisplay.ts non possono essere moduli ES, e anche i background sono classici worker/event page) e la pagina delle opzioni condivisa come ESM, e copia options.html/options.css da packages/webext/src/options/. webextension-polyfill è incluso in ogni script.

tooling/version.mjs marca le versioni: prende MAJOR.MINOR dal package.json radice e aggiunge un numero di BUILD auto-incrementante (letto dal terzo componente esistente più alto tra i manifest dei target) in ogni manifest di target, mantenendoli allineati. La maggior parte dei target porta un singolo manifest.json; l'add-in per Outlook porta due manifest versionati (manifest.json e manifest.xml) ed entrambi vengono marcati, e anche lo scaffold _template viene marcato in modo allineato. Il package.json radice è mantenuto alla stessa versione, ma resta semver valido (una versione a quattro parti è consentita in un manifest, mai in package.json). tooling/icons.mjs (eseguito tramite make icons) rasterizza l'unica sorgente vettoriale assets/icon.svg negli icons/icon48.png e icons/icon128.png di ciascun target, provando cairosvg, poi inkscape, poi ImageMagick.

Il contratto del manifest — gli esatti campi richiesti per motore — è l'unica fonte di verità in ogni extensions/<engine>/test/manifest.smoke.test.mjs (per esempio assertManifestShape di extensions/chrome/test/manifest.smoke.test.mjs, che compila anche il target e conferma che ogni file referenziato si risolva nella dist). Secondo la convenzione di de-duplicazione del progetto, la forma campo per campo NON è rienumerata qui; fai riferimento a quegli smoke test e alla nota «Manifest contract» nel piano di test manuale.

Internazionalizzazione (i18n)

Reveal URLs è localizzato in molte lingue oltre all'inglese. L'insieme ha un'unica fonte in packages/core/src/locales.json (SUPPORTED_LOCALES, i nomi nativi e il valore predefinito), che leggono sia i bundle dell'estensione (tramite l'import JSON risolto) sia la build del sito web in puro Node. Ogni stringa non inglese è tradotta automaticamente e in attesa di revisione umana; il marcatore di provenienza è per formato (i _locales usano la description di ogni messaggio, i dizionari del chrome del sito web lo registrano in site/i18n/README.md, e le sorgenti dei documenti tradotti portano un commento HTML in prima riga).

Ci sono tre superfici di localizzazione indipendenti, deliberatamente non interconnesse, e ciascuna è divisa lungo una linea build-time / runtime.

Soppressione dei «Siti attivi» nel client di posta (AD-3)

Un target client di posta (oggi Thunderbird) vede già ogni messaggio renderizzato, quindi l'editor «Siti attivi» per host è ridondante lì e viene rimosso. La decisione è veicolata da un flag documentato del descrittore per target, mailClient: true, su TARGETS.thunderbird di tooling/build.mjs — mai un nome di target hardcoded nella logica di build o dell'interfaccia — ed è implementata in due metà:

Postura di sicurezza e privacy

Test