検出フロー
3 つのデータソースパイプライン
StackPrism は 3 つのパイプラインを並行して実行し、結果をマージします:
┌──────────────────────────────────────────────────────────┐
│ パイプライン A:レスポンスヘッダー (chrome.webRequest) │
│ - background webRequest.onHeadersReceived │
│ - メインドキュメント / API / iframe の server / │
│ x-powered-by など │
│ - chrome.storage.session の tab:{id}.main / │
│ tab:{id}.apis[] / tab:{id}.frames[] に書き込む │
└──────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ パイプライン B:ページアクティブ検出 │
│ (chrome.scripting + injected) │
│ - ユーザーがポップアップの更新ボタンをクリックすると │
│ START_BACKGROUND_DETECTION が発火 │
│ - bg が scripting.executeScript を呼び出して │
│ page-detector を注入 │
│ - page-detector がページの MAIN world で実行されて │
│ 結果を返す │
│ - 結果を tab:{id}.page に書き込む │
└──────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ パイプライン C:動的収集 │
│ (content script + MutationObserver) │
│ - bg がページ load 時に content-observer.ts を注入 │
│ - MutationObserver / PerformanceObserver が継続的に │
│ 累積 │
│ - 800ms スロットリング後に DYNAMIC_PAGE_SNAPSHOT を │
│ sendMessage │
│ - bg が 800ms デバウンス後に │
│ detectFromDynamicSnapshot を実行 │
│ - 結果を tab:{id}.dynamic に書き込む │
└──────────────────────────────────────────────────────────┘3 つのタブキーはすべて chrome.storage.session に保存されます。サービスワーカーが再起動した場合もストレージから状態を復元でき、SW のメモリには依存しません。
データフロー:トリガーからポップアップ表示まで
ユーザーが拡張機能アイコンをクリック
↓
ポップアップのマウント
↓
loadCachedDetection()
↓
chrome.runtime.sendMessage({ type: 'GET_POPUP_RESULT', tabId })
↓
bg.message-router が getPopupResultResponse(tabId) にルーティング
↓
chrome.storage.session に popup:{tabId} のキャッシュがあるか確認
├─ ヒット且つ settingsKey が一致 → そのまま返す
└─ 未ヒット → buildPopupCacheRecord をその場で実行:
1. tab:{id} から page / main / apis / frames / dynamic を読み取る
2. addStoredCustomHeaderRules(ユーザーのカスタムレスポンスヘッダールールを適用)
3. buildDisplayTechnologies:
- 5 つのソースの結果を 1 つの配列にマージ
- mergeDisplayTechnologyRecords で重複排除 + sources Set の収集
- filterTechnologiesBySettings(設定ページのフィルターを適用)
4. mergeResourceSummary(リソース統計をマージ)
5. cleanPopupTechnology + ソート出力
6. popup:{tabId} キャッシュに書き込む
↓
bg が PopupResult を返す
↓
ポップアップが setState.result を更新して表示
注意:その後もバックグラウンドは webRequest のレスポンスヘッダーや
動的スナップショットなどの増分更新を受け続け、
更新のたびに popup:{tabId} キャッシュを再構築する
↓
chrome.storage.onChanged がポップアップ側の onStorageChange を発火
↓
popupCacheSignature の複数フィールドの署名を比較し、
真の変化があった場合のみ state.result を置き換えるアクティブ検出のタイミング
ポップアップで「更新」をクリックすると START_BACKGROUND_DETECTION メッセージが発火されます:
// background/detection.ts
const runActivePageDetection = async tabId => {
const pageRules = await buildEffectivePageRules()
// 1. ルールをページの一時的なグローバル変数に書き込む
await chrome.scripting.executeScript({
target: { tabId },
world: 'MAIN',
func: rules => {
window.__SP_RULES__ = rules
},
args: [pageRules]
})
// 2. IIFE を注入。IIFE 内部で結果を return
const [{ result }] = await chrome.scripting.executeScript({
target: { tabId },
world: 'MAIN',
files: ['injected/page-detector.iife.js']
})
// 3. tab:{id}.page に保存し、saveTabDataAndBadge がポップアップキャッシュを再構築
data.page = cleanPageDetectionRecord(result)
await saveTabDataAndBadge(tabId, data)
}動的収集のスロットリング
content-observer 側:
- MutationObserver / PerformanceObserver が発火するたびに変化を state に累積
scheduleSend()が 900ms のタイマーをセット。タイマーが切れたらDYNAMIC_PAGE_SNAPSHOTを送信- その間に追加の変化があれば累積を続け、タイマーをリセット — 最大 900ms に 1 回しか送信しない
background 側の dynamic-snapshot.ts:
- メッセージを受信後 800ms デバウンス
- デバウンス後に
detectFromDynamicSnapshot(snapshot, pageRules)を実行 tab:{id}.dynamicに書き込み、saveTabDataAndBadgeを実行
ルールマッチングレイヤー(rule-matcher.ts)
各ルールは 3 つの段階で使用されます:
detectFromXxx(snapshot, rules)
scanner.scan(lowerText) ─ すべてのルールのヒントでインデックスを構築、テキストは 1 回だけスキャン
for (const rule of rules):
1. passesResourceHintLookup(rule, lookup) ─ ビジネス側の resourceHints 閾値
2. passesHintLookup(rule, lookup) ─ 必須リテラルの事前フィルタリング(マッチしえないルールをスキップ)
passesLegacyHintLookup(rule, lookup) ─ 旧バージョンの閾値(__legacyHints、結果が変わらないことを保証)
3. matchesCompiledRulePatterns(rule, text) ─ 実際の正規表現 / キーワードマージ正規表現を実行
├─ keyword は getCompiledCombinedPattern:キャッシュされたマージ正規表現で 1 回マッチ
└─ regex は getCompiledRulePatterns.some:キャッシュされた RegExp[] を順に test
4. ヒット:add(category, name, confidence, evidence)WeakMap キャッシュにより、各ルールの正規表現 + ヒントのコンパイルは 1 回だけ実行されます。ヒントインデックスはルールリスト単位でキャッシュされ、ルールが変わらない限り再利用されます。
バッジの数字
saveTabDataAndBadge がポップアップキャッシュを書き込むたびに popupResult.counts.high(高信頼度技術の数)を取得し、chrome.action.setBadgeText を呼び出して拡張機能アイコンに表示します。ポップアップを開いて見える数字 ≈ バッジの数字です。
設定変更によるキャッシュの無効化
buildSettingsCacheKey(settings) は settings の主要フィールド(disabledCategories / disabledTechnologies / customRules)を 1 つの文字列にシリアライズします。getCachedPopupResult(popup, settings) のキャッシュヒット条件の 1 つは popup.settingsKey === buildSettingsCacheKey(settings) です。
設定が変わると settingsKey が変わり、古いキャッシュが自動的に無効化されます。次の GET_POPUP_RESULT でその場で再構築されます。