High Performance Web Sites http://www.stevesouders.com/blog Essential knowledge for making your web pages faster. Mon, 09 Jun 2014 05:23:24 +0000 en-US hourly 1 http://wordpress.org/?v=3.7.3 HTTP Archive – new stuff! http://www.stevesouders.com/blog/2014/06/08/http-archive-new-stuff/ http://www.stevesouders.com/blog/2014/06/08/http-archive-new-stuff/#comments Mon, 09 Jun 2014 05:11:43 +0000 http://www.stevesouders.com/blog/?p=4021 Background

The HTTP Archive crawls the world’s top 300K URLs twice each month and records detailed information like the number of HTTP requests, the most popular image formats, and the use of gzip compression. We also crawl the top 5K URLs on real iPhones as part of the HTTP Archive Mobile. In addition to aggregate stats, the HTTP Archive has the same set of data for individual websites plus images and video of the site loading.

I started the project in 2010 and merged it into the Internet Archive in 2011. The data is collected using WebPagetest. The code and data are open source. The hardware, mobile devices, storage, and bandwidth are funded by our generous sponsors:  GoogleMozillaNew RelicO’Reilly MediaEtsyRadwaredynaTrace SoftwareTorbitInstart Logic, and Catchpoint Systems.

For more information about the HTTP Archive see our About page.

New Stuff!

I’ve made a lot of progress on the HTTP Archive in the last two months and want to share the news in this blog post.

Github
A major change was moving the code to Github. It used to be on Google Code but Github is more popular now. There have been several contributors over the years, but I hope the move to Github increases the number of patches contributed to the project.
Histograms
The HTTP Archive’s trending charts show the average value of various stats over time. These are great for spotting performance regressions and other changes in the Web. But often it’s important to look beyond the average and see more detail about the distribution. As of today all the relevant trending charts have a corresponding histogram. For an example, take a look at the trending chart for Total Transfer Size & Total Requests and its corresponding histograms. I’d appreciate feedback on these histograms. In some cases I wonder if a CDF would be more appropriate.
Connections
We now plot the number of TCP connections that were used to load the website. (All desktop stats are gathered using IE9.) Currently the average is 37 connections per page.  
CDNs
We now record the CDN, if any, for each individual resource. This is currently visible in the CDNs section for individual websites. The determination is based on a reverse lookup of the IP address and a host-to-CDN mapping in WebPagetest.

Custom Metrics

Pat Meenan, the creator of WebPagetest, just added a new feature called custom metrics for gathering additional metrics using JavaScript. The HTTP Archive uses this feature to gather these additional stats:

Average DOM Depth
The complexity of the DOM hierarchy affects JavaScript, CSS, and rendering performance. I first saw average DOM depth as a performance metric in DOM Monster. I think it’s a good stat for tracking DOM complexity. 
Document Height
Web pages are growing in many ways such as total size and use of fonts. I’ve noticed pages also getting wider and taller so HTTP Archive now tracks document height and width. You can see the code here. Document width is more constrained by the viewport of the test agent and the results aren’t that interesting, so I only show document height. 
localStorage & sessionStorage
The use of localStorage and sessionStorage can help performance and offline apps, so the HTTP Archive tracks both of these. Right now the 95th percentile is under 200 characters for both, but watch these charts over the next year. I expect we’ll see some growth.
Iframes
Iframes are used frequently to contain third party content. This will be another good trend to watch.
SCRIPT Tags
The HTTP Archive has tracked the number of external scripts since its inception, but custom metrics allows us to track the total number of SCRIPT tags (inline and external) in the page.
Doctype
Specifying a doctype affects quirks mode and other behavior of the page. Based on the latest crawl, 14% of websites don’t specify a doctype, and “html” is the most popular doctype at 40%. Here are the top five doctypes.

Doctype Percentage
html 40%
html -//W3C//DTD XHTML 1.0 Transitional//EN http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd 31%
[none] 14%
html -//W3C//DTD XHTML 1.0 Strict//EN http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd 8%
HTML -//W3C//DTD HTML 4.01 Transitional//EN http://www.w3.org/TR/html4/loose.dtd 3%

Some of these new metrics are not yet available in the HTTP Archive Mobile but we’re working to add those soon. They’re available as histograms currently, but once we have a few months of data I’ll add trending charts, as well.

What’s next?

Big ticket items on the HTTP Archive TODO list include:

  • easier private instance - I estimate there are 20 private instances of HTTP Archive out there today (see here, here, here, here, and here). I applaud these folks because the code and documentation don’t make it easy to setup a private instance. There are thousands of WebPagetest private instances in the world. I feel that anyone running WebPagetest on a regular basis would benefit from storing and viewing the results in HTTP Archive. I’d like to lower the bar to make this happen.
  • 1,000,000 URLs – We’ve increased from 1K URLs at the beginning four years ago to 300K URLs today. I’d like to increase that to 1 million URLs on desktop. I also want to increase the coverage on mobile, but that’s going to probably require switching to emulators.
  • UI overhaul – The UI needs an update, especially the charts.

In the meantime, I encourage you to take a look at the HTTP Archive. Search for your website to see its performance history. If it’s not there (because it’s not in the top 300K) then add your website to the crawl. And if you have your own questions you’d like answered then try using the HTTP Archive dumps that Ilya Grigorik has exported to Google BigQuery and the examples from bigqueri.es.

]]>
http://www.stevesouders.com/blog/2014/06/08/http-archive-new-stuff/feed/ 6
MySQL dumps http://www.stevesouders.com/blog/2014/05/08/mysql-dumps/ http://www.stevesouders.com/blog/2014/05/08/mysql-dumps/#comments Fri, 09 May 2014 04:19:45 +0000 http://www.stevesouders.com/blog/?p=4003 As part of the HTTP Archive project, I create MySQL dumps for each crawl (on the 1st and 15th of each month). You can access the list of dumps from the downloads page. Several people use these dumps, most notably Ilya Grigorik who imports the data into Google BigQuery.

For the last year I’ve hesitated on many feature requests because they require schema changes. I wasn’t sure how changing the schema would affect the use of the dump files that preceded the change. This blog post summarizes my findings.

Format

When I started the HTTP Archive all the dumps were exported in MySQL format using a command like the following:

mysqldump --opt --skip-add-drop-table -u USERNAME -p -h SERVER DBNAME TABLENAME | gzip > TABLENAME.gz

These MySQL formatted dump files are imported like this:

gunzip -c TABLENAME.gz | mysql -u USERNAME -p -h SERVER DBNAME

People using databases other than MySQL requested that I also export in CSV format. The output of this export command is two files: TABLENAME.txt and TABLENAME.sql. The .txt file is CSV formatted and can be gzipped with a separate command.

mysqldump --opt --complete-insert --skip-add-drop-table -u USERNAME -p -h SERVER -T DIR DBNAME TABLENAME
gzip -c DIR/TABLENAME.txt > DIR/TABLENAME.csv.gz

This CSV dump is imported like this:

gunzip DIR/TABLENAME.csv.gz
mysqlimport --local --fields-optionally-enclosed-by="\"" --fields-terminated-by=, --user=USERNAME -p DBNAME DIR/TABLENAME.csv

The largest HTTP Archive dump file is ~25G unzipped and ~3G gzipped. This highlights a disadvantage of using CSV formatted dumps: there’s no way to gzip and ungzip in memory. This is because the mysqlimport command uses the filename to determine which table to use – if you piped in the rows then it wouldn’t know the table name. Unzipping a 25G file can be a challenge if disk space is limited.

