PageSourceSearch

https://skstoa.com/resources/js/lib/LAB.js

js skstoa.com collected 2026-10-05 18:35:16 UTC 19,398 bytes, 514 lines download raw bytes

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.