Files
obsidian-vault/work/projects/cpm-web-extension-code-guide-2026-10-09.md
T
2026-10-15 09:50:58 +00:00

16 KiB
Executable File
Raw Blame History

title, date, tags
title date tags
CPM Web Extension — карта кода по worktree и строкам 2026-10-09
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-процесса, обновление); старая база.