---
title: 'Embedded Content'
source: 'https://academia.sh/en/courses/web-fundamentals-and-html/embedded-content'
course: 'Web Fundamentals and HTML'
language: en
updated: '2026-08-17T18:09:32+00:00'
license: 'CC BY-SA 4.0'
---

# Embedded Content

Embedding another document with a frame, the embedded document's isolation, the capability-restriction mechanism, and the performance and accessibility consequences of embedding.

Audio and video were the document's own resources. Some content,
though, is the whole of another document: a map, a player, a section
from another site. Embedding these means placing a second document
inside the document.

The `iframe` element does this job. The element's name means "inline
frame," and that is exactly what it is: a separate document, loaded
on its own and with its own tree, within the document's flow.

## The Embedded Document Is a Separate Document

The document inside `iframe` is not part of the outer document's
tree. It has its own root, its own head section, its own styles, and
its own scripts. The outer document's style rules do not get applied
inside; the script inside cannot access the outer document's tree
directly.

This separation is not a limitation, it is the entire rationale for
embedding. Placing content from an uncontrolled source directly into
your document gives that content access to the whole document. The
frame establishes the boundary between them.

The boundary rests on the concept of **origin**: the trio of scheme,
host, and port. Same-origin documents can access each other's tree;
different-origin ones cannot. The address parts separated in the What
the Browser Does
lesson in the How the Internet Works curriculum turn into a security
boundary here.

## The Isolation Attribute

Different-origin separation draws a basic boundary, but is not
enough: the embedded document can still run scripts, submit
forms, open a new window, navigate the top document to another
address. The `sandbox` attribute shuts these capabilities off.

The attribute works in reverse: the moment it gets written, **no
capability gets granted**; the capabilities wanted back get listed one
by one.

```js
// embed.mjs — capability model of the sandbox attribute
const CAPABILITIES = [
  ["allow-scripts", "running scripts"],
  ["allow-forms", "submitting forms"],
  ["allow-popups", "opening a new window"],
  ["allow-same-origin", "being counted as its own origin"],
  ["allow-top-navigation", "navigating the top document elsewhere"],
  ["allow-modals", "opening a modal window"],
];

function resolve(attrValue) {
  if (attrValue === null) return new Set(CAPABILITIES.map(([name]) => name)); // no sandbox
  return new Set(attrValue.split(/\s+/).filter(Boolean));
}

const cases = [
  ["no sandbox attribute", null],
  ["sandbox (empty)", ""],
  ["sandbox=allow-scripts", "allow-scripts"],
  ["sandbox=allow-scripts allow-forms", "allow-scripts allow-forms"],
];

for (const [label, value] of cases) {
  const open = resolve(value);
  console.log("[" + label + "]");
  for (const [key, description] of CAPABILITIES) {
    console.log("  " + (open.has(key) ? "open" : "closed").padEnd(6) + "  " + description);
  }
}

console.log("--- dangerous combination ---");
const both = resolve("allow-scripts allow-same-origin");
console.log(
  "allow-scripts + allow-same-origin:",
  both.has("allow-scripts") && both.has("allow-same-origin")
    ? "the frame can remove its own sandbox attribute"
    : "isolation is preserved",
);
```

```
[no sandbox attribute]
  open    running scripts
  open    submitting forms
  open    opening a new window
  open    being counted as its own origin
  open    navigating the top document elsewhere
  open    opening a modal window
[sandbox (empty)]
  closed  running scripts
  closed  submitting forms
  closed  opening a new window
  closed  being counted as its own origin
  closed  navigating the top document elsewhere
  closed  opening a modal window
[sandbox=allow-scripts]
  open    running scripts
  closed  submitting forms
  closed  opening a new window
  closed  being counted as its own origin
  closed  navigating the top document elsewhere
  closed  opening a modal window
[sandbox=allow-scripts allow-forms]
  open    running scripts
  open    submitting forms
  closed  opening a new window
  closed  being counted as its own origin
  closed  navigating the top document elsewhere
  closed  opening a modal window
--- dangerous combination ---
allow-scripts + allow-same-origin: the frame can remove its own sandbox attribute
```

