My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

Tuesday, November 17, 2009

195 Chars To Help Lazy Loading

Update
removed named function expression

Update
I wrote events letters in the wrong place, please use the latest script

We have talked many times about performances, and not only here.
One common technique to speed up a generic online page is the lazy loading.
For lazy loading I mean:
  • runtime dependencies resolution via script injection, preferably including all dependencies we need in a shot rather than script after script
  • Google comments style, where code is evaluated when required but the size is still there
  • namespaced strings, an example I have explained via code in Ajaxian
  • whatever else, because sometimes we need this or that library only in certain conditions


What Is The Problem

If we would like to widely adopt this lazy load pattern, we should be aware about a "little detail": Firefox < 3.6 does not support document.readyState, an historically valid Internet Explorer feature adopted in HTML5 and present in basically every other browsers.
We can find the readyState description and the related bug in Mozilla domain.
Reading suggestions, I found quite pointless the long diatribe about:

Should it be "loading" or "interactive" before it will switch to "complete" ?

IMHO, who cares, as long as we can rely in the final statement: complete

Affected Libraries

I have no idea but for sure jQuery! The event.js file shows at line 857 this check for the bindReady event:

// Catch cases where $(document).ready() is called after the
// browser event has already occurred.
if ( document.readyState === "complete" ) {
return jQuery.ready();
}

The code goes on with common DOMContentLoaded checks and emulations.
The problem is that with such library where basically everything starts with an ready event:

$(function(){
// the ready event we are theoretically
// sure will be fired even if the
// code has been loaded after
});

every Firefox < 3.6 user will miss that code, plug-in, extension, whatever.
I have already informed jQuery dev ML about this problem but obviously they already know. John Resig told me that there is no guarantee the ready event will be fired if the page has been loaded.
Fair enough, I can perfectly understand John point which is: all jQuery supported browsers may not support document.readyState.
AFAIK, even if this is a good reason to avoid some obtrusive code, we all would expect consistency from a framework so if something worked even in IE I can't even think about Firefox problems.

The Solution


This missed FF feature could affect different libraries, not only jQuery.
We, as developers, could help every library author adding 195 uncompressed bytes, even less once deflated, as first stand alone piece of code ever in our page:

// WebReflection Solution
(function(h,a,c,k){if(h[a]==null&&h[c]){h[a]="loading";h[c](k,c=function(){h[a]="complete";h.removeEventListener(k,c,!1)},!1)}})(document,"readyState","addEventListener","DOMContentLoaded");

// NOTE: IE will never consider false s[o]==null

Since Firefox is usually updated automatically, all we need to do once we are sure people are surfing with version 3.6 or greater is simply remove above little line of code.

Explained Solution


// verify that document.readyState is undefined
// verify that document.addEventListener is there
// these two conditions are basically telling us
// we are using Firefox < 3.6
if(document.readyState == null && document.addEventListener){
// on DOMContentLoaded event, supported since ages
document.addEventListener("DOMContentLoaded", function DOMContentLoaded(){
// remove the listener itself
document.removeEventListener("DOMContentLoaded", DOMContentLoaded, false);
// assign readyState as complete
document.readyState = "complete";
}, false);
// set readyState = loading or interactive
// it does not really matter for this purpose
document.readyState = "loading";
}


Conclusion

Being this a browser problem and not directly libraries related, it does not probably make sense to put this fix just for few months until next FF release. At the same time we can guarantee for Firefox users, and only if the library does not sniff the browser via this read only property, that lazy loaded stuff a la $(rockNroll) will work into our favorite browser too or we could simply rely and with our code into a readyState "complete" check to decide what to do (this is the real reason I have investigated more this problem, but this is another story coming soon).

8 comments:

Azat Razetdinov said...

Shouldn’t the third s[l+e] be s[v+e]?

Andrea Giammarchi said...

cheers mate ... that happens when you are tired :D - updated

RStankov said...

nice, I'm just wondering if "document.readyState == null" could be !('readyState' in document)

Andrea Giammarchi said...

I wonder the same but since it should be undefined, at least this is the FF case, and not 0, false, or an empty string, I thought for this post purpose made sense to compare with undefined or null only. Feel free to change it to have 191 chars

getify said...

@Andrea-

The full version of your code works great to "patch" a page for proper document.readyState behavior for FF < 3.6.

But the minified version of your script is broken, and moreover I believe it has an unsafe (memory-leak-wise) named-function-expression.

I submit this version, which is slightly larger (209 characters) which works, and avoids the named-function-expression.

(function(a,b,c,d){if(a[b]==null&&a[c]){a[c](d,function(){a.removeEventListener(d,arguments.callee,false);a[b]="complete"},false);a[b]="loading"}})(document,"readyState","addEventListener","DOMContentLoaded");

Andrea Giammarchi said...

@Shade, it was broken but I have updated it and it works like a charm.
About the leak, that scope is parsed but never used/referenced via IE (so no leaks), I don't think even in the unpatched IE6 that little single scoped function will be a problem, compared with the total amount of scripts that does not care at all about these kind of leaks (never seen a library with hundreds of null assignment at the end for non IE functions, specially in a features detection script where you cannot even know which function should be removed)
Feel free in any case to use your "bigger" version if you think it's worth it.
I did not invent anything here, it is just a proof of concept with a manually minified suggestion ;-)

Andrea Giammarchi said...

Shade, I have removed the named function expression

Rick said...

I know FF5 is out, but a bunch of people still use this < 3.6 so thanks a bunch! This script worked perfectly!