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.
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
firstPaintTime is what I cared about at the time. Note that it’s reported in seconds since the epoch with microsecond resolution, so
timestamps which are in milliseconds, possibly with microsecond resolution if using the High Resolution Timer.
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.
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
requestAnimationFrameand remove it when it fires.
- Attach a handler to the
onloadevent 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
- 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.
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.
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