Prebrowsing

November 7, 2013 2:41 pm | 20 Comments

A favorite character from the MASH TV series is Corporal Walter Eugene O’Reilly, fondly referred to as “Radar” for his knack of anticipating events before they happen. Radar was a rare example of efficiency because he was able to carry out Lt. Col. Blake’s wishes before Blake had even issued the orders.

What if the browser could do the same thing? What if it anticipated the requests the user was going to need, and could complete those requests ahead of time? If this was possible, the performance impact would be significant. Even if just the few critical resources needed were already downloaded, pages would render much faster.

Browser cache isn’t enough

You might ask, “isn’t this what the cache is for?” Yes! In many cases when you visit a website the browser avoids making costly HTTP requests and just reads the necessary resources from disk cache. But there are many situations when the cache offers no help:

  • first visit – The cache only comes into play on subsequent visits to a site. The first time you visit a site it hasn’t had time to cache any resources.
  • cleared – The cache gets cleared more than you think. In addition to occasional clearing by the user, the cache can also be cleared by anti-virus software and browser bugs. (19% of Chrome users have their cache cleared at least once a week due to a bug.)
  • purged – Since the cache is shared by every website the user visits, it’s possible for one website’s resources to get purged from the cache to make room for another’s.
  • expired69% of resources don’t have any caching headers or are cacheable for less than one day. If the user revisits these pages and the browser determines the resource is expired, an HTTP request is needed to check for updates. Even if the response indicates the cached resource is still valid, these network delays still make pages load more slowly, especially on mobile.
  • revved – Even if the website’s resources are in the cache from a previous visit, the website might have changed and uses different resources.

Something more is needed.

Prebrowsing techniques

In their quest to make websites faster, today’s browsers offer a number of features for doing work ahead of time. These “prebrowsing” (short for “predictive browsing” – a word I made up and a domain I own) techniques include:

  • <link rel="dns-prefetch" ...>
  • <link rel="prefetch" ...>
  • <link rel="prerender" ...>
  • DNS pre-resolution
  • TCP pre-connect
  • prefreshing
  • the preloader

These features come into play at different times while navigating web pages. I break them into these three phases:

  1. previous page – If a web developer has high confidence about which page you’ll go to next, they can use LINK REL dns-prefetch, prefetch or prerender on the previous page to finish some work needed for the next page.
  2. transition – Once you navigate away from the previous page there’s a transition period after the previous page is unloaded but before the first byte of the next page arrives. During this time the web developer doesn’t have any control, but the browser can work in anticipation of the next page by doing DNS pre-resolution and TCP pre-connects, and perhaps even prefreshing resources.
  3. current page – As the current page is loading, browsers have a preloader that scans the HTML for downloads that can be started before they’re needed.

Let’s look at each of the prebrowsing techniques in the context of each phase.

Phase 1 – Previous page

As with any of this anticipatory work, there’s a risk that the prediction is wrong. If the anticipatory work is expensive (e.g., steals CPU from other processes, consumes battery, or wastes bandwidth) then caution is warranted. It would seem difficult to anticipate which page users will go to next, but high confidence scenarios do exist:

  • If the user has done a search with an obvious result, that result page is likely to be loaded next.
  • If the user navigated to a login page, the logged-in page is probably coming next.
  • If the user is reading a multi-page article or paginated set of results, the page after the current page is likely to be next.

Let’s take the example of searching for Adventure Time to illustrate how different prebrowsing techniques can be used.

DNS-PREFETCH

If the user searched for Adventure Time then it’s likely the user will click on the result for Cartoon Network, in which case we can prefetch the DNS like this:

<link rel="dns-prefetch" href="//cartoonnetwork.com">

DNS lookups are very low cost – they only send a few hundred bytes over the network – so there’s not a lot of risk. But the upside can be significant. This study from 2008 showed a median DNS lookup time of ~87 ms and a 90th percentile of ~539 ms. DNS resolutions might be faster now. You can see your own DNS lookup times by going to chrome://histograms/DNS (in Chrome) and searching for the DNS.PrefetchResolution histogram. Across 1325 samples my median is 50 ms with an average of 236 ms – ouch!