The first two cases show that whether the attribute gets written
makes the entire difference. An empty `sandbox` also places the
embedded document into a separate origin:
since it does not count as the same origin as its own server, it
cannot access stored data or cookies.

The last line declares two values that should not get written
together. `allow-same-origin` counts the embedded document as the
same as its own origin; `allow-scripts` permits running scripts. With
both together and a shared origin, the script inside the frame can
remove the outer document's `sandbox` attribute and reload the frame
— isolation cancels itself out.

## Capability Permissions

`sandbox` restricts the document's **behavior**; the `allow`
attribute, though, regulates access to device and platform
capabilities: camera, microphone, location, full screen.

```html
<iframe src="https://map.test/north-slope"
        title="The station's location on the map"
        width="640" height="480" loading="lazy"
        sandbox="allow-scripts"
        allow="fullscreen; geolocation 'none'"
        referrerpolicy="no-referrer"></iframe>
```

The default is that access is closed for most capabilities; the
`allow` declaration opens it, and the `'none'` value closes it
explicitly. These two attributes do not substitute for each other and
get used together.

The `referrerpolicy` attribute declares how much information about
the requesting document gets sent to the embedded resource. The
`no-referrer` value sends none; the embedded third party cannot learn
which page the user is on.

## Name and Loading

The `title` attribute gives the frame an accessible name. If it does
not get written, a screen reader announces the frame as an unnamed
region, and the user cannot tell what it is without entering it. This
attribute is not optional on embedded content.

`loading="lazy"` declares that the frame does not get loaded until it
nears the viewport. Because the embedded document also requests its
own subresources, its cost is much higher than a single image's; for
a map or player at the bottom of the page, this declaration noticeably
lowers opening cost.

The `width` and `height` attributes do the space-allotting job here
too; a frame with no size declaration shifts the content below it
once it loads.

## Protecting Against Being Embedded

Embedding runs both ways: just as a document can embed another,
others can embed this one in their own documents too. This can get used to
mislead the user about which site they are interacting with — the
visible document is yours, the transparent layer placed on top of it
is someone else's, and the place the user clicks and the button they
think they pressed pull apart.

Protection gets declared not in the document but in the server
response. The `frame-ancestors` directive in the
`Content-Security-Policy` header lists which origins can embed the
document.

```
Content-Security-Policy: frame-ancestors 'self'
```

This declaration allows the document to get embedded only by pages on
its own origin; the `'none'` value permits no embedding at all. As in
the Meta Tags lesson, this can also get written with `http-equiv`, but
not for this directive: the embedding decision gets made before the
document gets parsed, so the declaration has to exist in the response
header.

This header gets written on every document that carries session data
or initiates a transaction. If it does not get written, embedding the
document is free, letting a page outside the document's control put
it before the user in any context.

## The Cost of Embedding

Every embedded document starts an independent loading chain: its own
name resolution, its own connection, its own subresources. All the
steps listed in the Resource Loading Order lesson get carried out
again, and these steps share the same bandwidth as the outer
document's resources.

Where a function works without embedding, not embedding gets
preferred. If an interactive map is not needed to show
a location, an image and a coordinate text give the same information
much more cheaply. The station document's choice runs the same way:
the location got told with a figure, and the map did not get
embedded.

## Summary

- The document inside `iframe` is a separate document; it has its own
  tree, styles, and scripts, and cannot access the outer document if
  it comes from a different origin.
- `sandbox` shuts off all capabilities the moment it gets written;
  wanted capabilities get granted back one by one.
- When `allow-scripts` and `allow-same-origin` get written together
  at the same origin, isolation can get removed by the frame itself.
- `sandbox` regulates behavior, `allow` regulates device and platform
  capabilities; the two do not substitute for each other.
- `title` is required on embedded content; `loading="lazy"` and a
  size declaration limit the embedding's opening cost and content
  shift.

## Next Step

This topic completed the elements that give the document meaning.
What remains is a mechanism with no defined meaning of its own, but
useful for attaching data to the document: attributes that can get
attached to any element, produce no name collision, and can get read
by scripts. The next lesson takes up this mechanism and how an
attribute's name gets translated into its program-side counterpart.