On the other hand, the CSV import is ~30% faster than using the MySQL format file. This can save over an hour when importing 30 million rows. The HTTP Archive currently provides dumps in both MySQL and CVS format so people can choose between less disk space or faster imports.

Forward Compatibility

My primary concern is with the flexibility of previously-generated dump files in light of later schema changes – namely adding and dropping columns.

Dump files in MySQL format work fine with added columns. The INSERT commands in the dump are tied to specific column names, so the new columns are simply ignored. CSV formatted dumps are less flexible. The values in a row are stuffed into the table’s columns in order. If a new column is added at the end, everything works fine. But if a column is added in the middle of the existing columns, the row values will all shift one column to the left.

Neither format works well with dropped columns. MySQL formatted files will fail with an “unknown column” error. CSV formatted files will work but all the columns will be shifted, this time to the right.

Takeaways

I now feel comfortable making schema changes without invalidating the existing dump files provided I follow these guidelines:

  • don’t drop columns – If a column is no longer needed, I’ll leave it in place and modify the column definition to be a small size.
  • add columns at the end – I prefer to organize my columns semantically, but all new columns from this point forward will be added at the end.

I’ll continue to create dumps in MySQL and CSV format. These guidelines ensure that all past and future dump files will work against the latest schema.

 

 

 

 

]]>
http://www.stevesouders.com/blog/2014/05/08/mysql-dumps/feed/ 2
Unexpected prerender in Chrome http://www.stevesouders.com/blog/2014/04/30/unexpected-prerender-in-chrome/ http://www.stevesouders.com/blog/2014/04/30/unexpected-prerender-in-chrome/#comments Wed, 30 Apr 2014 21:55:49 +0000 http://www.stevesouders.com/blog/?p=3980 Over the last week I’ve been investigating the cacheability of resources from Shopify. I would visit the page every few hours and track the number of 200 OK versus 304 Not Modified responses. To my surprise, Chrome’s Network tab indicated that almost all the responses were “from cache”.

This didn’t make sense. In many cases the resource URLs changed between test loads. How could a never-before-seen URL be “from cache”? In cases where the URL was the same, I noticed that the Date response header had changed from the previous test but Chrome still marked it “from cache”. How could the Date change without a 200 response status code?

I started thinking about my “Prebrowsing” work (blog post, slides, video). In my findings I talk about how browsers, especially Chrome, are doing more work in anticipation of what the user needs next. This proactive work includes doing DNS lookups, establishing TCP connections, downloading resources, and even prerendering entire pages.

Was it possible that Chrome was prerendering the entire page?

I started by looking at chrome://predictors. Given characters entered into Omnibox (the location field), this shows which URL you’re predicted to go to. In my tests, I had always typed the URL into the location bar, so the predictions for “shopify” could affect Chrome’s behavior in my tests. Here’s what I found in chrome://predictors:

chrome-predictors-shopify

Chrome predicted that if I entered “www.s” into the Omnibox I would end up going to “http://www.shopify.com/” with confidence 1.0 (as shown in the rightmost column). In fact, just typing “ww” had a 0.9 confidence of ending up on Shopify. In other words, Chrome had developed a deep history mapping my Omnibox keystrokes to the Shopify website, as indicated by rows colored green.

From my Prebrowsing research I knew that if the chrome://predictors confidence was high enough, Chrome would pre-resolve DNS and pre-connect TCP. Perhaps it was possible that Chrome was also proactively sending HTTP requests before they were actually needed. To answer this I opened Chrome’s Net panel and typed “www.s” in the Omnibox but never hit return. Instead, I just sat there and waited 10 seconds. But nothing showed up in Chrome’s Net panel:

shopify-omnibox

Suspecting that these background requests might not show up in Net panel, I fired up tcpdump and repeated the test – again only typing “www.s” and NOT hitting return. I uploaded the pcap file to CloudShark and saw 86 HTTP requests!

cloudshark-shopify

I looked at individual requests and saw that they were new URLs that had never been seen before but were in the HTML document. This confirmed that Chrome was prerendering the HTML document (as opposed to prefetching individual resources based on prior history). I was surprised that no one had discovered this before, so I went back to High Performance Networking in Google Chrome by Ilya Grigorik and scanned the Omnibox section:

the yellow and green colors for the likely candidates are also important signals for the ResourceDispatcher! If we have a likely candidate (yellow), Chrome may trigger a DNS pre-fetch for the target host. If we have a high confidence candidate (green), then Chrome may also trigger a TCP pre-connect once the hostname has been resolved. And finally, if both complete while the user is still deliberating, then Chrome may even pre-render the entire page in a hidden tab.

Takeaways

What started off as a quick performance analysis turned into a multi-day puzzler. The puzzle’s solution yields a few takeaways:

  • Remember that Chrome may do DNS prefetch, TCP pre-connect, and even prerender the entire page based on the confidences in chrome://predictors.
  • Not all HTTP requests related to a page or tab are shown in Chrome’s Net panel. I’d like to see this fixed, perhaps with an option to show these behind-the-scenes requests.
  • Ilya knows everything. Re-read his posts, articles, and book before running multi-day experiments.

 

]]>
http://www.stevesouders.com/blog/2014/04/30/unexpected-prerender-in-chrome/feed/ 12
fast, faster, Fastly http://www.stevesouders.com/blog/2014/03/10/fast-faster-fastly/ http://www.stevesouders.com/blog/2014/03/10/fast-faster-fastly/#comments Mon, 10 Mar 2014 17:56:06 +0000 http://www.stevesouders.com/blog/?p=3963 Today I join Fastly as Chief Performance Officer. Read more in Fastly’s announcement.

I’m excited to get back to the world of startups. People who have known me for less than 15 years don’t know that side of me. In my early career I worked at Advanced Decision Systems, Helix Systems (co-founder), General Magic, WhoWhere?, and CoolSync (co-founder) – companies that ranged from 3 to 300 employees.

I went to Yahoo! with the intention of staying for a few years to have some good healthcare. I loved it so much I stayed for eight years working with people like Doug Crockford, Stoyan Stefanov, Nicholas Zakas, Nicole Sullivan, Tenni Thuerer, and Philip Tellis and many more incredible developers. It was there that Ash Patel and Geoff Ralston asked me to start a team focused on performance. As visionary executives, they believed there was a set of performance best practices that would be applicable to all the Yahoo! properties. It turned out those best practices apply to nearly every site on the Web.

Knowing Google’s culture of performance I was excited to continue my work there over the past six years. Google is an amazing company. I want to loudly thank Yahoo! and Google for giving me the opportunity to focus on web performance. They are responsible for sharing this web performance research, tools, and code with the web community. It’s important that we have companies like these to move the Web forward.

Many of Google’s performance projects, such as Chrome, SPDY, WebP, and Public DNS, focus on improving the Web’s infrastructure. These infrastructure projects have a dramatic impact – they help raise the tide for all websites. That’s half of the equation. The other half lies with how websites are built. Even on this fast infrastructure it’s still possible to build a slow website.

