Files
obsidian-vault/work/projects/cpm-web-extension-breakage-session-summary-2026-10-07.md
T

312 lines
33 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
updated: '2026-10-20'
---
# CPM Web Extension breakage — полное summary сессии
> **Контекст актуализирован 20 октября 2026.** Это summary исследований 7–9 октября с follow-up от 15 октября, а не текущий PR contract. Последние states/CI и implementations — [[cpm-status-2026-10-15]], код — [[cpm-web-extension-code-guide-2026-10-09#0. Текущая карта worktree и точек входа]].
>
> #7376 реализован как один terminal episode report, daily+count; PR закрыт без merge. Текущие `stuck`, двухфазный `bg_events`, удалённые события/поля и ограничения — [[cpm-pr0-episode-telemetry-summary-2026-10-16]]. Старые daily event ratios не являются переходами одного пользователя; новый baseline нужен отдельно.
>
> Source-linked P5/shared-window и условный P6 подтверждены; дальнейшие app/fix проверки датированы в основном статусе. Page ACK по-прежнему нет, а новые данные не объясняют весь Others. Production truncation evidence и оставшиеся измерения — §6.
## Историческое продолжение — 15 октября 2026
> **Текущее продолжение — 15 октября 2026:** [[cpm-status-2026-10-15]]; актуальная карта worktree — [[cpm-web-extension-code-guide-2026-10-09]] (§0). Разделы 1–9 ниже относятся к 7 октября, §10 — к 9 октября; они не являются последним статусом.
>
> Добавлены 14 P5/P6 tests, все текущие методы имеют green в раздельных запусках. Найдены и проверены пути browser window-close → WebKit forgetTab → P5/tab=false и условный main-feature-flag handler cycle → replacement controller → P6. Для P5/tab=true, естественной потери metadata, actual iOS suspend/resume и production main-flag transitions доказательств нет. Полный AppKit flow не запускался; browser source contract прочитан, production shared classes и system WebKit исполнены.
>
> Пакет: 478 XCTest / 28 opt-in skipped / 0 failures; strict SwiftLint — 0. Ранние красные ожидания, проблемы постановки и отрицательные результаты не удалены: см. основной статус §10–12. Состояния PR/CI и нарезка worktree теперь проверены отдельно; прежнее «всё локально» относится к датированной части.
**Дата:** 7 октября 2026
**Охват:** public-attribution после релиза iOS 7.240 / macOS 1.210, исходный код Browser и WebKit, release bundle CPM, WebKit tracker и upstream regression tests.
## Короткий вывод
> Разделы 1–9 — состояние на 7 октября; раздел 10 — продолжение на 9 октября. Текущий статус — [[cpm-status-2026-10-15]].
Главный production-сигнал — не доказанная потеря `initResp`, а отсутствие в течение 4 секунд native-вызова `refreshCpmDashboardState` после завершения навигации. Поэтому большая группа, где native-снимок выглядит здоровым, означает: WebKit controller/context/scripts могли существовать, но CPM не прислал или native не сопоставил ожидаемый dashboard refresh. Она не доказывает, что extension вообще не загрузился.
Наиболее близкие к этому симптому WebKit-кандидаты — гонки при cold start service worker:
1. **317981:** content script отправляет `runtime.sendMessage`, пока background ещё загружается; WebKit не должен терять сообщение.
2. **324743:** worker считается загруженным и получает сообщение до завершения activation/восстановления clients.
Два других WebKit-багa могут объяснять ухудшение после перезагрузки/recovery, но пока не доказано, что они происходят в CPM:
3. **322881:** после reload worker теряет существующие extension clients.
4. **324754:** при site isolation перезагруженный worker может оказаться в новом процессе без Web Extension bindings.
M4 recovery даёт сильный сигнал восстановления измеряемого background → native пути, но не подтверждает восстановление `initResp` или всей CPM-функции. Следующая работа: воспроизвести upstream-сценарии на system WebKit и одновременно добавить event-level instrumentation, которая разделит content `init`, worker receive, `initResp` и dashboard refresh.
---
## 1. Что измеряет current telemetry
### Основной сигнал
После `navigationDidFinish` `CPMMessagingHealthMonitor` ждёт до 4 секунд native `dashboardResponse`. Он приходит только когда background вызывает `refreshCpmDashboardState`, а native handler принимает и сопоставляет его с текущим документом.
```text
navigationDidFinish
→ ожидание dashboardResponse (4 s)
→ нет ответа
→ initialization.failed.after.*.daily
→ при следующем qualifying timeout в пределах episode
→ messaging.stuck.*.daily
→ M4 reload embedded extension
```
Следствия:
- `initialization.failed` не равно «content script не отправил init».
- `initialization.failed` не равно «background не получил init».
- `initialization.failed` не равно «initResp не дошёл до content script».
- `context_loaded`, живой background view, зарегистрированные scripts и native handler не подтверждают, что service worker уже активирован или обработал именно этот message.
`context_loaded` в WebKit означает, что controller уже назначен. Это происходит раньше асинхронной подготовки storage, создания background view, регистрации scripts и injected content. `tab_has_ext_scripts` отражает зарегистрированные `WKUserScript`, а не факт их исполнения в этом документе.
### Почему `initResp` и dashboard refresh не одно и то же
В release bundle content script отправляет `init` в background. Успешная ветка background запускает `updateCpmDashboardState(...).then(refreshDashboardState(...))` без `await`, а затем отдельно отправляет `initResp` во вкладку. В части веток (`config_unavailable`, `settings_missing`, `setting_disabled`, `site_disabled`) отправляется dashboard refresh, но `initResp` не отправляется.
Значит возможны оба расхождения:
- `initResp` был, но refresh задержался, не пришёл или не сопоставился — monitor сообщает failure.
- refresh пришёл, но `initResp` не было — monitor считает путь здоровым.
Дополнительные реальные задержки до refresh: remote config, native settings/site checks, последовательная async queue, session storage. JS native-request timeout в bundle — 20 секунд, тогда как monitor ждёт 4 секунды. URL сопоставляется по scheme, host, port, path и query; поздний refresh или другой query также дают failure при работающем JS.
---
## 2. Public attribution: охват и контрольные суммы
Окно: полный UTC-день 5 октября 2026, только события после release tag каждой платформы. `.daily` — максимум одно событие каждого имени в сутки на установку. Это не пользователи, не эпизоды и не transition rate.
| После release tag | iOS 7.240 | macOS 1.210 |
|---|---:|---:|
| Первое `initialization.failed.after.*.daily` | 6 624 | 1 377 |
| Последующее `messaging.stuck.*.daily` | 1 440 | 487 |
| Первый неудачный CPM probe после reload | 670 | 232 |
| `recovered.after.extension.reload.daily` | 963 = 916 stuck + 47 init | 362 = 354 stuck + 8 init |
| Все CPM daily по именам | 11 252 | 2 804 |
Контроль: 8 001 init + 1 927 stuck = основные первичные/вторичные строки после tag. До macOS tag в выгрузку попали 28 init, 2 stuck и 39 CPM daily; они вынесены из public-среза. До iOS tag строк этой версии нет.
### Группы снимков на первом failure
Каждая строка относится к одной группе по приоритету. Это описание состояния в момент timeout, не первопричина.
| Группа | iOS init | macOS init | iOS stuck | macOS stuck |
|---|---:|---:|---:|---:|
| Native snapshot без видимой ошибки | 3 717 | 911 | 789 | 335 |
| Tab attachment / injection | 931 | 116 | 53 | 38 |
| Background state | 768 | 158 | 457 | 35 |
| Только история context errors | 343 | 158 | 87 | 79 |
| Недостаточно tab facts | 865 | 34 | 54 | 0 |
Крупнейшая группа — **3 717 iOS / 911 macOS** — не является подтверждением «нормальной загрузки». Она только не содержит видимого native-признака поломки на момент снимка.
### Главные окружающие цепочки
- **Session restoration:** 952 iOS / 68 macOS init failures. На iOS у 604 controller совпадает, scripts отсутствуют, background view не создавался; 754 лежат в первых 30 секундах запуска. Это согласуется с временным startup miss, но 260 снимков выглядят здоровыми и требуют timings сообщений.
- **Обычные навигации iOS:** часты `load → view → proxy_on` и `load → view → proxy_on → dealloc → view → proxy_on`. `dealloc` означает освобождение background view, не process crash. Явный `died_*` есть лишь в 85 первичных iOS строках.
- **После extension reload:** 670 iOS / 232 macOS первых timeout. Типичный хвост содержит `load → base_url_changed → view → proxy_on`; `base_url_changed` сопутствует reload и не доказывает URL defect.
- **macOS controller/scripts mismatch:** после tag 77/1 377 init и 35/487 stuck имеют неверный controller и отсутствующие scripts; до tag таких строк нет. 45 из 77 совпали с `app_version_change=updated`, но update сам по себе не доказывает, что живая вкладка осталась на старом controller. Нужен отдельный event смены manager/controller при живой вкладке.
- **WebKit error 6:** в 364 iOS stuck снимках есть `WKWebExtensionContextErrorDomain:6`; в 363 перед ним есть `dealloc`. Это `BackgroundContentFailedToLoad`: service worker/background не удалось загрузить. Пиксель не содержит первичную причину registration/load failure.
---
## 3. Связь init failure и stuck
В коде связь прямая: первый timeout создаёт `Episode`; stuck появляется только после следующего qualifying timeout, обычно в пределах пяти минут. Причина stuck наследуется от первого failure, а stuck snapshot снимается со второй вкладки. Recorder хранит общую history background context, поэтому `stuck.bg_events` может содержать события до первого init failure.
В public-срезе сравнивались длинные цепочки (`>=6` событий) в окне пяти минут: порядок токенов и возраста событий должны были совпадать. Контроль — те же правила, но stuck timestamps сдвинуты на ±10 минут.
| Platform | Stuck | Длинная цепочка | Совпала с более ранним init | Контроль +10 / −10 min | Один кандидат |
|---|---:|---:|---:|---:|---:|
| iOS | 1 440 | 1 025 | 693 | 255 / 270 | 467 |
| macOS | 487 | 368 | 168 | 29 / 19 | 148 |
Это подтверждает, что stuck чаще продолжает предшествующий timeout-эпизод, чем случайную соседнюю цепочку. Это не соединение одного пользователя: нет reporter ID, `.daily` дедуплицирует события, короткие хвосты недостаточно уникальны.
**Важно:** один потерянный `init` сам по себе не доказывает global stuck. Для production stuck нужны два qualifying timeout в одном episode. Поэтому тест «один tab потерял init, второй сам сломался» не был честным доказательством и не получен. Проверять нужно: два timeout в одном стартовом окне → настоящий M4 recovery → третья вкладка без injection.
---
## 4. Проверка релизов и mitigation
### Session-restoration gate
M7 gate уже ждёт initial `loadAndSync` перед первой main-frame HTTP(S) navigation restored tab с bounded timeout. По public data временные session-restoration failures **не исчезли**: 952 iOS / 68 macOS. Без связки событий нельзя посчитать, какая часть затем восстановилась.
### Tab lifecycle fixes
Новые controller/scripts поля появились только в новом атрибуционном релизе, поэтому старые версии нельзя сравнивать по tab bugs напрямую. Доступен только proxy `stuck.other / init.other` на macOS:
| Версия | Proxy |
|---|---:|
| 1.208 | 39.52% |
| 1.209, содержит M1 tab identity fix | 38.81% |
| 1.210 public day | 39.81% |
Разница 1.208 → 1.209 составляет 0.71 п.п. и не доказывает эффект или его отсутствие: различаются cohort и нагрузка.
### M4 after-stuck recovery
M4 делает reload embedded extension после подтверждённого hang. После tag `recovered.after.extension.reload.daily` с `recovery_from=messaging_stuck` есть у 916 iOS и 354 macOS. Отношение всех `recovered.after` к stuck daily: 66.9% iOS и 74.3% macOS; на предыдущих версиях proxy был 3.23% / 2.70%.
Это сильный признак, что reload часто возвращает измеряемый background → native refresh. Это **не success rate** и не доказательство восстановленного CPM/initResp. После reload первый probe всё ещё timeout-ится 670 раз на iOS и 232 на macOS.
---
## 5. Другие CPM/Web Extension сигналы
После tag Web Extension продолжает работать на значимой части навигаций:
| Signal | iOS | macOS | Что доказывает |
|---|---:|---:|---|
| `autoconsent_init.daily`, `fromExtension=1` | 56 906 | 2 825 | Background получил какой-то init и отправил native pixel; не delivery `initResp` |
| `web_extension_loaded.daily` | 61 902 | 2 870 | Manager загрузил хотя бы одно extension |
| `web_extension_state_checked.daily` | 62 004 | 2 805 | Lifecycle проверил loaded contexts |
| `web_extension_embedded_not_loaded.daily` | 3 389 | 1 | Embedded extension отсутствовал в manager во время проверки |
| `web_extension_load_error.daily` | 4 | 4 | Ошибка общей загрузки extension |
Также есть extension-sourced `popup-found` и `done`, значит CPM functional path работает у части навигаций. Общие имена пикселей разделены с legacy user script: выводы допустимы только с `fromExtension=1`.
На macOS лишь один `embedded_not_loaded.daily` при 77 init failures с controller mismatch/no scripts. Это согласуется с локальной проблемой вкладки при исправном глобальном manager.
---
## 6. WebKit hypotheses
### 317981 — сообщение в cold start worker
Upstream regression test `SendMessageFromContentScriptWhileBackgroundIsLoading`:
1. Загружает MV3 extension с non-persistent background.
2. Background синхронно регистрирует `runtime.onMessage`.
3. Сразу открывает web tab без ожидания background readiness.
4. Content script сразу вызывает `runtime.sendMessage`.
5. Ожидает ответ background.
Правильное поведение — очередь до registration listener. Без фикса сообщение может быть потеряно. Это ближе всего к цепочке `scripts/context есть → init отправлен → нет response/refresh` и не требует искусственного JS timeout.
Статус: fix 317981 присутствует в public Safari/WebKit ветке `safari-7625.1.29.*`. macOS 27 VM из проверки имела WebKit bundle `22625.1.29.11.27`; точное соответствие Apple OS build и WebKit commit нельзя доказать лишь CFBundleVersion. Нужен system-framework regression run на конкретной VM, а не предположение по номеру.
### 324743 — worker получает event до activation
Upstream test выгружает worker, будит его `runtime.sendMessage` из extension tab и прямо в message listener проверяет `clients.matchAll()` без polling. Offscreen document обязан быть уже controlled client. Без фикса worker обрабатывает message, пока activation/reconnect ещё не завершились.
Это также похоже на primary telemetry: controller/view выглядят живыми, но реальная обработка message идёт в неправильный момент.
### 322881 — reload потерял extension clients
Upstream test создаёт offscreen document, проверяет его presence в `clients.matchAll()`, reload-ит background и проверяет, что новый worker вновь видит тот же client. В production relevance пока не установлена: CPM обычно работает с web tab/content script, а не обязательно с extension-owned offscreen page.
### 324754 — worker в новом process без bindings
Upstream test включает site isolation, создаёт offscreen document, reload-ит worker, проверяет смену PID и затем наличие `browser.runtime.getURL`. Без фикса JavaScript worker стартует в процессе без Web Extension API bindings.
Это хороший кандидат для проблемы после reload/recovery, но не доказанный источник current CPM data. Сначала нужен upstream-shaped reproduction с offscreen/extension page, затем full CPM scenario.
### Связь с error 6
Error 6 частично ложится на путь `refreshCpmDashboardState`: если background не загрузился, он не вызовет refresh. Но error 6 не говорит, где именно сломалось: service-worker registration, process, storage, bindings или другая внутренняя WebKit ошибка. Он не доказывает 317981, 324743, 322881 или 324754.
---
## 7. Fix в browser code
В рабочем дереве `alex/cpm-web-extension-ready-handshake` подготовлен некоммитнутый JS fix, ориентированный на потерянное/слишком раннее `init` сообщение: content side ждёт explicit readiness/ответ и повторяет handshake вместо того, чтобы считать один message окончательным. ZIP с extension — release artifact; в нормальной жизни он собирается из исходников расширения и попадает в bundle приложения на build/release pipeline, а не загружается пользователем.
Смысл фикса: не менять WebKit и не «лечить» искусственной задержкой, а сделать CPM handshake идемпотентным. Если первый init попал в cold-start race, content script повторно инициирует состояние и получает актуальный dashboard path. Это покрывает потерю первого сообщения/ранний readiness contract, но не восстановит worker без `browser.runtime` и не исправит WebKit process/client bug сам по себе.
Существующий fixture с `setTimeout` полезен как test retry protocol, но не доказывает настоящую WebKit race. Он показал важное ограничение: единичная потеря init не делает все tabs stuck автоматически.
---
## 8. План точного воспроизведения без изменения WebKit logic
### Почему не C++ `DYLD_INTERPOSE`
Нужные C++ пути выполняются внутри отдельных WebContent/Network processes и используют internal hidden symbols. Вызовы внутри WebKit dylib обычно не проходят через interposable imported symbol stubs. Инъекция dylib в Apple WebContent subprocesses ненадёжна из-за hardened runtime, SIP и XPC environment. Это не практичный первый метод.
### Рабочий harness
1. Создать Swift integration harness с Objective-C runtime swizzle и `RaceGate`; тесты запускать serially, потому что swizzle process-global.
2. Для 317981 не ставить gate: портировать exact upstream cold-start race и многократно запускать на system WebKit.
3. Для 324743 swizzle `-[WKWebExtensionContext loadBackgroundContentWithCompletionHandler:]`: сообщить completion test coordinator раньше, а оригинальный вызов удержать до explicit release. Это контролирует scheduling на публичной ObjC boundary, не меняя extension logic или C++ WebKit code.
4. Для real lifecycle использовать доступные private test SPI динамически: `_terminateServiceWorkers`, `_backgroundWebView`, `_killWebContentProcessAndResetState`, `_webProcessIdentifier`, `_setSiteIsolationEnabled:`.
5. Логировать OS build, WebKit CFBundleVersion, timestamps init/response, background PID до/после, outcome каждой итерации.
### Матрица
| Сценарий | Реальный триггер | Успех/отказ |
|---|---|---|
| 317981 | Сразу открыть tab после extension load | Каждый init получает ответ/refresh |
| 324743 | Gate вокруг background load completion, tab уже навигировался | Message не должен исполняться до activation |
| 322881 | Создать extension client, `_terminateServiceWorkers`, разбудить message | Client есть в `clients.matchAll()` |
| 324754 | Site isolation, убить/restart background, дождаться нового PID | В новом worker есть `browser.runtime.*` |
| CPM amplification | Два timeout в одном shared start window, настоящий M4 reload, затем third tab без injection | Third tab работает; иначе доказан cascade/recovery defect |
Нельзя подтверждать global stuck одним искусственно потерянным init на одной вкладке. Для честного test нужны два timeout, которые переводят health monitor в stuck, после чего injection выключается перед третьей вкладкой.
---
## 9. Какие данные нужны дальше
Приоритетная telemetry с общим, privacy-safe navigation/episode correlation ID:
1. `content_init_sent`.
2. `background_init_received` и окончание async prerequisites.
3. `initResp_sent` / `initResp_received`.
4. `refreshCpmDashboardState_sent` / `received` / URL match result.
5. worker lifecycle: start, activation, PID/process generation, background load error code, recovery generation.
Эти точки разделят пять разных случаев, которые сегодня попадают в один `init failure`: content injection miss, runtime message miss, worker activation race, delayed/mismatched dashboard refresh и native routing failure.
---
## 10. Статус на 9 октября
Подробно: [[cpm-web-extension-repro-and-fix-2026-10-08]]. Сводки на английском для Asana: [[cpm-web-extension-data-analysis-summary-2026-10-08]] и [[cpm-web-extension-repro-and-fixes-summary-2026-10-08]]. Карта кода по строкам: [[cpm-web-extension-code-guide-2026-10-09]]. **Ничего не закоммичено.**
**Воспроизведено и закрыто фиксами (worktree `alex/cpm-web-extension-ready-handshake`):**
- Harness на реальном WebKit (раздел 8): production extension, `WebExtensionManager`, coordinator + navigation gate, монитор, M4, production-recorder диагностики.
- **Одна вкладка:** потеря `init` при первой загрузке background нового идентификатора (установка или обновление), 317981. При запуске её закрывает gate coordinator'а; установку и обновление посреди сессии закрыл новый gate.
- **Полный отказ:** падение Network-процесса оставляет background живым, а worker мёртвым; ломаются все вкладки и открытые документы; M4 чинит только новые навигации. Закрыто watchdog'ом, перезапускающим только background-процесс.
- **Смерть worker'а при живом Network-процессе** из приложения не обнаружить; закрыто перезапуском background по первому сбою (стоит одну навигацию). **Риск:** при медленном пробуждении, когда процесс отвечает, перезапуск убьёт грузящийся worker.
**Сверка с данными 5 октября:**
- Network-процесс объясняет не больше 20–45% «здоровых» `stuck`.
- **Группа 1** («здоровые» без смены Network-процесса, iOS 748 / macOS 228 daily): около половины — сразу после пробуждения background после idle-выгрузки; около четверти iOS и 15% macOS — после загрузки или reload контекста.
- **Поправка:** `recovered_without_extension_reload(initialization_failed)` к stuck не относится — он закрывает только init-эпизоды. Init → recovery без reload: 23% iOS / 25% macOS устройство-дней; stuck → recovery после M4: 64% / 74%. Сбои после загрузки контекста чаще проходят сами; на пробуждении чаще доходят до stuck.
**Новые находки 8–9 октября (worktree `alex/cpm-web-extension-remaining-failures`, без фиксов):**
- **Медленный старт worker'а на пробуждении** (> 4 с) воспроизводит сигнатуру группы 1 полностью: `bg_process_responsive=true`, `context_errors=none`, хвост `dealloc → view → proxy_on@30s`. 5 с → `initialization_failed`, затем поздний ответ; 12 с + следующая навигация → `stuck` → M4, который убивает живой медленный worker. Старт до 2,5 с — потерь нет. Почему на устройствах старт бывает долгим — неизвестно.
- **Error 6 (iOS, ~1/3 строк):** причина в WebKit — 30-секундный таймер выгрузки взводится до окончания загрузки. Репро на macOS есть, на iPhone (iOS 26.7.1) не воспроизвёлся.
- **WebKit 320812 / 312231@main (26.6+):** при смене `baseURL` загрузка контекста удаляет website data старого origin, включая весь дисковый кэш, и только потом инъецирует content scripts и грузит background. У DDG `baseURL` новый на каждом запуске и reload. На Mac с ~20–24 тыс. записей кэша задержка 0,75–1,35 с. Кандидат-митигация — стабильный `baseURL` (ветка `alex/cpm-stable-base-url`).
- **Другие баги WebKit:** 324743 (worker «загружен» до завершения регистрации; исправлен 22 сен, нет в 26.7/27.0) и 314194 (Lockdown Mode блокирует все service worker'ы; открыт).
- **Сигналы готовности background:** окончание загрузки страницы background view бесполезно (0,1 с); completion `loadBackgroundContent` и первое нативное сообщение worker'а точно отмечают его старт.
- **Отброшено:** блокировка устройства / data protection, устаревший URL вкладки на iOS, мигание флага для привязки вкладок на macOS.
**Телеметрия фазы 1 (worktree `alex/cpm-messaging-readiness-telemetry`, от свежего `main`):**
- На сбоях: `bg_ready_after`, `bg_view_age`, `app_state`, `open_tabs`, `loading_tabs`, `bg_load_error_app_state`, `bg_load_error_after`.
- На восстановлениях: `recovery_document`, `recovery_after`.
- Проверено на реальном WebKit; 677 тестов пакета, SwiftLint, `validate-pixel-defs`, сборка iOS и macOS — зелёные.
**Дальше:** фаза 2 детектора (отсчёт 4 с с момента готовности background, без эскалации до неё), раннее пробуждение background, стабильный `baseURL`, флаги и A/B для фиксов, репорты в WebKit. Не воспроизведены: привязка вкладок на macOS, «нет background view», код 7, первый сбой после M4.
---
## Источники
- Public attribution: `cpm-web-extension-public-attribution-2026-10-05.md` и папка `cpm-public-attribution-2026-10-06/` в `apple-browsers.git`.
- Historical validation/methodology: `cpm-web-extension-breakage-validation.md`.
- WebKit 317981 regression: `Tools/TestWebKitAPI/Tests/WebKit/WKWebView/WKWebExtensionAPIRuntime.mm`, `SendMessageFromContentScriptWhileBackgroundIsLoading`.
- WebKit 322881/324743/324754 regressions: `Tools/TestWebKitAPI/Tests/WebKit/WKWebView/WKWebExtensionAPIOffscreen.mm`.
- Browser implementation: `CPMMessagingHealthMonitor.swift`, `CPMMessagingDiagnosticsRecorder.swift`, `WebExtensionManager.swift`, `AutoconsentWebExtensionMessageHandler.swift`.