Awhile back, Gmail team asked some Chromium devs why Google Chrome downloaded CSS so much more slowly than JS. This sounded strange to me, since I knew the code, and Chromium clearly prioritizes script and stylesheets at the same level (look for DetermineRequestPriority). I was initially skeptical of their claim, but then they said they had data that proved otherwise. Specifically, they told me:
I start downloading 400KB gzip'ed JS JS x--------; Short after that I start downloading a JSON file containing our CSS, 60KB gzip'ed. JS x-------->; CS x---->; I would expect to see this, in 99% of the cases: JS x---------------------------------------| done CS x--------| done But what we see often is this pattern: JS x---------------------------------------| done CS x-----------------------------------------------------------------------------| done
As can be seen, the CSS is smaller, and it starts after the JS load started, but it generally finishes afterward. Indeed, in practice, this matches what we see in the wild. The question is, why? Well, the answer of course is that this is expected behavior with SPDY.
But we should step back here and ask, is SPDY doing the right thing? Should a resource (or rather, a SPDY stream) at a higher priority starve a resource at a lower priority? It’s very reasonable how it made Gmail team wonder why CSS looked so slow to download. But if you think about it, SPDY prioritization isn’t changing the actual throughput. It’s simply affecting the order in which the resources’ bytes are being sent by the server, thus the time to download all resources should be the same in absence of prioritization. However, outside of exceptional, adversarial cases, it should generally result in individual resources completing sooner. Now, the question is whether or not it’s better to have individual resources complete sooner, or to get more interleaving amongst resources. It’s useful to note that for many resources such as script, the web rendering engine is not able to progressively process them. The entire script is delivered as a whole to the script engine (V8 in Chromium’s case). On the other hand, resources like HTML are able to be incrementally processed. That said, would it be better to completely deliver one HTML resource and then another HTML resource or interleave them? That’s completely unclear to the browser. For images, it’s likewise unclear whether or not it’s better to deliver them serially or interleaved, although it’s generally more likely that images earlier on in the document will be in the viewport. But some images are progressive, and interleaving them will let the user see lower quality versions of more images sooner, which is arguably a better user experience. And since documents don’t always have the layout markup for images, it may be better to interleave the images in order to get the dimensions for images sooner so the rendering engine can layout the page correctly sooner. Current SPDY prioritization allows for conveying fixed priority levels, but it only has very rough semantics for describing where the browser wants strict ordering of resources versus equivalency (such that a server could interleave if it’s better). Indeed, SPDY3 has 3 bits for priorities, which isn’t necessarily enough since there are generally far more than 8 resources per page. This, amongst other reasons, is why we’re revamping SPDY prioritization in SPDY4, and I will present many of these use cases to the httpbis working group next week in Tokyo so we can design for them for HTTP/2.
As far as why Gmail downloads CSS as JSON using an XHR, it’s actually for a number of reasons, not all of which I’m going to dive into here. But one major reason is to avoid blocking first paint, since rendering engines will block first paint until relevant stylesheets come in, in order to prevent FOUC. Gmail doesn’t want to block first paint on downloading the CSS for the main part of the web app, since it wants to render the progress bar in the meanwhile. There are other reasons CSS as JSON is useful for them, but if we ignore those, then what Gmail needs is a way in the web platform to declaratively (so the speculative parser can discover the resource sooner) asynchronously load stylesheets in a manner that doesn’t block first paint, fires a load event, and is properly recognized by the web platform as a stylesheet download (so it can be appropriately prioritized) rather than an opaque blob. There are many loading techniques, like creating a link element and appending from script, that meet most of these goals, but not all of them.
Gmail’s a fascinating case to study, since they’ve done so many web performance optimizations that had to work across a large number of browsers, many of them very old. These web performance techniques were the best options for older browsers, but they interact in interesting, potentially suboptimal ways with modern browsers.