• 1015 words
  • 6 mins

A synthetic iframe is one that doesn’t have web address. It's filled by its parent page instead.

Let’s take a look at every single way you can create one.

I define an iframe as synthetic if:

  1. It has an src attribute.
  2. That points to a web address.

We can visualize it using a decision tree:

Synthetic iframe decision tree

Synthetic iframes can have an src attribute, but one that contains a non-web address. They might also have no attributes at all.

Synthetic iframes are a bit strange, but they’re also simpler than non-synthetic ones. That’s because there are way fewer factors that can change their behavior.

You can also figure them out just by looking at the tag itself.

Meanwhile, non-synthetic iframes always make an HTTP/S request, and that request can have headers that change how the page functions. That includes the Content Security Policy (CSP), but also the Permissions Policy and other stuff too.

Because the contents of synthetic iframes are determined by the pages that built them, they can only provide security in one direction — the page can be protected from what’s happening in the iframe, but not the other way around.

Let’s take a look at every method for constructing a synthetic iframe and compare their advantages.

srcdoc attribute

A straight-forward method that probably resolves 90% of use-cases.

The srcdoc attribute can be used to set the contents of a synthetic iframe verbatim. It can be specified as part of the HTML of the iframe tag, or can be set using JavaScript.

Note that we can set the attribute before we attach the frame to the page.

Here is an example of it being set using HTML:

<!-- srcdoc attribute via HTML -->
<iframe id="myFrame" srcdoc="<h1>hello world</h1>"></iframe>

And here we set it via a JavaScript property:

// Create the iframe 
var iframe = document.createElement("iframe")

// Set the srcdoc attribute
iframe.srcdoc = "<h1>hello world</h1>"

// Attach it to the page
document.body.appendChild(iframe)

A srcdoc iframe doesn’t have an origin. That means its origin defaults to the page that created it, which in turn means it’s not isolated from that page by default.

  • Content specified verbatim
  • Can be inserted using JavaScript or be part of the HTML page itself
  • Loads synchronously
  • Not isolated from the parent page.

empty – no src or srcdoc

An iframe with neither an src nor srcdoc attributes starts out empty. Its content is manually constructed using DOM operations and JavaScript, via the iframe element’s contentWindow property.

You can only do this once the iframe is attached to the page. Otherwise its contentWindow property will be empty.

You can use pretty much any method you want, but if you use DOM objects, make sure to use the iframe’s createElement function. Inserting elements created by the parent page into the iframe will result in undefined behavior.

// Create the iframe
var iframe = document.createElement("iframe")

// Attach it to the page
document.body.appendChild(iframe)

// Now we can access its document
var doc = iframe.contentWindow.document

// Create an h1 element
// Remember to use the iframe’s createElement function
var iframeH1 = doc.createElement("h1")

// Set its text content
iframeH1.textContent = "hello world"

// Append it to the body
doc.body.appendChild(iframeH1)
  • Constructed using JavaScript.
  • Everything DOM update is applies synchronously.
  • Not isolated from the parent page.

data URI

Another kind of iframe uses the src attribute, but with a data URI in it. This is very similar to the srcdoc attribute, in that it allows us to specify the iframe’s contents verbatim in the attribute.

However, using a data URI is more flexible, since it allows different encodings. We can just use text:

<iframe src="data:text/html,<h1>hello world</h1>"></iframe>

But we can also use base64:

<iframe src="data:text/html;base64,PGgxPmhlbGxvIHdvcmxkPC9oMT4="></iframe>

An iframe with a data URI is considered to have an “opaque” origin, which means it’s isolated from everything. It doesn’t have any persistent storage and can’t access the parent page.

This restriction also goes the other way — the parent page can’t access it either — but this doesn’t protect the iframe since the parent page created it in the first place.

Something similar can be achieved for any iframe using the sandbox attribute, but the parent can remove that attribute at any time, whereas this method provides more thorough protection.

  • Inserted using JavaScript or part of the page.
  • Content is specified verbatim using different encodings.
  • Securely isolated from the parent page.
  • Loads after an asynchronous delay

blob URI

This kind of iframe can only be created using JavaScript. It uses a Blob URI, a really weird mechanism for generating a URI pointing to a dynamically allocated binary object.

To create one, we need to create a Blob object with the contents we want the iframe to have:

var blob = new Blob(["<h1>hello world</h1>"], {type: "text/html"})

Then we need to create a URI for it:

var uri = URL.createObjectURL(blob)

And finally, we can set the iframe’s src attribute to that URL:

var iframe = document.createElement("iframe")
iframe.src = uri
document.body.appendChild(iframe)

The result looks something like this:

<iframe src="blob:http://localhost/703363e4-f8a8-401f-90d7-b258264b4d60"></iframe>

Unlike data URIs, iframes constructed like this actually do inherit the parent’s origin.

  • Constructed using JavaScript
  • Content specified verbatim as part of the blob
  • Loads after an asynchronous delay
  • Not isolated from the parent page

about URI

Using an src attribute with the about: pseudo-protocol doesn’t actually construct an iframe. It sets its address without specifying what’s in it. Kind of like giving a nickname to a pet.

You can combine this with the undefined to set the frame’s location to something meaningful. Can be helpful when debugging.

Conclusion

Synthetic iframes are simpler than non-synthetic ones, but there are lots of different ways to create them, each with its own sets of benefits and drawbacks.

I’m pretty sure I covered literally every single method in this article. Please contact me if I missed anything!