Realms, globals & WindowProxy
Understand browser realms, iframe globals, WindowProxy, cross-realm values, same-origin access, and safe data transfer between windows.
- 01Recognize separate realmsExplain why an iframe owns separate built-ins such as Array, Object, and Function.
- 02Choose cross-realm checksKnow why instanceof can fail across frames and when Array.isArray is the safer question.
- 03Move data deliberatelyChoose direct same-origin access, postMessage, structured cloning, or transfer for a frame boundary.
One realm per global
A page does not run JavaScript in one universal bucket. A realm is one JavaScript global environment: it has a global object, global names, and its own built-in objects such as Array, Object, and Function.
A realm is the JavaScript environment attached to one global object. A top-level document, each iframe document, and each worker global normally have separate realms.
globalThis.lessonName = "realms";console.log(globalThis.lessonName);Line 1 stores a property on this realm's global object. Line 2 reads the same property and prints realms. Another iframe has a different global object, so it does not gain this property by accident.
globalThis.cartName = "parent cart";const frameGlobal = { cartName: "frame cart" }; console.log(globalThis.cartName);console.log(frameGlobal.cartName); // A real iframe has a separate global object, not this plain teaching object.Line 1 gives the parent global one cart name. Line 2 is a tiny teaching stand-in for another global, with its own name. Lines 4 and 5 print parent cart and frame cart. In a browser, an iframe supplies that separate global environment for real.
This separation prevents accidental sharing. A variable placed on one page's global object does not become a variable in every frame. Code can still communicate, but it must deliberately use a same-origin reference or a message boundary.
This matters when a page embeds a checkout, an editor, a preview, or an old application inside an iframe. Each frame can run familiar JavaScript, yet each frame owns distinct constructors and global state. The previous Bindings: how DOM objects exist in JavaScript lesson explains how browser objects appear in one realm.
| Name | Meaning | Example |
|---|---|---|
| Realm | A JavaScript global environment with its own intrinsics. | A top page, an iframe, and a worker each create their own realm. |
| Global object | The object holding global names for one realm. | In a document realm it is a Window object. |
| WindowProxy | The stable object exposed as a frame's window reference. | It forwards to the frame's current Window after navigation. |
| Intrinsic | A built-in object created for a realm. | Each frame gets its own Array, Object, Function, and Promise. |
Each iframe has its own intrinsics
Intrinsics are the built-in objects that make JavaScript work, including constructors and their prototypes. The parent page's Array and an iframe's Array have the same job, but they are different objects.
const frame = document.createElement("iframe");document.body.append(frame); const FrameArray = frame.contentWindow.Array;const list = new FrameArray("tea"); console.log(list instanceof Array);console.log(Array.isArray(list));Line 1 creates a blank iframe. Line 2 places it in the document so it receives a window. Line 4 reads that window's Array, and line 5 creates a list with it. Line 7 prints false; line 8 prints true.
The first result surprises people because instanceof follows a prototype chain and compares it to this realm's Array.prototype. The second result asks a different question: is this value an array? That works across realm boundaries.
Try this iframe example in the console of a normal page you control. The lesson editor sandbox has an opaque origin, so reading even a blank iframe's contentWindow.Array throws a SecurityError there.
Each iframe is like a separate classroom with its own copy of the textbook. A page from class A's book is not from class B's book, even when the words are identical.
- In real life: Each classroom has its own textbook
- In JavaScript: Each realm has its own Array and Object
- In real life: A page comes from one classroom
- In JavaScript: A value keeps its creating realm
- In real life: The words can match in both books
- In JavaScript: Built-ins can look the same
- In real life: The book owner is still different
- In JavaScript: instanceof sees different constructors
Where the analogy stops: Realms are technical environments, not physical rooms; the analogy only explains separate copies of built-ins.
Window and WindowProxy
Window is the document's actual global object. WindowProxy is the stable object exposed when one browsing context refers to another through contentWindow, parent, or frames.
const frame = document.querySelector("iframe");const room = frame.contentWindow; console.log(room === frame.contentWindow);frame.src = "/next-page";console.log(room === frame.contentWindow);Line 2 saves the frame reference. Line 4 begins a navigation. The source is shown for reading, not running, because this lesson sandbox has no /next-page. The important idea is that the reference stays useful while the active document and its Window can change.
Think of WindowProxy as a school reception desk. You always speak to the reception, and it forwards you to the current teacher in that room. When the room changes teacher after navigation, the desk reference still works.
A school reception desk is a stable way to contact a room. The teacher can change, but the number still reaches whoever currently teaches there.
- In real life: Reception keeps the room contact
- In JavaScript: WindowProxy represents a browsing context
- In real life: A new teacher starts in the room
- In JavaScript: Navigation creates a new active Window
- In real life: The desk forwards calls
- In JavaScript: Proxy forwards operations
- In real life: The old desk number still works
- In JavaScript: Saved contentWindow references remain stable
Where the analogy stops: WindowProxy has security rules and exotic behavior that a real reception desk does not have.
Entry, incumbent, current, relevant
The HTML specification has four labels for realm-sensitive algorithms. They solve platform edge cases, not ordinary application decisions. Most code only ever needs current and relevant.
const frame = document.querySelector("iframe");const printInFrame = frame.contentWindow.print;printInFrame.call(frame.contentWindow);Line 1 gets a frame. Line 2 reads a method from that frame. Line 3 calls the method with the frame as its receiver. This is only a small picture; specifications use the labels below when their algorithms must decide whose settings or global object matter.
- Entry realm: the script that initiated the currently running script action.
- Incumbent realm: the most recently entered author code, or code that scheduled a callback.
- Current realm: the realm of the function currently running.
- Relevant realm: the realm belonging to a particular platform object, usually its creation realm.
For example, a method invoked on a frame's DOM object usually needs that object's relevant realm. The standards warn new specifications away from entry and incumbent when possible because their callback history is difficult to reason about.
const callback = { entry: "the script that began this work", incumbent: "the author code that scheduled it", current: "the function running now", relevant: "the realm of the platform object",}; console.log(callback.current);console.log(callback.relevant); // These strings are a vocabulary aid, not browser introspection.Lines 1 through 6 store one plain explanation for each label. Lines 8 and 9 print the function running now and the realm of the platform object. This is not a browser API that reveals hidden realm state; it is a small way to keep the four terms separate while reading a specification.
When application code calls a method, start with the receiver. A frame-owned DOM node normally brings its relevant realm with it. The current realm is simply where the function body is running at that moment.
Cross-realm objects and instanceof
Cross-realm object means a value made in one realm and inspected or used in another. Arrays are the friendliest demonstration because both realms expose an Array name, but those names refer to different constructor objects.
const parentArray = ["tea"];const frameArray = new FrameArray("tea"); console.log(parentArray instanceof Array);console.log(frameArray instanceof Array);console.log(Array.isArray(frameArray));Line 1 creates an ordinary parent array. Line 2 creates a frame array. Line 4 is true because the parent made that value. Line 5 is false, while line 6 is true because Array.isArray is the right cross-realm predicate.
Do not turn this into a rule that instanceof is broken. It is doing an identity-based prototype test. It is useful when the constructor identity is part of the contract. When you only need to recognize arrays from unknown realms, prefer Array.isArray.
Replay instrumented lesson code. It shows the observable constructor-identity rule; it is not an engine debugger.
script
Object.setPrototypeOf(frameArray, null); const parentThinksItIsAnArray = frameArray instanceof Array;const parentRecognizesAnArray = Array.isArray(frameArray); console.log(parentThinksItIsAnArray);console.log(parentRecognizesAnArray);The step-through uses a runnable stand-in: it removes an ordinary array's parent prototype, so instanceof is false while Array.isArray remains true. A real iframe has its own prototype; use the earlier example in a normal page's console to observe that browser boundary directly.
Compare two realms
The playground uses two named realms and one array. Pick who creates the array, then pick whose instanceof Array check runs. Reset returns to the useful cross-realm case: iframe creates, parent checks.
const frameArray = ["tea"];Object.setPrototypeOf(frameArray, null); const parentThinksItIsAnArray = frameArray instanceof Array;const parentRecognizesAnArray = Array.isArray(frameArray); console.log(parentThinksItIsAnArray);console.log(parentRecognizesAnArray);falseIt compares constructor identity through the prototype chain.
trueIt asks whether the value is an array, regardless of realm.
The value came from Iframe realm but instanceof uses Parent realm's Array constructor, so it is false. Array.isArray stays true.
Every combination still reports true for Array.isArray. That result is the point: the value's array nature and the identity of a realm's Array constructor are different facts.
Same-origin frames
The same-origin policy controls direct access between documents. Same-origin frames can read each other's document and JavaScript objects. Cross-origin frames cannot read ordinary properties such as document; browsers throw a SecurityError.
const frame = document.createElement("iframe");frame.onload = () => { console.log(frame.contentWindow.document.body.textContent);};frame.srcdoc = "<p>Same-origin frame</p>";document.body.append(frame);Line 1 creates an iframe. Lines 2 through 4 install a load handler before line 5 supplies same-origin HTML. The handler reads the document body and prints Same-origin frame. This works because the generated document belongs to this page's origin. Try it in the console of a normal page you control; the lesson editor sandbox has an opaque origin and blocks reads from contentWindow.
Cross-origin windows still allow a narrow set of safe operations. A page can call postMessage and can assign a cross-origin frame's location, but it cannot inspect the other document. See Windows and frames for the browsing-context details.
const frame = document.querySelector("iframe"); window.addEventListener("message", (event) => { console.log(event.data);}); frame.contentWindow.postMessage({ type: "cart", count: 3 }, "*");Line 3 listens for a message. Line 7 sends plain data to the frame. In production, replace * with the receiver's exact origin and validate event.origin plus the message shape before acting.
const event = { origin: "https://checkout.example", data: { type: "order-ready", id: "tea-3" },}; const isExpected = event.origin === "https://checkout.example" && event.data.type === "order-ready";console.log(isExpected); // A receiver checks origin and message shape before using the data.Lines 1 through 4 describe one received event. Line 6 checks both its origin and its message type. Line 7 prints true only for the expected checkout event. Real code should reject unfamiliar origins and data before it changes a cart or account.
Serializable and transferable values
Frame boundaries often need data rather than direct object access. Structured clone creates a supported data value in another context. It copies serializable values. A transfer moves ownership of a transferable value instead of copying it.
const cart = { item: "tea", count: 3 };const copy = structuredClone(cart); copy.count = 4;console.log(cart.count);console.log(copy.count);Line 1 creates the original cart. Line 2 copies it. Line 4 changes only the copy. The logs print 3 then 4, proving the two objects are independent.
const order = { id: "tea", count: 3 };const copy = structuredClone(order);copy.count = 4; console.log(order.count);console.log(copy.count); // Clone creates another value; it does not share the original plain object.Line 1 creates an order. Line 2 makes the clone, and line 3 changes that clone. Lines 5 and 6 still print 3 then 4. Cloning is useful when sender and receiver should each safely change their own copy.
const buffer = new ArrayBuffer(8);const moved = structuredClone(buffer, { transfer: [buffer] }); console.log(buffer.byteLength);console.log(moved.byteLength);Line 1 creates eight bytes. Line 2 transfers ownership. Line 4 prints 0 because the original is detached. Line 5 prints 8 because the moved buffer owns the bytes.
Replay instrumented lesson code. It shows the visible result of transferring an ArrayBuffer.
script
const moved = structuredClone(buffer, { transfer: [buffer] }); console.log(buffer.byteLength);console.log(moved.byteLength);const cart = { save() {} }; try { structuredClone(cart);} catch (error) { console.log(error.name);}Line 1 creates an object containing a function. Line 4 tries to clone it. The catch block prints DataCloneError. Functions and DOM nodes cannot be cloned because their behavior or platform identity is not plain transferable data.
| Boundary | What happens | Use it for |
|---|---|---|
| Same origin | Can read the other frame's document and JavaScript objects. | Useful for tightly coupled pages you control. |
| Cross origin | Cannot read document or ordinary properties; reads raise SecurityError. | Use postMessage with a precise target origin. |
| Serializable | Structured clone makes an independent copy. | Plain objects, arrays, maps, dates, and many data values. |
| Transferable | Ownership moves; the sender loses its usable original. | ArrayBuffer and MessagePort. |
const parentSymbol = Symbol.for("cart-status");const otherRealmSymbol = Symbol.for("cart-status"); console.log(parentSymbol === otherRealmSymbol); // Symbol.for uses an agent-wide registry, unlike Array constructors per realm.Lines 1 and 2 ask the global symbol registry for the same key. Line 4 prints true. This is a useful exception to remember: realms have separate Array constructors, while Symbol.for uses the registry shared by the JavaScript agent.
Practical choices
First ask whether two documents truly need direct access. Same-origin integration can share objects, but it also creates a tight dependency between two global environments. A message boundary makes the data contract visible and works when origins differ.
For values from an extension, iframe, or another window, avoid checking application types only with instanceof. Prefer realm-safe tests such as Array.isArray, explicit discriminant properties such as type, or schema validation for untrusted messages.
- Use
postMessagewith an exact target origin for cross-origin communication. - Validate received origin and message shape before using a message.
- Transfer large buffers when one receiver should own them; clone when both sides need independent data.
- Use
Array.isArraywhen arrays may come from another realm.
- A plain cart object with strings and numbers
- A Map of product IDs to quantities
- An ArrayBuffer sent with a transfer list
- One end of a MessageChannel
- A save function closing over a cart
- A button element from the document
Sort each value by what structured clone can do with it. Read the explanation after every choice.
Common misconceptions
- “An iframe is just visual.” It has a document, a global object, and its own realm.
- “window is always one object.” A cross-frame window reference is normally a WindowProxy that can survive navigation.
- “instanceof recognizes every array.” It compares one constructor's prototype, so realm boundaries matter.
- “Same origin means no boundaries.” It permits direct access but realms and constructor identity remain separate.
- “structuredClone serializes everything.” Functions and DOM nodes produce DataCloneError.
| Term | What it means | Not the same as |
|---|---|---|
| A realm | A global environment with its own intrinsics. | A browser tab or an operating-system process. |
| Window | The actual document global object. | Always the same object as a saved frame reference. |
| WindowProxy | A stable forwarding reference to a browsing context's active Window. | A copy of Window that freezes at the first page. |
| structuredClone | Copies supported data into a new value. | A way to copy functions, DOM nodes, or closures. |
Practice exercises
A product widget returns an array from its iframe. Which check should the parent use to recognize it?
Array.isArray(value)Array.isArray recognizes arrays across realms, while instanceof Array depends on which realm owns Array.
Read the program and type its exact output.
const buffer = new ArrayBuffer(4);
const moved = structuredClone(buffer, { transfer: [buffer] });
console.log(buffer.byteLength, moved.byteLength);It prints 0 4. The original buffer is detached and the moved buffer owns the bytes.
A parent saves frame.contentWindow before the frame navigates. Why does the reference remain useful?
WindowProxy lets a saved frame reference survive navigation while forwarding to the active Window.
Your analytics iframe is cross-origin. What safe browser API should exchange a small order summary?
frame.contentWindow.postMessage(message, targetOrigin);postMessage is the intentional channel across origins. Use the exact target origin and verify received origins.
What error name should your code expect when it tries to structured-clone an object containing a function?
structuredClone throws DataCloneError for functions because it cannot serialize executable code and captured closure state.
A cross-origin checkout iframe must notify its parent that an order is ready. What communication approach should it use?
Use postMessage to send a plain order object. The browser structured-clones the message data; the parent validates origin and content before using it.
Check your understanding
Use the quiz to separate constructor identity, browsing-context identity, origin checks, and data movement.
Question 1 of 7What does a browser iframe normally create for JavaScript?
Choose an answer to see the explanation.
Question 2 of 7What does this model print when an array's prototype is not the parent's Array.prototype?
Read the code, then predictconst list = ["tea"]; Object.setPrototypeOf(list, null); console.log(list instanceof Array); console.log(Array.isArray(list));Choose an answer to see the explanation.
Question 3 of 7What is WindowProxy for?
Choose an answer to see the explanation.
Question 4 of 7Which pair is most useful in ordinary specification reasoning?
Choose an answer to see the explanation.
Question 5 of 7How should two cross-origin frames exchange a plain message?
Choose an answer to see the explanation.
Question 6 of 7What does the transfer code print?
Read the code, then predictconst buffer = new ArrayBuffer(4); const moved = structuredClone(buffer, { transfer: [buffer] }); console.log(buffer.byteLength, moved.byteLength);Choose an answer to see the explanation.
Question 7 of 7Why does structuredClone({ save() {} }) throw?
Choose an answer to see the explanation.
Key takeaways
- Every document global has a realm with its own built-ins.
- WindowProxy is the stable reference for a browsing context across navigation.
- Across realms, instanceof can be false while Array.isArray is true.
- Most code only needs current realm and an object's relevant realm.
- Same-origin frames can access documents directly; cross-origin frames should communicate with postMessage.
- Structured clone copies supported data, while transfer moves ownership of values such as ArrayBuffer.
Remember the one-liner.
A realm is one global JavaScript environment, so an iframe's familiar built-ins are still different objects.
Coming next: The browser security model, where the same-origin policy, isolation, and browser process boundaries protect those separate worlds.