[2026-10-08] eagle: work/projects/cpm-web-extension-breakage-session-summary-2026-10-07.md work/projects/cpm-web-extension-repro-and-fix-2026-10-08.md

This commit is contained in:
Alexey Martemyanov committed 2026-10-08 12:19:19 +06:00
1 parent 49589b9caa
commit 03f7bde748
2 files changed
+100

No files matched your search

@@ -246,6 +246,19 @@ Error 6 частично ложится на путь `refreshCpmDashboardState`
Эти точки разделят пять разных случаев, которые сегодня попадают в один `init failure`: content injection miss, runtime message miss, worker activation race, delayed/mismatched dashboard refresh и native routing failure.
---
## 10. Статус на 8 октября
Подробно: [[cpm-web-extension-repro-and-fix-2026-10-08]].
- Сделан harness на реальном WebKit (раздел 8): production extension, `WebExtensionManager`, coordinator + navigation gate, монитор и M4 в настоящих `WKWebView`.
- **Одна вкладка:** потеря `init` при первой загрузке background нового идентификатора (установка или обновление) воспроизведена, это 317981. При запуске её уже закрывает gate coordinator'а; открытыми оставались установка или обновление посреди сессии — закрыто.
- **Полный отказ:** падение Network-процесса оставляет background живым, а worker мёртвым; ломаются все вкладки и уже открытые документы; M4 чинит только новые навигации. Закрыто watchdog'ом, который перезапускает только background-процесс.
- **Смерть worker'а при живом Network-процессе** (SPI или удаление SW-регистраций) из приложения не обнаружить; закрыто перезапуском background по первому сбою (стоит одну навигацию).
- Фикс не закоммичен; открытые вопросы (feature flag, телеметрия, SPI, сборка приложений, ping в extension) — в заметке по ссылке.
---
## Источники
@@ -0,0 +1,87 @@
---
title: CPM Web Extension — воспроизведение и фикс
date: '2026-10-08'
status: fix-uncommitted
tags:
- work
- cpm
- webkit
---
# CPM Web Extension — воспроизведение и фикс
Продолжение [[cpm-web-extension-breakage-session-summary-2026-10-07]] (§8 «План точного воспроизведения»).
Среда: macOS 26.7.1 (25G241), system WebKit, production embedded extension 2026.9.29.
Код: worktree `alex/cpm-web-extension-ready-handshake`, пакет `SharedPackages/WebExtensions`. **Ничего не закоммичено.**
## Статус
- Harness на реальном WebKit готов: реальные `WebExtensionManager`, `WebExtensionLifecycleCoordinator` + navigation gate, `CPMMessagingHealthMonitor` (4 с), M4, production extension без изменений логики.
- Оба класса отказа воспроизведены, причины найдены, фикс на стороне app написан, тесты зелёные.
- Весь пакет: 490 тестов, 0 падений (repro-тесты без env пропускаются). SwiftLint `--strict` чистый.
## Что нашли
### Отказ одной вкладки — потеря `init` при первой загрузке background
- WebKit при *первой* загрузке extension с новым идентификатором (первая установка или обновление — апгрейд ставит новый UUID) грузит background сразу. `runtime.sendMessage`, пришедший до выполнения background-скрипта, резолвится `undefined`, listener не вызывается.
- Без gate: 14/20 при первой установке, 18/20 при обновлении; с busy-wait 1 с в скрипте — 3/3 детерминированно.
- При обычном перезапуске (extension уже установлен), после reload (M4) и при пробуждении после idle WebKit ставит сообщения в очередь — потерь нет.
- **Поправка к первому отчёту:** при запуске это уже закрывает gate в `WebExtensionLifecycleCoordinator` (флаг `webExtensionStateRestorationGate`). Дыры были: установка или обновление посреди сессии (`sync()`, отложенная установка на iOS до protected data), таймаут gate 5 с.
### Полный отказ — мёртвый worker при живом background
- **Краш Network-процесса** (`SIGKILL`, аналог jetsam на iOS): background view и его PID прежние, WebKit считает background загруженным, worker мёртв. Теряются сообщения из всех вкладок и уже открытых документов. Монитор: init failed → stuck → M4. После M4 новые навигации работают, **документы, открытые до сбоя, сломаны навсегда**. Без M4 держится, пока пользователь активен; снимается после ~30 с idle-выгрузки. 20/20.
- **Смерть worker'а при живом Network-процессе:** `_terminateServiceWorkers` (SPI) или удаление service worker registrations из data store (то, что делает очистка данных). Состояние то же.
- Fire в обоих приложениях выгружает extensions на время очистки и загружает потом, поэтому сам Fire сюда не приводит. Других путей в проде, которые убивают только worker, не нашёл.
- **Не триггеры:** `org.WebKit.lowMemory`, заморозка background на 10–75 с, kill background-процесса (WebKit перезапускает сам).
- Если background занят или заморожен дольше ~30 с, WebKit выгружает его, и сообщение «в полёте» теряется навсегда (одна вкладка, не каскад).
### Восстановление
- Убить **только background WebContent-процесс** (`_killWebContentProcessAndResetState`) — WebKit поднимет новый worker на следующем сообщении; новые навигации **и уже открытые документы** работают.
- Reload extension (M4) чинит только новые навигации.
- `loadBackgroundContent()` ничего не даёт: WebKit считает background загруженным.
### Почему мёртвый worker не обнаружить из приложения
- Регистрация SW остаётся `activated`.
- `runtime.sendMessage` из page world background web view до worker'а не доходит (WebKit считает страницу самим background-контекстом) — и у живого, и у мёртвого worker'а сразу `undefined`.
- Background CPM слушает только `onMessage`, `onConnect` (порт privacy-dashboard) и `onAlarm` — приложение не может их вызвать.
## Фикс (app, пакет WebExtensions)
1. **Gate для первой загрузки посреди сессии.** После `installEmbeddedExtension` менеджер запоминает задачу `loadBackgroundContent()` нового контекста; `coordinator.initialLoadWaiter` (его уже вызывают обе платформы на каждой main-frame навигации) после открытия стартового gate отдаёт этот waiter, не дольше 5 с.
2. **`EmbeddedBackgroundLivenessWatchdog`.** Запоминает PID Network-процесса background'а; при смене перезапускает background-процесс. Проверка на каждой навигации и раз в 1 с.
3. **Перезапуск по первому сбою.** Монитор вызывает `onEpisodeStarted` на первом сбое обычной навигации; менеджер перезапускает background, **если процесс отвечает** (`callAsyncJavaScript("return 1")`, 1 с). Замороженный или занятый не трогаем: убийство остановленного процесса давало глухой новый worker.
4. Пункты 2 и 3 включаются одним переключателем `isEmbeddedBackgroundRecoveryEnabled` (по умолчанию `true`).
| Сценарий | Без фикса | С фиксом |
|---|---|---|
| Установка посреди сессии | 3/3 потерь | 5/5 доставлено |
| Обновление посреди сессии | 5/5 потерь | 5/5 доставлено |
| Краш Network-процесса, 4 вкладки + открытые документы | все сломаны | всё работает |
| Краш Network-процесса без M4 | не восстанавливается | работает |
| Смерть worker'а (SPI / удаление регистраций) | 2 таймаута → M4, старые документы сломаны | 1 потерянная навигация, затем всё работает без reload |
## Тесты
Папка `Tests/WebExtensionsTests/CPMRepro/`, запуск из `SharedPackages/WebExtensions`:
- `CPM_REPRO=1 swift test --filter CPMRepro` — путь приложения: `CPMReproSingleTabTests` (10), `CPMReproFullFailureTests` (8). Должны быть зелёными.
- `CPM_REPRO=1 CPM_REPRO_WEBKIT=1` — ещё `CPMReproWebKitTests` (7): защиты выключены, тесты утверждают, что баги WebKit всё ещё есть. Покраснеют, когда Apple исправит — сигнал убрать обходы.
- Ручки: `CPM_REPRO_ITERATIONS`, `CPM_REPRO_FREEZE`, `CPM_REPRO_INSTALL_DIR` (настоящий перезапуск процесса: запустить дважды).
- Unit-тесты без env: `EmbeddedBackgroundLivenessWatchdogTests` (11), 4 новых в `WebExtensionLifecycleCoordinatorTests`, 3 новых в `CPMMessagingHealthMonitorTests`.
## Открытые вопросы — решить до коммита
- [ ] **Feature flag.** Watchdog и перезапуск по первому сбою сейчас включены по умолчанию (`isEmbeddedBackgroundRecoveryEnabled = { true }`), в приложениях не подключены к удалённому флагу. Завести флаг (`ddg-add-feature-flag`), желательно раздельно для watchdog и для перезапуска по сбою.
- [ ] **Риск перезапуска по первому сбою.** Первых сбоев много (~6.6k установок/день на iOS); часть — не мёртвый worker (нет инъекции, медленный native). Перезапуск дешевле M4, но рвёт незавершённую работу background для других вкладок. Решить: включать сразу или через эксперимент.
- [ ] **Телеметрия.** Восстановление после перезапуска сейчас попадает в `recovered_without_extension_reload` и неотличимо. Нужен пиксель или параметр «background restarted» с причиной (network process replaced / first failure) — `ddg-add-pixel`.
- [ ] **SPI.** Перезапуск через `_killWebContentProcessAndResetState`, детекция через `_networkProcessIdentifier`; есть `responds(to:)`. Согласовать с командой (приложение уже использует SPI WebKit в этом месте).
- [ ] **Сборка приложений.** iOS и macOS не собирал; изменения обратно совместимы (дефолтная реализация нового метода протокола, дефолтный параметр init). Проверить сборку и прогнать на iOS Simulator.
- [ ] **Zero-loss для смерти worker'а.** Нужен ping-канал в extension (native port с heartbeat или обработчик ping) — задача в репозитории extension.
- [ ] **Не воспроизведено:** продовое «первый probe после M4 падает» (670 iOS / 232 macOS). В тестах после M4 навигации проходят.
- [ ] **WebKit-баги для репорта:** (1) потеря сообщения при eager-загрузке background первой загрузки; (2) после краша Network-процесса или удаления регистраций `WebExtensionContext` не замечает мёртвый worker.
- [ ] **Прогнать на macOS 27 VM** — фикс 317981 может уже стоять; тогда `testWebKitDropsInitDuringFirstBackgroundLoad` покраснеет.
- [ ] Отчёт `cpm-web-extension-repro-results-2026-10-07.md` в `apple-browsers.git` устарел: он не учитывает gate приложения и фиксы. Актуальное — эта заметка.