Flusso di rilevamento
Tre pipeline di dati
StackPrism esegue tre pipeline in parallelo e ne unisce i risultati:
┌──────────────────────────────────────────────────────────┐
│ Pipeline A: Intestazioni (chrome.webRequest) │
│ - background webRequest.onHeadersReceived │
│ - server / x-powered-by ecc. di documento / API / iframe │
│ - scritto in chrome.storage.session su tab:{id}.main / │
│ tab:{id}.apis[] / tab:{id}.frames[] │
└──────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ Pipeline B: Rilevamento attivo (chrome.scripting + injected) │
│ - attivato dal pulsante "Aggiorna" nel popup │
│ - bg chiama scripting.executeScript per iniettare page-detector │
│ - page-detector viene eseguito nel MAIN world e restituisce il risultato │
│ - risultato scritto in tab:{id}.page │
└──────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ Pipeline C: Raccolta dinamica (content script + MutationObserver) │
│ - bg inietta content-observer.ts al caricamento della pagina │
│ - MutationObserver / PerformanceObserver accumulano continuamente │
│ - throttle 800ms poi sendMessage DYNAMIC_PAGE_SNAPSHOT │
│ - bg con debounce 800ms esegue detectFromDynamicSnapshot │
│ - risultato scritto in tab:{id}.dynamic │
└──────────────────────────────────────────────────────────┘Le tre chiavi tab sono tutte in chrome.storage.session; dopo un riavvio del service worker lo stato viene ripristinato dallo storage senza dipendere dalla memoria del SW.
Flusso dati: dal trigger alla visualizzazione nel popup
Utente clicca l'icona estensione
↓
Popup mount
↓
loadCachedDetection()
↓
chrome.runtime.sendMessage({ type: 'GET_POPUP_RESULT', tabId })
↓
bg.message-router instrada a getPopupResultResponse(tabId)
↓
Verifica se esiste cache popup:{tabId} in chrome.storage.session
├─ presente e settingsKey invariato → risposta diretta
└─ assente → esecuzione di buildPopupCacheRecord:
1. legge page / main / apis / frames / dynamic da tab:{id}
2. addStoredCustomHeaderRules (applica regole intestazioni personalizzate)
3. buildDisplayTechnologies:
- fonde i 5 risultati in un unico array
- mergeDisplayTechnologyRecords: dedup + raccolta Set delle origini
- filterTechnologiesBySettings (filtra per impostazioni)
4. mergeResourceSummary (unisce statistiche risorse)
5. cleanPopupTechnology + output ordinato
6. scritto in cache popup:{tabId}
↓
bg restituisce PopupResult
↓
popup setState.result, visualizzazione
Nota: in seguito background continua a ricevere intestazioni webRequest, snapshot
dinamici e altri aggiornamenti incrementali; ogni aggiornamento riscrive la cache popup:{tabId}
↓
chrome.storage.onChanged attiva onStorageChange lato popup
↓
confronta firma multipla popupCacheSignature, sostituisce state.result solo se ci sono cambiamenti realiTiming del rilevamento attivo
Cliccando "Aggiorna" nel popup si attiva il messaggio START_BACKGROUND_DETECTION:
// background/detection.ts
const runActivePageDetection = async tabId => {
const pageRules = await buildEffectivePageRules()
// 1. scrittura regole nella variabile globale temporanea della pagina
await chrome.scripting.executeScript({
target: { tabId },
world: 'MAIN',
func: rules => {
window.__SP_RULES__ = rules
},
args: [pageRules]
})
// 2. iniezione IIFE; l'IIFE restituisce internamente il risultato
const [{ result }] = await chrome.scripting.executeScript({
target: { tabId },
world: 'MAIN',
files: ['injected/page-detector.iife.js']
})
// 3. salvataggio in tab:{id}.page, triggera saveTabDataAndBadge per ricostruire la cache popup
data.page = cleanPageDetectionRecord(result)
await saveTabDataAndBadge(tabId, data)
}Throttle della raccolta dinamica
Lato content-observer:
- Ad ogni trigger di MutationObserver / PerformanceObserver, le variazioni vengono accumulate nello stato
scheduleSend()imposta un timer da 900ms; allo scadere inviaDYNAMIC_PAGE_SNAPSHOT- Se nel frattempo arrivano altri cambiamenti, continuano ad accumularsi e il timer si azzera — in totale al massimo un invio ogni ~900ms
Lato background dynamic-snapshot.ts:
- Alla ricezione del messaggio, debounce di 800ms
- Allo scadere del debounce esegue
detectFromDynamicSnapshot(snapshot, pageRules) - Scrive in
tab:{id}.dynamic, poi eseguesaveTabDataAndBadge
Livello di matching delle regole (rule-matcher.ts)
Ogni regola viene usata su tre livelli:
detectFromXxx(snapshot, rules)
scanner.scan(lowerText) ─ hint di tutte le regole in un indice, testo scansionato una sola volta
for (const rule of rules):
1. passesResourceHintLookup(rule, lookup) ─ soglia resourceHints lato business
2. passesHintLookup(rule, lookup) ─ pre-filtro letterali necessari (salta solo le regole impossibili)
passesLegacyHintLookup(rule, lookup) ─ soglia legacy (__legacyHints, garantisce invarianza risultati)
3. matchesCompiledRulePatterns(rule, text) ─ esecuzione regex / keyword reale
├─ keyword usa getCompiledCombinedPattern: regex combinata in cache, un solo match
└─ regex usa getCompiledCombinedPattern.some: RegExp[] in cache, test uno per uno
4. corrispondenza: add(category, name, confidence, evidence)La cache WeakMap fa sì che la compilazione di regex + hint di ogni regola avvenga una sola volta; l'indice hint viene messo in cache per lista di regole e riutilizzato se le regole non cambiano.
Badge numerico
Ogni volta che saveTabDataAndBadge finisce di scrivere la cache popup, prende popupResult.counts.high (numero di tecnologie ad alta confidenza) e chiama chrome.action.setBadgeText per mostrarlo sull'icona dell'estensione. Il numero nel popup ≈ numero nel badge.
Come le modifiche alle impostazioni invalidano la cache
buildSettingsCacheKey(settings) serializza i campi chiave delle impostazioni (disabledCategories / disabledTechnologies / customRules) in una stringa. Una delle condizioni di hit di getCachedPopupResult(popup, settings) è popup.settingsKey === buildSettingsCacheKey(settings).
Appena le impostazioni cambiano, la settingsKey cambia, la cache precedente viene automaticamente invalidata e la prossima GET_POPUP_RESULT ricostruisce tutto da zero.