Defending the Perimeter Against Web Widgets

On July 14, 1789, citizens of Paris stormed the Bastille, igniting a revolution that toppled the French monarchy. On July 14 of this year, there was a less dramatic (though more tweeted) takedown: The Deck network, which delivers advertising to some of the most popular web design and culture destinations, was down for about thirty minutes. During this period, most partner sites running ads from The Deck could not be viewed as result.

A few partners were unaffected (aside from not having an ad to display). Fortunately, Dribbble, was one of them. In this article, I’ll discuss outages like this and how to defend against them. But first, a few qualifiers: The Deck has been rock solid – this is the only downtime we’ve witnessed since joining in June. More importantly, the issues in play are applicable to any web widget you might add to your site to display third-party content.

Down and out

Your defense is only as good as its weakest link. Web pages are filled with links, some of which threaten the ability of your page to load quickly and correctly. If you want your site to work when external resources fail, you need to identify the weak links on your site. In this article, we’ll talk about web widgets as a point of failure and defensive JavaScript techniques for handling them.

Widgets 101

Imagine a widget that prints out a Pun of the Day on your site. A simple technique for both widget provider and consumer is for the provider to expose a URL:

http://widgetjonesdiary.com/punoftheday.js

which returns a JavaScript file like this:

document.write("<h2>The Pun of the Day</h2><p>Where do frogs go for beers after work? Hoppy hour!</p>");

The call to document.write() injects the string passed into the document where it is called. So to display the widget on your page, simply add an external script tag where you want it to appear:

<div class="punoftheday">
  <script src="http://widgetjonesdiary.com/punoftheday.js"></script>
  <!-- Content appears here as output of script above -->
</div>

This approach is incredibly easy for both provider and consumer. But there are implications…

document.write()… or wrong?

As in the example above, scripts may perform a document.write() to inject HTML. Page rendering halts while a script is processed so any output can be inlined into the document. Therefore, page rendering speed depends on how fast the script returns the data. If an external JavaScript widget hangs, so does the page content that follows. It was this scenario that briefly stalled partner sites of The Deck last summer.

The elegant solution

To make our web widget more robust, calls to document.write() should be avoided. This can be achieved with a technique called JSONP (AKA JSON with padding). In our example, instead of writing inline with document.write(), a JSONP script passes content to a callback function:

publishPun("<h2>Pun of the Day</h2><p>Where do frogs go for beers after work? Hoppy hour!</p>");

Then, it’s up to the widget consumer to implement a callback function responsible for displaying the content. Here’s a simple example where our callback uses jQuery to write the content into a target <div>:

<!-- Where widget content should appear -->
<div class="punoftheday"></div>




View Example 1

Even if the widget content appears at the top of the page, our script can be included at the bottom so it’s non-blocking: a slow response leaves page rendering unaffected. It simply invokes the callback which, in turn, writes the widget content to its display destination.

The hack

But what to do if your provider doesn’t support JSONP? This was our case with The Deck. Returning to our example, I’m reminded of computer scientist David Wheeler’s statement, “All problems in computer science can be solved by another level of indirection… Except for the problem of too many layers of indirection.”

In our case, the indirection is to move the widget content into position after writing it to the page. This allows us to place the widget <script> tag at the bottom of the page so rendering won’t be blocked, but still display the widget in the target. The strategy:

  1. Load widget content into a hidden <div> at the bottom of the page.
  2. Move the loaded content from the hidden <div> to its display location.

and the code:

<!-- Where widget content should appear -->
<div class="punoftheday"></div>




View Example 2

After the external punoftheday.js script has processed, the rendered HTML will look as follows:

<div class="loading-dock hidden">
  <script src="http://widgetjonesdiary.com/punoftheday.js"></script>
  <h2>Pun of the Day</h2>
  <p>Where do frogs go for beers after work? Hoppy hour!</p>
</div>

The ‘loading-dock’ <div> now includes the widget content, albeit hidden from view (if we’ve styled the ‘hidden’ class with display: none). There’s just one more step: move the content to its display destination. This line of jQuery (from above) does the trick:

$('.punoftheday').append($('.loading-dock').children(':gt(0)'));

This selects all child elements in the ‘loading-doc’ <div> except the first – the widget <script> tag which generated it – and moves it to the display destination. Worth noting is the :gt(0) jQuery selector extension, which allows us to exclude the first (in a 0-based array) child element – the widget <script> tag – from selection.

Since all of this happens at the bottom of the page, just before the </body> tag, no rendering has to wait on the external widget script. The only thing that fails if our widget hangs is… the widget itself. Our weakest link has been strengthened and so has our site. DE-FENSE!

About the author

Rich Thornett wanted to play pro basketball when he grew up, but found himself trapped in the body of a software developer. After working on his game for over a decade at software shops large and small, he created Dribbble, a show and tell site for designers. What was once a side project is now a small company where he serves as lead developer and product designer.

He lives in Salem, MA with his witty, wonderful wife and two kids, who are lost in bonkers. If you believe the pun is mightier than the sword, you can follow him on Twitter.

More articles by Rich

Comments