Script downloading in Chrome

Yesterday I was testing the performance penalty of loading jQuery in the head element when I noticed something unexpected happening in Chrome. It appeared that having an external JavaScript reference in the head caused all the external JavaScript on the page to be loaded before the elements in the body.

Compare these two test pages using Steve Souders Cuzillion tool. According to Chrome’s Developer Tools network inspector, it appears there’s no difference between having 1 script in the head or having all the scripts in the head. Tested in 13.0.782.107 stable and 15.0.842.0 canary, both on a Mac.

1 external script in head, 3 before closing body tag


(click to view full-size)

All 4 external scripts in head


(click to view full-size)

No difference at all.

But then if we look at how Chrome downloads scripts when they’re all in the bottom of page we see this:

All scripts in bottom


(click to view full-size)

Chrome still requests the scripts first but the image resources in the body are not blocked.

With one more example that uses a script in the body with a longer request duration we can see what really happens when you have only a single script request in the head:


(click to view full-size)

Resources are blocked only while the one script in the head downloads, just as we’d expect. In the Cuzillion environment, with its precise request lengths, Chrome’s pre-downloading of scripts only made it appear as if something nefarious was going on.

So that all makes sense. No anomalous behavior. What I didn’t realize previously is that Chrome attempts to download all external scripts up front. I knew Chrome/WebKit can download scripts in parallel with other resources but I thought the requests were kicked off in the order in which they appeared in the document.

Is this a goal of WebKit’s preload scanner? Download all JS as soon as possible and then execute in document order (assuming no defer/async)?

Update (11/12/2011): Nothing’s changed but rereading this post it’s clear to me that I did not word this very well. Here’s a quick summary of my conclusions about what happens in WebKit:

  • An external JS file in the head blocks the parser. This kicks off the PreloadScanner, which attempts to download all other scripts and stylesheets — but not images.
  • Preloaded scripts do not block images and other assets. They’re only blocked on the script in the head. See example 4 above.
  • Read Tony Gentilcore’s comment below and this WebKit bug thread for details about why this happens.
Both comments and pings are currently closed.

Discussion

hi Rob,

nice post, good insight.
If I read your article correctly, Chrome will preload the external JS file referenced in the BODY, even though there are other assets (images) referenced first.
This is most likely intentional. I will ask @paul_irish.

In my opinion, this behaviour is not optimal. I’d rather see Chrome preload the assets in order.

Thanks Aaron. Correct, that’s what I’m seeing. I agree it’s likely intentional. Btw, it appears the same thing happens in Safari but that the preloading of JS is limited to 2 requests. Really curious to hear if Paul or any of the Chrome/WebKit devs have any insight.

The difference you are noticing is because the PreloadScanner gets a chance to run while blocked on that script in the head.

The main parser downloads each resource as it encounters them (exactly how you’d expect). The PreloadScanner has some subtleties. For instance, it will only download scripts and stylesheets while the parser is blocked in the head. This is designed so that images (which will never block the main parser) will not contend for bandwidth with the critical resources that can block the parser. This allows the page to display more progressively with images filling in around the content.

It is possible to contrive test cases where this leads to inefficiencies. There’s a good example here: https://bugs.webkit.org/show_bug.cgi?id=45072

But I’ve experimented with lots of other behaviors and starting images while blocked in the head always led to significant regressions on certain real world websites.

I still suspect there are some improvements that could be made in this area. Ideas welcome :)

Tony,
Thanks a lot for your comment. Very interesting bug thread. Are there any cases in which the PreloadScanner does not run? Even this example with no scripts or stylesheets in the head shows scripts in the body being loaded before some of the images.