Path: blob/trunk/third_party/closure/goog/module/modulemanager.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 A singleton object for managing Javascript code modules.16*17*/1819goog.provide('goog.module.ModuleManager');20goog.provide('goog.module.ModuleManager.CallbackType');21goog.provide('goog.module.ModuleManager.FailureType');2223goog.require('goog.Disposable');24goog.require('goog.array');25goog.require('goog.asserts');26goog.require('goog.async.Deferred');27goog.require('goog.debug.Trace');28/** @suppress {extraRequire} */29goog.require('goog.dispose');30goog.require('goog.log');31/** @suppress {extraRequire} */32goog.require('goog.module');33/** @suppress {extraRequire} interface */34goog.require('goog.module.AbstractModuleLoader');35goog.require('goog.module.ModuleInfo');36goog.require('goog.module.ModuleLoadCallback');37goog.require('goog.object');38394041/**42* The ModuleManager keeps track of all modules in the environment.43* Since modules may not have their code loaded, we must keep track of them.44* @constructor45* @extends {goog.Disposable}46* @struct47*/48goog.module.ModuleManager = function() {49goog.module.ModuleManager.base(this, 'constructor');5051/**52* A mapping from module id to ModuleInfo object.53* @private {Object<string, !goog.module.ModuleInfo>}54*/55this.moduleInfoMap_ = {};5657// TODO (malteubl): Switch this to a reentrant design.58/**59* The ids of the currently loading modules. If batch mode is disabled, then60* this array will never contain more than one element at a time.61* @type {Array<string>}62* @private63*/64this.loadingModuleIds_ = [];6566/**67* The requested ids of the currently loading modules. This does not include68* module dependencies that may also be loading.69* @type {Array<string>}70* @private71*/72this.requestedLoadingModuleIds_ = [];7374// TODO(user): Make these and other arrays that are used as sets be75// actual sets.76/**77* All module ids that have ever been requested. In concurrent loading these78* are the ones to subtract from future requests.79* @type {!Array<string>}80* @private81*/82this.requestedModuleIds_ = [];8384/**85* A queue of the ids of requested but not-yet-loaded modules. The zero86* position is the front of the queue. This is a 2-D array to group modules87* together with other modules that should be batch loaded with them, if88* batch loading is enabled.89* @type {Array<Array<string>>}90* @private91*/92this.requestedModuleIdsQueue_ = [];9394/**95* The ids of the currently loading modules which have been initiated by user96* actions.97* @type {Array<string>}98* @private99*/100this.userInitiatedLoadingModuleIds_ = [];101102/**103* A map of callback types to the functions to call for the specified104* callback type.105* @type {Object<goog.module.ModuleManager.CallbackType, Array<Function>>}106* @private107*/108this.callbackMap_ = {};109110/**111* Module info for the base module (the one that contains the module112* manager code), which we set as the loading module so one can113* register initialization callbacks in the base module.114*115* The base module is considered loaded when #setAllModuleInfo is called or116* #setModuleContext is called, whichever comes first.117*118* @type {goog.module.ModuleInfo}119* @private120*/121this.baseModuleInfo_ = new goog.module.ModuleInfo([], '');122123/**124* The module that is currently loading, or null if not loading anything.125* @type {goog.module.ModuleInfo}126* @private127*/128this.currentlyLoadingModule_ = this.baseModuleInfo_;129130/**131* The id of the last requested initial module. When it loaded132* the deferred in {@code this.initialModulesLoaded_} resolves.133* @private {?string}134*/135this.lastInitialModuleId_ = null;136137/**138* Deferred for when all initial modules have loaded. We currently block139* sending additional module requests until this deferred resolves. In a140* future optimization it may be possible to use the initial modules as141* seeds for the module loader "requested module ids" and start making new142* requests even sooner.143* @private {!goog.async.Deferred}144*/145this.initialModulesLoaded_ = new goog.async.Deferred();146147/**148* A logger.149* @private {goog.log.Logger}150*/151this.logger_ = goog.log.getLogger('goog.module.ModuleManager');152153/**154* Whether the batch mode (i.e. the loading of multiple modules with just one155* request) has been enabled.156* @private {boolean}157*/158this.batchModeEnabled_ = false;159160/**161* Whether the module requests may be sent out of order.162* @private {boolean}163*/164this.concurrentLoadingEnabled_ = false;165166/**167* A loader for the modules that implements loadModules(ids, moduleInfoMap,168* opt_successFn, opt_errorFn, opt_timeoutFn, opt_forceReload) method.169* @private {goog.module.AbstractModuleLoader}170*/171this.loader_ = null;172173// TODO(user): Remove tracer.174/**175* Tracer that measures how long it takes to load a module.176* @private {?number}177*/178this.loadTracer_ = null;179180/**181* The number of consecutive failures that have happened upon module load182* requests.183* @private {number}184*/185this.consecutiveFailures_ = 0;186187/**188* Determines if the module manager was just active before the processing of189* the last data.190* @private {boolean}191*/192this.lastActive_ = false;193194/**195* Determines if the module manager was just user active before the processing196* of the last data. The module manager is user active if any of the197* user-initiated modules are loading or queued up to load.198* @private {boolean}199*/200this.userLastActive_ = false;201202/**203* The module context needed for module initialization.204* @private {Object}205*/206this.moduleContext_ = null;207};208goog.inherits(goog.module.ModuleManager, goog.Disposable);209goog.addSingletonGetter(goog.module.ModuleManager);210211212/**213* The type of callbacks that can be registered with the module manager,.214* @enum {string}215*/216goog.module.ModuleManager.CallbackType = {217/**218* Fired when an error has occurred.219*/220ERROR: 'error',221222/**223* Fired when it becomes idle and has no more module loads to process.224*/225IDLE: 'idle',226227/**228* Fired when it becomes active and has module loads to process.229*/230ACTIVE: 'active',231232/**233* Fired when it becomes idle and has no more user-initiated module loads to234* process.235*/236USER_IDLE: 'userIdle',237238/**239* Fired when it becomes active and has user-initiated module loads to240* process.241*/242USER_ACTIVE: 'userActive'243};244245246/**247* A non-HTTP status code indicating a corruption in loaded module.248* This should be used by a ModuleLoader as a replacement for the HTTP code249* given to the error handler function to indicated that the module was250* corrupted.251* This will set the forceReload flag on the loadModules method when retrying252* module loading.253* @type {number}254*/255goog.module.ModuleManager.CORRUPT_RESPONSE_STATUS_CODE = 8001;256257258/**259* Sets the batch mode as enabled or disabled for the module manager.260* @param {boolean} enabled Whether the batch mode is to be enabled or not.261*/262goog.module.ModuleManager.prototype.setBatchModeEnabled = function(enabled) {263this.batchModeEnabled_ = enabled;264};265266267/**268* Sets the concurrent loading mode as enabled or disabled for the module269* manager. Requires a moduleloader implementation that supports concurrent270* loads. The default {@see goog.module.ModuleLoader} does not.271* @param {boolean} enabled272*/273goog.module.ModuleManager.prototype.setConcurrentLoadingEnabled = function(274enabled) {275this.concurrentLoadingEnabled_ = enabled;276};277278279/**280* Sets the module info for all modules. Should only be called once.281*282* @param {Object<Array<string>>} infoMap An object that contains a mapping283* from module id (String) to list of required module ids (Array).284*/285goog.module.ModuleManager.prototype.setAllModuleInfo = function(infoMap) {286for (var id in infoMap) {287this.moduleInfoMap_[id] = new goog.module.ModuleInfo(infoMap[id], id);288}289if (!this.initialModulesLoaded_.hasFired()) {290this.initialModulesLoaded_.callback();291}292this.maybeFinishBaseLoad_();293};294295296/**297* Sets the module info for all modules. Should only be called once. Also298* marks modules that are currently being loaded.299*300* @param {string=} opt_info A string representation of the module dependency301* graph, in the form: module1:dep1,dep2/module2:dep1,dep2 etc.302* Where depX is the base-36 encoded position of the dep in the module list.303* @param {Array<string>=} opt_loadingModuleIds A list of moduleIds that304* are currently being loaded.305*/306goog.module.ModuleManager.prototype.setAllModuleInfoString = function(307opt_info, opt_loadingModuleIds) {308if (!goog.isString(opt_info)) {309// The call to this method is generated in two steps, the argument is added310// after some of the compilation passes. This means that the initial code311// doesn't have any arguments and causes compiler errors. We make it312// optional to satisfy this constraint.313return;314}315316var modules = opt_info.split('/');317var moduleIds = [];318319// Split the string into the infoMap of id->deps320for (var i = 0; i < modules.length; i++) {321var parts = modules[i].split(':');322var id = parts[0];323var deps;324if (parts[1]) {325deps = parts[1].split(',');326for (var j = 0; j < deps.length; j++) {327var index = parseInt(deps[j], 36);328goog.asserts.assert(329moduleIds[index], 'No module @ %s, dep of %s @ %s', index, id, i);330deps[j] = moduleIds[index];331}332} else {333deps = [];334}335moduleIds.push(id);336this.moduleInfoMap_[id] = new goog.module.ModuleInfo(deps, id);337}338if (opt_loadingModuleIds && opt_loadingModuleIds.length) {339goog.array.extend(this.loadingModuleIds_, opt_loadingModuleIds);340// The last module in the list of initial modules. When it has loaded all341// initial modules have loaded.342this.lastInitialModuleId_ =343/** @type {?string} */ (goog.array.peek(opt_loadingModuleIds));344} else {345if (!this.initialModulesLoaded_.hasFired()) {346this.initialModulesLoaded_.callback();347}348}349this.maybeFinishBaseLoad_();350};351352353/**354* Gets a module info object by id.355* @param {string} id A module identifier.356* @return {!goog.module.ModuleInfo} The module info.357*/358goog.module.ModuleManager.prototype.getModuleInfo = function(id) {359return this.moduleInfoMap_[id];360};361362363/**364* Sets the module uris.365*366* @param {Object} moduleUriMap The map of id/uris pairs for each module.367*/368goog.module.ModuleManager.prototype.setModuleUris = function(moduleUriMap) {369for (var id in moduleUriMap) {370this.moduleInfoMap_[id].setUris(moduleUriMap[id]);371}372};373374375/**376* Gets the application-specific module loader.377* @return {goog.module.AbstractModuleLoader} An object that has a378* loadModules(ids, moduleInfoMap, opt_successFn, opt_errFn,379* opt_timeoutFn, opt_forceReload) method.380*/381goog.module.ModuleManager.prototype.getLoader = function() {382return this.loader_;383};384385386/**387* Sets the application-specific module loader.388* @param {goog.module.AbstractModuleLoader} loader An object that has a389* loadModules(ids, moduleInfoMap, opt_successFn, opt_errFn,390* opt_timeoutFn, opt_forceReload) method.391*/392goog.module.ModuleManager.prototype.setLoader = function(loader) {393this.loader_ = loader;394};395396397/**398* Gets the module context to use to initialize the module.399* @return {Object} The context.400*/401goog.module.ModuleManager.prototype.getModuleContext = function() {402return this.moduleContext_;403};404405406/**407* Sets the module context to use to initialize the module.408* @param {Object} context The context.409*/410goog.module.ModuleManager.prototype.setModuleContext = function(context) {411this.moduleContext_ = context;412this.maybeFinishBaseLoad_();413};414415416/**417* Determines if the ModuleManager is active418* @return {boolean} TRUE iff the ModuleManager is active (i.e., not idle).419*/420goog.module.ModuleManager.prototype.isActive = function() {421return this.loadingModuleIds_.length > 0;422};423424425/**426* Determines if the ModuleManager is user active427* @return {boolean} TRUE iff the ModuleManager is user active (i.e., not idle).428*/429goog.module.ModuleManager.prototype.isUserActive = function() {430return this.userInitiatedLoadingModuleIds_.length > 0;431};432433434/**435* Dispatches an ACTIVE or IDLE event if necessary.436* @private437*/438goog.module.ModuleManager.prototype.dispatchActiveIdleChangeIfNeeded_ =439function() {440var lastActive = this.lastActive_;441var active = this.isActive();442if (active != lastActive) {443this.executeCallbacks_(444active ? goog.module.ModuleManager.CallbackType.ACTIVE :445goog.module.ModuleManager.CallbackType.IDLE);446447// Flip the last active value.448this.lastActive_ = active;449}450451// Check if the module manager is user active i.e., there are user initiated452// modules being loaded or queued up to be loaded.453var userLastActive = this.userLastActive_;454var userActive = this.isUserActive();455if (userActive != userLastActive) {456this.executeCallbacks_(457userActive ? goog.module.ModuleManager.CallbackType.USER_ACTIVE :458goog.module.ModuleManager.CallbackType.USER_IDLE);459460// Flip the last user active value.461this.userLastActive_ = userActive;462}463};464465466/**467* Preloads a module after a short delay.468*469* @param {string} id The id of the module to preload.470* @param {number=} opt_timeout The number of ms to wait before adding the471* module id to the loading queue (defaults to 0 ms). Note that the module472* will be loaded asynchronously regardless of the value of this parameter.473* @return {!goog.async.Deferred} A deferred object.474*/475goog.module.ModuleManager.prototype.preloadModule = function(id, opt_timeout) {476var d = new goog.async.Deferred();477window.setTimeout(478goog.bind(this.addLoadModule_, this, id, d), opt_timeout || 0);479return d;480};481482483/**484* Prefetches a JavaScript module and its dependencies, which means that the485* module will be downloaded, but not evaluated. To complete the module load,486* the caller should also call load or execOnLoad after prefetching the module.487*488* @param {string} id The id of the module to prefetch.489*/490goog.module.ModuleManager.prototype.prefetchModule = function(id) {491var moduleInfo = this.getModuleInfo(id);492if (moduleInfo.isLoaded() || this.isModuleLoading(id)) {493throw Error('Module load already requested: ' + id);494} else if (this.batchModeEnabled_) {495throw Error('Modules prefetching is not supported in batch mode');496} else {497var idWithDeps = this.getNotYetLoadedTransitiveDepIds_(id);498for (var i = 0; i < idWithDeps.length; i++) {499this.loader_.prefetchModule(500idWithDeps[i], this.moduleInfoMap_[idWithDeps[i]]);501}502}503};504505506/**507* Loads a single module for use with a given deferred.508*509* @param {string} id The id of the module to load.510* @param {goog.async.Deferred} d A deferred object.511* @private512*/513goog.module.ModuleManager.prototype.addLoadModule_ = function(id, d) {514var moduleInfo = this.getModuleInfo(id);515if (moduleInfo.isLoaded()) {516d.callback(this.moduleContext_);517return;518}519520this.registerModuleLoadCallbacks_(id, moduleInfo, false, d);521if (!this.isModuleLoading(id)) {522this.loadModulesOrEnqueue_([id]);523}524};525526527/**528* Loads a list of modules or, if some other module is currently being loaded,529* appends the ids to the queue of requested module ids. Registers callbacks a530* module that is currently loading and returns a fired deferred for a module531* that is already loaded.532*533* @param {Array<string>} ids The id of the module to load.534* @param {boolean=} opt_userInitiated If the load is a result of a user action.535* @return {!Object<string, !goog.async.Deferred>} A mapping from id (String)536* to deferred objects that will callback or errback when the load for that537* id is finished.538* @private539*/540goog.module.ModuleManager.prototype.loadModulesOrEnqueueIfNotLoadedOrLoading_ =541function(ids, opt_userInitiated) {542var uniqueIds = [];543goog.array.removeDuplicates(ids, uniqueIds);544var idsToLoad = [];545var deferredMap = {};546for (var i = 0; i < uniqueIds.length; i++) {547var id = uniqueIds[i];548var moduleInfo = this.getModuleInfo(id);549if (!moduleInfo) {550throw new Error('Unknown module: ' + id);551}552var d = new goog.async.Deferred();553deferredMap[id] = d;554if (moduleInfo.isLoaded()) {555d.callback(this.moduleContext_);556} else {557this.registerModuleLoadCallbacks_(id, moduleInfo, !!opt_userInitiated, d);558if (!this.isModuleLoading(id)) {559idsToLoad.push(id);560}561}562}563564// If there are ids to load, load them, otherwise, they are all loading or565// loaded.566if (idsToLoad.length > 0) {567this.loadModulesOrEnqueue_(idsToLoad);568}569return deferredMap;570};571572573/**574* Registers the callbacks and handles logic if it is a user initiated module575* load.576*577* @param {string} id The id of the module to possibly load.578* @param {!goog.module.ModuleInfo} moduleInfo The module identifier for the579* given id.580* @param {boolean} userInitiated If the load was user initiated.581* @param {goog.async.Deferred} d A deferred object.582* @private583*/584goog.module.ModuleManager.prototype.registerModuleLoadCallbacks_ = function(585id, moduleInfo, userInitiated, d) {586moduleInfo.registerCallback(d.callback, d);587moduleInfo.registerErrback(function(err) { d.errback(Error(err)); });588// If it's already loading, we don't have to do anything besides handle589// if it was user initiated590if (this.isModuleLoading(id)) {591if (userInitiated) {592goog.log.info(593this.logger_, 'User initiated module already loading: ' + id);594this.addUserInitiatedLoadingModule_(id);595this.dispatchActiveIdleChangeIfNeeded_();596}597} else {598if (userInitiated) {599goog.log.info(this.logger_, 'User initiated module load: ' + id);600this.addUserInitiatedLoadingModule_(id);601} else {602goog.log.info(this.logger_, 'Initiating module load: ' + id);603}604}605};606607608/**609* Initiates loading of a list of modules or, if a module is currently being610* loaded, appends the modules to the queue of requested module ids.611*612* The caller should verify that the requested modules are not already loaded or613* loading. {@link #loadModulesOrEnqueueIfNotLoadedOrLoading_} is a more lenient614* alternative to this method.615*616* @param {Array<string>} ids The ids of the modules to load.617* @private618*/619goog.module.ModuleManager.prototype.loadModulesOrEnqueue_ = function(ids) {620// With concurrent loading we always just send off the request.621if (this.concurrentLoadingEnabled_) {622// For now we wait for initial modules to have downloaded as this puts the623// loader in a good state for calculating the needed deps of additional624// loads.625// TODO(user): Make this wait unnecessary.626this.initialModulesLoaded_.addCallback(627goog.bind(this.loadModules_, this, ids));628} else {629if (goog.array.isEmpty(this.loadingModuleIds_)) {630this.loadModules_(ids);631} else {632this.requestedModuleIdsQueue_.push(ids);633this.dispatchActiveIdleChangeIfNeeded_();634}635}636};637638639/**640* Gets the amount of delay to wait before sending a request for more modules.641* If a certain module request fails, we backoff a little bit and try again.642* @return {number} Delay, in ms.643* @private644*/645goog.module.ModuleManager.prototype.getBackOff_ = function() {646// 5 seconds after one error, 20 seconds after 2.647return Math.pow(this.consecutiveFailures_, 2) * 5000;648};649650651/**652* Loads a list of modules and any of their not-yet-loaded prerequisites.653* If batch mode is enabled, the prerequisites will be loaded together with the654* requested modules and all requested modules will be loaded at the same time.655*656* The caller should verify that the requested modules are not already loaded657* and that no modules are currently loading before calling this method.658*659* @param {Array<string>} ids The ids of the modules to load.660* @param {boolean=} opt_isRetry If the load is a retry of a previous load661* attempt.662* @param {boolean=} opt_forceReload Whether to bypass cache while loading the663* module.664* @private665*/666goog.module.ModuleManager.prototype.loadModules_ = function(667ids, opt_isRetry, opt_forceReload) {668if (!opt_isRetry) {669this.consecutiveFailures_ = 0;670}671672// Not all modules may be loaded immediately if batch mode is not enabled.673var idsToLoadImmediately = this.processModulesForLoad_(ids);674675goog.log.info(this.logger_, 'Loading module(s): ' + idsToLoadImmediately);676this.loadingModuleIds_ = idsToLoadImmediately;677678if (this.batchModeEnabled_) {679this.requestedLoadingModuleIds_ = ids;680} else {681// If batch mode is disabled, we treat each dependency load as a separate682// load.683this.requestedLoadingModuleIds_ = goog.array.clone(idsToLoadImmediately);684}685686// Dispatch an active/idle change if needed.687this.dispatchActiveIdleChangeIfNeeded_();688689if (goog.array.isEmpty(idsToLoadImmediately)) {690// All requested modules and deps have been either loaded already or have691// already been requested.692return;693}694695this.requestedModuleIds_.push.apply(696this.requestedModuleIds_, idsToLoadImmediately);697698var loadFn = goog.bind(699this.loader_.loadModules, this.loader_,700goog.array.clone(idsToLoadImmediately), this.moduleInfoMap_, null,701goog.bind(702this.handleLoadError_, this, this.requestedLoadingModuleIds_,703idsToLoadImmediately),704goog.bind(this.handleLoadTimeout_, this), !!opt_forceReload);705706var delay = this.getBackOff_();707if (delay) {708window.setTimeout(loadFn, delay);709} else {710loadFn();711}712};713714715/**716* Processes a list of module ids for loading. Checks if any of the modules are717* already loaded and then gets transitive deps. Queues any necessary modules718* if batch mode is not enabled. Returns the list of ids that should be loaded.719*720* @param {Array<string>} ids The ids that need to be loaded.721* @return {!Array<string>} The ids to load, including dependencies.722* @throws {Error} If the module is already loaded.723* @private724*/725goog.module.ModuleManager.prototype.processModulesForLoad_ = function(ids) {726for (var i = 0; i < ids.length; i++) {727var moduleInfo = this.moduleInfoMap_[ids[i]];728if (moduleInfo.isLoaded()) {729throw Error('Module already loaded: ' + ids[i]);730}731}732733// Build a list of the ids of this module and any of its not-yet-loaded734// prerequisite modules in dependency order.735var idsWithDeps = [];736for (var i = 0; i < ids.length; i++) {737idsWithDeps =738idsWithDeps.concat(this.getNotYetLoadedTransitiveDepIds_(ids[i]));739}740goog.array.removeDuplicates(idsWithDeps);741742if (!this.batchModeEnabled_ && idsWithDeps.length > 1) {743var idToLoad = idsWithDeps.shift();744goog.log.info(745this.logger_, 'Must load ' + idToLoad + ' module before ' + ids);746747// Insert the requested module id and any other not-yet-loaded prereqs748// that it has at the front of the queue.749var queuedModules =750goog.array.map(idsWithDeps, function(id) { return [id]; });751this.requestedModuleIdsQueue_ =752queuedModules.concat(this.requestedModuleIdsQueue_);753return [idToLoad];754} else {755return idsWithDeps;756}757};758759760/**761* Builds a list of the ids of the not-yet-loaded modules that a particular762* module transitively depends on, including itself.763*764* @param {string} id The id of a not-yet-loaded module.765* @return {!Array<string>} An array of module ids in dependency order that's766* guaranteed to end with the provided module id.767* @private768*/769goog.module.ModuleManager.prototype.getNotYetLoadedTransitiveDepIds_ = function(770id) {771// NOTE(user): We want the earliest occurrence of a module, not the first772// dependency we find. Therefore we strip duplicates at the end rather than773// during. See the tests for concrete examples.774var ids = [];775if (!goog.array.contains(this.requestedModuleIds_, id)) {776ids.push(id);777}778var depIds = goog.array.clone(this.getModuleInfo(id).getDependencies());779while (depIds.length) {780var depId = depIds.pop();781if (!this.getModuleInfo(depId).isLoaded() &&782!goog.array.contains(this.requestedModuleIds_, depId)) {783ids.unshift(depId);784// We need to process direct dependencies first.785Array.prototype.unshift.apply(786depIds, this.getModuleInfo(depId).getDependencies());787}788}789goog.array.removeDuplicates(ids);790return ids;791};792793794/**795* If we are still loading the base module, consider the load complete.796* @private797*/798goog.module.ModuleManager.prototype.maybeFinishBaseLoad_ = function() {799if (this.currentlyLoadingModule_ == this.baseModuleInfo_) {800this.currentlyLoadingModule_ = null;801var error =802this.baseModuleInfo_.onLoad(goog.bind(this.getModuleContext, this));803if (error) {804this.dispatchModuleLoadFailed_(805goog.module.ModuleManager.FailureType.INIT_ERROR);806}807808this.dispatchActiveIdleChangeIfNeeded_();809}810};811812813/**814* Records that a module was loaded. Also initiates loading the next module if815* any module requests are queued. This method is called by code that is816* generated and appended to each dynamic module's code at compilation time.817*818* @param {string} id A module id.819*/820goog.module.ModuleManager.prototype.setLoaded = function(id) {821if (this.isDisposed()) {822goog.log.warning(823this.logger_, 'Module loaded after module manager was disposed: ' + id);824return;825}826827goog.log.info(this.logger_, 'Module loaded: ' + id);828829var error =830this.moduleInfoMap_[id].onLoad(goog.bind(this.getModuleContext, this));831if (error) {832this.dispatchModuleLoadFailed_(833goog.module.ModuleManager.FailureType.INIT_ERROR);834}835836// Remove the module id from the user initiated set if it existed there.837goog.array.remove(this.userInitiatedLoadingModuleIds_, id);838839// Remove the module id from the loading modules if it exists there.840goog.array.remove(this.loadingModuleIds_, id);841842if (goog.array.isEmpty(this.loadingModuleIds_)) {843// No more modules are currently being loaded (e.g. arriving later in the844// same HTTP response), so proceed to load the next module in the queue.845this.loadNextModules_();846}847848if (this.lastInitialModuleId_ && id == this.lastInitialModuleId_) {849if (!this.initialModulesLoaded_.hasFired()) {850this.initialModulesLoaded_.callback();851}852}853854// Dispatch an active/idle change if needed.855this.dispatchActiveIdleChangeIfNeeded_();856};857858859/**860* Gets whether a module is currently loading or in the queue, waiting to be861* loaded.862* @param {string} id A module id.863* @return {boolean} TRUE iff the module is loading.864*/865goog.module.ModuleManager.prototype.isModuleLoading = function(id) {866if (goog.array.contains(this.loadingModuleIds_, id)) {867return true;868}869for (var i = 0; i < this.requestedModuleIdsQueue_.length; i++) {870if (goog.array.contains(this.requestedModuleIdsQueue_[i], id)) {871return true;872}873}874return false;875};876877878/**879* Requests that a function be called once a particular module is loaded.880* Client code can use this method to safely call into modules that may not yet881* be loaded. For consistency, this method always calls the function882* asynchronously -- even if the module is already loaded. Initiates loading of883* the module if necessary, unless opt_noLoad is true.884*885* @param {string} moduleId A module id.886* @param {Function} fn Function to execute when the module has loaded.887* @param {Object=} opt_handler Optional handler under whose scope to execute888* the callback.889* @param {boolean=} opt_noLoad TRUE iff not to initiate loading of the module.890* @param {boolean=} opt_userInitiated TRUE iff the loading of the module was891* user initiated.892* @param {boolean=} opt_preferSynchronous TRUE iff the function should be893* executed synchronously if the module has already been loaded.894* @return {!goog.module.ModuleLoadCallback} A callback wrapper that exposes895* an abort and execute method.896*/897goog.module.ModuleManager.prototype.execOnLoad = function(898moduleId, fn, opt_handler, opt_noLoad, opt_userInitiated,899opt_preferSynchronous) {900var moduleInfo = this.moduleInfoMap_[moduleId];901var callbackWrapper;902903if (moduleInfo.isLoaded()) {904goog.log.info(this.logger_, moduleId + ' module already loaded');905// Call async so that code paths don't change between loaded and unloaded906// cases.907callbackWrapper = new goog.module.ModuleLoadCallback(fn, opt_handler);908if (opt_preferSynchronous) {909callbackWrapper.execute(this.moduleContext_);910} else {911window.setTimeout(goog.bind(callbackWrapper.execute, callbackWrapper), 0);912}913} else if (this.isModuleLoading(moduleId)) {914goog.log.info(this.logger_, moduleId + ' module already loading');915callbackWrapper = moduleInfo.registerCallback(fn, opt_handler);916if (opt_userInitiated) {917goog.log.info(918this.logger_, 'User initiated module already loading: ' + moduleId);919this.addUserInitiatedLoadingModule_(moduleId);920this.dispatchActiveIdleChangeIfNeeded_();921}922} else {923goog.log.info(this.logger_, 'Registering callback for module: ' + moduleId);924callbackWrapper = moduleInfo.registerCallback(fn, opt_handler);925if (!opt_noLoad) {926if (opt_userInitiated) {927goog.log.info(this.logger_, 'User initiated module load: ' + moduleId);928this.addUserInitiatedLoadingModule_(moduleId);929}930goog.log.info(this.logger_, 'Initiating module load: ' + moduleId);931this.loadModulesOrEnqueue_([moduleId]);932}933}934return callbackWrapper;935};936937938/**939* Loads a module, returning a goog.async.Deferred for keeping track of the940* result.941*942* @param {string} moduleId A module id.943* @param {boolean=} opt_userInitiated If the load is a result of a user action.944* @return {goog.async.Deferred} A deferred object.945*/946goog.module.ModuleManager.prototype.load = function(947moduleId, opt_userInitiated) {948return this.loadModulesOrEnqueueIfNotLoadedOrLoading_(949[moduleId], opt_userInitiated)[moduleId];950};951952953/**954* Loads a list of modules, returning a goog.async.Deferred for keeping track of955* the result.956*957* @param {Array<string>} moduleIds A list of module ids.958* @param {boolean=} opt_userInitiated If the load is a result of a user action.959* @return {!Object<string, !goog.async.Deferred>} A mapping from id (String)960* to deferred objects that will callback or errback when the load for that961* id is finished.962*/963goog.module.ModuleManager.prototype.loadMultiple = function(964moduleIds, opt_userInitiated) {965return this.loadModulesOrEnqueueIfNotLoadedOrLoading_(966moduleIds, opt_userInitiated);967};968969970/**971* Ensures that the module with the given id is listed as a user-initiated972* module that is being loaded. This method guarantees that a module will never973* get listed more than once.974* @param {string} id Identifier of the module.975* @private976*/977goog.module.ModuleManager.prototype.addUserInitiatedLoadingModule_ = function(978id) {979if (!goog.array.contains(this.userInitiatedLoadingModuleIds_, id)) {980this.userInitiatedLoadingModuleIds_.push(id);981}982};983984985/**986* Method called just before a module code is loaded.987* @param {string} id Identifier of the module.988*/989goog.module.ModuleManager.prototype.beforeLoadModuleCode = function(id) {990this.loadTracer_ =991goog.debug.Trace.startTracer('Module Load: ' + id, 'Module Load');992if (this.currentlyLoadingModule_) {993goog.log.error(994this.logger_, 'beforeLoadModuleCode called with module "' + id +995'" while module "' + this.currentlyLoadingModule_.getId() +996'" is loading');997}998this.currentlyLoadingModule_ = this.getModuleInfo(id);999};100010011002/**1003* Method called just after module code is loaded1004* @param {string} id Identifier of the module.1005*/1006goog.module.ModuleManager.prototype.afterLoadModuleCode = function(id) {1007if (!this.currentlyLoadingModule_ ||1008id != this.currentlyLoadingModule_.getId()) {1009goog.log.error(1010this.logger_, 'afterLoadModuleCode called with module "' + id +1011'" while loading module "' +1012(this.currentlyLoadingModule_ &&1013this.currentlyLoadingModule_.getId()) +1014'"');1015}1016this.currentlyLoadingModule_ = null;1017goog.debug.Trace.stopTracer(this.loadTracer_);1018};101910201021/**1022* Register an initialization callback for the currently loading module. This1023* should only be called by script that is executed during the evaluation of1024* a module's javascript. This is almost equivalent to calling the function1025* inline, but ensures that all the code from the currently loading module1026* has been loaded. This makes it cleaner and more robust than calling the1027* function inline.1028*1029* If this function is called from the base module (the one that contains1030* the module manager code), the callback is held until #setAllModuleInfo1031* is called, or until #setModuleContext is called, whichever happens first.1032*1033* @param {Function} fn A callback function that takes a single argument1034* which is the module context.1035* @param {Object=} opt_handler Optional handler under whose scope to execute1036* the callback.1037*/1038goog.module.ModuleManager.prototype.registerInitializationCallback = function(1039fn, opt_handler) {1040if (!this.currentlyLoadingModule_) {1041goog.log.error(this.logger_, 'No module is currently loading');1042} else {1043this.currentlyLoadingModule_.registerEarlyCallback(fn, opt_handler);1044}1045};104610471048/**1049* Register a late initialization callback for the currently loading module.1050* Callbacks registered via this function are executed similar to1051* {@see registerInitializationCallback}, but they are fired after all1052* initialization callbacks are called.1053*1054* @param {Function} fn A callback function that takes a single argument1055* which is the module context.1056* @param {Object=} opt_handler Optional handler under whose scope to execute1057* the callback.1058*/1059goog.module.ModuleManager.prototype.registerLateInitializationCallback =1060function(fn, opt_handler) {1061if (!this.currentlyLoadingModule_) {1062goog.log.error(this.logger_, 'No module is currently loading');1063} else {1064this.currentlyLoadingModule_.registerCallback(fn, opt_handler);1065}1066};106710681069/**1070* Sets the constructor to use for the module object for the currently1071* loading module. The constructor should derive from1072* {@see goog.module.BaseModule}.1073* @param {Function} fn The constructor function.1074*/1075goog.module.ModuleManager.prototype.setModuleConstructor = function(fn) {1076if (!this.currentlyLoadingModule_) {1077goog.log.error(this.logger_, 'No module is currently loading');1078return;1079}1080this.currentlyLoadingModule_.setModuleConstructor(fn);1081};108210831084/**1085* The possible reasons for a module load failure callback being fired.1086* @enum {number}1087*/1088goog.module.ModuleManager.FailureType = {1089/** 401 Status. */1090UNAUTHORIZED: 0,10911092/** Error status (not 401) returned multiple times. */1093CONSECUTIVE_FAILURES: 1,10941095/** Request timeout. */1096TIMEOUT: 2,10971098/** 410 status, old code gone. */1099OLD_CODE_GONE: 3,11001101/** The onLoad callbacks failed. */1102INIT_ERROR: 41103};110411051106/**1107* Handles a module load failure.1108*1109* @param {!Array<string>} requestedLoadingModuleIds Modules ids that were1110* requested in failed request. Does not included calculated dependencies.1111* @param {!Array<string>} requestedModuleIdsWithDeps All module ids requested1112* in the failed request including all dependencies.1113* @param {?number} status The error status.1114* @private1115*/1116goog.module.ModuleManager.prototype.handleLoadError_ = function(1117requestedLoadingModuleIds, requestedModuleIdsWithDeps, status) {1118this.consecutiveFailures_++;1119// Module manager was not designed to be reentrant. Reinstate the instance1120// var with actual value when request failed (Other requests may have1121// started already.)1122this.requestedLoadingModuleIds_ = requestedLoadingModuleIds;1123// Pretend we never requested the failed modules.1124goog.array.forEach(1125requestedModuleIdsWithDeps,1126goog.partial(goog.array.remove, this.requestedModuleIds_), this);11271128if (status == 401) {1129// The user is not logged in. They've cleared their cookies or logged out1130// from another window.1131goog.log.info(this.logger_, 'Module loading unauthorized');1132this.dispatchModuleLoadFailed_(1133goog.module.ModuleManager.FailureType.UNAUTHORIZED);1134// Drop any additional module requests.1135this.requestedModuleIdsQueue_.length = 0;1136} else if (status == 410) {1137// The requested module js is old and not available.1138this.requeueBatchOrDispatchFailure_(1139goog.module.ModuleManager.FailureType.OLD_CODE_GONE);1140this.loadNextModules_();1141} else if (this.consecutiveFailures_ >= 3) {1142goog.log.info(1143this.logger_,1144'Aborting after failure to load: ' + this.loadingModuleIds_);1145this.requeueBatchOrDispatchFailure_(1146goog.module.ModuleManager.FailureType.CONSECUTIVE_FAILURES);1147this.loadNextModules_();1148} else {1149goog.log.info(1150this.logger_,1151'Retrying after failure to load: ' + this.loadingModuleIds_);1152var forceReload =1153status == goog.module.ModuleManager.CORRUPT_RESPONSE_STATUS_CODE;1154this.loadModules_(this.requestedLoadingModuleIds_, true, forceReload);1155}1156};115711581159/**1160* Handles a module load timeout.1161* @private1162*/1163goog.module.ModuleManager.prototype.handleLoadTimeout_ = function() {1164goog.log.info(1165this.logger_, 'Aborting after timeout: ' + this.loadingModuleIds_);1166this.requeueBatchOrDispatchFailure_(1167goog.module.ModuleManager.FailureType.TIMEOUT);1168this.loadNextModules_();1169};117011711172/**1173* Requeues batch loads that had more than one requested module1174* (i.e. modules that were not included as dependencies) as separate loads or1175* if there was only one requested module, fails that module with the received1176* cause.1177* @param {goog.module.ModuleManager.FailureType} cause The reason for the1178* failure.1179* @private1180*/1181goog.module.ModuleManager.prototype.requeueBatchOrDispatchFailure_ = function(1182cause) {1183// The load failed, so if there are more than one requested modules, then we1184// need to retry each one as a separate load. Otherwise, if there is only one1185// requested module, remove it and its dependencies from the queue.1186if (this.requestedLoadingModuleIds_.length > 1) {1187var queuedModules = goog.array.map(1188this.requestedLoadingModuleIds_, function(id) { return [id]; });1189this.requestedModuleIdsQueue_ =1190queuedModules.concat(this.requestedModuleIdsQueue_);1191} else {1192this.dispatchModuleLoadFailed_(cause);1193}1194};119511961197/**1198* Handles when a module load failed.1199* @param {goog.module.ModuleManager.FailureType} cause The reason for the1200* failure.1201* @private1202*/1203goog.module.ModuleManager.prototype.dispatchModuleLoadFailed_ = function(1204cause) {1205var failedIds = this.requestedLoadingModuleIds_;1206this.loadingModuleIds_.length = 0;1207// If any pending modules depend on the id that failed,1208// they need to be removed from the queue.1209var idsToCancel = [];1210for (var i = 0; i < this.requestedModuleIdsQueue_.length; i++) {1211var dependentModules = goog.array.filter(1212this.requestedModuleIdsQueue_[i],1213/**1214* Returns true if the requestedId has dependencies on the modules that1215* just failed to load.1216* @param {string} requestedId The module to check for dependencies.1217* @return {boolean} True if the module depends on failed modules.1218*/1219function(requestedId) {1220var requestedDeps =1221this.getNotYetLoadedTransitiveDepIds_(requestedId);1222return goog.array.some(failedIds, function(id) {1223return goog.array.contains(requestedDeps, id);1224});1225},1226this);1227goog.array.extend(idsToCancel, dependentModules);1228}12291230// Also insert the ids that failed to load as ids to cancel.1231for (var i = 0; i < failedIds.length; i++) {1232goog.array.insert(idsToCancel, failedIds[i]);1233}12341235// Remove ids to cancel from the queues.1236for (var i = 0; i < idsToCancel.length; i++) {1237for (var j = 0; j < this.requestedModuleIdsQueue_.length; j++) {1238goog.array.remove(this.requestedModuleIdsQueue_[j], idsToCancel[i]);1239}1240goog.array.remove(this.userInitiatedLoadingModuleIds_, idsToCancel[i]);1241}12421243// Call the functions for error notification.1244var errorCallbacks =1245this.callbackMap_[goog.module.ModuleManager.CallbackType.ERROR];1246if (errorCallbacks) {1247for (var i = 0; i < errorCallbacks.length; i++) {1248var callback = errorCallbacks[i];1249for (var j = 0; j < idsToCancel.length; j++) {1250callback(1251goog.module.ModuleManager.CallbackType.ERROR, idsToCancel[j],1252cause);1253}1254}1255}12561257// Call the errbacks on the module info.1258for (var i = 0; i < failedIds.length; i++) {1259if (this.moduleInfoMap_[failedIds[i]]) {1260this.moduleInfoMap_[failedIds[i]].onError(cause);1261}1262}12631264// Clear the requested loading module ids.1265this.requestedLoadingModuleIds_.length = 0;12661267this.dispatchActiveIdleChangeIfNeeded_();1268};126912701271/**1272* Loads the next modules on the queue.1273* @private1274*/1275goog.module.ModuleManager.prototype.loadNextModules_ = function() {1276while (this.requestedModuleIdsQueue_.length) {1277// Remove modules that are already loaded.1278var nextIds = goog.array.filter(1279this.requestedModuleIdsQueue_.shift(),1280function(id) { return !this.getModuleInfo(id).isLoaded(); }, this);1281if (nextIds.length > 0) {1282this.loadModules_(nextIds);1283return;1284}1285}12861287// Dispatch an active/idle change if needed.1288this.dispatchActiveIdleChangeIfNeeded_();1289};129012911292/**1293* The function to call if the module manager is in error.1294* @param1295* {goog.module.ModuleManager.CallbackType|Array<goog.module.ModuleManager.CallbackType>}1296* types1297* The callback type.1298* @param {Function} fn The function to register as a callback.1299*/1300goog.module.ModuleManager.prototype.registerCallback = function(types, fn) {1301if (!goog.isArray(types)) {1302types = [types];1303}13041305for (var i = 0; i < types.length; i++) {1306this.registerCallback_(types[i], fn);1307}1308};130913101311/**1312* Register a callback for the specified callback type.1313* @param {goog.module.ModuleManager.CallbackType} type The callback type.1314* @param {Function} fn The callback function.1315* @private1316*/1317goog.module.ModuleManager.prototype.registerCallback_ = function(type, fn) {1318var callbackMap = this.callbackMap_;1319if (!callbackMap[type]) {1320callbackMap[type] = [];1321}1322callbackMap[type].push(fn);1323};132413251326/**1327* Call the callback functions of the specified type.1328* @param {goog.module.ModuleManager.CallbackType} type The callback type.1329* @private1330*/1331goog.module.ModuleManager.prototype.executeCallbacks_ = function(type) {1332var callbacks = this.callbackMap_[type];1333for (var i = 0; callbacks && i < callbacks.length; i++) {1334callbacks[i](type);1335}1336};133713381339/** @override */1340goog.module.ModuleManager.prototype.disposeInternal = function() {1341goog.module.ModuleManager.base(this, 'disposeInternal');13421343// Dispose of each ModuleInfo object.1344goog.disposeAll(1345goog.object.getValues(this.moduleInfoMap_), this.baseModuleInfo_);1346this.moduleInfoMap_ = null;1347this.loadingModuleIds_ = null;1348this.requestedLoadingModuleIds_ = null;1349this.userInitiatedLoadingModuleIds_ = null;1350this.requestedModuleIdsQueue_ = null;1351this.callbackMap_ = null;1352};135313541355