Path: blob/trunk/third_party/closure/goog/net/jsloader.js
2868 views
// Copyright 2011 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview A utility to load JavaScript files via DOM script tags.16* Refactored from goog.net.Jsonp. Works cross-domain.17*18*/1920goog.provide('goog.net.jsloader');21goog.provide('goog.net.jsloader.Error');22goog.provide('goog.net.jsloader.ErrorCode');23goog.provide('goog.net.jsloader.Options');2425goog.require('goog.array');26goog.require('goog.async.Deferred');27goog.require('goog.debug.Error');28goog.require('goog.dom');29goog.require('goog.dom.TagName');30goog.require('goog.html.TrustedResourceUrl');31goog.require('goog.html.legacyconversions');32goog.require('goog.object');333435/**36* The name of the property of goog.global under which the JavaScript37* verification object is stored by the loaded script.38* @private {string}39*/40goog.net.jsloader.GLOBAL_VERIFY_OBJS_ = 'closure_verification';414243/**44* The default length of time, in milliseconds, we are prepared to wait for a45* load request to complete.46* @type {number}47*/48goog.net.jsloader.DEFAULT_TIMEOUT = 5000;495051/**52* Optional parameters for goog.net.jsloader.send.53* timeout: The length of time, in milliseconds, we are prepared to wait54* for a load request to complete, or 0 or negative for no timeout. Default55* is 5 seconds.56* document: The HTML document under which to load the JavaScript. Default is57* the current document.58* cleanupWhenDone: If true clean up the script tag after script completes to59* load. This is important if you just want to read data from the JavaScript60* and then throw it away. Default is false.61* attributes: Additional attributes to set on the script tag.62*63* @typedef {{64* timeout: (number|undefined),65* document: (HTMLDocument|undefined),66* cleanupWhenDone: (boolean|undefined),67* attributes: (!Object<string, string>|undefined)68* }}69*/70goog.net.jsloader.Options;717273/**74* Scripts (URIs) waiting to be loaded.75* @private {!Array<!goog.html.TrustedResourceUrl>}76*/77goog.net.jsloader.scriptsToLoad_ = [];787980/**81* The deferred result of loading the URIs in scriptsToLoad_.82* We need to return this to a caller that wants to load URIs while83* a deferred is already working on them.84* @private {!goog.async.Deferred<null>}85*/86goog.net.jsloader.scriptLoadingDeferred_;87888990/**91* This is deprecated, please use safeLoadMany.92*93* @param {Array<string>} uris The URIs to load.94* @param {goog.net.jsloader.Options=} opt_options Optional parameters. See95* goog.net.jsloader.options documentation for details.96* @return {!goog.async.Deferred} The deferred result, that may be used to add97* callbacks98* @deprecated99*/100goog.net.jsloader.loadMany = function(uris, opt_options) {101var trustedUris = goog.array.map(102uris, goog.html.legacyconversions.trustedResourceUrlFromString);103return goog.net.jsloader.safeLoadMany(trustedUris, opt_options);104};105106107/**108* Loads and evaluates the JavaScript files at the specified URIs, guaranteeing109* the order of script loads.110*111* Because we have to load the scripts in serial (load script 1, exec script 1,112* load script 2, exec script 2, and so on), this will be slower than doing113* the network fetches in parallel.114*115* If you need to load a large number of scripts but dependency order doesn't116* matter, you should just call goog.net.jsloader.load N times.117*118* If you need to load a large number of scripts on the same domain,119* you may want to use goog.module.ModuleLoader.120*121* @param {Array<!goog.html.TrustedResourceUrl>} trustedUris The URIs to load.122* @param {goog.net.jsloader.Options=} opt_options Optional parameters. See123* goog.net.jsloader.options documentation for details.124* @return {!goog.async.Deferred} The deferred result, that may be used to add125* callbacks126*/127goog.net.jsloader.safeLoadMany = function(trustedUris, opt_options) {128// Loading the scripts in serial introduces asynchronosity into the flow.129// Therefore, there are race conditions where client A can kick off the load130// sequence for client B, even though client A's scripts haven't all been131// loaded yet.132//133// To work around this issue, all module loads share a queue.134if (!trustedUris.length) {135return goog.async.Deferred.succeed(null);136}137138var isAnotherModuleLoading = goog.net.jsloader.scriptsToLoad_.length;139goog.array.extend(goog.net.jsloader.scriptsToLoad_, trustedUris);140if (isAnotherModuleLoading) {141// jsloader is still loading some other scripts.142// In order to prevent the race condition noted above, we just add143// these URIs to the end of the scripts' queue and return the deferred144// result of the ongoing script load, so the caller knows when they145// finish loading.146return goog.net.jsloader.scriptLoadingDeferred_;147}148149trustedUris = goog.net.jsloader.scriptsToLoad_;150var popAndLoadNextScript = function() {151var trustedUri = trustedUris.shift();152var deferred = goog.net.jsloader.safeLoad(trustedUri, opt_options);153if (trustedUris.length) {154deferred.addBoth(popAndLoadNextScript);155}156return deferred;157};158goog.net.jsloader.scriptLoadingDeferred_ = popAndLoadNextScript();159return goog.net.jsloader.scriptLoadingDeferred_;160};161162163/**164* This is deprecated, please use safeLoad instead.165*166* @param {string} uri The URI of the JavaScript.167* @param {goog.net.jsloader.Options=} opt_options Optional parameters. See168* goog.net.jsloader.Options documentation for details.169* @return {!goog.async.Deferred} The deferred result, that may be used to add170* callbacks and/or cancel the transmission.171* The error callback will be called with a single goog.net.jsloader.Error172* parameter.173*/174goog.net.jsloader.load = function(uri, opt_options) {175var trustedUri =176goog.html.legacyconversions.trustedResourceUrlFromString(uri);177return goog.net.jsloader.safeLoad(trustedUri, opt_options);178};179180181/**182* Loads and evaluates a JavaScript file.183* When the script loads, a user callback is called.184* It is the client's responsibility to verify that the script ran successfully.185*186* @param {!goog.html.TrustedResourceUrl} trustedUri The URI of the JavaScript.187* @param {goog.net.jsloader.Options=} opt_options Optional parameters. See188* goog.net.jsloader.Options documentation for details.189* @return {!goog.async.Deferred} The deferred result, that may be used to add190* callbacks and/or cancel the transmission.191* The error callback will be called with a single goog.net.jsloader.Error192* parameter.193*/194goog.net.jsloader.safeLoad = function(trustedUri, opt_options) {195var options = opt_options || {};196var doc = options.document || document;197var uri = goog.html.TrustedResourceUrl.unwrap(trustedUri);198199var script = goog.dom.createElement(goog.dom.TagName.SCRIPT);200var request = {script_: script, timeout_: undefined};201var deferred = new goog.async.Deferred(goog.net.jsloader.cancel_, request);202203// Set a timeout.204var timeout = null;205var timeoutDuration = goog.isDefAndNotNull(options.timeout) ?206options.timeout :207goog.net.jsloader.DEFAULT_TIMEOUT;208if (timeoutDuration > 0) {209timeout = window.setTimeout(function() {210goog.net.jsloader.cleanup_(script, true);211deferred.errback(212new goog.net.jsloader.Error(213goog.net.jsloader.ErrorCode.TIMEOUT,214'Timeout reached for loading script ' + uri));215}, timeoutDuration);216request.timeout_ = timeout;217}218219// Hang the user callback to be called when the script completes to load.220// NOTE(user): This callback will be called in IE even upon error. In any221// case it is the client's responsibility to verify that the script ran222// successfully.223script.onload = script.onreadystatechange = function() {224if (!script.readyState || script.readyState == 'loaded' ||225script.readyState == 'complete') {226var removeScriptNode = options.cleanupWhenDone || false;227goog.net.jsloader.cleanup_(script, removeScriptNode, timeout);228deferred.callback(null);229}230};231232// Add an error callback.233// NOTE(user): Not supported in IE.234script.onerror = function() {235goog.net.jsloader.cleanup_(script, true, timeout);236deferred.errback(237new goog.net.jsloader.Error(238goog.net.jsloader.ErrorCode.LOAD_ERROR,239'Error while loading script ' + uri));240};241242var properties = options.attributes || {};243goog.object.extend(properties, {244'type': 'text/javascript',245'charset': 'UTF-8',246// NOTE(user): Safari never loads the script if we don't set247// the src attribute before appending.248'src': uri249});250goog.dom.setProperties(script, properties);251var scriptParent = goog.net.jsloader.getScriptParentElement_(doc);252scriptParent.appendChild(script);253254return deferred;255};256257258/**259* This function is deprecated, please use safeLoadAndVerify instead.260*261* @param {string} uri The URI of the JavaScript.262* @param {string} verificationObjName The name of the verification object that263* the loaded script should set.264* @param {goog.net.jsloader.Options} options Optional parameters. See265* goog.net.jsloader.Options documentation for details.266* @return {!goog.async.Deferred} The deferred result, that may be used to add267* callbacks and/or cancel the transmission.268* The success callback will be called with a single parameter containing269* the value of the verification object.270* The error callback will be called with a single goog.net.jsloader.Error271* parameter.272* @deprecated273*/274goog.net.jsloader.loadAndVerify = function(uri, verificationObjName, options) {275var trustedUri =276goog.html.legacyconversions.trustedResourceUrlFromString(uri);277return goog.net.jsloader.safeLoadAndVerify(278trustedUri, verificationObjName, options);279};280281282/**283* Loads a JavaScript file and verifies it was evaluated successfully, using a284* verification object.285* The verification object is set by the loaded JavaScript at the end of the286* script.287* We verify this object was set and return its value in the success callback.288* If the object is not defined we trigger an error callback.289*290* @param {!goog.html.TrustedResourceUrl} trustedUri The URI of the JavaScript.291* @param {string} verificationObjName The name of the verification object that292* the loaded script should set.293* @param {goog.net.jsloader.Options} options Optional parameters. See294* goog.net.jsloader.Options documentation for details.295* @return {!goog.async.Deferred} The deferred result, that may be used to add296* callbacks and/or cancel the transmission.297* The success callback will be called with a single parameter containing298* the value of the verification object.299* The error callback will be called with a single goog.net.jsloader.Error300* parameter.301*/302goog.net.jsloader.safeLoadAndVerify = function(303trustedUri, verificationObjName, options) {304// Define the global objects variable.305if (!goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_]) {306goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_] = {};307}308var verifyObjs = goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_];309var uri = goog.html.TrustedResourceUrl.unwrap(trustedUri);310311// Verify that the expected object does not exist yet.312if (goog.isDef(verifyObjs[verificationObjName])) {313// TODO(user): Error or reset variable?314return goog.async.Deferred.fail(315new goog.net.jsloader.Error(316goog.net.jsloader.ErrorCode.VERIFY_OBJECT_ALREADY_EXISTS,317'Verification object ' + verificationObjName +318' already defined.'));319}320321// Send request to load the JavaScript.322var sendDeferred = goog.net.jsloader.safeLoad(trustedUri, options);323324// Create a deferred object wrapping the send result.325var deferred =326new goog.async.Deferred(goog.bind(sendDeferred.cancel, sendDeferred));327328// Call user back with object that was set by the script.329sendDeferred.addCallback(function() {330var result = verifyObjs[verificationObjName];331if (goog.isDef(result)) {332deferred.callback(result);333delete verifyObjs[verificationObjName];334} else {335// Error: script was not loaded properly.336deferred.errback(337new goog.net.jsloader.Error(338goog.net.jsloader.ErrorCode.VERIFY_ERROR, 'Script ' + uri +339' loaded, but verification object ' + verificationObjName +340' was not defined.'));341}342});343344// Pass error to new deferred object.345sendDeferred.addErrback(function(error) {346if (goog.isDef(verifyObjs[verificationObjName])) {347delete verifyObjs[verificationObjName];348}349deferred.errback(error);350});351352return deferred;353};354355356/**357* Gets the DOM element under which we should add new script elements.358* How? Take the first head element, and if not found take doc.documentElement,359* which always exists.360*361* @param {!HTMLDocument} doc The relevant document.362* @return {!Element} The script parent element.363* @private364*/365goog.net.jsloader.getScriptParentElement_ = function(doc) {366var headElements = goog.dom.getElementsByTagName(goog.dom.TagName.HEAD, doc);367if (!headElements || goog.array.isEmpty(headElements)) {368return doc.documentElement;369} else {370return headElements[0];371}372};373374375/**376* Cancels a given request.377* @this {{script_: Element, timeout_: number}} The request context.378* @private379*/380goog.net.jsloader.cancel_ = function() {381var request = this;382if (request && request.script_) {383var scriptNode = request.script_;384if (scriptNode && scriptNode.tagName == goog.dom.TagName.SCRIPT) {385goog.net.jsloader.cleanup_(scriptNode, true, request.timeout_);386}387}388};389390391/**392* Removes the script node and the timeout.393*394* @param {Node} scriptNode The node to be cleaned up.395* @param {boolean} removeScriptNode If true completely remove the script node.396* @param {?number=} opt_timeout The timeout handler to cleanup.397* @private398*/399goog.net.jsloader.cleanup_ = function(400scriptNode, removeScriptNode, opt_timeout) {401if (goog.isDefAndNotNull(opt_timeout)) {402goog.global.clearTimeout(opt_timeout);403}404405scriptNode.onload = goog.nullFunction;406scriptNode.onerror = goog.nullFunction;407scriptNode.onreadystatechange = goog.nullFunction;408409// Do this after a delay (removing the script node of a running script can410// confuse older IEs).411if (removeScriptNode) {412window.setTimeout(function() { goog.dom.removeNode(scriptNode); }, 0);413}414};415416417/**418* Possible error codes for jsloader.419* @enum {number}420*/421goog.net.jsloader.ErrorCode = {422LOAD_ERROR: 0,423TIMEOUT: 1,424VERIFY_ERROR: 2,425VERIFY_OBJECT_ALREADY_EXISTS: 3426};427428429430/**431* A jsloader error.432*433* @param {goog.net.jsloader.ErrorCode} code The error code.434* @param {string=} opt_message Additional message.435* @constructor436* @extends {goog.debug.Error}437* @final438*/439goog.net.jsloader.Error = function(code, opt_message) {440var msg = 'Jsloader error (code #' + code + ')';441if (opt_message) {442msg += ': ' + opt_message;443}444goog.net.jsloader.Error.base(this, 'constructor', msg);445446/**447* The code for this error.448*449* @type {goog.net.jsloader.ErrorCode}450*/451this.code = code;452};453goog.inherits(goog.net.jsloader.Error, goog.debug.Error);454455456