/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

  1. // This ScriptLoader works by injecting script tags into the DOM sequentially, waiting for each script
  2. // to finish loading before proceeding to the next one.
  3. // It supports a timeout which applies to ALL scripts.
  4. $type = Sys._ScriptLoader = function _ScriptLoader() {
  5. this._scriptsToLoad = null;
  6. this._sessions = [];
  7. this._scriptLoadedDelegate = Function.createDelegate(this, this._scriptLoadedHandler);
  8. }
  9. $type.prototype = {
  10. dispose: function _ScriptLoader$dispose() {
  11. this._stopSession();
  12. this._loading = false;
  13. if(this._events) {
  14. delete this._events;
  15. }
  16. this._sessions = null;
  17. this._currentSession = null;
  18. this._scriptLoadedDelegate = null;
  19. },
  20. loadScripts: function _ScriptLoader$loadScripts(scriptTimeout, allScriptsLoadedCallback, scriptLoadFailedCallback, scriptLoadTimeoutCallback) {
  21. /// <summary locid="M:J#Sys._ScriptLoader.loadScripts">Begins loading scripts that have been queued.</summary>
  22. /// <param name="scriptTimeout" type="Number" integer="true">Timeout in seconds for loading all scripts.</param>
  23. /// <param name="allScriptsLoadedCallback" type="Function" mayBeNull="true">Callback for notification when all scripts have successfully loaded.</param>
  24. /// <param name="scriptLoadFailedCallback" type="Function" mayBeNull="true">Callback for notification when a script fails to load.</param>
  25. /// <param name="scriptLoadTimeoutCallback" type="Function" mayBeNull="true">Callback for notification when scripts have not finished loading within the given timeout.</param>
  26. //#if DEBUG
  27. var e = Function._validateParams(arguments, [
  28. {name: "scriptTimeout", type: Number, integer: true},
  29. {name: "allScriptsLoadedCallback", type: Function, mayBeNull: true},
  30. {name: "scriptLoadFailedCallback", type: Function, mayBeNull: true},
  31. {name: "scriptLoadTimeoutCallback", type: Function, mayBeNull: true}
  32. ]);
  33. if (e) throw e;
  34. //#endif
  35. var session = {
  36. allScriptsLoadedCallback: allScriptsLoadedCallback,
  37. scriptLoadFailedCallback: scriptLoadFailedCallback,
  38. scriptLoadTimeoutCallback: scriptLoadTimeoutCallback,
  39. scriptsToLoad: this._scriptsToLoad,
  40. scriptTimeout: scriptTimeout };
  41. this._scriptsToLoad = null;
  42. this._sessions.push(session);
  43. if (!this._loading) {
  44. this._nextSession();
  45. }
  46. },
  47. queueCustomScriptTag: function _ScriptLoader$queueCustomScriptTag(scriptAttributes) {
  48. /// <summary locid="M:J#Sys._ScriptLoader.queueCustomScriptTag">Queues a script reference with the given set of custom script element attributes.</summary>
  49. /// <param name="scriptAttributes" mayBeNull="false">A JSON object that describtes the attributes to apply to the script element.</param>
  50. //#if DEBUG
  51. var e = Function._validateParams(arguments, [
  52. {name: "scriptAttributes"}
  53. ]);
  54. if (e) throw e;
  55. //#endif
  56. if(!this._scriptsToLoad) {
  57. this._scriptsToLoad = [];
  58. }
  59. Array.add(this._scriptsToLoad, scriptAttributes);
  60. },
  61. queueScriptBlock: function _ScriptLoader$queueScriptBlock(scriptContent) {
  62. /// <summary locid="M:J#Sys._ScriptLoader.queueScriptBlock">Queues a script reference with literal script.</summary>
  63. /// <param name="scriptContent" type="String" mayBeNull="false">Literal script to execute.</param>
  64. //#if DEBUG
  65. var e = Function._validateParams(arguments, [
  66. {name: "scriptContent", type: String}
  67. ]);
  68. if (e) throw e;
  69. //#endif
  70. if(!this._scriptsToLoad) {
  71. this._scriptsToLoad = [];
  72. }
  73. Array.add(this._scriptsToLoad, {text: scriptContent});
  74. },
  75. queueScriptReference: function _ScriptLoader$queueScriptReference(scriptUrl) {
  76. /// <summary locid="M:J#Sys._ScriptLoader.queueScriptReference">Queues a script reference to the given script URL.</summary>
  77. /// <param name="scriptUrl" type="String" mayBeNull="false">URL to the script to reference.</param>
  78. //#if DEBUG
  79. var e = Function._validateParams(arguments, [
  80. {name: "scriptUrl", type: String}
  81. ]);
  82. if (e) throw e;
  83. //#endif
  84. if(!this._scriptsToLoad) {
  85. this._scriptsToLoad = [];
  86. }
  87. Array.add(this._scriptsToLoad, {src: scriptUrl});
  88. },
  89. _createScriptElement: function _ScriptLoader$_createScriptElement(queuedScript) {
  90. var scriptElement = document.createElement('script');
  91. // Initialize default script type to JavaScript - but it might get overwritten
  92. // if a custom script tag has a different type attribute.
  93. scriptElement.type = 'text/javascript';
  94. // Apply script element attributes
  95. for (var attr in queuedScript) {
  96. scriptElement[attr] = queuedScript[attr];
  97. }
  98. return scriptElement;
  99. },
  100. _loadScriptsInternal: function _ScriptLoader$_loadScriptsInternal() {
  101. var session = this._currentSession;
  102. // Load up the next script in the list
  103. if (session.scriptsToLoad && session.scriptsToLoad.length > 0) {
  104. var nextScript = Array.dequeue(session.scriptsToLoad);
  105. // Inject a script element into the DOM
  106. var scriptElement = this._createScriptElement(nextScript);
  107. if (scriptElement.text && Sys.Browser.agent === Sys.Browser.Safari) {
  108. // Safari requires the inline script to be in the innerHTML attribute
  109. scriptElement.innerHTML = scriptElement.text;
  110. delete scriptElement.text;
  111. }
  112. // AtlasWhidbey 36149: If they queue an empty script block "", we can't tell the difference between
  113. // a script block queue entry and a src entry with just if(!element.text).
  114. // dont use scriptElement.src --> FF resolves that to the current directory, IE leaves it blank.
  115. // nextScript.src is always a string if it's a non block script.
  116. if (typeof(nextScript.src) === "string") {
  117. // We only need to worry about timing out and loading if the script tag has a 'src'.
  118. this._currentTask = new Sys._ScriptLoaderTask(scriptElement, this._scriptLoadedDelegate);
  119. // note: task is responsible for disposing of _itself_. This is necessary so that the ScriptLoader can continue
  120. // with script loading after a script notifies it has loaded. The task sticks around until the dom element finishes
  121. // completely, and disposes itself automatically.
  122. // note: its possible for notify to occur before this method even returns in IE! So it should remain the last possible statement.
  123. this._currentTask.execute();
  124. }
  125. else {
  126. // script is literal script, so just load the script by adding the new element to the DOM
  127. // DevDiv Bugs 146697: use lowercase names on getElementsByTagName to work with xhtml content type
  128. //#if DEBUG
  129. // DevDiv Bugs 146327: In debug mode, report useful error message for pages without <head> element
  130. var headElements = document.getElementsByTagName('head');
  131. if (headElements.length === 0) {
  132. throw new Error.invalidOperation(Sys.Res.scriptLoadFailedNoHead);
  133. }
  134. else {
  135. headElements[0].appendChild(scriptElement);
  136. }
  137. //#else
  138. document.getElementsByTagName('head')[0].appendChild(scriptElement);
  139. //#endif
  140. // DevDiv 157097: Removed setTimeout, assuming the script executed synchronously.
  141. // Previously the setTimeout worked around a Firefox bug where the script was not
  142. // executed immediately when the element is appended. However, that bug was fixed,
  143. // and the timeout causes a significant hit to performance. The timeout also causes
  144. // an inconsistency in the order of events between regular and async requests. With the
  145. // timeout, it would be possible for delayed operations in component initialize methods
  146. // to complete before the app loaded event is raised, where that is not possible on a
  147. // GET because the loaded event is raised within the same execution chain that executes
  148. // the initialize methods.
  149. // cleanup (removes the script element in release mode).
  150. Sys._ScriptLoaderTask._clearScript(scriptElement);
  151. // Resume script loading progress.
  152. this._loadScriptsInternal();
  153. }
  154. }
  155. else {
  156. // When there are no more scripts to load, call the final event
  157. this._stopSession();
  158. var callback = session.allScriptsLoadedCallback;
  159. if(callback) {
  160. callback(this);
  161. }
  162. // and move on to the next session, if any
  163. this._nextSession();
  164. }
  165. },
  166. _nextSession: function _ScriptLoader$_nextSession() {
  167. if (this._sessions.length === 0) {
  168. this._loading = false;
  169. this._currentSession = null;
  170. return;
  171. }
  172. this._loading = true;
  173. var session = Array.dequeue(this._sessions);
  174. this._currentSession = session;
  175. //#if DEBUG
  176. //#else
  177. if(session.scriptTimeout > 0) {
  178. this._timeoutCookie = window.setTimeout(
  179. Function.createDelegate(this, this._scriptLoadTimeoutHandler), session.scriptTimeout * 1000);
  180. }
  181. //#endif
  182. this._loadScriptsInternal();
  183. },
  184. _raiseError: function _ScriptLoader$_raiseError() {
  185. // Abort script loading and raise an error.
  186. var callback = this._currentSession.scriptLoadFailedCallback;
  187. var scriptElement = this._currentTask.get_scriptElement();
  188. this._stopSession();
  189. if(callback) {
  190. callback(this, scriptElement);
  191. this._nextSession();
  192. }
  193. else {
  194. this._loading = false;
  195. throw Sys._ScriptLoader._errorScriptLoadFailed(scriptElement.src);
  196. }
  197. },
  198. _scriptLoadedHandler: function _ScriptLoader$_scriptLoadedHandler(scriptElement, loaded) {
  199. // called by the ScriptLoaderTask when the script element has finished loading, which could be because it loaded or
  200. // errored out (for browsers that support the error event).
  201. if (loaded) {
  202. // script loaded and contained a single notify callback, move on to next script
  203. // DevDiv Bugs 123213: Note that scriptElement.src is read as un-htmlencoded, even if it was html encoded originally
  204. Array.add(Sys._ScriptLoader._getLoadedScripts(), scriptElement.src);
  205. this._currentTask.dispose();
  206. this._currentTask = null;
  207. this._loadScriptsInternal();
  208. }
  209. else {
  210. // script loaded with an error
  211. this._raiseError();
  212. }
  213. },
  214. //#if DEBUG
  215. //#else
  216. _scriptLoadTimeoutHandler: function _ScriptLoader$_scriptLoadTimeoutHandler() {
  217. var callback = this._currentSession.scriptLoadTimeoutCallback;
  218. this._stopSession();
  219. if(callback) {
  220. callback(this);
  221. }
  222. this._nextSession();
  223. },
  224. //#endif
  225. _stopSession: function _ScriptLoader$_stopSession() {
  226. //#if DEBUG
  227. //#else
  228. if(this._timeoutCookie) {
  229. window.clearTimeout(this._timeoutCookie);
  230. this._timeoutCookie = null;
  231. }
  232. //#endif
  233. if(this._currentTask) {
  234. this._currentTask.dispose();
  235. this._currentTask = null;
  236. }
  237. }
  238. }
  239. $type.registerClass('Sys._ScriptLoader', null, Sys.IDisposable);
  240. $type.getInstance = function _ScriptLoader$getInstance() {
  241. var sl = Sys._ScriptLoader._activeInstance;
  242. if(!sl) {
  243. sl = Sys._ScriptLoader._activeInstance = new Sys._ScriptLoader();
  244. }
  245. return sl;
  246. }
  247. $type.isScriptLoaded = function _ScriptLoader$isScriptLoaded(scriptSrc) {
  248. // For Firefox we need to resolve the script src attribute
  249. // since the script elements already in the DOM are always
  250. // resolved. To do this we create a dummy element to see
  251. // what it would resolve to.
  252. // DevDiv Bugs 146697: Need to use lower-case tag name for xhtml content type.
  253. var dummyScript = document.createElement('script');
  254. dummyScript.src = scriptSrc;
  255. return Array.contains(Sys._ScriptLoader._getLoadedScripts(), dummyScript.src);
  256. }
  257. $type.readLoadedScripts = function _ScriptLoader$readLoadedScripts() {
  258. // enumerates the SCRIPT elements in the DOM and ensures we have their SRC's in the referencedScripts array.
  259. if(!Sys._ScriptLoader._referencedScripts) {
  260. var referencedScripts = Sys._ScriptLoader._referencedScripts = [];
  261. // DevDiv Bugs 146697: use lowercase names on getElementsByTagName to work with xhtml content type
  262. var existingScripts = document.getElementsByTagName('script');
  263. for (var i = existingScripts.length - 1; i >= 0; i--) {
  264. var scriptNode = existingScripts[i];
  265. var scriptSrc = scriptNode.src;
  266. if (scriptSrc.length) {
  267. if (!Array.contains(referencedScripts, scriptSrc)) {
  268. Array.add(referencedScripts, scriptSrc);
  269. }
  270. }
  271. }
  272. }
  273. }
  274. $type._errorScriptLoadFailed = function _ScriptLoader$_errorScriptLoadFailed(scriptUrl) {
  275. var errorMessage;
  276. //#if DEBUG
  277. // a much more detailed message is displayed in debug mode
  278. errorMessage = Sys.Res.scriptLoadFailedDebug;
  279. //#else
  280. // a simplier error is displayed in release
  281. errorMessage = Sys.Res.scriptLoadFailed;
  282. //#endif
  283. var displayMessage = "Sys.ScriptLoadFailedException: " + String.format(errorMessage, scriptUrl);
  284. var e = Error.create(displayMessage, {name: 'Sys.ScriptLoadFailedException', 'scriptUrl': scriptUrl });
  285. e.popStackFrame();
  286. return e;
  287. }
  288. $type._getLoadedScripts = function _ScriptLoader$_getLoadedScripts() {
  289. if(!Sys._ScriptLoader._referencedScripts) {
  290. Sys._ScriptLoader._referencedScripts = [];
  291. Sys._ScriptLoader.readLoadedScripts();
  292. }
  293. return Sys._ScriptLoader._referencedScripts;
  294. }