119 lines
16 KiB
Markdown
Executable File
119 lines
16 KiB
Markdown
Executable File
---
|
||
title: CPM Web Extension — карта кода по worktree и строкам
|
||
date: '2026-10-09'
|
||
tags:
|
||
- work
|
||
- cpm
|
||
- webkit
|
||
---
|
||
|
||
# CPM Web Extension — карта кода по worktree и строкам
|
||
|
||
Контекст: [[cpm-web-extension-breakage-session-summary-2026-10-07]] (раздел 10), [[cpm-web-extension-repro-and-fix-2026-10-08]]. Номера строк — на 9 октября. **Ничего не закоммичено.** Корень всех worktree — `/Users/admin/DuckDuckGo/apple-browsers.git/`; пути ниже даны от корня worktree. `SP` = `SharedPackages/WebExtensions/Sources/WebExtensions`, `TP` = `SharedPackages/WebExtensions/Tests/WebExtensionsTests`.
|
||
|
||
| Worktree | Ветка, база | Что внутри |
|
||
|---|---|---|
|
||
| `alex/cpm-web-extension-ready-handshake` | та же, `a0717cf098` | Фиксы + основной harness |
|
||
| `alex/cpm-web-extension-remaining-failures` | та же, `a0717cf098` | Прод-код без фиксов, error 6 телеметрия (старая копия), репро error 6, очистки, медленного пробуждения |
|
||
| `alex/cpm-messaging-readiness-telemetry` | та же, `main` `92e09cdbbc` | Телеметрия фазы 1 (включает error 6) — кандидат на первый PR |
|
||
| `cpm-stable-base-url` | `alex/cpm-stable-base-url`, старая база `570c2f8ef4` | Эксперимент со стабильным `baseURL` |
|
||
|
||
## 1. Фиксы — `alex/cpm-web-extension-ready-handshake`
|
||
|
||
Цепочка: навигация → coordinator спрашивает менеджер → менеджер проверяет watchdog и незавершённый первый старт background → при сбое монитор зовёт перезапуск.
|
||
|
||
1. `SP/Lifecycle/WebExtensionLifecycleCoordinator.swift:79-93` — `initialLoadWaiter`, его вызывают обе платформы на каждой main-frame навигации. Было: после открытия стартового gate → `nil`. Стало: `:89` зовёт `manager.prepareEmbeddedBackgroundForNavigation()` и ждёт его не дольше `initialLoadTimeout` (5 с, `:91-92`).
|
||
2. `SP/Manager/WebExtensionManager+EmbeddedBackground.swift` (новый):
|
||
- `:35` `prepareEmbeddedBackgroundForNavigation()` — сначала `watchdog.restartBackgroundIfStale()` (`:36`), потом возвращает waiter, если идёт первый старт background только что установленного extension.
|
||
- `:45` `trackEmbeddedBackgroundStartup(identifier:)` — запускает `context.loadBackgroundContent()` (`:49`) как `Task` и держит его в `embeddedBackgroundStartup`, пока не завершится. Зачем: WebKit при первой загрузке нового идентификатора грузит background сразу и теряет `init`, пришедший до выполнения скрипта (317981).
|
||
3. `SP/Manager/WebExtensionManager+EmbeddedExtensions.swift:178-180` — после `installationStore.add` для `.embedded` вызывается `trackEmbeddedBackgroundStartup`. Покрывает установку и обновление посреди сессии (на запуске это уже делал стартовый gate).
|
||
4. `SP/Manager/WebExtensionManager.swift`:
|
||
- `:134-139` — свойства `embeddedBackgroundWatchdog` и `embeddedBackgroundStartup`.
|
||
- `:167` — параметр init `isEmbeddedBackgroundRecoveryEnabled` (по умолчанию `{ true }`, к удалённому флагу не подключён).
|
||
- `:186` — создание watchdog.
|
||
- `:217-221` — `healthMonitor.onEpisodeStarted` → `restartBackgroundIfResponsive(reason:)`: перезапуск по первому сбою.
|
||
- `:757` — в делегате `didCreateBackgroundWebView` watchdog получает новую background view.
|
||
5. `SP/Lifecycle/EmbeddedBackgroundLivenessWatchdog.swift` (новый):
|
||
- `:63` init — все зависимости подменяемые (PID, перезапуск, проба отзывчивости).
|
||
- `:76` `backgroundWebViewDidChange` — запоминает view и PID Network-процесса, запускает периодическую проверку.
|
||
- `:85` `restartBackgroundIfStale` — PID Network-процесса сменился (или исчез) → перезапуск. Это и есть детектор «Network-процесс упал, worker мёртв».
|
||
- `:107` `restartBackgroundIfResponsive` — перезапуск по сбою, только если процесс ответил на `return 1` за 1 с (`:166-170`). Замороженный не трогаем: убийство остановленного процесса давало глухой новый worker.
|
||
- `:137` периодическая проверка раз в 1 с; `:156` `_networkProcessIdentifier` (SPI через KVC); `:173-180` перезапуск через `_killWebContentProcessAndResetState` — WebKit поднимает новый worker, и чинятся даже уже открытые документы.
|
||
6. `SP/Autoconsent/CPMMessagingHealthMonitor.swift:96-100` — колбэк `onEpisodeStarted`; `:656-658` — вызывается на первом сбое нового эпизода, только для `failureReason == .other` (не session restoration).
|
||
7. `SP/Manager/WebExtensionManaging.swift:164-168` — требование протокола; `:176-178` — реализация по умолчанию `{ nil }`, чтобы моки и платформы не ломались.
|
||
|
||
**Тесты:**
|
||
- `TP/EmbeddedBackgroundLivenessWatchdogTests.swift` — 11 unit-тестов (`:48`–`:150`): смена PID, исчезновение Network-процесса, отзывчивый и неотзывчивый процесс, выключатель, `stop()`, периодическая проверка.
|
||
- `TP/WebExtensionLifecycleCoordinatorTests.swift:193, :217, :227, :243` — навигация ждёт первый старт; не ждёт, если готов; ожидание ограничено 5 с; при выключенном gate ничего не готовится. Мок — `:667`.
|
||
- `TP/CPMMessagingHealthMonitorTests.swift:50, :61, :71` — `onEpisodeStarted` на первом сбое; не на session restoration; снова для нового эпизода.
|
||
- **Harness** `TP/CPMRepro/` (запуск: `CPM_REPRO=1 swift test --filter CPMRepro` из `SharedPackages/WebExtensions`):
|
||
- `CPMReproSingleTabTests.swift` — 10 тестов одной вкладки: холодный старт `:32`, обновление на запуске `:46`, **установка/обновление посреди сессии `:64`, `:87`** (до фикса терялись), перезапуск `:111`, `:128`, после reload `:146`, `:165`, пробуждение `:187`, падение background `:206`.
|
||
- `CPMReproFullFailureTests.swift` — краш Network-процесса `:33`, `:38`, `:55` (без M4); смерть worker'а через SPI `:76` и удаление регистраций `:85` (стоит одну навигацию); не-триггеры `:124`, `:129`, `:136`.
|
||
- `CPMReproWebKitTests.swift` — 7 тестов, нужен `CPM_REPRO_WEBKIT=1`: защиты выключены, утверждают, что баги WebKit ещё есть (`:45` потеря `init` при первой загрузке, `:80`/`:87`/`:151` мёртвый worker, `:97` только перезапуск background чинит открытые документы, `:133` сообщение из страницы background не доходит до worker'а).
|
||
- `CPMReproWakeExperimentTests.swift:27-28` — **эксперимент, не для коммита**: медленное пробуждение 5 и 12 с с фиксами. Для него в `CPMReproHarness.swift` свойство `storage` сделано не `private`.
|
||
|
||
## 2. Расследование без фиксов — `alex/cpm-web-extension-remaining-failures`
|
||
|
||
Прод-код как в релизе; harness перенесён без watchdog, с production-recorder'ом, поэтому сбои дают те же параметры пикселей, что в проде.
|
||
|
||
- `TP/CPMRepro/CPMReproHarness.swift`: `:51` recorder диагностики; `:191` `contextErrorCodes`; `:196` папка установленного extension; `:203` `prependToBackgroundScript` — дописывает код в начало background-скрипта на диске (WebKit читает его заново на каждом пробуждении); `:212` PID Network-процесса; `:262` `loadRealPage` (для наполнения кэша).
|
||
- `TP/CPMRepro/CPMReproBackgroundLoadFailureTests.swift` — **error 6**, 9 тестов, все с проверками:
|
||
- `:59` новый процесс background заморожен на 45 с при пробуждении → только `:6`, `bg_load_error_after=25s_to_35s`. Это таймер выгрузки WebKit.
|
||
- `:70` 10 с → error 6 нет.
|
||
- `:79`, `:89`, `:96` — другие пути к error 6 (нет файлов, скрипт бросает, нет `browser`) — быстрые, `under_5s`; в проде их сигнатур нет.
|
||
- `:104`, `:111`, `:118`, `:125` — условия, которые error 6 не дают.
|
||
- `TP/CPMRepro/CPMReproExtensionLoadCleanupTests.swift:49, :69` — задержка очистки при смене `baseURL` на перезапуске и reload. Нужен локальный сервер: `CPM_REPRO_CACHE_URL`, `CPM_REPRO_CACHE_ENTRIES` (наполнение ~10 мин).
|
||
- `TP/CPMRepro/CPMReproWakeExperimentTests.swift` — **эксперимент, не для коммита**:
|
||
- `:27` старт worker'а 5 с на пробуждении → `initialization_failed` с сигнатурой группы 1, затем поздний ответ.
|
||
- `:28` 12 с + навигация → `stuck` → M4.
|
||
- `:31` сигналы готовности: `isLoading=false` через 0,1 с (бесполезен), completion `loadBackgroundContent` и первое нативное сообщение — ровно на старте worker'а.
|
||
- Продовые изменения здесь (`CPMMessagingDiagnostics*`, JSON5, `WebExtensionManager+iOS.swift`, `AppDelegate.swift`) — **старая копия телеметрии error 6**, она целиком перенесена в worktree телеметрии. Отсюда не коммитить.
|
||
|
||
## 3. Телеметрия фазы 1 — `alex/cpm-messaging-readiness-telemetry`
|
||
|
||
Только параметры пикселей, поведение детектора не меняется.
|
||
|
||
1. `SP/Autoconsent/CPMMessagingDiagnostics.swift`:
|
||
- `:29` `ApplicationState`, `:38` провайдер состояния приложения в `CPMAppSessionDiagnostics`.
|
||
- `:50` `CPMBackgroundReadiness`: `noBackground` / `pending` / `loaded(seconds)` / `failed`.
|
||
- `:109-123` новые поля снимка (error 6, `applicationState`, возраст view, готовность, вкладки).
|
||
- `:182-188` имена параметров (`bg_load_error_*`, `app_state`, `bg_view_age`, `bg_ready_after`, `open_tabs`, `loading_tabs`).
|
||
- `:217-231` сериализация: error 6 — `none`, если ошибки не было, и опускается, если неизвестно; остальные опускаются при `nil`.
|
||
- Бакеты: `:248` error 6 (вокруг таймера 30 с), `:258` возраст view, `:269` готовность (вокруг 4 с монитора и 30 с WebKit), `:285` число вкладок.
|
||
2. `SP/Autoconsent/CPMBackgroundReadinessObserver.swift` (новый) — откуда берётся `bg_ready_after`:
|
||
- `:52` `observe` на создании view: ставит `pending` и на **следующем шаге run loop** (`:56`) зовёт `loadBackgroundContent`. View уже есть, поэтому вызов лишь присоединяется к идущей загрузке и ничего не будит.
|
||
- completion → `loaded(секунды от создания view)` или `failed`; ответ для заменённой view игнорируется.
|
||
- `:34` загрузчик подменяется в тестах; `:72` `readiness(of:)` — только для текущей view.
|
||
3. `SP/Autoconsent/CPMMessagingDiagnosticsRecorder.swift`:
|
||
- `:125`, `:141` наблюдатель; `:223` сброс при новом контексте.
|
||
- `:311-316` наблюдение включается тем же флагом, что и delegate proxy (`cpmBackgroundDelegateProxy`).
|
||
- `:413-415` поля error 6; `:426` состояние приложения; `:430-431` возраст view и готовность; `:435-438` `open_tabs` / `loading_tabs` из `context.openTabs` и `webView(for:).isLoading`.
|
||
- `:503-517` `recordContextErrors` — при error 6 фиксирует состояние приложения и секунды от создания view.
|
||
4. `SP/Pixels/WebExtensionPixelFiring.swift`:
|
||
- `:71`, `:73` — у recovery-событий появился `details` (по умолчанию `nil`, старые вызовы не ломаются).
|
||
- `:85` `CPMMessagingRecoveryDetails`: `failed_navigation` (опоздавший ответ) / `later_navigation` / `unattributed` + `:108` бакеты `recovery_after`.
|
||
- `:181-188` параметры пикселя.
|
||
5. `SP/Autoconsent/CPMMessagingHealthMonitor.swift`:
|
||
- `:451-452` неатрибутированный ответ закрывает stuck → `unattributed`.
|
||
- `:712-716` обычное восстановление: тот же упавший замер → `failed_navigation`, иначе `later_navigation`.
|
||
- `:722` `recoveryDetails` — секунды от первого сбоя эпизода.
|
||
6. Приложения: `iOS/DuckDuckGo/WebExtensions/WebExtensionManager+iOS.swift:107-114` (`UIApplication.applicationState`), `macOS/DuckDuckGo/Application/AppDelegate.swift:122-123` (`NSApp.isActive`).
|
||
7. JSON5:
|
||
- словари `iOS/PixelDefinitions/pixels/params_dictionary.json5` (`:602` recovery, `:619` error 6, `:643` готовность и вкладки) и `macOS/.../params_dictionary.json5` (`:677`, `:694`, `:718`);
|
||
- определения — iOS `debug_pixels.json5`, macOS `web_extensions.json5`: по 8 пикселям сбоя и 2 пикселям восстановления.
|
||
|
||
**Тесты:**
|
||
- `TP/CPMMessagingDiagnosticsTests.swift`:
|
||
- бакеты `:135`, `:144`, `:149`; полный набор параметров `:154`;
|
||
- error 6 `:279`, `:417`;
|
||
- готовность: загрузилась `:304`, упала `:327`, заменённая view `:343`, наблюдение выключено `:360`;
|
||
- состояние приложения `:377`; вкладки на реальном контроллере с незавершающейся загрузкой `:384`.
|
||
- `TP/CPMMessagingHealthMonitorTests.swift`: опоздавший ответ `:1061`, следующая навигация `:1081`, `unattributed` `:1095`, бакеты `:1112`, обновлённый тест параметров `:1119`.
|
||
- Проверка на реальном WebKit (harness временно копировался): медленное пробуждение 5 с → `bg_ready_after=pending`, `bg_view_age=4s_to_8s`, затем `recovery_document=failed_navigation`, `recovery_after=under_4s`.
|
||
|
||
## 4. Стабильный `baseURL` — `cpm-stable-base-url`
|
||
|
||
- `SP/Loading/WebExtensionLoader.swift:56`, `:93`, `:134`, `:154`, `:158-165` — в `makeContext` выставляется `context.baseURL = webkit-extension://<uniqueIdentifier>/`; если из идентификатора не собрать URL — ошибка `invalidIdentifier`.
|
||
- `TP/WebExtensionLoaderDelegateTests.swift:61-94` — 2 теста: URL выставлен до загрузки; неверный идентификатор → загрузка не начинается.
|
||
- Открыто: сейчас меняет URL **всем** extension (нужно — только встроенным без `web_accessible_resources`); не прогнан через harness (M4, краш Network-процесса, обновление); старая база.
|