33 KiB
Executable File
updated
| 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:
- 317981: content script отправляет
runtime.sendMessage, пока background ещё загружается; WebKit не должен терять сообщение. - 324743: worker считается загруженным и получает сообщение до завершения activation/восстановления clients.
Два других WebKit-багa могут объяснять ухудшение после перезагрузки/recovery, но пока не доказано, что они происходят в CPM:
- 322881: после reload worker теряет существующие extension clients.
- 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 принимает и сопоставляет его с текущим документом.
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:
- Загружает MV3 extension с non-persistent background.
- Background синхронно регистрирует
runtime.onMessage. - Сразу открывает web tab без ожидания background readiness.
- Content script сразу вызывает
runtime.sendMessage. - Ожидает ответ 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
- Создать Swift integration harness с Objective-C runtime swizzle и
RaceGate; тесты запускать serially, потому что swizzle process-global. - Для 317981 не ставить gate: портировать exact upstream cold-start race и многократно запускать на system WebKit.
- Для 324743 swizzle
-[WKWebExtensionContext loadBackgroundContentWithCompletionHandler:]: сообщить completion test coordinator раньше, а оригинальный вызов удержать до explicit release. Это контролирует scheduling на публичной ObjC boundary, не меняя extension logic или C++ WebKit code. - Для real lifecycle использовать доступные private test SPI динамически:
_terminateServiceWorkers,_backgroundWebView,_killWebContentProcessAndResetState,_webProcessIdentifier,_setSiteIsolationEnabled:. - Логировать 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:
content_init_sent.background_init_receivedи окончание async prerequisites.initResp_sent/initResp_received.refreshCpmDashboardState_sent/received/ URL match result.- 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. У DDGbaseURLновый на каждом запуске и 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.