That’s why I’m excited to join Fastly where I’ll be able to engage with Fastly’s customers to produce websites that are blazingly fast. Fastly’s CDN platform is built with latest generation technology and software. I hope to add more performance to an already fast network, and make it go even fastlier.

]]>
http://www.stevesouders.com/blog/2014/03/10/fast-faster-fastly/feed/ 16
Measuring localStorage Performance http://www.stevesouders.com/blog/2014/02/11/measuring-localstorage-performance/ http://www.stevesouders.com/blog/2014/02/11/measuring-localstorage-performance/#comments Wed, 12 Feb 2014 00:59:57 +0000 http://www.stevesouders.com/blog/?p=3889 Recently someone asked me if it was possible to measure the performance of localStorage. While this was difficult a few years ago, we can do it now thanks to Navigation Timing. This post explains how to measure localStorage performance as well as the results of my tests showing the maximum size of localStorage, when the penalty of reading localStorage happens, and how localStorage behavior varies across browsers.

Past Challenges

In 2011 & 2012, Nicholas Zakas wrote three blog posts about the performance of localStorage. In the last one, The performance of localStorage revisited, he shared this insight from Jonas Sicking (who worked on localStorage in Firefox) explaining why attempts to measure localStorage up to that point were not accurate:

Firefox starts out by reading all of the data from localStorage into memory for the page’s origin. Once the data is in memory, reads and writes should be relatively fast (…), so our measuring of reads and writes doesn’t capture the full picture.

Nicholas (and others) had tried measuring the performance of localStorage by placing timers around calls to localStorage.getItem(). The fact that Firefox precaches localStorage means a different approach is needed, at least for Firefox.

Measuring localStorage with Navigation Timing

Early attempts to measure localStorage performance didn’t capture the true cost when localStorage is precached. As Jonas described, Firefox precaches localStorage, in other words, it starts reading a domain’s localStorage data from disk when the browser first navigates to a page with that domain. When this happens the performance of localStorage isn’t captured because it might already be in memory before the call to getItem().

My hypothesis is that we should be able to use Navigation Timing to measure localStorage performance, even in browsers that precache it. Proving this hypothesis would let us measure localStorage performance in Firefox, and determine if any other browsers have similar precaching behavior.

The Navigation Timing timeline begins with navigationStart - the time at which the browser begins loading a page. Reading localStorage from disk must happen AFTER navigationStart. Even with this knowledge, it’s still tricky to design an experiment that measures localStorage performance. My experiment includes the following considerations:

  • Fill localStorage to its maximum so that any delays are more noticeable.
  • Use two different domains. In my case I use st2468.com and stevesouders.com. The first domain is used for storing and measuring localStorage. The second domain is for landing pages that have links to the first domain. This provides a way to restart the browser, go to a landing page on stevesouders.com, and measure the first visit to a page on st2468.com.
  • Restart the browser and clear the operating system’s disk cache between measurements.
  • In the measurement page, wrap the getItem() calls with timers as well as recording the Navigation Timing metrics in order to see when the precache occurs. We know it’s sometime after navigationStart but we don’t know what marker it’s before.
  • Make the measurement page cacheable. This removes any variability due to network activity.

The step-by-step instructions can be seen on my test page. I use Browserscope to record the results but otherwise this test is very manual, especially since the only reliable way to clear the OS disk cache on Windows, iOS, and Android is to do a power cycle (AFAIK). On the Macbook I used the purge command.

The Results: maximum localStorage

The results of filling localStorage to the maximum are shown in Table 1. Each browser was tested nine times and the median value is shown in Table 1. (You can also see the raw results in Browserscope.) Before determining if we were able to capture Firefox’s precaching behavior, let me describe the table:

  • The User Agent column shows the browser and version. Chrome, Firefox, Opera, and Safari were tested on a Macbook Air running 10.9.1. IE was tested on a Thinkpad running Windows 7. Chrome Mobile was tested on a Samsung Galaxy Nexus running Android 4.3. Mobile Safari was tested on an iPhone 5 running iOS 7.
  • The size column shows how many characters localStorage accepted before throwing a quota exceeded error. The actual amount of space depends on how the browser stores the strings – single byte, double byte, mixed. The number of characters is more relevant to most developers since everything saved to localStorage is converted to a string. (People using FT Labs’s ftdatasquasher might care more about the actual storage mechanism underneath the covers.)
  • The delta getItem column shows how long the call to getItem() took. It’s the median of the difference between “AFTER getItem time” and “BEFORE getItem time”. (In other words, it’s possible that the difference of the medians in the table don’t equal the “delta getItem” median exactly. This is an artifact of how Browserscope displays results. Reviewing the raw results shows that if the math isn’t exact it’s very close.)
  • The remaining columns are markers from Navigation Timing, plus the manual markers before and after the call to getItem(). The value is the number of milliseconds at which that marker took place relative to navigationStart. For example, in the first row responseStart took place 3 ms after navigationStart. Notice how responseEnd takes place just 2 ms later because this page was read from cache (as mentioned above).

One thing to notice is that there are no Navigation Timing metrics for Safari and Mobile Safari. These are the only major browsers that have yet to adopt the W3C Navigation Timing specification. I encourage you to add your name to this petition encouraging Apple to support the Navigation Timing API. For these browsers, the before and after times are relative to a marker in an inline script at the top of the HEAD.

Table 1. maximum localStorage
User Agent size (K-chars) delta getItem (ms) response- Start time response- End time dom- Loading time BEFORE getItem time AFTER getItem time dom- Interactive time
Chrome 33 5,120 1,038 3 5 21 26 1,064 1,065
Chrome Mobile 32 5,120 1,114 63 69 128 163 1,314 1,315
Firefox 27 5,120 143 2 158 4 15 158 160
IE 11 4,883 759 3 3 3 15 774 777
Opera 19 5,120 930 2 4 14 20 950 950
Mobile Safari 7 2,560 453 1 454
Safari 7 2,560 520 0 520

Did we capture it?

The results from Table 1 show that Firefox’s localStorage precaching behavior is captured using Navigation Timing. The delta of responseStart and responseEnd (the time to read the HTML document) is 156 ms for Firefox. This doesn’t make sense since the HTML was read from cache. This should only take a few milliseconds, which is exactly what we see for all the other browsers that support Navigation Timing (Chrome, IE, and Opera).

Something else is happening in Firefox during the loading of the HTML document that is taking 156 ms. The likely suspect is Firefox precaching localStorage. To determine if this is the cause we reduce the amount of localStorage data to 10K. These results are shown in Table 2 (raw results in Browserscope). With only 10K in localStorage we see that Firefox reads the HTML document from cache in 13 ms (responseEnd minus responseStart). The only variable that changed between these two tests was the amount of data in localStorage: 10K vs 5M. Thus, we can conclude that the increase from 13 ms to 156 ms is due to Firefox precaching taking longer when there is more localStorage data.

Table 2. 10K localStorage
User Agent size (K-chars) delta getItem (ms) response- Start time response- End time dom- Loading time BEFORE getItem time AFTER getItem time dom- Interactive time
Chrome 33 10 3 5 7 18 28 29 29
Chrome Mobile 32 10 28 73 76 179 229 248 250
Firefox 27 10 1 3 16 4 15 16 16
IE 11 10 15 6 6 6 48 60 57
Opera 19 10 7 2 4 15 23 33 33
Mobile Safari 7 10 16 1 17
Safari 7 10 11 0 11

Using Navigation Timing we’re able to measure Firefox’s precaching behavior. We can’t guarantee when it starts but presumably it’s after navigationStart. In this experiment it ends with responseEnd but that’s likely due to the page blocking on this synchronous disk read when the call to getItem() is reached. In the next section we’ll see what happens when the call to getItem() is delayed so there is not a race condition.

Does anyone else precache localStorage?

