242 lines
40 KiB
Markdown
Executable File
242 lines
40 KiB
Markdown
Executable File
---
|
||
title: 'CPM Web Extension — единый статус (PR, данные, гипотезы, телеметрия)'
|
||
date: '2026-10-15'
|
||
tags:
|
||
- work
|
||
- cpm
|
||
- status
|
||
---
|
||
|
||
# CPM Web Extension: единый статус на 15 октября 2026
|
||
|
||
Внутренний документ. **Цифры из ClickHouse, в публичные PR, комментарии и issues не переносить** (репозитории `duckduckgo/apple-browsers` и `duckduckgo/duckduckgo-privacy-extension` публичные). Заменяет заметку `cpm-telemetry-data-requirements-2026-10-15` (теперь ссылка сюда). Старые документы с устаревшими числами: [[cpm-web-extension-public-attribution-2026-10-05]], [[cpm-web-extension-data-analysis-summary-2026-10-08]], [[cpm-web-extension-repro-and-fix-2026-10-08]], [[cpm-web-extension-code-guide-2026-10-09]] (карта «большого» worktree, **больше не актуальна**: всё нарезано на PR ниже).
|
||
|
||
## 1. Коротко
|
||
|
||
- Фикс **B** (падение Network-процесса) проверен локально и красным прогоном на CI. **Основание в данных есть, но неполное:** обогащение в stuck есть, а доказать, что строки это именно мёртвый worker, нельзя (не хватает `network_replaced_since_view`).
|
||
- Фикс **A** (первая загрузка посреди сессии) реален, но мелкий: около 1% stuck на iOS, на macOS почти не виден, чаще самоизлечивается.
|
||
- Три самые крупные группы сбоев фиксами A и B **не закрыты**: пробуждение после idle, error 6 (iOS), «нет background view» на запуске (iOS). Механизмы воспроизведены локально на macOS, но проверенного рычага в приложении нет.
|
||
- Телеметрия PR0 пока в виде, вызвавшем вопросы о склейке пикселей. Решение по правкам не принято (раздел 8).
|
||
|
||
## 2. Цепочки PR (все в `duckduckgo/apple-browsers`, задачи Asana в телах PR; не читать)
|
||
|
||
**Телеметрия:**
|
||
|
||
| PR | Ветка | База | Что | Статус |
|
||
|---|---|---|---|---|
|
||
| #7376 (PR0) | `alex/cpm-messaging-readiness-telemetry` | main | параметры готовности и тайминга на failure/recovery пикселях (без новых пикселей) | CI зелёный, нужен ревью. Ревьюер по пикселям задал вопросы JA1–JA3 о склейке, ответ не отправлен |
|
||
| #7384 (0b) | `alex/cpm-readiness-baseline` | #7376 | ежедневные базлайн-пиксели `cpm_background_ready_{context_load,wake}`, нагрузка устройства, возраст возобновления | CI в основном зелёный |
|
||
| #7385 (0c) | `alex/cpm-init-progress-native` | #7384 | `init_stage` на failure-пикселях (нужен JS из privacy-extension#3727) | CI в основном зелёный |
|
||
| privacy-extension #3727 | `alex/cpm-init-progress` | | уведомления `cpmInitProgress` из встроенного расширения | открыт, нужен ревью |
|
||
|
||
**Harness и фиксы** (красный коммит, затем зелёный; не ребейзить и не форс-пушить, иначе пропадёт доказательство красного прогона):
|
||
|
||
| PR | Ветка | База | Что | Статус |
|
||
|---|---|---|---|---|
|
||
| #7378 | `alex/cpm-repro-harness` | main | harness (только тесты); 4 быстрых теста в CI около 3 с, остальное локально через `CPM_REPRO=1` | CI зелёный, нужен ревью. Danger: 1570 добавленных строк (предупреждение). В ветке есть коммит другой сессии «Require initResp in the page…» |
|
||
| #7379 | `alex/cpm-fix-init-loss` | harness | фикс A: ожидание первой загрузки background после установки/апгрейда посреди сессии | красный прогон и зелёный: CI зелёный |
|
||
| #7381 | `alex/cpm-fix-network-crash` | фикс A | фикс B: перезапуск background при замене Network-процесса и мёртвом worker'е | **красный прогон подтверждён** (падает `testNetworkProcessCrashKeepsEveryTabWorking`, 3 ретрая, Non-Sandbox). Фикс-коммит `f1085846e7` **локально, не запушен** |
|
||
| (нет PR) | `alex/cpm-fix-pixels` | фикс B + PR0 | пиксели `background_restart` и `first_load_wait` для измерения эффекта | держим до слияния PR0, A, B |
|
||
|
||
Локальные проверки фикса A: на коде без фикса оба repro-теста красные (5 из 5 итераций), с фиксом 0 из 10 потерь, 761 тест пакета зелёный. Фикс B: на `main` красные тесты падения Network-процесса и смерти worker'а, 5 из 5 итераций.
|
||
|
||
Недоделано в harness и CI: CI гоняет тесты с `-test-iterations 3 -retry-tests-on-failure`, красный тест должен падать стабильно; тесты WebExtensions идут внутри схем `macOS Browser` и `iOS Browser` на `osx-tahoe-27`. Медленные эксперименты (заморозка 45 с, заполнение кэша около 10 минут, медленный старт worker'а) локальные и не для коммита.
|
||
|
||
## 3. Данные (ClickHouse, 12–14 октября, версии iOS 7.240+ и macOS 1.210+)
|
||
|
||
Единица: `daily`-пиксель = устройство-день на причину, не пользователь и не эпизод. `initialization_failed` только `.daily`; `stuck`, `recovered_*`, `reload_failed`: `daily`+`count`. Параметры снимка есть только на init_failed и stuck.
|
||
|
||
| | iOS | macOS |
|
||
|---|---|---|
|
||
| `initialization_failed.daily` | 3 273 025 | 119 950 |
|
||
| `stuck.daily` | 511 660 | 40 842 |
|
||
| init восстановились без reload | 1 135 477 (34.7%) | 30 738 (25.6%) |
|
||
| stuck → восстановились после M4 reload | 293 557 (57.4%) | 21 865 (53.5%) |
|
||
| `reload_failed.daily` | 140 921 (27.5% от stuck) | 17 796 (43.6%) |
|
||
|
||
**Когорты** (по приоритету: error 6 → нет view → вкладка не подключена → Network заменён → пробуждение (view <30 с после `dealloc`) → недавняя загрузка (`load`/`base_url_changed` ≤2 мин) → other). Доля init → stuck:
|
||
|
||
| Когорта | iOS | macOS | Смысл роста или падения |
|
||
|---|---|---|---|
|
||
| error 6 | 6.1% → 21.5% | 1.0% → 1.9% | остаётся сломанной |
|
||
| нет background view | 48.1% → 10.9% | 3.3% → 1.8% | самоизлечивается |
|
||
| вкладка не подключена | 0.03% → 0.05% | 6.2% → 8.9% | остаётся сломанной |
|
||
| Network заменён | 16.8% → 39.7% | 13.6% → 26.6% | остаётся сломанной |
|
||
| пробуждение | 7.9% → 9.5% | 32.5% → 32.2% | без изменений |
|
||
| недавняя загрузка контекста | 15.7% → 6.0% | 20.0% → 6.4% | самоизлечивается |
|
||
| other | 5.3% → 12.5% | 23.4% → 22.2% | |
|
||
|
||
Объёмы `stuck.count` в разных версиях на порядки выше `daily`: дашборды сравнивать только по версии и частоте.
|
||
|
||
## 4. Основание фиксов
|
||
|
||
- **B:** доля «Network заменён» растёт в 2–2.4× при переходе init → stuck. `reload_failed` высок: reload расширения мёртвый worker не лечит. **Оговорка:** `network_restarted` сравнивает PID с загрузкой *контекста*, а не с последним view. Если view пересоздан после смены процесса, worker свежий и механизм неприменим. По заметке 8 октября механизм объясняет в лучшем случае 20–45% «здоровых» stuck. Причинность подтвердят `network_replaced_since_view` (нет ни в одной ветке) и `init_stage=none` (#7385).
|
||
- **A:** сигнатура «загрузка контекста ≤2 мин, не при запуске, первый view»: iOS 129k init (3.9%) → 4.6k stuck (0.9%); macOS 505 → 118. Разовая потеря одной навигации (0.23× при переходе в stuck). Сигнатура неточна (захватывает M4-reload). Название связанной Asana-задачи («init loss on background unload») относится скорее к группе пробуждения, чем к фиксу A; несоответствие надо снять с автором задачи.
|
||
|
||
## 5. Картины событий (stuck.daily, 14 октября, хвост `bg_events` без возрастов) и воспроизведение
|
||
|
||
| Картина | Хвост | iOS / macOS | Воспроизведено? | Рычаг в приложении |
|
||
|---|---|---|---|---|
|
||
| **P1. Цикл пробуждения** | `view>proxy_on>dealloc>view>proxy_on` | 16.0k / 4.3k (wake) + 16.3k / 2.4k (старше 30 с) | только с искусственно занятым worker'ом (5 с: init_failed и поздний ответ; 12 с: stuck → M4); органическое пробуждение в тестах проходит. WebKit-баг 324743 (исправлен 22 сентября, нет в 26.7/27.0) | гипотезы: отсчёт 4 с от готовности background («фаза 2 детектора»), ожидание готовности при пробуждении, предпрогрев |
|
||
| **P2. «Пробуждение → error 6»**, циклы | `proxy_on>dealloc>view>proxy_on>error_E:6` (23.6k), `error>dealloc>view>proxy_on>error` (5.7k), после `died_crash` (1.5k) | iOS; macOS 118 | macOS: заморозка нового процесса на ≥30 с даёт error 6, корзина 25–35 с. iPhone: 4 сценария безуспешно. Главный iOS-хвост **без** `unresponsive` (в воспроизведении он есть) | корень в WebKit (`wakeUpBackgroundContentIfNecessary` взводит 30-секундный таймер до загрузки worker'а). Гипотеза: перезагрузка расширения при возврате на передний план |
|
||
| **P3. Контекст загружен, view нет** | `load` / `load>base_url_changed` | 17.1k + 0.9k / 162 + 81 | задержка очистки при смене `baseURL` воспроизведена на macOS при большом кэше (около 10 минут заполнения); iOS-гонка запуска (`bg_view_creations=0`, восстановление сессии, первые 30 с) не воспроизведена: холодный старт в harness'е отвечает за миллисекунды | кандидат: стабильный `baseURL` (ветка `cpm-stable-base-url`: не доведён, меняет URL всем расширениям, не прогнан через harness, старая база). Фикс WebKit 318756@main, номер сборки ОС не проверен |
|
||
| **P4. Первая загрузка** | `load>view>proxy_on` | 6.8k + 2.3k / 570 | да (фикс A) | фикс A |
|
||
| **P5. Фон выгружен в момент сбоя** | `proxy_on>dealloc>view>proxy_on>dealloc` | 1.9k / 494 | да для test-only потери регистрации живой вкладки; другие гипотезы проверяются (§10) | кандидат для tab_in_context=false: восстановить корректный tab lifecycle; естественный вход не доказан |
|
||
| **P6. macOS: вкладка без расширения** | `load>view>proxy_on>dealloc` / `load>view>proxy_on`; варианты с `extension_updated` отдельно | iOS ≈0 / 627 + 235 | до idle воспроизведено отсутствием controller; полный прогон см. §10 | гипотеза: view создана без текущего controller или сохраняет controller прежнего менеджера; естественный вход не найден |
|
||
| **Network заменён** | | см. раздел 4 | да (краш, worker мёртв при том же view) | фикс B |
|
||
| **«Здоровый» остаток, other** | | iOS 12.5%, macOS 22% | нет | нет гипотезы |
|
||
|
||
Размер группы не равен возможности её починить: P1 и P3 в основном опаздывающие ответы, а часть «сбоев» это срабатывание детектора («нет ответа за 4 с», опоздавший ответ тоже считается). iOS-когорта «other» на самом деле тот же рисунок P1, но со view старше 30 секунд.
|
||
|
||
## 6. Телеметрия
|
||
|
||
**Что делает PR0 (#7376):** на 8 failure-пикселей и 2 recovery-пикселя добавлены параметры `bg_ready_after`, `bg_view_age`, `bg_load_error_after`, `bg_load_error_app_state`, `cpm_app_state`, `cpm_open_tabs`, `cpm_loading_tabs` (failure) и `cpm_recovery_document`, `cpm_recovery_after` (recovery). Новых пикселей нет. Recovery-пиксели получают только свои два параметра, `extension_reload_failed` без параметров.
|
||
|
||
**Замечания ревьюера (JA1–JA3):** корреляция между пикселями из-за множества перечислений; сколько пикселей на ошибку; константность `cpm_open_tabs`/`cpm_app_state`; что такое background view.
|
||
|
||
**Что верно по пикселям (исправление моей прежней ошибки):** `initialization_failed` только daily (не больше одного на устройство в день на причину); `stuck` daily+count (daily и count одного события идут с одинаковыми параметрами); snapshot пересобирается при каждой отправке, поэтому быстро меняющиеся поля (`bg_view_age`, `bg_ready_after`) между init_failed и stuck различаются, медленные (`app_state`, `open_tabs`) повторяются.
|
||
|
||
**Уникальность строк `stuck.daily` внутри дня** (доля уникальных по всем параметрам):
|
||
|
||
| Вариант | iOS | macOS |
|
||
|---|---|---|
|
||
| сейчас, `bg_events` из 12 событий | 8.3% | 30% |
|
||
| информативные токены, последние 4 | 2.1% | 5.2% |
|
||
| скаляры вместо `bg_events` | 0.96% | 4.4% |
|
||
| без `bg_events` | 0.25% | 2.0% |
|
||
|
||
Верхняя граница прироста от новых полей (синтетическое независимое равновероятное поле): со старым `bg_events` при десяти значениях iOS 21%, macOS 65%; со скалярами 3.6% / 15.8%. **Вывод:** «хуже не будет» можно утверждать только со скалярами. Главный идентификатор уже выкачен (`bg_events`).
|
||
|
||
**`bg_events`:** единственный источник давности загрузки (`load`, `base_url_changed`) и причины смерти (`died_*`). `view`, `dealloc`, `proxy_on`, `error_*` дублируют `bg_view_creations` и `context_errors`; `hold/release/proxy_off` равны нулю. Скаляры воспроизводят когорты загрузки и пробуждения на 100% строк. Теряется: поиск неожиданных последовательностей в остатке (смягчить удалённым флагом).
|
||
|
||
**Минимум данных по гипотезам:**
|
||
|
||
| Поле / пиксель | Вопрос | Где |
|
||
|---|---|---|
|
||
| `bg_ready_after`, `bg_view_age` | идёт ли загрузка, пробуждение или давно | PR0 |
|
||
| `bg_load_error_after`, `bg_load_error_app_state` (только при error 6) | таймер 30 с, состояние приложения | PR0 |
|
||
| `recovery_document`, `recovery_after` | опоздавший ответ или потерянный | PR0 |
|
||
| `app_state`, `open_tabs` (грубо, 1 / 2–5 / >5), только на `initialization_failed` | когорта «нет view» на запуске | PR0 |
|
||
| **новое:** `context_load_cause`, `context_load_age` (вместо `load@` и `extension_updated`) | точная сигнатура A | PR0 |
|
||
| **новое:** `bg_died_reason`, `base_url_changed` | смерть процесса, очистка при смене baseURL | PR0 |
|
||
| **новое:** мелкие корзины `app_launch_age` для первых 30 с (<4 / 4–10 / 10–30) | гонка запуска | PR0 |
|
||
| **новое на stuck:** `stuck_after` | сколько от первого сбоя до stuck | PR0 |
|
||
| **новое, срочно:** `network_replaced_since_view` | доля реальных мёртвых worker'ов в когорте B (рекордер вычисляет, но только логирует; предложено 8 октября, **нигде не реализовано**) | PR0 |
|
||
| `init_stage` | дошёл ли init до background | #7385 (срочно) |
|
||
| `cpm_background_ready_{context_load,wake}` | базлайн здоровых загрузок и пробуждений | #7384 |
|
||
| `background_restart` (причина, исход; **убрать из него `bg_ready_after`**, он дублирует соседний failure-пиксель), `first_load_wait` | эффект B и A | удержанная ветка |
|
||
| убрать | `loading_tabs`; из таймлайна `view`, `dealloc`, `proxy_on`, `error_*`, `hold/release/proxy_off` | |
|
||
|
||
Связность стека: #7384 добавляет ещё 5 измерений на те же 8 failure-пикселей (`context_load_to_background_view`, `cpm_low_power_mode`, `cpm_thermal_state`, `cpm_lockdown_mode`, `cpm_resume_age`); оценку склейки надо повторять и по нему. `background_restart` срабатывает вместе с первым `initialization_failed`: общий ключ `bg_ready_after`.
|
||
|
||
## 7. Критерии успеха и опровержения
|
||
|
||
- **B** подтверждается, если после релиза падают доля «Network заменён» среди stuck и `reload_failed`, `background_restart` показывает восстановление, а у таких stuck `init_stage=none`.
|
||
- **A** подтверждается, если сокращается когорта «мид-сессионная первая загрузка» (`context_load_cause`). Если нет, основание слабое.
|
||
- Остаток P1/P3: гипотеза «медленный старт, а не потеря» подтверждается, если `bg_ready_after` у упавших заметно хуже базлайна здоровых загрузок и `recovery_document=failed_navigation`.
|
||
|
||
## 8. Открытые решения и следующие шаги
|
||
|
||
1. **Правки PR0** (ждут вашего «да»): скаляры вместо `bg_events`, `context_load_cause`, `stuck_after`, `network_replaced_since_view`, мелкие корзины `app_launch_age`, убрать `loading_tabs`, константы только на `initialization_failed`, `open_tabs` грубо, `bg_load_error_*` только при error 6; после этого ребейз #7384 и #7385. Альтернатива «оставить `bg_events`» требует ответа ревьюеру без утверждения «хуже не будет» и обязательства перемерить после релиза.
|
||
2. **Ответ ревьюеру** (публично, **без цифр**): JA1 (init_failed daily-only; stuck daily+count с одинаковыми параметрами; recovered несут только `recovery_*`), JA2 (медленные поля повторяются, быстрые различаются; планируется сократить набор), JA3 (background view = скрытый `WKWebView`, в котором WebKit запускает фоновый скрипт расширения; выгружается через около 30 с простоя и пересоздаётся при пробуждении). Обязательство: перемерить различимость строк после первого релиза.
|
||
3. **Запушить фикс B** (`f1085846e7`): красный прогон подтверждён, ждёт зелёного.
|
||
4. **Судьба фикса A:** слабое основание в данных, не совпадает с названием задачи; решить, сливать ли без зелёного измерения эффекта через `first_load_wait`.
|
||
5. **P3:** довести стабильный `baseURL` (ограничить встроенными расширениями без `web_accessible_resources`, прогнать harness, перенести на свежий `main`), проверить номер сборки ОС с фиксом 318756@main.
|
||
6. **Локальные repro для P1–P3** отдельным PR без фиксов (по образцу harness'а, `requireLocalRun`), чтобы гонять после релизов WebKit.
|
||
7. **iOS:** воспроизведение error 6 на реальном устройстве; сценарий «вкладка без расширения» на macOS.
|
||
8. Блокеры по ревью: #7376 и #7378 ждут ревью; #7384/#7385 стоят на #7376.
|
||
|
||
## 9. Методика и инструменты
|
||
|
||
- Доступ: `~/Developer/grafana-data-tools` (Grafana → ClickHouse `metrics.pixels`, read-only; авторизация через `grafana-auth`). Ad-hoc SQL запускался отдельным раннером через `~/DuckDuckGo/grafana-dashboards/tools/grafana-query/read-dashboard.py`. Сканы по `pixel LIKE` без фильтра по дате, агенту и `pixel_id='debug'` превышают лимит строк. Временные запросы лежали в папке сессии и могут пропасть; ключевая классификация ниже.
|
||
- Версии с диагностикой: iOS 7.240+, macOS 1.210+. Окно: 12–14 октября (3 полных дня). Ограничения: сигнатуры эвристические; `network_restarted` только прокси; устройства нельзя проследить от init до stuck, сравниваются только доли; доля не равна пользовательскому эффекту.
|
||
|
||
```sql
|
||
-- классификация когорт для init_failed.daily / stuck.daily
|
||
multiIf(
|
||
errs LIKE '%ContextErrorDomain:6%', '1_error6',
|
||
creations = '0', '2_no_background_view',
|
||
scripts = 'false', '3_tab_not_connected',
|
||
net = 'true', '4_network_replaced',
|
||
has(ev,'view@30s') AND arrayExists(x -> startsWith(x,'dealloc@'), ev), '5_wake_view_lt30s',
|
||
arrayExists(x -> x IN ('load@30s','load@1m','load@2m','base_url_changed@30s','base_url_changed@1m','base_url_changed@2m'), ev), '6_context_load_recent',
|
||
'7_other')
|
||
-- параметры: network_restarted, context_errors, bg_view_creations, tab_has_ext_scripts, bg_events (decodeURLComponent)
|
||
```
|
||
|
||
## 10. Журнал воспроизведения P5/P6 — 15 октября 2026
|
||
|
||
Работа начата по запросу: добавить тесты новых гипотез и попытаться воспроизвести наблюдаемые цепочки. Продакшен-фиксы и публичные PR этим запросом не меняются.
|
||
|
||
**Выбор worktree:** `/Users/admin/DuckDuckGo/apple-browsers.git/alex/cpm-web-extension-remaining-failures`, ветка `alex/cpm-web-extension-remaining-failures`, HEAD `a0717cf098cd1cbd32d5bc9d19aa7db0fab2399f`. Здесь общий harness, production recorder, локальные эксперименты пробуждения, error 6 и очистки baseURL (P1–P3), без watchdog/фиксов A/B. Полного единого набора всех сценариев сейчас нет: базовые A/B находятся в `cpm-repro-harness`/фикс-ветках, init-progress E2E — в `cpm-e2e-init`. Существующие dirty-изменения сохранены; добавления этой работы ограничиваются тестовым harness.
|
||
|
||
**Среда:** macOS 26.7.1 (25G241), Xcode 27.1 (27A9275), системный WebKit. Прогон на macOS проверяет механизм; перенос на iOS требует отдельной проверки.
|
||
|
||
**Уточнение данных перед тестами:** read-only перепроверка ClickHouse за 14 октября, `stuck.daily`, iOS 7.240+/macOS 1.210+:
|
||
- P5: в выборке хвоста `proxy_on>dealloc>view>proxy_on>dealloc` без error 6/Network replacement/scripts=false — 1953 iOS и 499 macOS, `bg_view_alive=false` во всех снимках; последняя `view` везде старше 30 с. Это определение чуть шире строки P5 выше, поэтому 499 не подменяет прежние 494 без сверки дополнительных фильтров. На macOS 68 снимков с `tab_in_context=false`; на iOS у 1299 не разрешилась вкладка для табличных диагностических полей.
|
||
- P6: `load>view>proxy_on>dealloc` — 627 macOS; `load>view>proxy_on` — 235. В обеих подгруппах `context_loaded=true`, `tab_in_context=true`, `tab_controller_match=false`, `tab_has_ext_scripts=false`. В первых двух минутах запуска — 468/627 и 234/235. **235 — это хвост без dealloc, а не маркер extension_updated**; варианты с extension_updated существуют отдельно.
|
||
|
||
**Проверяемые гипотезы:**
|
||
1. P5: потеря регистрации вкладки или runtime listener приводит к отсутствию пробуждения после штатного idle; сравнить content-script send/error, background receive, текущую view и параметры failure-пикселя.
|
||
2. P5: долгий документ/пауза приложения позволяют background выгрузиться до диагностического снимка; отличить запоздалый снимок от неработающего worker.
|
||
3. P6: вкладка создана без CPM controller, затем попала в openTabs при загрузке контекста.
|
||
4. P6: существующая вкладка сохранила controller предыдущего менеджера; новый контекст видит вкладку через openTabs, но не внедряет в неё скрипты.
|
||
|
||
**Текущий статус:** читаю точки расширения harness; тесты и результаты ещё не получены. Контролируемая инъекция состояния будет явно отделена от естественного воспроизведения.
|
||
|
||
### P5/P6 — тесты добавлены, начат первый прогон
|
||
|
||
В `CPMReproLifecycleHypothesisTests.swift` добавлены 7 локальных XCTest-проверок, отдельный opt-in `CPM_REPRO_P5_P6=1` поверх `CPM_REPRO=1`:
|
||
- P6: отсутствующий controller до idle; тот же случай после штатной выгрузки и после reload контекста; перенос существующего WKWebView в replacement manager с сохранением прежнего controller.
|
||
- P5: забытая в WebKit, но оставшаяся открытой вкладка после двух idle-циклов; отсутствие runtime listener после следующего пробуждения; два здоровых документа с реальной задержкой image-subresource на 35 с; те же документы с искусственно потерянными dashboard-уведомлениями.
|
||
|
||
Harness позволяет отдельно выбирать controller вкладки, переносить существующую view и подавлять конкретный native method только в тестах. `CPMReproDelayedResource.swift` удерживает реальный subresource через WKURLSchemeHandler; навигационные finish-события не подделываются. Проверки требуют actual `stuck` от production monitor, полный набор диагностических признаков, content-script `initResp` для здоровых контролей и восстановление после снятия инъекции.
|
||
|
||
Первым запущен `testTabWithoutControllerMatchesP6BeforeIdleUnload`; пока идёт сборка, результата нет. Логи сессии: `/private/tmp/cpm-p5-p6-20261015/`. Команда сборки: `CPM_REPRO=1 CPM_REPRO_P5_P6=1 swift test --disable-automatic-resolution --filter CPMReproLifecycleHypothesisTests/testTabWithoutControllerMatchesP6BeforeIdleUnload`.
|
||
|
||
### Первый результат P6 до idle
|
||
|
||
Сборка прошла. Первый тест получил реальный `initialization_failed` и затем `stuck` с `load>view>proxy_on`, `context_loaded=true`, `tab_in_context=true`, `tab_controller_match=false`, `tab_has_ext_scripts=false`, `bg_view_alive=true`, `bg_view_creations=1`. Content marks пустые: CPM не внедрён. Новая вкладка с current controller получила `initResp` и вызвала recovery.
|
||
|
||
**Прогон пока красный (1 assertion):** дополнительное ожидание `network_restarted=false` не выполнено — поле true. Падения/замены Network-процесса тест не инъецирует. Проверяю холодный старт Network до контекста: recorder сравнивает PID с context-load, что может смешивать первичный запуск и замену. Результат нельзя объявлять полным совпадением всех признаков до этой проверки. Лог: `build-and-p6-before-idle.log`. SwiftLint пяти изменённых файлов прошёл с `--no-cache`, 0 нарушений; обычный режим упёрся в недоступный для записи кэш.
|
||
|
||
### Уточнение harness и первый зелёный P6
|
||
|
||
Проверенный прогрев default WKWebsiteDataStore перед context-load даёт ненулевой Network PID; в последующем failure `network_restarted=false`. Это сняло лишнее отличие первого холодного прогона. **`testTabWithoutControllerMatchesP6BeforeIdleUnload` теперь зелёный:** совпадают хвост и все проверяемые признаки, новая правильно подключённая вкладка работает. Механизм получен контролируемым отсутствием controller; естественный путь создания такой вкладки в приложении ещё не найден.
|
||
|
||
Проверка replacement manager сначала не дошла до failure-пикселя: teardown первого harness снимает navigationDelegate, и при переносе view его надо восстановить. Исправлен только тестовый `adoptTab`; это ошибка постановки эксперимента, не результат о CPM. Начат полный прогон семи тестов с исправлением: `p5-p6-full.log`.
|
||
|
||
### Дополнительный контроль P6
|
||
|
||
Добавлен восьмой тест `testCurrentControllerBeforeContextLoadRemainsHealthy`: создать вкладку до завершения context-load, но сразу с правильным controller, и потребовать `initResp`, скрипты и отсутствие initialization_failed. Это отделяет обычный ранний startup от отсутствующей привязки controller. Он будет выполнен после текущего запущенного бинарника; результат пока не получен.
|
||
|
||
### Подтверждены два контролируемых механизма
|
||
|
||
**P6, replacement manager — зелёный, 9.186 с.** Сначала существующая вкладка получила initResp со старым менеджером. После teardown старого менеджера её тот же WKWebView и прежний controller зарегистрированы в replacement manager. Получены два failure и actual stuck с `load>view>proxy_on`, context/tab=true, controller/scripts=false, view=true, creations=1, errors=none, network_restarted=false. Новая вкладка replacement manager получила initResp. Это воспроизводит сохранённую привязку; естественный триггер замены менеджера не доказан.
|
||
|
||
**P5, забытая живая вкладка — зелёный, 72.744 с.** Два нормальных initResp и две реальные idle-выгрузки, затем test-only `didCloseTab` без удаления WKWebView. Получен actual stuck с `load>view>proxy_on>dealloc>view>proxy_on>dealloc`, creations=2, view=false, context=true, tab=false, controller/scripts=true, errors=none, network_restarted=false. Content script отправляет init и получает init-error. После `didOpenTab` эта же вкладка снова получила initResp. Механизм подходит под P5-подгруппу с tab_in_context=false; его доля в проде и естественное действие приложения, создающее нарушение lifecycle, этим тестом не установлены.
|
||
|
||
Оба результата находятся в `p5-p6-full.log`; другие сценарии продолжают выполняться.
|
||
|
||
### Полный первый прогон: положительные и отрицательные результаты
|
||
|
||
Прогон `p5-p6-full.log` завершён: 7 тестов, 4 зелёных и 3 красных (11 assertions), 273.8 с. Не все красные результаты означают дефект harness:
|
||
|
||
- **P6 после idle — зелёный, 38.658 с.** Получена цепочка `load>view>proxy_on>dealloc` с исходной сигнатурой controller/scripts=false, tab/context=true. Reload контекста не исправил существующую вкладку; новая правильно подключённая вкладка получила initResp.
|
||
- **Потеря runtime listener только на одном wake не воспроизвела P5.** Инъекция действительно потеряла первый init (native dashboard не пришёл), но после восстановления файла и следующего idle новый worker принял следующий init и прислал initResp, stuck=0. Ожидание устойчивого P5 в тесте было неверным; превращаю его в отрицательную characterization-проверку этого результата.
|
||
- **Долгий документ:** первый image-subresource действительно удерживался 35 с. Background выгрузился примерно на 32 с, но на finish навигации в 35 с WebKit снова создал background; на failure в 39 с view уже жива. При искусственной потере dashboard получен stuck с живой view, а не P5. Здоровый документ получил initResp и не вызвал failure.
|
||
- **Ошибка постановки второго долгого документа:** одинаковый URL image взялся из кэша, поэтому второй load не был долгим. Исправляю уникальный URL на каждую навигацию и повторю оба контроля.
|
||
- **Наблюдение о trace:** watcher держал strong reference на view через 50-мс await. При переходе сразу после `context._backgroundWebView=nil` dealloc старой view иногда попадал после создания новой. Убираю удержание через await и буду ждать одновременно отсутствия WebKit background и dealloc в production recorder, чтобы не менять исследуемую последовательность самим наблюдением.
|
||
|
||
Все неподтверждённые результаты выше остаются отрицательными/ограниченными; совпадение P5 пока подтверждено только для test-only потери регистрации живой вкладки.
|
||
|
||
### Уточнённые проверки P5 — начат повтор
|
||
|
||
Теперь 9 тестов. В новом `testNoRuntimeListenerMetadataFromFirstStartupMatchesP5` runtime listener не регистрируется с самого первого старта; затем background явно загружается второй раз и оба раза штатно выгружается. Это отдельно проверяет пустые listener metadata, которые нельзя было получить простым пропуском регистрации на одном wake после здорового старта. Условия искусственные; естественная утрата metadata не доказана. После проверки инъекция снимается, выполняются explicit background load и проверка initResp.
|
||
|
||
Первоначальный listener-тест переименован в `testMissingRuntimeListenerOnOneWakeRecoversAfterNextIdle` и проверяет реально наблюдённое восстановление, отсутствие stuck и третью view.
|
||
|
||
Оба 35-секундных документа теперь используют разные URL image. Ожидания изменены по наблюдению первого прогона: здоровый длинный документ — no failure; потеря dashboard — stuck с живой view, не P5. Расширен test-only background trace на все типы autoconsent-сообщений, чтобы установить, какое сообщение повторно будит worker на finish.
|
||
|
||
Watcher больше не удерживает WKWebView через await, а idle-helper требует и освобождения background в WebKit, и dealloc по recorder. Повтор пяти новых/уточнённых проверок: `p5-refined-and-controls.log`.
|