cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Windows, iframes & postMessage

Learn how popups, iframes, sandboxing, postMessage, origin checks, and clickjacking defenses let browser windows communicate safely.

By the end, you can
  • 01
    Open windows deliberatelyUse popups only from user gestures, handle blocked windows, and understand noopener.
  • 02
    Frame safelyExplain same-origin iframe limits, sandbox tokens, and why allow-scripts plus allow-same-origin is dangerous for same-origin content.
  • 03
    Validate messagesSend structured-cloned data with postMessage, then check event.origin, event.source, and data shape before acting.

Several windows, one careful page

A web app is not always one document in one tab. It may open a popup for sign-in, embed a payment widget in an iframe, preview untrusted content in a sandbox, or receive a message from a map, video player, or account portal. Those pieces are all browsing contexts: separate places where a document lives.

The important idea is simple: a reference is not permission. Your page may hold a reference to another window, but the browser still decides what can be read, changed, or messaged. That decision starts with the same-origin policy from the CORS lesson: scheme, host, and port must match before one document can freely inspect another.

Popup or iframe?
FeaturePopupIframe
Created bywindow.open() or a link with target="_blank"An <iframe> element in the page
Where it appearsA separate tab or window in most browsersInside the current page’s layout
Common useOAuth sign-in, print previews, optional toolsEmbedded maps, payments, videos, widgets, isolated demos
Safety habitOpen from a click, handle null, use noopenerUse sandbox, a narrow src, and message validation
Real-life analogyAn iframe is a window into a neighbor's house

An iframe lets one page appear inside another. If the framed page belongs to the same origin, it is like another room in your own house. If it belongs to a different origin, you can see that it exists, but you cannot reach through the window and move the furniture.

In real life: A window in your own house
In JavaScript: A same-origin iframe you may read and change
In real life: A window into a neighbor's house
In JavaScript: A cross-origin iframe you can see but cannot reach into
In real life: Knocking and passing a note
In JavaScript: Using postMessage instead of reading private state

Where the analogy stops: A real neighbor can invite you in. Browser permission is stricter: the origin, sandbox tokens, and headers decide what code can do.

This lesson builds the safe habits: open popups only when the user asked, treat cross-origin frames as opaque, send messages with a narrow target, validate messages on arrival, and protect sensitive pages from being framed in the first place.

window.open and popups

INTERACTIVE

A popup is a top-level browsing context: usually a new tab or window. JavaScript opens one with window.open(url, target, features). In most browsers, a popup is allowed when that call happens directly during a user gesture, such as a button click. Calls from timers, page load, or background code are often blocked, and window.open returns null.

The safest popup code has a boring shape: call it from a click, check for null, do only what the browser allows, then clean up. The lab opens about:blank, which inherits the opener’s origin, writes a tiny document, reads closed, and closes it. It also shows that noopener cuts the opener link and returns null.

Popup lab: open, inspect, close
Popup patternPop out in the code editor (opens in a new tab)JavaScript
button.addEventListener("click", () => {  const popup = window.open("about:blank", "lessonPopup", "width=360,height=240");  if (!popup) return "blocked";  popup.document.write("<h1>Hello from the opener</h1>");  console.log(popup.closed);  popup.close();}); const safePopup = window.open("about:blank", "_blank", "noopener");console.log(safePopup);
Popup loguser gesture
  • Click a button to open a popup from a user gesture.
Try it yourself

Browsers usually allow this because the call happens directly inside a click handler. Code that tries later, without a user gesture, is often blocked.

The popup content is a tiny same-origin about:blank document written by the opener. Close any tab your browser keeps open after the demo.
Links need the same habit

For links that open a new tab, write target="_blank" rel="noopener". Modern browsers often default target="_blank" to noopener, but being explicit documents the security reason: the new page should not control its opener.

Iframes and same-origin access

An iframe embeds another document inside the current page. Same-origin iframes are powerful: the parent can read their DOM, call functions, and share origin-scoped storage. Cross-origin iframes are intentionally narrow. The parent can keep a window reference, but most reads throw a security error.