We discovered Firefox’s precaching behavior by comparing timings for localStorage with 10K versus the maximum of 5M. Using the same comparisons it appears that none of the other browsers are precaching localStorage; the delta of responseStart and responseEnd for all other browsers is just a few milliseconds. We can investigate further by delaying the call to getItem() until one second after the window onload event. The results of this variation are shown in Table 3 (raw results in Browserscope).

Table 3. maximum localStorage, delayed getItem
User Agent size (K-chars) delta getItem (ms) response- Start time response- End time dom- Loading time BEFORE getItem time AFTER getItem time dom- Interactive time
Chrome 33 5,120 1,026 3 5 21 1112 2139 85
Chrome Mobile 32 5,120 1,066 83 87 188 1240 2294 234
Firefox 27 5,120 0 3 17 4 1038 1039 20
IE 11 4,883 872 5 5 5 1075 1967 49
Opera 19 5,120 313 2 4 15 1025 1336 23
Mobile Safari 7 2,560 104 1003 1106
Safari 7 2,560 177 1004 1181

Table 3 confirms that Firefox is precaching localStorage – “delta getItem” is 0 ms because there was plenty of time for Firefox to finish precaching before the call to getItem(). All the other browsers, however, have positive values for “delta getItem”. The values for Chrome, Chrome Mobile, and IE are comparable between Table 1 and Table 3: 1038 vs 1026, 1114 vs 1066, and 759 vs 872.

The values for Opera, Mobile Safari, and Safari are slower in Table 1 compared to Table 3: 930 vs 313, 453 vs 104, and 520 vs 177. I don’t have an explanation for this. I don’t think these browsers are precaching localStorage (the values from Table 3 would be closer to zero). Perhaps the call to getItem() took longer in Table 1 because the page was actively loading and there was contention for memory and CPU resources, whereas for Table 3 the page had already finished loading.

500K localStorage

So far we’ve measured maximum localStorage (Table 1) and 10K of localStorage (Table 2). Table 4 shows the results with 500K of localStorage. All of the “delta getItem” values fall between the 10K and maximum values. No real surprizes here.

Table 4. 500K localStorage
User Agent size (K-chars) delta getItem (ms) response- Start time response- End time dom- Loading time BEFORE getItem time AFTER getItem time dom- Interactive time
Chrome 33 500 20 3 4 19 25 43 43
Chrome Mobile 32 500 164 78 85 144 183 368 368
Firefox 27 500 14 2 30 3 15 30 31
IE 11 500 32 5 5 5 48 89 83
Opera 19 500 36 2 4 14 23 57 58
Mobile Safari 7 500 37 1 38
Safari 7 500 44 0 44

Conclusions

The goal of this blog post was to see if Firefox’s localStorage precaching behavior was measurable with Navigation Timing. We succeeded in doing that in this contrived example. For real world pages it might be harder to capture Firefox’s behavior. If localStorage is accessed early in the page then it may recreate the condition found in this test where responseEnd is blocked waiting for precaching to complete.

Another finding from these tests is that Firefox is the only browser doing precaching. This means that the simple approach of wrapping the first access to localStorage with timers accurately captures localStorage performance in all browsers except Firefox.

It’s hard not to focus on the time values from these tests but keep in mind that this is a small sample size. I did nine tests per browser for Table 1 and dropped to five tests per browser for Tables 2-4 to save time. Another important factor is that the structure of my test page is very simple and unlike almost any real world website. Rather than focus on these time values, it would be better to use the conclusions about how localStorage performs to collect real user metrics.

There are takeaways here for browser developers. There’s quite a variance in results across browsers, and Firefox’s precaching behavior appears to improve performance. If browser teams do more extensive testing coupled with their knowledge of current implementation it’s likely that localStorage performance will improve.

Update Feb 24, 2014: This Firefox telemetry chart shows that 80% of first accesses to localStorage have zero wait time because localStorage is already in memory confirming that precaching localStorage has a positive effect on performance.

A smaller takeaway is the variance in storage size. Whether you measure by number of characters or bytes, Safari holds half as much as other major browsers.

Notes, Next Steps, and Caveats

As mentioned above, the time values shown in these results are based on a small sample size and aren’t the focus of this post. A good next step would be for website owners to use these techniques to measure the performance of localStorage for their real users.

In constructing my test page I tried various techniques for filling localStorage. I settled on writing as many strings as possible of length 1M, then 100K, then 10K, then 1K – this resulted in a small number of keys with some really long strings. I also tried starting with strings of length 100K then dropping to 1K – this resulted in more keys and shorter strings. I found that the first approach (with some 1M strings) produced slower read times. A good follow-on experiment would be to measure the performance of localStorage with various numbers of keys and string lengths.

With regard to how browsers encode characters when saving to localStorage, I chose to use string values that contained some non-ASCII characters in an attempt to force browsers to use the same character encoding.

In my call to getItem() I referenced a key that did not exist. This was to eliminate any variability in reading (potentially large) strings into memory since my main focus was on reading all of localStorage into memory. Another follow-on experiment would be to test the performance of reading keys of different states and lengths to see if browsers performed differently. For example, one possible browser optimization would be to precache the keys without the values – this would allow for more efficient handling of the case of referencing a nonexistant key.

A downside of Firefox’s precaching behavior would seem to be that all pages on the domain would suffer the penalty of reading localStorage regardless of whether they actually used it. However, in my testing it seemed like Firefox learned which pages used localStorage and avoided precaching on pages that didn’t. Further testing is needed to confirm this behavior. Regardless, it seems like a good optimization.

Thanks

Thanks to Nicholas Zakas, Jonas Sicking, Honza Bambas, Andrew Betts, and Tony Gentilcore for providing advice and information for this post.

]]>
http://www.stevesouders.com/blog/2014/02/11/measuring-localstorage-performance/feed/ 3
Browser Wishlist 2013 http://www.stevesouders.com/blog/2014/01/22/browser-wishlist-2013/ http://www.stevesouders.com/blog/2014/01/22/browser-wishlist-2013/#comments Wed, 22 Jan 2014 19:34:16 +0000 http://www.stevesouders.com/blog/?p=3872 This is a cross-post from last month’s Performance Calendar (hence the reference to “holiday” spirit).

