What a realm is, and why a browser has several
A realm in the ECMAScript sense is a self-contained execution environment: one global object, one set of intrinsic objects, one copy of every native function. A single tab routinely holds many. The page itself is one realm. Every iframe gets its own. Every Web Worker, Shared Worker, and Service Worker gets its own. Even an iframe with no src attribute creates a fresh realm whose contentWindow exposes a clean global.
Realms are isolated from each other at the object level - iframe.contentWindow.Array is not the same object as the page's Array, which is why instanceof famously fails across frames. But they are not isolated at the fact level. All of them ask the same engine the same questions about the same device. navigator.hardwareConcurrency in a Worker reads the same CPU as navigator.hardwareConcurrency in the page. The timezone resolved by Intl in an iframe comes from the same system setting.
That asymmetry is the entire basis of the check. Object identity differs by design; reported facts must not.
The realm checks that carry the most weight
A probe typically spawns one or two extra realms and re-reads a short list of fields. The comparisons that matter most:
- Worker navigator agreement -
userAgent,platform,languages,hardwareConcurrency, anddeviceMemoryread inside a Worker must match the page. A blob-URL Worker takes three lines to create and is the single highest-yield probe in this family. - Worker timezone agreement -
Intl.DateTimeFormat().resolvedOptions().timeZoneresolved in a Worker must name the same IANA zone as the page. Timezone is a common target for per-session adjustment, and Workers are a common place for that adjustment to be missing. - Worker GPU agreement -
OffscreenCanvasgives Workers a real WebGL context, so the renderer string can be read from a second realm and compared against the page. - Fresh iframe contentWindow - create an empty iframe, read
contentWindow.navigator, and compare. Because the realm is created after any page-load-time modification ran, it often holds the original values. - Storage quota across realms -
navigator.storage.estimate()describes one origin-scoped quota and should report consistently from page and Worker alike.
None of these are exotic APIs. That is the point - the check is cheap enough to run on every page load.
Why realm gaps appear in the first place
The gap is structural, not accidental. JavaScript-level modification works by replacing properties on a global object - but there is one global object per realm, and code that runs in the page cannot reach into a Worker's global. A Worker is a separate thread with a separate WorkerGlobalScope; nothing the page does to window.navigator touches it. The same structural limit shows up in two related entries: why content scripts cannot hook page JavaScript and why hooks miss out-of-process iframes, where process boundaries make the isolation even harder to cross.
Closing the gap from JavaScript means intercepting every realm-creation path - Worker, SharedWorker, OffscreenCanvas, every iframe insertion, srcdoc documents, and blob URLs - and re-applying the same values inside each one before any page script observes it. That interception layer is itself observable, because the constructors doing the intercepting no longer pass native function integrity checks.
Engine-level implementations avoid the problem by construction: if the value is changed inside the browser engine rather than in page JavaScript, every realm inherits it because every realm reads from the same engine state. This is the practical reason patched Chromium and Firefox builds behave differently from injected scripts, and why a managed web data API that owns the browser layer does not expose this class of mismatch to the pages it visits.