shutterstock_1157171824 placeholder
Articles, Tech

Why We Decided to Abandon the iFrame

By Dor Tzur

On a daily basis, we face a series of technological challenges that require us to think outside of the box. One of the challenges we face is the fact that our products are embedded on publishers’ websites, instead of existing as a standalone page. Typically, embedded products are supported by the Inline Frame HTML element – iFrame for short. But, we wanted to think critically about whether or not we really needed to use an iFrame to support our products.

What is an iFrame?

The biggest benefit of using an iFrame is protection. An iFrame represents a nested browsing context inside an existing HTML document. It’s the number one way to host third-party content safely. The content inside an iFrame can’t access the content of its host and vice-versa. This creates a wall between the host website and the third party.

Why Use an iFrame?

If we rule out security concerns, why would these companies still put their embeds inside of an iFrame?

  1. Preventing CSS leaks – This happens when a host sets a global CSS rule affecting all elements on the page, not just those rendered by them. Even in 2020, you might be surprised to learn this is still the number one concern. Most websites don’t strictly use modular CSS and have many global rules. The use of the !important suffix is also too widespread.
  2. Preventing prototype patching – This happens when a host changes the behavior. While we’ve considered patching a global object as bad for more than a decade, some websites and even libraries still do so sometimes.
  3. JavaScript collisions – This happens the host and the embed use the same library. For example, both have different versions of Lodash which aren’t compatible.

The Problems with iFrame:

Embed Codes

If you try to create an embed code that resides solely inside an iFrame, you’re going to run into problems fast. The majority of which stem from limited capabilities due to security concerns.

The first major problem you’re going to run into is that you can’t change the height of an iFrame from inside the iFrame. So, if the content you’re embedding has dynamic height, you’re out of luck.

That and other limitations make embedding a third-party widget solely via iFrame a challenge.

This is why most third-party services don’t give an iFrame directly as an embed code. What most do is provide an HTML content placeholder plus a link to a remote script.

Here’s an example of both Embedly and Twitter third-party embed codes.

In both cases, the script will transform the blockquote element into an iFrame containing the embedded content.

But here’s the catch: Once you allow a third party to load a remote script, all the security protections an iFrame gives you go out the window. A remote script can change at any moment, load other scripts and generally has the exact same privileges as any other script running on the page.

Multiple Modules, Multiple Troubles

The other problem with using an iFrame arises when you have several interconnected, yet separate UI Modules on a single page.

  1. No resource sharing – If I download React in one iFrame I still have to download and parse it in a different iFrame. Caching can mitigate this, but I still need to make sure the versions are in sync.
  2. Shared memory & state management – iFrames can’t share state directly. For example: If a user changes their avatar, the iFrame would need to notify the host, which in turn will notify all other active iFrames. The syncing state could add significant complexity to each module.
  3. The onerror event lies – Another unforeseen roadblock is that onerror event doesn’t fire in iFrames. So the only way to know an iFrame failed to load is waiting for a postMessage event for an X amount of time and treat timeouts as failures.
  4. Overhead – The last challenge is the overhead of creating multiple iFrames on the page. Even with everything cached, this is a significant overhead over creating a div and making an API call.

Overcoming the Challenges: Life without an iFrame

The safety of an iFrame comes with a price, so we decided to explore how we could step out of the iFrame and overcome the challenges that come along with it. Here are a couple ways we protected ourselves without the security of an iFrame.  

  1. CSS Reset – We added extensive CSS reset attributes prefixed with our own class so that we don’t accidentally reset our host’s CSS.
  2. CSS Specify – We created a webpack plugin that duplicates classes several times for each class. This raised the specificity level for each class in a way that’s transparent to the developer. We also prefixed classes with a unique id for each module, further increasing specificity.
  3. Shadow DOM – We wrapped some of our modules in shadow DOM, completely isolating the module from the outside world. This allows you to enjoy the isolation of an iframe, without the overhead.
    Note that wrapping with Shadow DOM React and rich text editors such as Quill could be challenging due to the way Shadow DOM handles events.
  4. Namespacing – All of our modules namespaced with Webpack in a way that won’t collide with other libraries or other webpack bundles. We also prefix all the side effects we do such writing to localStorage.
  5. iFrame Objects – In the worst cases, where the host overrode globals such JSON.parse there’s a neat trick we use. We can create an invisible inline Iframe and use its JSON.parse implementation.

A Worthwhile Risk

We believe that stepping out of the iFrame was the right move for us. We weighed all of our options and realized that we could find different ways to protect ourselves without the support of an iFrame. Here at Spot.IM, we enjoy challenging the system and exploring new ways of thinking. 

As we look to build the next generation of Spot.IM products we are looking for talented Software Engineers who want to be challenged and apply out-of-the-box thinking to a unique set of problems.

Subscribe to our blog