I love web performance conferences because I learn so much from interacting with other practitioners in my field. Velocity and WebPerfdays are the big conference + unconference pair in our field and so much happens there.
At WebPerfDays London last year, while adding support for SPDY to boomerang, I learnt about Chrome’s alternate performance timing object… one that was different from Navigation Timing.
That’s when I noticed that Chrome also reports when the the first paint happens.
First Paint
In Chrome, first paint is reported via the object returned from window.chrome.loadTimes()
. If you’re a performance geek like me, you’ve probably just opened up your WebDev console and inspected this object, and you’ve probably seen that it looks something like this:
{
commitLoadTime: 1384152185.366253
connectionInfo: "http/1"
finishDocumentLoadTime: 1384152185.810402
finishLoadTime: 1384152185.906618
firstPaintAfterLoadTime: 1384152185.974076
firstPaintTime: 1384152185.817258
navigationType: "Other"
npnNegotiatedProtocol: "unknown"
requestTime: 1384152185.094807
startLoadTime: 1384152185.220614
wasAlternateProtocolAvailable: false
wasFetchedViaSpdy: false
wasNpnNegotiated: false
}
firstPaintTime
is what I cared about at the time. Note that it’s reported in seconds since the epoch with microsecond resolution, so everything before the decimal point is seconds and everything after it is microseconds. This is different from other JavaScript timestamps which are in milliseconds, possibly with microsecond resolution if using the High Resolution Timer.
IE too
Chrome is not the only browser that reports first paint. IE also reports it as part of window.performance.msFirstPaint
, although in IE’s case it’s reported in milliseconds.
Other modern browsers
And this brings us to the topic of this post and why I love Velocity.
At Velocity New York earlier this year, I was chatting with Seth Walker from Etsy. Etsy was our first customer but more importantly keep contributing ideas that make boomerang better. This was one such idea.
Seth’s new engineer, Daniel Espeset worked on a proxy for detecting first paint. We compared the numbers to what Chrome’s firstPaintTime
reported, and they were within 2-3 milliseconds of each other, so the proxy was pretty good on Chrome at least.
Daniel’s trick was to attach a handler to requestAnimationFrame
, and measure when it first fired. The logic was that requestAnimationFrame
fires when the browser is ready to draw something to screen, and the first time that happens is when the browser can first draw stuff to screen, ie, on first paint.
The brilliance is in its simplicity.
I won’t post code here. I’ll let Daniel do that, or wait for his pull request to boomerang.
Older browsers
And this is where it gets even better. While chatting with Steve a little later in the conference, he mentioned another one of our customers who had a different proxy for first paint.
Flipkart.com is an online marketplace in India, much like what Amazon is in the US. They’d been using boomerang to measure load time long before lognormal was founded, and moved over to our platform soon after.
Flipkart’s idea was to measure when the last CSS file had finished loading. Since CSS blocks rendering, this was a pretty good lower limit of when rendering could start. It may not tell you that rendering has in fact started, but it’s pretty certain that nothing has rendered before this time.
The full list
So for the full list of first paint measures, I’d go through in this order:
- Attach a handler to
requestAnimationFrame
and remove it when it fires. - Attach a handler to the
onload
event of all my CSS files (probably do this inline) (Edit: Andy Davies suggests all blocking resources in the HEAD instead of just CSS) - Check for
window.chrome.loadTimes().firstPaintTime
orwindow.performance.msFirstPaint
- Use the earliest time from 1. and 3. or use the time from 2. if neither 1. or 3. are available.
See the Navigation Timing Plugin for what we have right now.
Do you have a better hack in place? Let us know in the comments.
Acknowledgements
I’d like to thank Daniel Espeset for his technique, Seth Walker for showing it to me and Steve Souders for letting me know about the CSS hack.
I’d also like to thank all of our customers. You’re all performance geeks just like we are, and a lot of the awesome sauce in our products come from ideas that you share with us. The above is just one example.
Update 2013-11-25
We’ve been doing a lot of testing on these techniques, and at the moment it looks like the requestAnimationFrame
hack only works correctly on Chrome. All other browsers fire the first RAF
within 40ms of the page starting up, even before HEAD
has completed loading, and then repeatedly every 13-23ms.
This is likely a bug in all browsers since painting cannot start until HEAD has loaded. From the MDN docs:
The window.requestAnimationFrame() method tells the browser that you wish to perform an animation and requests that the browser call a specified function to update an animation before the next repaint.
I have no idea why this happens. Unfortunately, since Chrome already reports first paint time, this technique won’t be very useful until browsers fix their bug.
Strangely, Opera, which uses Chrome’s engine, also has the bug. On the other hand, since Opera uses Chrome’s engine, it also exposes window.chrome.loadTimes()