In addition to resolving the DNS lookup, some browsers may go one step further and establish a TCP connection. In summary, using dns-prefetch can save a lot of time, especially for redirects and on mobile.

PREFETCH

If we’re more confident that the user will navigate to the Adventure Time page and we know some of its critical resources, we can download those resources early using prefetch:

<link rel="prefetch" href="http://cartoonnetwork.com/utils.js">

This is great, but the spec is vague, so it’s not surprising that browser implementations behave differently. For example,

  • Firefox downloads just one prefetch item at a time, while Chrome prefetches up to ten resources in parallel.
  • Android browser, Firefox, and Firefox mobile start prefetch requests after window.onload, but Chrome and Opera start them immediately possibly stealing TCP connections from more important resources needed for the current page.
  • An unexpected behavior is that all the browsers that support prefetch cancel the request when the user transitions to the next page. This is strange because the purpose of prefetch is to get resources for the next page, but there might often not be enough time to download the entire response. Canceling the request means the browser has to start over when the user navigates to the expected page. A possible workaround is to add the “Accept-Ranges: bytes” header so that browsers can resume the request from where it left off.

It’s best to prefetch the most important resources in the page: scripts, stylesheets, and fonts. Only prefetch resources that are cacheable – which means that you probably should avoid prefetching HTML responses.

PRERENDER

If we’re really confident the user is going to the Adventure Time page next, we can prerender the page like this:

<link rel="prerender" href="http://cartoonnetwork.com/">

This is like opening the URL in a hidden tab – all the resources are downloaded, the DOM is created, the page is laid out, the CSS is applied, the JavaScript is executed, etc. If the user navigates to the specified href, then the hidden page is swapped into view making it appear to load instantly. Google Search has had this feature for years under the name Instant Pages. Microsoft recently announced they’re going to similarly use prerender in Bing on IE11.

Many pages use JavaScript for ads, analytics, and DHTML behavior (start a slideshow, play a video) that don’t make sense when the page is hidden. Website owners can workaround this issue by using the page visibility API to only execute that JavaScript once the page is visible.

Support for dns-prefetch, prefetch, and prerender is currently pretty spotty. The following table shows the results crowdsourced from my prebrowsing tests. You can see the full results here. Just as the IE team announced upcoming support for prerender, I hope other browsers will see the value of these features and add support as well.

dns-prefetch prefetch prerender
Android 4 4
Chrome 22+ 31+1 22+
Chrome Mobile 29+
Firefox 22+2 23+2
Firefox Mobile 24+ 24+
IE 113 113 113
Opera 15+
  • 1 Need to use the --prerender=enabled commandline option.
  • 2 My friend at Mozilla said these features have been present since version 12.
  • 3 This is based on a Bing blog post. It has not been tested.

Ilya Grigorik‘s High Performance Networking in Google Chrome is a fantastic source of information on these techniques, including many examples of how to see them in action in Chrome.

Phase 2 – Transition

When the user clicks a link the browser requests the next page’s HTML document. At this point the browser has to wait for the first byte to arrive before it can start processing the next page. The time-to-first-byte (TTFB) is fairly long – data from the HTTP Archive in BigQuery indicate a median TTFB of 561 ms and a 90th percentile of 1615 ms.

During this “transition” phase the browser is presumably idle – twiddling its thumbs waiting for the first byte of the next page. But that’s not so! Browser developers realized that this transition time is a HUGE window of opportunity for performance prebrowsing optimizations. Once the browser starts requesting a page, it doesn’t have to wait for that page to arrive to start working. Just like Radar, the browser can anticipate what will need to be done next and can start that work ahead of time.

DNS pre-resolution & TCP pre-connect

The browser doesn’t have a lot of context to go on – all it knows is the URL being requested, but that’s enough to do DNS pre-resolution and TCP pre-connect. Browsers can reference prior browsing history to find clues about the DNS and TCP work that’ll likely be needed. For example, suppose the user is navigating to http://cartoonnetwork.com/. From previous history the browser can remember what other domains were used by resources in that page. You can see this information in Chrome at chrome://dns. My history shows the following domains were seen previously:

  • ads.cartoonnetwork.com
  • gdyn.cartoonnetwork.com
  • i.cdn.turner.com

