PageRenderTime 22ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/booktype/apps/edit/static/edit/js/aloha/lib/css.js

https://gitlab.com/wilane/Booktype
JavaScript | 455 lines | 367 code | 6 blank | 82 comment | 0 complexity | 13cccf2d1cb23821bcb2ab9a2cb0b3e2 MD5 | raw file
  1. /** MIT License (c) copyright B Cavalier & J Hann */
  2. /**
  3. * curl css! plugin
  4. *
  5. * Licensed under the MIT License at:
  6. * http://www.opensource.org/licenses/mit-license.php
  7. *
  8. */
  9. (function (global) {
  10. /*
  11. * AMD css! plugin
  12. * This plugin will load and wait for css files. This could be handy when
  13. * loading css files as part of a layer or as a way to apply a run-time theme.
  14. * Most browsers do not support the load event handler of the link element.
  15. * Therefore, we have to use other means to detect when a css file loads.
  16. * (The HTML5 spec states that the LINK element should have a load event, but
  17. * not even Chrome 8 or FF4b7 have it, yet.
  18. * http://www.w3.org/TR/html5/semantics.html#the-link-element)
  19. *
  20. * This plugin tries to use the load event and a universal work-around when
  21. * it is invoked the first time. If the load event works, it is used on
  22. * every successive load. Therefore, browsers that support the load event will
  23. * just work (i.e. no need for hacks!). FYI, Feature-detecting the load
  24. * event is tricky since most browsers have a non-functional onload property.
  25. *
  26. * The universal work-around watches a stylesheet until its rules are
  27. * available (not null or undefined). There are nuances, of course, between
  28. * the various browsers. The isLinkReady function accounts for these.
  29. *
  30. * Note: it appears that all browsers load @import'ed stylesheets before
  31. * fully processing the rest of the importing stylesheet. Therefore, we
  32. * don't need to find and wait for any @import rules explicitly.
  33. *
  34. * Note #2: for Opera compatibility, stylesheets must have at least one rule.
  35. * AFAIK, there's no way to tell the difference between an empty sheet and
  36. * one that isn't finished loading in Opera (XD or same-domain).
  37. *
  38. * Options:
  39. * !nowait - does not wait for the stylesheet to be parsed, just loads it
  40. *
  41. * Global configuration options:
  42. *
  43. * cssDeferLoad: Boolean. You can also instruct this plugin to not wait
  44. * for css resources. They'll get loaded asap, but other code won't wait
  45. * for them. This is just like using the !nowait option on every css file.
  46. *
  47. * cssWatchPeriod: if direct load-detection techniques fail, this option
  48. * determines the msec to wait between brute-force checks for rules. The
  49. * default is 50 msec.
  50. *
  51. * You may specify an alternate file extension:
  52. * require('css!myproj/component.less') // --> myproj/component.less
  53. * require('css!myproj/component.scss') // --> myproj/component.scss
  54. *
  55. * When using alternative file extensions, be sure to serve the files from
  56. * the server with the correct mime type (text/css) or some browsers won't
  57. * parse them, causing an error in the plugin.
  58. *
  59. * usage:
  60. * require(['css!myproj/comp']); // load and wait for myproj/comp.css
  61. * define(['css!some/folder/file'], {}); // wait for some/folder/file.css
  62. * require(['css!myWidget!nowait']);
  63. *
  64. * Tested in:
  65. * Firefox 1.5, 2.0, 3.0, 3.5, 3.6, and 4.0b6
  66. * Safari 3.0.4, 3.2.1, 5.0
  67. * Chrome 7 (8+ is partly b0rked)
  68. * Opera 9.52, 10.63, and Opera 11.00
  69. * IE 6, 7, and 8
  70. * Netscape 7.2 (WTF? SRSLY!)
  71. * Does not work in Safari 2.x :(
  72. * In Chrome 8+, there's no way to wait for cross-domain (XD) stylesheets.
  73. * See comments in the code below.
  74. * TODO: figure out how to be forward-compatible when browsers support HTML5's
  75. * load handler without breaking IE and Opera
  76. */
  77. var
  78. // compressibility shortcuts
  79. onreadystatechange = 'onreadystatechange',
  80. onload = 'onload',
  81. createElement = 'createElement',
  82. // failed is true if RequireJS threw an exception
  83. failed = false,
  84. undef,
  85. insertedSheets = {},
  86. features = {
  87. // true if the onload event handler works
  88. // "event-link-onload" : false
  89. },
  90. // this actually tests for absolute urls and root-relative urls
  91. // they're both non-relative
  92. nonRelUrlRe = /^\/|^[^:]*:\/\//,
  93. // Note: this will fail if there are parentheses in the url
  94. findUrlRx = /url\s*\(['"]?([^'"\)]*)['"]?\)/g,
  95. // doc will be undefined during a build
  96. doc = global.document,
  97. // find the head element and set it to it's standard property if nec.
  98. head,
  99. // collection of modules that have been written to the built file
  100. built = {};
  101. if (doc) {
  102. head = doc.head || (doc.head = doc.getElementsByTagName('head')[0]);
  103. }
  104. function has (feature) {
  105. return features[feature];
  106. }
  107. /***** load-detection functions *****/
  108. function loadHandler (params, cb) {
  109. // We're using 'readystatechange' because IE and Opera happily support both
  110. var link = params.link;
  111. link[onreadystatechange] = link[onload] = function () {
  112. if (!link.readyState || link.readyState == 'complete') {
  113. features["event-link-onload"] = true;
  114. cleanup(params);
  115. cb();
  116. }
  117. };
  118. }
  119. function nameWithExt (name, defaultExt) {
  120. return name.lastIndexOf('.') <= name.lastIndexOf('/') ?
  121. name + '.' + defaultExt : name;
  122. }
  123. function parseSuffixes (name) {
  124. // creates a dual-structure: both an array and a hashmap
  125. // suffixes[0] is the actual name
  126. var parts = name.split('!'),
  127. suf, i = 1, pair;
  128. while ((suf = parts[i++])) { // double-parens to avoid jslint griping
  129. pair = suf.split('=', 2);
  130. parts[pair[0]] = pair.length == 2 ? pair[1] : true;
  131. }
  132. return parts;
  133. }
  134. var collectorSheet;
  135. function createLink (doc, optHref) {
  136. // detect if we need to avoid 31-sheet limit in IE (how to detect this for realz?)
  137. if (document.createStyleSheet) {
  138. if (!collectorSheet) {
  139. collectorSheet = document.createStyleSheet();
  140. }
  141. if (document.styleSheets.length >= 30) {
  142. moveLinksToCollector();
  143. }
  144. }
  145. var link = doc[createElement]('link');
  146. link.rel = "stylesheet";
  147. link.type = "text/css";
  148. link.setAttribute('_curl_movable', true);
  149. if (optHref) {
  150. link.href = optHref;
  151. }
  152. return link;
  153. }
  154. var testEl;
  155. function styleIsApplied () {
  156. // Chrome 8 hax0rs!
  157. // This is an ugly hack needed by Chrome 8+ which no longer waits for rules
  158. // to be applied to the document before exposing them to javascript.
  159. // Unfortunately, this routine will never fire for XD stylesheets since
  160. // Chrome will also throw an exception if attempting to access the rules
  161. // of an XD stylesheet. Therefore, there's no way to detect the load
  162. // event of XD stylesheets until Google fixes this, preferably with a
  163. // functional load event! As a work-around, use domReady() before
  164. // rendering widgets / components that need the css to be ready.
  165. if (!testEl) {
  166. testEl = doc[createElement]('div');
  167. testEl.id = '_cssx_load_test';
  168. head.appendChild(testEl);
  169. }
  170. return doc.defaultView.getComputedStyle(testEl, null).marginTop == '-5px';
  171. }
  172. function isLinkReady (link) {
  173. // This routine is a bit fragile: browser vendors seem oblivious to
  174. // the need to know precisely when stylesheets load. Therefore, we need
  175. // to continually test beta browsers until they all support the LINK load
  176. // event like IE and Opera.
  177. var sheet, rules, ready = false;
  178. try {
  179. // webkit's and IE's sheet is null until the sheet is loaded
  180. sheet = link.sheet || link.styleSheet;
  181. // mozilla's sheet throws an exception if trying to access xd rules
  182. rules = sheet.cssRules || sheet.rules;
  183. // webkit's xd sheet returns rules == null
  184. // opera's sheet always returns rules, but length is zero until loaded
  185. // friggin IE doesn't count @import rules as rules, but IE should
  186. // never hit this routine anyways.
  187. ready = rules ?
  188. rules.length > 0 : // || (sheet.imports && sheet.imports.length > 0) :
  189. rules !== undef;
  190. // thanks, Chrome 8+, for this lovely hack. TODO: find a better way
  191. if (ready && {}.toString.call(window.chrome) == '[object Chrome]') {
  192. // fwiw, we'll never get this far if this is an XD stylesheet
  193. sheet.insertRule('#_cssx_load_test{margin-top:-5px;}', 0);
  194. ready = styleIsApplied();
  195. sheet.deleteRule(0);
  196. }
  197. }
  198. catch (ex) {
  199. // 1000 means FF loaded an xd stylesheet
  200. // other browsers just throw a security error here (IE uses the phrase 'Access is denied')
  201. ready = (ex.code == 1000) || (ex.message.match(/security|denied/i));
  202. }
  203. return ready;
  204. }
  205. function ssWatcher (params, cb) {
  206. // watches a stylesheet for loading signs.
  207. if (isLinkReady(params.link)) {
  208. cleanup(params);
  209. cb();
  210. }
  211. else if (!failed) {
  212. setTimeout(function () { ssWatcher(params, cb); }, params.wait);
  213. }
  214. }
  215. function loadDetector (params, cb) {
  216. // It would be nice to use onload everywhere, but the onload handler
  217. // only works in IE and Opera.
  218. // Detecting it cross-browser is completely impossible, too, since
  219. // THE BROWSERS ARE LIARS! DON'T TELL ME YOU HAVE AN ONLOAD PROPERTY
  220. // IF IT DOESN'T DO ANYTHING!
  221. var loaded;
  222. function cbOnce () {
  223. if (!loaded) {
  224. loaded = true;
  225. cb();
  226. }
  227. }
  228. loadHandler(params, cbOnce);
  229. if (!has("event-link-onload")) {
  230. ssWatcher(params, cbOnce);
  231. }
  232. }
  233. function cleanup (params) {
  234. var link = params.link;
  235. link[onreadystatechange] = link[onload] = null;
  236. }
  237. function moveLinksToCollector () {
  238. // IE 6-8 fails when over 31 sheets, so we collect them.
  239. // Note: this hack relies on proper cache headers.
  240. var link, links, collector, pos = 0;
  241. collector = collectorSheet;
  242. collectorSheet = null; // so a new one will be created
  243. links = document.getElementsByTagName('link');
  244. while ((link = links[pos])) {
  245. if (link.getAttribute('_curl_movable')) {
  246. // move to the collectorSheet (note: bad cache directive will cause a re-download)
  247. collector.addImport(link.href);
  248. // remove from document
  249. link.parentNode && link.parentNode.removeChild(link);
  250. }
  251. else {
  252. // skip this sheet
  253. pos++;
  254. }
  255. }
  256. }
  257. /***** style element functions *****/
  258. var currentStyle;
  259. function translateUrls (cssText, baseUrl) {
  260. return cssText.replace(findUrlRx, function (all, url) {
  261. return 'url("' + translateUrl(url, baseUrl) + '")';
  262. });
  263. }
  264. function translateUrl (url, parentPath) {
  265. // if this is a relative url
  266. if (!nonRelUrlRe.test(url)) {
  267. // append path onto it
  268. url = parentPath + url;
  269. }
  270. return url;
  271. }
  272. function createStyle (cssText) {
  273. clearTimeout(createStyle.debouncer);
  274. if (createStyle.accum) {
  275. createStyle.accum.push(cssText);
  276. }
  277. else {
  278. createStyle.accum = [cssText];
  279. currentStyle = doc.createStyleSheet ? doc.createStyleSheet() :
  280. head.appendChild(doc.createElement('style'));
  281. }
  282. createStyle.debouncer = setTimeout(function () {
  283. // Note: IE 6-8 won't accept the W3C method for inserting css text
  284. var style, allCssText;
  285. style = currentStyle;
  286. currentStyle = undef;
  287. allCssText = createStyle.accum.join('\n');
  288. createStyle.accum = undef;
  289. // for safari which chokes on @charset "UTF-8";
  290. allCssText = allCssText.replace(/.+charset[^;]+;/g, '');
  291. // TODO: hoist all @imports to the top of the file to follow w3c spec
  292. 'cssText' in style ? style.cssText = allCssText :
  293. style.appendChild(doc.createTextNode(allCssText));
  294. }, 0);
  295. return currentStyle;
  296. }
  297. function createSheetProxy (sheet) {
  298. return {
  299. cssRules: function () {
  300. return sheet.cssRules || sheet.rules;
  301. },
  302. insertRule: sheet.insertRule || function (text, index) {
  303. var parts = text.split(/\{|\}/g);
  304. sheet.addRule(parts[0], parts[1], index);
  305. return index;
  306. },
  307. deleteRule: sheet.deleteRule || function (index) {
  308. sheet.removeRule(index);
  309. return index;
  310. },
  311. sheet: function () {
  312. return sheet;
  313. }
  314. };
  315. }
  316. /***** finally! the actual plugin *****/
  317. define(/*=='css',==*/ {
  318. 'normalize': function (resourceId, normalize) {
  319. var resources, normalized;
  320. if (!resourceId) return resourceId;
  321. resources = resourceId.split(",");
  322. normalized = [];
  323. for (var i = 0, len = resources.length; i < len; i++) {
  324. normalized.push(normalize(resources[i]));
  325. }
  326. return normalized.join(',');
  327. },
  328. 'load': function (resourceId, require, callback, config) {
  329. var resources = (resourceId || '').split(","),
  330. loadingCount = resources.length;
  331. // all detector functions must ensure that this function only gets
  332. // called once per stylesheet!
  333. function loaded () {
  334. // load/error handler may have executed before stylesheet is
  335. // fully parsed / processed in Opera, so use setTimeout.
  336. // Opera will process before the it next enters the event loop
  337. // (so 0 msec is enough time).
  338. if(--loadingCount == 0){
  339. // TODO: move this setTimeout to loadHandler
  340. setTimeout(function () { callback(createSheetProxy(link.sheet || link.styleSheet)); }, 0);
  341. }
  342. }
  343. if (!resourceId) {
  344. // return the run-time API
  345. callback({
  346. 'translateUrls': function (cssText, baseId) {
  347. var baseUrl;
  348. baseUrl = require['toUrl'](baseId);
  349. baseUrl = baseUrl.substr(0, baseUrl.lastIndexOf('/') + 1);
  350. return translateUrls(cssText, baseUrl);
  351. },
  352. 'injectStyle': function (cssText) {
  353. return createStyle(cssText);
  354. },
  355. 'proxySheet': function (sheet) {
  356. // for W3C, `sheet` is a reference to a <style> node so we need to
  357. // return the sheet property.
  358. if (sheet.sheet /* instanceof CSSStyleSheet */) sheet = sheet.sheet;
  359. return createSheetProxy(sheet);
  360. }
  361. });
  362. }
  363. else {
  364. // `after` will become truthy once the loop executes a second time
  365. for (var i = resources.length - 1, after; i >= 0; i--, after = true) {
  366. resourceId = resources[i];
  367. var
  368. // TODO: this is a bit weird: find a better way to extract name?
  369. opts = parseSuffixes(resourceId),
  370. name = opts.shift(),
  371. url = require['toUrl'](nameWithExt(name, 'css')),
  372. link = createLink(doc),
  373. nowait = 'nowait' in opts ? opts['nowait'] != 'false' : !!config['cssDeferLoad'],
  374. params = {
  375. link: link,
  376. url: url,
  377. wait: config['cssWatchPeriod'] || 50
  378. };
  379. if (nowait) {
  380. callback(createSheetProxy(link.sheet || link.styleSheet));
  381. }
  382. else {
  383. // hook up load detector(s)
  384. loadDetector(params, loaded);
  385. }
  386. // go!
  387. link.href = url;
  388. if (after) {
  389. head.insertBefore(link, insertedSheets[after].previousSibling);
  390. }
  391. else {
  392. head.appendChild(link);
  393. }
  394. insertedSheets[url] = link;
  395. }
  396. }
  397. },
  398. 'plugin-builder': './builder/css'
  399. });
  400. })(this);