[2026-09-25] eagle: work/projects/cpm-service-worker-architecture-and-diagnostics.md work/projects/cpm-web-extension-breakage-findings.md work/projects/cpm-web-extension-breakage-validation.md
This commit is contained in:
1 parent
3a4cad459c
commit
49ae1c15e7
3 files changed
+184
-10
No files matched your search
@@ -0,0 +1,173 @@
|
||||
# CPM Service Worker — architecture, diagnostics, failure gaps
|
||||
|
||||
> **2026-09-25 · source audit, not a new device reproduction.** Start with §4: the five validation priorities.
|
||||
> Current assessment supersedes the architecture/diagnostics assumptions in [[cpm-web-extension-breakage-findings]]. Milestone status remains in [[cpm-web-extension-breakage-validation]].
|
||||
>
|
||||
> **Answer:** no, we do not measure every useful signal. Existing telemetry describes the extension context and hosting process reasonably well, but cannot establish worker readiness, successful event delivery, native-request progress, or delivery of `initResp` to the current document. Some missing signals are implementable; others are WebKit internals or test-only facilities.
|
||||
|
||||
## 1. What the Service Worker is in CPM
|
||||
|
||||
The bundled extension is MV3, version **2026.9.2**. Its classic service worker is `public/js/background-embedded.js`; `public/js/content-scripts/cpm.js` runs at document start in an isolated world in all matching frames. A background worker is shared by the tabs using that extension context; it is not one worker per tab and not necessarily one across every profile/data store.
|
||||
|
||||
It is the extension's background JavaScript environment: message router, native-settings/configuration access, CPM rule selection and replies. It has a worker global, not the page DOM. CPM uses extension messaging—not the website Service Worker fetch/offline-cache path.
|
||||
|
||||
```text
|
||||
Page/frame: isolated CPM content script
|
||||
runtime.sendMessage(init)
|
||||
→ WebKit UI process: resolve tab → check listener/wake background
|
||||
→ background Service Worker: runtime.onMessage
|
||||
↔ Swift native-messaging handlers: settings, configuration, dashboard
|
||||
→ tabs.sendMessage(initResp, frameId)
|
||||
→ current page/frame must actually receive and apply the reply
|
||||
|
||||
Network process: SW registrations, jobs, clients and worker-process connections
|
||||
Hidden WKWebView: host page connecting the extension context to its worker global
|
||||
```
|
||||
|
||||
**Important WebKit-specific detail:** the hidden view registers the worker through a generated `navigator.serviceWorker.register(...)` page. For workers backed by such a page, this checkout selects **`WorkerThreadMode::UseMainThread`**. JavaScript executes on the hosting WebContent process's main thread, despite having a separate worker global. Extension bindings are installed by locating that host page **inside the same process**.
|
||||
|
||||
Consequently, these are different states:
|
||||
|
||||
| Observable | What it actually proves |
|
||||
|---|---|
|
||||
| `context.isLoaded` | Context is associated with its controller; asynchronous setup may still be pending. |
|
||||
| Hidden view exists / nonzero PID | A host object/process identifier exists—not a usable worker. |
|
||||
| SW registration is active | Registration lifecycle state—not live event-loop progress or extension bindings. |
|
||||
| Background load completed | WebKit's registration/load path settled—not successful CPM round-trip. |
|
||||
| CPM dashboard response | Background/native path answered—not necessarily `initResp` delivery or consent handling. |
|
||||
|
||||
**Lifecycle:** normal nonpersistent background eviction is scheduled after 30 seconds of idle time. Repeated wake-ups, ports, permission requests or inspection can prevent/delay it. Process termination closes the host; a nonpersistent background is recreated on demand. Failed registration records error 6 and can leave the host and pending actions retained until eviction. A retry only helps if its underlying cause is gone.
|
||||
|
||||
Sources: [background host lifecycle](https://github.com/WebKit/WebKit/blob/0c7e7ad97b9c50e549387914d31ecc2908ae0171/Source/WebKit/UIProcess/Extensions/Cocoa/WebExtensionContextCocoa.mm#L2603), [registration page](https://github.com/WebKit/WebKit/blob/0c7e7ad97b9c50e549387914d31ecc2908ae0171/Source/WebKit/UIProcess/WebPageProxy.cpp#L18459), [main-thread selection](https://github.com/WebKit/WebKit/blob/0c7e7ad97b9c50e549387914d31ecc2908ae0171/Source/WebCore/workers/service/server/SWServerWorker.cpp#L427), [page-dependent worker binding](https://github.com/WebKit/WebKit/blob/0c7e7ad97b9c50e549387914d31ecc2908ae0171/Source/WebCore/workers/service/ServiceWorkerGlobalScope.cpp#L118).
|
||||
|
||||
## 2. What we measure, what is missing
|
||||
|
||||
### Existing coverage: useful but mostly indirect
|
||||
|
||||
| Area | Current signal | Limitation |
|
||||
|---|---|---|
|
||||
| Context/errors | `context_loaded`, `context_errors` including immediate underlying domain/code | Errors combine historical and current state. WebKit deduplicates errors; neither array length nor notifications count failures reliably. |
|
||||
| Background lifecycle | `bg_view_creations`, `bg_view_alive`, `bg_view_leaked_count`, `bg_process_alive`, `bg_process_responsive`, `bg_events` | Object/PID and cached responsiveness are not worker readiness. Timeline records load failures, deaths, responsiveness and retention, not message progress. |
|
||||
| Routing prerequisites | `tab_in_context`, `handler_registered`, `tab_controller_match`, `tab_has_ext_scripts` | Membership/registration/script metadata do not prove actual injection, handler execution or reply delivery. |
|
||||
| Environment | `app_version_change`, `app_launch_age`, `critical_memory_age`, `network_restarted` | Correlation, not causation. Network PID comparison is not a worker-connection health check. |
|
||||
| Experiment/outcome | Cohort, termination/outcome events, init-failure/stuck/recovery events | Current “healthy” means a valid dashboard response. It is not an end-to-end CPM-success metric. |
|
||||
|
||||
Two measurement confounds are source-confirmed:
|
||||
|
||||
- **Intentional retention can count as a leak.** The leak calculation is live weak-tracked views minus one expected current view. During overlap with a replacement, the graveyard's deliberately retained old view can increase `bg_view_leaked_count`.
|
||||
- **The timeline cannot resolve the 3-second race.** Its first age bucket is 30 seconds; it retains at most 12 events/255 characters. Ordering helps, but local monotonic timestamps and context/view generations are needed for causal timing.
|
||||
|
||||
The recorder's weak “current view” also survives while another owner retains it. Comparing it with WebKit's actual current `_backgroundWebView` would distinguish an intentionally retained, closed host from the context's active host.
|
||||
|
||||
### Useful additions within our control
|
||||
|
||||
| Missing evidence | Proposed measurement | Boundary |
|
||||
|---|---|---|
|
||||
| Worker boot / listener readiness | Worker-generated boot + listener-installed marker, associated locally with a context/worker generation | An application protocol addition, not an existing native WebKit readiness callback. Absence alone cannot distinguish failed boot from failed native delivery. |
|
||||
| Request progress | Host-side arrival/completion/error, outstanding-count and oldest-request-age buckets per allowlisted native method | Independent of the JS diagnostic channel. No URLs, payloads, arbitrary method strings or raw error text in pixels. |
|
||||
| Actual initialization delivery | Document-scoped init/reply acknowledgement; distinguish background-received, reply-sent and page-acknowledged | Must reject late responses from previous navigation/context generations. Do not rename this metric “consent handled.” |
|
||||
| Script/load failure phase | Separate current vs previously observed error codes; classify known boot/runtime/native stages | Code 6 is generic load failure. Code 7 is script execution error in this checkout; OS support needs verification. |
|
||||
| Worker exception contract | Handle the bundle's `autoconsent.extensionException` with bounded, privacy-reviewed categories | **Confirmed mismatch:** bundle emits it; Swift `Method` has no matching case and returns unknown-method failure. |
|
||||
|
||||
The exception mismatch does **not** mean all errors are invisible: WebKit's own error channel may report some of them. Nor will fixing it catch everything: the bundle's extension polyfill can throw **before** its global error/rejection listeners are installed, and the exception notification itself uses the same potentially failing native channel.
|
||||
|
||||
### WebKit diagnostics that are not drop-in production telemetry
|
||||
|
||||
| Facility | What it offers | Why it needs care |
|
||||
|---|---|---|
|
||||
| Public `loadBackgroundContent(completionHandler:)` | Explicit attempt to wake/load background | **Active intervention.** Changes idle timing and can repair/perturb the episode. Completion queues behind load and may remain pending on failure. Use only a deliberate debug probe with timeout/generation protection. |
|
||||
| Private `_backgroundWebView`, `_backgroundContentURL`; context error updates | Current host identity and failure information | Availability-gate SPIs. Do not upload raw URLs/descriptions/stacks. Preserve WebKit's delegate behavior. |
|
||||
| Worker Inspector + ServiceWorker/Extensions logs | Worker global, exceptions, breakpoints, registration/job/connection/PID evidence | Inspector changes worker lifetime. Reproduce detached; use inspection afterward or in a separate run. This checkout's Inspector ServiceWorker protocol is not Chromium's full lifecycle protocol. |
|
||||
| `_runningOrTerminatingServiceWorkerCountForTesting`; datastore console callback | Aggregate test count; conditional console forwarding | Count is not CPM-specific. Console forwarding is disabled by default; the enabling call found in this tree is in `ServiceWorkerInternals` testing code. Implementing the datastore delegate alone does not enable a production worker-console stream. |
|
||||
| SWServer maps/queues, registration-worker-host affinity, listener destinations, queued wake-up actions | The decisive internal state for several failures | No passive public native accessor found. Requires existing internal logs or an instrumented WebKit build; do not invent KVC access to C++ state. |
|
||||
|
||||
**Two tempting probes are invalid:**
|
||||
|
||||
- The generic worker heartbeat is **disabled** for `WorkerMainRunLoop`. It cannot serve as CPM's extra 60-second watchdog. Existing UI-side process-responsiveness monitoring is a different mechanism.
|
||||
- `evaluateJavaScript` on the hidden view evaluates a frame/document global, not the Service Worker's global. Missing `browser` there does not prove missing worker bindings. A registration query likewise does not prove the worker can answer.
|
||||
|
||||
Sources: [error recording](https://github.com/WebKit/WebKit/blob/0c7e7ad97b9c50e549387914d31ecc2908ae0171/Source/WebKit/UIProcess/Extensions/Cocoa/WebExtensionContextCocoa.mm#L230), [worker script-error callback](https://github.com/WebKit/WebKit/blob/0c7e7ad97b9c50e549387914d31ecc2908ae0171/Source/WebKit/WebProcess/Extensions/Cocoa/WebExtensionControllerProxyCocoa.mm#L110), [active load API implementation](https://github.com/WebKit/WebKit/blob/0c7e7ad97b9c50e549387914d31ecc2908ae0171/Source/WebKit/UIProcess/Extensions/WebExtensionContext.cpp#L1787), [heartbeat exclusion](https://github.com/WebKit/WebKit/blob/0c7e7ad97b9c50e549387914d31ecc2908ae0171/Source/WebCore/workers/service/context/ServiceWorkerThread.cpp#L570), [conditional console forwarding](https://github.com/WebKit/WebKit/blob/0c7e7ad97b9c50e549387914d31ecc2908ae0171/Source/WebCore/workers/service/ServiceWorkerGlobalScope.cpp#L265), [test-only enablement](https://github.com/WebKit/WebKit/blob/0c7e7ad97b9c50e549387914d31ecc2908ae0171/Source/WebCore/testing/ServiceWorkerInternals.cpp#L208).
|
||||
|
||||
## 3. The process-death failure and what graveyard actually changes
|
||||
|
||||
**Historical evidence:** the September 10 investigation recorded forced background-process death causing the all-tabs registration-error loop. This audit checked the supporting source chain; it did not repeat that device test or establish natural production incidence.
|
||||
|
||||
```text
|
||||
Background process dies
|
||||
→ Network closes SW connection before unregistering that process's clients
|
||||
→ remaining clients cause a connection request referencing the old host page
|
||||
→ if old WebProcessProxy is gone, process selection may create a standalone worker process
|
||||
→ new registration reuses that domain's existing connection
|
||||
→ worker cannot find its host page in this other process
|
||||
→ browser/chrome bindings are not installed
|
||||
→ bundle's initial polyfill throws
|
||||
→ registration fails → error 6 → later retries may repeat
|
||||
```
|
||||
|
||||
An existing SW connection is selected by domain/COEP rather than by the new host page's process affinity. Merely creating a fresh background view therefore does not necessarily repair the connection.
|
||||
|
||||
**Graveyard treatment retains the terminated WKWebView and its native proxy object graph.** The old process proxy can still resolve when WebKit services the stale connection request. In this source branch, the initial host-page process lookup does not exclude a terminated proxy. The intended effect is to make that stale request fail/cancel instead of taking the standalone-process fallback while old clients drain. It does **not** revive the worker or fix its JavaScript/native handler.
|
||||
|
||||
The exact winning timing remains an experimental hypothesis, not a WebKit guarantee. Current implementation holds **one** view with a **3-second scheduled release**. A second death replaces it immediately; main-queue blockage/app suspension can delay release beyond three wall-clock seconds. Context transitions also release it.
|
||||
|
||||
**M2 changes the recovery assumption.** The current M5 loader still uses the default changing origin; separate M2 assigns `webkit-extension://com.duckduckgo.web-extension.embedded/`. After M2, extension reload cannot rely on escaping a poisoned connection via a new origin. Fire may also clear data, so its behavior must be tested separately—not assumed equivalent to reload.
|
||||
|
||||
Sources: [connection teardown order](https://github.com/WebKit/WebKit/blob/0c7e7ad97b9c50e549387914d31ecc2908ae0171/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp#L470), [connection recreation](https://github.com/WebKit/WebKit/blob/0c7e7ad97b9c50e549387914d31ecc2908ae0171/Source/WebCore/workers/service/server/SWServer.cpp#L1785), [process selection](https://github.com/WebKit/WebKit/blob/0c7e7ad97b9c50e549387914d31ecc2908ae0171/Source/WebKit/UIProcess/WebProcessPool.cpp#L709), [existing connection reuse](https://github.com/WebKit/WebKit/blob/0c7e7ad97b9c50e549387914d31ecc2908ae0171/Source/WebCore/workers/service/server/SWServer.cpp#L1068).
|
||||
|
||||
## 4. Five validation priorities
|
||||
|
||||
These are **under-tested paths, not five newly proven root causes**. Several broad hypotheses existed in the old note; this audit adds concrete mechanisms and discriminating tests.
|
||||
|
||||
### 1. Stable-origin reload and old-registration teardown — highest integration risk
|
||||
|
||||
**Source-confirmed paths; combined race unproven.** Registration can immediately reuse an existing same-script/type/cache registration without rebinding its host-page identifier in that branch. Old host-client removal separately clears its associated registration. A rapid same-origin replacement may therefore reuse a registration still owned by the old page, which later gets cleared. Separately, the poisoned domain connection can survive reload.
|
||||
|
||||
**Test:** combine M2 with M5+M6; force background death in control/treatment, then test extension reload and Fire separately. Also rapidly unload/reload a healthy context without killing it. Capture register/reuse/unregister order, host/worker PID affinity and page acknowledgement. Require no late old-generation teardown to invalidate the new worker.
|
||||
|
||||
Sources: [reusable registration fast path](https://github.com/WebKit/WebKit/blob/0c7e7ad97b9c50e549387914d31ecc2908ae0171/Source/WebCore/workers/service/server/SWServerJobQueue.cpp#L324), [old page unregister](https://github.com/WebKit/WebKit/blob/0c7e7ad97b9c50e549387914d31ecc2908ae0171/Source/WebCore/workers/service/server/SWServer.cpp#L1526).
|
||||
|
||||
### 2. Main-thread stall, event flood and missing startup deadline
|
||||
|
||||
**Heartbeat exclusion confirmed; production trigger unproven.** A genuine synchronous background stall cannot be detected by the disabled worker heartbeat. Main-thread JS/native work, repeated all-frame init traffic, or a registration that never settles can leave a live-looking host with no progress. Repeated events can prevent idle eviction; retained load actions have no CPM-owned deadline. An unresolved Promise alone is not a blocked event loop.
|
||||
|
||||
**Test:** separately inject a controlled background busy loop, delayed registration and sustained frame churn. Stop the producer and check drain/recovery. Capture process responsiveness, startup age, native outstanding work and page acknowledgements. Test iOS suspend/resume separately from foreground hangs. Ordinary page JS is only relevant if it actually shares the affected process; do not assume it blocks the extension.
|
||||
|
||||
### 3. Native errors/timeouts hidden by the diagnostic transport
|
||||
|
||||
**Contract mismatch and 20-second timeout confirmed; natural stall unproven.** The worker races native requests against 20 seconds, but does not cancel native work. Failure can become `undefined` and disabled settings. Exception/diagnostic notifications travel over that same native channel, and `extensionException` is currently unsupported. Session-storage/configuration operations can also fail before a usable init reply.
|
||||
|
||||
**Test:** independently fail/delay settings, feature and config calls; reject session-storage access; inject a runtime exception. Assert host-side arrival/completion/timeout evidence, bounded outstanding work and explicit failure stages. Repeated frame churn must not leave unlimited old native requests.
|
||||
|
||||
### 4. “Healthy” before response delivery; stale document replies
|
||||
|
||||
**Source-confirmed measurement gap; user-visible race needs reproduction.** The background schedules dashboard stage `init_received` before rule filtering and `tabs.sendMessage(initResp)`. Swift credits a valid dashboard response as healthy without requiring that stage to prove page receipt. The reply send is not awaited/caught here and targets `frameId`, not a document identifier. Navigation while native work is pending can drop the reply or target a newer document.
|
||||
|
||||
**Test:** delay configuration, navigate the same frame, then release the reply; separately reject the reply send or throw during rule filtering. Distinguish “background answered” from “current document initialized.” Require document/generation attribution before claiming end-to-end recovery.
|
||||
|
||||
### 5. Registration failure vs listener-routing failure
|
||||
|
||||
**Both source paths exist; no new production transition demonstrated.** Generic error 6 merges resource/script startup failures with wrong-process binding failure. Separately, persisted listener metadata and live listener-frame destinations can disagree: WebKit can skip wake-up or have no usable receiver despite a loaded-looking context.
|
||||
|
||||
**Test:** independently inject a missing/throwing background resource and a controlled no-listener case; compare cold launch, normal idle eviction and context reload. Capture the load result, boot/listener marker and request arrival. In an instrumented WebKit build inspect listener sets/destinations and pending actions. Do not infer a listener bug from PID/alive alone.
|
||||
|
||||
## 5. What not to conclude; next implementation boundary
|
||||
|
||||
**Deprioritized or disproven explanations:** this WebKit checkout explicitly does **not persist extension-backed SW registrations to disk**, so ordinary website SW-database corruption across relaunch is not the leading explanation. Extension listener-state/session/local storage are separate and still deserve validation. `removeAllUserScripts()` excludes WebExtension scripts. Earlier negative memory-pressure/Network-restart tests remain negative on their tested OS—not universal proof those paths cannot fail.
|
||||
|
||||
**Recommended order:** fix the exception-reporting contract and define request/document-stage evidence first; run priority 1 with stable origin before relying on reload as recovery; then exercise priorities 2–5. Instrumentation must preserve control/treatment behavior, avoid unsolicited wake-ups, keep raw data local and receive normal pixel/privacy review.
|
||||
|
||||
**Scope and evidence:** WebKit checkout `0c7e7ad97b9c50e549387914d31ecc2908ae0171` (2026-08-23), tracked sources clean. App audit used the current dirty `cpm-graveyard-mitigation` worktree (HEAD `4de02983986c47769e4e5ce0d267c23f3ca8d48c`) and inspected M2 separately in `cpm-stable-base-url`. The old `cpm-breakage-pixel` worktree is not the current M5+M6 implementation. Exact behavior/SPI availability must be checked on supported iOS and macOS releases; trunk source is not proof of the shipped OS binary.
|
||||
|
||||
App evidence is in `SharedPackages/WebExtensions/Sources/WebExtensions/`:
|
||||
|
||||
| Source | Relevant evidence |
|
||||
|---|---|
|
||||
| `BundledWebExtensions/duckduckgo-embedded-web-extension.zip` | Manifest; background JS lines 64, 2645–2693, 2955–2999, 3300–3347: early polyfill, dashboard-before-reply, native timeout, exception notifications. |
|
||||
| `Messaging/MessageHandlers/AutoconsentWebExtensionMessageHandler.swift` | Methods 30–41 omit extensionException; 68 rejects unknown method; dashboard response forwarded without requiring initResp acknowledgement. |
|
||||
| `Autoconsent/CPMMessagingHealthMonitor.swift` | Healthy outcome at 442/680 represents dashboard response, with existing tab/generation safeguards. |
|
||||
| `Autoconsent/CPMMessagingDiagnostics{,Recorder}.swift` | Pixel coverage, 30-second minimum timeline age bucket, historical error accumulation, leak-count calculation at recorder 506. |
|
||||
| `Autoconsent/CPMBackgroundWebViewGraveyard.swift` | Dedicated retention entity, one retained view, 3-second scheduling, replacement/reset behavior and experiment outcomes. |
|
||||
|
||||
Additional source: [extension workers are not persisted](https://github.com/WebKit/WebKit/blob/0c7e7ad97b9c50e549387914d31ecc2908ae0171/Source/WebCore/workers/service/server/SWServerWorker.cpp#L561).
|
||||
|
||||
**Validation status:** source tracing and bundle/Swift contract comparison completed. No new device reproduction, build, unit test or app-code change was performed for this documentation audit.
|
||||
@@ -10,6 +10,8 @@ tags:
|
||||
---
|
||||
# CPM Web Extension Breakage Findings
|
||||
|
||||
> **Current architecture/source audit — 2026-09-25:** [[cpm-service-worker-architecture-and-diagnostics]]. Read it for Service Worker architecture, measured/missing diagnostics and five prioritized validation gaps. The investigation below is historical evidence, not a current readiness verdict. In particular: extension workers use the host process’s main thread; their generic SW heartbeat is disabled; dashboard “healthy” does not prove page receipt; and M2’s stable origin requires revalidating reload/Fire recovery. No new device reproduction was performed on September 25.
|
||||
|
||||
> Investigation summary as of 2026-09-08. This note distinguishes confirmed code paths from hypotheses that still need production evidence.
|
||||
>
|
||||
> **2026-09-10: two additional app-side tab-lifecycle violations found in production-reachable code** — `Close Other Tabs` sends `didCloseTab` for the one loaded tab it keeps, then puts the same `Tab` object back without `didOpenTab`; `Duplicate Tab` invoked from a popup inserts the original `Tab` into the main window instead of a copy, so closing the popup makes WebKit forget the still-live tab. Both produce a deterministic **single-tab** routing failure: reload does not help, new tabs work, and Fire / extension reload / tab recreation repairs it. The first chain is source-confirmed and has a minimal manual repro below; the popup chain is source-confirmed and still needs a manual run.
|
||||
@@ -35,7 +37,7 @@ The debug-menu simulation enters the third state deliberately. It proves the hea
|
||||
|
||||
The strongest explanation for the observed **old/specific tabs dead, new tabs fine** shape is now the app violating WebKit's tab lifecycle contract. It has three concrete instances, all ending in the same `m_tabMap` loss. The separate **all tabs including new tabs** shape still points to the background-service-worker lifecycle and must not be conflated with the tab-routing failure.
|
||||
|
||||
## Current conclusions (authoritative, 2026-09-10)
|
||||
## Historical conclusions (2026-09-10; current source audit linked above)
|
||||
|
||||
Use the scope first when classifying an episode:
|
||||
|
||||
@@ -55,17 +57,15 @@ Use the scope first when classifying an episode:
|
||||
|
||||
Explicitly not active production hypotheses: critical-memory-pressure termination (manual test negative), cold-launch/restoration miss (transient and fixed), feature-flag transitions (not a production event), privacy-config scriptlet extension reload (different extension context), `removeAllUserScripts()` (WebExtension scripts are excluded), and a normal page's long-running JavaScript by itself (limited to that page's WebContent thread).
|
||||
|
||||
## CPM architecture
|
||||
## CPM architecture (refreshed 2026-09-25)
|
||||
|
||||
The bundled extension is Manifest V3:
|
||||
The bundled MV3 extension (2026.9.2) runs `public/js/content-scripts/cpm.js` at `document_start` in isolated worlds/all matching frames. Its shared background entry point is the classic Service Worker `public/js/background-embedded.js`.
|
||||
|
||||
- Background entry point: `public/js/background-embedded.js`
|
||||
- CPM content script: `public/js/content-scripts/cpm.js`
|
||||
- Injection: `document_start`, isolated world, all frames
|
||||
- Content-to-background transport: `chrome.runtime.sendMessage`
|
||||
- Background receiver: `runtime.onMessage`, registered by `MessageRouter`
|
||||
`content script → runtime.sendMessage → WebKit tab/listener routing → background worker → Swift native settings/configuration → tabs.sendMessage(initResp) → page acknowledgement`
|
||||
|
||||
The content script sends an `autoconsent` message to the background service worker. The background routes that message and, where required, forwards it through native messaging.
|
||||
WebKit creates a hidden `WKWebView` whose page registers the worker. In the inspected checkout this extension worker uses **the host WebContent process’s main thread**, with a separate worker global. WebKit must find the host page in that process to install `browser`/`chrome` bindings. The Network process owns registration/jobs/client and connection bookkeeping. This is extension messaging, not a website’s offline/fetch Service Worker.
|
||||
|
||||
Context loaded, host alive, registration active, listener ready and current-document reply received are **different states**. Current diagnostics mostly observe the first two. Dashboard responses can precede `initResp`; the worker heartbeat is disabled for this main-thread mode. Detailed coverage, source links and validation priorities: [[cpm-service-worker-architecture-and-diagnostics]].
|
||||
|
||||
## Normal message path
|
||||
|
||||
@@ -1216,7 +1216,7 @@ Symptom match: all tabs, new tabs too, survives reload, banners not handled, das
|
||||
|
||||
**Observation (Alex, 2026-09-10):** with the delegate proxy installed (its lifetime tied to the background `WKWebView`), `kill -9` no longer breaks CPM; without the proxy, or with the proxy tied to the original delegate, it does. Reading: the outcome of step 4 depends on whether the dead P1 still exists as a `WebProcessProxy` when the Network process asks for the context connection — `processForIdentifier` is cleared only in `~WebProcessProxy` (`WebProcessProxy.cpp:488`), which is kept alive by the closed `WebPageProxy`, i.e. by the `WKWebView`. If P1 still resolves: `useProcessForRemoteWorkers(P1)` → send to a dead process → cancelled → Network's retry finds no clients → clean. If P1 is gone: fallback → stray process S → bug. The proxy shifts the view's dealloc by enough to win that race — accidental timing, not a fix.
|
||||
|
||||
**Graveyard experiment (implemented 2026-09-10):** `FeatureFlag.cpmBackgroundGraveyardExperiment` (subfeature `webExtensions.cpmBackgroundGraveyard`, cohorts `control`/`treatment`). Treatment: on the termination callback the recorder holds the dead background `WKWebView` strongly for `CPMMessagingDiagnosticsRecorder.graveyardHoldDuration` (3 s), so `processForIdentifier(P1)` keeps resolving while the Network process re-requests; control releases immediately. Pixel: `background_graveyard=control|treatment`. Prediction: treatment never shows `error_background_failed_to_load` after `died_*`; control does. Deterministic A/B: local-override the cohort, `kill -9`, watch `background_events`.
|
||||
**Graveyard mechanism (refreshed 2026-09-25):** current M5+M6 uses the dedicated `CPMBackgroundWebViewGraveyard`, with control/treatment selected by `cpmBackgroundGraveyardExperiment`. Treatment retains one terminated `WKWebView` and schedules release after 3 seconds. Keeping the native proxy graph alive lets the stale host-page process lookup resolve the terminated proxy instead of falling back to a standalone worker process. The intended effect is cancellation/failure of the stale connection request while clients drain—not revival of the dead worker. This timing benefit is an experimental hypothesis, not a guarantee that treatment never fails. A second death replaces the retained view; app suspension/main-queue blockage can delay scheduled release. Current diagnostics use `background_graveyard_cohort` and `bg_events`, alongside dedicated termination/outcome events. See [[cpm-service-worker-architecture-and-diagnostics]] for the source chain, M2 stable-origin interaction and outcome-measurement limitations.
|
||||
|
||||
**Delegate proxy flag:** `FeatureFlag.cpmBackgroundDelegateProxy` (subfeature `webExtensions.cpmBackgroundDelegateProxy`, remote-releasable, on by default). The recorder observes `FeatureFlagger.updatesPublisher`; turning it off hands WebKit's original delegate back on the live view (`CPMBackgroundWebViewDelegateProxy.uninstall`), turning it on re-installs.
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
> Актуально на 2026-09-23. Полная трассировка: [[cpm-web-extension-breakage-findings]].
|
||||
> Experiment Definition: [[cpm-background-graveyard-experiment-definition]].
|
||||
> **2026-09-25 source audit:** [[cpm-service-worker-architecture-and-diagnostics]]. Earlier readiness/test results below are historical, not validation of the newly identified gaps. Additional gates: M2 stable-origin + M5/M6 reload/Fire interaction; worker exception contract; distinguish dashboard response from actual page initialization. No new device tests were run for this audit.
|
||||
|
||||
## Решение
|
||||
|
||||
|
||||
Reference in new issue
Block a user