(… continued …)

NOTE: For the use-case of loading jQuery (and other script assets) via LABjs (or other script-loaders), the current work-around for the problem described below is to simply manually load jQuery with a plain-old <script> tag, and then use LABjs to load everything else.

But jQuery understands DOM-ready, so, what’s the problem?

In all 4 of these scenarios (and other less common variations), the current (as of this post) version of jQuery has a problem that you probably aren’t aware of. Because of peculiarities of how jQuery has had to “hack” to reliably detect the DOM-ready event in all supported browsers, it has an Achilles’ heel: if jQuery is added to a page after DOM-ready has passed, in the Firefox brand of browsers prior to version 3.6 (current version at time of post is 3.5.5), then jQuery will never actually detect the DOM-ready for that page, nor will it recognize that DOM-ready has passed.

Now, you may be saying to yourself, “I’ve loaded jQuery dynamically before and never had a problem.” It’s true, for most of the functionality that jQuery provides, this is not an issue. But specifically for code that you queue up to wait for DOM-ready, that code will never execute like you wanted it to; it’ll just sit there waiting forever for that never-to-occur DOM-ready event.

If jQuery is loaded before DOM-ready, it will properly detect DOM-ready, and any code that is wrapped in a $(document).ready(…) wait wrapper will be safe and will execute only at the proper time. But, if jQuery is loaded after DOM-ready, all such wait-wrapped code will never execute.

So, we have a conundrum: we can either write code that uses jQuery that is “safe” for when jQuery is loaded before DOM-ready, or we can write code that is “unsafe” (that is, not wrapped to wait) and just trust that somehow, something else will be in control to make sure our code will never be run in an unsafe manner before DOM-ready. But we cannot write the same bit of code to do both; we have to choose when we write our code exactly which set of use-cases our code is valid for.

This is generally not a big deal, as it’s true that a lot of code will only ever be used in one scenario or another. But plugins that are designed to be generic and reusable can suffer from this problem — the plugin author will often explicitly choose to either wrap the code in a safety net, or not, and you as a user of that plugin are bound to only use the plugin in a manner that befits the plugin’s definition.

So, if you want a plugin that can run in both types of situations, you are just out of luck.

That sucks!

Yeah, I know, that’s why I asserted “DOM-ready still sucks”. Now, I’ve been picking on jQuery thus far in this post, but really, most DOM-ready detection code has this problem. I’ve actually been talking about jQuery because, as you’ll see in a moment, jQuery is moving toward a solution!

To get particular about why jQuery currently (1.3.2 and before) has this paradox of DOM-ready cart-before-the-horse detection, only in Firefox prior to 3.6, the reason has to do with the fact that Firefox has not (yet) exposed a particular property used in DOM-ready detection, called “document.readyState”. This property is supposed to be a string value that indicates “loading” (while loading, obviously) and then eventually “complete”, when, not surprisingly, the document (DOM) is ready to go.

So, if this property were present, then no matter when jQuery was added to the page, it would be able to inspect this property, and if it was already “complete”, then it could just immediately set it’s internal flag to complete and trigger any code that was scheduled to wait for DOM-ready.

But, because the property is not there for the affected versions of Firefox, jQuery falls back to listening for an event for DOM-ready, which of course has already happened in the above use-cases, and so jQuery sits there and waits forever.

So, what is jQuery’s response?

To be clear, jQuery 1.3.2 and before do not even inspect for this property at load time, because it was well known for a long time that Firefox didn’t expose it. So, up to the current release, the jQuery folks have simply said that the use-cases I’ve described are not supported by the framework. This limitation however is not a well-known fact as far as I can tell. I used jQuery for well over 2 years before I had any inkling that such a caveat was in place, especially knowingly so.

But, thankfully, John Resig, creator and core maintainer of jQuery, who now works for Mozilla (Firefox), responded to a bug I reported on FF DOM-ready issues by landing a patch to jQuery to start inspecting for that property, in anticipation of Firefox quickly picking up the ball and exposing that property.

At the time of this bug resolution, it was thought imminent that a soon-to-be-released patch version of 3.5.x would indeed provide “document.readyState”, and so jQuery was duly set up to be ready for that when the next release of the framework came out. Since Firefox 3.x and below were already rapidly decreasing in popularity in favor of 3.5, it was felt that although the fix would not solve anything for those older FF browser users, most would be upgraded to some version of 3.5.x by the time the next jQuery rolled out, and the issue would become much less prevalent.