In case any browser teams are awash in the spirit of holiday gift-giving, here’s my wishlist for performance features I’d love to see. I did a similar Browser Performance Wishlist in 2010. I hope this list will initiate numerous discussions that result in a faster browsing experience for everyone.

  1. Navigation Timing in Safari
  2. async 3rd party content
  3. async document.write
  4. Preferred Caching
  5. Prefresh
  6. responsive images (Client Hints)
  7. more RUM (rendering, fps, GC, etc.)
  1. smarter font loading
  2. anchor PING support
  3. DEFER for inline scripts
  4. lookahead for @import
  5. UX for perception of speed
  6. web components
  1. async 3rd party content (especially ads)

    The amount of 3rd party content on websites continues to grow.* Websites need a way to load this content without having it delay the 1st party content on the page. Iframes are a possible solution, but there are various issues including fixed sizing. Many 3rd party content providers provide async snippets, but this is the exception rather than the rule.

    Perhaps the most popular 3rd party content on the Web is ads, where asynchronous snippets are almost unheard of. Asynchronous loading is more complex for ads because they often use document.write. We need a way to load 3rd party content, including ads, asynchronously even when there’s a possibility of document.write being used. HTML imports are a possible solution depending on the final implementation and the need for workarounds.

    * This is based on data such as the 50% increase in the number of domains per page and the increase in estimated amount of third party content from 32% to 38% over the previous two years. It would be great if someone used the HTTP Archive data to more precisely quantify this. The hard part is defining “3rd party content” in SQL.

  2. async document.write

    Another approach to enable loading scripts asynchronously would be if browsers supported an async version of document.write. Currently, if document.write occurs in an asynchronous script the best case is the document.write is ignored. The worst case is the entire page is erased and replaced with just the output from document.write. (See this test page.)

    It would be better if browsers instead inserted the output from document.write right below the associated SCRIPT tag. The implementation is much more complex that I make it sound. In fact, Opera used to have a Delayed Script Execution option that was able to do this but it’s no longer available.

  3. Preferred Caching
    There’s a long tail to Internet browsing, but users also have favorite websites that they visit every day. To increase the probability of those sites loading faster, browsers should more aggressively cache the resources associated with these favorite websites. Websites could be deemed “favorite” manually by the user as well as automatically based on browsing history. (See related blog post.)
  4. Prefresh
    “Prefresh” is a concept Tony Gentilcore and I came up with to make websites load faster by proactively downloading and updating critical resources before they’re needed. The determination of what to prefresh is based on prior activity. This is currently an experimental feature in Chrome. (See related blog post.) I’d like to see the code finished in Chrome and, based on results, adopted by other browsers.
  5. responsive images (Client Hints)
    Responsive images is a complex issue with multiple candidate solutions. (See Bruce Lawson’s Responsive images – end of year report.) All of the proposals have pros and cons. The issue has been discussed for more than a year. It’s important for browsers to pick the preferred solution. I wish Client Hints was supported. It doesn’t solve all the problems, but I think it’s a lightweight solution that works in most cases, especially since CDNs can automate the process for website owners.
  6. more RUM (rendering, fps, GC, etc.)

    The W3C Web Performance Working Group has made incredible progress with the Navigation Timing, Resource Timing, and User Timing specifications. (Except on Safari as mentioned earlier.) These APIs allow us to measure performance where it matters the most: on real users. But the performance we’re able to measure is more about the mechanics of the page and less about the user experience and what’s going on behind the scenes.

    I’d like JavaScript access to information about rendering, garbage collection, frames per second, memory use, and JavaScript execution. Much of this is available today in browser dev tools. The big work seems to be defining what to measure in a way that can be standardized across browsers while not slowing down the user experience, making that accessible via JavaScript, and adding it to preferred RUM solutions and dashboards.

  7. smarter font loading
    The number of Top 1000 websites that use custom fonts has increased from 13% to 35% in the last year (according to the HTTP Archive). The use of custom fonts is increasing rapidly. Unfortunately, custom fonts can make websites render more slowly and have Flash of Unstyled Content (FOUC) issues. These issues would be mitigated if browsers loaded fonts with some special handling:
    • Browser preloaders (AKA speculative parsers and lookahead parsers) should start downloading fonts sooner. The Font Priority test page shows this does not happen today. The test page has a @font-face rule that is defined in a STYLE block in the HEAD. That font is used by the H3 element at the top of the page, which is followed by 18 images. The font and images are all on the same domain. It’s more important to download the font than the images, but all the major browsers download 6-8 images before downloading the font. That’s because the preloader sees the IMG tags and starts downloading those images first. Eventually it parses the H3 element and determines the font is needed and queues it up for the second round of downloads. It would be better if the preloader was extended to scan ahead for the definition of as well as the use of @font-face styles.
    • Browsers should avoid Flash of Unstyled Text and blank elements by waiting a very short amount of time (300-500 ms) for the custom font to download. If the custom font hasn’t arrived by that time then the element should be rendered in a default font and should not be repainted if/when the custom font finally downloads. The selection of a time value is a hard decision; I lean toward a short timeout. Current behavior varies widely across major browsers. The @font-face FOUT test page shows that Chrome, Opera, and Safari leave the element blank until the font is fully downloaded. Firefox leaves it blank for ~3 seconds (too late) at which point it draws the element in a default font, and then redraws it when the custom font arrives (FOUT). IE10 draws the text immediately using a default font (too early) and then redraws it later (FOUT). Checkout Typekit’s asynchronous embed code and WebFont Loader to see how to achieve your desired behavior today. (Hint: inline your font as a data: URI inside a stylesheet.)
    • Developers should be able to specify how font loading and rendering behaves. It’s going to be hard to pick a default behavior and timeout value that pleases everyone. The question are:
      • Should the element be blank until the font file arrives, or should it be rendered with a default font?
      • If it’s rendered with a default font, when should that happen (immediately or after a delay)?
      • If it’s rendered with a default font after a delay, should it be repainted when the custom font arrives?

      The Resource Priorities spec helps by defining the “resource-priorities” CSS property which can take values of “lazyload” and “postpone”, but the rendering behavior in the presence of this property isn’t specified. I propose that if “lazyload” or “postpone” is used with @font-face, then the browser should immediately render the element with a default font and it should be repainted when the font file arrives. If neither “lazyload” nor “postpone” is specified, then the element should be blank until the font file arrives or the request times out. (Note that CSS Font Load Events provides a way to do much of this programmatically.)

    • Font files should be given a high priority in the browser cache. I.e., images and video should be purged from the cache before custom font files. It’s possible this is already the case – I don’t have an easy way to test this.
    • Browsers should re-use expired fonts with a stale-while-revalidate policy by default. If a font file is in the cache but is expired, browsers should reuse it immediately while doing the conditional GET request in the background. This is similar to the behavior proposed for the stale-while-revalidate Cache-Control extension.
  8. anchor PING support
    The HTML5 PING attribute for anchors is great for performance. Google Search on mobile got users to their favorite search result website 200-400 ms faster by switching from redirects to A PING. Right now A PING is only supported in Chrome and Safari. We need broader browser support.
  9. DEFER for inline scripts

    Downloading external scripts so they don’t block the HTML parser is good. The main ways to do that today are the ASYNC and DEFER attributes. Many sites have multiple scripts with a dependency order. These sites can’t use ASYNC since that executes scripts in a non-deterministic order. Therefore, DEFER is the method of choice for interdependent external scripts. But these sites also typically have inline scripts that rely on the symbols from those external scripts. Currently, there’s no way in markup to declare that dependency.

    Adding support for DEFER for inline scripts would solve this problem, but it’s currently only supported for external scripts. If it was supported for inline scripts, developers could ensure proper execution order by adding DEFER to all the relevant inline and external SCRIPT tags.

    Personally, I’d prefer to see support for John Resig’s Degrading Script Tags pattern - it puts the dependent inline code within the external script’s SCRIPT block, like so:

    <script src="main.js">
    functionDefinedInMainJs();
    </script>

    The beauty of this technique is functionDefinedInMainJs isn’t called if main.js fails to load. This avoids undefined symbol errors in these failure scenarios.

  10. lookahead for @import

    Browsers use a preloader (AKA lookahead parser or speculative parser) to accelerate resource downloads while the main parser is blocked downloading scripts. The preloader does pretty well with typical resource downloads: SCRIPT, LINK, IMG, and IFRAME. But the preloader logic should be improved to accelerate the downloading of stylesheets specified with @import.

    The @import Priority test page shows that the preloader in Firefox and IE 9, 10 & 11 doesn’t recognize “@import” and instead loads a bunch of images ahead of the stylesheet (even though the stylesheet is in the HEAD and the images are later in the BODY).

    Another optimization for @import would be if browsers speculatively parsed the first few lines of a “main” stylesheet to see if it references any “sub” stylesheets using “@import”. If so, the browser should start downloading these “sub” stylesheets immediately without waiting for the entire “main” stylesheet to finish downloading. Fortunately, browsers only have to look at the first few lines because the CSS spec dictates that all @imports “must precede all other at-rules and style rules in a style sheet”. Unfortunately, the link with BIG @import test page shows that all major browsers wait until the entire stylesheet is downloaded before downloading an @import’ed stylesheet. Improving the browser preloader to scan stylesheets for “@import” would speedup page rendering, especially on mobile.

  11. UX for perception of speed
    I wish browsers would invest more time in promoting a perception of speed into the browser UI. I’ve spoken about the perception of speed (video, slides). Some of the techniques are more relevant for web developers, but many ideas are targeted at browsers and thus would benefit every website. Examples include improving the browser busy indicators, clearing the page, and “natural” progress indicators.
  12. improved web components
    I’m optimistic about the future of Web Components, but I made some suggestions in a recent series of blog posts: Async Ads with HTML Imports, Performance of Custom Elements, and HTML Imports: scope, security, and suggestions. As we specify and implement new web features it’d be good if we focused on performance to ensure a faster future on the Web.
]]>
http://www.stevesouders.com/blog/2014/01/22/browser-wishlist-2013/feed/ 1
HTML Imports: scope, security, and suggestions http://www.stevesouders.com/blog/2013/12/02/html-imports-scope-security-and-suggestions/ http://www.stevesouders.com/blog/2013/12/02/html-imports-scope-security-and-suggestions/#comments Mon, 02 Dec 2013 22:03:37 +0000 http://www.stevesouders.com/blog/?p=3848 This is the third of three blog posts on Web Components, primarily focusing on the performance impact of HTML imports.

