StyleSheetLoader.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. /**
  2. * StyleSheetLoader.js
  3. *
  4. * Copyright, Moxiecode Systems AB
  5. * Released under LGPL License.
  6. *
  7. * License: http://www.tinymce.com/license
  8. * Contributing: http://www.tinymce.com/contributing
  9. */
  10. /**
  11. * This class handles loading of external stylesheets and fires events when these are loaded.
  12. *
  13. * @class tinymce.dom.StyleSheetLoader
  14. * @private
  15. */
  16. define("tinymce/dom/StyleSheetLoader", [], function() {
  17. "use strict";
  18. return function(document, settings) {
  19. var idCount = 0, loadedStates = {}, maxLoadTime;
  20. settings = settings || {};
  21. maxLoadTime = settings.maxLoadTime || 5000;
  22. function appendToHead(node) {
  23. document.getElementsByTagName('head')[0].appendChild(node);
  24. }
  25. /**
  26. * Loads the specified css style sheet file and call the loadedCallback once it's finished loading.
  27. *
  28. * @method load
  29. * @param {String} url Url to be loaded.
  30. * @param {Function} loadedCallback Callback to be executed when loaded.
  31. * @param {Function} errorCallback Callback to be executed when failed loading.
  32. */
  33. function load(url, loadedCallback, errorCallback) {
  34. var link, style, startTime, state;
  35. function passed() {
  36. var callbacks = state.passed, i = callbacks.length;
  37. while (i--) {
  38. callbacks[i]();
  39. }
  40. state.status = 2;
  41. state.passed = [];
  42. state.failed = [];
  43. }
  44. function failed() {
  45. var callbacks = state.failed, i = callbacks.length;
  46. while (i--) {
  47. callbacks[i]();
  48. }
  49. state.status = 3;
  50. state.passed = [];
  51. state.failed = [];
  52. }
  53. // Sniffs for older WebKit versions that have the link.onload but a broken one
  54. function isOldWebKit() {
  55. var webKitChunks = navigator.userAgent.match(/WebKit\/(\d*)/);
  56. return !!(webKitChunks && webKitChunks[1] < 536);
  57. }
  58. // Calls the waitCallback until the test returns true or the timeout occurs
  59. function wait(testCallback, waitCallback) {
  60. if (!testCallback()) {
  61. // Wait for timeout
  62. if ((new Date().getTime()) - startTime < maxLoadTime) {
  63. window.setTimeout(waitCallback, 0);
  64. } else {
  65. failed();
  66. }
  67. }
  68. }
  69. // Workaround for WebKit that doesn't properly support the onload event for link elements
  70. // Or WebKit that fires the onload event before the StyleSheet is added to the document
  71. function waitForWebKitLinkLoaded() {
  72. wait(function() {
  73. var styleSheets = document.styleSheets, styleSheet, i = styleSheets.length, owner;
  74. while (i--) {
  75. styleSheet = styleSheets[i];
  76. owner = styleSheet.ownerNode ? styleSheet.ownerNode : styleSheet.owningElement;
  77. if (owner && owner.id === link.id) {
  78. passed();
  79. return true;
  80. }
  81. }
  82. }, waitForWebKitLinkLoaded);
  83. }
  84. // Workaround for older Geckos that doesn't have any onload event for StyleSheets
  85. function waitForGeckoLinkLoaded() {
  86. wait(function() {
  87. try {
  88. // Accessing the cssRules will throw an exception until the CSS file is loaded
  89. var cssRules = style.sheet.cssRules;
  90. passed();
  91. return !!cssRules;
  92. } catch (ex) {
  93. // Ignore
  94. }
  95. }, waitForGeckoLinkLoaded);
  96. }
  97. if (!loadedStates[url]) {
  98. state = {
  99. passed: [],
  100. failed: []
  101. };
  102. loadedStates[url] = state;
  103. } else {
  104. state = loadedStates[url];
  105. }
  106. if (loadedCallback) {
  107. state.passed.push(loadedCallback);
  108. }
  109. if (errorCallback) {
  110. state.failed.push(errorCallback);
  111. }
  112. // Is loading wait for it to pass
  113. if (state.status == 1) {
  114. return;
  115. }
  116. // Has finished loading and was success
  117. if (state.status == 2) {
  118. passed();
  119. return;
  120. }
  121. // Has finished loading and was a failure
  122. if (state.status == 3) {
  123. failed();
  124. return;
  125. }
  126. // Start loading
  127. state.status = 1;
  128. link = document.createElement('link');
  129. link.rel = 'stylesheet';
  130. link.type = 'text/css';
  131. link.id = 'u' + (idCount++);
  132. link.async = false;
  133. link.defer = false;
  134. startTime = new Date().getTime();
  135. // Feature detect onload on link element and sniff older webkits since it has an broken onload event
  136. if ("onload" in link && !isOldWebKit()) {
  137. link.onload = waitForWebKitLinkLoaded;
  138. link.onerror = failed;
  139. } else {
  140. // Sniff for old Firefox that doesn't support the onload event on link elements
  141. // TODO: Remove this in the future when everyone uses modern browsers
  142. if (navigator.userAgent.indexOf("Firefox") > 0) {
  143. style = document.createElement('style');
  144. style.textContent = '@import "' + url + '"';
  145. waitForGeckoLinkLoaded();
  146. appendToHead(style);
  147. return;
  148. } else {
  149. // Use the id owner on older webkits
  150. waitForWebKitLinkLoaded();
  151. }
  152. }
  153. appendToHead(link);
  154. link.href = url;
  155. }
  156. this.load = load;
  157. };
  158. });