diff --git a/work/projects/cpm-web-extension-breakage-session-summary-2026-10-07.md b/work/projects/cpm-web-extension-breakage-session-summary-2026-10-07.md index 8b11da12..9c0d6cd4 100644 --- a/work/projects/cpm-web-extension-breakage-session-summary-2026-10-07.md +++ b/work/projects/cpm-web-extension-breakage-session-summary-2026-10-07.md @@ -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) — в заметке по ссылке. + --- ## Источники diff --git a/work/projects/cpm-web-extension-repro-and-fix-2026-10-08.md b/work/projects/cpm-web-extension-repro-and-fix-2026-10-08.md new file mode 100644 index 00000000..ea674f03 --- /dev/null +++ b/work/projects/cpm-web-extension-repro-and-fix-2026-10-08.md @@ -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 приложения и фиксы. Актуальное — эта заметка.