Hixie's Natural Log

2011-02-03 05:37 UTC Script execution order post-mortem

LABjs, a JavaScript library for loading JavaScript libraries, relied on one of two behaviours to implement its asynchronous loading behaviour:

It would use the first behaviour if the browser implemented the MozAppearance property in the CSSOM (a Mozilla extension) or if the browser had an opera property, and it would use the second behaviour otherwise.

Unfortunately, neither of these behaviours were defined: historically, the HTML spec has been incredibly vague about what order dynamically-inserted scripts should execute in and when to download scripts. This is why the browsers didn't all do the same thing.

With the WHATWG effort, we try to pin all these behaviours down so that browsers can converge on one set of behaviours and Web developers don't have to worry about sniffing to use different behaviours in different browsers, like LABjs does.

When browsers don't have good interoperability, we usually pick the saner behaviour. In this case, that meant not downloading scripts that wouldn't execute anyway, and not applying a strict execution order on scripts that get inserted into the document dynamically. Both of these decisions are intended to improve performance.

Over time, browsers converged on these behaviours. First, Gecko removed the ordering on dynamic scripts. This broke LABjs. Arnout Kazemier was first to report the breakage in Firefox nightly builds. Kyle Simpson, who works on LABjs, posted about the issue on the Getify blog. A Mozilla evangelist noticed and pointed the situation out to Henri Sivonen, who subsequently started a public-html thread to discuss possible solutions; discussion later migrated to a WHATWG wiki page.

Thankfully, Henri was able to coordinate with Kyle and other Web developers, as well as with the other browser vendors, to come up with a solution (mostly based on Kyle's suggestions), which he documented in detail (See also: Mozilla bug, WebKit bug).

In the near future, WebKit builds will likely stop downloading scripts that it isn't going to execute (this has already changed for parser-inserted elements, but script-inserted elements haven't yet had this change applied). I've updated the HTML spec with the above proposal, so they'll likely implement that too.

The solution is basically to add a magic flag which can be reset from script. The flag is only set on script elements created from script, and causes the browser to assume that the async attribute is set. If you reset this flag (by setting script.async = false in script) then the attribute is honoured.

I must admit to not being a huge fan of the solution. Magic like that is highly unintuitive. However, in this particular instance Henri took the lead and got the key stakeholders of the moment on board, and that often has more weight than getting a perfect solution.