What a window reference really permits
RelationshipWhat JavaScript can usually doWhat it cannot do
Same-origin iframeRead and change its DOM, call functions, read storage for that originBypass sandbox tokens if the sandbox still blocks an action
Cross-origin iframeHold a window reference, call postMessage, focus, blur, close, read closed, length, and assign location in limited waysRead its DOM, cookies, storage, URL path, variables, or most properties
Sandboxed without allow-same-originRun only the permissions named by sandbox tokens; event.origin becomes "null"Behave as the parent’s normal same-origin document

That narrow list is still useful. Cross-origin references can usually use postMessage, focus, blur, close, read simple properties such as closed, length, and frames, and assign location in limited ways. They cannot read the other page’s DOM, cookies, storage, variables, or full URL.

Fun fact

The browser does not care that two pages are both on your laptop during development. If the scheme, host, or port differs, they are cross-origin. The URL objects lesson is helpful for reading those pieces precisely.

postMessage and origin checks

STEP THROUGH

When two windows are not allowed to read each other directly, they can still cooperate by sending messages. The sender calls otherWindow.postMessage(data, targetOrigin). Later, the receiver gets a message event. That event has data, origin, and source.

Real-life analogypostMessage is passing a note through a mail slot

Anyone can push a note through a mail slot. Before you unlock a door, you read the return address and check who handed it over. targetOrigin is like writing the recipient’s name on the envelope: if the frame navigated somewhere else, the browser refuses delivery.

In real life: The note
In JavaScript: event.data, copied with structured clone
In real life: The return address
In JavaScript: event.origin
In real life: The person who handed it over
In JavaScript: event.source
In real life: Writing the recipient's name
In JavaScript: targetOrigin

Where the analogy stops: A paper note can be forged. Browser origins are supplied by the browser, but you still must validate the sender and the data shape before acting.

Opaque sandboxed frames are the awkward case. Without allow-same-origin, their origin is exposed to message handlers as the string "null". You cannot name that as a normal URL origin when sending, so * is required for delivery. That makes the receiving checks even more important.

postMessage lab: trusted frame and stranger frame
Parent-side handlerPop out in the code editor (opens in a new tab)JavaScript
const frame = document.querySelector("iframe");const trustedWindow = frame.contentWindow; window.addEventListener("message", (event) => {  if (event.origin !== "null") return;  if (event.source !== trustedWindow) return;  show("accepted", event.data);}); trustedWindow.postMessage({ kind: "ping" }, "*");
Real framesopaque origin

Use the controls to send messages.

Try it yourself

The trusted frame is sandboxed without allow-same-origin, so its message origin is the string null. A specific page origin does not match that opaque origin; * is needed for delivery, then the receiver checks origin, source, and data.

Both frames use predefined srcdoc and sandbox=allow-scripts. The parent validates messages by event.source before accepting them.

The live lab uses two sandboxed srcdoc frames. Both have origin "null", but only one is the stored trusted contentWindow. The stranger can copy the data shape, but it cannot pass the event.source check.

Step through the message round trip
Step 0 of 6Ready
Your turn: follow the blue line

Choose the trusted frame or the stranger, then step through the origin and source checks.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
const trustedFrame = "lesson-frame"; function receiveMessage(event) {  const originOK = event.origin === trustedOrigin;  const sourceOK = event.source === trustedFrame;  if (!originOK || !sourceOK) return "rejected";  return "accepted: " + event.data.kind;} receiveMessage(incomingEvent);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Choose which frame sends the message
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Message data is structured-cloned: plain objects are copied into the other window, not shared. Functions and DOM nodes cannot be cloned. The next lesson goes deep on structured cloning, transfer, and channels; here the key habit is validation before action.

Safe or unsafe message handling?
  • Checks event.origin and event.source before updating state
  • Sends a secret token with targetOrigin: "*"
  • Accepts only objects with type: "cart-ready" and a numeric id
  • Runs whatever command name arrives in event.data.action
  • Uses child.postMessage(message, "https://pay.example") for a payment frame
  • Accepts every message whose origin is "null"
Try it yourself
0 of 6 correct

Sort each realistic snippet. Look for target origin, origin checks, source checks, and data validation.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Sandboxed frames

INTERACTIVE

