src/js/libs/lab.js
branchlab-port
changeset 495 41a9f7b27952
equal deleted inserted replaced
494:cb88c0c8ddfa 495:41a9f7b27952
       
     1 /*! LAB.js (LABjs :: Loading And Blocking JavaScript)
       
     2     v2.0.3 (c) Kyle Simpson
       
     3     MIT License
       
     4 */
       
     5 
       
     6 (function(global){
       
     7 	var _$LAB = global.$LAB,
       
     8 	
       
     9 		// constants for the valid keys of the options object
       
    10 		_UseLocalXHR = "UseLocalXHR",
       
    11 		_AlwaysPreserveOrder = "AlwaysPreserveOrder",
       
    12 		_AllowDuplicates = "AllowDuplicates",
       
    13 		_CacheBust = "CacheBust",
       
    14 		/*!START_DEBUG*/_Debug = "Debug",/*!END_DEBUG*/
       
    15 		_BasePath = "BasePath",
       
    16 		
       
    17 		// stateless variables used across all $LAB instances
       
    18 		root_page = /^[^?#]*\//.exec(location.href)[0],
       
    19 		root_domain = /^\w+\:\/\/\/?[^\/]+/.exec(root_page)[0],
       
    20 		append_to = document.head || document.getElementsByTagName("head"),
       
    21 		
       
    22 		// inferences... ick, but still necessary
       
    23 		opera_or_gecko = (global.opera && Object.prototype.toString.call(global.opera) == "[object Opera]") || ("MozAppearance" in document.documentElement.style),
       
    24 
       
    25 /*!START_DEBUG*/
       
    26 		// console.log() and console.error() wrappers
       
    27 		log_msg = function(){}, 
       
    28 		log_error = log_msg,
       
    29 /*!END_DEBUG*/
       
    30 		
       
    31 		// feature sniffs (yay!)
       
    32 		test_script_elem = document.createElement("script"),
       
    33 		explicit_preloading = typeof test_script_elem.preload == "boolean", // http://wiki.whatwg.org/wiki/Script_Execution_Control#Proposal_1_.28Nicholas_Zakas.29
       
    34 		real_preloading = explicit_preloading || (test_script_elem.readyState && test_script_elem.readyState == "uninitialized"), // will a script preload with `src` set before DOM append?
       
    35 		script_ordered_async = !real_preloading && test_script_elem.async === true, // http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
       
    36 		
       
    37 		// XHR preloading (same-domain) and cache-preloading (remote-domain) are the fallbacks (for some browsers)
       
    38 		xhr_or_cache_preloading = !real_preloading && !script_ordered_async && !opera_or_gecko
       
    39 	;
       
    40 
       
    41 /*!START_DEBUG*/
       
    42 	// define console wrapper functions if applicable
       
    43 	if (global.console && global.console.log) {
       
    44 		if (!global.console.error) global.console.error = global.console.log;
       
    45 		log_msg = function(msg) { global.console.log(msg); };
       
    46 		log_error = function(msg,err) { global.console.error(msg,err); };
       
    47 	}
       
    48 /*!END_DEBUG*/
       
    49 
       
    50 	// test for function
       
    51 	function is_func(func) { return Object.prototype.toString.call(func) == "[object Function]"; }
       
    52 
       
    53 	// test for array
       
    54 	function is_array(arr) { return Object.prototype.toString.call(arr) == "[object Array]"; }
       
    55 
       
    56 	// make script URL absolute/canonical
       
    57 	function canonical_uri(src,base_path) {
       
    58 		var absolute_regex = /^\w+\:\/\//;
       
    59 		
       
    60 		// is `src` is protocol-relative (begins with // or ///), prepend protocol
       
    61 		if (/^\/\/\/?/.test(src)) {
       
    62 			src = location.protocol + src;
       
    63 		}
       
    64 		// is `src` page-relative? (not an absolute URL, and not a domain-relative path, beginning with /)
       
    65 		else if (!absolute_regex.test(src) && src.charAt(0) != "/") {
       
    66 			// prepend `base_path`, if any
       
    67 			src = (base_path || "") + src;
       
    68 		}
       
    69 		// make sure to return `src` as absolute
       
    70 		return absolute_regex.test(src) ? src : ((src.charAt(0) == "/" ? root_domain : root_page) + src);
       
    71 	}
       
    72 
       
    73 	// merge `source` into `target`
       
    74 	function merge_objs(source,target) {
       
    75 		for (var k in source) { if (source.hasOwnProperty(k)) {
       
    76 			target[k] = source[k]; // TODO: does this need to be recursive for our purposes?
       
    77 		}}
       
    78 		return target;
       
    79 	}
       
    80 
       
    81 	// does the chain group have any ready-to-execute scripts?
       
    82 	function check_chain_group_scripts_ready(chain_group) {
       
    83 		var any_scripts_ready = false;
       
    84 		for (var i=0; i<chain_group.scripts.length; i++) {
       
    85 			if (chain_group.scripts[i].ready && chain_group.scripts[i].exec_trigger) {
       
    86 				any_scripts_ready = true;
       
    87 				chain_group.scripts[i].exec_trigger();
       
    88 				chain_group.scripts[i].exec_trigger = null;
       
    89 			}
       
    90 		}
       
    91 		return any_scripts_ready;
       
    92 	}
       
    93 
       
    94 	// creates a script load listener
       
    95 	function create_script_load_listener(elem,registry_item,flag,onload) {
       
    96 		elem.onload = elem.onreadystatechange = function() {
       
    97 			if ((elem.readyState && elem.readyState != "complete" && elem.readyState != "loaded") || registry_item[flag]) return;
       
    98 			elem.onload = elem.onreadystatechange = null;
       
    99 			onload();
       
   100 		};
       
   101 	}
       
   102 
       
   103 	// script executed handler
       
   104 	function script_executed(registry_item) {
       
   105 		registry_item.ready = registry_item.finished = true;
       
   106 		for (var i=0; i<registry_item.finished_listeners.length; i++) {
       
   107 			registry_item.finished_listeners[i]();
       
   108 		}
       
   109 		registry_item.ready_listeners = [];
       
   110 		registry_item.finished_listeners = [];
       
   111 	}
       
   112 
       
   113 	// make the request for a scriptha
       
   114 	function request_script(chain_opts,script_obj,registry_item,onload,preload_this_script) {
       
   115 		// setTimeout() "yielding" prevents some weird race/crash conditions in older browsers
       
   116 		setTimeout(function(){
       
   117 			var script, src = script_obj.real_src, xhr;
       
   118 			
       
   119 			// don't proceed until `append_to` is ready to append to
       
   120 			if ("item" in append_to) { // check if `append_to` ref is still a live node list
       
   121 				if (!append_to[0]) { // `append_to` node not yet ready
       
   122 					// try again in a little bit -- note: will re-call the anonymous function in the outer setTimeout, not the parent `request_script()`
       
   123 					setTimeout(arguments.callee,25);
       
   124 					return;
       
   125 				}
       
   126 				// reassign from live node list ref to pure node ref -- avoids nasty IE bug where changes to DOM invalidate live node lists
       
   127 				append_to = append_to[0];
       
   128 			}
       
   129 			script = document.createElement("script");
       
   130 			if (script_obj.type) script.type = script_obj.type;
       
   131 			if (script_obj.charset) script.charset = script_obj.charset;
       
   132 			
       
   133 			// should preloading be used for this script?
       
   134 			if (preload_this_script) {
       
   135 				// real script preloading?
       
   136 				if (real_preloading) {
       
   137 					/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script preload: "+src);/*!END_DEBUG*/
       
   138 					registry_item.elem = script;
       
   139 					if (explicit_preloading) { // explicit preloading (aka, Zakas' proposal)
       
   140 						script.preload = true;
       
   141 						script.onpreload = onload;
       
   142 					}
       
   143 					else {
       
   144 						script.onreadystatechange = function(){
       
   145 							if (script.readyState == "loaded") onload();
       
   146 						};
       
   147 					}
       
   148 					script.src = src;
       
   149 					// NOTE: no append to DOM yet, appending will happen when ready to execute
       
   150 				}
       
   151 				// same-domain and XHR allowed? use XHR preloading
       
   152 				else if (preload_this_script && src.indexOf(root_domain) == 0 && chain_opts[_UseLocalXHR]) {
       
   153 					xhr = new XMLHttpRequest(); // note: IE never uses XHR (it supports true preloading), so no more need for ActiveXObject fallback for IE <= 7
       
   154 					/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script preload (xhr): "+src);/*!END_DEBUG*/
       
   155 					xhr.onreadystatechange = function() {
       
   156 						if (xhr.readyState == 4) {
       
   157 							xhr.onreadystatechange = function(){}; // fix a memory leak in IE
       
   158 							registry_item.text = xhr.responseText + "\n//@ sourceURL=" + src; // http://blog.getfirebug.com/2009/08/11/give-your-eval-a-name-with-sourceurl/
       
   159 							onload();
       
   160 						}
       
   161 					};
       
   162 					xhr.open("GET",src);
       
   163 					xhr.send();
       
   164 				}
       
   165 				// as a last resort, use cache-preloading
       
   166 				else {
       
   167 					/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script preload (cache): "+src);/*!END_DEBUG*/
       
   168 					script.type = "text/cache-script";
       
   169 					create_script_load_listener(script,registry_item,"ready",function() {
       
   170 						append_to.removeChild(script);
       
   171 						onload();
       
   172 					});
       
   173 					script.src = src;
       
   174 					append_to.insertBefore(script,append_to.firstChild);
       
   175 				}
       
   176 			}
       
   177 			// use async=false for ordered async? parallel-load-serial-execute http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
       
   178 			else if (script_ordered_async) {
       
   179 				/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script load (ordered async): "+src);/*!END_DEBUG*/
       
   180 				script.async = false;
       
   181 				create_script_load_listener(script,registry_item,"finished",onload);
       
   182 				script.src = src;
       
   183 				append_to.insertBefore(script,append_to.firstChild);
       
   184 			}
       
   185 			// otherwise, just a normal script element
       
   186 			else {
       
   187 				/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script load: "+src);/*!END_DEBUG*/
       
   188 				create_script_load_listener(script,registry_item,"finished",onload);
       
   189 				script.src = src;
       
   190 				append_to.insertBefore(script,append_to.firstChild);
       
   191 			}
       
   192 		},0);
       
   193 	}
       
   194 		
       
   195 	// create a clean instance of $LAB
       
   196 	function create_sandbox() {
       
   197 		var global_defaults = {},
       
   198 			can_use_preloading = real_preloading || xhr_or_cache_preloading,
       
   199 			queue = [],
       
   200 			registry = {},
       
   201 			instanceAPI
       
   202 		;
       
   203 		
       
   204 		// global defaults
       
   205 		global_defaults[_UseLocalXHR] = true;
       
   206 		global_defaults[_AlwaysPreserveOrder] = false;
       
   207 		global_defaults[_AllowDuplicates] = false;
       
   208 		global_defaults[_CacheBust] = false;
       
   209 		/*!START_DEBUG*/global_defaults[_Debug] = false;/*!END_DEBUG*/
       
   210 		global_defaults[_BasePath] = "";
       
   211 
       
   212 		// execute a script that has been preloaded already
       
   213 		function execute_preloaded_script(chain_opts,script_obj,registry_item) {
       
   214 			var script;
       
   215 			
       
   216 			function preload_execute_finished() {
       
   217 				if (script != null) { // make sure this only ever fires once
       
   218 					script = null;
       
   219 					script_executed(registry_item);
       
   220 				}
       
   221 			}
       
   222 			
       
   223 			if (registry[script_obj.src].finished) return;
       
   224 			if (!chain_opts[_AllowDuplicates]) registry[script_obj.src].finished = true;
       
   225 			
       
   226 			script = registry_item.elem || document.createElement("script");
       
   227 			if (script_obj.type) script.type = script_obj.type;
       
   228 			if (script_obj.charset) script.charset = script_obj.charset;
       
   229 			create_script_load_listener(script,registry_item,"finished",preload_execute_finished);
       
   230 			
       
   231 			// script elem was real-preloaded
       
   232 			if (registry_item.elem) {
       
   233 				registry_item.elem = null;
       
   234 			}
       
   235 			// script was XHR preloaded
       
   236 			else if (registry_item.text) {
       
   237 				script.onload = script.onreadystatechange = null;	// script injection doesn't fire these events
       
   238 				script.text = registry_item.text;
       
   239 			}
       
   240 			// script was cache-preloaded
       
   241 			else {
       
   242 				script.src = script_obj.real_src;
       
   243 			}
       
   244 			append_to.insertBefore(script,append_to.firstChild);
       
   245 
       
   246 			// manually fire execution callback for injected scripts, since events don't fire
       
   247 			if (registry_item.text) {
       
   248 				preload_execute_finished();
       
   249 			}
       
   250 		}
       
   251 	
       
   252 		// process the script request setup
       
   253 		function do_script(chain_opts,script_obj,chain_group,preload_this_script) {
       
   254 			var registry_item,
       
   255 				registry_items,
       
   256 				ready_cb = function(){ script_obj.ready_cb(script_obj,function(){ execute_preloaded_script(chain_opts,script_obj,registry_item); }); },
       
   257 				finished_cb = function(){ script_obj.finished_cb(script_obj,chain_group); }
       
   258 			;
       
   259 			
       
   260 			script_obj.src = canonical_uri(script_obj.src,chain_opts[_BasePath]);
       
   261 			script_obj.real_src = script_obj.src + 
       
   262 				// append cache-bust param to URL?
       
   263 				(chain_opts[_CacheBust] ? ((/\?.*$/.test(script_obj.src) ? "&_" : "?_") + ~~(Math.random()*1E9) + "=") : "")
       
   264 			;
       
   265 			
       
   266 			if (!registry[script_obj.src]) registry[script_obj.src] = {items:[],finished:false};
       
   267 			registry_items = registry[script_obj.src].items;
       
   268 
       
   269 			// allowing duplicates, or is this the first recorded load of this script?
       
   270 			if (chain_opts[_AllowDuplicates] || registry_items.length == 0) {
       
   271 				registry_item = registry_items[registry_items.length] = {
       
   272 					ready:false,
       
   273 					finished:false,
       
   274 					ready_listeners:[ready_cb],
       
   275 					finished_listeners:[finished_cb]
       
   276 				};
       
   277 
       
   278 				request_script(chain_opts,script_obj,registry_item,
       
   279 					// which callback type to pass?
       
   280 					(
       
   281 					 	(preload_this_script) ? // depends on script-preloading
       
   282 						function(){
       
   283 							registry_item.ready = true;
       
   284 							for (var i=0; i<registry_item.ready_listeners.length; i++) {
       
   285 								registry_item.ready_listeners[i]();
       
   286 							}
       
   287 							registry_item.ready_listeners = [];
       
   288 						} :
       
   289 						function(){ script_executed(registry_item); }
       
   290 					),
       
   291 					// signal if script-preloading should be used or not
       
   292 					preload_this_script
       
   293 				);
       
   294 			}
       
   295 			else {
       
   296 				registry_item = registry_items[0];
       
   297 				if (registry_item.finished) {
       
   298 					finished_cb();
       
   299 				}
       
   300 				else {
       
   301 					registry_item.finished_listeners.push(finished_cb);
       
   302 				}
       
   303 			}
       
   304 		}
       
   305 
       
   306 		// creates a closure for each separate chain spawned from this $LAB instance, to keep state cleanly separated between chains
       
   307 		function create_chain() {
       
   308 			var chainedAPI,
       
   309 				chain_opts = merge_objs(global_defaults,{}),
       
   310 				chain = [],
       
   311 				exec_cursor = 0,
       
   312 				scripts_currently_loading = false,
       
   313 				group
       
   314 			;
       
   315 			
       
   316 			// called when a script has finished preloading
       
   317 			function chain_script_ready(script_obj,exec_trigger) {
       
   318 				/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("script preload finished: "+script_obj.real_src);/*!END_DEBUG*/
       
   319 				script_obj.ready = true;
       
   320 				script_obj.exec_trigger = exec_trigger;
       
   321 				advance_exec_cursor(); // will only check for 'ready' scripts to be executed
       
   322 			}
       
   323 
       
   324 			// called when a script has finished executing
       
   325 			function chain_script_executed(script_obj,chain_group) {
       
   326 				/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("script execution finished: "+script_obj.real_src);/*!END_DEBUG*/
       
   327 				script_obj.ready = script_obj.finished = true;
       
   328 				script_obj.exec_trigger = null;
       
   329 				// check if chain group is all finished
       
   330 				for (var i=0; i<chain_group.scripts.length; i++) {
       
   331 					if (!chain_group.scripts[i].finished) return;
       
   332 				}
       
   333 				// chain_group is all finished if we get this far
       
   334 				chain_group.finished = true;
       
   335 				advance_exec_cursor();
       
   336 			}
       
   337 
       
   338 			// main driver for executing each part of the chain
       
   339 			function advance_exec_cursor() {
       
   340 				while (exec_cursor < chain.length) {
       
   341 					if (is_func(chain[exec_cursor])) {
       
   342 						/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("$LAB.wait() executing: "+chain[exec_cursor]);/*!END_DEBUG*/
       
   343 						try { chain[exec_cursor++](); } catch (err) {
       
   344 							/*!START_DEBUG*/if (chain_opts[_Debug]) log_error("$LAB.wait() error caught: ",err);/*!END_DEBUG*/
       
   345 						}
       
   346 						continue;
       
   347 					}
       
   348 					else if (!chain[exec_cursor].finished) {
       
   349 						if (check_chain_group_scripts_ready(chain[exec_cursor])) continue;
       
   350 						break;
       
   351 					}
       
   352 					exec_cursor++;
       
   353 				}
       
   354 				// we've reached the end of the chain (so far)
       
   355 				if (exec_cursor == chain.length) {
       
   356 					scripts_currently_loading = false;
       
   357 					group = false;
       
   358 				}
       
   359 			}
       
   360 			
       
   361 			// setup next chain script group
       
   362 			function init_script_chain_group() {
       
   363 				if (!group || !group.scripts) {
       
   364 					chain.push(group = {scripts:[],finished:true});
       
   365 				}
       
   366 			}
       
   367 
       
   368 			// API for $LAB chains
       
   369 			chainedAPI = {
       
   370 				// start loading one or more scripts
       
   371 				script:function(){
       
   372 					for (var i=0; i<arguments.length; i++) {
       
   373 						(function(script_obj,script_list){
       
   374 							var splice_args;
       
   375 							
       
   376 							if (!is_array(script_obj)) {
       
   377 								script_list = [script_obj];
       
   378 							}
       
   379 							for (var j=0; j<script_list.length; j++) {
       
   380 								init_script_chain_group();
       
   381 								script_obj = script_list[j];
       
   382 								
       
   383 								if (is_func(script_obj)) script_obj = script_obj();
       
   384 								if (!script_obj) continue;
       
   385 								if (is_array(script_obj)) {
       
   386 									// set up an array of arguments to pass to splice()
       
   387 									splice_args = [].slice.call(script_obj); // first include the actual array elements we want to splice in
       
   388 									splice_args.unshift(j,1); // next, put the `index` and `howMany` parameters onto the beginning of the splice-arguments array
       
   389 									[].splice.apply(script_list,splice_args); // use the splice-arguments array as arguments for splice()
       
   390 									j--; // adjust `j` to account for the loop's subsequent `j++`, so that the next loop iteration uses the same `j` index value
       
   391 									continue;
       
   392 								}
       
   393 								if (typeof script_obj == "string") script_obj = {src:script_obj};
       
   394 								script_obj = merge_objs(script_obj,{
       
   395 									ready:false,
       
   396 									ready_cb:chain_script_ready,
       
   397 									finished:false,
       
   398 									finished_cb:chain_script_executed
       
   399 								});
       
   400 								group.finished = false;
       
   401 								group.scripts.push(script_obj);
       
   402 								
       
   403 								do_script(chain_opts,script_obj,group,(can_use_preloading && scripts_currently_loading));
       
   404 								scripts_currently_loading = true;
       
   405 								
       
   406 								if (chain_opts[_AlwaysPreserveOrder]) chainedAPI.wait();
       
   407 							}
       
   408 						})(arguments[i],arguments[i]);
       
   409 					}
       
   410 					return chainedAPI;
       
   411 				},
       
   412 				// force LABjs to pause in execution at this point in the chain, until the execution thus far finishes, before proceeding
       
   413 				wait:function(){
       
   414 					if (arguments.length > 0) {
       
   415 						for (var i=0; i<arguments.length; i++) {
       
   416 							chain.push(arguments[i]);
       
   417 						}
       
   418 						group = chain[chain.length-1];
       
   419 					}
       
   420 					else group = false;
       
   421 					
       
   422 					advance_exec_cursor();
       
   423 					
       
   424 					return chainedAPI;
       
   425 				}
       
   426 			};
       
   427 
       
   428 			// the first chain link API (includes `setOptions` only this first time)
       
   429 			return {
       
   430 				script:chainedAPI.script, 
       
   431 				wait:chainedAPI.wait, 
       
   432 				setOptions:function(opts){
       
   433 					merge_objs(opts,chain_opts);
       
   434 					return chainedAPI;
       
   435 				}
       
   436 			};
       
   437 		}
       
   438 
       
   439 		// API for each initial $LAB instance (before chaining starts)
       
   440 		instanceAPI = {
       
   441 			// main API functions
       
   442 			setGlobalDefaults:function(opts){
       
   443 				merge_objs(opts,global_defaults);
       
   444 				return instanceAPI;
       
   445 			},
       
   446 			setOptions:function(){
       
   447 				return create_chain().setOptions.apply(null,arguments);
       
   448 			},
       
   449 			script:function(){
       
   450 				return create_chain().script.apply(null,arguments);
       
   451 			},
       
   452 			wait:function(){
       
   453 				return create_chain().wait.apply(null,arguments);
       
   454 			},
       
   455 
       
   456 			// built-in queuing for $LAB `script()` and `wait()` calls
       
   457 			// useful for building up a chain programmatically across various script locations, and simulating
       
   458 			// execution of the chain
       
   459 			queueScript:function(){
       
   460 				queue[queue.length] = {type:"script", args:[].slice.call(arguments)};
       
   461 				return instanceAPI;
       
   462 			},
       
   463 			queueWait:function(){
       
   464 				queue[queue.length] = {type:"wait", args:[].slice.call(arguments)};
       
   465 				return instanceAPI;
       
   466 			},
       
   467 			runQueue:function(){
       
   468 				var $L = instanceAPI, len=queue.length, i=len, val;
       
   469 				for (;--i>=0;) {
       
   470 					val = queue.shift();
       
   471 					$L = $L[val.type].apply(null,val.args);
       
   472 				}
       
   473 				return $L;
       
   474 			},
       
   475 
       
   476 			// rollback `[global].$LAB` to what it was before this file was loaded, the return this current instance of $LAB
       
   477 			noConflict:function(){
       
   478 				global.$LAB = _$LAB;
       
   479 				return instanceAPI;
       
   480 			},
       
   481 
       
   482 			// create another clean instance of $LAB
       
   483 			sandbox:function(){
       
   484 				return create_sandbox();
       
   485 			}
       
   486 		};
       
   487 
       
   488 		return instanceAPI;
       
   489 	}
       
   490 
       
   491 	// create the main instance of $LAB
       
   492 	global.$LAB = create_sandbox();
       
   493 
       
   494 
       
   495 	/* The following "hack" was suggested by Andrea Giammarchi and adapted from: http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html
       
   496 	   NOTE: this hack only operates in FF and then only in versions where document.readyState is not present (FF < 3.6?).
       
   497 	   
       
   498 	   The hack essentially "patches" the **page** that LABjs is loaded onto so that it has a proper conforming document.readyState, so that if a script which does 
       
   499 	   proper and safe dom-ready detection is loaded onto a page, after dom-ready has passed, it will still be able to detect this state, by inspecting the now hacked 
       
   500 	   document.readyState property. The loaded script in question can then immediately trigger any queued code executions that were waiting for the DOM to be ready. 
       
   501 	   For instance, jQuery 1.4+ has been patched to take advantage of document.readyState, which is enabled by this hack. But 1.3.2 and before are **not** safe or 
       
   502 	   fixed by this hack, and should therefore **not** be lazy-loaded by script loader tools such as LABjs.
       
   503 	*/ 
       
   504 	(function(addEvent,domLoaded,handler){
       
   505 		if (document.readyState == null && document[addEvent]){
       
   506 			document.readyState = "loading";
       
   507 			document[addEvent](domLoaded,handler = function(){
       
   508 				document.removeEventListener(domLoaded,handler,false);
       
   509 				document.readyState = "complete";
       
   510 			},false);
       
   511 		}
       
   512 	})("addEventListener","DOMContentLoaded");
       
   513 
       
   514 })(this);