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 invali
126date 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);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.