[2026-09-09] eagle: work/projects/cpm-web-extension-breakage-findings.md
This commit is contained in:
@@ -709,3 +709,62 @@ Outcome: health monitor becomes a classifier; every stuck pixel names its chain.
|
|||||||
1. Extension background service worker terminated by `terminateIdleServiceWorkers` / `terminateServiceWorkers` while `WebExtensionContext` keeps `m_backgroundContentIsLoaded == true`; messages resolve `undefined` with no error. Attach chain 1 and 2 repro steps.
|
1. Extension background service worker terminated by `terminateIdleServiceWorkers` / `terminateServiceWorkers` while `WebExtensionContext` keeps `m_backgroundContentIsLoaded == true`; messages resolve `undefined` with no error. Attach chain 1 and 2 repro steps.
|
||||||
2. `_loadServiceWorker` failure path does not schedule `scheduleBackgroundContentToUnload()`, so recovery depends on a second message arriving (minor).
|
2. `_loadServiceWorker` failure path does not schedule `scheduleBackgroundContentToUnload()`, so recovery depends on a second message arriving (minor).
|
||||||
3. Documentation/API request: a delegate callback or notification for background content load/unload and worker start/stop.
|
3. Documentation/API request: a delegate callback or notification for background content load/unload and worker start/stop.
|
||||||
|
|
||||||
|
|
||||||
|
## Status 2026-09-09 (evening)
|
||||||
|
|
||||||
|
### Manual checks on shipping WebKit (macOS 26.x)
|
||||||
|
|
||||||
|
| Check | Result | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| Debug sim, wait 35 s idle, navigate | **recovered** | Idle eviction in `wakeUpBackgroundContentIfNecessary` is present in shipping WebKit; retained-failed-view is self-healing, as traced. |
|
||||||
|
| `sudo memory_pressure -S -l critical`, then CMP page | **CPM kept working** | Chain 1 not reproduced. Either `memoryPressureStatusChangedForProcess → TerminateIdleServiceWorkers` is not in this WebKit (it is under `ENABLE(WEB_PROCESS_SUSPENSION_DELAY)`, recent), or `-S` did not reach the WebContent process. Verify with `log stream --predicate 'process CONTAINS "WebKit.Networking" AND eventMessage CONTAINS "terminateIdleServiceWorkers"'`; until then chain 1 is downgraded to "future macOS". |
|
||||||
|
| Network process kill | pending | Use Debug → Web Extensions → "Terminate WebKit Network Process" (added, calls `_terminateNetworkProcess`) or lldb `[[WKWebsiteDataStore defaultDataStore] _networkProcessIdentifier]`. WebKit XPC processes are launchd children (ppid 1), so they cannot be found by parent PID. |
|
||||||
|
|
||||||
|
### Implemented (branch `cpm-breakage-pixel`, not yet compiled here — build before trusting)
|
||||||
|
|
||||||
|
`SharedPackages/WebExtensions`:
|
||||||
|
- `Autoconsent/CPMMessagingDiagnostics.swift` — the fact struct and its bucketed, PII-free `pixelParameters`.
|
||||||
|
- `Autoconsent/CPMMessagingDiagnosticsRecorder.swift` — `CPMMessagingDiagnosticsProviding` + recorder: critical-memory-pressure dispatch source; Network-process PID at context load vs now (`_networkProcessIdentifier` via guarded KVC); `errorsDidUpdateNotification` observer; background web view create count, current view (weak), leak count via `onDeinit` and a weak list, `_webProcessIdentifier` liveness; `openTabs` membership; probes: `loadBackgroundContent` with timeout, `navigator.serviceWorker.getRegistration()` in the background page, `globalThis.__ddgCPM` in `WKContentWorld.world(name: "WebExtension-<uniqueIdentifier>")`.
|
||||||
|
- `CPMMessagingHealthMonitor.diagnosticsProvider` — failure pixels get content-script probe + snapshot; stuck pixels also get background probes. Without a provider behaviour is unchanged (tests rely on that).
|
||||||
|
- `WebExtensionPixelEvent.cpmInitializationFailed/.cpmMessagingStuck` carry `diagnostics:`; `CPMWebExtensionPixelMetadata.parameters` renders them.
|
||||||
|
- `WebExtensionManager`: owns the recorder, feeds `willLoad`, unload (uninstall / unloadAll / reload) and the private `didCreateBackgroundWebView` delegate into it.
|
||||||
|
|
||||||
|
`macOS`:
|
||||||
|
- `WebExtensionManagerFactory` creates the recorder with a `Tab.uuid` → `Tab` resolver (`WindowControllersManager.loadedTab(withUUID:)`, new).
|
||||||
|
- Debug menu: "Terminate WebKit Network Process", "Print CPM Diagnostics Snapshot".
|
||||||
|
|
||||||
|
Tests: `CPMMessagingDiagnosticsTests` (bucketing, sanitization, descriptors, recorder lifecycle), `CPMMessagingHealthMonitorDiagnosticsTests` (async attachment, probe levels).
|
||||||
|
|
||||||
|
### Pixel parameters now attached
|
||||||
|
|
||||||
|
`extension_context_loaded`, `memory_pressure_critical` (none/under_1_min/under_5_min/under_30_min/over_30_min), `network_process_restarted`, `extension_context_errors` (`background_failed_to_load:NSURLErrorDomain:-1100` style, or `none`), `background_view_create_count` (0/1/2/3_to_5/over_5), `background_view_alive`, `background_view_leaked_count`, `background_web_process_alive`, `tab_known_to_webkit`, `content_script` (present/marker_absent/unavailable), `content_script_lifecycle`, `content_script_init_response`, `content_script_send_error` (none/tab_not_found/extension_id_mismatch/other); stuck only: `background_load_probe` (loaded/failed:<code>/timed_out/unavailable), `background_sw_registration` (present/missing), `background_sw_state`.
|
||||||
|
|
||||||
|
### Required extension change (probe 1 reads `marker_absent` until this ships)
|
||||||
|
|
||||||
|
In the autoconsent content script (`cpm.js` source, `shared/js/cpm.js` in the extension repo), publish the marker on the isolated-world global:
|
||||||
|
|
||||||
|
```js
|
||||||
|
globalThis.__ddgCPM = { injectedAt: Date.now(), lifecycle: 'created', receivedInitResponse: false, lastSendError: null };
|
||||||
|
|
||||||
|
const consent = new AutoConsent(async (msg) => {
|
||||||
|
try {
|
||||||
|
await chrome.runtime.sendMessage({ messageType: 'autoconsent', autoconsentPayload: msg });
|
||||||
|
} catch (e) {
|
||||||
|
globalThis.__ddgCPM.lastSendError = String(e && e.message || e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
chrome.runtime.onMessage.addListener((message) => {
|
||||||
|
if (message && message.type === 'initResp') globalThis.__ddgCPM.receivedInitResponse = true;
|
||||||
|
return Promise.resolve(consent.receiveMessageCallback(message));
|
||||||
|
});
|
||||||
|
// in AutoConsent.updateState({ lifecycle }): globalThis.__ddgCPM.lifecycle = lifecycle;
|
||||||
|
```
|
||||||
|
|
||||||
|
Keys read by the recorder: `lifecycle` (string), `receivedInitResponse` (bool), `lastSendError` (string|null). Values are sanitized/classified before reaching the pixel.
|
||||||
|
|
||||||
|
### Not done on purpose
|
||||||
|
|
||||||
|
- OSLogStore-based counters (per request).
|
||||||
|
- iOS wiring (factory passes no recorder; behaviour unchanged there).
|
||||||
|
- Breakage-report fields — `CPMMessagingDiagnosticsRecorder.snapshot().pixelParameters` is ready to be merged into `CookieConsentInfo` when wanted.
|
||||||
|
|||||||
Reference in New Issue
Block a user