v151 · origin trial · performance · reporting api
Declarative Performance Observer
A browser-resident telemetry system that reports performance metrics from navigation initiation to page termination. Activated by a declarative Performance-Observer HTTP response header rather than JavaScript, it captures data even when the request fails with a network error or the renderer process is killed by the OS, and delivers a consolidated report at session termination via the Reporting API.
This feature is in origin trial in Chrome 151–155 on desktop, Android, and WebView (Intent to Experiment; ChromeStatus API record, stage data fetched 2026-07-28). The proposal is pre-incubation and its TAG review is pending, so the header grammar and payload format may change.
- To use it on your origin: register for the “Declarative Performance Observer” trial at developer.chrome.com/origintrials and serve the token (origin-trial feature name
DeclarativePerformanceObserver). - For local development: launch Chrome with
--enable-blink-features=DeclarativePerformanceObserver. The runtime feature isDeclarativePerformanceObserver(status: experimental; it follows the origin trial unless the base feature is overridden by Finch or command line). - Feedback: explainer issue tracker.
at a glance
| What it is | Declarative (HTTP-header-driven) activation of browser-process performance telemetry: a Performance-Observer response header selects entry types; the browser collects them out-of-band and POSTs a consolidated report at session end via the Reporting API |
|---|---|
| What it is not | A replacement for the JavaScript PerformanceObserver API, Network Error Logging, or crash reporting (explicit non-goals in the explainer) |
| Activation | Performance-Observer HTTP response header on the top-level main-document navigation only — first-party, no JavaScript API, no subresource/iframe activation |
| Delivery | Reporting API: POST with Content-Type: application/reports+json to an endpoint named by the header's report-to directive and defined in Reporting-Endpoints |
| New entry type | PerformanceSessionEndTiming (entryType "session-end") — an anonymous terminal marker giving precise session duration |
| Chrome status | Origin trial, Chrome 151–155 (desktop, Android, WebView) — the milestone listing is authoritative (verified 2026-07-28) |
| Spec status | Pre-incubation explainer (“early design sketch”); WICG proposal #280 open; TAG review pending (per the Intent to Experiment) |
| Tracking bug | crbug.com/505208781 |
| ChromeStatus | 6594955352080384 — Declarative Performance Observer |
why it exists
JavaScript performance APIs are bound to the page's execution environment, so three classes of real user-journey data escape them:
- Early network failures. If a navigation fails before any payload arrives (DNS timeout, connection refused, TLS failure), no JavaScript runs and the site never learns the visit happened — success-rate denominators are wrong. NEL captures network errors but cannot correlate them with application journeys.
- Abrupt terminations. On out-of-memory kills (common on low-end mobile) or sudden tab closures,
unload/pagehidebeacons are lost — andunloadis deprecated anyway because it breaks BFCache. - Session end. There is no reliable way to timestamp when the user actually left, so dwell time is approximate.
The proposal moves collection into the browser process: events stream out of the renderer as they happen, so an OOM kill cannot take the already-recorded data with it, and the browser itself finalizes the report at the true session boundary.
Source: explainer — introduction and use cases; ChromeStatus motivation.how it works
- The server responds to a top-level navigation with
Performance-Observer(selecting entry types and a reporting endpoint) plus aReporting-Endpointsheader defining that endpoint. - The browser process records the requested built-in entries (e.g.
navigation,mark,visibility-state) and allowlistedperformance.mark()/measure()events for the life of the session — no page JavaScript involved. - On session termination (tab close, navigation away, BFCache entry, early network error, renderer crash), the browser finalizes the payload — appending a terminal
PerformanceSessionEndTimingentry — and POSTs it to the endpoint via the Reporting API. - With
capture-early-failures, failures that happen before any response (when no header could have been received) are persisted to a bounded on-disk buffer and flushed on the next successful navigation to the same origin.
report format
The payload is a JSON array of report objects delivered via the Reporting API with Content-Type: application/reports+json. Each report carries the standard Reporting-API fields plus a body whose entries array holds objects extending PerformanceEntry:
[{
"type": "performance-observer",
"age": 100,
"url": "https://www.example.com/second",
"user_agent": "Mozilla/5.0 … Chrome/146.0.0.0 …",
"body": {
"entries": [
{ "name": "https://www.example.com/second", "entryType": "navigation",
"startTime": 0, "domainLookupStart": 68, "domainLookupEnd": 120,
"connectStart": 122, "secureConnectionStart": 160,
"requestStart": 196, "responseStart": 562, "activationStart": 0 },
{ "name": "hero-image-loaded", "entryType": "mark",
"startTime": 780, "duration": 0,
"detail": { "additionalinfo": "user defined arbitrary data" } },
{ "name": "hidden", "entryType": "visibility-state",
"startTime": 13870, "duration": 0 },
{ "name": "session-end-event", "entryType": "session-end",
"startTime": 240200, "duration": 0 }
]
}
}]
detailon mark entries passes through the arbitrary data set viaperformance.mark()'sdetailoption.- Time-based fields are subject to standard HR-Time clock resolution coarsening.
- Multiple reports may be bundled into one request (the explainer's worked example batches two).
- The explainer does not define a JSON Schema for the payload; the structure above is its documented example format (pre-incubation — field set may change).
synthesized navigation entries for early failures
When a network error or early abandonment happens before the response completes, the failure is reported as a synthesized PerformanceNavigationTiming entry: milestones that were never reached (e.g. domainLookupEnd, responseStart, loadEventEnd) are set to 0, so the last non-zero milestone marks where the failure occurred. The explainer's example shows a DNS failure as domainLookupStart: 50 with every subsequent field zeroed.
capture-early-failures and deferred reporting
On a first-ever navigation that fails before any response, the browser cannot know the origin wanted telemetry — the activating header never arrived. The capture-early-failures directive opts the origin into persisting that intent:
- Once set, an origin-level flag tells the browser to record subsequent early navigation failures (pre-response) and persist them to a bounded on-disk buffer.
- Reporting endpoints are deliberately not persisted (the Reporting API v1 is ephemeral and document-tied). Instead, when a later navigation to the same origin succeeds and provides a
Reporting-Endpointsheader, the buffered failure reports are flushed alongside the current session's reports at session termination. If the user never returns, the buffered report is lost — an accepted tradeoff. - Quotas: a 640KB memory buffer per document (when full, newest entries are silently dropped, preserving early-load metrics); persisted reports are limited to 640KB globally per storage partition in Chromium, evicted FIFO.
- Expiry: both the persisted failure reports and the origin-level opt-in flag expire after 7 days in Chromium (cleaned up on database initialization); reports are deleted immediately once successfully sent.
- Clearing: a Clear-Site-Data header with
"storage", or the user clearing site data/history, purges both the buffer and the flag. Storage is partitioned by top-level site and isolated between regular and Incognito profiles. - Serving
capture-early-failures=falseexplicitly disables the origin flag. By default nothing is persisted.
session lifecycle and deactivation
| Event | Behavior |
|---|---|
| Header received on top-level navigation | Activation: browser begins collecting the requested entries for this document's session |
| Tab closure / navigation away | Report finalized and dispatched |
| BFCache entry | Report finalized and dispatched; if the page is restored, a new session begins automatically |
| Early network error | Synthesized zero-filled navigation entry reported (persisted first if no endpoint is available and capture-early-failures opted in) |
| Renderer crash (e.g. OS OOM kill) | Browser process still holds the accumulated payload and flushes it — this is the core reliability win over JS beaconing |
| Report dispatched | Deactivation: observation does not outlive the document; with no header on the next navigation, nothing is collected, stored, or reported |
examples
Enable telemetry for navigation, marks, and visibility changes, allowlist two application marks, opt into early-failure capture, and name the reporting endpoint:
Reporting-Endpoints: telemetry="https://log.example.com/v1"
Performance-Observer: report-to="telemetry",
entry-types=("navigation" "mark" "visibility-state"),
include-user-timing=("hero-image-loaded" "next-link-clicked"),
capture-early-failures=?1
Emit an allowlisted mark with attached detail from page JavaScript (the only JS involved — collection and delivery remain browser-side):
performance.mark("hero-image-loaded", {
detail: { additional_info: 12345 }
});
// reported as:
// { "name": "hero-image-loaded", "entryType": "mark",
// "startTime": 780, "duration": 0,
// "detail": { "additional_info": 12345 } }
There is no Chrome Platform Showcase demo for this feature yet (the showcase route returns 404 as of 2026-07-28) — server-side header configuration is not meaningfully demonstrable in a client-side demo frame.
Source: explainer — syntax and report format.browser compatibility
No BCD entry and no webstatus.dev web-feature exists for this proposal (both verified 2026-07-28), so this interim table is built from linked primary sources rather than BCD:
| Browser | Support | Evidence |
|---|---|---|
| Chrome / Edge (Chromium) | Origin trial 151–155 (desktop, Android, WebView) | ChromeStatus API record; Intent to Experiment |
| Firefox | No signal | ChromeStatus vendor views (no position recorded) |
| Safari | No signal | ChromeStatus vendor views (no position recorded) |
| WPT | None found upstream | No declarative-performance-observer directory or test in the web-platform-tests tree (checked 2026-07-28; performance-timeline suite covers only the imperative API) |
security & privacy
- Header-only, first-party activation. There is no JavaScript API and no subresource or iframe activation — third-party scripts cannot enable observation without the origin's server-side consent.
- Telemetry pollution. Third-party scripts can call
performance.mark()with allowlisted names and pollute data; the explainer's mitigation is to use specific, non-generic mark names. - DoS bounds. 640KB memory buffer per document (newest dropped when full); 640KB disk quota per storage partition for persisted failure reports (FIFO eviction).
- Data minimization. Only explicitly requested entry types and allowlisted marks are collected; the session-end entry is anonymous (timestamp only). Persisted data and the origin flag expire after 7 days; nothing persists by default.
- Side-channels. Timestamps are subject to standard high-resolution-timer coarsening (Spectre-class mitigation) — explainer — timer resolution and side-channels.
- Endpoint isolation. Reports go only to endpoints the document's own headers configured; note that multiple origins sharing one endpoint can be correlated by it (same caveat as shared analytics, per the Reporting API).
- Network leakage. In-memory queued reports are discarded on network-interface change per Reporting API §3.6; persisted failure reports survive but are only ever flushed during an active navigation to their own origin, exposing no new hostnames.
- Partitioning & Incognito. All state lives in partitioned storage, isolated by top-level site and between regular and Incognito profiles.
specifications
| Specification | Status |
|---|---|
| Declarative Performance Observer (explainer) | Pre-incubation — personal-repository proposal, no community adoption (WICG proposal #280 open) |
| Reporting API | Delivery mechanism (cg-draft) |
| Performance Timeline | Defines PerformanceEntry / PerformanceObserver the payload extends (no declarative variant covered) |
| Navigation Timing | Defines PerformanceNavigationTiming (synthesized on early failures) |
| User Timing | Defines performance.mark()/measure() and detail |
| High Resolution Time | Clock resolution for time-based fields |
see also
- Chrome Platform Status — Declarative Performance Observer
- gendn — the
Performance-Observerheader (directive reference) - gendn —
PerformanceSessionEndTiming - Explainer (explainers-by-googlers) · issue tracker
- blink-dev — Intent to Prototype
- blink-dev — Intent to Experiment (origin trial 151–155)
- Chromium tracking bug 505208781
- WICG proposal #280
- MDN — PerformanceObserver (the imperative API this complements)