During this transition (while it’s waiting for the first byte of Cartoon Network’s HTML document to arrive) the browser can resolve these DNS lookups. This is a low cost exercise that has significant payoffs as we saw in the earlier dns-prefetch discussion.

If the confidence is high enough, the browser can go a step further and establish a TCP connection (or two) for each domain. This will save time when the HTML document finally arrives and requires page resources. The Subresource PreConnects column in chrome://dns indicates when this occurs. For more information about dns-presolution and tcp-preconnect see DNS Prefetching.

Prefresh

Similar to the progression from LINK REL dns-prefetch to prefetch, the browser can progress from DNS lookups to actual fetching of resources that are likely to be needed by the page. The determination of which resources to fetch is based on prior browsing history, similar to what is done in DNS pre-resolution. This is implemented as an experimental feature in Chrome called “prefresh” that can be turned on using the --speculative-resource-prefetching="enabled" flag. You can see the resources that are predicted to be needed for a given URL by going to chrome://predictors and clicking on the Resource Prefetch Predictor tab.

The resource history records which resources were downloaded in previous visits to the same URL, how often the resource was hit as well as missed, and a score for the likelihood that the resource will be needed again. Based on these scores the browser can start downloading critical resources while it’s waiting for the first byte of the HTML document to arrive. Prefreshed resources are thus immediately available when the HTML needs them without the delays to fetch, read, and preprocess them. The implementation of prefresh is still evolving and being tested, but it holds potential to be another prebrowsing timesaver that can be utilized during the transition phase.

Phase 3 – Current Page

Once the current page starts loading there’s not much opportunity to do prebrowsing – the user has already arrived at their destination. However, given that the average page takes 6+ seconds to load, there is a benefit in finding all the necessary resources as early as possible and downloading them in a prioritized order. This is the role of the preloader.

Most of today’s browsers utilize a preloader – also called a lookahead parser or speculative parser. The preloader is, in my opinion, the most important browser performance optimization ever made. One study found that the preloader alone improved page load times by ~20%. The invention of preloaders was in response to the old browser behavior where scripts were downloaded one-at-a-time in daisy chain fashion.

Starting with IE 8, parsing the HTML document was modified such that it forked when an external SCRIPT SRC tag was hit: the main parser is blocked waiting for the script to download and execute, but the lookahead parser continues parsing the HTML only looking for tags that might generate HTTP requests (IMG, SCRIPT, LINK, IFRAME, etc.). The lookahead parser queues these requests resulting in a high degree of parallelized downloads. Given that the average web page today has 17 external scripts, you can imagine what page load times would be like if they were downloaded sequentially. Being able to download scripts and other requests in parallel results in much faster pages.

The preloader has changed the logic of how and when resources are requested. These changes can be summarized by the goal of loading critical resources (scripts and stylesheets) early while loading less critical resources (images) later. This simple goal can produce some surprising results that web developers should keep in mind. For example:

  • JS responsive images get queued last – I’ve seen pages that had critical (bigger) images that were loaded using a JavaScript responsive images technique, while less critical (smaller) images were loaded using a normal IMG tag. Most of the time I see these images being downloaded from the same domain. The preloader looks ahead for IMG tags, sees all the less critical images, and adds those to the download queue for that domain. Later (after DOMContentLoaded) the JavaScript responsive images technique kicks in and adds the more critical images to the download queue – behind the less critical images! This is often not the expected nor desired behavior.
  • scripts “at the bottom” get loaded “at the top” – A rule I promoted starting in 2007 is to move scripts to the bottom of the page. In the days before preloaders this would ensure that all the requests higher in the page, including images, got downloaded first – a good thing when the scripts weren’t needed to render the page. But most preloaders give scripts a higher priority than images. This can result in a script at the bottom stealing a TCP connection from an image higher in the page causing above-the-fold rendering to take longer.

