reloader.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. (function() {
  2. var IMAGE_STYLES, Reloader, numberOfMatchingSegments, pathFromUrl, pathsMatch, pickBestMatch, splitUrl;
  3. splitUrl = function(url) {
  4. var comboSign, hash, index, params;
  5. if ((index = url.indexOf('#')) >= 0) {
  6. hash = url.slice(index);
  7. url = url.slice(0, index);
  8. } else {
  9. hash = '';
  10. }
  11. comboSign = url.indexOf('??');
  12. if (comboSign >= 0) {
  13. if (comboSign + 1 !== url.lastIndexOf('?')) {
  14. index = url.lastIndexOf('?');
  15. }
  16. } else {
  17. index = url.indexOf('?');
  18. }
  19. if (index >= 0) {
  20. params = url.slice(index);
  21. url = url.slice(0, index);
  22. } else {
  23. params = '';
  24. }
  25. return {
  26. url: url,
  27. params: params,
  28. hash: hash
  29. };
  30. };
  31. pathFromUrl = function(url) {
  32. var path;
  33. url = splitUrl(url).url;
  34. if (url.indexOf('file://') === 0) {
  35. path = url.replace(/^file:\/\/(localhost)?/, '');
  36. } else {
  37. path = url.replace(/^([^:]+:)?\/\/([^:\/]+)(:\d*)?\//, '/');
  38. }
  39. return decodeURIComponent(path);
  40. };
  41. pickBestMatch = function(path, objects, pathFunc) {
  42. var bestMatch, i, len1, object, score;
  43. bestMatch = {
  44. score: 0
  45. };
  46. for (i = 0, len1 = objects.length; i < len1; i++) {
  47. object = objects[i];
  48. score = numberOfMatchingSegments(path, pathFunc(object));
  49. if (score > bestMatch.score) {
  50. bestMatch = {
  51. object: object,
  52. score: score
  53. };
  54. }
  55. }
  56. if (bestMatch.score > 0) {
  57. return bestMatch;
  58. } else {
  59. return null;
  60. }
  61. };
  62. numberOfMatchingSegments = function(path1, path2) {
  63. var comps1, comps2, eqCount, len;
  64. path1 = path1.replace(/^\/+/, '').toLowerCase();
  65. path2 = path2.replace(/^\/+/, '').toLowerCase();
  66. if (path1 === path2) {
  67. return 10000;
  68. }
  69. comps1 = path1.split('/').reverse();
  70. comps2 = path2.split('/').reverse();
  71. len = Math.min(comps1.length, comps2.length);
  72. eqCount = 0;
  73. while (eqCount < len && comps1[eqCount] === comps2[eqCount]) {
  74. ++eqCount;
  75. }
  76. return eqCount;
  77. };
  78. pathsMatch = function(path1, path2) {
  79. return numberOfMatchingSegments(path1, path2) > 0;
  80. };
  81. IMAGE_STYLES = [
  82. {
  83. selector: 'background',
  84. styleNames: ['backgroundImage']
  85. }, {
  86. selector: 'border',
  87. styleNames: ['borderImage', 'webkitBorderImage', 'MozBorderImage']
  88. }
  89. ];
  90. exports.Reloader = Reloader = (function() {
  91. function Reloader(window, console, Timer) {
  92. this.window = window;
  93. this.console = console;
  94. this.Timer = Timer;
  95. this.document = this.window.document;
  96. this.importCacheWaitPeriod = 200;
  97. this.plugins = [];
  98. }
  99. Reloader.prototype.addPlugin = function(plugin) {
  100. return this.plugins.push(plugin);
  101. };
  102. Reloader.prototype.analyze = function(callback) {
  103. return results;
  104. };
  105. Reloader.prototype.reload = function(path, options) {
  106. var base, i, len1, plugin, ref;
  107. this.options = options;
  108. if ((base = this.options).stylesheetReloadTimeout == null) {
  109. base.stylesheetReloadTimeout = 15000;
  110. }
  111. ref = this.plugins;
  112. for (i = 0, len1 = ref.length; i < len1; i++) {
  113. plugin = ref[i];
  114. if (plugin.reload && plugin.reload(path, options)) {
  115. return;
  116. }
  117. }
  118. if (options.liveCSS && path.match(/\.css(?:\.map)?$/i)) {
  119. if (this.reloadStylesheet(path)) {
  120. return;
  121. }
  122. }
  123. if (options.liveImg && path.match(/\.(jpe?g|png|gif)$/i)) {
  124. this.reloadImages(path);
  125. return;
  126. }
  127. if (options.isChromeExtension) {
  128. this.reloadChromeExtension();
  129. return;
  130. }
  131. return this.reloadPage();
  132. };
  133. Reloader.prototype.reloadPage = function() {
  134. return this.window.document.location.reload();
  135. };
  136. Reloader.prototype.reloadChromeExtension = function() {
  137. return this.window.chrome.runtime.reload();
  138. };
  139. Reloader.prototype.reloadImages = function(path) {
  140. var expando, i, img, j, k, len1, len2, len3, len4, m, ref, ref1, ref2, ref3, results1, selector, styleNames, styleSheet;
  141. expando = this.generateUniqueString();
  142. ref = this.document.images;
  143. for (i = 0, len1 = ref.length; i < len1; i++) {
  144. img = ref[i];
  145. if (pathsMatch(path, pathFromUrl(img.src))) {
  146. img.src = this.generateCacheBustUrl(img.src, expando);
  147. }
  148. }
  149. if (this.document.querySelectorAll) {
  150. for (j = 0, len2 = IMAGE_STYLES.length; j < len2; j++) {
  151. ref1 = IMAGE_STYLES[j], selector = ref1.selector, styleNames = ref1.styleNames;
  152. ref2 = this.document.querySelectorAll("[style*=" + selector + "]");
  153. for (k = 0, len3 = ref2.length; k < len3; k++) {
  154. img = ref2[k];
  155. this.reloadStyleImages(img.style, styleNames, path, expando);
  156. }
  157. }
  158. }
  159. if (this.document.styleSheets) {
  160. ref3 = this.document.styleSheets;
  161. results1 = [];
  162. for (m = 0, len4 = ref3.length; m < len4; m++) {
  163. styleSheet = ref3[m];
  164. results1.push(this.reloadStylesheetImages(styleSheet, path, expando));
  165. }
  166. return results1;
  167. }
  168. };
  169. Reloader.prototype.reloadStylesheetImages = function(styleSheet, path, expando) {
  170. var e, error, i, j, len1, len2, rule, rules, styleNames;
  171. try {
  172. rules = styleSheet != null ? styleSheet.cssRules : void 0;
  173. } catch (error) {
  174. e = error;
  175. }
  176. if (!rules) {
  177. return;
  178. }
  179. for (i = 0, len1 = rules.length; i < len1; i++) {
  180. rule = rules[i];
  181. switch (rule.type) {
  182. case CSSRule.IMPORT_RULE:
  183. this.reloadStylesheetImages(rule.styleSheet, path, expando);
  184. break;
  185. case CSSRule.STYLE_RULE:
  186. for (j = 0, len2 = IMAGE_STYLES.length; j < len2; j++) {
  187. styleNames = IMAGE_STYLES[j].styleNames;
  188. this.reloadStyleImages(rule.style, styleNames, path, expando);
  189. }
  190. break;
  191. case CSSRule.MEDIA_RULE:
  192. this.reloadStylesheetImages(rule, path, expando);
  193. }
  194. }
  195. };
  196. Reloader.prototype.reloadStyleImages = function(style, styleNames, path, expando) {
  197. var i, len1, newValue, styleName, value;
  198. for (i = 0, len1 = styleNames.length; i < len1; i++) {
  199. styleName = styleNames[i];
  200. value = style[styleName];
  201. if (typeof value === 'string') {
  202. newValue = value.replace(/\burl\s*\(([^)]*)\)/, (function(_this) {
  203. return function(match, src) {
  204. if (pathsMatch(path, pathFromUrl(src))) {
  205. return "url(" + (_this.generateCacheBustUrl(src, expando)) + ")";
  206. } else {
  207. return match;
  208. }
  209. };
  210. })(this));
  211. if (newValue !== value) {
  212. style[styleName] = newValue;
  213. }
  214. }
  215. }
  216. };
  217. Reloader.prototype.reloadStylesheet = function(path) {
  218. var i, imported, j, k, len1, len2, len3, len4, link, links, m, match, ref, ref1, style;
  219. links = (function() {
  220. var i, len1, ref, results1;
  221. ref = this.document.getElementsByTagName('link');
  222. results1 = [];
  223. for (i = 0, len1 = ref.length; i < len1; i++) {
  224. link = ref[i];
  225. if (link.rel.match(/^stylesheet$/i) && !link.__LiveReload_pendingRemoval) {
  226. results1.push(link);
  227. }
  228. }
  229. return results1;
  230. }).call(this);
  231. imported = [];
  232. ref = this.document.getElementsByTagName('style');
  233. for (i = 0, len1 = ref.length; i < len1; i++) {
  234. style = ref[i];
  235. if (style.sheet) {
  236. this.collectImportedStylesheets(style, style.sheet, imported);
  237. }
  238. }
  239. for (j = 0, len2 = links.length; j < len2; j++) {
  240. link = links[j];
  241. this.collectImportedStylesheets(link, link.sheet, imported);
  242. }
  243. if (this.window.StyleFix && this.document.querySelectorAll) {
  244. ref1 = this.document.querySelectorAll('style[data-href]');
  245. for (k = 0, len3 = ref1.length; k < len3; k++) {
  246. style = ref1[k];
  247. links.push(style);
  248. }
  249. }
  250. this.console.log("LiveReload found " + links.length + " LINKed stylesheets, " + imported.length + " @imported stylesheets");
  251. match = pickBestMatch(path, links.concat(imported), (function(_this) {
  252. return function(l) {
  253. return pathFromUrl(_this.linkHref(l));
  254. };
  255. })(this));
  256. if (match) {
  257. if (match.object.rule) {
  258. this.console.log("LiveReload is reloading imported stylesheet: " + match.object.href);
  259. this.reattachImportedRule(match.object);
  260. } else {
  261. this.console.log("LiveReload is reloading stylesheet: " + (this.linkHref(match.object)));
  262. this.reattachStylesheetLink(match.object);
  263. }
  264. } else {
  265. if (this.options.reloadMissingCSS) {
  266. this.console.log("LiveReload will reload all stylesheets because path '" + path + "' did not match any specific one. To disable this behavior, set 'options.reloadMissingCSS' to 'false'.");
  267. for (m = 0, len4 = links.length; m < len4; m++) {
  268. link = links[m];
  269. this.reattachStylesheetLink(link);
  270. }
  271. } else {
  272. this.console.log("LiveReload will not reload path '" + path + "' because the stylesheet was not found on the page and 'options.reloadMissingCSS' was set to 'false'.");
  273. }
  274. }
  275. return true;
  276. };
  277. Reloader.prototype.collectImportedStylesheets = function(link, styleSheet, result) {
  278. var e, error, i, index, len1, rule, rules;
  279. try {
  280. rules = styleSheet != null ? styleSheet.cssRules : void 0;
  281. } catch (error) {
  282. e = error;
  283. }
  284. if (rules && rules.length) {
  285. for (index = i = 0, len1 = rules.length; i < len1; index = ++i) {
  286. rule = rules[index];
  287. switch (rule.type) {
  288. case CSSRule.CHARSET_RULE:
  289. continue;
  290. case CSSRule.IMPORT_RULE:
  291. result.push({
  292. link: link,
  293. rule: rule,
  294. index: index,
  295. href: rule.href
  296. });
  297. this.collectImportedStylesheets(link, rule.styleSheet, result);
  298. break;
  299. default:
  300. break;
  301. }
  302. }
  303. }
  304. };
  305. Reloader.prototype.waitUntilCssLoads = function(clone, func) {
  306. var callbackExecuted, executeCallback, poll;
  307. callbackExecuted = false;
  308. executeCallback = (function(_this) {
  309. return function() {
  310. if (callbackExecuted) {
  311. return;
  312. }
  313. callbackExecuted = true;
  314. return func();
  315. };
  316. })(this);
  317. clone.onload = (function(_this) {
  318. return function() {
  319. _this.console.log("LiveReload: the new stylesheet has finished loading");
  320. _this.knownToSupportCssOnLoad = true;
  321. return executeCallback();
  322. };
  323. })(this);
  324. if (!this.knownToSupportCssOnLoad) {
  325. (poll = (function(_this) {
  326. return function() {
  327. if (clone.sheet) {
  328. _this.console.log("LiveReload is polling until the new CSS finishes loading...");
  329. return executeCallback();
  330. } else {
  331. return _this.Timer.start(50, poll);
  332. }
  333. };
  334. })(this))();
  335. }
  336. return this.Timer.start(this.options.stylesheetReloadTimeout, executeCallback);
  337. };
  338. Reloader.prototype.linkHref = function(link) {
  339. return link.href || link.getAttribute('data-href');
  340. };
  341. Reloader.prototype.reattachStylesheetLink = function(link) {
  342. var clone, parent;
  343. if (link.__LiveReload_pendingRemoval) {
  344. return;
  345. }
  346. link.__LiveReload_pendingRemoval = true;
  347. if (link.tagName === 'STYLE') {
  348. clone = this.document.createElement('link');
  349. clone.rel = 'stylesheet';
  350. clone.media = link.media;
  351. clone.disabled = link.disabled;
  352. } else {
  353. clone = link.cloneNode(false);
  354. }
  355. clone.href = this.generateCacheBustUrl(this.linkHref(link));
  356. parent = link.parentNode;
  357. if (parent.lastChild === link) {
  358. parent.appendChild(clone);
  359. } else {
  360. parent.insertBefore(clone, link.nextSibling);
  361. }
  362. return this.waitUntilCssLoads(clone, (function(_this) {
  363. return function() {
  364. var additionalWaitingTime;
  365. if (/AppleWebKit/.test(navigator.userAgent)) {
  366. additionalWaitingTime = 5;
  367. } else {
  368. additionalWaitingTime = 200;
  369. }
  370. return _this.Timer.start(additionalWaitingTime, function() {
  371. var ref;
  372. if (!link.parentNode) {
  373. return;
  374. }
  375. link.parentNode.removeChild(link);
  376. clone.onreadystatechange = null;
  377. return (ref = _this.window.StyleFix) != null ? ref.link(clone) : void 0;
  378. });
  379. };
  380. })(this));
  381. };
  382. Reloader.prototype.reattachImportedRule = function(arg) {
  383. var href, index, link, media, newRule, parent, rule, tempLink;
  384. rule = arg.rule, index = arg.index, link = arg.link;
  385. parent = rule.parentStyleSheet;
  386. href = this.generateCacheBustUrl(rule.href);
  387. media = rule.media.length ? [].join.call(rule.media, ', ') : '';
  388. newRule = "@import url(\"" + href + "\") " + media + ";";
  389. rule.__LiveReload_newHref = href;
  390. tempLink = this.document.createElement("link");
  391. tempLink.rel = 'stylesheet';
  392. tempLink.href = href;
  393. tempLink.__LiveReload_pendingRemoval = true;
  394. if (link.parentNode) {
  395. link.parentNode.insertBefore(tempLink, link);
  396. }
  397. return this.Timer.start(this.importCacheWaitPeriod, (function(_this) {
  398. return function() {
  399. if (tempLink.parentNode) {
  400. tempLink.parentNode.removeChild(tempLink);
  401. }
  402. if (rule.__LiveReload_newHref !== href) {
  403. return;
  404. }
  405. parent.insertRule(newRule, index);
  406. parent.deleteRule(index + 1);
  407. rule = parent.cssRules[index];
  408. rule.__LiveReload_newHref = href;
  409. return _this.Timer.start(_this.importCacheWaitPeriod, function() {
  410. if (rule.__LiveReload_newHref !== href) {
  411. return;
  412. }
  413. parent.insertRule(newRule, index);
  414. return parent.deleteRule(index + 1);
  415. });
  416. };
  417. })(this));
  418. };
  419. Reloader.prototype.generateUniqueString = function() {
  420. return 'livereload=' + Date.now();
  421. };
  422. Reloader.prototype.generateCacheBustUrl = function(url, expando) {
  423. var hash, oldParams, originalUrl, params, ref;
  424. if (expando == null) {
  425. expando = this.generateUniqueString();
  426. }
  427. ref = splitUrl(url), url = ref.url, hash = ref.hash, oldParams = ref.params;
  428. if (this.options.overrideURL) {
  429. if (url.indexOf(this.options.serverURL) < 0) {
  430. originalUrl = url;
  431. url = this.options.serverURL + this.options.overrideURL + "?url=" + encodeURIComponent(url);
  432. this.console.log("LiveReload is overriding source URL " + originalUrl + " with " + url);
  433. }
  434. }
  435. params = oldParams.replace(/(\?|&)livereload=(\d+)/, function(match, sep) {
  436. return "" + sep + expando;
  437. });
  438. if (params === oldParams) {
  439. if (oldParams.length === 0) {
  440. params = "?" + expando;
  441. } else {
  442. params = oldParams + "&" + expando;
  443. }
  444. }
  445. return url + params + hash;
  446. };
  447. return Reloader;
  448. })();
  449. }).call(this);