// 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 Defines the goog.module.ModuleInfo class. * */ goog.provide('goog.module.ModuleInfo'); goog.require('goog.Disposable'); goog.require('goog.async.throwException'); goog.require('goog.functions'); /** @suppress {extraRequire} */ goog.require('goog.module'); goog.require('goog.module.BaseModule'); goog.require('goog.module.ModuleLoadCallback'); // TODO(johnlenz): goog.module.ModuleManager.FailureType into its own file. goog.forwardDeclare('goog.module.ModuleManager.FailureType'); /** * A ModuleInfo object is used by the ModuleManager to hold information about a * module of js code that may or may not yet be loaded into the environment. * * @param {Array} deps Ids of the modules that must be loaded before * this one. The ids must be in dependency order (i.e. if the ith module * depends on the jth module, then i > j). * @param {string} id The module's ID. * @constructor * @extends {goog.Disposable} * @final */ goog.module.ModuleInfo = function(deps, id) { goog.Disposable.call(this); /** * A list of the ids of the modules that must be loaded before this module. * @type {Array} * @private */ this.deps_ = deps; /** * The module's ID. * @type {string} * @private */ this.id_ = id; /** * Callbacks to execute once this module is loaded. * @type {Array} * @private */ this.onloadCallbacks_ = []; /** * Callbacks to execute if the module load errors. * @type {Array} * @private */ this.onErrorCallbacks_ = []; /** * Early callbacks to execute once this module is loaded. Called after * module initialization but before regular onload callbacks. * @type {Array} * @private */ this.earlyOnloadCallbacks_ = []; }; goog.inherits(goog.module.ModuleInfo, goog.Disposable); /** * The uris that can be used to retrieve this module's code. * @type {Array?} * @private */ goog.module.ModuleInfo.prototype.uris_ = null; /** * The constructor to use to instantiate the module object after the module * code is loaded. This must be either goog.module.BaseModule or a subclass of * it. * @type {Function} * @private */ goog.module.ModuleInfo.prototype.moduleConstructor_ = goog.module.BaseModule; /** * The module object. This will be null until the module is loaded. * @type {goog.module.BaseModule?} * @private */ goog.module.ModuleInfo.prototype.module_ = null; /** * Gets the dependencies of this module. * @return {Array} The ids of the modules that this module depends on. */ goog.module.ModuleInfo.prototype.getDependencies = function() { return this.deps_; }; /** * Gets the ID of this module. * @return {string} The ID. */ goog.module.ModuleInfo.prototype.getId = function() { return this.id_; }; /** * Sets the uris of this module. * @param {Array} uris Uris for this module's code. */ goog.module.ModuleInfo.prototype.setUris = function(uris) { this.uris_ = uris; }; /** * Gets the uris of this module. * @return {Array?} Uris for this module's code. */ goog.module.ModuleInfo.prototype.getUris = function() { return this.uris_; }; /** * Sets the constructor to use to instantiate the module object after the * module code is loaded. * @param {Function} constructor The constructor of a goog.module.BaseModule * subclass. */ goog.module.ModuleInfo.prototype.setModuleConstructor = function(constructor) { if (this.moduleConstructor_ === goog.module.BaseModule) { this.moduleConstructor_ = constructor; } else { throw Error('Cannot set module constructor more than once.'); } }; /** * Registers a function that should be called after the module is loaded. These * early callbacks are called after {@link Module#initialize} is called but * before the other 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. * @return {!goog.module.ModuleLoadCallback} Reference to the callback * object. */ goog.module.ModuleInfo.prototype.registerEarlyCallback = function( fn, opt_handler) { return this.registerCallback_(this.earlyOnloadCallbacks_, fn, opt_handler); }; /** * Registers a function that should be called after the module is loaded. * @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. * @return {!goog.module.ModuleLoadCallback} Reference to the callback * object. */ goog.module.ModuleInfo.prototype.registerCallback = function(fn, opt_handler) { return this.registerCallback_(this.onloadCallbacks_, fn, opt_handler); }; /** * Registers a function that should be called if the module load fails. * @param {Function} fn A callback function that takes a single argument which * is the failure type. * @param {Object=} opt_handler Optional handler under whose scope to execute * the callback. * @return {!goog.module.ModuleLoadCallback} Reference to the callback * object. */ goog.module.ModuleInfo.prototype.registerErrback = function(fn, opt_handler) { return this.registerCallback_(this.onErrorCallbacks_, fn, opt_handler); }; /** * Registers a function that should be called after the module is loaded. * @param {Array} callbacks The array to * add the callback to. * @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. * @return {!goog.module.ModuleLoadCallback} Reference to the callback * object. * @private */ goog.module.ModuleInfo.prototype.registerCallback_ = function( callbacks, fn, opt_handler) { var callback = new goog.module.ModuleLoadCallback(fn, opt_handler); callbacks.push(callback); return callback; }; /** * Determines whether the module has been loaded. * @return {boolean} Whether the module has been loaded. */ goog.module.ModuleInfo.prototype.isLoaded = function() { return !!this.module_; }; /** * Gets the module. * @return {goog.module.BaseModule?} The module if it has been loaded. * Otherwise, null. */ goog.module.ModuleInfo.prototype.getModule = function() { return this.module_; }; /** * Sets this module as loaded. * @param {function() : Object} contextProvider A function that provides the * module context. * @return {boolean} Whether any errors occurred while executing the onload * callbacks. */ goog.module.ModuleInfo.prototype.onLoad = function(contextProvider) { // Instantiate and initialize the module object. var module = new this.moduleConstructor_; module.initialize(contextProvider()); // Keep an internal reference to the module. this.module_ = module; // Fire any early callbacks that were waiting for the module to be loaded. var errors = !!this.callCallbacks_(this.earlyOnloadCallbacks_, contextProvider()); // Fire any callbacks that were waiting for the module to be loaded. errors = errors || !!this.callCallbacks_(this.onloadCallbacks_, contextProvider()); if (!errors) { // Clear the errbacks. this.onErrorCallbacks_.length = 0; } return errors; }; /** * Calls the error callbacks for the module. * @param {goog.module.ModuleManager.FailureType} cause What caused the error. */ goog.module.ModuleInfo.prototype.onError = function(cause) { var result = this.callCallbacks_(this.onErrorCallbacks_, cause); if (result) { // Throw an exception asynchronously. Do not let the exception leak // up to the caller, or it will blow up the module loading framework. window.setTimeout( goog.functions.error('Module errback failures: ' + result), 0); } this.earlyOnloadCallbacks_.length = 0; this.onloadCallbacks_.length = 0; }; /** * Helper to call the callbacks after module load. * @param {Array} callbacks The callbacks * to call and then clear. * @param {*} context The module context. * @return {Array<*>} Any errors encountered while calling the callbacks, * or null if there were no errors. * @private */ goog.module.ModuleInfo.prototype.callCallbacks_ = function(callbacks, context) { // NOTE(nicksantos): // In practice, there are two error-handling scenarios: // 1) The callback does some mandatory initialization of the module. // 2) The callback is for completion of some optional UI event. // There's no good way to handle both scenarios. // // Our strategy here is to protect module manager from exceptions, so that // the failure of one module doesn't affect the loading of other modules. // Errors are thrown outside of the current stack frame, so they still // get reported but don't interrupt execution. // Call each callback in the order they were registered var errors = []; for (var i = 0; i < callbacks.length; i++) { try { callbacks[i].execute(context); } catch (e) { goog.async.throwException(e); errors.push(e); } } // Clear the list of callbacks. callbacks.length = 0; return errors.length ? errors : null; }; /** @override */ goog.module.ModuleInfo.prototype.disposeInternal = function() { goog.module.ModuleInfo.superClass_.disposeInternal.call(this); goog.dispose(this.module_); };