both generations of the navigation timing API describe the same page load
Check id navigation-timing-dual-api
What an ordinary browser yields
Every milestone reached on both APIs reconciles through timeOrigin to within a couple hundred milliseconds, and timeOrigin itself matches the legacy navigationStart.
What a detector infers
The platform kept two generations of one API alive. performance.timing speaks Unix epoch; PerformanceNavigationTiming speaks milliseconds since timeOrigin. Separate objects, separate code paths, the same eight moments in a page's life — and timeOrigin is the exchange rate between them. That redundancy is free ground truth: you can audit a browser's time origin without asking it for the origin, by solving for it from the pair. The lesson generalises well past clocks: whenever a platform ships a deprecated API beside its replacement, the two are a coherence oracle, because tooling that spoofs the modern surface almost never remembers the fossil underneath it. Three guards keep it honest. The deprecated interface's PRESENCE is a gate rather than an assertion, so the day Chrome finally removes performance.timing this check goes quiet instead of firing — its own rot makes it silent, never wrong. Any milestone reading zero on either side is skipped, because milestones read zero until reached and comparing a zero against timeOrigin plus zero manufactures a timeOrigin-sized error, which would be a guaranteed false critical on a page still loading. And a page activated from a prerender is excluded outright: it keeps timeOrigin at prerender start while the two APIs anchor differently by design, so any real site using speculation rules would trip a naive version.
How to resolve it
Both APIs record the same instants through different code paths, and timeOrigin converts between them. Anything rewriting the modern surface has to rewrite the fossil underneath it in lockstep — which is easier to achieve by not rewriting either.
Read in the wild by
PerimeterX / HUMAN
PerimeterX's collector records the presence of the legacy performance.timing object and whether the modern performance.getEntries is a function as two separate boolean fields in one payload, alongside performance.navigation.type — it probes both API generations for existence rather than reconciling their milestones against each other.
PerimeterX main.min.js as deployed on ifood (PerimeterX_RE corpus, stample/ifood/source/main.min.js; identical copy at node_bridge/ifood/perimeterx/main.min.js). `function Cn(t)` runs a vendor-prefix sweep over ["","ms","o","webkit","moz"] with `u=""===c?"performance":c+"Performance"`, and on each pass does `hS[u]&&(r=!!hS[u].timing,h=hR(hS[u].getEntries)===ie)` — hS is the global, hR is the Babel _typeof helper and `ie="function"`, so r is `!!performance.timing` and h is `typeof performance.getEntries === "function"`. The two are emitted as separate fields: `t["eEQNDj0jDzg="]=r` (legacy timing present) and `t[hQ(798)]=h` with hQ(798)="NSEAa3NBAFs=". `function Co(t)` adds `t[hQ(819)]=hS.performance&&hS[hQ(199)].navigation&&hS.performance[hQ(820)][hQ(821)]`, where hQ(199)="performance", hQ(819)="b19aVSo/XWc=", hQ(820)="navigation", hQ(821)="type". Indices resolved against bug_report/sdk_drift_cases/2026-05-19_ifood/hQ_map.json. Observed on the wire in a captured payload (bug_report/sdk_drift_cases/2026-05-19_ifood/diff_result.json), the three arriving adjacent: {"k":"NSEAa3NBAFs=","value":true}, {"k":"eEQNDj0jDzg=","value":true}, {"k":"b19aVSo/XWc=","value":1}; the navigation-type field also appears in stample/ifood/sample/{4,5,6}/decoded_payload_2.json with value 0.
Every attribution traces to a published artifact. See the sources and their limits.
Other checks in Clocks & ledgers
- the monotonic clock and the wall clock name the same instant
epoch-vs-monotonic-clockA browser carries two physically different clocks: a monotonic counter anchored at performance.timeOrigin, and the wall clock behind… - a stylesheet that provably loaded appears in the performance timeline
resource-timing-vs-cssomFetching a resource writes two records: the DOM object you can see, and an immutable entry in the performance timeline. - wall-clock and monotonic time advance at the same rate
wallclock-vs-monotonic-rateA browser owns two clocks and they are not the same clock. Date.now() is wall time: it can be wrong, it can be corrected, it can jump… - PerformanceEntry timestamps land on the same grid performance.now() exhibits
perf-clock-grid-coherenceA timestamp's low bits are a fingerprint. DataDome ships its performance floats unrounded — 2253.7000000029802 rather than 2253.7 — and that… - every surface that reports when this document started agrees
navigation-origin-consensusOne document has one navigation start, and Chrome exposes it through four different doors: performance.timeOrigin, the legacy…
Clearcote is a browser built for fingerprint coherence
It is a Chromium fork, maintained by the same people who wrote this reference. It ships as a compiled browser rather than as a stealth script injected into someone else's — which is a description of how it is built, and is not an argument about how it behaves on this check.
This audit takes no position on how Clearcote scores on Clocks & ledgers checks, on this one, or anywhere else. It has no baseline corpus of other people's fingerprints to rank you against and no vendor scoreboard — nearly every check is self-referential, asking one browser the same question through two independent APIs and reporting whether both answers can be true at once. It runs identically on any browser, including ours. Run it on yours and read the result yourself.
See the other checks in Clocks & ledgers — the family navigation-timing-dual-api belongs to.