/Client/MicrosoftAjax/Extensions/Sys/ScriptLoader.js
http://ajaxcontroltoolkit.codeplex.com · JavaScript · 322 lines · 211 code · 28 blank · 83 comment · 35 complexity · f07cf2348f149696d9aef1b969fcc8fa MD5 · raw file
- // This ScriptLoader works by injecting script tags into the DOM sequentially, waiting for each script
- // to finish loading before proceeding to the next one.
- // It supports a timeout which applies to ALL scripts.
-
- $type = Sys._ScriptLoader = function _ScriptLoader() {
- this._scriptsToLoad = null;
- this._sessions = [];
- this._scriptLoadedDelegate = Function.createDelegate(this, this._scriptLoadedHandler);
- }
- $type.prototype = {
- dispose: function _ScriptLoader$dispose() {
- this._stopSession();
- this._loading = false;
- if(this._events) {
- delete this._events;
- }
- this._sessions = null;
- this._currentSession = null;
- this._scriptLoadedDelegate = null;
- },
-
- loadScripts: function _ScriptLoader$loadScripts(scriptTimeout, allScriptsLoadedCallback, scriptLoadFailedCallback, scriptLoadTimeoutCallback) {
- /// <summary locid="M:J#Sys._ScriptLoader.loadScripts">Begins loading scripts that have been queued.</summary>
- /// <param name="scriptTimeout" type="Number" integer="true">Timeout in seconds for loading all scripts.</param>
- /// <param name="allScriptsLoadedCallback" type="Function" mayBeNull="true">Callback for notification when all scripts have successfully loaded.</param>
- /// <param name="scriptLoadFailedCallback" type="Function" mayBeNull="true">Callback for notification when a script fails to load.</param>
- /// <param name="scriptLoadTimeoutCallback" type="Function" mayBeNull="true">Callback for notification when scripts have not finished loading within the given timeout.</param>
- //#if DEBUG
- var e = Function._validateParams(arguments, [
- {name: "scriptTimeout", type: Number, integer: true},
- {name: "allScriptsLoadedCallback", type: Function, mayBeNull: true},
- {name: "scriptLoadFailedCallback", type: Function, mayBeNull: true},
- {name: "scriptLoadTimeoutCallback", type: Function, mayBeNull: true}
- ]);
- if (e) throw e;
- //#endif
- var session = {
- allScriptsLoadedCallback: allScriptsLoadedCallback,
- scriptLoadFailedCallback: scriptLoadFailedCallback,
- scriptLoadTimeoutCallback: scriptLoadTimeoutCallback,
- scriptsToLoad: this._scriptsToLoad,
- scriptTimeout: scriptTimeout };
- this._scriptsToLoad = null;
- this._sessions.push(session);
-
- if (!this._loading) {
- this._nextSession();
- }
- },
-
- queueCustomScriptTag: function _ScriptLoader$queueCustomScriptTag(scriptAttributes) {
- /// <summary locid="M:J#Sys._ScriptLoader.queueCustomScriptTag">Queues a script reference with the given set of custom script element attributes.</summary>
- /// <param name="scriptAttributes" mayBeNull="false">A JSON object that describtes the attributes to apply to the script element.</param>
- //#if DEBUG
- var e = Function._validateParams(arguments, [
- {name: "scriptAttributes"}
- ]);
- if (e) throw e;
- //#endif
- if(!this._scriptsToLoad) {
- this._scriptsToLoad = [];
- }
- Array.add(this._scriptsToLoad, scriptAttributes);
- },
-
- queueScriptBlock: function _ScriptLoader$queueScriptBlock(scriptContent) {
- /// <summary locid="M:J#Sys._ScriptLoader.queueScriptBlock">Queues a script reference with literal script.</summary>
- /// <param name="scriptContent" type="String" mayBeNull="false">Literal script to execute.</param>
- //#if DEBUG
- var e = Function._validateParams(arguments, [
- {name: "scriptContent", type: String}
- ]);
- if (e) throw e;
- //#endif
- if(!this._scriptsToLoad) {
- this._scriptsToLoad = [];
- }
- Array.add(this._scriptsToLoad, {text: scriptContent});
- },
-
- queueScriptReference: function _ScriptLoader$queueScriptReference(scriptUrl) {
- /// <summary locid="M:J#Sys._ScriptLoader.queueScriptReference">Queues a script reference to the given script URL.</summary>
- /// <param name="scriptUrl" type="String" mayBeNull="false">URL to the script to reference.</param>
- //#if DEBUG
- var e = Function._validateParams(arguments, [
- {name: "scriptUrl", type: String}
- ]);
- if (e) throw e;
- //#endif
- if(!this._scriptsToLoad) {
- this._scriptsToLoad = [];
- }
- Array.add(this._scriptsToLoad, {src: scriptUrl});
- },
-
- _createScriptElement: function _ScriptLoader$_createScriptElement(queuedScript) {
- var scriptElement = document.createElement('script');
-
- // Initialize default script type to JavaScript - but it might get overwritten
- // if a custom script tag has a different type attribute.
- scriptElement.type = 'text/javascript';
-
- // Apply script element attributes
- for (var attr in queuedScript) {
- scriptElement[attr] = queuedScript[attr];
- }
-
- return scriptElement;
- },
-
- _loadScriptsInternal: function _ScriptLoader$_loadScriptsInternal() {
- var session = this._currentSession;
- // Load up the next script in the list
- if (session.scriptsToLoad && session.scriptsToLoad.length > 0) {
- var nextScript = Array.dequeue(session.scriptsToLoad);
- // Inject a script element into the DOM
- var scriptElement = this._createScriptElement(nextScript);
-
- if (scriptElement.text && Sys.Browser.agent === Sys.Browser.Safari) {
- // Safari requires the inline script to be in the innerHTML attribute
- scriptElement.innerHTML = scriptElement.text;
- delete scriptElement.text;
- }
-
- // AtlasWhidbey 36149: If they queue an empty script block "", we can't tell the difference between
- // a script block queue entry and a src entry with just if(!element.text).
- // dont use scriptElement.src --> FF resolves that to the current directory, IE leaves it blank.
- // nextScript.src is always a string if it's a non block script.
- if (typeof(nextScript.src) === "string") {
- // We only need to worry about timing out and loading if the script tag has a 'src'.
- this._currentTask = new Sys._ScriptLoaderTask(scriptElement, this._scriptLoadedDelegate);
- // note: task is responsible for disposing of _itself_. This is necessary so that the ScriptLoader can continue
- // with script loading after a script notifies it has loaded. The task sticks around until the dom element finishes
- // completely, and disposes itself automatically.
- // note: its possible for notify to occur before this method even returns in IE! So it should remain the last possible statement.
- this._currentTask.execute();
- }
- else {
- // script is literal script, so just load the script by adding the new element to the DOM
- // DevDiv Bugs 146697: use lowercase names on getElementsByTagName to work with xhtml content type
- //#if DEBUG
- // DevDiv Bugs 146327: In debug mode, report useful error message for pages without <head> element
- var headElements = document.getElementsByTagName('head');
- if (headElements.length === 0) {
- throw new Error.invalidOperation(Sys.Res.scriptLoadFailedNoHead);
- }
- else {
- headElements[0].appendChild(scriptElement);
- }
- //#else
- document.getElementsByTagName('head')[0].appendChild(scriptElement);
- //#endif
-
- // DevDiv 157097: Removed setTimeout, assuming the script executed synchronously.
- // Previously the setTimeout worked around a Firefox bug where the script was not
- // executed immediately when the element is appended. However, that bug was fixed,
- // and the timeout causes a significant hit to performance. The timeout also causes
- // an inconsistency in the order of events between regular and async requests. With the
- // timeout, it would be possible for delayed operations in component initialize methods
- // to complete before the app loaded event is raised, where that is not possible on a
- // GET because the loaded event is raised within the same execution chain that executes
- // the initialize methods.
-
- // cleanup (removes the script element in release mode).
- Sys._ScriptLoaderTask._clearScript(scriptElement);
- // Resume script loading progress.
- this._loadScriptsInternal();
- }
- }
- else {
- // When there are no more scripts to load, call the final event
- this._stopSession();
- var callback = session.allScriptsLoadedCallback;
- if(callback) {
- callback(this);
- }
- // and move on to the next session, if any
- this._nextSession();
- }
- },
-
- _nextSession: function _ScriptLoader$_nextSession() {
- if (this._sessions.length === 0) {
- this._loading = false;
- this._currentSession = null;
- return;
- }
- this._loading = true;
-
- var session = Array.dequeue(this._sessions);
- this._currentSession = session;
- //#if DEBUG
- //#else
- if(session.scriptTimeout > 0) {
- this._timeoutCookie = window.setTimeout(
- Function.createDelegate(this, this._scriptLoadTimeoutHandler), session.scriptTimeout * 1000);
- }
- //#endif
- this._loadScriptsInternal();
- },
-
- _raiseError: function _ScriptLoader$_raiseError() {
- // Abort script loading and raise an error.
- var callback = this._currentSession.scriptLoadFailedCallback;
- var scriptElement = this._currentTask.get_scriptElement();
- this._stopSession();
-
- if(callback) {
- callback(this, scriptElement);
- this._nextSession();
- }
- else {
- this._loading = false;
- throw Sys._ScriptLoader._errorScriptLoadFailed(scriptElement.src);
- }
- },
-
- _scriptLoadedHandler: function _ScriptLoader$_scriptLoadedHandler(scriptElement, loaded) {
- // called by the ScriptLoaderTask when the script element has finished loading, which could be because it loaded or
- // errored out (for browsers that support the error event).
- if (loaded) {
- // script loaded and contained a single notify callback, move on to next script
- // DevDiv Bugs 123213: Note that scriptElement.src is read as un-htmlencoded, even if it was html encoded originally
- Array.add(Sys._ScriptLoader._getLoadedScripts(), scriptElement.src);
- this._currentTask.dispose();
- this._currentTask = null;
- this._loadScriptsInternal();
- }
- else {
- // script loaded with an error
- this._raiseError();
- }
- },
- //#if DEBUG
- //#else
- _scriptLoadTimeoutHandler: function _ScriptLoader$_scriptLoadTimeoutHandler() {
- var callback = this._currentSession.scriptLoadTimeoutCallback;
- this._stopSession();
-
- if(callback) {
- callback(this);
- }
- this._nextSession();
- },
- //#endif
- _stopSession: function _ScriptLoader$_stopSession() {
- //#if DEBUG
- //#else
- if(this._timeoutCookie) {
- window.clearTimeout(this._timeoutCookie);
- this._timeoutCookie = null;
- }
- //#endif
- if(this._currentTask) {
- this._currentTask.dispose();
- this._currentTask = null;
- }
- }
- }
- $type.registerClass('Sys._ScriptLoader', null, Sys.IDisposable);
-
- $type.getInstance = function _ScriptLoader$getInstance() {
- var sl = Sys._ScriptLoader._activeInstance;
- if(!sl) {
- sl = Sys._ScriptLoader._activeInstance = new Sys._ScriptLoader();
- }
- return sl;
- }
-
- $type.isScriptLoaded = function _ScriptLoader$isScriptLoaded(scriptSrc) {
- // For Firefox we need to resolve the script src attribute
- // since the script elements already in the DOM are always
- // resolved. To do this we create a dummy element to see
- // what it would resolve to.
- // DevDiv Bugs 146697: Need to use lower-case tag name for xhtml content type.
- var dummyScript = document.createElement('script');
- dummyScript.src = scriptSrc;
- return Array.contains(Sys._ScriptLoader._getLoadedScripts(), dummyScript.src);
- }
-
- $type.readLoadedScripts = function _ScriptLoader$readLoadedScripts() {
- // enumerates the SCRIPT elements in the DOM and ensures we have their SRC's in the referencedScripts array.
- if(!Sys._ScriptLoader._referencedScripts) {
- var referencedScripts = Sys._ScriptLoader._referencedScripts = [];
-
- // DevDiv Bugs 146697: use lowercase names on getElementsByTagName to work with xhtml content type
- var existingScripts = document.getElementsByTagName('script');
- for (var i = existingScripts.length - 1; i >= 0; i--) {
- var scriptNode = existingScripts[i];
- var scriptSrc = scriptNode.src;
- if (scriptSrc.length) {
- if (!Array.contains(referencedScripts, scriptSrc)) {
- Array.add(referencedScripts, scriptSrc);
- }
- }
- }
- }
- }
-
- $type._errorScriptLoadFailed = function _ScriptLoader$_errorScriptLoadFailed(scriptUrl) {
- var errorMessage;
- //#if DEBUG
- // a much more detailed message is displayed in debug mode
- errorMessage = Sys.Res.scriptLoadFailedDebug;
- //#else
- // a simplier error is displayed in release
- errorMessage = Sys.Res.scriptLoadFailed;
- //#endif
-
- var displayMessage = "Sys.ScriptLoadFailedException: " + String.format(errorMessage, scriptUrl);
- var e = Error.create(displayMessage, {name: 'Sys.ScriptLoadFailedException', 'scriptUrl': scriptUrl });
- e.popStackFrame();
- return e;
- }
-
- $type._getLoadedScripts = function _ScriptLoader$_getLoadedScripts() {
- if(!Sys._ScriptLoader._referencedScripts) {
- Sys._ScriptLoader._referencedScripts = [];
- Sys._ScriptLoader.readLoadedScripts();
- }
- return Sys._ScriptLoader._referencedScripts;
- }