Realms, agents and the execution model

JavaScript · Expert · 12 min

Cross-frame bugs, workers, vm sandboxes and multi-threaded code all follow from this model.

The ECMAScript specification defines a few abstract building blocks that explain behaviour you'd otherwise find mysterious.

A realm is a global environment: a global object plus its own complete set of intrinsics — Array, Object.prototype, Promise, and so on. Each window, iframe, worker and Node vm context has its own realm. Objects can flow between realms in the same thread (e.g. a parent page reading an iframe's array), but their prototypes are that realm's intrinsics. Hence:

  • iframeArray instanceof Array is false in the parent — it inherits from the iframe's Array.prototype. Use Array.isArray, which works across realms.
  • Object.prototype.toString.call(x) and duck typing are more robust than instanceof for values that may cross realms.
  • Symbol.for symbols are shared across realms; other symbols and intrinsics are not.

An agent is a thread of execution: it has its own execution-context stack, its own job (microtask) queues and its own event loop provided by the host. A window and its same-origin iframes may share one agent (they're synchronously reachable), while each Worker is a separate agent. Agents communicate by message passing (structured clone, transferables) — or, within an agent cluster, by sharing memory through SharedArrayBuffer.

The spec delegates scheduling to the host (the browser via the HTML standard, or Node.js) through host hooks: that's why the event loop details live in the HTML spec, not ECMAScript, and why Node has extra phases.

A proposal called ShadowRealm would let code create a fresh, isolated realm synchronously, for sandboxing plugins and tests; until it ships, iframes, workers and Node's vm module fill that role.

Example

// Run with Node.js: each vm context is a separate realm
import vm from "node:vm";

const foreignArray = vm.runInNewContext("[1, 2, 3]");
console.log(foreignArray instanceof Array);       // false: different Array intrinsic
console.log(Array.isArray(foreignArray));         // true: realm-independent check
console.log(Object.prototype.toString.call(foreignArray)); // [object Array]

const ForeignObject = vm.runInNewContext("Object");
console.log(ForeignObject === Object);            // false: separate intrinsics

const sym = vm.runInNewContext('Symbol.for("app.id")');
console.log(sym === Symbol.for("app.id"));        // true: registry is shared

// structuredClone copies into *this* realm's intrinsics
const local = structuredClone(foreignArray);
console.log(local instanceof Array);              // true

Remember

  • A realm = global object + its own intrinsics; iframes, workers and vm contexts each have one
  • instanceof fails across realms — use Array.isArray or brand checks
  • An agent = thread with its own stack, job queues and event loop; workers are separate agents

Watch out for

  • Using instanceof Array or instanceof Error on values that came from an iframe or vm context.

Check yourself

  1. A parent page receives an array created inside a same-origin iframe. What does arr instanceof Array return in the parent?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Execution_model