9.8 KiB
title, date, status, tags
| title | date | status | tags | |||
|---|---|---|---|---|---|---|
| CPM Web Extension failures — what the data shows | 2026-10-08 | draft-for-asana |
|
CPM Web Extension failures: what the data shows
Context
Cookie Pop-up Management (CPM) runs in the iOS and macOS browsers as a built-in web extension. It has two parts:
- a content script inside each web page;
- a background service worker, the extension's shared "brain". WebKit starts it on demand and unloads it after about 30 seconds of inactivity.
On every page load, the content script asks the background worker for its state. The worker then reports to the app.
The app watches this exchange with a health monitor:
- If no report arrives within 4 seconds after a page finishes loading, the app sends an "initialization failed" pixel.
- If a later page load in the same episode also fails, the app sends a "messaging stuck" pixel and reloads the extension to recover.
- When answers resume, the app sends a "recovered" pixel.
- Failure pixels carry a snapshot of the extension's state: whether it is loaded, whether its background is alive, recent background lifecycle events, and WebKit errors.
Important: a failure pixel means "no answer within 4 seconds". It does not mean the extension is broken. A late answer also counts as a failure.
Key findings
- Most failures happen right after WebKit wakes the background worker following idle unload, while the app-side state looks healthy. A test reproduces this exact pattern when the worker takes longer than 4 seconds to start. Many of these failures are late answers rather than lost ones.
- About a third of iOS "stuck" reports carry WebKit error 6, "background failed to load". The root cause is in WebKit: the 30-second idle-unload timer starts before the background has finished loading. A load slower than 30 seconds is cancelled.
- A crash of WebKit's networking process explains only a minority (20–45%) of the cases that look healthy. It is the only cause we found that breaks every tab at once, including pages that were already open.
- Phase 1 telemetry, ready but not shipped, separates "slow start" from "lost message" and "late answer" from "no answer". It should ship before or together with any fix, so the effect of fixes can be measured.
Data used
- One full day, October 5, 2026 (UTC), on iOS 7.240 and macOS 1.210.
- The first daily "initialization failed" pixel: 6,624 iOS installs and 1,377 macOS installs.
- The daily "messaging stuck" pixel: 1,440 iOS installs and 487 macOS installs.
- Daily pixels fire at most once per install per day, so these are install-days, not users or episodes.
Failure cohorts
"Stuck" reports split by the state the app recorded at the time of failure. This describes symptoms, not root causes.
- Looks healthy (no visible error): the largest cohort, about 55% of iOS stuck reports.
- In 17–21% of iOS and 30–45% of macOS reports, the networking process had been replaced since the extension loaded.
- The rest has no visible cause, which is 748 iOS and 228 macOS daily reports. This is the main open question, covered below.
- WebKit error 6, background failed to load: about a third of iOS stuck reports; almost none on macOS.
- No background at all: 158 iOS and 35 macOS reports.
- Tab not connected to the extension (the tab has the wrong extension controller or no content scripts): 81 iOS and 102 macOS reports. On macOS this appeared only with version 1.210.
- Background process died: 14 iOS reports, almost always together with a networking process restart. This looks like iOS killing both processes under memory pressure.
The largest unexplained cohort: healthy-looking failures
- About half come right after the background was woken up again after idle unload. The background had been created less than 30 seconds earlier: 373 of 748 on iOS, 120 of 228 on macOS.
- A quarter on iOS and 15% on macOS come right after the extension was loaded or reloaded.
- On macOS, 63% of these reports come shortly after an app update, against 52% across all stuck reports.
- Memory pressure and WebKit errors are almost never present.
- Both macOS 26.7 and 27.0 are affected. The iOS version split has not been checked yet.
How often failures recover:
- After a single failure, answers resume without a reload on about a quarter of install-days (23% iOS, 25% macOS). The same devices recover repeatedly, about twice a day on iOS.
- After a "stuck" episode and the extension reload, answers resume on 64% of iOS and 74% of macOS install-days.
- The recovery pixels cannot yet tell a late answer from a later page that worked. Phase 1 telemetry adds this.
Single failures and stuck episodes have different patterns:
- Failures right after an extension load make up 32% of single failures but only 18% of stuck reports.
- Failures right after a wake-up make up 44% of single failures but 61% of stuck reports.
- So failures after a load usually clear on their own, while failures after a wake-up more often turn into "stuck".
Theories and their status
-
Networking process crash leaves the background worker dead. Reproduced in tests. Explains at most 20–45% of healthy-looking stuck reports. App fix written, not merged.
-
The first message is lost when a new extension version loads. Reproduced in tests. At launch the app already waits for the extension, so this is covered. Installing or updating the extension mid-session was not covered; app fix written, not merged.
-
The background worker dies while the networking process stays alive. Reproduced in tests. The app cannot detect it directly, so it can only react after the first failure.
-
Error 6. The cause was found in WebKit source code and reproduced on macOS. It did not reproduce on an iPhone with iOS 26.7.1 in four scenarios. Telemetry has been added to tell which conditions trigger it.
-
The worker starts slowly on wake-up. A start longer than 4 seconds reproduces the healthy-looking pattern exactly. Why real devices would be that slow is still unknown. Candidates:
- CPU contention with the page that is loading;
- process startup cost;
- the extension process running at low priority;
- a busy networking process.
Telemetry has been added for this.
-
WebKit cleans up website data every time the extension loads. WebKit change 312231@main, shipped in OS 26.6 and later, does this when an extension's internal base URL changes, which for us happens on every launch and every reload. It delays content scripts and the background start. On a Mac with a large browser cache, the delay was 0.75–1.35 seconds. This explains part of the "right after load" failures. A possible mitigation is to give the extension a stable base URL.
-
Ruled out:
- device lock and iOS data protection;
- an outdated tab URL on iOS;
- a macOS feature flag toggling (as the cause of tabs not connected to the extension);
- low-memory notifications;
- long freezes of the background as a cause of all-tab failures.
-
Not explained yet:
- tabs not connected to the extension on macOS;
- the "no background" cohort;
- WebKit error code 7;
- Lockdown Mode;
- the first page after an extension reload still failing (670 iOS and 232 macOS daily reports).
Relevant WebKit bugs
- Bug 317981: a message sent while the background is still starting is dropped. Fixed upstream, but still present on macOS 26.7.1, where we reproduced it for the first load of a new extension version.
- Bug 324743: a background worker is treated as ready before it has finished starting. Fixed upstream on September 22; not in 26.7 or 27.0. It fits the wake-up pattern, but we have no deterministic reproduction.
- Bug 320812: "Web Extensions load with significant delay on iOS 26.6 when Safari is cold launched". This is the slow website-data cleanup described above. Fixed upstream; which OS release ships the fix is unverified.
- Bug 314194, still open: Lockdown Mode blocks all service workers, so CPM cannot work for those users. We do not know how many users this affects.
- Bug 322881 and bug 324754: issues after a background reload. Probably not relevant to CPM, unproven.
- Bug 319976, fixed: a WebKit callback could fire twice on error. Relevant because the app uses this API.
- Not yet reported to WebKit:
- after a networking process crash, WebKit does not notice that the background worker is dead;
- the idle-unload timer starts before the background finishes loading, which causes error 6.
Telemetry added (phase 1, not shipped yet)
All values are coarse buckets or fixed labels, with no URLs or identifiers.
- On failure pixels:
- whether the background had finished starting, and how long it took (
bg_ready_after); - how long ago the background was created (
bg_view_age); - whether the app was in the foreground (
app_state); - how many tabs were open and how many were loading (
open_tabs,loading_tabs); - for error 6, the app state and the time until the error (
bg_load_error_app_state,bg_load_error_after).
- whether the background had finished starting, and how long it took (
- On recovery pixels:
- whether the failed page answered late or a later page answered (
recovery_document); - how long after the first failure answers resumed (
recovery_after).
- whether the failed page answered late or a later page answered (
How to read it:
- "Background not ready" together with "created a few seconds ago" means a slow wake-up.
- "Background ready" with no answer means a lost message.
- A recovery on the failed page means the answer was late, not lost.
- An error 6 about 30 seconds after the background was created points at WebKit's unload timer.