Scope

The first post, Async Ads with HTML Imports, proposes using this new technology as a way to avoid the blocking behavior of synchronous ads. It’s better than using iframes because the HTML import’s LINK tag can be placed in HEAD so that the ads show up more quickly resulting in higher ad revenue.

One downside is that many ads use document.write and this erases the entire page when wrapped inside an HTML import. This is surprising for folks who know that document.write works fine within an iframe. Why shouldn’t it work just as well within an HTML import? After all, HTML imports are separate documents just like iframes. The first line of the spec confirms this:

HTML Imports, or just imports from here on, are HTML documents…

And the interface clearly shows that a document is created to contain the content:

interface Import {
    readonly attribute DOMString? href;
    readonly attribute Node ownerNode;
    readonly attribute Document content;
};

It turns out the issue is because when JavaScript in the HTML import references the “document” variable, it does NOT refer to the HTML import’s document. Instead, it refers to the main page’s document. In other words, the HTML import’s JavaScript is executed in the scope of the main page’s document, not in the scope of its own document.

Jonas Sicking (Mozilla) started a thread with the topic what scope to run in that discusses this issue. The suggested solution is to recommend that HTML imports use the MODULE tag rather than SCRIPT tags. There’s more discussion of module loaders and module namespace collision. To me the bigger issue is that, despite this recommendation, HTML imports will still have SCRIPT tags, and that JavaScript will execute in a context that is likely to be counterintuitive to developers.

It would be better if JavaScript within an HTML import was executed in the context of the HTML import’s document. The HTML import’s JavaScript could still reach the parent window’s namespace, for example, window.parent.document. Although, it would be nice if the website owner could control this access, which leads us to security.

Security

I believe Web Components will become popular as a way to reuse 3rd party widgets, but this introduces a security risk where 3rd parties might peruse information that’s confidential to the website and its users, such as cookies.

I noticed that the spec said HTML imports must be CORS-enabled. At first I thought this was to address the issue of 3rd party HTML imports accessing privileged information in the main page. But on further reading I realized it’s the other way around: the purpose is to provide a way for the HTML Import to allow the main page to access the content inside the imported document.

I created some test pages to confirm this behavior. The HTML import in the Custom Element example is CORS-enabled by adding this response header to the HTML import:

Access-Control-Allow-Origin: *

The Custom Element no CORS test page has that header removed and thus the HTML import fails. While this correctly protects the 3rd party content from being exposed to the main website, it’s important for the adoption of HTML imports to provide a means of security in the other direction. Iframes address this issue with the HTML5 sandbox attribute. I’d like to see something similar added for HTML imports.

Suggestions

These last three blog posts have talked about the performance, rendering, and JavaScript issues I found with regard to Web Components. I’d like to wrap it up by providing a list of my suggestions for Web Components:

  • Add a “lazyload” attribute to <link rel="import" ...>. This allows for faster page rendering. This might be addressed by the Resource Priorities spec, but the desired behavior needs to be implemented where HTML imports do not block rendering when a SCRIPT tag is encountered.
  • Add an “elements” attribute to <link rel="import" ...>. This provides a way to avoid FOUC for custom elements while allowing prior DOM elements to render. Daniel Buchner and I proposed this on the W3C public-webapps mailing list.
  • Make <link rel="import" ...> valid inside BODY. Right now they’re only valid inside HEAD. It’s better if the HTML import’s LINK tag is placed in the BODY where the content is to be imported because:
    • It eliminates the possibility of the SCRIPT tag blocking rendering of the entire page while ALSO providing a way to make certain types of HTML imports synchronous (block rendering until the import is ready) thus avoiding FOUC.
    • It makes it easier for new developers to understand the semantics of the page’s construction by having all the parts of the Web Component located next to each other.
  • Make JavaScript inside an HTML import execute in the import’s document context. This is likely more intuitive for developers and provides more portability for existing JavaScript migrated to be an HTML import.
  • Add a “sandbox” attribute to <link rel="import" ...>This allows website owners to include 3rd party content while preventing those unknown sources from accessing confidential information on the page (such as cookies).

3rd party content is a performance issue that gets worse every year. I’m excited about the potential of Web Components as the specification and implementations grow to address the needs of web developers and website owners.

]]>
http://www.stevesouders.com/blog/2013/12/02/html-imports-scope-security-and-suggestions/feed/ 0
Performance and Custom Elements http://www.stevesouders.com/blog/2013/11/26/performance-and-custom-elements/ http://www.stevesouders.com/blog/2013/11/26/performance-and-custom-elements/#comments Tue, 26 Nov 2013 23:43:06 +0000 http://www.stevesouders.com/blog/?p=3827 This past week I dug into the performance of custom elements and found some surprises.

In my previous blog post, Async Ads with HTML Imports, I complained that HTML imports block the page from rendering when a SCRIPT tag is found and lamented the fact that the spec doesn’t provide a mechanism to make this coupling asynchronous. Custom elements are just the opposite: they’re asynchronous by default and the spec doesn’t provide a way to make them synchronous.

Cake and eat it too

It’s important to understand why we need both synchronous AND asynchronous mechanisms for loading content.

  • Sometimes content is so critical to the page it should be rendered before anything else. In these situations it’s necessary to load the content synchronously in order to achieve the user experience desired by the website owner and designers. Synchronous loading is also necessary in other situations such as when there are dependencies between resources (e.g., interdependent scripts) and to avoid jarring changes in page layout (also known as Flash of Unstyled Content or FOUC).
  • Other times, the content coming from sub-resources in the page is secondary to the main page’s content and developers & designers prefer to load it asynchronously. This is a newer pattern in web development. (I like to think I had something to do with it becoming popular.) Loading these less critical resources asynchronously produces a better user experience in terms of faster loading and rendering.

