// // browser.js - client-side engine // /*global window, document, location */ var less; /* TODO - options is now hidden - we should expose it on the less object, but not have it "as" the less object less = { fileAsync: true } then access as less.environment.options.fileAsync ? */ var isFileProtocol = /^(file|chrome(-extension)?|resource|qrc|app):/.test(window.location.protocol), options = window.less || {}; window.less = less = require('../less')(require("./environment")); var environment = less.environment, browserImport = require("./browser-import")(options, isFileProtocol, less.logger); environment.addFileManager(browserImport); require("./log-listener")(less, options); //var utils = require("./utils"); var errors = require("./error-reporting")(window, less, options); var browser = require("./browser"); options.env = options.env || (window.location.hostname == '127.0.0.1' || window.location.hostname == '0.0.0.0' || window.location.hostname == 'localhost' || (window.location.port && window.location.port.length > 0) || isFileProtocol ? 'development' : 'production'); // Load styles asynchronously (default: false) // // This is set to `false` by default, so that the body // doesn't start loading before the stylesheets are parsed. // Setting this to `true` can result in flickering. // options.async = options.async || false; options.fileAsync = options.fileAsync || false; // Interval between watch polls options.poll = options.poll || (isFileProtocol ? 1000 : 1500); //Setup user functions if (options.functions) { less.functions.functionRegistry.addMultiple(options.functions); } var dumpLineNumbers = /!dumpLineNumbers:(comments|mediaquery|all)/.exec(location.hash); if (dumpLineNumbers) { options.dumpLineNumbers = dumpLineNumbers[1]; } var typePattern = /^text\/(x-)?less$/; var cache = null; function postProcessCSS(styles) { if (options.postProcessor && typeof options.postProcessor === 'function') { styles = options.postProcessor.call(styles, styles) || styles; } return styles; } // Replace the cache system // Don't update the local store if the file wasn't modified //if (lastModified && cache) { // logger.info('saving ' + href + ' to cache.'); // try { // cache.setItem(href, styles); // cache.setItem(href + ':timestamp', lastModified); // } catch(e) { // //TODO - could do with adding more robust error handling // logger.error('failed to save'); // }// //} function clone(obj) { var cloned = {}; for(var prop in obj) { if (obj.hasOwnProperty(prop)) { cloned[prop] = obj[prop]; } } return cloned; } // only really needed for phantom function bind(func, thisArg) { var curryArgs = Array.prototype.slice.call(arguments, 2); return function() { var args = curryArgs.concat(Array.prototype.slice.call(arguments, 0)); return func.apply(thisArg, args); }; } function loadStyles(modifyVars) { var styles = document.getElementsByTagName('style'), style; for (var i = 0; i < styles.length; i++) { style = styles[i]; if (style.type.match(typePattern)) { var instanceOptions = clone(options); instanceOptions.modifyVars = modifyVars; var lessText = style.innerHTML || ''; instanceOptions.filename = document.location.href.replace(/#.*$/, ''); if (modifyVars || instanceOptions.globalVars) { instanceOptions.useFileCache = true; } /*jshint loopfunc:true */ // use closure to store current style less.render(lessText, instanceOptions) .then(bind(function(style, result) { style.type = 'text/css'; if (style.styleSheet) { style.styleSheet.cssText = result.css; } else { style.innerHTML = result.css; } }, null, style), function(e) { errors.add(e, "inline"); }); } } } function loadStyleSheet(sheet, callback, reload, remaining, modifyVars) { var instanceOptions = clone(options); instanceOptions.mime = sheet.type; if (modifyVars) { instanceOptions.modifyVars = modifyVars; } if (modifyVars || options.globalVars) { instanceOptions.useFileCache = true; } browserImport.loadFile(sheet.href, null, instanceOptions, environment) .then(function loadInitialFileCallback(loadedFile) { var data = loadedFile.contents, path = loadedFile.filename, webInfo = loadedFile.webInfo; var newFileInfo = { currentDirectory: browserImport.getPath(path), filename: path, rootFilename: path, relativeUrls: instanceOptions.relativeUrls}; newFileInfo.entryPath = newFileInfo.currentDirectory; newFileInfo.rootpath = instanceOptions.rootpath || newFileInfo.currentDirectory; if (webInfo) { webInfo.remaining = remaining; var css = cache && cache.getItem(path), timestamp = cache && cache.getItem(path + ':timestamp'); if (!reload && timestamp && webInfo.lastModified && (new(Date)(webInfo.lastModified).valueOf() === new(Date)(timestamp).valueOf())) { // Use local copy browser.createCSS(window.document, css, sheet); webInfo.local = true; callback(null, null, data, sheet, webInfo, path); return; } } //TODO add tests around how this behaves when reloading errors.remove(path); instanceOptions.rootFileInfo = newFileInfo; less.render(data, instanceOptions) .then(function(result) { callback(null, result.css, data, sheet, webInfo, path); }, function(e) { e.href = path; callback(e); }); }, function(e) { callback(e); }); } function loadStyleSheets(callback, reload, modifyVars) { for (var i = 0; i < less.sheets.length; i++) { loadStyleSheet(less.sheets[i], callback, reload, less.sheets.length - (i + 1), modifyVars); } } function initRunningMode(){ if (less.env === 'development') { less.watchTimer = setInterval(function () { if (less.watchMode) { loadStyleSheets(function (e, css, _, sheet, context) { if (e) { errors.add(e, e.href || sheet.href); } else if (css) { css = postProcessCSS(css); browser.createCSS(window.document, css, sheet, context.lastModified); } }); } }, options.poll); } } // // Watch mode // less.watch = function () { if (!less.watchMode ){ less.env = 'development'; initRunningMode(); } this.watchMode = true; return true; }; less.unwatch = function () {clearInterval(less.watchTimer); this.watchMode = false; return false; }; if (/!watch/.test(location.hash)) { less.watch(); } if (less.env != 'development') { try { cache = (typeof(window.localStorage) === 'undefined') ? null : window.localStorage; } catch (_) {} } // // Get all tags with the 'rel' attribute set to "stylesheet/less" // var links = document.getElementsByTagName('link'); less.sheets = []; for (var i = 0; i < links.length; i++) { if (links[i].rel === 'stylesheet/less' || (links[i].rel.match(/stylesheet/) && (links[i].type.match(typePattern)))) { less.sheets.push(links[i]); } } // // With this function, it's possible to alter variables and re-render // CSS without reloading less-files // less.modifyVars = function(record) { less.refresh(false, record); }; less.refresh = function (reload, modifyVars) { var startTime, endTime; startTime = endTime = new Date(); loadStyleSheets(function (e, css, _, sheet, webInfo) { if (e) { return errors.add(e, e.href || sheet.href); } if (webInfo.local) { less.logger.info("loading " + sheet.href + " from cache."); } else { less.logger.info("rendered " + sheet.href + " successfully."); css = postProcessCSS(css); browser.createCSS(window.document, css, sheet, webInfo.lastModified); } less.logger.info("css for " + sheet.href + " generated in " + (new Date() - endTime) + 'ms'); if (webInfo.remaining === 0) { less.logger.info("less has finished. css generated in " + (new Date() - startTime) + 'ms'); } endTime = new Date(); }, reload, modifyVars); loadStyles(modifyVars); }; less.refreshStyles = loadStyles; less.refresh(less.env === 'development');