When it comes to the preloader the bottomline is that the preloader is a fantastic performance optimization for browsers, but the logic is new and still evolving so web developers should be aware of how the preloader works and watch their pages for any unexpected download behavior.

As the low hanging fruit of web performance optimization is harvested, we have to look harder to find the next big wins. Prebrowsing is an area that holds a lot of potential to deliver pages instantly. Web developers and browser developers have the tools at their disposal and some are taking advantage of them to create these instant experiences. I hope we’ll see even wider browser support for these prebrowsing features, as well as wider adoption by web developers.

[Here are the slides and video of my Prebrowsing talk from Velocity New York 2013.]

 

20 Responses to Prebrowsing

  1. Hi Steve,
    Even if the bottom placed scripts will get downloaded before it’s still a good thing to place them there right? For not blocking the rendering. Of course, if the defer/async attribute is not specified.

  2. Prefetching DNS or websites is awesome to improve perf, but unfortunately all websites that I work on are over https and none of this works.

    The bad part is that I hope that most websites start to be fully https, and are not going to be able to use this. I’m particularly bitter about the prefetch-dns. I see why they disable it but I think is bit too much as a security measurement.

  3. Ionut: It’s best to load scripts asynchronously. Next best is making them synchronous and putting them at the bottom. Worst is making them synchronous and putting them in HEAD.

    Juan: I didn’t test https, but from reading the docs all I saw was Chrome didn’t pre-resolve https links in https pages. I don’t see anything that says link rel dns-prefetch, prefetch, and prerender don’t work for https. Can you clarify what you believe doesn’t work for https?

  4. I would love it if someone came up with a method for responsive images that works with the preloader. This is undoubtedly the single biggest area for improvement for the sites that I work on. Especially as the trend in design these days is to use larger and higher-res images.

  5. @dalin Maybe if <link prefetch/subresource> supported the media attribute like stylesheets do, we could request the right image during the preparser phase. I tested on Chrome here and the media attr doesn’t work on prefetch/subresource links.

  6. Personally I think we can over complicate everything. If a site is properly optimised following all the industry standard recommendations, predicting user behaviour is not a requirement.

  7. Steve,

    Thanks for the great write-up.

    I’d like to note that TCP pre-connect can actually cause Chrome (and possibly other browsers) to perform an unintentional Denial of Service (DoS) attack against servers that can only accept a limited number of concurrent TCP connections. This includes simple servers (e.g. single-process, single-threaded, maybe in embedded devices) and old (e.g. apache 1.3) servers.

    There is an open bug on this for Chrome: https://code.google.com/p/chromium/issues/detail?id=85229 – but unfortunately willchan, who had planned to address some of the issues, no longer works at Google. Steve, are you able to ping anyone at Google that might want to take a look at the problem?

    I have written a Perl script that can be run against an apache 1.3 server that kills processes waiting to serve pre-connect sockets; this script is effective at preventing Chrome from DoSing my servers: https://gist.github.com/eqhmcow/5222092

    There are, of course, many cases where pre-connect does not cause any issues, and there are also cases where improving the server (upgrading to a newer httpd or otherwise changing how the server handles connections) will mitigate issues, but pre-connect is notably the only case I know of where a new feature of a mainstream HTTP client can cause serious failure of an HTTP server until that server is upgraded or reconfigured. That is, an argument could be made that pre-connect causes clients to break the “robustness principle” (aka Postel’s law).

    One more note: Based on the data from my work-around script, Chrome always makes at least 4 pre-connections to my servers (not just one or two as noted in the article), and does NOT close the connections itself if they go unused; at least not for many 10s of seconds.

    Thanks much,
    Dan Sterling

  8. dalin: Once we setting on srcset, srcN, etc. it won’t take long for the preloaders to recognize them.

    edward: Web development gets more complicated every year. Creating a high quality site is no longer a simple task, esp. as the amount of content on websites continues to grow. Also, some situations (like first time visit, just cleared cache, etc.) are clearly made faster if critical resources are downloaded ahead of time.

  9. Dan: You could try the X-DNS-Prefetch-Control response header. I believe the number of connections is based on the confidence. I’m not sure what the maximum is.

  10. “Personally I think we can over complicate everything. If a site is properly optimised following all the industry standard recommendations, predicting user behaviour is not a requirement.”

    You don’t need to guess what user behaviour is – examine the analytical data and heat maps and analysis the most common user navigation paths.

  11. I’m working on a really large ecommerce site, and I thought I’d add a ‘prerender’ tag to the anonymous homepage to load the sign-in page. (I was taking the assumption that most users want to sign in on the homepage.) The sign in page is opened inside an iframe in a js popup dialog. It was taking much longer than I wanted it to load, so I thought the prerender would help.

    I have a utility that runs a WebPageTest every few hours on various pages of my site. Since I added the prerender tag, the anonymous homepage has been taking much longer to load!
    I actually see a few seconds more. I analyzed the different waterfall charts and I see that each request along the chart is taking a few tens/hundreds of milliseconds longer, which adds up to a few seconds.

    On the ‘cached view’ in the WebPageTest I also get a hard hit. It went from ~2seconds to ~6seconds!!

    Is there a way to tell the page to start the prerendering only when the page is fully loaded, or at least only after page load ?
    This is a great improvement for the sign in popup in my case, but I’m not sure it’s worth the delay for the anonymous homepage… :/

  12. Gilly: Are you sure the anon homepage is slowed down because of prerender? Please provide links to your WebPagetest results. Make sure they’re done under the same conditions (which means you’ll need a param to turn prerender on/off). Prerender probably already starts after onload, but in case it doesn’t you can create the link dynamically. See slide #31 in my Prebrowsing deck.

  13. Steve: thanks, as always, for the article. It’s enlightening.

    @all:

    I’ve tried the prefetch to stay 1 page ahead in our infinite scroll, Chrome 31.0.1650.16 beta starts to make the request but marks it red in the network tab, and says “(canceled)”. The request never reaches the server.

    Later, if I scroll, the infinite scroll script triggers and XHR loads the same URL, but gets a 200 there.

    Any hints?

    I could post

  14. Hi Steve,

    Juan may be referring to the language on this page:

    https://developers.google.com/chrome/whitepapers/prerender

    that says https pages are a situation in which prerendering is aborted.

  15. Hi Steve – let’s not forget a permissioning model that states: “If the user gives the client permission to predict my Future behaviors, and then subsequently grant my credentials to that Future endeavor, then you (the client app) must obey my specific permission.”

    I think the end-user’s context for a “permitted” state in-session (ergo “logged-in” vs. “not logged-in”) would require a management of their client experience akin to managing all the cookies and saved passwords, etc.

    Would that be too much to manage? Would we “end-users” freely appreciate the benefits of proxying our permission to a predictive engine?

    How would a normal human react to this potential innovation?

  16. Hi Steve,

    With preloaders in place in most major browsers, does it still make sense to combine all of your JS into a single script, or does it make more sense to split scripts into 3-4 different resources to allow the preloader to work in parallel?

    For a higher latency connection ( let’s say 200ms RTT ), it seems like it’d be beneficial to allow resources to load in parallel rather than loading one giant resource.

  17. Daniel: This is a constant question probably because there’s not a simple answer. “It depends.” If the only thing in your page is 4 scripts, it’s probably better to load them separately for better parallelization. The downside of that would be the cost of spinning up 4 TCP connections with each going through slow start. But that really doesn’t matter IMO because most pages have 50-100 requests, so there’s probably enough parallelization to saturate the network pipe even if you concatenate scripts. Also, there are 17 external scripts on the average website, so even if you concatenate 4 or 6 of those, there are plenty of other scripts that will be loading in parallel.

    So – it depends – but in general I think it’s better to combine scripts where you can.

  18. Steve,

    Are these requests and resolution stuffs are captured as part of Network tab in chrome dev tools?

  19. Vignesh: In many cases the Network tab does NOT show these requests since they’re happening in the background and are not part of the visiible page’s resources. However, you can use the chrome:// URLs to track much of the behavior.

  20. Radar uncanny ability was being able to her the choppers of wounded soldiers arrive before anybody else. So it gave the impression that he had his own radar. He wasn’t able to foresee the future.