The bottomline is there are situations that call for both behaviors and developers need a way to achieve the user experience they deem appropriate. The main role for specs and browsers is to provide both mechanisms and choose a good default. We didn’t do that in the previous versions of HTML and are trying to fill that gap now with the Resource Priorities spec which adds the lazyload attribute to various tags including IMG, SCRIPT, and LINK. We don’t want to repeat this gap-filling-after-the-fact process in the future, so we need to provide sync and async capabilities to the HTML5 features being spec’ed now – and that includes Web Components.

Custom Elements howto

Note that right now the only browser I found that supports Web Components is Chrome Canary – so you’ll need to install that to play with the examples. I turned on the following flags in chrome://flags/: Experimental Web Platform features, Experimental JavaScript, and HTML Imports.

The way to define a custom element is in JavaScript. Here’s the custom element used in my examples. It creates a new custom element called x-foo:

var XFooProto = Object.create(HTMLElement.prototype);

XFooProto.createdCallback = function() {
    this.innerHTML = '<div id="imported-content" style="background: #E99; border: 2px; font-size: 2em; text-align: center; padding: 8px; height: 100px;">CUSTOM ELEMENT</div>';
};

var XFoo = document.register('x-foo', {prototype: XFooProto});

To make custom elements more reusable they’re wrapped inside an HTML import:

<link rel="import" href="import-custom-element.php">

In the HTML document we can use the custom element just like any other HTML tag:

<x-foo></x-foo>

Experienced developers recognize that this creates a race condition: what happens if the x-foo tag gets parsed before import-custom-element.php is done downloading?

(async) Custom Elements = FOUC

The first example, custom-element.php, demonstrates the typical custom element implementation described above. If you load it (in Chrome Canary) you’ll see that there’s a Flash of Unstyled Content (FOUC). This reveals that browsers handle custom elements asynchronously: the HTML import starts downloading but the browser continues to parse the page. When it reaches the x-foo tag it skips over it as an unrecognized element and renders the rest of the page. When the HTML import finishes loading the browser backfills x-foo which causes the page’s content to jump down ~100 pixels – a jarring FOUC experience.

This is great for faster rendering! I love that the default is async. And there are certainly scenarios when this wouldn’t created FOUC (custom elements that aren’t visible or will be used later) or the FOUC isn’t so jarring (below-the-fold, changes style but not layout). But in cases like this one where the FOUC is undesirable, there needs to be a way to avoid this disruptive change in layout. Sadly, the spec doesn’t provide a way of doing this. Let’s look at two possible workarounds.

Sized Custom Elements

The jarring change in layout can be avoided if the main page reserves space for the custom element. This is done in the custom-element-sized.php example like this:

<div style="height: 120px;">
<x-foo></x-foo>
</div>

The custom element is inside a fixed size container. As shown by this example, the existing page content is rendered immediately and when the HTML import finally finishes downloading the custom element is backfilled without a change in layout. We’ve achieved the best of both worlds!

The drawback to this approach is it only works for custom elements that have a fixed, predefined size. That condition might hold for some custom elements, but certainly not for all of them.

Sync Custom Elements

The custom-element-sync.php example shows a workaround to avoid FOUC for custom elements that have an unknown size. Unfortunately, this technique blocks rendering for everything in the page that occurs below the custom element. The workaround is to add a SCRIPT tag right above the custom element, for example:

<script>
var foo=128;
</script>
<x-foo></x-foo>

As shown in my previous post, HTML imports cause the parser to stop at the first SCRIPT tag that is encountered. There is a slight benefit here of making sure the only SCRIPT tag after the <link rel="import"...> is right before the custom element – this allows the content above the custom element to render without being blocked. You can see this in action in the example – only the content below the custom element is blocked from rendering until the HTML import finishes loading.

By blocking everything below the custom element we’ve avoided the FOUC issue, but the cost is high. Blocking this much content can be a bad user experience depending on the main page’s content. Certainly if the custom element occupied the entire above-the-fold area (e.g., on a mobile device) then this would be a viable alternative.

It would be better if the spec for custom elements included a way to make them synchronous. One solution proposed by Daniel Buchner and me to W3 Public Webapps is to add an attribute called “elements” to HTML imports:

<link rel="import" href="elements.html" elements="x-carousel, x-button">

The “elements” attribute is a list of the custom elements that should be loaded synchronously. (In other words, it’s NOT the list of all custom elements in the HTML import – only the ones that should cause rendering to be blocked.) As the browser parses the page it would skip over all custom elements just as it does now, unless it encounters a custom element that is listed in the “elements” attribute value (e.g., “x-carousel” and “x-button”). If one of the listed custom elements is reached, the parser would block until either the custom element becomes defined or all outstanding HTML import requests are done loading.

Tired of hacks

I love finding ways to make things work the way I want them to, but it’s wrong to resort to hacks for these new HTML5 features to achieve basic behavior like avoiding FOUC and asynchronously loading. Luckily, the specs and implementations are in early stages. Perhaps there’s still time to get them changed. An important part of that is hearing from the web development community. If you have preferences and use cases for HTML imports and custom elements, please weigh in. A little effort today will result in a better Web tomorrow.

Many thanks to the authors for these fantastic articles on Web Components:

]]>
http://www.stevesouders.com/blog/2013/11/26/performance-and-custom-elements/feed/ 6
Async Ads with HTML Imports http://www.stevesouders.com/blog/2013/11/16/async-ads-with-html-imports/ http://www.stevesouders.com/blog/2013/11/16/async-ads-with-html-imports/#comments Sat, 16 Nov 2013 13:05:03 +0000 http://www.stevesouders.com/blog/?p=3803 Scripts loaded in the typical way (<script src="a.js"></script>) block rendering which is bad for performance. The solution to this problem is to load scripts asynchronously. This is the technique used by the best 3rd party snippets, for example, Google Analytics, Tweet button, Facebook SDK, and Google+ Follow button.

Ads are probably the most prevalent 3rd party content on the Web. Unfortunately, most ads can’t be loaded asynchronously because they use document.write. (Doing document.write in an async script causes the entire page to be erased. We’ll see this in the examples below.) Some teams (GhostWriter, PageSpeed) have tried to solve the async document.write problem but it requires a lot of code and edge cases exist.

In my recent investigations into the performance of HTML Imports I discovered a way to make ads load asynchronously – even ads that use document.write. Let’s take a look at how HTML imports block, how to make HTML imports asynchronous, the dangers of doing this with document.write, and the workaround to make this all work.

HTML imports block rendering

HTML imports block rendering if used in the default way. This is unfortunate, especially given that this is a recent addition to HTML when the importance of not blocking rendering is well known. The HTML Imports spec is still a working draft, so it’s possible this could be switched so that they load asynchronously by default.

Nevertheless, HTML imports are typically created like this:

<link rel="import" href="import.php">

Content from the imported HTML document is inserted like this:

<div id=import-container></div>
<script>
var link = document.querySelector('link[rel=import]');
var content = link.import.querySelector('#imported-content');
document.getElementById('import-container').appendChild(content.cloneNode(true));
</script>

The LINK tag itself doesn’t block rendering – the browser knows the import can be downloaded asynchronously. But rendering is blocked when the browser encounters the first SCRIPT tag following the LINK. This behavior is demonstrated in the sync.php test page. To make the blocking observable, the import takes five seconds to download and then the pink “IMPORTED CONTENT” is displayed. The SCRIPT block is in the middle of the page so the first paragraph IS rendered, but the last paragraph IS NOT rendered until after five seconds. This demonstrates that HTML imports block rendering.

Running the examples: Currently HTML imports only work in Chrome Canary and you have to turn on the following flags in chrome://flags/: Experimental Web Platform features, Experimental JavaScript, and HTML Imports.