The sandbox attribute starts an iframe with many powers removed. Then tokens add specific powers back. A bare sandbox blocks scripts, forms, popups, modals, and same-origin treatment. Tokens such as allow-scripts, allow-forms, allow-popups, allow-modals, and allow-same-origin each open one door.

Sandbox lab: rebuild the frame with different powers
Sandbox capability reportPop out in the code editor (opens in a new tab)JavaScript
// The parent rebuilds the iframe with selected tokens.frame.sandbox = "allow-scripts allow-forms";frame.srcdoc = predefinedHtml; // Inside predefinedHtml:parent.postMessage({  scriptRan: true,  origin: self.origin,  canSubmitForms: sandbox.contains("allow-forms"),  canOpenPopups: sandbox.contains("allow-popups"),  canShowModals: sandbox.contains("allow-modals")}, "*");
Frame sandboxallow-scripts
script runsyes
reported originnull
forms allowedno
popups allowedno
modals allowedno
Try it yourself

Start locked down, then add only the powers the frame truly needs. Without allow-scripts, this demo frame cannot report from inside at all.

The visible frame runs predefined srcdoc only. Without allow-scripts, Chrome logs “Blocked script execution” in the console: that is the sandbox working. Popup and modal behavior can still vary, so the table describes the permissions the sandbox grants.
The dangerous pair

Be very cautious with sandbox="allow-scripts allow-same-origin" on same-origin content. Scripts can run, and the frame is treated as same-origin, which can let it remove or work around the sandbox you expected to enforce. For untrusted examples, prefer an opaque origin and communicate with validated messages.

This is why the CORS sandbox demo and the postMessage lab both use predefined srcdoc and sandbox="allow-scripts". The code can run, but the frame stays opaque and all communication goes through a small message protocol.

Clickjacking: when the visual page lies

INTERACTIVE

Clickjacking, also called UI redressing, is not about reading another page. It is about tricking a user into clicking it. The attacker frames a sensitive page, puts a decoy on top, and lines up the real button underneath the fake one.

Real-life analogyClickjacking is a transparent sheet over a real button

Imagine a transparent sheet laid over a real button. The label on the sheet says Play, but your finger presses the Transfer money button underneath. That mismatch is the danger.

In real life: A transparent sheet
In JavaScript: A decoy layer above an iframe
In real life: The button underneath
In JavaScript: The real framed action
In real life: Thinking you clicked Play
In JavaScript: The user sees the decoy
In real life: Actually clicking Transfer
In JavaScript: The browser sends the click to the hidden target

Where the analogy stops: Real attacks tune pointer events, opacity, and layout. This lesson uses a visible slider so you can inspect the trick safely.

Clickjacking lab: reveal the layer trick
<iframe title="fake bank" sandbox srcdoc="...bank button..."></iframe><div class="decoy">  <p>Looks like a video player.</p>  <button>Play</button></div> /* Lower the decoy opacity to reveal the hidden frame underneath. */
Stacked pages96% decoy

Video preview

The pointer is over the decoy, but another page can be underneath.
Try it yourself

At high opacity you see a harmless decoy. Lower it and the framed fake bank underneath appears. Real defenses are response headers from the sensitive site, not a script inside the victim page.

The bank is fake, sandboxed srcdoc for this lesson. No money, network, or external page is involved.
Reliable clickjacking defenses
DefenseSet byWhat it means
X-Frame-Options: DENYServerNo site may frame this page.
X-Frame-Options: SAMEORIGINServerOnly pages from the same origin may frame it.
Content-Security-Policy: frame-ancestors 'self' https://partner.exampleServerModern, flexible allow-list for who may embed the page.
Frame-busting scriptPage JavaScriptUnreliable because scripts can be blocked, raced, or sandboxed.

This public lesson page does not send those protective headers because there is no sensitive action to protect. An admin site, account settings page, or payment approval page should. In our stack, the admin site sends X-Frame-Options: DENY; public lessons do not need to block learners from embedding harmless content.

Where you will use this

These APIs show up in ordinary product work:

  • OAuth sign-in. Open a provider popup from a click, use noopener when appropriate, and receive a final success message from a controlled redirect page.
  • Payment widgets. Embed a cross-origin iframe, send it setup data with a specific targetOrigin, and accept only messages from its known origin and source.
  • CMS previews. Render preview HTML in a sandboxed iframe so it cannot mutate the editor page. Use messages for height, navigation, or selection events.
  • Interactive docs. Run risky demos in sandboxed srcdoc frames and report results back through validated messages.
