Files
obsidian-vault/work/projects/cpm-wake-init-detection-tech-design-2026-11-10.md
T

4.5 KiB

tags, updated, status
tags updated status
work
cpm
tech-design
pixel
web-extension
2026-11-11 draft

TD: Pixel Definition: CPM Wake Init Detection (macOS/iOS + embedded web extension)

Author: Alex M Reviewer: TBD (previous CPM pixel designs: Michal) Stakeholders: Michal, Konrad, Russell (from the previous Tech Designs; confirm). Extension change: the privacy-extension owners. Project: CPM: root-cause the stuck embedded extension messaging (macOS/iOS) Task: to create ("Extension lifecycle metrics") Previous Tech Designs: CPM breakage pixels, CPM failure diagnostics Privacy Triage: to request together with the CPM permanent breakage metric design, which this one extends Extension PR: duckduckgo-privacy-extension#3727

Background & Requirements

Failures after a background wake are the largest group with no fix: on macOS 1.214, 39% of all failures (0.68% of DAU); on iOS 7.244, 22% (0.06% of DAU).

The app cannot see where the startup message stops. For one page: page script sends init → background receives it → reads configuration → reads the user setting → checks the site → answers the page and refreshes the app's dashboard. The app sees only the last step, so every failure looks the same: no answer.

Requirements: find where the chain stopped, without changing what the extension does for the page, without collecting URLs or persistent identifiers, and with older and newer app and extension tolerating each other.

Problem Statement

For every tab the permanent breakage metric reports as silent, record the furthest init step the extension reported, so the next fix is picked from data.

The extension sends the app a fire-and-forget notification at each init step. The app keeps the furthest step per tab (for its current page only) and adds it as last_stage to web_extension_cpm_breakage. A notification is never an answer and never creates a report. Unknown step names are ignored.

last_stage The chain stopped
none nothing reached the app (script not injected, or a message was lost)
page_init_sent the page script sent init; the background never processed it
received init arrived; configuration or storage read did not finish
config_read waiting for the user setting from the app
settings_checked waiting for the site check
site_checked all checks passed; no answer arrived
response_sent the answer was sent to the page, but no dashboard refresh followed
response_failed sending the answer to the page failed

The extension change only adds these notifications inside the existing init handling; they never wait, throw or change what the page receives. The extension ships inside the app, so it reaches users with an app release; the app side can ship first (then last_stage is none).

Pros and Cons

  • Pros: turns "no answer" into a position in a known chain that maps to concrete fixes; stays out of the page's way.
  • Cons: notifications pass through the background we study, so the earliest failures end as none; up to seven extra notifications per page, load not measured (internal builds first); first data comes about two release cycles after the work.

Testing

App unit tests for the furthest step per tab, none without notifications, ignored unknown steps and the pixel definitions. Extension unit tests for the order of notifications (part not run yet). Not done: a real-WebKit run showing every step in order and the right last_stage for a stall at each step.

Additional Considerations

Privacy: one new enumerated parameter. Tab number and URL travel inside the app only; neither enters a pixel or a log. Privacy Triage is requested once for both designs. Security: no new private WebKit selectors; the new message is read-only. Site Breakage: no intended change. Experimentation: none; rollout by release channel. Operational: app side first, extension after review and the embedded-files update. Removed when the wake group has a fix or is closed. Localization: none.

Open Questions

  1. Are config_read and site_checked worth the extra notifications for the first read?
  2. Send page_init_sent only in the embedded build, or in every build?