Issues (4868)

api/js/labjs/LAB.src.js (7 issues)

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*/
0 ignored issues
show
The call to log_msg seems to have too many arguments starting with "start script preload: " + src.
Loading history...
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*/
0 ignored issues
show
The call to log_msg seems to have too many arguments starting with "script preload finished: " + script_obj.real_src.
Loading history...
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*/
0 ignored issues
show
The call to log_msg seems to have too many arguments starting with "script execution finish..." + script_obj.real_src.
Loading history...
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) {
0 ignored issues
show
The variable exec_cursor is changed as part of the while loop for example by exec_cursor++ on line 343. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
341
					if (is_func(chain[exec_cursor])) {
342
						/*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("$LAB.wait() executing: "+chain[exec_cursor]);/*!END_DEBUG*/
0 ignored issues
show
The call to log_msg seems to have too many arguments starting with "$LAB.wait() executing: " + chain.exec_cursor.
Loading history...
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
0 ignored issues
show
Complexity Coding Style introduced by
You seem to be assigning a new value to the loop variable j here. Please check if this was indeed your intention. Even if it was, consider using another kind of loop instead.
Loading history...
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));
0 ignored issues
show
The variable scripts_currently_loading is changed as part of the for loop for example by true on line 404. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
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);