123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518 |
- // 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 The module loader for loading modules across the network.
- *
- * Browsers do not guarantee that scripts appended to the document
- * are executed in the order they are added. For production mode, we use
- * XHRs to load scripts, because they do not have this problem and they
- * have superior mechanisms for handling failure. However, XHR-evaled
- * scripts are harder to debug.
- *
- * In debugging mode, we use normal script tags. In order to make this work,
- * we load the scripts in serial: we do not execute script B to the document
- * until we are certain that script A is finished loading.
- *
- */
- goog.provide('goog.module.ModuleLoader');
- goog.require('goog.Timer');
- goog.require('goog.array');
- goog.require('goog.events');
- goog.require('goog.events.Event');
- goog.require('goog.events.EventHandler');
- goog.require('goog.events.EventId');
- goog.require('goog.events.EventTarget');
- goog.require('goog.labs.userAgent.browser');
- goog.require('goog.log');
- goog.require('goog.module.AbstractModuleLoader');
- goog.require('goog.net.BulkLoader');
- goog.require('goog.net.EventType');
- goog.require('goog.net.jsloader');
- goog.require('goog.userAgent');
- goog.require('goog.userAgent.product');
- /**
- * A class that loads Javascript modules.
- * @constructor
- * @extends {goog.events.EventTarget}
- * @implements {goog.module.AbstractModuleLoader}
- */
- goog.module.ModuleLoader = function() {
- goog.module.ModuleLoader.base(this, 'constructor');
- /**
- * Event handler for managing handling events.
- * @type {goog.events.EventHandler<!goog.module.ModuleLoader>}
- * @private
- */
- this.eventHandler_ = new goog.events.EventHandler(this);
- /**
- * A map from module IDs to goog.module.ModuleLoader.LoadStatus.
- * @type {!Object<Array<string>, goog.module.ModuleLoader.LoadStatus>}
- * @private
- */
- this.loadingModulesStatus_ = {};
- };
- goog.inherits(goog.module.ModuleLoader, goog.events.EventTarget);
- /**
- * A logger.
- * @type {goog.log.Logger}
- * @protected
- */
- goog.module.ModuleLoader.prototype.logger =
- goog.log.getLogger('goog.module.ModuleLoader');
- /**
- * Whether debug mode is enabled.
- * @type {boolean}
- * @private
- */
- goog.module.ModuleLoader.prototype.debugMode_ = false;
- /**
- * Whether source url injection is enabled.
- * @type {boolean}
- * @private
- */
- goog.module.ModuleLoader.prototype.sourceUrlInjection_ = false;
- /**
- * @return {boolean} Whether sourceURL affects stack traces.
- */
- goog.module.ModuleLoader.supportsSourceUrlStackTraces = function() {
- return goog.userAgent.product.CHROME ||
- (goog.labs.userAgent.browser.isFirefox() &&
- goog.labs.userAgent.browser.isVersionOrHigher('36'));
- };
- /**
- * @return {boolean} Whether sourceURL affects the debugger.
- */
- goog.module.ModuleLoader.supportsSourceUrlDebugger = function() {
- return goog.userAgent.product.CHROME || goog.userAgent.GECKO;
- };
- /**
- * Gets the debug mode for the loader.
- * @return {boolean} Whether the debug mode is enabled.
- */
- goog.module.ModuleLoader.prototype.getDebugMode = function() {
- return this.debugMode_;
- };
- /**
- * Sets the debug mode for the loader.
- * @param {boolean} debugMode Whether the debug mode is enabled.
- */
- goog.module.ModuleLoader.prototype.setDebugMode = function(debugMode) {
- this.debugMode_ = debugMode;
- };
- /**
- * When enabled, we will add a sourceURL comment to the end of all scripts
- * to mark their origin.
- *
- * On WebKit, stack traces will reflect the sourceURL comment, so this is
- * useful for debugging webkit stack traces in production.
- *
- * Notice that in debug mode, we will use source url injection + eval rather
- * then appending script nodes to the DOM, because the scripts will load far
- * faster. (Appending script nodes is very slow, because we can't parallelize
- * the downloading and evaling of the script).
- *
- * The cost of appending sourceURL information is negligible when compared to
- * the cost of evaling the script. Almost all clients will want this on.
- *
- * TODO(nicksantos): Turn this on by default. We may want to turn this off
- * for clients that inject their own sourceURL.
- *
- * @param {boolean} enabled Whether source url injection is enabled.
- */
- goog.module.ModuleLoader.prototype.setSourceUrlInjection = function(enabled) {
- this.sourceUrlInjection_ = enabled;
- };
- /**
- * @return {boolean} Whether we're using source url injection.
- * @private
- */
- goog.module.ModuleLoader.prototype.usingSourceUrlInjection_ = function() {
- return this.sourceUrlInjection_ ||
- (this.getDebugMode() &&
- goog.module.ModuleLoader.supportsSourceUrlStackTraces());
- };
- /** @override */
- goog.module.ModuleLoader.prototype.loadModules = function(
- ids, moduleInfoMap, opt_successFn, opt_errorFn, opt_timeoutFn,
- opt_forceReload) {
- var loadStatus = this.loadingModulesStatus_[ids] ||
- new goog.module.ModuleLoader.LoadStatus();
- loadStatus.loadRequested = true;
- loadStatus.successFn = opt_successFn || null;
- loadStatus.errorFn = opt_errorFn || null;
- if (!this.loadingModulesStatus_[ids]) {
- // Modules were not prefetched.
- this.loadingModulesStatus_[ids] = loadStatus;
- this.downloadModules_(ids, moduleInfoMap);
- // TODO(user): Need to handle timeouts in the module loading code.
- } else if (goog.isDefAndNotNull(loadStatus.responseTexts)) {
- // Modules prefetch is complete.
- this.evaluateCode_(ids);
- }
- // Otherwise modules prefetch is in progress, and these modules will be
- // executed after the prefetch is complete.
- };
- /**
- * Evaluate the JS code.
- * @param {Array<string>} moduleIds The module ids.
- * @private
- */
- goog.module.ModuleLoader.prototype.evaluateCode_ = function(moduleIds) {
- this.dispatchEvent(
- new goog.module.ModuleLoader.RequestSuccessEvent(moduleIds));
- goog.log.info(this.logger, 'evaluateCode ids:' + moduleIds);
- var loadStatus = this.loadingModulesStatus_[moduleIds];
- var uris = loadStatus.requestUris;
- var texts = loadStatus.responseTexts;
- var error = null;
- try {
- if (this.usingSourceUrlInjection_()) {
- for (var i = 0; i < uris.length; i++) {
- var uri = uris[i];
- goog.globalEval(texts[i] + ' //# sourceURL=' + uri);
- }
- } else {
- goog.globalEval(texts.join('\n'));
- }
- } catch (e) {
- error = e;
- // TODO(user): Consider throwing an exception here.
- goog.log.warning(
- this.logger, 'Loaded incomplete code for module(s): ' + moduleIds, e);
- }
- this.dispatchEvent(new goog.module.ModuleLoader.EvaluateCodeEvent(moduleIds));
- if (error) {
- this.handleErrorHelper_(
- moduleIds, loadStatus.errorFn, null /* status */, error);
- } else if (loadStatus.successFn) {
- loadStatus.successFn();
- }
- delete this.loadingModulesStatus_[moduleIds];
- };
- /**
- * Handles a successful response to a request for prefetch or load one or more
- * modules.
- *
- * @param {goog.net.BulkLoader} bulkLoader The bulk loader.
- * @param {Array<string>} moduleIds The ids of the modules requested.
- * @private
- */
- goog.module.ModuleLoader.prototype.handleSuccess_ = function(
- bulkLoader, moduleIds) {
- goog.log.info(this.logger, 'Code loaded for module(s): ' + moduleIds);
- var loadStatus = this.loadingModulesStatus_[moduleIds];
- loadStatus.responseTexts = bulkLoader.getResponseTexts();
- if (loadStatus.loadRequested) {
- this.evaluateCode_(moduleIds);
- }
- // NOTE: A bulk loader instance is used for loading a set of module ids.
- // Once these modules have been loaded successfully or in error the bulk
- // loader should be disposed as it is not needed anymore. A new bulk loader
- // is instantiated for any new modules to be loaded. The dispose is called
- // on a timer so that the bulkloader has a chance to release its
- // objects.
- goog.Timer.callOnce(bulkLoader.dispose, 5, bulkLoader);
- };
- /** @override */
- goog.module.ModuleLoader.prototype.prefetchModule = function(id, moduleInfo) {
- // Do not prefetch in debug mode.
- if (this.getDebugMode()) {
- return;
- }
- var loadStatus = this.loadingModulesStatus_[[id]];
- if (loadStatus) {
- return;
- }
- var moduleInfoMap = {};
- moduleInfoMap[id] = moduleInfo;
- this.loadingModulesStatus_[[id]] = new goog.module.ModuleLoader.LoadStatus();
- this.downloadModules_([id], moduleInfoMap);
- };
- /**
- * Downloads a list of JavaScript modules.
- *
- * @param {Array<string>} ids The module ids in dependency order.
- * @param {Object} moduleInfoMap A mapping from module id to ModuleInfo object.
- * @private
- */
- goog.module.ModuleLoader.prototype.downloadModules_ = function(
- ids, moduleInfoMap) {
- var uris = [];
- for (var i = 0; i < ids.length; i++) {
- goog.array.extend(uris, moduleInfoMap[ids[i]].getUris());
- }
- goog.log.info(this.logger, 'downloadModules ids:' + ids + ' uris:' + uris);
- if (this.getDebugMode() && !this.usingSourceUrlInjection_()) {
- // In debug mode use <script> tags rather than XHRs to load the files.
- // This makes it possible to debug and inspect stack traces more easily.
- // It's also possible to use it to load JavaScript files that are hosted on
- // another domain.
- // The scripts need to load serially, so this is much slower than parallel
- // script loads with source url injection.
- goog.net.jsloader.loadMany(uris);
- } else {
- var loadStatus = this.loadingModulesStatus_[ids];
- loadStatus.requestUris = uris;
- var bulkLoader = new goog.net.BulkLoader(uris);
- var eventHandler = this.eventHandler_;
- eventHandler.listen(
- bulkLoader, goog.net.EventType.SUCCESS,
- goog.bind(this.handleSuccess_, this, bulkLoader, ids));
- eventHandler.listen(
- bulkLoader, goog.net.EventType.ERROR,
- goog.bind(this.handleError_, this, bulkLoader, ids));
- bulkLoader.load();
- }
- };
- /**
- * Handles an error during a request for one or more modules.
- * @param {goog.net.BulkLoader} bulkLoader The bulk loader.
- * @param {Array<string>} moduleIds The ids of the modules requested.
- * @param {number} status The response status.
- * @private
- */
- goog.module.ModuleLoader.prototype.handleError_ = function(
- bulkLoader, moduleIds, status) {
- var loadStatus = this.loadingModulesStatus_[moduleIds];
- // The bulk loader doesn't cancel other requests when a request fails. We will
- // delete the loadStatus in the first failure, so it will be undefined in
- // subsequent errors.
- if (loadStatus) {
- delete this.loadingModulesStatus_[moduleIds];
- this.handleErrorHelper_(moduleIds, loadStatus.errorFn, status);
- }
- // NOTE: A bulk loader instance is used for loading a set of module ids. Once
- // these modules have been loaded successfully or in error the bulk loader
- // should be disposed as it is not needed anymore. A new bulk loader is
- // instantiated for any new modules to be loaded. The dispose is called
- // on another thread so that the bulkloader has a chance to release its
- // objects.
- goog.Timer.callOnce(bulkLoader.dispose, 5, bulkLoader);
- };
- /**
- * Handles an error during a request for one or more modules.
- * @param {Array<string>} moduleIds The ids of the modules requested.
- * @param {?function(?number)} errorFn The function to call on failure.
- * @param {?number} status The response status.
- * @param {!Error=} opt_error The error encountered, if available.
- * @private
- */
- goog.module.ModuleLoader.prototype.handleErrorHelper_ = function(
- moduleIds, errorFn, status, opt_error) {
- this.dispatchEvent(
- new goog.module.ModuleLoader.RequestErrorEvent(moduleIds, opt_error));
- goog.log.warning(this.logger, 'Request failed for module(s): ' + moduleIds);
- if (errorFn) {
- errorFn(status);
- }
- };
- /** @override */
- goog.module.ModuleLoader.prototype.disposeInternal = function() {
- goog.module.ModuleLoader.superClass_.disposeInternal.call(this);
- this.eventHandler_.dispose();
- this.eventHandler_ = null;
- };
- /**
- * Events dispatched by the ModuleLoader.
- * @const
- */
- goog.module.ModuleLoader.EventType = {
- /**
- * @const {!goog.events.EventId<
- * !goog.module.ModuleLoader.EvaluateCodeEvent>} Called after the code for
- * a module is evaluated.
- */
- EVALUATE_CODE:
- new goog.events.EventId(goog.events.getUniqueId('evaluateCode')),
- /**
- * @const {!goog.events.EventId<
- * !goog.module.ModuleLoader.RequestSuccessEvent>} Called when the
- * BulkLoader finishes successfully.
- */
- REQUEST_SUCCESS:
- new goog.events.EventId(goog.events.getUniqueId('requestSuccess')),
- /**
- * @const {!goog.events.EventId<
- * !goog.module.ModuleLoader.RequestErrorEvent>} Called when the
- * BulkLoader fails, or code loading fails.
- */
- REQUEST_ERROR:
- new goog.events.EventId(goog.events.getUniqueId('requestError'))
- };
- /**
- * @param {Array<string>} moduleIds The ids of the modules being evaluated.
- * @constructor
- * @extends {goog.events.Event}
- * @final
- * @protected
- */
- goog.module.ModuleLoader.EvaluateCodeEvent = function(moduleIds) {
- goog.module.ModuleLoader.EvaluateCodeEvent.base(
- this, 'constructor', goog.module.ModuleLoader.EventType.EVALUATE_CODE);
- /**
- * @type {Array<string>}
- */
- this.moduleIds = moduleIds;
- };
- goog.inherits(goog.module.ModuleLoader.EvaluateCodeEvent, goog.events.Event);
- /**
- * @param {Array<string>} moduleIds The ids of the modules being evaluated.
- * @constructor
- * @extends {goog.events.Event}
- * @final
- * @protected
- */
- goog.module.ModuleLoader.RequestSuccessEvent = function(moduleIds) {
- goog.module.ModuleLoader.RequestSuccessEvent.base(
- this, 'constructor', goog.module.ModuleLoader.EventType.REQUEST_SUCCESS);
- /**
- * @type {Array<string>}
- */
- this.moduleIds = moduleIds;
- };
- goog.inherits(goog.module.ModuleLoader.RequestSuccessEvent, goog.events.Event);
- /**
- * @param {Array<string>} moduleIds The ids of the modules being evaluated.
- * @param {!Error=} opt_error The error encountered, if available.
- * @constructor
- * @extends {goog.events.Event}
- * @final
- * @protected
- */
- goog.module.ModuleLoader.RequestErrorEvent = function(moduleIds, opt_error) {
- goog.module.ModuleLoader.RequestErrorEvent.base(
- this, 'constructor', goog.module.ModuleLoader.EventType.REQUEST_ERROR);
- /**
- * @type {Array<string>}
- */
- this.moduleIds = moduleIds;
- /** @type {?Error} */
- this.error = opt_error || null;
- };
- goog.inherits(goog.module.ModuleLoader.RequestErrorEvent, goog.events.Event);
- /**
- * A class that keeps the state of the module during the loading process. It is
- * used to save loading information between modules download and evaluation.
- * @constructor
- * @final
- */
- goog.module.ModuleLoader.LoadStatus = function() {
- /**
- * The request uris.
- * @type {Array<string>}
- */
- this.requestUris = null;
- /**
- * The response texts.
- * @type {Array<string>}
- */
- this.responseTexts = null;
- /**
- * Whether loadModules was called for the set of modules referred by this
- * status.
- * @type {boolean}
- */
- this.loadRequested = false;
- /**
- * Success callback.
- * @type {?function()}
- */
- this.successFn = null;
- /**
- * Error callback.
- * @type {?function(?number)}
- */
- this.errorFn = null;
- };
|