Making HTML imports async

It’s not too hard to make HTML imports asynchronous thus avoiding the default blocking behavior. Instead of using the LINK tag in markup, we create it using JavaScript:

var link = document.createElement('link');
link.rel = 'import';
link.onload = function() {
    var link = document.querySelector('link[rel=import]');
    var content = link.import.querySelector('#imported-content');
    document.getElementById('import-container').appendChild(content.cloneNode(true));
};
link.href = url;
document.getElementsByTagName('head')[0].appendChild(link);

The async.php test page shows how using this asynchronous pattern doesn’t block rendering – the last paragraph is rendered immediately, then after five seconds we see the pink “IMPORTED CONTENT” from the HTML import. This shows that HTML imports can be used without blocking the page from rendering.

HTML imports with document.write – watch out!

This is kind of weird and might be hard to grok: HTML imports have their own HTML document. BUT (here’s the complex part) any JavaScript within the HTML import is executed in the context of the main page. At least that’s the way it works now in Chrome. The spec doesn’t address this issue.

This is important because some 3rd party content (especially ads) use document.write. Some people might think that a document.write inside an HTML import would write to the HTML import’s document. But that’s not the case. Instead, document refers to the main page’s document. This can produce surprising (as in “bad”) results.

As shown in the sync docwrite.php and async docwrite.php test pages, when the HTML import contains a script that uses document.write it erases the content of the main page. If you’re uncertain whether the imported content uses document.write then it’s risky to use HTML imports. Or is it?

Safely using HTML imports with document.write

Since much 3rd party content (especially ads) use document.write, there’s a motivation to make them work with HTML imports. However, as shown by the previous examples, this can have disastrous results because when the HTML import does document.write it’s actually referencing the main page’s document and erases the main page.

There’s a simple “hack” to get around this. We can’t redefine document, but we CAN redefine document.write within the HTML import:

// inside the HTML import
document.write = function(msg) {
    document.currentScript.ownerDocument.write(msg);
};

With this change, all document.write output from scripts inside the HTML import goes to the HTML import’s document. This eliminates the problem of the HTML import clearing the main page. This fix is shown by the sync docwrite-override.php and async docwrite-override.php test pages.

Async (document.write) ads with HTML imports

Let’s see this technique in action. The async-ads.php test page includes Google’s show_ads.js to load real ads. The overridden version of document.write also echoes the output to the page so you can verify what’s happening. The document.write works and the ad is shown even though it’s loaded asynchronously.

This is a major accomplishment but there are some big caveats:

  • Although we’ve overridden document.write, there might be other JavaScript in the HTML import that assumes it’s running in the main page’s context (e.g., location.href, document.title).
  • Some people believe it would be good to disable document.write inside HTML imports, in which case ads wouldn’t work.
  • We need a fallback as support for HTML imports grows. This is possible by detecting support for HTML imports and reverting to the current (blocking) technique for ads.

Perhaps the biggest caveat is whether it’s realistic to expect website owners to do this. I don’t think a majority of websites would adopt this technique, but I like having an option to make ads async for websites that are willing to do the work. Right now, motivated website owners don’t have good alternatives for loading ads without blocking their own content on their page. I know some sites that have loaded ads at the bottom of the page in a hidden div and then clone them to the top when done, but this usually results in a drop in ad revenue because the ads load later. Using HTML imports allows the ad to be loaded at the top so we can have asynchronous behavior without a loss in ad revenue.

The goal of this post is to suggest that we find a way to solve one of today’s biggest obstacles to fast web pages: ads. The spec for HTML imports is a working draft and there’s only one implementation, so both are likely to change. My hope is we can make HTML imports asynchronous by default so they don’t block rendering, and use them as technique for achieving asynchronous ads.

]]>
http://www.stevesouders.com/blog/2013/11/16/async-ads-with-html-imports/feed/ 15
HTTP Archive 3 Year Anniversary (Thank You Pat Meenan) http://www.stevesouders.com/blog/2013/11/13/http-archive-3-year-anniversary-thank-you-pat-meenan/ http://www.stevesouders.com/blog/2013/11/13/http-archive-3-year-anniversary-thank-you-pat-meenan/#comments Wed, 13 Nov 2013 10:19:53 +0000 http://www.stevesouders.com/blog/?p=3796 The earliest results available in the HTTP Archive are from Nov 15 2010, so in a sense this week is our three year anniversary. Three years! This was on my mind during breakfast as I thought back on how the HTTP Archive came about.

The idea came to me in 2007. I spent years evangelizing the idea which I called “the Internet Performance Archive”. The amount of work seemed large so instead of building it myself I met with various companies and encouraged them to build it, but to no avail. I knew it was worthwhile to record performance metrics aggregated across top websites. Each year that passed without this archive meant data that we’d never be able to reclaim. I felt a sense of urgency around getting the archive going.

Then, in September 2010 a confluence of events made me realize I could build it myself. The HTTP Archive file format, an effort I coordinated with Jan Odvarko (Firebug) and Simon Perkins (HttpWatch), had been announced the year before and was gaining wider support. There were more tools available that supported the HAR file format.

But the key factor was the work Pat Meenan was doing on WebPagetest. At this time Pat was working still at AOL. He was expanding the features of WebPagetest significantly and it was becoming one of the most important performance tools in the industry. On September 29 2010 I sent him this email:

Hi, Pat.

Do you have time to talk today about an idea? I’m open 10:30am-12:30pm and after 3:30pm PT.

The project is the Internet Performance Archive (I mention it here) – a data warehouse of web performance stats. I’ve been talking about this for years, and I’d like to put up a first version now that would have stats for Fortune 500, Global 500, Alexa 1000 and perhaps other major lists. I’d like to get your thoughts and figure out a way to generate the HAR files as easily as possible (ie, it doesn’t take any of your time ;-).

-Steve

In the ensuing discussion I suggested that Pat create an API for WebPagetest, so that I could build the HTTP Archive as a layer on top of it. In usual fashion, Pat informed me that the feature I wanted was already implemented. We proceeded to iterate on the initial LAMP prototype and started recording data less than two months later. After gathering six months of data the HTTP Archive was announced in March 2011.

There was one part of that initial concept that I was UNable to achieve – doing it without taking any of Pat’s time. Just the opposite, Pat has put in a ton of time to make the HTTP Archive possible. All of the tests are done on a private instance of WebPagetest (which Pat setup). When our load became too costly to run on AWS, Pat helped buy our own hardware and get it setup in our data center, Internet Systems Consortium. When we merged with the Internet Archive, Pat integrated our systems to use their S3-like storage system. He has built special widgets, added stats, and customized the data in the HAR file to make the HTTP Archive work better.

At  this three year mark I’m thankful that the HTTP Archive has grown to be a popular source for performance and other stats about how the Web works. It’s a successful project. There’s a lot more to do (most importantly moving to Github to promote more contributions so I’m less of a bottleneck) but we’ve accomplished a lot and the future is bright.

I’m thankful for our sponsors: GoogleMozillaNew RelicO’Reilly MediaEtsyRadwaredynaTrace Software, and Torbit (now Walmart). Their support allowed us to move to our own hardware and purchase mobile devices for the HTTP Archive Mobile.

And I’m especially thankful for Pat’s help in creating the HTTP Archive from day one. WebPagetest is awesome, as is its creator.

]]>
http://www.stevesouders.com/blog/2013/11/13/http-archive-3-year-anniversary-thank-you-pat-meenan/feed/ 2