modulemanager.js 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353
  1. // Copyright 2008 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview A singleton object for managing Javascript code modules.
  16. *
  17. */
  18. goog.provide('goog.module.ModuleManager');
  19. goog.provide('goog.module.ModuleManager.CallbackType');
  20. goog.provide('goog.module.ModuleManager.FailureType');
  21. goog.require('goog.Disposable');
  22. goog.require('goog.array');
  23. goog.require('goog.asserts');
  24. goog.require('goog.async.Deferred');
  25. goog.require('goog.debug.Trace');
  26. /** @suppress {extraRequire} */
  27. goog.require('goog.dispose');
  28. goog.require('goog.log');
  29. /** @suppress {extraRequire} */
  30. goog.require('goog.module');
  31. /** @suppress {extraRequire} interface */
  32. goog.require('goog.module.AbstractModuleLoader');
  33. goog.require('goog.module.ModuleInfo');
  34. goog.require('goog.module.ModuleLoadCallback');
  35. goog.require('goog.object');
  36. /**
  37. * The ModuleManager keeps track of all modules in the environment.
  38. * Since modules may not have their code loaded, we must keep track of them.
  39. * @constructor
  40. * @extends {goog.Disposable}
  41. * @struct
  42. */
  43. goog.module.ModuleManager = function() {
  44. goog.module.ModuleManager.base(this, 'constructor');
  45. /**
  46. * A mapping from module id to ModuleInfo object.
  47. * @private {Object<string, !goog.module.ModuleInfo>}
  48. */
  49. this.moduleInfoMap_ = {};
  50. // TODO (malteubl): Switch this to a reentrant design.
  51. /**
  52. * The ids of the currently loading modules. If batch mode is disabled, then
  53. * this array will never contain more than one element at a time.
  54. * @type {Array<string>}
  55. * @private
  56. */
  57. this.loadingModuleIds_ = [];
  58. /**
  59. * The requested ids of the currently loading modules. This does not include
  60. * module dependencies that may also be loading.
  61. * @type {Array<string>}
  62. * @private
  63. */
  64. this.requestedLoadingModuleIds_ = [];
  65. // TODO(user): Make these and other arrays that are used as sets be
  66. // actual sets.
  67. /**
  68. * All module ids that have ever been requested. In concurrent loading these
  69. * are the ones to subtract from future requests.
  70. * @type {!Array<string>}
  71. * @private
  72. */
  73. this.requestedModuleIds_ = [];
  74. /**
  75. * A queue of the ids of requested but not-yet-loaded modules. The zero
  76. * position is the front of the queue. This is a 2-D array to group modules
  77. * together with other modules that should be batch loaded with them, if
  78. * batch loading is enabled.
  79. * @type {Array<Array<string>>}
  80. * @private
  81. */
  82. this.requestedModuleIdsQueue_ = [];
  83. /**
  84. * The ids of the currently loading modules which have been initiated by user
  85. * actions.
  86. * @type {Array<string>}
  87. * @private
  88. */
  89. this.userInitiatedLoadingModuleIds_ = [];
  90. /**
  91. * A map of callback types to the functions to call for the specified
  92. * callback type.
  93. * @type {Object<goog.module.ModuleManager.CallbackType, Array<Function>>}
  94. * @private
  95. */
  96. this.callbackMap_ = {};
  97. /**
  98. * Module info for the base module (the one that contains the module
  99. * manager code), which we set as the loading module so one can
  100. * register initialization callbacks in the base module.
  101. *
  102. * The base module is considered loaded when #setAllModuleInfo is called or
  103. * #setModuleContext is called, whichever comes first.
  104. *
  105. * @type {goog.module.ModuleInfo}
  106. * @private
  107. */
  108. this.baseModuleInfo_ = new goog.module.ModuleInfo([], '');
  109. /**
  110. * The module that is currently loading, or null if not loading anything.
  111. * @type {goog.module.ModuleInfo}
  112. * @private
  113. */
  114. this.currentlyLoadingModule_ = this.baseModuleInfo_;
  115. /**
  116. * The id of the last requested initial module. When it loaded
  117. * the deferred in {@code this.initialModulesLoaded_} resolves.
  118. * @private {?string}
  119. */
  120. this.lastInitialModuleId_ = null;
  121. /**
  122. * Deferred for when all initial modules have loaded. We currently block
  123. * sending additional module requests until this deferred resolves. In a
  124. * future optimization it may be possible to use the initial modules as
  125. * seeds for the module loader "requested module ids" and start making new
  126. * requests even sooner.
  127. * @private {!goog.async.Deferred}
  128. */
  129. this.initialModulesLoaded_ = new goog.async.Deferred();
  130. /**
  131. * A logger.
  132. * @private {goog.log.Logger}
  133. */
  134. this.logger_ = goog.log.getLogger('goog.module.ModuleManager');
  135. /**
  136. * Whether the batch mode (i.e. the loading of multiple modules with just one
  137. * request) has been enabled.
  138. * @private {boolean}
  139. */
  140. this.batchModeEnabled_ = false;
  141. /**
  142. * Whether the module requests may be sent out of order.
  143. * @private {boolean}
  144. */
  145. this.concurrentLoadingEnabled_ = false;
  146. /**
  147. * A loader for the modules that implements loadModules(ids, moduleInfoMap,
  148. * opt_successFn, opt_errorFn, opt_timeoutFn, opt_forceReload) method.
  149. * @private {goog.module.AbstractModuleLoader}
  150. */
  151. this.loader_ = null;
  152. // TODO(user): Remove tracer.
  153. /**
  154. * Tracer that measures how long it takes to load a module.
  155. * @private {?number}
  156. */
  157. this.loadTracer_ = null;
  158. /**
  159. * The number of consecutive failures that have happened upon module load
  160. * requests.
  161. * @private {number}
  162. */
  163. this.consecutiveFailures_ = 0;
  164. /**
  165. * Determines if the module manager was just active before the processing of
  166. * the last data.
  167. * @private {boolean}
  168. */
  169. this.lastActive_ = false;
  170. /**
  171. * Determines if the module manager was just user active before the processing
  172. * of the last data. The module manager is user active if any of the
  173. * user-initiated modules are loading or queued up to load.
  174. * @private {boolean}
  175. */
  176. this.userLastActive_ = false;
  177. /**
  178. * The module context needed for module initialization.
  179. * @private {Object}
  180. */
  181. this.moduleContext_ = null;
  182. };
  183. goog.inherits(goog.module.ModuleManager, goog.Disposable);
  184. goog.addSingletonGetter(goog.module.ModuleManager);
  185. /**
  186. * The type of callbacks that can be registered with the module manager,.
  187. * @enum {string}
  188. */
  189. goog.module.ModuleManager.CallbackType = {
  190. /**
  191. * Fired when an error has occurred.
  192. */
  193. ERROR: 'error',
  194. /**
  195. * Fired when it becomes idle and has no more module loads to process.
  196. */
  197. IDLE: 'idle',
  198. /**
  199. * Fired when it becomes active and has module loads to process.
  200. */
  201. ACTIVE: 'active',
  202. /**
  203. * Fired when it becomes idle and has no more user-initiated module loads to
  204. * process.
  205. */
  206. USER_IDLE: 'userIdle',
  207. /**
  208. * Fired when it becomes active and has user-initiated module loads to
  209. * process.
  210. */
  211. USER_ACTIVE: 'userActive'
  212. };
  213. /**
  214. * A non-HTTP status code indicating a corruption in loaded module.
  215. * This should be used by a ModuleLoader as a replacement for the HTTP code
  216. * given to the error handler function to indicated that the module was
  217. * corrupted.
  218. * This will set the forceReload flag on the loadModules method when retrying
  219. * module loading.
  220. * @type {number}
  221. */
  222. goog.module.ModuleManager.CORRUPT_RESPONSE_STATUS_CODE = 8001;
  223. /**
  224. * Sets the batch mode as enabled or disabled for the module manager.
  225. * @param {boolean} enabled Whether the batch mode is to be enabled or not.
  226. */
  227. goog.module.ModuleManager.prototype.setBatchModeEnabled = function(enabled) {
  228. this.batchModeEnabled_ = enabled;
  229. };
  230. /**
  231. * Sets the concurrent loading mode as enabled or disabled for the module
  232. * manager. Requires a moduleloader implementation that supports concurrent
  233. * loads. The default {@see goog.module.ModuleLoader} does not.
  234. * @param {boolean} enabled
  235. */
  236. goog.module.ModuleManager.prototype.setConcurrentLoadingEnabled = function(
  237. enabled) {
  238. this.concurrentLoadingEnabled_ = enabled;
  239. };
  240. /**
  241. * Sets the module info for all modules. Should only be called once.
  242. *
  243. * @param {Object<Array<string>>} infoMap An object that contains a mapping
  244. * from module id (String) to list of required module ids (Array).
  245. */
  246. goog.module.ModuleManager.prototype.setAllModuleInfo = function(infoMap) {
  247. for (var id in infoMap) {
  248. this.moduleInfoMap_[id] = new goog.module.ModuleInfo(infoMap[id], id);
  249. }
  250. if (!this.initialModulesLoaded_.hasFired()) {
  251. this.initialModulesLoaded_.callback();
  252. }
  253. this.maybeFinishBaseLoad_();
  254. };
  255. /**
  256. * Sets the module info for all modules. Should only be called once. Also
  257. * marks modules that are currently being loaded.
  258. *
  259. * @param {string=} opt_info A string representation of the module dependency
  260. * graph, in the form: module1:dep1,dep2/module2:dep1,dep2 etc.
  261. * Where depX is the base-36 encoded position of the dep in the module list.
  262. * @param {Array<string>=} opt_loadingModuleIds A list of moduleIds that
  263. * are currently being loaded.
  264. */
  265. goog.module.ModuleManager.prototype.setAllModuleInfoString = function(
  266. opt_info, opt_loadingModuleIds) {
  267. if (!goog.isString(opt_info)) {
  268. // The call to this method is generated in two steps, the argument is added
  269. // after some of the compilation passes. This means that the initial code
  270. // doesn't have any arguments and causes compiler errors. We make it
  271. // optional to satisfy this constraint.
  272. return;
  273. }
  274. var modules = opt_info.split('/');
  275. var moduleIds = [];
  276. // Split the string into the infoMap of id->deps
  277. for (var i = 0; i < modules.length; i++) {
  278. var parts = modules[i].split(':');
  279. var id = parts[0];
  280. var deps;
  281. if (parts[1]) {
  282. deps = parts[1].split(',');
  283. for (var j = 0; j < deps.length; j++) {
  284. var index = parseInt(deps[j], 36);
  285. goog.asserts.assert(
  286. moduleIds[index], 'No module @ %s, dep of %s @ %s', index, id, i);
  287. deps[j] = moduleIds[index];
  288. }
  289. } else {
  290. deps = [];
  291. }
  292. moduleIds.push(id);
  293. this.moduleInfoMap_[id] = new goog.module.ModuleInfo(deps, id);
  294. }
  295. if (opt_loadingModuleIds && opt_loadingModuleIds.length) {
  296. goog.array.extend(this.loadingModuleIds_, opt_loadingModuleIds);
  297. // The last module in the list of initial modules. When it has loaded all
  298. // initial modules have loaded.
  299. this.lastInitialModuleId_ =
  300. /** @type {?string} */ (goog.array.peek(opt_loadingModuleIds));
  301. } else {
  302. if (!this.initialModulesLoaded_.hasFired()) {
  303. this.initialModulesLoaded_.callback();
  304. }
  305. }
  306. this.maybeFinishBaseLoad_();
  307. };
  308. /**
  309. * Gets a module info object by id.
  310. * @param {string} id A module identifier.
  311. * @return {!goog.module.ModuleInfo} The module info.
  312. */
  313. goog.module.ModuleManager.prototype.getModuleInfo = function(id) {
  314. return this.moduleInfoMap_[id];
  315. };
  316. /**
  317. * Sets the module uris.
  318. *
  319. * @param {Object} moduleUriMap The map of id/uris pairs for each module.
  320. */
  321. goog.module.ModuleManager.prototype.setModuleUris = function(moduleUriMap) {
  322. for (var id in moduleUriMap) {
  323. this.moduleInfoMap_[id].setUris(moduleUriMap[id]);
  324. }
  325. };
  326. /**
  327. * Gets the application-specific module loader.
  328. * @return {goog.module.AbstractModuleLoader} An object that has a
  329. * loadModules(ids, moduleInfoMap, opt_successFn, opt_errFn,
  330. * opt_timeoutFn, opt_forceReload) method.
  331. */
  332. goog.module.ModuleManager.prototype.getLoader = function() {
  333. return this.loader_;
  334. };
  335. /**
  336. * Sets the application-specific module loader.
  337. * @param {goog.module.AbstractModuleLoader} loader An object that has a
  338. * loadModules(ids, moduleInfoMap, opt_successFn, opt_errFn,
  339. * opt_timeoutFn, opt_forceReload) method.
  340. */
  341. goog.module.ModuleManager.prototype.setLoader = function(loader) {
  342. this.loader_ = loader;
  343. };
  344. /**
  345. * Gets the module context to use to initialize the module.
  346. * @return {Object} The context.
  347. */
  348. goog.module.ModuleManager.prototype.getModuleContext = function() {
  349. return this.moduleContext_;
  350. };
  351. /**
  352. * Sets the module context to use to initialize the module.
  353. * @param {Object} context The context.
  354. */
  355. goog.module.ModuleManager.prototype.setModuleContext = function(context) {
  356. this.moduleContext_ = context;
  357. this.maybeFinishBaseLoad_();
  358. };
  359. /**
  360. * Determines if the ModuleManager is active
  361. * @return {boolean} TRUE iff the ModuleManager is active (i.e., not idle).
  362. */
  363. goog.module.ModuleManager.prototype.isActive = function() {
  364. return this.loadingModuleIds_.length > 0;
  365. };
  366. /**
  367. * Determines if the ModuleManager is user active
  368. * @return {boolean} TRUE iff the ModuleManager is user active (i.e., not idle).
  369. */
  370. goog.module.ModuleManager.prototype.isUserActive = function() {
  371. return this.userInitiatedLoadingModuleIds_.length > 0;
  372. };
  373. /**
  374. * Dispatches an ACTIVE or IDLE event if necessary.
  375. * @private
  376. */
  377. goog.module.ModuleManager.prototype.dispatchActiveIdleChangeIfNeeded_ =
  378. function() {
  379. var lastActive = this.lastActive_;
  380. var active = this.isActive();
  381. if (active != lastActive) {
  382. this.executeCallbacks_(
  383. active ? goog.module.ModuleManager.CallbackType.ACTIVE :
  384. goog.module.ModuleManager.CallbackType.IDLE);
  385. // Flip the last active value.
  386. this.lastActive_ = active;
  387. }
  388. // Check if the module manager is user active i.e., there are user initiated
  389. // modules being loaded or queued up to be loaded.
  390. var userLastActive = this.userLastActive_;
  391. var userActive = this.isUserActive();
  392. if (userActive != userLastActive) {
  393. this.executeCallbacks_(
  394. userActive ? goog.module.ModuleManager.CallbackType.USER_ACTIVE :
  395. goog.module.ModuleManager.CallbackType.USER_IDLE);
  396. // Flip the last user active value.
  397. this.userLastActive_ = userActive;
  398. }
  399. };
  400. /**
  401. * Preloads a module after a short delay.
  402. *
  403. * @param {string} id The id of the module to preload.
  404. * @param {number=} opt_timeout The number of ms to wait before adding the
  405. * module id to the loading queue (defaults to 0 ms). Note that the module
  406. * will be loaded asynchronously regardless of the value of this parameter.
  407. * @return {!goog.async.Deferred} A deferred object.
  408. */
  409. goog.module.ModuleManager.prototype.preloadModule = function(id, opt_timeout) {
  410. var d = new goog.async.Deferred();
  411. window.setTimeout(
  412. goog.bind(this.addLoadModule_, this, id, d), opt_timeout || 0);
  413. return d;
  414. };
  415. /**
  416. * Prefetches a JavaScript module and its dependencies, which means that the
  417. * module will be downloaded, but not evaluated. To complete the module load,
  418. * the caller should also call load or execOnLoad after prefetching the module.
  419. *
  420. * @param {string} id The id of the module to prefetch.
  421. */
  422. goog.module.ModuleManager.prototype.prefetchModule = function(id) {
  423. var moduleInfo = this.getModuleInfo(id);
  424. if (moduleInfo.isLoaded() || this.isModuleLoading(id)) {
  425. throw Error('Module load already requested: ' + id);
  426. } else if (this.batchModeEnabled_) {
  427. throw Error('Modules prefetching is not supported in batch mode');
  428. } else {
  429. var idWithDeps = this.getNotYetLoadedTransitiveDepIds_(id);
  430. for (var i = 0; i < idWithDeps.length; i++) {
  431. this.loader_.prefetchModule(
  432. idWithDeps[i], this.moduleInfoMap_[idWithDeps[i]]);
  433. }
  434. }
  435. };
  436. /**
  437. * Loads a single module for use with a given deferred.
  438. *
  439. * @param {string} id The id of the module to load.
  440. * @param {goog.async.Deferred} d A deferred object.
  441. * @private
  442. */
  443. goog.module.ModuleManager.prototype.addLoadModule_ = function(id, d) {
  444. var moduleInfo = this.getModuleInfo(id);
  445. if (moduleInfo.isLoaded()) {
  446. d.callback(this.moduleContext_);
  447. return;
  448. }
  449. this.registerModuleLoadCallbacks_(id, moduleInfo, false, d);
  450. if (!this.isModuleLoading(id)) {
  451. this.loadModulesOrEnqueue_([id]);
  452. }
  453. };
  454. /**
  455. * Loads a list of modules or, if some other module is currently being loaded,
  456. * appends the ids to the queue of requested module ids. Registers callbacks a
  457. * module that is currently loading and returns a fired deferred for a module
  458. * that is already loaded.
  459. *
  460. * @param {Array<string>} ids The id of the module to load.
  461. * @param {boolean=} opt_userInitiated If the load is a result of a user action.
  462. * @return {!Object<string, !goog.async.Deferred>} A mapping from id (String)
  463. * to deferred objects that will callback or errback when the load for that
  464. * id is finished.
  465. * @private
  466. */
  467. goog.module.ModuleManager.prototype.loadModulesOrEnqueueIfNotLoadedOrLoading_ =
  468. function(ids, opt_userInitiated) {
  469. var uniqueIds = [];
  470. goog.array.removeDuplicates(ids, uniqueIds);
  471. var idsToLoad = [];
  472. var deferredMap = {};
  473. for (var i = 0; i < uniqueIds.length; i++) {
  474. var id = uniqueIds[i];
  475. var moduleInfo = this.getModuleInfo(id);
  476. if (!moduleInfo) {
  477. throw new Error('Unknown module: ' + id);
  478. }
  479. var d = new goog.async.Deferred();
  480. deferredMap[id] = d;
  481. if (moduleInfo.isLoaded()) {
  482. d.callback(this.moduleContext_);
  483. } else {
  484. this.registerModuleLoadCallbacks_(id, moduleInfo, !!opt_userInitiated, d);
  485. if (!this.isModuleLoading(id)) {
  486. idsToLoad.push(id);
  487. }
  488. }
  489. }
  490. // If there are ids to load, load them, otherwise, they are all loading or
  491. // loaded.
  492. if (idsToLoad.length > 0) {
  493. this.loadModulesOrEnqueue_(idsToLoad);
  494. }
  495. return deferredMap;
  496. };
  497. /**
  498. * Registers the callbacks and handles logic if it is a user initiated module
  499. * load.
  500. *
  501. * @param {string} id The id of the module to possibly load.
  502. * @param {!goog.module.ModuleInfo} moduleInfo The module identifier for the
  503. * given id.
  504. * @param {boolean} userInitiated If the load was user initiated.
  505. * @param {goog.async.Deferred} d A deferred object.
  506. * @private
  507. */
  508. goog.module.ModuleManager.prototype.registerModuleLoadCallbacks_ = function(
  509. id, moduleInfo, userInitiated, d) {
  510. moduleInfo.registerCallback(d.callback, d);
  511. moduleInfo.registerErrback(function(err) { d.errback(Error(err)); });
  512. // If it's already loading, we don't have to do anything besides handle
  513. // if it was user initiated
  514. if (this.isModuleLoading(id)) {
  515. if (userInitiated) {
  516. goog.log.info(
  517. this.logger_, 'User initiated module already loading: ' + id);
  518. this.addUserInitiatedLoadingModule_(id);
  519. this.dispatchActiveIdleChangeIfNeeded_();
  520. }
  521. } else {
  522. if (userInitiated) {
  523. goog.log.info(this.logger_, 'User initiated module load: ' + id);
  524. this.addUserInitiatedLoadingModule_(id);
  525. } else {
  526. goog.log.info(this.logger_, 'Initiating module load: ' + id);
  527. }
  528. }
  529. };
  530. /**
  531. * Initiates loading of a list of modules or, if a module is currently being
  532. * loaded, appends the modules to the queue of requested module ids.
  533. *
  534. * The caller should verify that the requested modules are not already loaded or
  535. * loading. {@link #loadModulesOrEnqueueIfNotLoadedOrLoading_} is a more lenient
  536. * alternative to this method.
  537. *
  538. * @param {Array<string>} ids The ids of the modules to load.
  539. * @private
  540. */
  541. goog.module.ModuleManager.prototype.loadModulesOrEnqueue_ = function(ids) {
  542. // With concurrent loading we always just send off the request.
  543. if (this.concurrentLoadingEnabled_) {
  544. // For now we wait for initial modules to have downloaded as this puts the
  545. // loader in a good state for calculating the needed deps of additional
  546. // loads.
  547. // TODO(user): Make this wait unnecessary.
  548. this.initialModulesLoaded_.addCallback(
  549. goog.bind(this.loadModules_, this, ids));
  550. } else {
  551. if (goog.array.isEmpty(this.loadingModuleIds_)) {
  552. this.loadModules_(ids);
  553. } else {
  554. this.requestedModuleIdsQueue_.push(ids);
  555. this.dispatchActiveIdleChangeIfNeeded_();
  556. }
  557. }
  558. };
  559. /**
  560. * Gets the amount of delay to wait before sending a request for more modules.
  561. * If a certain module request fails, we backoff a little bit and try again.
  562. * @return {number} Delay, in ms.
  563. * @private
  564. */
  565. goog.module.ModuleManager.prototype.getBackOff_ = function() {
  566. // 5 seconds after one error, 20 seconds after 2.
  567. return Math.pow(this.consecutiveFailures_, 2) * 5000;
  568. };
  569. /**
  570. * Loads a list of modules and any of their not-yet-loaded prerequisites.
  571. * If batch mode is enabled, the prerequisites will be loaded together with the
  572. * requested modules and all requested modules will be loaded at the same time.
  573. *
  574. * The caller should verify that the requested modules are not already loaded
  575. * and that no modules are currently loading before calling this method.
  576. *
  577. * @param {Array<string>} ids The ids of the modules to load.
  578. * @param {boolean=} opt_isRetry If the load is a retry of a previous load
  579. * attempt.
  580. * @param {boolean=} opt_forceReload Whether to bypass cache while loading the
  581. * module.
  582. * @private
  583. */
  584. goog.module.ModuleManager.prototype.loadModules_ = function(
  585. ids, opt_isRetry, opt_forceReload) {
  586. if (!opt_isRetry) {
  587. this.consecutiveFailures_ = 0;
  588. }
  589. // Not all modules may be loaded immediately if batch mode is not enabled.
  590. var idsToLoadImmediately = this.processModulesForLoad_(ids);
  591. goog.log.info(this.logger_, 'Loading module(s): ' + idsToLoadImmediately);
  592. this.loadingModuleIds_ = idsToLoadImmediately;
  593. if (this.batchModeEnabled_) {
  594. this.requestedLoadingModuleIds_ = ids;
  595. } else {
  596. // If batch mode is disabled, we treat each dependency load as a separate
  597. // load.
  598. this.requestedLoadingModuleIds_ = goog.array.clone(idsToLoadImmediately);
  599. }
  600. // Dispatch an active/idle change if needed.
  601. this.dispatchActiveIdleChangeIfNeeded_();
  602. if (goog.array.isEmpty(idsToLoadImmediately)) {
  603. // All requested modules and deps have been either loaded already or have
  604. // already been requested.
  605. return;
  606. }
  607. this.requestedModuleIds_.push.apply(
  608. this.requestedModuleIds_, idsToLoadImmediately);
  609. var loadFn = goog.bind(
  610. this.loader_.loadModules, this.loader_,
  611. goog.array.clone(idsToLoadImmediately), this.moduleInfoMap_, null,
  612. goog.bind(
  613. this.handleLoadError_, this, this.requestedLoadingModuleIds_,
  614. idsToLoadImmediately),
  615. goog.bind(this.handleLoadTimeout_, this), !!opt_forceReload);
  616. var delay = this.getBackOff_();
  617. if (delay) {
  618. window.setTimeout(loadFn, delay);
  619. } else {
  620. loadFn();
  621. }
  622. };
  623. /**
  624. * Processes a list of module ids for loading. Checks if any of the modules are
  625. * already loaded and then gets transitive deps. Queues any necessary modules
  626. * if batch mode is not enabled. Returns the list of ids that should be loaded.
  627. *
  628. * @param {Array<string>} ids The ids that need to be loaded.
  629. * @return {!Array<string>} The ids to load, including dependencies.
  630. * @throws {Error} If the module is already loaded.
  631. * @private
  632. */
  633. goog.module.ModuleManager.prototype.processModulesForLoad_ = function(ids) {
  634. for (var i = 0; i < ids.length; i++) {
  635. var moduleInfo = this.moduleInfoMap_[ids[i]];
  636. if (moduleInfo.isLoaded()) {
  637. throw Error('Module already loaded: ' + ids[i]);
  638. }
  639. }
  640. // Build a list of the ids of this module and any of its not-yet-loaded
  641. // prerequisite modules in dependency order.
  642. var idsWithDeps = [];
  643. for (var i = 0; i < ids.length; i++) {
  644. idsWithDeps =
  645. idsWithDeps.concat(this.getNotYetLoadedTransitiveDepIds_(ids[i]));
  646. }
  647. goog.array.removeDuplicates(idsWithDeps);
  648. if (!this.batchModeEnabled_ && idsWithDeps.length > 1) {
  649. var idToLoad = idsWithDeps.shift();
  650. goog.log.info(
  651. this.logger_, 'Must load ' + idToLoad + ' module before ' + ids);
  652. // Insert the requested module id and any other not-yet-loaded prereqs
  653. // that it has at the front of the queue.
  654. var queuedModules =
  655. goog.array.map(idsWithDeps, function(id) { return [id]; });
  656. this.requestedModuleIdsQueue_ =
  657. queuedModules.concat(this.requestedModuleIdsQueue_);
  658. return [idToLoad];
  659. } else {
  660. return idsWithDeps;
  661. }
  662. };
  663. /**
  664. * Builds a list of the ids of the not-yet-loaded modules that a particular
  665. * module transitively depends on, including itself.
  666. *
  667. * @param {string} id The id of a not-yet-loaded module.
  668. * @return {!Array<string>} An array of module ids in dependency order that's
  669. * guaranteed to end with the provided module id.
  670. * @private
  671. */
  672. goog.module.ModuleManager.prototype.getNotYetLoadedTransitiveDepIds_ = function(
  673. id) {
  674. // NOTE(user): We want the earliest occurrence of a module, not the first
  675. // dependency we find. Therefore we strip duplicates at the end rather than
  676. // during. See the tests for concrete examples.
  677. var ids = [];
  678. if (!goog.array.contains(this.requestedModuleIds_, id)) {
  679. ids.push(id);
  680. }
  681. var depIds = goog.array.clone(this.getModuleInfo(id).getDependencies());
  682. while (depIds.length) {
  683. var depId = depIds.pop();
  684. if (!this.getModuleInfo(depId).isLoaded() &&
  685. !goog.array.contains(this.requestedModuleIds_, depId)) {
  686. ids.unshift(depId);
  687. // We need to process direct dependencies first.
  688. Array.prototype.unshift.apply(
  689. depIds, this.getModuleInfo(depId).getDependencies());
  690. }
  691. }
  692. goog.array.removeDuplicates(ids);
  693. return ids;
  694. };
  695. /**
  696. * If we are still loading the base module, consider the load complete.
  697. * @private
  698. */
  699. goog.module.ModuleManager.prototype.maybeFinishBaseLoad_ = function() {
  700. if (this.currentlyLoadingModule_ == this.baseModuleInfo_) {
  701. this.currentlyLoadingModule_ = null;
  702. var error =
  703. this.baseModuleInfo_.onLoad(goog.bind(this.getModuleContext, this));
  704. if (error) {
  705. this.dispatchModuleLoadFailed_(
  706. goog.module.ModuleManager.FailureType.INIT_ERROR);
  707. }
  708. this.dispatchActiveIdleChangeIfNeeded_();
  709. }
  710. };
  711. /**
  712. * Records that a module was loaded. Also initiates loading the next module if
  713. * any module requests are queued. This method is called by code that is
  714. * generated and appended to each dynamic module's code at compilation time.
  715. *
  716. * @param {string} id A module id.
  717. */
  718. goog.module.ModuleManager.prototype.setLoaded = function(id) {
  719. if (this.isDisposed()) {
  720. goog.log.warning(
  721. this.logger_, 'Module loaded after module manager was disposed: ' + id);
  722. return;
  723. }
  724. goog.log.info(this.logger_, 'Module loaded: ' + id);
  725. var error =
  726. this.moduleInfoMap_[id].onLoad(goog.bind(this.getModuleContext, this));
  727. if (error) {
  728. this.dispatchModuleLoadFailed_(
  729. goog.module.ModuleManager.FailureType.INIT_ERROR);
  730. }
  731. // Remove the module id from the user initiated set if it existed there.
  732. goog.array.remove(this.userInitiatedLoadingModuleIds_, id);
  733. // Remove the module id from the loading modules if it exists there.
  734. goog.array.remove(this.loadingModuleIds_, id);
  735. if (goog.array.isEmpty(this.loadingModuleIds_)) {
  736. // No more modules are currently being loaded (e.g. arriving later in the
  737. // same HTTP response), so proceed to load the next module in the queue.
  738. this.loadNextModules_();
  739. }
  740. if (this.lastInitialModuleId_ && id == this.lastInitialModuleId_) {
  741. if (!this.initialModulesLoaded_.hasFired()) {
  742. this.initialModulesLoaded_.callback();
  743. }
  744. }
  745. // Dispatch an active/idle change if needed.
  746. this.dispatchActiveIdleChangeIfNeeded_();
  747. };
  748. /**
  749. * Gets whether a module is currently loading or in the queue, waiting to be
  750. * loaded.
  751. * @param {string} id A module id.
  752. * @return {boolean} TRUE iff the module is loading.
  753. */
  754. goog.module.ModuleManager.prototype.isModuleLoading = function(id) {
  755. if (goog.array.contains(this.loadingModuleIds_, id)) {
  756. return true;
  757. }
  758. for (var i = 0; i < this.requestedModuleIdsQueue_.length; i++) {
  759. if (goog.array.contains(this.requestedModuleIdsQueue_[i], id)) {
  760. return true;
  761. }
  762. }
  763. return false;
  764. };
  765. /**
  766. * Requests that a function be called once a particular module is loaded.
  767. * Client code can use this method to safely call into modules that may not yet
  768. * be loaded. For consistency, this method always calls the function
  769. * asynchronously -- even if the module is already loaded. Initiates loading of
  770. * the module if necessary, unless opt_noLoad is true.
  771. *
  772. * @param {string} moduleId A module id.
  773. * @param {Function} fn Function to execute when the module has loaded.
  774. * @param {Object=} opt_handler Optional handler under whose scope to execute
  775. * the callback.
  776. * @param {boolean=} opt_noLoad TRUE iff not to initiate loading of the module.
  777. * @param {boolean=} opt_userInitiated TRUE iff the loading of the module was
  778. * user initiated.
  779. * @param {boolean=} opt_preferSynchronous TRUE iff the function should be
  780. * executed synchronously if the module has already been loaded.
  781. * @return {!goog.module.ModuleLoadCallback} A callback wrapper that exposes
  782. * an abort and execute method.
  783. */
  784. goog.module.ModuleManager.prototype.execOnLoad = function(
  785. moduleId, fn, opt_handler, opt_noLoad, opt_userInitiated,
  786. opt_preferSynchronous) {
  787. var moduleInfo = this.moduleInfoMap_[moduleId];
  788. var callbackWrapper;
  789. if (moduleInfo.isLoaded()) {
  790. goog.log.info(this.logger_, moduleId + ' module already loaded');
  791. // Call async so that code paths don't change between loaded and unloaded
  792. // cases.
  793. callbackWrapper = new goog.module.ModuleLoadCallback(fn, opt_handler);
  794. if (opt_preferSynchronous) {
  795. callbackWrapper.execute(this.moduleContext_);
  796. } else {
  797. window.setTimeout(goog.bind(callbackWrapper.execute, callbackWrapper), 0);
  798. }
  799. } else if (this.isModuleLoading(moduleId)) {
  800. goog.log.info(this.logger_, moduleId + ' module already loading');
  801. callbackWrapper = moduleInfo.registerCallback(fn, opt_handler);
  802. if (opt_userInitiated) {
  803. goog.log.info(
  804. this.logger_, 'User initiated module already loading: ' + moduleId);
  805. this.addUserInitiatedLoadingModule_(moduleId);
  806. this.dispatchActiveIdleChangeIfNeeded_();
  807. }
  808. } else {
  809. goog.log.info(this.logger_, 'Registering callback for module: ' + moduleId);
  810. callbackWrapper = moduleInfo.registerCallback(fn, opt_handler);
  811. if (!opt_noLoad) {
  812. if (opt_userInitiated) {
  813. goog.log.info(this.logger_, 'User initiated module load: ' + moduleId);
  814. this.addUserInitiatedLoadingModule_(moduleId);
  815. }
  816. goog.log.info(this.logger_, 'Initiating module load: ' + moduleId);
  817. this.loadModulesOrEnqueue_([moduleId]);
  818. }
  819. }
  820. return callbackWrapper;
  821. };
  822. /**
  823. * Loads a module, returning a goog.async.Deferred for keeping track of the
  824. * result.
  825. *
  826. * @param {string} moduleId A module id.
  827. * @param {boolean=} opt_userInitiated If the load is a result of a user action.
  828. * @return {goog.async.Deferred} A deferred object.
  829. */
  830. goog.module.ModuleManager.prototype.load = function(
  831. moduleId, opt_userInitiated) {
  832. return this.loadModulesOrEnqueueIfNotLoadedOrLoading_(
  833. [moduleId], opt_userInitiated)[moduleId];
  834. };
  835. /**
  836. * Loads a list of modules, returning a goog.async.Deferred for keeping track of
  837. * the result.
  838. *
  839. * @param {Array<string>} moduleIds A list of module ids.
  840. * @param {boolean=} opt_userInitiated If the load is a result of a user action.
  841. * @return {!Object<string, !goog.async.Deferred>} A mapping from id (String)
  842. * to deferred objects that will callback or errback when the load for that
  843. * id is finished.
  844. */
  845. goog.module.ModuleManager.prototype.loadMultiple = function(
  846. moduleIds, opt_userInitiated) {
  847. return this.loadModulesOrEnqueueIfNotLoadedOrLoading_(
  848. moduleIds, opt_userInitiated);
  849. };
  850. /**
  851. * Ensures that the module with the given id is listed as a user-initiated
  852. * module that is being loaded. This method guarantees that a module will never
  853. * get listed more than once.
  854. * @param {string} id Identifier of the module.
  855. * @private
  856. */
  857. goog.module.ModuleManager.prototype.addUserInitiatedLoadingModule_ = function(
  858. id) {
  859. if (!goog.array.contains(this.userInitiatedLoadingModuleIds_, id)) {
  860. this.userInitiatedLoadingModuleIds_.push(id);
  861. }
  862. };
  863. /**
  864. * Method called just before a module code is loaded.
  865. * @param {string} id Identifier of the module.
  866. */
  867. goog.module.ModuleManager.prototype.beforeLoadModuleCode = function(id) {
  868. this.loadTracer_ =
  869. goog.debug.Trace.startTracer('Module Load: ' + id, 'Module Load');
  870. if (this.currentlyLoadingModule_) {
  871. goog.log.error(
  872. this.logger_, 'beforeLoadModuleCode called with module "' + id +
  873. '" while module "' + this.currentlyLoadingModule_.getId() +
  874. '" is loading');
  875. }
  876. this.currentlyLoadingModule_ = this.getModuleInfo(id);
  877. };
  878. /**
  879. * Method called just after module code is loaded
  880. * @param {string} id Identifier of the module.
  881. */
  882. goog.module.ModuleManager.prototype.afterLoadModuleCode = function(id) {
  883. if (!this.currentlyLoadingModule_ ||
  884. id != this.currentlyLoadingModule_.getId()) {
  885. goog.log.error(
  886. this.logger_, 'afterLoadModuleCode called with module "' + id +
  887. '" while loading module "' +
  888. (this.currentlyLoadingModule_ &&
  889. this.currentlyLoadingModule_.getId()) +
  890. '"');
  891. }
  892. this.currentlyLoadingModule_ = null;
  893. goog.debug.Trace.stopTracer(this.loadTracer_);
  894. };
  895. /**
  896. * Register an initialization callback for the currently loading module. This
  897. * should only be called by script that is executed during the evaluation of
  898. * a module's javascript. This is almost equivalent to calling the function
  899. * inline, but ensures that all the code from the currently loading module
  900. * has been loaded. This makes it cleaner and more robust than calling the
  901. * function inline.
  902. *
  903. * If this function is called from the base module (the one that contains
  904. * the module manager code), the callback is held until #setAllModuleInfo
  905. * is called, or until #setModuleContext is called, whichever happens first.
  906. *
  907. * @param {Function} fn A callback function that takes a single argument
  908. * which is the module context.
  909. * @param {Object=} opt_handler Optional handler under whose scope to execute
  910. * the callback.
  911. */
  912. goog.module.ModuleManager.prototype.registerInitializationCallback = function(
  913. fn, opt_handler) {
  914. if (!this.currentlyLoadingModule_) {
  915. goog.log.error(this.logger_, 'No module is currently loading');
  916. } else {
  917. this.currentlyLoadingModule_.registerEarlyCallback(fn, opt_handler);
  918. }
  919. };
  920. /**
  921. * Register a late initialization callback for the currently loading module.
  922. * Callbacks registered via this function are executed similar to
  923. * {@see registerInitializationCallback}, but they are fired after all
  924. * initialization callbacks are called.
  925. *
  926. * @param {Function} fn A callback function that takes a single argument
  927. * which is the module context.
  928. * @param {Object=} opt_handler Optional handler under whose scope to execute
  929. * the callback.
  930. */
  931. goog.module.ModuleManager.prototype.registerLateInitializationCallback =
  932. function(fn, opt_handler) {
  933. if (!this.currentlyLoadingModule_) {
  934. goog.log.error(this.logger_, 'No module is currently loading');
  935. } else {
  936. this.currentlyLoadingModule_.registerCallback(fn, opt_handler);
  937. }
  938. };
  939. /**
  940. * Sets the constructor to use for the module object for the currently
  941. * loading module. The constructor should derive from
  942. * {@see goog.module.BaseModule}.
  943. * @param {Function} fn The constructor function.
  944. */
  945. goog.module.ModuleManager.prototype.setModuleConstructor = function(fn) {
  946. if (!this.currentlyLoadingModule_) {
  947. goog.log.error(this.logger_, 'No module is currently loading');
  948. return;
  949. }
  950. this.currentlyLoadingModule_.setModuleConstructor(fn);
  951. };
  952. /**
  953. * The possible reasons for a module load failure callback being fired.
  954. * @enum {number}
  955. */
  956. goog.module.ModuleManager.FailureType = {
  957. /** 401 Status. */
  958. UNAUTHORIZED: 0,
  959. /** Error status (not 401) returned multiple times. */
  960. CONSECUTIVE_FAILURES: 1,
  961. /** Request timeout. */
  962. TIMEOUT: 2,
  963. /** 410 status, old code gone. */
  964. OLD_CODE_GONE: 3,
  965. /** The onLoad callbacks failed. */
  966. INIT_ERROR: 4
  967. };
  968. /**
  969. * Handles a module load failure.
  970. *
  971. * @param {!Array<string>} requestedLoadingModuleIds Modules ids that were
  972. * requested in failed request. Does not included calculated dependencies.
  973. * @param {!Array<string>} requestedModuleIdsWithDeps All module ids requested
  974. * in the failed request including all dependencies.
  975. * @param {?number} status The error status.
  976. * @private
  977. */
  978. goog.module.ModuleManager.prototype.handleLoadError_ = function(
  979. requestedLoadingModuleIds, requestedModuleIdsWithDeps, status) {
  980. this.consecutiveFailures_++;
  981. // Module manager was not designed to be reentrant. Reinstate the instance
  982. // var with actual value when request failed (Other requests may have
  983. // started already.)
  984. this.requestedLoadingModuleIds_ = requestedLoadingModuleIds;
  985. // Pretend we never requested the failed modules.
  986. goog.array.forEach(
  987. requestedModuleIdsWithDeps,
  988. goog.partial(goog.array.remove, this.requestedModuleIds_), this);
  989. if (status == 401) {
  990. // The user is not logged in. They've cleared their cookies or logged out
  991. // from another window.
  992. goog.log.info(this.logger_, 'Module loading unauthorized');
  993. this.dispatchModuleLoadFailed_(
  994. goog.module.ModuleManager.FailureType.UNAUTHORIZED);
  995. // Drop any additional module requests.
  996. this.requestedModuleIdsQueue_.length = 0;
  997. } else if (status == 410) {
  998. // The requested module js is old and not available.
  999. this.requeueBatchOrDispatchFailure_(
  1000. goog.module.ModuleManager.FailureType.OLD_CODE_GONE);
  1001. this.loadNextModules_();
  1002. } else if (this.consecutiveFailures_ >= 3) {
  1003. goog.log.info(
  1004. this.logger_,
  1005. 'Aborting after failure to load: ' + this.loadingModuleIds_);
  1006. this.requeueBatchOrDispatchFailure_(
  1007. goog.module.ModuleManager.FailureType.CONSECUTIVE_FAILURES);
  1008. this.loadNextModules_();
  1009. } else {
  1010. goog.log.info(
  1011. this.logger_,
  1012. 'Retrying after failure to load: ' + this.loadingModuleIds_);
  1013. var forceReload =
  1014. status == goog.module.ModuleManager.CORRUPT_RESPONSE_STATUS_CODE;
  1015. this.loadModules_(this.requestedLoadingModuleIds_, true, forceReload);
  1016. }
  1017. };
  1018. /**
  1019. * Handles a module load timeout.
  1020. * @private
  1021. */
  1022. goog.module.ModuleManager.prototype.handleLoadTimeout_ = function() {
  1023. goog.log.info(
  1024. this.logger_, 'Aborting after timeout: ' + this.loadingModuleIds_);
  1025. this.requeueBatchOrDispatchFailure_(
  1026. goog.module.ModuleManager.FailureType.TIMEOUT);
  1027. this.loadNextModules_();
  1028. };
  1029. /**
  1030. * Requeues batch loads that had more than one requested module
  1031. * (i.e. modules that were not included as dependencies) as separate loads or
  1032. * if there was only one requested module, fails that module with the received
  1033. * cause.
  1034. * @param {goog.module.ModuleManager.FailureType} cause The reason for the
  1035. * failure.
  1036. * @private
  1037. */
  1038. goog.module.ModuleManager.prototype.requeueBatchOrDispatchFailure_ = function(
  1039. cause) {
  1040. // The load failed, so if there are more than one requested modules, then we
  1041. // need to retry each one as a separate load. Otherwise, if there is only one
  1042. // requested module, remove it and its dependencies from the queue.
  1043. if (this.requestedLoadingModuleIds_.length > 1) {
  1044. var queuedModules = goog.array.map(
  1045. this.requestedLoadingModuleIds_, function(id) { return [id]; });
  1046. this.requestedModuleIdsQueue_ =
  1047. queuedModules.concat(this.requestedModuleIdsQueue_);
  1048. } else {
  1049. this.dispatchModuleLoadFailed_(cause);
  1050. }
  1051. };
  1052. /**
  1053. * Handles when a module load failed.
  1054. * @param {goog.module.ModuleManager.FailureType} cause The reason for the
  1055. * failure.
  1056. * @private
  1057. */
  1058. goog.module.ModuleManager.prototype.dispatchModuleLoadFailed_ = function(
  1059. cause) {
  1060. var failedIds = this.requestedLoadingModuleIds_;
  1061. this.loadingModuleIds_.length = 0;
  1062. // If any pending modules depend on the id that failed,
  1063. // they need to be removed from the queue.
  1064. var idsToCancel = [];
  1065. for (var i = 0; i < this.requestedModuleIdsQueue_.length; i++) {
  1066. var dependentModules = goog.array.filter(
  1067. this.requestedModuleIdsQueue_[i],
  1068. /**
  1069. * Returns true if the requestedId has dependencies on the modules that
  1070. * just failed to load.
  1071. * @param {string} requestedId The module to check for dependencies.
  1072. * @return {boolean} True if the module depends on failed modules.
  1073. */
  1074. function(requestedId) {
  1075. var requestedDeps =
  1076. this.getNotYetLoadedTransitiveDepIds_(requestedId);
  1077. return goog.array.some(failedIds, function(id) {
  1078. return goog.array.contains(requestedDeps, id);
  1079. });
  1080. },
  1081. this);
  1082. goog.array.extend(idsToCancel, dependentModules);
  1083. }
  1084. // Also insert the ids that failed to load as ids to cancel.
  1085. for (var i = 0; i < failedIds.length; i++) {
  1086. goog.array.insert(idsToCancel, failedIds[i]);
  1087. }
  1088. // Remove ids to cancel from the queues.
  1089. for (var i = 0; i < idsToCancel.length; i++) {
  1090. for (var j = 0; j < this.requestedModuleIdsQueue_.length; j++) {
  1091. goog.array.remove(this.requestedModuleIdsQueue_[j], idsToCancel[i]);
  1092. }
  1093. goog.array.remove(this.userInitiatedLoadingModuleIds_, idsToCancel[i]);
  1094. }
  1095. // Call the functions for error notification.
  1096. var errorCallbacks =
  1097. this.callbackMap_[goog.module.ModuleManager.CallbackType.ERROR];
  1098. if (errorCallbacks) {
  1099. for (var i = 0; i < errorCallbacks.length; i++) {
  1100. var callback = errorCallbacks[i];
  1101. for (var j = 0; j < idsToCancel.length; j++) {
  1102. callback(
  1103. goog.module.ModuleManager.CallbackType.ERROR, idsToCancel[j],
  1104. cause);
  1105. }
  1106. }
  1107. }
  1108. // Call the errbacks on the module info.
  1109. for (var i = 0; i < failedIds.length; i++) {
  1110. if (this.moduleInfoMap_[failedIds[i]]) {
  1111. this.moduleInfoMap_[failedIds[i]].onError(cause);
  1112. }
  1113. }
  1114. // Clear the requested loading module ids.
  1115. this.requestedLoadingModuleIds_.length = 0;
  1116. this.dispatchActiveIdleChangeIfNeeded_();
  1117. };
  1118. /**
  1119. * Loads the next modules on the queue.
  1120. * @private
  1121. */
  1122. goog.module.ModuleManager.prototype.loadNextModules_ = function() {
  1123. while (this.requestedModuleIdsQueue_.length) {
  1124. // Remove modules that are already loaded.
  1125. var nextIds = goog.array.filter(
  1126. this.requestedModuleIdsQueue_.shift(),
  1127. function(id) { return !this.getModuleInfo(id).isLoaded(); }, this);
  1128. if (nextIds.length > 0) {
  1129. this.loadModules_(nextIds);
  1130. return;
  1131. }
  1132. }
  1133. // Dispatch an active/idle change if needed.
  1134. this.dispatchActiveIdleChangeIfNeeded_();
  1135. };
  1136. /**
  1137. * The function to call if the module manager is in error.
  1138. * @param
  1139. * {goog.module.ModuleManager.CallbackType|Array<goog.module.ModuleManager.CallbackType>}
  1140. * types
  1141. * The callback type.
  1142. * @param {Function} fn The function to register as a callback.
  1143. */
  1144. goog.module.ModuleManager.prototype.registerCallback = function(types, fn) {
  1145. if (!goog.isArray(types)) {
  1146. types = [types];
  1147. }
  1148. for (var i = 0; i < types.length; i++) {
  1149. this.registerCallback_(types[i], fn);
  1150. }
  1151. };
  1152. /**
  1153. * Register a callback for the specified callback type.
  1154. * @param {goog.module.ModuleManager.CallbackType} type The callback type.
  1155. * @param {Function} fn The callback function.
  1156. * @private
  1157. */
  1158. goog.module.ModuleManager.prototype.registerCallback_ = function(type, fn) {
  1159. var callbackMap = this.callbackMap_;
  1160. if (!callbackMap[type]) {
  1161. callbackMap[type] = [];
  1162. }
  1163. callbackMap[type].push(fn);
  1164. };
  1165. /**
  1166. * Call the callback functions of the specified type.
  1167. * @param {goog.module.ModuleManager.CallbackType} type The callback type.
  1168. * @private
  1169. */
  1170. goog.module.ModuleManager.prototype.executeCallbacks_ = function(type) {
  1171. var callbacks = this.callbackMap_[type];
  1172. for (var i = 0; callbacks && i < callbacks.length; i++) {
  1173. callbacks[i](type);
  1174. }
  1175. };
  1176. /** @override */
  1177. goog.module.ModuleManager.prototype.disposeInternal = function() {
  1178. goog.module.ModuleManager.base(this, 'disposeInternal');
  1179. // Dispose of each ModuleInfo object.
  1180. goog.disposeAll(
  1181. goog.object.getValues(this.moduleInfoMap_), this.baseModuleInfo_);
  1182. this.moduleInfoMap_ = null;
  1183. this.loadingModuleIds_ = null;
  1184. this.requestedLoadingModuleIds_ = null;
  1185. this.userInitiatedLoadingModuleIds_ = null;
  1186. this.requestedModuleIdsQueue_ = null;
  1187. this.callbackMap_ = null;
  1188. };