1371 lines
43 KiB
JavaScript
1371 lines
43 KiB
JavaScript
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS-IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
/**
|
|
* @fileoverview A singleton object for managing Javascript code modules.
|
|
*
|
|
*/
|
|
|
|
goog.provide('goog.module.ModuleManager');
|
|
goog.provide('goog.module.ModuleManager.CallbackType');
|
|
goog.provide('goog.module.ModuleManager.FailureType');
|
|
|
|
goog.require('goog.Disposable');
|
|
goog.require('goog.array');
|
|
goog.require('goog.asserts');
|
|
goog.require('goog.async.Deferred');
|
|
goog.require('goog.debug.Trace');
|
|
/** @suppress {extraRequire} */
|
|
goog.require('goog.dispose');
|
|
goog.require('goog.log');
|
|
goog.require('goog.module.ModuleInfo');
|
|
goog.require('goog.module.ModuleLoadCallback');
|
|
goog.require('goog.object');
|
|
|
|
|
|
|
|
/**
|
|
* The ModuleManager keeps track of all modules in the environment.
|
|
* Since modules may not have their code loaded, we must keep track of them.
|
|
* @constructor
|
|
* @extends {goog.Disposable}
|
|
*/
|
|
goog.module.ModuleManager = function() {
|
|
goog.Disposable.call(this);
|
|
|
|
/**
|
|
* A mapping from module id to ModuleInfo object.
|
|
* @type {Object}
|
|
* @private
|
|
*/
|
|
this.moduleInfoMap_ = {};
|
|
|
|
// TODO (malteubl): Switch this to a reentrant design.
|
|
/**
|
|
* The ids of the currently loading modules. If batch mode is disabled, then
|
|
* this array will never contain more than one element at a time.
|
|
* @type {Array.<string>}
|
|
* @private
|
|
*/
|
|
this.loadingModuleIds_ = [];
|
|
|
|
/**
|
|
* The requested ids of the currently loading modules. This does not include
|
|
* module dependencies that may also be loading.
|
|
* @type {Array.<string>}
|
|
* @private
|
|
*/
|
|
this.requestedLoadingModuleIds_ = [];
|
|
|
|
// TODO(user): Make these and other arrays that are used as sets be
|
|
// actual sets.
|
|
/**
|
|
* All module ids that have ever been requested. In concurrent loading these
|
|
* are the ones to subtract from future requests.
|
|
* @type {!Array.<string>}
|
|
* @private
|
|
*/
|
|
this.requestedModuleIds_ = [];
|
|
|
|
/**
|
|
* A queue of the ids of requested but not-yet-loaded modules. The zero
|
|
* position is the front of the queue. This is a 2-D array to group modules
|
|
* together with other modules that should be batch loaded with them, if
|
|
* batch loading is enabled.
|
|
* @type {Array.<Array.<string>>}
|
|
* @private
|
|
*/
|
|
this.requestedModuleIdsQueue_ = [];
|
|
|
|
/**
|
|
* The ids of the currently loading modules which have been initiated by user
|
|
* actions.
|
|
* @type {Array.<string>}
|
|
* @private
|
|
*/
|
|
this.userInitiatedLoadingModuleIds_ = [];
|
|
|
|
/**
|
|
* A map of callback types to the functions to call for the specified
|
|
* callback type.
|
|
* @type {Object}
|
|
* @private
|
|
*/
|
|
this.callbackMap_ = {};
|
|
|
|
/**
|
|
* Module info for the base module (the one that contains the module
|
|
* manager code), which we set as the loading module so one can
|
|
* register initialization callbacks in the base module.
|
|
*
|
|
* The base module is considered loaded when #setAllModuleInfo is called or
|
|
* #setModuleContext is called, whichever comes first.
|
|
*
|
|
* @type {goog.module.ModuleInfo}
|
|
* @private
|
|
*/
|
|
this.baseModuleInfo_ = new goog.module.ModuleInfo([], '');
|
|
|
|
/**
|
|
* The module that is currently loading, or null if not loading anything.
|
|
* @type {goog.module.ModuleInfo}
|
|
* @private
|
|
*/
|
|
this.currentlyLoadingModule_ = this.baseModuleInfo_;
|
|
|
|
/**
|
|
* The id of the last requested initial module. When it loaded
|
|
* the deferred in {@code this.initialModulesLoaded_} resolves.
|
|
* @private {?string}
|
|
*/
|
|
this.lastInitialModuleId_ = null;
|
|
|
|
/**
|
|
* Deferred for when all initial modules have loaded. We currently block
|
|
* sending additional module requests until this deferred resolves. In a
|
|
* future optimization it may be possible to use the initial modules as
|
|
* seeds for the module loader "requested module ids" and start making new
|
|
* requests even sooner.
|
|
* @private {!goog.async.Deferred}
|
|
*/
|
|
this.initialModulesLoaded_ = new goog.async.Deferred();
|
|
};
|
|
goog.inherits(goog.module.ModuleManager, goog.Disposable);
|
|
goog.addSingletonGetter(goog.module.ModuleManager);
|
|
|
|
|
|
/**
|
|
* The type of callbacks that can be registered with the module manager,.
|
|
* @enum {string}
|
|
*/
|
|
goog.module.ModuleManager.CallbackType = {
|
|
/**
|
|
* Fired when an error has occurred.
|
|
*/
|
|
ERROR: 'error',
|
|
|
|
/**
|
|
* Fired when it becomes idle and has no more module loads to process.
|
|
*/
|
|
IDLE: 'idle',
|
|
|
|
/**
|
|
* Fired when it becomes active and has module loads to process.
|
|
*/
|
|
ACTIVE: 'active',
|
|
|
|
/**
|
|
* Fired when it becomes idle and has no more user-initiated module loads to
|
|
* process.
|
|
*/
|
|
USER_IDLE: 'userIdle',
|
|
|
|
/**
|
|
* Fired when it becomes active and has user-initiated module loads to
|
|
* process.
|
|
*/
|
|
USER_ACTIVE: 'userActive'
|
|
};
|
|
|
|
|
|
/**
|
|
* A non-HTTP status code indicating a corruption in loaded module.
|
|
* This should be used by a ModuleLoader as a replacement for the HTTP code
|
|
* given to the error handler function to indicated that the module was
|
|
* corrupted.
|
|
* This will set the forceReload flag on the loadModules method when retrying
|
|
* module loading.
|
|
* @type {number}
|
|
*/
|
|
goog.module.ModuleManager.CORRUPT_RESPONSE_STATUS_CODE = 8001;
|
|
|
|
|
|
/**
|
|
* A logger.
|
|
* @type {goog.log.Logger}
|
|
* @private
|
|
*/
|
|
goog.module.ModuleManager.prototype.logger_ = goog.log.getLogger(
|
|
'goog.module.ModuleManager');
|
|
|
|
|
|
/**
|
|
* Whether the batch mode (i.e. the loading of multiple modules with just one
|
|
* request) has been enabled.
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
goog.module.ModuleManager.prototype.batchModeEnabled_ = false;
|
|
|
|
|
|
/**
|
|
* Whether the module requests may be sent out of order.
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
goog.module.ModuleManager.prototype.concurrentLoadingEnabled_ = false;
|
|
|
|
|
|
/**
|
|
* A loader for the modules that implements loadModules(ids, moduleInfoMap,
|
|
* opt_successFn, opt_errorFn, opt_timeoutFn, opt_forceReload) method.
|
|
* @type {goog.module.AbstractModuleLoader}
|
|
* @private
|
|
*/
|
|
goog.module.ModuleManager.prototype.loader_ = null;
|
|
|
|
|
|
// TODO(user): Remove tracer.
|
|
/**
|
|
* Tracer that measures how long it takes to load a module.
|
|
* @type {?number}
|
|
* @private
|
|
*/
|
|
goog.module.ModuleManager.prototype.loadTracer_ = null;
|
|
|
|
|
|
/**
|
|
* The number of consecutive failures that have happened upon module load
|
|
* requests.
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
goog.module.ModuleManager.prototype.consecutiveFailures_ = 0;
|
|
|
|
|
|
/**
|
|
* Determines if the module manager was just active before the processing of
|
|
* the last data.
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
goog.module.ModuleManager.prototype.lastActive_ = false;
|
|
|
|
|
|
/**
|
|
* Determines if the module manager was just user active before the processing
|
|
* of the last data. The module manager is user active if any of the
|
|
* user-initiated modules are loading or queued up to load.
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
goog.module.ModuleManager.prototype.userLastActive_ = false;
|
|
|
|
|
|
/**
|
|
* The module context needed for module initialization.
|
|
* @type {Object}
|
|
* @private
|
|
*/
|
|
goog.module.ModuleManager.prototype.moduleContext_ = null;
|
|
|
|
|
|
/**
|
|
* Sets the batch mode as enabled or disabled for the module manager.
|
|
* @param {boolean} enabled Whether the batch mode is to be enabled or not.
|
|
*/
|
|
goog.module.ModuleManager.prototype.setBatchModeEnabled = function(
|
|
enabled) {
|
|
this.batchModeEnabled_ = enabled;
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the concurrent loading mode as enabled or disabled for the module
|
|
* manager. Requires a moduleloader implementation that supports concurrent
|
|
* loads. The default {@see goog.module.ModuleLoader} does not.
|
|
* @param {boolean} enabled
|
|
*/
|
|
goog.module.ModuleManager.prototype.setConcurrentLoadingEnabled = function(
|
|
enabled) {
|
|
this.concurrentLoadingEnabled_ = enabled;
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the module info for all modules. Should only be called once.
|
|
*
|
|
* @param {Object.<Array.<string>>} infoMap An object that contains a mapping
|
|
* from module id (String) to list of required module ids (Array).
|
|
*/
|
|
goog.module.ModuleManager.prototype.setAllModuleInfo = function(infoMap) {
|
|
for (var id in infoMap) {
|
|
this.moduleInfoMap_[id] = new goog.module.ModuleInfo(infoMap[id], id);
|
|
}
|
|
if (!this.initialModulesLoaded_.hasFired()) {
|
|
this.initialModulesLoaded_.callback();
|
|
}
|
|
this.maybeFinishBaseLoad_();
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the module info for all modules. Should only be called once. Also
|
|
* marks modules that are currently being loaded.
|
|
*
|
|
* @param {string=} opt_info A string representation of the module dependency
|
|
* graph, in the form: module1:dep1,dep2/module2:dep1,dep2 etc.
|
|
* Where depX is the base-36 encoded position of the dep in the module list.
|
|
* @param {Array.<string>=} opt_loadingModuleIds A list of moduleIds that
|
|
* are currently being loaded.
|
|
*/
|
|
goog.module.ModuleManager.prototype.setAllModuleInfoString = function(
|
|
opt_info, opt_loadingModuleIds) {
|
|
if (!goog.isString(opt_info)) {
|
|
// The call to this method is generated in two steps, the argument is added
|
|
// after some of the compilation passes. This means that the initial code
|
|
// doesn't have any arguments and causes compiler errors. We make it
|
|
// optional to satisfy this constraint.
|
|
return;
|
|
}
|
|
|
|
var modules = opt_info.split('/');
|
|
var moduleIds = [];
|
|
|
|
// Split the string into the infoMap of id->deps
|
|
for (var i = 0; i < modules.length; i++) {
|
|
var parts = modules[i].split(':');
|
|
var id = parts[0];
|
|
var deps;
|
|
if (parts[1]) {
|
|
deps = parts[1].split(',');
|
|
for (var j = 0; j < deps.length; j++) {
|
|
var index = parseInt(deps[j], 36);
|
|
goog.asserts.assert(
|
|
moduleIds[index], 'No module @ %s, dep of %s @ %s', index, id, i);
|
|
deps[j] = moduleIds[index];
|
|
}
|
|
} else {
|
|
deps = [];
|
|
}
|
|
moduleIds.push(id);
|
|
this.moduleInfoMap_[id] = new goog.module.ModuleInfo(deps, id);
|
|
}
|
|
if (opt_loadingModuleIds && opt_loadingModuleIds.length) {
|
|
goog.array.extend(this.loadingModuleIds_, opt_loadingModuleIds);
|
|
// The last module in the list of initial modules. When it has loaded all
|
|
// initial modules have loaded.
|
|
this.lastInitialModuleId_ = /** @type {?string} */ (
|
|
goog.array.peek(opt_loadingModuleIds));
|
|
} else {
|
|
if (!this.initialModulesLoaded_.hasFired()) {
|
|
this.initialModulesLoaded_.callback();
|
|
}
|
|
}
|
|
this.maybeFinishBaseLoad_();
|
|
};
|
|
|
|
|
|
/**
|
|
* Gets a module info object by id.
|
|
* @param {string} id A module identifier.
|
|
* @return {goog.module.ModuleInfo} The module info.
|
|
*/
|
|
goog.module.ModuleManager.prototype.getModuleInfo = function(id) {
|
|
return this.moduleInfoMap_[id];
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the module uris.
|
|
*
|
|
* @param {Object} moduleUriMap The map of id/uris pairs for each module.
|
|
*/
|
|
goog.module.ModuleManager.prototype.setModuleUris = function(moduleUriMap) {
|
|
for (var id in moduleUriMap) {
|
|
this.moduleInfoMap_[id].setUris(moduleUriMap[id]);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Gets the application-specific module loader.
|
|
* @return {goog.module.AbstractModuleLoader} An object that has a
|
|
* loadModules(ids, moduleInfoMap, opt_successFn, opt_errFn,
|
|
* opt_timeoutFn, opt_forceReload) method.
|
|
*/
|
|
goog.module.ModuleManager.prototype.getLoader = function() {
|
|
return this.loader_;
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the application-specific module loader.
|
|
* @param {goog.module.AbstractModuleLoader} loader An object that has a
|
|
* loadModules(ids, moduleInfoMap, opt_successFn, opt_errFn,
|
|
* opt_timeoutFn, opt_forceReload) method.
|
|
*/
|
|
goog.module.ModuleManager.prototype.setLoader = function(loader) {
|
|
this.loader_ = loader;
|
|
};
|
|
|
|
|
|
/**
|
|
* Gets the module context to use to initialize the module.
|
|
* @return {Object} The context.
|
|
*/
|
|
goog.module.ModuleManager.prototype.getModuleContext = function() {
|
|
return this.moduleContext_;
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the module context to use to initialize the module.
|
|
* @param {Object} context The context.
|
|
*/
|
|
goog.module.ModuleManager.prototype.setModuleContext = function(context) {
|
|
this.moduleContext_ = context;
|
|
this.maybeFinishBaseLoad_();
|
|
};
|
|
|
|
|
|
/**
|
|
* Determines if the ModuleManager is active
|
|
* @return {boolean} TRUE iff the ModuleManager is active (i.e., not idle).
|
|
*/
|
|
goog.module.ModuleManager.prototype.isActive = function() {
|
|
return this.loadingModuleIds_.length > 0;
|
|
};
|
|
|
|
|
|
/**
|
|
* Determines if the ModuleManager is user active
|
|
* @return {boolean} TRUE iff the ModuleManager is user active (i.e., not idle).
|
|
*/
|
|
goog.module.ModuleManager.prototype.isUserActive = function() {
|
|
return this.userInitiatedLoadingModuleIds_.length > 0;
|
|
};
|
|
|
|
|
|
/**
|
|
* Dispatches an ACTIVE or IDLE event if necessary.
|
|
* @private
|
|
*/
|
|
goog.module.ModuleManager.prototype.dispatchActiveIdleChangeIfNeeded_ =
|
|
function() {
|
|
var lastActive = this.lastActive_;
|
|
var active = this.isActive();
|
|
if (active != lastActive) {
|
|
this.executeCallbacks_(active ?
|
|
goog.module.ModuleManager.CallbackType.ACTIVE :
|
|
goog.module.ModuleManager.CallbackType.IDLE);
|
|
|
|
// Flip the last active value.
|
|
this.lastActive_ = active;
|
|
}
|
|
|
|
// Check if the module manager is user active i.e., there are user initiated
|
|
// modules being loaded or queued up to be loaded.
|
|
var userLastActive = this.userLastActive_;
|
|
var userActive = this.isUserActive();
|
|
if (userActive != userLastActive) {
|
|
this.executeCallbacks_(userActive ?
|
|
goog.module.ModuleManager.CallbackType.USER_ACTIVE :
|
|
goog.module.ModuleManager.CallbackType.USER_IDLE);
|
|
|
|
// Flip the last user active value.
|
|
this.userLastActive_ = userActive;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Preloads a module after a short delay.
|
|
*
|
|
* @param {string} id The id of the module to preload.
|
|
* @param {number=} opt_timeout The number of ms to wait before adding the
|
|
* module id to the loading queue (defaults to 0 ms). Note that the module
|
|
* will be loaded asynchronously regardless of the value of this parameter.
|
|
* @return {goog.async.Deferred} A deferred object.
|
|
*/
|
|
goog.module.ModuleManager.prototype.preloadModule = function(
|
|
id, opt_timeout) {
|
|
var d = new goog.async.Deferred();
|
|
window.setTimeout(
|
|
goog.bind(this.addLoadModule_, this, id, d),
|
|
opt_timeout || 0);
|
|
return d;
|
|
};
|
|
|
|
|
|
/**
|
|
* Prefetches a JavaScript module and its dependencies, which means that the
|
|
* module will be downloaded, but not evaluated. To complete the module load,
|
|
* the caller should also call load or execOnLoad after prefetching the module.
|
|
*
|
|
* @param {string} id The id of the module to prefetch.
|
|
*/
|
|
goog.module.ModuleManager.prototype.prefetchModule = function(id) {
|
|
var moduleInfo = this.getModuleInfo(id);
|
|
if (moduleInfo.isLoaded() || this.isModuleLoading(id)) {
|
|
throw Error('Module load already requested: ' + id);
|
|
} else if (this.batchModeEnabled_) {
|
|
throw Error('Modules prefetching is not supported in batch mode');
|
|
} else {
|
|
var idWithDeps = this.getNotYetLoadedTransitiveDepIds_(id);
|
|
for (var i = 0; i < idWithDeps.length; i++) {
|
|
this.loader_.prefetchModule(idWithDeps[i],
|
|
this.moduleInfoMap_[idWithDeps[i]]);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Loads a single module for use with a given deferred.
|
|
*
|
|
* @param {string} id The id of the module to load.
|
|
* @param {goog.async.Deferred} d A deferred object.
|
|
* @private
|
|
*/
|
|
goog.module.ModuleManager.prototype.addLoadModule_ = function(id, d) {
|
|
var moduleInfo = this.getModuleInfo(id);
|
|
if (moduleInfo.isLoaded()) {
|
|
d.callback(this.moduleContext_);
|
|
return;
|
|
}
|
|
|
|
this.registerModuleLoadCallbacks_(id, moduleInfo, false, d);
|
|
if (!this.isModuleLoading(id)) {
|
|
this.loadModulesOrEnqueue_([id]);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Loads a list of modules or, if some other module is currently being loaded,
|
|
* appends the ids to the queue of requested module ids. Registers callbacks a
|
|
* module that is currently loading and returns a fired deferred for a module
|
|
* that is already loaded.
|
|
*
|
|
* @param {Array.<string>} ids The id of the module to load.
|
|
* @param {boolean=} opt_userInitiated If the load is a result of a user action.
|
|
* @return {Object.<!goog.async.Deferred>} A mapping from id (String) to
|
|
* deferred objects that will callback or errback when the load for that
|
|
* id is finished.
|
|
* @private
|
|
*/
|
|
goog.module.ModuleManager.prototype.loadModulesOrEnqueueIfNotLoadedOrLoading_ =
|
|
function(ids, opt_userInitiated) {
|
|
var uniqueIds = [];
|
|
goog.array.removeDuplicates(ids, uniqueIds);
|
|
var idsToLoad = [];
|
|
var deferredMap = {};
|
|
for (var i = 0; i < uniqueIds.length; i++) {
|
|
var id = uniqueIds[i];
|
|
var moduleInfo = this.getModuleInfo(id);
|
|
goog.asserts.assertObject(moduleInfo, 'Unknown module: ' + id);
|
|
var d = new goog.async.Deferred();
|
|
deferredMap[id] = d;
|
|
if (moduleInfo.isLoaded()) {
|
|
d.callback(this.moduleContext_);
|
|
} else {
|
|
this.registerModuleLoadCallbacks_(id, moduleInfo, !!opt_userInitiated, d);
|
|
if (!this.isModuleLoading(id)) {
|
|
idsToLoad.push(id);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If there are ids to load, load them, otherwise, they are all loading or
|
|
// loaded.
|
|
if (idsToLoad.length > 0) {
|
|
this.loadModulesOrEnqueue_(idsToLoad);
|
|
}
|
|
return deferredMap;
|
|
};
|
|
|
|
|
|
/**
|
|
* Registers the callbacks and handles logic if it is a user initiated module
|
|
* load.
|
|
*
|
|
* @param {string} id The id of the module to possibly load.
|
|
* @param {!goog.module.ModuleInfo} moduleInfo The module identifier for the
|
|
* given id.
|
|
* @param {boolean} userInitiated If the load was user initiated.
|
|
* @param {goog.async.Deferred} d A deferred object.
|
|
* @private
|
|
*/
|
|
goog.module.ModuleManager.prototype.registerModuleLoadCallbacks_ =
|
|
function(id, moduleInfo, userInitiated, d) {
|
|
moduleInfo.registerCallback(d.callback, d);
|
|
moduleInfo.registerErrback(function(err) { d.errback(Error(err)); });
|
|
// If it's already loading, we don't have to do anything besides handle
|
|
// if it was user initiated
|
|
if (this.isModuleLoading(id)) {
|
|
if (userInitiated) {
|
|
goog.log.info(this.logger_,
|
|
'User initiated module already loading: ' + id);
|
|
this.addUserInitiatedLoadingModule_(id);
|
|
this.dispatchActiveIdleChangeIfNeeded_();
|
|
}
|
|
} else {
|
|
if (userInitiated) {
|
|
goog.log.info(this.logger_, 'User initiated module load: ' + id);
|
|
this.addUserInitiatedLoadingModule_(id);
|
|
} else {
|
|
goog.log.info(this.logger_, 'Initiating module load: ' + id);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Initiates loading of a list of modules or, if a module is currently being
|
|
* loaded, appends the modules to the queue of requested module ids.
|
|
*
|
|
* The caller should verify that the requested modules are not already loaded or
|
|
* loading. {@link #loadModulesOrEnqueueIfNotLoadedOrLoading_} is a more lenient
|
|
* alternative to this method.
|
|
*
|
|
* @param {Array.<string>} ids The ids of the modules to load.
|
|
* @private
|
|
*/
|
|
goog.module.ModuleManager.prototype.loadModulesOrEnqueue_ = function(ids) {
|
|
// With concurrent loading we always just send off the request.
|
|
if (this.concurrentLoadingEnabled_) {
|
|
// For now we wait for initial modules to have downloaded as this puts the
|
|
// loader in a good state for calculating the needed deps of additional
|
|
// loads.
|
|
// TODO(user): Make this wait unnecessary.
|
|
this.initialModulesLoaded_.addCallback(
|
|
goog.bind(this.loadModules_, this, ids));
|
|
} else {
|
|
if (goog.array.isEmpty(this.loadingModuleIds_)) {
|
|
this.loadModules_(ids);
|
|
} else {
|
|
this.requestedModuleIdsQueue_.push(ids);
|
|
this.dispatchActiveIdleChangeIfNeeded_();
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Gets the amount of delay to wait before sending a request for more modules.
|
|
* If a certain module request fails, we backoff a little bit and try again.
|
|
* @return {number} Delay, in ms.
|
|
* @private
|
|
*/
|
|
goog.module.ModuleManager.prototype.getBackOff_ = function() {
|
|
// 5 seconds after one error, 20 seconds after 2.
|
|
return Math.pow(this.consecutiveFailures_, 2) * 5000;
|
|
};
|
|
|
|
|
|
/**
|
|
* Loads a list of modules and any of their not-yet-loaded prerequisites.
|
|
* If batch mode is enabled, the prerequisites will be loaded together with the
|
|
* requested modules and all requested modules will be loaded at the same time.
|
|
*
|
|
* The caller should verify that the requested modules are not already loaded
|
|
* and that no modules are currently loading before calling this method.
|
|
*
|
|
* @param {Array.<string>} ids The ids of the modules to load.
|
|
* @param {boolean=} opt_isRetry If the load is a retry of a previous load
|
|
* attempt.
|
|
* @param {boolean=} opt_forceReload Whether to bypass cache while loading the
|
|
* module.
|
|
* @private
|
|
*/
|
|
goog.module.ModuleManager.prototype.loadModules_ = function(
|
|
ids, opt_isRetry, opt_forceReload) {
|
|
if (!opt_isRetry) {
|
|
this.consecutiveFailures_ = 0;
|
|
}
|
|
|
|
// Not all modules may be loaded immediately if batch mode is not enabled.
|
|
var idsToLoadImmediately = this.processModulesForLoad_(ids);
|
|
|
|
goog.log.info(this.logger_, 'Loading module(s): ' + idsToLoadImmediately);
|
|
this.loadingModuleIds_ = idsToLoadImmediately;
|
|
|
|
if (this.batchModeEnabled_) {
|
|
this.requestedLoadingModuleIds_ = ids;
|
|
} else {
|
|
// If batch mode is disabled, we treat each dependency load as a separate
|
|
// load.
|
|
this.requestedLoadingModuleIds_ = goog.array.clone(idsToLoadImmediately);
|
|
}
|
|
|
|
// Dispatch an active/idle change if needed.
|
|
this.dispatchActiveIdleChangeIfNeeded_();
|
|
|
|
if (goog.array.isEmpty(idsToLoadImmediately)) {
|
|
// All requested modules and deps have been either loaded already or have
|
|
// already been requested.
|
|
return;
|
|
}
|
|
|
|
this.requestedModuleIds_.push.apply(this.requestedModuleIds_,
|
|
idsToLoadImmediately);
|
|
|
|
var loadFn = goog.bind(this.loader_.loadModules, this.loader_,
|
|
goog.array.clone(idsToLoadImmediately),
|
|
this.moduleInfoMap_,
|
|
null,
|
|
goog.bind(this.handleLoadError_, this, this.requestedLoadingModuleIds_,
|
|
idsToLoadImmediately),
|
|
goog.bind(this.handleLoadTimeout_, this),
|
|
!!opt_forceReload);
|
|
|
|
var delay = this.getBackOff_();
|
|
if (delay) {
|
|
window.setTimeout(loadFn, delay);
|
|
} else {
|
|
loadFn();
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Processes a list of module ids for loading. Checks if any of the modules are
|
|
* already loaded and then gets transitive deps. Queues any necessary modules
|
|
* if batch mode is not enabled. Returns the list of ids that should be loaded.
|
|
*
|
|
* @param {Array.<string>} ids The ids that need to be loaded.
|
|
* @return {Array.<string>} The ids to load, including dependencies.
|
|
* @throws {Error} If the module is already loaded.
|
|
* @private
|
|
*/
|
|
goog.module.ModuleManager.prototype.processModulesForLoad_ = function(ids) {
|
|
for (var i = 0; i < ids.length; i++) {
|
|
var moduleInfo = this.moduleInfoMap_[ids[i]];
|
|
if (moduleInfo.isLoaded()) {
|
|
throw Error('Module already loaded: ' + ids[i]);
|
|
}
|
|
}
|
|
|
|
// Build a list of the ids of this module and any of its not-yet-loaded
|
|
// prerequisite modules in dependency order.
|
|
var idsWithDeps = [];
|
|
for (var i = 0; i < ids.length; i++) {
|
|
idsWithDeps = idsWithDeps.concat(
|
|
this.getNotYetLoadedTransitiveDepIds_(ids[i]));
|
|
}
|
|
goog.array.removeDuplicates(idsWithDeps);
|
|
|
|
if (!this.batchModeEnabled_ && idsWithDeps.length > 1) {
|
|
var idToLoad = idsWithDeps.shift();
|
|
goog.log.info(this.logger_,
|
|
'Must load ' + idToLoad + ' module before ' + ids);
|
|
|
|
// Insert the requested module id and any other not-yet-loaded prereqs
|
|
// that it has at the front of the queue.
|
|
var queuedModules = goog.array.map(idsWithDeps, function(id) {
|
|
return [id];
|
|
});
|
|
this.requestedModuleIdsQueue_ = queuedModules.concat(
|
|
this.requestedModuleIdsQueue_);
|
|
return [idToLoad];
|
|
} else {
|
|
return idsWithDeps;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Builds a list of the ids of the not-yet-loaded modules that a particular
|
|
* module transitively depends on, including itself.
|
|
*
|
|
* @param {string} id The id of a not-yet-loaded module.
|
|
* @return {Array.<string>} An array of module ids in dependency order that's
|
|
* guaranteed to end with the provided module id.
|
|
* @private
|
|
*/
|
|
goog.module.ModuleManager.prototype.getNotYetLoadedTransitiveDepIds_ =
|
|
function(id) {
|
|
// NOTE(user): We want the earliest occurrance of a module, not the first
|
|
// dependency we find. Therefore we strip duplicates at the end rather than
|
|
// during. See the tests for concrete examples.
|
|
var ids = [];
|
|
if (!goog.array.contains(this.requestedModuleIds_, id)) {
|
|
ids.push(id);
|
|
}
|
|
var depIds = goog.array.clone(this.getModuleInfo(id).getDependencies());
|
|
while (depIds.length) {
|
|
var depId = depIds.pop();
|
|
if (!this.getModuleInfo(depId).isLoaded() &&
|
|
!goog.array.contains(this.requestedModuleIds_, depId)) {
|
|
ids.unshift(depId);
|
|
// We need to process direct dependencies first.
|
|
Array.prototype.unshift.apply(depIds,
|
|
this.getModuleInfo(depId).getDependencies());
|
|
}
|
|
}
|
|
goog.array.removeDuplicates(ids);
|
|
return ids;
|
|
};
|
|
|
|
|
|
/**
|
|
* If we are still loading the base module, consider the load complete.
|
|
* @private
|
|
*/
|
|
goog.module.ModuleManager.prototype.maybeFinishBaseLoad_ = function() {
|
|
if (this.currentlyLoadingModule_ == this.baseModuleInfo_) {
|
|
this.currentlyLoadingModule_ = null;
|
|
var error = this.baseModuleInfo_.onLoad(
|
|
goog.bind(this.getModuleContext, this));
|
|
if (error) {
|
|
this.dispatchModuleLoadFailed_(
|
|
goog.module.ModuleManager.FailureType.INIT_ERROR);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Records that a module was loaded. Also initiates loading the next module if
|
|
* any module requests are queued. This method is called by code that is
|
|
* generated and appended to each dynamic module's code at compilation time.
|
|
*
|
|
* @param {string} id A module id.
|
|
*/
|
|
goog.module.ModuleManager.prototype.setLoaded = function(id) {
|
|
if (this.isDisposed()) {
|
|
goog.log.warning(this.logger_,
|
|
'Module loaded after module manager was disposed: ' + id);
|
|
return;
|
|
}
|
|
|
|
goog.log.info(this.logger_, 'Module loaded: ' + id);
|
|
|
|
var error = this.moduleInfoMap_[id].onLoad(
|
|
goog.bind(this.getModuleContext, this));
|
|
if (error) {
|
|
this.dispatchModuleLoadFailed_(
|
|
goog.module.ModuleManager.FailureType.INIT_ERROR);
|
|
}
|
|
|
|
// Remove the module id from the user initiated set if it existed there.
|
|
goog.array.remove(this.userInitiatedLoadingModuleIds_, id);
|
|
|
|
// Remove the module id from the loading modules if it exists there.
|
|
goog.array.remove(this.loadingModuleIds_, id);
|
|
|
|
if (goog.array.isEmpty(this.loadingModuleIds_)) {
|
|
// No more modules are currently being loaded (e.g. arriving later in the
|
|
// same HTTP response), so proceed to load the next module in the queue.
|
|
this.loadNextModules_();
|
|
}
|
|
|
|
if (this.lastInitialModuleId_ && id == this.lastInitialModuleId_) {
|
|
if (!this.initialModulesLoaded_.hasFired()) {
|
|
this.initialModulesLoaded_.callback();
|
|
}
|
|
}
|
|
|
|
// Dispatch an active/idle change if needed.
|
|
this.dispatchActiveIdleChangeIfNeeded_();
|
|
};
|
|
|
|
|
|
/**
|
|
* Gets whether a module is currently loading or in the queue, waiting to be
|
|
* loaded.
|
|
* @param {string} id A module id.
|
|
* @return {boolean} TRUE iff the module is loading.
|
|
*/
|
|
goog.module.ModuleManager.prototype.isModuleLoading = function(id) {
|
|
if (goog.array.contains(this.loadingModuleIds_, id)) {
|
|
return true;
|
|
}
|
|
for (var i = 0; i < this.requestedModuleIdsQueue_.length; i++) {
|
|
if (goog.array.contains(this.requestedModuleIdsQueue_[i], id)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
|
|
/**
|
|
* Requests that a function be called once a particular module is loaded.
|
|
* Client code can use this method to safely call into modules that may not yet
|
|
* be loaded. For consistency, this method always calls the function
|
|
* asynchronously -- even if the module is already loaded. Initiates loading of
|
|
* the module if necessary, unless opt_noLoad is true.
|
|
*
|
|
* @param {string} moduleId A module id.
|
|
* @param {Function} fn Function to execute when the module has loaded.
|
|
* @param {Object=} opt_handler Optional handler under whose scope to execute
|
|
* the callback.
|
|
* @param {boolean=} opt_noLoad TRUE iff not to initiate loading of the module.
|
|
* @param {boolean=} opt_userInitiated TRUE iff the loading of the module was
|
|
* user initiated.
|
|
* @param {boolean=} opt_preferSynchronous TRUE iff the function should be
|
|
* executed synchronously if the module has already been loaded.
|
|
* @return {goog.module.ModuleLoadCallback} A callback wrapper that exposes
|
|
* an abort and execute method.
|
|
*/
|
|
goog.module.ModuleManager.prototype.execOnLoad = function(
|
|
moduleId, fn, opt_handler, opt_noLoad,
|
|
opt_userInitiated, opt_preferSynchronous) {
|
|
var moduleInfo = this.moduleInfoMap_[moduleId];
|
|
var callbackWrapper;
|
|
|
|
if (moduleInfo.isLoaded()) {
|
|
goog.log.info(this.logger_, moduleId + ' module already loaded');
|
|
// Call async so that code paths don't change between loaded and unloaded
|
|
// cases.
|
|
callbackWrapper = new goog.module.ModuleLoadCallback(fn, opt_handler);
|
|
if (opt_preferSynchronous) {
|
|
callbackWrapper.execute(this.moduleContext_);
|
|
} else {
|
|
window.setTimeout(
|
|
goog.bind(callbackWrapper.execute, callbackWrapper), 0);
|
|
}
|
|
} else if (this.isModuleLoading(moduleId)) {
|
|
goog.log.info(this.logger_, moduleId + ' module already loading');
|
|
callbackWrapper = moduleInfo.registerCallback(fn, opt_handler);
|
|
if (opt_userInitiated) {
|
|
goog.log.info(this.logger_,
|
|
'User initiated module already loading: ' + moduleId);
|
|
this.addUserInitiatedLoadingModule_(moduleId);
|
|
this.dispatchActiveIdleChangeIfNeeded_();
|
|
}
|
|
} else {
|
|
goog.log.info(this.logger_,
|
|
'Registering callback for module: ' + moduleId);
|
|
callbackWrapper = moduleInfo.registerCallback(fn, opt_handler);
|
|
if (!opt_noLoad) {
|
|
if (opt_userInitiated) {
|
|
goog.log.info(this.logger_, 'User initiated module load: ' + moduleId);
|
|
this.addUserInitiatedLoadingModule_(moduleId);
|
|
}
|
|
goog.log.info(this.logger_, 'Initiating module load: ' + moduleId);
|
|
this.loadModulesOrEnqueue_([moduleId]);
|
|
}
|
|
}
|
|
return callbackWrapper;
|
|
};
|
|
|
|
|
|
/**
|
|
* Loads a module, returning a goog.async.Deferred for keeping track of the
|
|
* result.
|
|
*
|
|
* @param {string} moduleId A module id.
|
|
* @param {boolean=} opt_userInitiated If the load is a result of a user action.
|
|
* @return {goog.async.Deferred} A deferred object.
|
|
*/
|
|
goog.module.ModuleManager.prototype.load = function(
|
|
moduleId, opt_userInitiated) {
|
|
return this.loadModulesOrEnqueueIfNotLoadedOrLoading_(
|
|
[moduleId], opt_userInitiated)[moduleId];
|
|
};
|
|
|
|
|
|
/**
|
|
* Loads a list of modules, returning a goog.async.Deferred for keeping track of
|
|
* the result.
|
|
*
|
|
* @param {Array.<string>} moduleIds A list of module ids.
|
|
* @param {boolean=} opt_userInitiated If the load is a result of a user action.
|
|
* @return {Object.<!goog.async.Deferred>} A mapping from id (String) to
|
|
* deferred objects that will callback or errback when the load for that
|
|
* id is finished.
|
|
*/
|
|
goog.module.ModuleManager.prototype.loadMultiple = function(
|
|
moduleIds, opt_userInitiated) {
|
|
return this.loadModulesOrEnqueueIfNotLoadedOrLoading_(
|
|
moduleIds, opt_userInitiated);
|
|
};
|
|
|
|
|
|
/**
|
|
* Ensures that the module with the given id is listed as a user-initiated
|
|
* module that is being loaded. This method guarantees that a module will never
|
|
* get listed more than once.
|
|
* @param {string} id Identifier of the module.
|
|
* @private
|
|
*/
|
|
goog.module.ModuleManager.prototype.addUserInitiatedLoadingModule_ = function(
|
|
id) {
|
|
if (!goog.array.contains(this.userInitiatedLoadingModuleIds_, id)) {
|
|
this.userInitiatedLoadingModuleIds_.push(id);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Method called just before a module code is loaded.
|
|
* @param {string} id Identifier of the module.
|
|
*/
|
|
goog.module.ModuleManager.prototype.beforeLoadModuleCode = function(id) {
|
|
this.loadTracer_ = goog.debug.Trace.startTracer('Module Load: ' + id,
|
|
'Module Load');
|
|
if (this.currentlyLoadingModule_) {
|
|
goog.log.error(this.logger_,
|
|
'beforeLoadModuleCode called with module "' + id +
|
|
'" while module "' +
|
|
this.currentlyLoadingModule_.getId() +
|
|
'" is loading');
|
|
}
|
|
this.currentlyLoadingModule_ = this.getModuleInfo(id);
|
|
};
|
|
|
|
|
|
/**
|
|
* Method called just after module code is loaded
|
|
* @param {string} id Identifier of the module.
|
|
*/
|
|
goog.module.ModuleManager.prototype.afterLoadModuleCode = function(id) {
|
|
if (!this.currentlyLoadingModule_ ||
|
|
id != this.currentlyLoadingModule_.getId()) {
|
|
goog.log.error(this.logger_,
|
|
'afterLoadModuleCode called with module "' + id +
|
|
'" while loading module "' +
|
|
(this.currentlyLoadingModule_ &&
|
|
this.currentlyLoadingModule_.getId()) + '"');
|
|
|
|
}
|
|
this.currentlyLoadingModule_ = null;
|
|
goog.debug.Trace.stopTracer(this.loadTracer_);
|
|
};
|
|
|
|
|
|
/**
|
|
* Register an initialization callback for the currently loading module. This
|
|
* should only be called by script that is executed during the evaluation of
|
|
* a module's javascript. This is almost equivalent to calling the function
|
|
* inline, but ensures that all the code from the currently loading module
|
|
* has been loaded. This makes it cleaner and more robust than calling the
|
|
* function inline.
|
|
*
|
|
* If this function is called from the base module (the one that contains
|
|
* the module manager code), the callback is held until #setAllModuleInfo
|
|
* is called, or until #setModuleContext is called, whichever happens first.
|
|
*
|
|
* @param {Function} fn A callback function that takes a single argument
|
|
* which is the module context.
|
|
* @param {Object=} opt_handler Optional handler under whose scope to execute
|
|
* the callback.
|
|
*/
|
|
goog.module.ModuleManager.prototype.registerInitializationCallback = function(
|
|
fn, opt_handler) {
|
|
if (!this.currentlyLoadingModule_) {
|
|
goog.log.error(this.logger_, 'No module is currently loading');
|
|
} else {
|
|
this.currentlyLoadingModule_.registerEarlyCallback(fn, opt_handler);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Register a late initialization callback for the currently loading module.
|
|
* Callbacks registered via this function are executed similar to
|
|
* {@see registerInitializationCallback}, but they are fired after all
|
|
* initialization callbacks are called.
|
|
*
|
|
* @param {Function} fn A callback function that takes a single argument
|
|
* which is the module context.
|
|
* @param {Object=} opt_handler Optional handler under whose scope to execute
|
|
* the callback.
|
|
*/
|
|
goog.module.ModuleManager.prototype.registerLateInitializationCallback =
|
|
function(fn, opt_handler) {
|
|
if (!this.currentlyLoadingModule_) {
|
|
goog.log.error(this.logger_, 'No module is currently loading');
|
|
} else {
|
|
this.currentlyLoadingModule_.registerCallback(fn, opt_handler);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the constructor to use for the module object for the currently
|
|
* loading module. The constructor should derive from {@see
|
|
* goog.module.BaseModule}.
|
|
* @param {Function} fn The constructor function.
|
|
*/
|
|
goog.module.ModuleManager.prototype.setModuleConstructor = function(fn) {
|
|
if (!this.currentlyLoadingModule_) {
|
|
goog.log.error(this.logger_, 'No module is currently loading');
|
|
return;
|
|
}
|
|
this.currentlyLoadingModule_.setModuleConstructor(fn);
|
|
};
|
|
|
|
|
|
/**
|
|
* The possible reasons for a module load failure callback being fired.
|
|
* @enum {number}
|
|
*/
|
|
goog.module.ModuleManager.FailureType = {
|
|
/** 401 Status. */
|
|
UNAUTHORIZED: 0,
|
|
|
|
/** Error status (not 401) returned multiple times. */
|
|
CONSECUTIVE_FAILURES: 1,
|
|
|
|
/** Request timeout. */
|
|
TIMEOUT: 2,
|
|
|
|
/** 410 status, old code gone. */
|
|
OLD_CODE_GONE: 3,
|
|
|
|
/** The onLoad callbacks failed. */
|
|
INIT_ERROR: 4
|
|
};
|
|
|
|
|
|
/**
|
|
* Handles a module load failure.
|
|
*
|
|
* @param {!Array.<string>} requestedLoadingModuleIds Modules ids that were
|
|
* requested in failed request. Does not included calculated dependencies.
|
|
* @param {!Array.<string>} requestedModuleIdsWithDeps All module ids requested
|
|
* in the failed request including all dependencies.
|
|
* @param {?number} status The error status.
|
|
* @private
|
|
*/
|
|
goog.module.ModuleManager.prototype.handleLoadError_ = function(
|
|
requestedLoadingModuleIds, requestedModuleIdsWithDeps, status) {
|
|
this.consecutiveFailures_++;
|
|
// Module manager was not designed to be reentrant. Reinstate the instance
|
|
// var with actual value when request failed (Other requests may have
|
|
// started already.)
|
|
this.requestedLoadingModuleIds_ = requestedLoadingModuleIds;
|
|
// Pretend we never requested the failed modules.
|
|
goog.array.forEach(requestedModuleIdsWithDeps,
|
|
goog.partial(goog.array.remove, this.requestedModuleIds_), this);
|
|
|
|
if (status == 401) {
|
|
// The user is not logged in. They've cleared their cookies or logged out
|
|
// from another window.
|
|
goog.log.info(this.logger_, 'Module loading unauthorized');
|
|
this.dispatchModuleLoadFailed_(
|
|
goog.module.ModuleManager.FailureType.UNAUTHORIZED);
|
|
// Drop any additional module requests.
|
|
this.requestedModuleIdsQueue_.length = 0;
|
|
} else if (status == 410) {
|
|
// The requested module js is old and not available.
|
|
this.requeueBatchOrDispatchFailure_(
|
|
goog.module.ModuleManager.FailureType.OLD_CODE_GONE);
|
|
this.loadNextModules_();
|
|
} else if (this.consecutiveFailures_ >= 3) {
|
|
goog.log.info(this.logger_, 'Aborting after failure to load: ' +
|
|
this.loadingModuleIds_);
|
|
this.requeueBatchOrDispatchFailure_(
|
|
goog.module.ModuleManager.FailureType.CONSECUTIVE_FAILURES);
|
|
this.loadNextModules_();
|
|
} else {
|
|
goog.log.info(this.logger_, 'Retrying after failure to load: ' +
|
|
this.loadingModuleIds_);
|
|
var forceReload =
|
|
status == goog.module.ModuleManager.CORRUPT_RESPONSE_STATUS_CODE;
|
|
this.loadModules_(this.requestedLoadingModuleIds_, true, forceReload);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Handles a module load timeout.
|
|
* @private
|
|
*/
|
|
goog.module.ModuleManager.prototype.handleLoadTimeout_ = function() {
|
|
goog.log.info(this.logger_,
|
|
'Aborting after timeout: ' + this.loadingModuleIds_);
|
|
this.requeueBatchOrDispatchFailure_(
|
|
goog.module.ModuleManager.FailureType.TIMEOUT);
|
|
this.loadNextModules_();
|
|
};
|
|
|
|
|
|
/**
|
|
* Requeues batch loads that had more than one requested module
|
|
* (i.e. modules that were not included as dependencies) as separate loads or
|
|
* if there was only one requested module, fails that module with the received
|
|
* cause.
|
|
* @param {goog.module.ModuleManager.FailureType} cause The reason for the
|
|
* failure.
|
|
* @private
|
|
*/
|
|
goog.module.ModuleManager.prototype.requeueBatchOrDispatchFailure_ =
|
|
function(cause) {
|
|
// The load failed, so if there are more than one requested modules, then we
|
|
// need to retry each one as a separate load. Otherwise, if there is only one
|
|
// requested module, remove it and its dependencies from the queue.
|
|
if (this.requestedLoadingModuleIds_.length > 1) {
|
|
var queuedModules = goog.array.map(this.requestedLoadingModuleIds_,
|
|
function(id) {
|
|
return [id];
|
|
});
|
|
this.requestedModuleIdsQueue_ = queuedModules.concat(
|
|
this.requestedModuleIdsQueue_);
|
|
} else {
|
|
this.dispatchModuleLoadFailed_(cause);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Handles when a module load failed.
|
|
* @param {goog.module.ModuleManager.FailureType} cause The reason for the
|
|
* failure.
|
|
* @private
|
|
*/
|
|
goog.module.ModuleManager.prototype.dispatchModuleLoadFailed_ = function(
|
|
cause) {
|
|
var failedIds = this.requestedLoadingModuleIds_;
|
|
this.loadingModuleIds_.length = 0;
|
|
// If any pending modules depend on the id that failed,
|
|
// they need to be removed from the queue.
|
|
var idsToCancel = [];
|
|
for (var i = 0; i < this.requestedModuleIdsQueue_.length; i++) {
|
|
var dependentModules = goog.array.filter(
|
|
this.requestedModuleIdsQueue_[i],
|
|
/**
|
|
* Returns true if the requestedId has dependencies on the modules that
|
|
* just failed to load.
|
|
* @param {string} requestedId The module to check for dependencies.
|
|
* @return {boolean} True if the module depends on failed modules.
|
|
*/
|
|
function(requestedId) {
|
|
var requestedDeps = this.getNotYetLoadedTransitiveDepIds_(
|
|
requestedId);
|
|
return goog.array.some(failedIds, function(id) {
|
|
return goog.array.contains(requestedDeps, id);
|
|
});
|
|
}, this);
|
|
goog.array.extend(idsToCancel, dependentModules);
|
|
}
|
|
|
|
// Also insert the ids that failed to load as ids to cancel.
|
|
for (var i = 0; i < failedIds.length; i++) {
|
|
goog.array.insert(idsToCancel, failedIds[i]);
|
|
}
|
|
|
|
// Remove ids to cancel from the queues.
|
|
for (var i = 0; i < idsToCancel.length; i++) {
|
|
for (var j = 0; j < this.requestedModuleIdsQueue_.length; j++) {
|
|
goog.array.remove(this.requestedModuleIdsQueue_[j], idsToCancel[i]);
|
|
}
|
|
goog.array.remove(this.userInitiatedLoadingModuleIds_, idsToCancel[i]);
|
|
}
|
|
|
|
// Call the functions for error notification.
|
|
var errorCallbacks = this.callbackMap_[
|
|
goog.module.ModuleManager.CallbackType.ERROR];
|
|
if (errorCallbacks) {
|
|
for (var i = 0; i < errorCallbacks.length; i++) {
|
|
var callback = errorCallbacks[i];
|
|
for (var j = 0; j < idsToCancel.length; j++) {
|
|
callback(goog.module.ModuleManager.CallbackType.ERROR, idsToCancel[j],
|
|
cause);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Call the errbacks on the module info.
|
|
for (var i = 0; i < failedIds.length; i++) {
|
|
if (this.moduleInfoMap_[failedIds[i]]) {
|
|
this.moduleInfoMap_[failedIds[i]].onError(cause);
|
|
}
|
|
}
|
|
|
|
// Clear the requested loading module ids.
|
|
this.requestedLoadingModuleIds_.length = 0;
|
|
|
|
this.dispatchActiveIdleChangeIfNeeded_();
|
|
};
|
|
|
|
|
|
/**
|
|
* Loads the next modules on the queue.
|
|
* @private
|
|
*/
|
|
goog.module.ModuleManager.prototype.loadNextModules_ = function() {
|
|
while (this.requestedModuleIdsQueue_.length) {
|
|
// Remove modules that are already loaded.
|
|
var nextIds = goog.array.filter(this.requestedModuleIdsQueue_.shift(),
|
|
/** @param {string} id The module id. */
|
|
function(id) {
|
|
return !this.getModuleInfo(id).isLoaded();
|
|
}, this);
|
|
if (nextIds.length > 0) {
|
|
this.loadModules_(nextIds);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Dispatch an active/idle change if needed.
|
|
this.dispatchActiveIdleChangeIfNeeded_();
|
|
};
|
|
|
|
|
|
/**
|
|
* The function to call if the module manager is in error.
|
|
* @param {goog.module.ModuleManager.CallbackType|Array.<goog.module.ModuleManager.CallbackType>} types
|
|
* The callback type.
|
|
* @param {Function} fn The function to register as a callback.
|
|
*/
|
|
goog.module.ModuleManager.prototype.registerCallback = function(
|
|
types, fn) {
|
|
if (!goog.isArray(types)) {
|
|
types = [types];
|
|
}
|
|
|
|
for (var i = 0; i < types.length; i++) {
|
|
this.registerCallback_(types[i], fn);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Register a callback for the specified callback type.
|
|
* @param {goog.module.ModuleManager.CallbackType} type The callback type.
|
|
* @param {Function} fn The callback function.
|
|
* @private
|
|
*/
|
|
goog.module.ModuleManager.prototype.registerCallback_ = function(type, fn) {
|
|
var callbackMap = this.callbackMap_;
|
|
if (!callbackMap[type]) {
|
|
callbackMap[type] = [];
|
|
}
|
|
callbackMap[type].push(fn);
|
|
};
|
|
|
|
|
|
/**
|
|
* Call the callback functions of the specified type.
|
|
* @param {goog.module.ModuleManager.CallbackType} type The callback type.
|
|
* @private
|
|
*/
|
|
goog.module.ModuleManager.prototype.executeCallbacks_ = function(type) {
|
|
var callbacks = this.callbackMap_[type];
|
|
for (var i = 0; callbacks && i < callbacks.length; i++) {
|
|
callbacks[i](type);
|
|
}
|
|
};
|
|
|
|
|
|
/** @override */
|
|
goog.module.ModuleManager.prototype.disposeInternal = function() {
|
|
goog.module.ModuleManager.superClass_.disposeInternal.call(this);
|
|
|
|
// Dispose of each ModuleInfo object.
|
|
goog.disposeAll(
|
|
goog.object.getValues(this.moduleInfoMap_), this.baseModuleInfo_);
|
|
this.moduleInfoMap_ = null;
|
|
this.loadingModuleIds_ = null;
|
|
this.requestedLoadingModuleIds_ = null;
|
|
this.userInitiatedLoadingModuleIds_ = null;
|
|
this.requestedModuleIdsQueue_ = null;
|
|
this.callbackMap_ = null;
|
|
};
|