A small production checklist
  1. Store the expected popup or contentWindow reference.
  2. Send with a specific targetOrigin unless the target is intentionally opaque.
  3. Check event.origin, event.source, and the data shape.
  4. Ignore unknown messages. Do not throw from a global message handler.
  5. Protect sensitive pages with X-Frame-Options or CSP frame-ancestors.

Common misconceptions

"I have a window reference, so I can read it."

Not across origins. A reference lets you use a few safe operations, especially postMessage, but most reads are blocked.

"Checking origin is always enough."

Opaque sandboxed frames can all report "null". Same-origin pages can also contain multiple frames. Check event.source too.

"targetOrigin: * means insecure every time."

It is sometimes necessary for opaque origins, but do not send secrets with it, and validate on receipt.

"Sandbox makes any iframe safe."

The tokens matter. In particular, allow-scripts plus allow-same-origin can undermine a sandbox for same-origin content.

"Frame-busting JavaScript prevents clickjacking."

It is unreliable. Use server headers: X-Frame-Options or CSP frame-ancestors.

Practice: safe windows and frames

5 EXERCISES
Exercise 1 · Warm-upChoose the target origin

A parent page needs to send a non-secret ping to a sandboxed srcdoc frame whose origin is "null". What targetOrigin is required?

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticePredict the validator

    Read the code and predict the printed word.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const trustedOrigin = "null";
    const trustedSource = "frame-a";
    const event = { origin: "null", source: "frame-b" };
    console.log(event.origin === trustedOrigin && event.source === trustedSource ? "accept" : "reject");

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeRead popup.closed

      What two values print, in order?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const popup = { closed: false, close() { this.closed = true; } };
      console.log(popup.closed);
      popup.close();
      console.log(popup.closed);

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · ChallengePick a clickjacking defense

        Name one reliable header or directive that prevents a sensitive page from being framed by attackers.

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeReview the unsafe handler

          Find the security problem in the handler. Then type the risky sandbox token pair from this lesson.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          window.addEventListener("message", (event) => {
            if (event.origin === "null") {
              runCommand(event.data.action);
            }
          });

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: check your understanding

            7 QUESTIONS

            Your score counts first tries. Read the explanations for wrong answers; they are written like mini code reviews.

            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7What should a popup-opening function check first after window.open?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does this message validator print for the spoofed frame?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const trustedOrigin = "null";
              const trustedSource = "lesson-frame";
              const event = { origin: "null", source: "stranger-frame" };
              console.log(event.origin === trustedOrigin && event.source === trustedSource ? "accept" : "reject");

              Choose an answer to see the explanation.

            3. Question 3 of 7Why is targetOrigin: "*" sometimes necessary for sandboxed srcdoc frames?

              Choose an answer to see the explanation.

            4. Question 4 of 7Which cross-origin window operation is generally allowed?

              Choose an answer to see the explanation.

            5. Question 5 of 7What is risky about sandbox="allow-scripts allow-same-origin" on same-origin content?

              Choose an answer to see the explanation.

            6. Question 6 of 7What does this popup model print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const popup = { closed: false, close() { this.closed = true; } };
              console.log(popup.closed);
              popup.close();
              console.log(popup.closed);

              Choose an answer to see the explanation.

            7. Question 7 of 7Which is a reliable clickjacking defense?

              Choose an answer to see the explanation.

            Key takeaways

            • Popups should open from user gestures; handle null, closed, and noopener.
            • Same-origin iframes are readable; cross-origin iframe references are narrow but can use postMessage.
            • postMessage data is structured-cloned and delivered later as a message event.
            • Validate event.origin, event.source, and the data shape before acting.
            • Sandbox tokens add back powers one by one; avoid risky combinations for same-origin untrusted content.
            • Clickjacking is stopped by server headers such as X-Frame-Options and CSP frame-ancestors.

            One-liner.
            Windows and frames can cooperate safely when the browser limits access and your code validates every message before it acts.

            Up next: Messaging & structured cloning.

            CompleteFrontend Clear concepts. Working examples.