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