iFrame Performance: Part 1, The Bad News

Max Rafferty
Slices of Bread
Published in
5 min readDec 4, 2020

This article is the second in a series about working with iFrames. The first, iframe: A Love Story, describes a brief history of the iframe, and how and why we need them at Bread.

•✧─────────────✧¸.•´*¨`*•✿•*`¨*`•.¸✧─────────────✧•

It’s just HTML, who cares about performance?

We previously discussed why our team over here at Bread has no alternative to the iFrame element and all of its quirks. One of the most important of these quirks is ensuring that our frames, and the pages and scripts within them are responsive and available, and crucially, do not degrade the experience of users on the sites of our merchant partners: in short, performance. In modern browsers, we rarely concern ourselves with the performance of HTML, in favor of the typically much more fruitful optimizations to be made in Javascript. As with so many other things, the iFrame is once again the exception. iFrames have their own network concerns, separate document context, and their own set of lifecycle moments, not to mention them being quite variable from browser to browser. Taken all together, there is not a lot of good news on this front: iFrame performance is, generally speaking, terrible.

Alright, how bad is it?

Pretty bad.

Consider the following two control examples we will be using:

  1. Control 1: Large Image (~8mb), Ten smaller images (~1mb)
  2. Control 2: Large Image (~8mb), Ten iFrames each containing one image (~1mb)

These will represent our practical “best” and “worst” case scenarios, the former being content without iFrames, the latter being iFrames we have not attempted to optimize at all. We’ve set up these scenarios in the following way: The large image gives us something to “race” the iFrames against, both from a strict timing perspective and visually, as as with many UX optimizations, we’ll need to be ok here if we can only improve the perceived performance, rather than the actual underlying loads and events. We’ve written a tiny timer script which will be the only additional code in this document, to keep things as light as possible. We’ve opted not to use the lovely console.time here because in part two we’ll be automating these tests for conveniently repeating the experiment in multiple browsers, which will require us to be able to read the time values. For the iFrame contents, we’ll use this simple HTML:

<!DOCTYPE html><html><head>
<meta http-equiv=”content-type” content=”text/html; charset=UTF-8"><title>iFrame Performance</title>
<body>
<a title=”Michal Klajban, CC BY-SA 4.0 &lt;https://creativecommons.org/licenses/by-sa/4.0&gt;, via Wikimedia Commons” href=”https://commons.wikimedia.org/wiki/File:%22Everything_is_Going_to_be_Alright%22_artwork,_Christchurch_Art_Gallery,_Christchurch,_New_Zealand.jpg"><img alt=”&quot;Everything is Going to be Alright&quot; artwork, Christchurch Art Gallery, Christchurch, New Zealand” src=”https://upload.wikimedia.org/wikipedia/commons/thumb/2/21/%22Everything_is_Going_to_be_Alright%22_artwork%2C_Christchurch_Art_Gallery%2C_Christchurch%2C_New_Zealand.jpg/4096px-%22Everything_is_Going_to_be_Alright%22_artwork%2C_Christchurch_Art_Gallery%2C_Christchurch%2C_New_Zealand.jpg"></a>
<script type=”text/javascript”>//<![CDATA[
window.addEventListener(‘load’, (event) => {
window.parent.postMessage(“frame loaded”, “*”);
});
//]]></script>
</body></html>

I’ll maintain a copy of this on an additional JSFiddle, though note that these numbers will not be in a CORS situation, where performance is further degraded. For our control numbers, same-site will be fine to illustrate our challenges.

The Damage

https://sheetsu.com/tables/39820ed9f8

The numbers in the image averages are the most striking here — the mere presence of the frames on the page increases the load time of the other content on the page by 4x. Mind, this is with ten frames — that said, the presence of even one adds between 300ms and 600ms to the time it takes the image to load on the page, and that’s with a reduction of page size by almost 1mb — clearly the network is not the culprit here. For further confirmation, the child page takes ~75ms to load when viewed directly, indicating that neither network nor render time is, at least directly, the cause either. If we enable our local cache, the problem becomes even more clear — the images appear near instantaneously, but the frame load time remains largely unaffected. We can see that the number of frames on a page has a direct and linear relationship with the loading of all elements on the page:

Note in both of these datasets the presence of the occasional “blip” outliers where the image loads in “normal” time. I’m not going to talk any more about that, but its certainly interesting!

Analysis and Solutions

Based on these control numbers, we’re going to need to try to see what about a frame adds a static ~600ms. We know iFrames are going to have two, or possibly three inherent delays to the loading of the page — they can’t begin until they are reached in the DOM, they may have a network request, and they must create a new document context and load the DOM into it. Fortunately, for all their ambiguity, iFrames are very configurable. We can control all three of these delays in a variety of ways.

  1. The DOM. We most frequently encounter issues here with scripts — HTML loads synchronously line by line, downloading resources in the order in which they are encountered before the first paint. Scripts desiring to not block the visual page from loading are commonly placed at the end of the body, so as not to block rendering. Alternatively, they can be loaded dynamically after first page load, or be given async attributes that affect their loading. All of these are options are available for iFrames as well.
  2. The network. Caching takes care of the majority of issues here, but we have a few options to play with. iFrames provide the srcdoc attribute for manually loading a document, which provides a first vehicle for bypassing the network, as well as for manually GETting and loading the doc. The src attribute can also be used to load an inline document, encoded in the same way as an inline image source.
  3. The document. This is the area in which we have the least control. A frame loaded without a source defaults to the page about:blank. We may be able to manipulate this default with a combination of the above techniques to shorten the time it takes to mount the page. That said, any solutions here will be highly susceptible to browser to browser variation.

As alluded to in the third bullet, there is an unfortunate fourth aspect that can affect these load times: the browser. As a result of their many quirks, each browser handles iFrames differently. This is particularly true for cross-origin (CORS) frames, where security concerns are increasingly causing vendors to take varied approaches to locking down any CORS activity, and frequently without much fanfare in release notes. Quirky. We have very little ability to control for this, though we can note when we see aberrant behavior.

All of this taken together calls for an experiment! We have control numbers, we have possible solutions. In part two of this article, we will set up a series of experiments similar to our second control, utilizing a page containing ten iFrames and the techniques described above. We can then hopefully understand the nature of what is happening in those extra 600ms per frame, and show us how to best speed up pages when we must use iFrames.

--

--