[2026-09-09] eagle: work/projects/cpm-web-extension-breakage-findings.md
This commit is contained in:
@@ -661,3 +661,51 @@ No hook; `m_backgroundContentEventListeners` is not exposed. If everything else
|
||||
6. `cpmBgSwRegistration` / `cpmBgSwState` via the SW-page probe — confirms 1 vs 2. SPI-adjacent (needs the background `WKWebView` reference).
|
||||
|
||||
Items 1–4 are enough to attribute the current stuck pixels; 5–6 turn the health monitor into a classifier instead of a timer.
|
||||
|
||||
|
||||
## Next steps (2026-09-09)
|
||||
|
||||
Ordered so each step de-risks the next. Days are rough effort, not calendar.
|
||||
|
||||
### Phase 0 — confirm trunk matches shipping WebKit (½ day, manual, no code)
|
||||
|
||||
1. Debug sim persistence: enable "fail CPM background load", load one CMP page (fails), wait 35 s without navigating, load again. Expected: recovers. If it stays broken, shipping WebKit lacks the idle-eviction-on-wake-up path and chain 5/6 ranking changes.
|
||||
2. Chain 1 repro: launch, wait 15 s, `sudo memory_pressure -S -l critical` for ~5 s, load a CMP page. Expected: no banner handling, `errors` empty, SW inspector target gone, recovery after 35 s idle.
|
||||
3. Chain 2 repro: `kill -9` DDG's `com.apple.WebKit.Networking`, load a CMP page. Same expectation.
|
||||
4. Chain 1 revive test: after step 2, in the background `WKWebView` (grab it from `didCreateBackgroundWebView` behind a debug flag) run `navigator.serviceWorker.getRegistration().then(r => r.active.postMessage({type:"ping"}))`, then load a CMP page. If CPM works again, we have a remediation that avoids the reload window.
|
||||
|
||||
Outcome: which chains are real on macOS 26.x, and whether `postMessage` revive is viable.
|
||||
|
||||
### Phase 1 — attribution in existing pixels (2–3 days)
|
||||
|
||||
Add to `CPMMessagingHealthMonitor` failure/stuck/recovered pixels and to `BrokenSiteReport`:
|
||||
|
||||
1. `cpmMemPressureCriticalRecently` / `cpmSecondsSinceCritical` — in-app `DispatchSource.makeMemoryPressureSource`.
|
||||
2. `cpmNetworkProcessRestarted` — compare `WKWebsiteDataStore.default()._networkProcessIdentifier` against the value recorded at context load (SPI; fall back to OSLogStore crash-line count if SPI is off the table).
|
||||
3. `cpmContextErrors` (`errors` codes + underlying NSURLError) and `cpmBgViewCreateCount` (count `didCreateBackgroundWebView`).
|
||||
4. `cpmTabKnownToWebKit` (`context.openTabs.contains(tab)`) and `cpmTabNotFoundLogCount` (OSLogStore, own PID, `category == "Extensions" AND eventMessage CONTAINS "Tab not found"`).
|
||||
5. `cpmContextLoadedAtCommit`, `cpmMsSinceLastReload`.
|
||||
|
||||
Ship behind the existing pixel plumbing; no extension change needed. After one release the stuck pixels split into chains 1 / 2 / 3 / 4 / 5.
|
||||
|
||||
### Phase 2 — probes that classify at failure time (2–3 days, needs `cpm.js` + app)
|
||||
|
||||
1. `cpm.js`: publish `globalThis.__ddgCPM = { injectedAt, lifecycle, initSentAt, gotInitResp, lastSendError }`; wrap the `sendMessage` `await` in try/catch to capture `tab not found`.
|
||||
2. App: on `cpmInitializationFailed`, evaluate `globalThis.__ddgCPM` in `WKContentWorld.world(name: "WebExtension-\(uniqueIdentifier)")` for the failing tab; attach `cpmContentScriptInjected`, `cpmContentScriptLifecycle`, `cpmLastSendError`.
|
||||
3. App: on `cpmMessagingStuck`, run the SW-page probe on the background web view (`hasRegistration`, `active.state`) and `loadBackgroundContent` with a 2 s timeout; attach `cpmBgSwRegistration`, `cpmBgLoadProbe`.
|
||||
|
||||
Outcome: health monitor becomes a classifier; every stuck pixel names its chain.
|
||||
|
||||
### Phase 3 — fixes (in order of confidence)
|
||||
|
||||
1. **Stable base URL** (½ day): `context.baseURL = URL(string: "webkit-extension://\(identifier)/")` in `WebExtensionLoader.makeContext`. Removes `_renameOrigin` from every load, shrinks the startup miss window (chain 3), and stops migrating extension web storage on every launch. Verify existing `storage.local` is unaffected (it is SQLite, not origin-keyed web storage) and that the first launch after the change performs one final rename from the last random URL.
|
||||
2. **Self-heal on stuck** (1 day): on `cpmMessagingStuck`, if Phase 0 step 4 works → `postMessage` revive first; else `reloadExtension(identifier:, trigger: .healthRecovery)`. Gate by a 5-minute cooldown; fire `cpmMessagingRecoveredAfterExtensionReload` (already exists).
|
||||
3. **Content-script retry** (½ day, `cpm.js`): if no `initResp` within ~3 s, resend `init` (max 2–3 times, backoff). Covers chain 6 and any transient drop, and gives chain 1/2 a natural retry after the 30 s eviction instead of waiting for the next navigation.
|
||||
4. **Keep the worker alive** (research, 1 day): the SW server only refreshes `needsRunning()` on server-routed events. A background `chrome.alarms` tick does not go through the SW server either. Options: (a) from the SW page, periodic `registration.active.postMessage` keep-alive — would also count as "not idle" for `terminateIdleServiceWorkers`; (b) file a WebKit bug: extension service workers should be exempt from `terminateIdleServiceWorkers`/`terminateServiceWorkers`, or `WebExtensionContext` should observe worker termination and unload/reload the background view. (b) is the real fix; (a) is the interim.
|
||||
5. **Tab registration audit** (only if Phase 1 shows chain 4 volume): log every `didOpenTab`/`didCloseTab` with tab id in debug builds and diff against `context.openTabs` on stuck.
|
||||
|
||||
### WebKit bug reports to file (after Phase 0 confirms)
|
||||
|
||||
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).
|
||||
3. Documentation/API request: a delegate callback or notification for background content load/unload and worker start/stop.
|
||||
|
||||
Reference in New Issue
Block a user