Path: blob/trunk/third_party/closure/goog/module/moduleloader.js
2868 views
// Copyright 2008 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview The module loader for loading modules across the network.16*17* Browsers do not guarantee that scripts appended to the document18* are executed in the order they are added. For production mode, we use19* XHRs to load scripts, because they do not have this problem and they20* have superior mechanisms for handling failure. However, XHR-evaled21* scripts are harder to debug.22*23* In debugging mode, we use normal script tags. In order to make this work,24* we load the scripts in serial: we do not execute script B to the document25* until we are certain that script A is finished loading.26*27*/2829goog.provide('goog.module.ModuleLoader');3031goog.require('goog.Timer');32goog.require('goog.array');33goog.require('goog.events');34goog.require('goog.events.Event');35goog.require('goog.events.EventHandler');36goog.require('goog.events.EventId');37goog.require('goog.events.EventTarget');38goog.require('goog.labs.userAgent.browser');39goog.require('goog.log');40goog.require('goog.module.AbstractModuleLoader');41goog.require('goog.net.BulkLoader');42goog.require('goog.net.EventType');43goog.require('goog.net.jsloader');44goog.require('goog.userAgent');45goog.require('goog.userAgent.product');46474849/**50* A class that loads Javascript modules.51* @constructor52* @extends {goog.events.EventTarget}53* @implements {goog.module.AbstractModuleLoader}54*/55goog.module.ModuleLoader = function() {56goog.module.ModuleLoader.base(this, 'constructor');5758/**59* Event handler for managing handling events.60* @type {goog.events.EventHandler<!goog.module.ModuleLoader>}61* @private62*/63this.eventHandler_ = new goog.events.EventHandler(this);6465/**66* A map from module IDs to goog.module.ModuleLoader.LoadStatus.67* @type {!Object<Array<string>, goog.module.ModuleLoader.LoadStatus>}68* @private69*/70this.loadingModulesStatus_ = {};71};72goog.inherits(goog.module.ModuleLoader, goog.events.EventTarget);737475/**76* A logger.77* @type {goog.log.Logger}78* @protected79*/80goog.module.ModuleLoader.prototype.logger =81goog.log.getLogger('goog.module.ModuleLoader');828384/**85* Whether debug mode is enabled.86* @type {boolean}87* @private88*/89goog.module.ModuleLoader.prototype.debugMode_ = false;909192/**93* Whether source url injection is enabled.94* @type {boolean}95* @private96*/97goog.module.ModuleLoader.prototype.sourceUrlInjection_ = false;9899100/**101* @return {boolean} Whether sourceURL affects stack traces.102*/103goog.module.ModuleLoader.supportsSourceUrlStackTraces = function() {104return goog.userAgent.product.CHROME ||105(goog.labs.userAgent.browser.isFirefox() &&106goog.labs.userAgent.browser.isVersionOrHigher('36'));107};108109110/**111* @return {boolean} Whether sourceURL affects the debugger.112*/113goog.module.ModuleLoader.supportsSourceUrlDebugger = function() {114return goog.userAgent.product.CHROME || goog.userAgent.GECKO;115};116117118/**119* Gets the debug mode for the loader.120* @return {boolean} Whether the debug mode is enabled.121*/122goog.module.ModuleLoader.prototype.getDebugMode = function() {123return this.debugMode_;124};125126127/**128* Sets the debug mode for the loader.129* @param {boolean} debugMode Whether the debug mode is enabled.130*/131goog.module.ModuleLoader.prototype.setDebugMode = function(debugMode) {132this.debugMode_ = debugMode;133};134135136/**137* When enabled, we will add a sourceURL comment to the end of all scripts138* to mark their origin.139*140* On WebKit, stack traces will reflect the sourceURL comment, so this is141* useful for debugging webkit stack traces in production.142*143* Notice that in debug mode, we will use source url injection + eval rather144* then appending script nodes to the DOM, because the scripts will load far145* faster. (Appending script nodes is very slow, because we can't parallelize146* the downloading and evaling of the script).147*148* The cost of appending sourceURL information is negligible when compared to149* the cost of evaling the script. Almost all clients will want this on.150*151* TODO(nicksantos): Turn this on by default. We may want to turn this off152* for clients that inject their own sourceURL.153*154* @param {boolean} enabled Whether source url injection is enabled.155*/156goog.module.ModuleLoader.prototype.setSourceUrlInjection = function(enabled) {157this.sourceUrlInjection_ = enabled;158};159160161/**162* @return {boolean} Whether we're using source url injection.163* @private164*/165goog.module.ModuleLoader.prototype.usingSourceUrlInjection_ = function() {166return this.sourceUrlInjection_ ||167(this.getDebugMode() &&168goog.module.ModuleLoader.supportsSourceUrlStackTraces());169};170171172/** @override */173goog.module.ModuleLoader.prototype.loadModules = function(174ids, moduleInfoMap, opt_successFn, opt_errorFn, opt_timeoutFn,175opt_forceReload) {176var loadStatus = this.loadingModulesStatus_[ids] ||177new goog.module.ModuleLoader.LoadStatus();178loadStatus.loadRequested = true;179loadStatus.successFn = opt_successFn || null;180loadStatus.errorFn = opt_errorFn || null;181182if (!this.loadingModulesStatus_[ids]) {183// Modules were not prefetched.184this.loadingModulesStatus_[ids] = loadStatus;185this.downloadModules_(ids, moduleInfoMap);186// TODO(user): Need to handle timeouts in the module loading code.187} else if (goog.isDefAndNotNull(loadStatus.responseTexts)) {188// Modules prefetch is complete.189this.evaluateCode_(ids);190}191// Otherwise modules prefetch is in progress, and these modules will be192// executed after the prefetch is complete.193};194195196/**197* Evaluate the JS code.198* @param {Array<string>} moduleIds The module ids.199* @private200*/201goog.module.ModuleLoader.prototype.evaluateCode_ = function(moduleIds) {202this.dispatchEvent(203new goog.module.ModuleLoader.RequestSuccessEvent(moduleIds));204205goog.log.info(this.logger, 'evaluateCode ids:' + moduleIds);206var loadStatus = this.loadingModulesStatus_[moduleIds];207var uris = loadStatus.requestUris;208var texts = loadStatus.responseTexts;209var error = null;210try {211if (this.usingSourceUrlInjection_()) {212for (var i = 0; i < uris.length; i++) {213var uri = uris[i];214goog.globalEval(texts[i] + ' //# sourceURL=' + uri);215}216} else {217goog.globalEval(texts.join('\n'));218}219} catch (e) {220error = e;221// TODO(user): Consider throwing an exception here.222goog.log.warning(223this.logger, 'Loaded incomplete code for module(s): ' + moduleIds, e);224}225226this.dispatchEvent(new goog.module.ModuleLoader.EvaluateCodeEvent(moduleIds));227228if (error) {229this.handleErrorHelper_(230moduleIds, loadStatus.errorFn, null /* status */, error);231} else if (loadStatus.successFn) {232loadStatus.successFn();233}234delete this.loadingModulesStatus_[moduleIds];235};236237238/**239* Handles a successful response to a request for prefetch or load one or more240* modules.241*242* @param {goog.net.BulkLoader} bulkLoader The bulk loader.243* @param {Array<string>} moduleIds The ids of the modules requested.244* @private245*/246goog.module.ModuleLoader.prototype.handleSuccess_ = function(247bulkLoader, moduleIds) {248goog.log.info(this.logger, 'Code loaded for module(s): ' + moduleIds);249250var loadStatus = this.loadingModulesStatus_[moduleIds];251loadStatus.responseTexts = bulkLoader.getResponseTexts();252253if (loadStatus.loadRequested) {254this.evaluateCode_(moduleIds);255}256257// NOTE: A bulk loader instance is used for loading a set of module ids.258// Once these modules have been loaded successfully or in error the bulk259// loader should be disposed as it is not needed anymore. A new bulk loader260// is instantiated for any new modules to be loaded. The dispose is called261// on a timer so that the bulkloader has a chance to release its262// objects.263goog.Timer.callOnce(bulkLoader.dispose, 5, bulkLoader);264};265266267/** @override */268goog.module.ModuleLoader.prototype.prefetchModule = function(id, moduleInfo) {269// Do not prefetch in debug mode.270if (this.getDebugMode()) {271return;272}273var loadStatus = this.loadingModulesStatus_[[id]];274if (loadStatus) {275return;276}277278var moduleInfoMap = {};279moduleInfoMap[id] = moduleInfo;280this.loadingModulesStatus_[[id]] = new goog.module.ModuleLoader.LoadStatus();281this.downloadModules_([id], moduleInfoMap);282};283284285/**286* Downloads a list of JavaScript modules.287*288* @param {Array<string>} ids The module ids in dependency order.289* @param {Object} moduleInfoMap A mapping from module id to ModuleInfo object.290* @private291*/292goog.module.ModuleLoader.prototype.downloadModules_ = function(293ids, moduleInfoMap) {294var uris = [];295for (var i = 0; i < ids.length; i++) {296goog.array.extend(uris, moduleInfoMap[ids[i]].getUris());297}298goog.log.info(this.logger, 'downloadModules ids:' + ids + ' uris:' + uris);299300if (this.getDebugMode() && !this.usingSourceUrlInjection_()) {301// In debug mode use <script> tags rather than XHRs to load the files.302// This makes it possible to debug and inspect stack traces more easily.303// It's also possible to use it to load JavaScript files that are hosted on304// another domain.305// The scripts need to load serially, so this is much slower than parallel306// script loads with source url injection.307goog.net.jsloader.loadMany(uris);308} else {309var loadStatus = this.loadingModulesStatus_[ids];310loadStatus.requestUris = uris;311312var bulkLoader = new goog.net.BulkLoader(uris);313314var eventHandler = this.eventHandler_;315eventHandler.listen(316bulkLoader, goog.net.EventType.SUCCESS,317goog.bind(this.handleSuccess_, this, bulkLoader, ids));318eventHandler.listen(319bulkLoader, goog.net.EventType.ERROR,320goog.bind(this.handleError_, this, bulkLoader, ids));321bulkLoader.load();322}323};324325326/**327* Handles an error during a request for one or more modules.328* @param {goog.net.BulkLoader} bulkLoader The bulk loader.329* @param {Array<string>} moduleIds The ids of the modules requested.330* @param {number} status The response status.331* @private332*/333goog.module.ModuleLoader.prototype.handleError_ = function(334bulkLoader, moduleIds, status) {335var loadStatus = this.loadingModulesStatus_[moduleIds];336// The bulk loader doesn't cancel other requests when a request fails. We will337// delete the loadStatus in the first failure, so it will be undefined in338// subsequent errors.339if (loadStatus) {340delete this.loadingModulesStatus_[moduleIds];341this.handleErrorHelper_(moduleIds, loadStatus.errorFn, status);342}343344// NOTE: A bulk loader instance is used for loading a set of module ids. Once345// these modules have been loaded successfully or in error the bulk loader346// should be disposed as it is not needed anymore. A new bulk loader is347// instantiated for any new modules to be loaded. The dispose is called348// on another thread so that the bulkloader has a chance to release its349// objects.350goog.Timer.callOnce(bulkLoader.dispose, 5, bulkLoader);351};352353354/**355* Handles an error during a request for one or more modules.356* @param {Array<string>} moduleIds The ids of the modules requested.357* @param {?function(?number)} errorFn The function to call on failure.358* @param {?number} status The response status.359* @param {!Error=} opt_error The error encountered, if available.360* @private361*/362goog.module.ModuleLoader.prototype.handleErrorHelper_ = function(363moduleIds, errorFn, status, opt_error) {364this.dispatchEvent(365new goog.module.ModuleLoader.RequestErrorEvent(moduleIds, opt_error));366367goog.log.warning(this.logger, 'Request failed for module(s): ' + moduleIds);368369if (errorFn) {370errorFn(status);371}372};373374375/** @override */376goog.module.ModuleLoader.prototype.disposeInternal = function() {377goog.module.ModuleLoader.superClass_.disposeInternal.call(this);378379this.eventHandler_.dispose();380this.eventHandler_ = null;381};382383384/**385* Events dispatched by the ModuleLoader.386* @const387*/388goog.module.ModuleLoader.EventType = {389/**390* @const {!goog.events.EventId<391* !goog.module.ModuleLoader.EvaluateCodeEvent>} Called after the code for392* a module is evaluated.393*/394EVALUATE_CODE:395new goog.events.EventId(goog.events.getUniqueId('evaluateCode')),396397/**398* @const {!goog.events.EventId<399* !goog.module.ModuleLoader.RequestSuccessEvent>} Called when the400* BulkLoader finishes successfully.401*/402REQUEST_SUCCESS:403new goog.events.EventId(goog.events.getUniqueId('requestSuccess')),404405/**406* @const {!goog.events.EventId<407* !goog.module.ModuleLoader.RequestErrorEvent>} Called when the408* BulkLoader fails, or code loading fails.409*/410REQUEST_ERROR:411new goog.events.EventId(goog.events.getUniqueId('requestError'))412};413414415416/**417* @param {Array<string>} moduleIds The ids of the modules being evaluated.418* @constructor419* @extends {goog.events.Event}420* @final421* @protected422*/423goog.module.ModuleLoader.EvaluateCodeEvent = function(moduleIds) {424goog.module.ModuleLoader.EvaluateCodeEvent.base(425this, 'constructor', goog.module.ModuleLoader.EventType.EVALUATE_CODE);426427/**428* @type {Array<string>}429*/430this.moduleIds = moduleIds;431};432goog.inherits(goog.module.ModuleLoader.EvaluateCodeEvent, goog.events.Event);433434435436/**437* @param {Array<string>} moduleIds The ids of the modules being evaluated.438* @constructor439* @extends {goog.events.Event}440* @final441* @protected442*/443goog.module.ModuleLoader.RequestSuccessEvent = function(moduleIds) {444goog.module.ModuleLoader.RequestSuccessEvent.base(445this, 'constructor', goog.module.ModuleLoader.EventType.REQUEST_SUCCESS);446447/**448* @type {Array<string>}449*/450this.moduleIds = moduleIds;451};452goog.inherits(goog.module.ModuleLoader.RequestSuccessEvent, goog.events.Event);453454455456/**457* @param {Array<string>} moduleIds The ids of the modules being evaluated.458* @param {!Error=} opt_error The error encountered, if available.459* @constructor460* @extends {goog.events.Event}461* @final462* @protected463*/464goog.module.ModuleLoader.RequestErrorEvent = function(moduleIds, opt_error) {465goog.module.ModuleLoader.RequestErrorEvent.base(466this, 'constructor', goog.module.ModuleLoader.EventType.REQUEST_ERROR);467468/**469* @type {Array<string>}470*/471this.moduleIds = moduleIds;472473/** @type {?Error} */474this.error = opt_error || null;475};476goog.inherits(goog.module.ModuleLoader.RequestErrorEvent, goog.events.Event);477478479480/**481* A class that keeps the state of the module during the loading process. It is482* used to save loading information between modules download and evaluation.483* @constructor484* @final485*/486goog.module.ModuleLoader.LoadStatus = function() {487/**488* The request uris.489* @type {Array<string>}490*/491this.requestUris = null;492493/**494* The response texts.495* @type {Array<string>}496*/497this.responseTexts = null;498499/**500* Whether loadModules was called for the set of modules referred by this501* status.502* @type {boolean}503*/504this.loadRequested = false;505506/**507* Success callback.508* @type {?function()}509*/510this.successFn = null;511512/**513* Error callback.514* @type {?function(?number)}515*/516this.errorFn = null;517};518519520