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