1 // ==========================================================================
  2 // Project:   SproutCore - JavaScript Application Framework
  3 // Copyright: ©2006-2011 Strobe Inc. and contributors.
  4 //            Portions ©2008-2011 Apple Inc. All rights reserved.
  5 // License:   Licensed under MIT license (see license.js)
  6 // ==========================================================================
  7 
  8 /*globals jQuery */
  9 
 10 sc_require('tasks/task');
 11 
 12 //@if(debug)
 13 SC.LOG_MODULE_LOADING = YES;
 14 //@endif
 15 
 16 /**
 17   SC.Module is responsible for dynamically loading in JavaScript and other
 18   resources. These packages of code and resources, called bundles, can be
 19   loaded by your application once it has finished loading, allowing you to
 20   reduce the time taken for it to launch.
 21 
 22   You can explicitly load a module by calling SC.Module.loadModule(), or you
 23   can mark a module as prefetched in your Buildfile. In those cases,
 24   SproutCore will automatically start to load the bundle once the application
 25   has loaded and the user has remained idle for more than one second.
 26 */
 27 
 28 SC.Module = SC.Object.create(/** @scope SC.Module */ {
 29 
 30   /**
 31     Returns YES if the module is ready; NO if it is not loaded or its
 32     dependencies have not yet loaded.
 33 
 34     @param {String} moduleName the name of the module to check
 35     @returns {Boolean}
 36   */
 37   isModuleReady: function (moduleName) {
 38     var moduleInfo = SC.MODULE_INFO[moduleName];
 39     return moduleInfo ? !!moduleInfo.isReady : NO;
 40   },
 41 
 42   /**
 43     Asynchronously loads a module if it is not already loaded. If you pass
 44     a function, or a target and action, it will be called once the module
 45     has finished loading.
 46 
 47     If the module you request has dependencies (as specified in the Buildfile)
 48     that are not yet loaded, it will load them first before executing the
 49     requested module.
 50 
 51     @param moduleName {String}
 52     @param target {Function}
 53     @param method {Function}
 54     @returns {Boolean} YES if already loaded, NO otherwise
 55   */
 56   loadModule: function (moduleName, target, method) {
 57     var module = SC.MODULE_INFO[moduleName],
 58         //@if(debug)
 59         log    = SC.LOG_MODULE_LOADING,
 60         //@endif
 61         args;
 62 
 63     if (arguments.length > 3) {
 64       // Fast arguments access.
 65       // Accessing `arguments.length` is just a Number and doesn't materialize the `arguments` object, which is costly.
 66       args = new Array(arguments.length - 3); //  SC.A(arguments).slice(3)
 67       for (var i = 0, len = args.length; i < len; i++) { args[i] = arguments[i + 3]; }
 68     } else {
 69       args = [];
 70     }
 71 
 72     // Treat the first parameter as the callback if the target is a function and there is
 73     // no method supplied.
 74     if (method === undefined && SC.typeOf(target) === SC.T_FUNCTION) {
 75       method = target;
 76       target = null;
 77     }
 78 
 79     //@if(debug)
 80     if (log) SC.debug("SC.Module: Attempting to load '%@'", moduleName);
 81     //@endif
 82 
 83     // If we couldn't find anything in the SC.MODULE_INFO hash, we don't have any record of the
 84     // requested module.
 85     if (!module) {
 86       throw new Error("SC.Module: could not find module '%@'".fmt(moduleName));
 87     }
 88 
 89     // If this module was in the middle of being prefetched, we now need to
 90     // execute it immediately when it loads.
 91     module.isPrefetching = NO;
 92 
 93     // If the module is already loaded, execute the callback immediately if SproutCore is loaded,
 94     // or else as soon as SC has finished loading.
 95     if (module.isLoaded && !module.isWaitingForRunLoop) {
 96       //@if(debug)
 97       if (log) SC.debug("SC.Module: Module '%@' already loaded.", moduleName);
 98       //@endif
 99 
100       // we can't just eval it if its dependencies have not been met...
101       if (!this._dependenciesMetForModule(moduleName)) {
102         // we can't let it return normally here, because we need the module to wait until the end of the run loop.
103         // This is because the module may set up bindings.
104         this._addCallbackForModule(moduleName, target, method, args);
105 
106         this._loadDependenciesForModule(moduleName);
107 
108         return NO;
109       }
110 
111       // If the module has finished loading and we have the string
112       // representation, try to evaluate it now.
113       if (module.source) {
114         //@if(debug)
115         if (log) SC.debug("SC.Module: Evaluating JavaScript for module '%@'.", moduleName);
116         //@endif
117         this._evaluateStringLoadedModule(module);
118 
119         // we can't let it return normally here, because we need the module to wait until the end of the run loop.
120         // This is because the module may set up bindings.
121         this._addCallbackForModule(moduleName, target, method, args);
122 
123         this.invokeLast(function () {
124           module.isReady = YES;
125           this._moduleDidBecomeReady(moduleName);
126         });
127 
128         return NO;
129       }
130 
131       if (method) {
132         if (SC.isReady) {
133           SC.Module._invokeCallback(moduleName, target, method, args);
134         } else {
135           // Queue callback for when SC has finished loading.
136           SC.ready(SC.Module, function () {
137             SC.Module._invokeCallback(moduleName, target, method, args);
138           });
139         }
140       }
141 
142       return YES;
143     }
144 
145     // The module has loaded, but is waiting for the end of the run loop before it is "ready";
146     // we just need to add the callback.
147     else if (module.isWaitingForRunLoop) {
148       this._addCallbackForModule(moduleName, target, method, args);
149     }
150     // The module is not yet loaded, so register the callback and, if necessary, begin loading
151     // the code.
152     else {
153 
154       //@if(debug)
155       if (log) SC.debug("SC.Module: Module '%@' is not loaded, loading now.", moduleName);
156       //@endif
157 
158       // If this method is called more than once for the same module before it is finished
159       // loading, we might have multiple callbacks that need to be executed once it loads.
160       this._addCallbackForModule(moduleName, target, method, args);
161 
162       // If this is the first time the module has been requested, determine its dependencies
163       // and begin loading them as well as the JavaScript for this module.
164       if (!module.isLoading) {
165         this._loadDependenciesForModule(moduleName);
166         this._loadCSSForModule(moduleName);
167         this._loadJavaScriptForModule(moduleName);
168         module.isLoading = YES;
169       }
170 
171       return NO;
172     }
173   },
174 
175   _addCallbackForModule: function (moduleName, target, method, args) {
176     var module = SC.MODULE_INFO[moduleName];
177 
178     // Retrieve array of callbacks from MODULE_INFO hash.
179     var callbacks = module.callbacks || [];
180 
181     if (method) {
182       callbacks.push(function () {
183         SC.Module._invokeCallback(moduleName, target, method, args);
184       });
185     }
186 
187     module.callbacks = callbacks;
188   },
189 
190   /**
191     @private
192 
193     Loads a module in string form. If you prefetch a module, its source will
194     be held as a string in memory until SC.Module.loadModule() is called, at
195     which time its JavaScript will be evaluated.
196 
197     You shouldn't call this method directly; instead, mark modules as
198     prefetched in your Buildfile. SproutCore will automatically prefetch those
199     modules once your application has loaded and the user is idle.
200 
201     @param {String} moduleName the name of the module to prefetch
202   */
203   prefetchModule: function (moduleName) {
204     var module = SC.MODULE_INFO[moduleName];
205 
206     if (module.isLoading || module.isLoaded) return;
207 
208     if (SC.LOG_MODULE_LOADING) SC.debug("SC.Module: Prefetching module '%@'.", moduleName);
209     this._loadDependenciesForModule(moduleName);
210     this._loadCSSForModule(moduleName);
211     this._loadJavaScriptForModule(moduleName);
212     module.isLoading = YES;
213     module.isPrefetching = YES;
214   },
215 
216   // ..........................................................
217   // INTERNAL SUPPORT
218   //
219 
220   /** @private
221     If a module is marked for lazy instantiation, this method will execute the closure and call
222     any registered callbacks.
223   */
224   _executeLazilyInstantiatedModule: function (moduleName, targetName, methodName) {
225     var lazyInfo =  SC.LAZY_INSTANTIATION[moduleName];
226     var target;
227     var method;
228     var idx, len;
229 
230     if (SC.LOG_MODULE_LOADING) {
231       SC.debug("SC.Module: Module '%@' is marked for lazy instantiation, instantiating it now…", moduleName);
232     }
233 
234     len = lazyInfo.length;
235     for (idx = 0; idx < len; idx++) {
236       // Iterate through each function associated with this module, and attempt to execute it.
237       try {
238         lazyInfo[idx]();
239       } catch (e) {
240         SC.Logger.error("SC.Module: Failed to lazily instatiate entry for  '%@'".fmt(moduleName));
241       }
242     }
243 
244     // Free up memory containing the functions once they have been executed.
245     delete SC.LAZY_INSTANTIATION[moduleName];
246 
247     // Now that we have executed the functions, try to find the target and action for the callback.
248     target = this._targetForTargetName(targetName);
249     method = this._methodForMethodNameInTarget(methodName, target);
250 
251     if (!method) {
252       throw new Error("SC.Module: could not find callback for lazily instantiated module '%@'".fmt(moduleName));
253     }
254   },
255 
256   /**
257     Evaluates a module's JavaScript if it is stored in string format, then
258     deletes that code from memory.
259 
260     @param {Hash} module the module to evaluate
261   */
262   _evaluateStringLoadedModule: function (module) {
263     var moduleSource = module.source;
264 
265     // so, force a run loop.
266     jQuery.globalEval(moduleSource);
267 
268     delete module.source;
269 
270     if (module.cssSource) {
271       var el = document.createElement('style');
272       el.setAttribute('type', 'text/css');
273       if (el.styleSheet) {
274         el.styleSheet.cssText = module.cssSource;
275       } else {
276         var content = document.createTextNode(module.cssSource);
277         el.appendChild(content);
278       }
279 
280       document.getElementsByTagName('head')[0].appendChild(el);
281     }
282 
283     module.isReady = YES;
284   },
285 
286   /**
287     @private
288 
289     Creates <link> tags for every CSS resource in a module.
290 
291     @param {String} moduleName the name of the module whose CSS should be loaded
292   */
293   _loadCSSForModule: function (moduleName) {
294     var head = document.getElementsByTagName('head')[0];
295     var module = SC.MODULE_INFO[moduleName];
296     var styles = module.styles || [];
297     var len = styles.length;
298     var url;
299     var el;
300     var idx;
301 
302     if (!head) head = document.documentElement; // fix for Opera
303     len = styles.length;
304 
305     for (idx = 0; idx < len; idx++) {
306       url = styles[idx];
307 
308       if (url.length > 0) {
309         if (SC.LOG_MODULE_LOADING) SC.debug("SC.Module: Loading CSS file in '%@' -> '%@'", moduleName, url);
310 
311         // if we are on a retina device lets load the retina style sheet instead
312         if (window.devicePixelRatio > 1 || window.location.search.indexOf("2x") > -1) {
313           url = url.replace('.css', '@2x.css');
314         }
315 
316         el = document.createElement('link');
317         el.setAttribute('href', url);
318         el.setAttribute('rel', "stylesheet");
319         el.setAttribute('type', "text/css");
320         head.appendChild(el);
321       }
322     }
323 
324     el = null;
325   },
326 
327   _loadJavaScriptForModule: function (moduleName) {
328     var module = SC.MODULE_INFO[moduleName];
329     var el;
330     var url;
331     var dependencies = module.dependencies;
332     var dependenciesAreLoaded = YES;
333 
334     // If this module has dependencies, determine if they are loaded.
335     if (dependencies && dependencies.length > 0) {
336       dependenciesAreLoaded = this._dependenciesMetForModule(moduleName);
337     }
338 
339     // If the module is prefetched, always load the string representation.
340     if (module.isPrefetched) {
341       url = module.stringURL;
342     } else {
343       if (dependenciesAreLoaded) {
344         // Either we have no dependencies or they've all loaded already,
345         // so just execute the code immediately once it loads.
346         url = module.scriptURL;
347       } else {
348         // Because the dependencies might load after this module, load the
349         // string representation so we can execute it once all dependencies
350         // are in place.
351         url = module.stringURL;
352       }
353     }
354 
355     if (url.length > 0) {
356       if (SC.LOG_MODULE_LOADING) SC.debug("SC.Module: Loading JavaScript file in '%@' -> '%@'", moduleName, url);
357 
358       el = document.createElement('script');
359       el.setAttribute('type', "text/javascript");
360       el.setAttribute('src', url);
361 
362       if (el.addEventListener) {
363         el.addEventListener('load', function () {
364           SC.run(function () {
365             SC.Module._moduleDidLoad(moduleName);
366           });
367         }, false);
368       } else if (el.readyState) {
369         el.onreadystatechange = function () {
370           if (this.readyState === 'complete' || this.readyState === 'loaded') {
371             SC.run(function () {
372               SC.Module._moduleDidLoad(moduleName);
373             });
374           }
375         };
376       }
377 
378       document.body.appendChild(el);
379     }
380   },
381 
382   /**
383     @private
384 
385     Returns YES if all of the dependencies for a module are ready.
386 
387     @param {String} moduleName the name of the module being checked
388     @returns {Boolean} whether the dependencies are loaded
389   */
390   _dependenciesMetForModule: function (moduleName) {
391     var dependencies = SC.MODULE_INFO[moduleName].dependencies || [];
392     var idx, len = dependencies.length;
393     var dependencyName;
394     var module;
395 
396     for (idx = 0; idx < len; idx++) {
397       dependencyName = dependencies[idx];
398       module = SC.MODULE_INFO[dependencyName];
399 
400       if (!module) throw new Error("SC.loadModule: Unable to find dependency %@ for module %@.".fmt(dependencyName, moduleName));
401 
402       if (!module.isReady) {
403         return NO;
404       }
405     }
406 
407     return YES;
408   },
409 
410   /**
411     Loads all unloaded dependencies for a module, then creates the <script> and <link> tags to
412     load the JavaScript and CSS for the module.
413   */
414   _loadDependenciesForModule: function (moduleName) {
415     // Load module's dependencies first.
416     var moduleInfo      = SC.MODULE_INFO[moduleName];
417 
418     //@if(debug)
419     var log             = SC.LOG_MODULE_LOADING;
420     //@endif
421     var dependencies    = moduleInfo.dependencies || [];
422     var dependenciesMet = YES;
423     var len             = dependencies.length;
424     var idx;
425     var requiredModuleName;
426     var requiredModule;
427     var dependents;
428 
429     for (idx = 0; idx < len; idx++) {
430       requiredModuleName = dependencies[idx];
431       requiredModule = SC.MODULE_INFO[requiredModuleName];
432 
433       // Try to find dependent module in MODULE_INFO
434       if (!requiredModule) {
435         throw new Error("SC.Module: could not find required module '%@' for module '%@'".fmt(requiredModuleName, moduleName));
436       } else {
437 
438         // Required module has been requested but hasn't loaded yet.
439         if (requiredModule.isLoading) {
440           dependenciesMet = NO;
441 
442           dependents = requiredModule.dependents;
443           if (!dependents) requiredModule.dependents = dependents = [];
444           dependents.push(moduleName);
445         }
446 
447         // Required module has already been loaded and evaluated, no need to worry about it.
448         else if (requiredModule.isReady) {
449           continue;
450         }
451         // Required module has not been loaded nor requested yet.
452         else {
453           dependenciesMet = NO;
454 
455           // Register this as a dependent module (used by SC._moduleDidLoad()...)
456           dependents = requiredModule.dependents;
457           if (!dependents) requiredModule.dependents = dependents = [];
458 
459           dependents.push(moduleName);
460 
461           //@if(debug)
462           if (log) SC.debug("SC.Module: '%@' depends on '%@', loading dependency…", moduleName, requiredModuleName);
463           //@endif
464 
465           // Load dependencies
466           SC.Module.loadModule(requiredModuleName);
467         }
468       }
469     }
470   },
471 
472   /**
473     @private
474 
475     Calls an action on a target to notify the target that a module has loaded.
476   */
477   _invokeCallback: function (moduleName, targetName, methodName, args) {
478     var method;
479     var target;
480 
481     target = this._targetForTargetName(targetName);
482     method = this._methodForMethodNameInTarget(methodName, target);
483 
484     // If we weren't able to find the callback, this module may be lazily instantiated and
485     // the callback won't exist until we execute the closure that it is wrapped in.
486     if (!method) {
487       if (SC.LAZY_INSTANTIATION[moduleName]) {
488         this._executeLazilyInstantiatedModule(moduleName, targetName, methodName);
489 
490         target = this._targetForTargetName(targetName);
491         method = this._methodForMethodNameInTarget(methodName, target);
492       } else {
493         throw new Error("SC.Module: could not find callback for '%@'".fmt(moduleName));
494       }
495     }
496 
497     if (!args) {
498       args = [];
499     }
500 
501     // The first parameter passed to the callback is the name of the module.
502     args.unshift(moduleName);
503 
504     // Invoke the callback. Wrap it in a run loop if we are not in a runloop already.
505     var needsRunLoop = !!SC.RunLoop.currentRunLoop;
506     if (needsRunLoop) {
507       SC.run(function () {
508         method.apply(target, args);
509       });
510     } else {
511       method.apply(target, args);
512     }
513   },
514 
515   /** @private
516     Given a module name, iterates through all registered callbacks and calls them.
517   */
518   _invokeCallbacksForModule: function (moduleName) {
519     var moduleInfo = SC.MODULE_INFO[moduleName], callbacks;
520     if (!moduleInfo) return; // shouldn't happen, but recover anyway
521 
522     //@if(debug)
523     if (SC.LOG_MODULE_LOADING) SC.debug("SC.Module: Module '%@' has completed loading, invoking callbacks.", moduleName);
524     //@endif
525 
526     callbacks = moduleInfo.callbacks || [];
527 
528     for (var idx = 0, len = callbacks.length; idx < len; ++idx) {
529       callbacks[idx]();
530     }
531   },
532 
533   _evaluateAndInvokeCallbacks: function (moduleName) {
534     var moduleInfo = SC.MODULE_INFO;
535     var module = moduleInfo[moduleName];
536     //@if(debug)
537     var log = SC.LOG_MODULE_LOADING;
538 
539     if (log) SC.debug("SC.Module: Evaluating and invoking callbacks for '%@'.", moduleName);
540     //@endif
541 
542     if (module.source) {
543       this._evaluateStringLoadedModule(module);
544     }
545 
546     // this is ugly, but a module evaluated late like this won't be done instantiating
547     // until the end of a run loop. Also, the code here is not structured in a way that makes
548     // it easy to "add a step" before saying a module is ready. And finally, invokeLater doesn't
549     // accept arguments; hence, the closure.
550     module.isWaitingForRunLoop = YES;
551     this.invokeLast(function () {
552       module.isReady = YES;
553       this._moduleDidBecomeReady(moduleName);
554     });
555   },
556 
557   _moduleDidBecomeReady: function (moduleName) {
558     var moduleInfo = SC.MODULE_INFO;
559     var module = moduleInfo[moduleName];
560     //@if(debug)
561     var log = SC.LOG_MODULE_LOADING;
562     //@endif
563 
564     module.isWaitingForRunLoop = NO;
565 
566     if (SC.isReady) {
567       SC.Module._invokeCallbacksForModule(moduleName);
568       delete module.callbacks;
569     } else {
570       SC.ready(SC, function () {
571         SC.Module._invokeCallbacksForModule(moduleName);
572         delete module.callbacks;
573       });
574     }
575 
576     // for each dependent module, try and load them again...
577     var dependents = module.dependents || [];
578     var dependentName, dependent;
579 
580     for (var idx = 0, len = dependents.length; idx < len; idx++) {
581       dependentName = dependents[idx];
582       dependent = moduleInfo[dependentName];
583       if (dependent.isLoaded && this._dependenciesMetForModule(dependentName)) {
584         //@if(debug)
585         if (log) SC.debug("SC.Module: Now that %@ has loaded, all dependencies for a dependent %@ are met.", moduleName, dependentName);
586         //@endif
587         this._evaluateAndInvokeCallbacks(dependentName);
588       }
589     }
590 
591   },
592 
593   /** @private
594     Called when the JavaScript for a module finishes loading.
595 
596     Any pending callbacks are called (if SC.isReady), and any dependent
597     modules which were waiting for this module to load are notified so they
598     can continue loading.
599 
600     @param moduleName {String} the name of the module that just loaded
601   */
602   _moduleDidLoad: function (moduleName) {
603     var module = SC.MODULE_INFO[moduleName];
604     //@if(debug)
605     var log    = SC.LOG_MODULE_LOADING;
606     //@endif
607     var dependenciesMet;
608 
609     //@if(debug)
610     if (log) SC.debug("SC.Module: Module '%@' finished loading.", moduleName);
611     //@endif
612 
613     if (!module) {
614       //@if(debug)
615       if (log) SC.debug("SC._moduleDidLoad() called for unknown module '@'.", moduleName);
616       //@endif
617       module = SC.MODULE_INFO[moduleName] = { isLoaded: YES, isReady: YES };
618       return;
619     }
620 
621     if (module.isLoaded) {
622       //@if(debug)
623       if (log) SC.debug("SC._moduleDidLoad() called more than once for module '%@'. Skipping.", moduleName);
624       //@endif
625       return;
626     }
627 
628     // Remember that we're loaded.
629     delete module.isLoading;
630     module.isLoaded = YES;
631 
632     if (!module.isPrefetching) {
633       dependenciesMet = this._dependenciesMetForModule(moduleName);
634       if (dependenciesMet) {
635         this._evaluateAndInvokeCallbacks(moduleName);
636       } else {
637         //@if(debug)
638         if (log) SC.debug("SC.Module: Dependencies for '%@' not met yet, waiting to evaluate.", moduleName);
639         //@endif
640       }
641     } else {
642       delete module.isPrefetching;
643       //@if(debug)
644       if (log) SC.debug("SC.Module: Module '%@' was prefetched, not evaluating until needed.", moduleName);
645       //@endif
646     }
647   },
648 
649   /**
650     @private
651 
652     If necessary, converts a property path into a target object.
653 
654     @param {String|Object} targetName the string or object representing the target
655     @returns Object
656   */
657   _targetForTargetName: function (targetName) {
658     if (SC.typeOf(targetName) === SC.T_STRING) {
659       return SC.objectForPropertyPath(targetName);
660     }
661 
662     return targetName;
663   },
664 
665   /**
666     @private
667 
668     If necessary, converts a property path into a method object.
669 
670     @param {String|Object} methodName the string or object representing the method
671     @param {Object} target the target from which to retrieve the method
672     @returns Object
673   */
674   _methodForMethodNameInTarget: function (methodName, target) {
675     if (SC.typeOf(methodName) === SC.T_STRING) {
676       return SC.objectForPropertyPath(methodName, target);
677     }
678 
679     return methodName;
680   },
681 
682   /**
683     A list of the methods to temporarily disable (and buffer calls for) when we are suspended.
684   */
685   methodsForSuspend: "loadModule _moduleDidLoad prefetchModule _moduleDidBecomeReady".w(),
686 
687   /**
688     Call this in order to prevent expensive tasks from occurring at inopportune times.
689   */
690   suspend: function () {
691 
692     //Increment the suspension count, to support nested suspend()/resume() pairs.
693     //We only do anything if the suspend count ends up at 1, as that implies it's
694     //the first suspend() call.
695     this._suspendCount = (this._suspendCount || 0) + 1;
696     if (this._suspendCount !== 1) return;
697 
698     //Yummy variables.
699     var methods = this.get('methodsForSuspend'),
700         replaceKey, saveKey, key, i;
701 
702     //Now we go through the list of methods to suspend, and overwrite them with
703     //versions that will buffer their calls in a _bufferedCalls array.
704     for (i = 0; (key = methods[i]); i++) {
705       //jshint loopfunc: true
706       //Ensure the replacement function exists at a key where it'll be cached.
707       if (!this[replaceKey = "__replacement_" + key + "__"]) {
708         (this[replaceKey] = function () {
709           (this._bufferedCalls || (this._bufferedCalls = [])).push({
710             method: arguments.callee.methodName,
711             arguments: arguments
712           });
713         }).methodName = key;
714       }
715 
716       //Ensure the original function exists at a key where it'll be cached.
717       if (!this[saveKey = "__saved_" + key + "__"]) this[saveKey] = this[key];
718 
719       //Ensure that the replacement function exists where the rest of the
720       //code expects the original.
721       this[key] = this[replaceKey];
722     }
723   },
724 
725   /**
726     Call this in order to resume normal behavior of the methods here, and to
727     finally perform any calls that may have occurred during suspension. Calls
728     will run in the order they were received.
729   */
730   resume: function () {
731 
732     //First, we need to decrement the suspension count, and warn if the suspension
733     //count implied that we weren't already suspended. Furthermore, if the suspend
734     //count is not zero, then we haven't tackled the last suspend() call with a resume(),
735     //and should therefore not resume.
736     this._suspendCount = (this._suspendCount || 0) - 1;
737     if (this._suspendCount < 0) {
738       SC.warn("SC.Module.resume() was called without SC.Module having been in a suspended state. Call aborted.");
739       this._suspendCount = 0;
740       return;
741     }
742     if (this._suspendCount > 0) return;
743 
744     //Yummy variables.
745     var methods = this.get('methodsForSuspend'),
746         calls = this._bufferedCalls,
747         key, i, call;
748 
749     //Restore the original methods to where they belong for normal functionality.
750     for (i = 0; (key = methods[i]); i++) this[key] = this["__saved_" + key + "__"];
751 
752     //Perform any buffered calls that built up during the suspended period.
753     for (i = 0; (call = calls) && calls[i]; i++) this[call.method].apply(this, call.arguments);
754 
755     //Clear the list of calls, so subsequent resume() calls won't flush them again.
756     if (calls) calls.length = 0;
757   }
758 });
759 
760 /**
761 Inspect the list of modules and, for every prefetched module, create a
762 background task to load the module when the user remains idle.
763 */
764 SC.ready(function () {
765   var moduleInfo = SC.MODULE_INFO;
766   var moduleName;
767   var module;
768   var task;
769 
770   // Iterate through all known modules and look for those that are marked
771   // as prefetched.
772   for (moduleName in moduleInfo) {
773     module = moduleInfo[moduleName];
774 
775     if (module.isPrefetched) {
776       // Create a task that will load the module, and then register it with
777       // the global background task queue.
778       task = SC.Module.PrefetchModuleTask.create({ prefetchedModuleName: moduleName });
779       SC.backgroundTaskQueue.push(task);
780     }
781   }
782 });
783 
784 SC.Module.PrefetchModuleTask = SC.Task.extend({
785   prefetchedModuleName: null,
786   run: function () {
787     SC.Module.prefetchModule(this.prefetchedModuleName);
788   }
789 });
790