webkit css-on-demand issues

February 11th, 2013. Tagged: CSS, performance

This post brought to you via Facebook engineers Jeff Morrison and Andrey Sukhachev, who discovered and helped isolate the issue.

Use case

Think a "single page app" use case. You click a button. Content comes via XHR. But content is complex (and app is as lazy-loading as possible) and content requires extra CSS. In an external file.

Only when the external CSS arrives should the app show the content. Otherwise content will be weirdly styled.


Two "modules" (or "widgets") of the app require two different CSS files. Both modules are requested at about the same time. We listen to onload of the CSS files. Expected behavior: whenever a module and its CSS dependency arrive - show that module. Asynchronously. No one cares which module shows first, as long as they show up as soon as possible.


Two modules. Two CSS files. 1st CSS happens to take one second. The second CSS takes 5 seconds.

Test pages: one and two

Here's the end result. The first module is pinky, the second is yellow. All good.

Same with network panel ON:

The question is what does the user see during the ?-mark - between the first CSS is done and the second one is still loading.

Oh, and here's the load() function that runs when the user clicks the button "load" initiating the new modules to appear:

function load() {
  var these = ['class1.css.php', 'class2.css.php'];
  var classes = ['class1', 'class2'];
  var head = document.getElementsByTagName('head')[0];
  for (var i = 0; i < these.length; i++) {
    var url = these[i];
    var link = document.createElement('link');
    link.type = "text/css";
    link.rel = "stylesheet"
    link.href = url;
    link.onload = (function (i) {
      return function () {
        var div = document.createElement('div')
        div.className = classes[i];
        //s = getComputedStyle(result).height;

Expected behavior

In FF, whenever the the first CSS is loaded, we see a new module.

Test for yourself (in Firefox)

#1 issue: "efficient" webkit

You know that browsers batch layout and paints tasks because these tend to be expensive. For example they wait for all CSS (even useless print and other @media stylesheets) to arrive and block the rendering of the page. (More on these topics: here, here)

So turns out that here webkit also waits for both CSS files to arrive before rendering anything.

You see in the console we know (in JavaScript) that CSS has arrived. But webkit (chrome, safari, mobile safari) doesn't paint anything, waiting for the second CSS. Bummer!

Issue #2: painting unstyled content

While issue #1 is just a bummer that can be done better for the progressive feedback-y user experience, #2 is a bug. This is the issue that Jeff and Andrey found and were floored.

If there's a paint going on between the two stylesheets, the browser dumps the unstyled content on the page. Ugly stuff.

This was only happening sometimes, but after forming and testing an hypothesis, I was able to distill a reproducible test case. The only change is: after the load of the first CSS, flush the rendering queue by requesting a style information. e.g.

link.onload = function () {
  s = getComputedStyle(dom).height;

Repro and file a bug

You can reproduce for yourself. Use chrome and try:

  1. Issue: no paint till all CSS is here
  2. Bug: unstyled paint while waiting for all CSS

I was faced with "registration wall" trying to file a webkit bug, hence this post. Someone please file a bug and feel free to use the provided test cases.

The solution, IMO, is to make webkit behave like FF. No waiting for all CSS. This solves both issues. In the worst case, at least the unstyled bug (issue #2) should be addressed.

Interim solution for web developers: inline CSS required by the module together with the module content.

Thanks for reading!

Tell your friends about this post: Facebook, Twitter, Google+

6 Responses

  1. Some browsers already have experimental support for Scoped Styles:
    For those browsers, a better user experience might be had by just injecting the content in with the scoped CSS (in the one injection).

    STYLE scoped
    DIV class=”…”

  2. IIRC webkit keeps a count of pending stylesheets and does not paint until all of them have been loaded. To work around this you would need to load each sheet as a non-stylesheet (e.g. via XHR) and inject it into the DOM as a stylesheet only once the download has completed. I think webkit’s counter of pending sheets is only for the sheets that have matching media, so you might be able to load these as media=foo and then flip the media type to screen once they finish downloading. I don’t know, though, if the browser will download for a media type that it doesn’t understand (I think that happens in some browsers; not sure about WK).

  3. About issue #2 – requesting computed style for dimensions forces the browser to render the element in order to return correct information. This is often used to break the CSS rendering batches you mention and is the proper behavior.

  4. Nice post. I learn some thing additional challenging on different blogs everyday. It is going to often be stimulating to read content from other writers and practice a bit some thing from their store. I’d prefer to use some with the content on my blog regardless of whether you don’t mind. Natually I’ll provide you with a link on your web blog. Thanks for sharing.

    [url=http://topschristianlouboutina.totalh.net]cheap christian louboutin shoes[/url]


  5. You can also get mobile phones at a cheaper rate

  6. I mean no insult to Superman,I love the character, but doesn that S (El) look like a big Superman themed doormat? I think they were going for a chainmail look but in my mind I thought doormat the first time I saw it up close. It was just what popped into my mind when I saw this very same image above.”Fragrance is a huge part of our business, and Euphoria in particular has enjoyed great success,” he said. “Now we want to grow it even more and this will do that with a new approach into the essence

Leave a Reply