Sounds like the problem is about to be solved!

Unfortunately, as I’ve now recently learned, “document.readyState” has been deferred by Mozilla until at least FF 3.6 (and who knows, possibly later?). And although the next release of jQuery itself has also been delayed longer than anticipated, the 1.4 release is now expected to roll out in public alpha form “soon”, as early as next month (December 2009). But FF 3.6 (and “document.readyState” along with it) are probably not set for release until well into next year.

This means that jQuery will have future-ready code in place looking for document.readyState long before most users of FF have a browser version which supports it. And during all this time, the above use-cases for dynamic script-loading will remain dangerous and unsupported for jQuery users.

So, yeah, DOM-ready still sucks. I get it.

Luckily, this is not the end of the story. A recent bug thread over in the jQuery-dev group list was hijacked mid-way through, and became a re-hashing of these issues.

Andrea Giammarchi chimed into the discussion and offered an alternative solution. Since jQuery itself can’t (at least yet) be adequately patched so that it can detect a passed DOM-ready event in FF, what if we could instead “patch” the page (as viewed in affected versions of FF) to simulate this property and thus provide the crutch that jQuery needs?

Turns out, this is not really all that hard to do. JavaScript is cool like that, in that we can add properties to even the most native of objects like “document”. So, if we can get a special snippet of code to “patch” a page being viewed in affected FF versions, and make sure the snippet runs before DOM-ready, it will properly set the “document.readyState” variable at the appropriate time, which means that whenever jQuery (or any other framework/script) comes along later, it’ll see the property and correct value, and all will be happy in the world!

True, this merely shifts the burden of being loaded before DOM-ready away from the jQuery framework to this small little 194 character snippet of minifed JavaScript code. But since the “patch” code is so small, it can much more reasonably and innocuously be included in an inline <script> tag at the bottom of any/all affected pages, and all frameworks who play well with their DOM-ready detection code (including the coming jQuery 1.4!) will benefit.

Then, when FF 3.5.x and before has been given enough time to pass that they’re statistically not widespread or important enough any more, pages can quietly remove this “page hack” from their HTML and noone will be the wiser.

What if I still don’t want to use this “page hack”?

The good news is, almost all the time, if your page is using a script-loader for lazy-loading/on-demand-loading of scripts, your script-loader is usually present at the time of page-load. This means that script-loaders, like LABjs, can include this hack for you, to at least address some of the use-cases without you having to modify your pages at all. And so that is what LABjs has done. So now, when jQuery 1.4 comes out, you will be able to load it dynamically using LABjs, in all browsers, and have no worries over orphaned DOM-ready wrapped code!

Of course, the search can and should continue to find a truly robust DOM-ready detection hack which can be included in the core of jQuery (and other frameworks). But until that time, at least we have made some progress toward making DOM-ready suck a little less (and to quote Steve Souders, “…for some meaning of ’suck’ and some meaning of ‘less’…”).

Bookmark and Share

Pages: 1 2

This entry was written by getify , posted on Saturday November 21 2009at 10:11 am , filed under JavaScript and tagged , , , , . Bookmark the permalink . Post a comment below or leave a trackback: Trackback URL.

4 Responses to “Why DOM-ready still sucks”

  • codylindley says:

    So, if I never use the custom DOM ready event and place my all my scripts before the closing body element then I shouldn’t have to worry about the issues with labjs and jquery. Correct?

  • getify says:

    @codylindley — that is true, as long as none of the scripts you are loading care about $(document).ready(…), jQuery is perfectly safe to use with LABjs!

    It’s just $(document).ready(…) usage is so common with jQuery pages that it’s easier to assume it is being used and be cautionary, than to assume the opposite and have lots of weird race-condition gotchas. :)

  • Buzz says:

    you may need to fetch jQuery at some point well after the initial page loading occured, right? I still getting errors, dunno why.

  • getify says:

    Make sure if you’re trying to load jQuery dynamically, that it’s at least 1.4+. Previous versions of jQuery didn’t support that use case.

Leave a Reply

Consider Registering or Logging in before commenting.

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Switch to our mobile site