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?
- 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
!importantsuffix is also too widespread.
- 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.
The Problems with iFrame:
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.
- 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.
- 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.
- 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
postMessageevent for an X amount of time and treat timeouts as failures.
- 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
divand 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.
- CSS Reset – We added extensive CSS reset attributes prefixed with our own class so that we don’t accidentally reset our host’s CSS.
- 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.
- 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.
- 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.
- iFrame Objects – In the worst cases, where the host overrode globals such
JSON.parsethere’s a neat trick we use. We can create an invisible inline Iframe and use its
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.