123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353 |
- // 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');
- /** @suppress {extraRequire} */
- goog.require('goog.module');
- /** @suppress {extraRequire} interface */
- goog.require('goog.module.AbstractModuleLoader');
- 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}
- * @struct
- */
- goog.module.ModuleManager = function() {
- goog.module.ModuleManager.base(this, 'constructor');
- /**
- * A mapping from module id to ModuleInfo object.
- * @private {Object<string, !goog.module.ModuleInfo>}
- */
- 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<goog.module.ModuleManager.CallbackType, Array<Function>>}
- * @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();
- /**
- * A logger.
- * @private {goog.log.Logger}
- */
- this.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.
- * @private {boolean}
- */
- this.batchModeEnabled_ = false;
- /**
- * Whether the module requests may be sent out of order.
- * @private {boolean}
- */
- this.concurrentLoadingEnabled_ = false;
- /**
- * A loader for the modules that implements loadModules(ids, moduleInfoMap,
- * opt_successFn, opt_errorFn, opt_timeoutFn, opt_forceReload) method.
- * @private {goog.module.AbstractModuleLoader}
- */
- this.loader_ = null;
- // TODO(user): Remove tracer.
- /**
- * Tracer that measures how long it takes to load a module.
- * @private {?number}
- */
- this.loadTracer_ = null;
- /**
- * The number of consecutive failures that have happened upon module load
- * requests.
- * @private {number}
- */
- this.consecutiveFailures_ = 0;
- /**
- * Determines if the module manager was just active before the processing of
- * the last data.
- * @private {boolean}
- */
- this.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.
- * @private {boolean}
- */
- this.userLastActive_ = false;
- /**
- * The module context needed for module initialization.
- * @private {Object}
- */
- this.moduleContext_ = null;
- };
- 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;
- /**
- * 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<string, !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);
- if (!moduleInfo) {
- throw new Error('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 occurrence 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);
- }
- this.dispatchActiveIdleChangeIfNeeded_();
- }
- };
- /**
- * 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<string, !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(),
- 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.base(this, 'disposeInternal');
- // 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;
- };
|