Files
less.js/lib/less/environment/browser.js
2014-08-14 17:27:16 +01:00

207 lines
7.1 KiB
JavaScript

/*global window, XMLHttpRequest */
module.exports = function(less, isFileProtocol, log, logLevel) {
var fileCache = {};
//TODOS - move log somewhere. pathDiff and doing something similiar in node. use pathDiff in the other browser file for the initial load
// isFileProtocol is global
function getXMLHttpRequest() {
if (window.XMLHttpRequest && (window.location.protocol !== "file:" || !("ActiveXObject" in window))) {
return new XMLHttpRequest();
} else {
try {
/*global ActiveXObject */
return new ActiveXObject("Microsoft.XMLHTTP");
} catch (e) {
log("browser doesn't support AJAX.", logLevel.errors);
return null;
}
}
}
return {
// make generic but overriddable
warn: function warn(env, msg) {
console.warn(msg);
},
// make generic but overriddable
getPath: function getPath(env, filename) {
var j = filename.lastIndexOf('/');
if (j < 0) {
j = filename.lastIndexOf('\\');
}
if (j < 0) {
return "";
}
return filename.slice(0, j + 1);
},
// make generic but overriddable
isPathAbsolute: function isPathAbsolute(env, filename) {
return /^(?:[a-z-]+:|\/|\\)/i.test(filename);
},
alwaysMakePathsAbsolute: function alwaysMakePathsAbsolute() {
return true;
},
getCleanCSS: function () {
},
supportsDataURI: function() {
return false;
},
pathDiff: function pathDiff(url, baseUrl) {
// diff between two paths to create a relative path
var urlParts = this.extractUrlParts(url),
baseUrlParts = this.extractUrlParts(baseUrl),
i, max, urlDirectories, baseUrlDirectories, diff = "";
if (urlParts.hostPart !== baseUrlParts.hostPart) {
return "";
}
max = Math.max(baseUrlParts.directories.length, urlParts.directories.length);
for(i = 0; i < max; i++) {
if (baseUrlParts.directories[i] !== urlParts.directories[i]) { break; }
}
baseUrlDirectories = baseUrlParts.directories.slice(i);
urlDirectories = urlParts.directories.slice(i);
for(i = 0; i < baseUrlDirectories.length-1; i++) {
diff += "../";
}
for(i = 0; i < urlDirectories.length-1; i++) {
diff += urlDirectories[i] + "/";
}
return diff;
},
join: function join(basePath, laterPath) {
if (!basePath) {
return laterPath;
}
return this.extractUrlParts(laterPath, basePath).path;
},
// helper function, not part of API
extractUrlParts: function extractUrlParts(url, baseUrl) {
// urlParts[1] = protocol&hostname || /
// urlParts[2] = / if path relative to host base
// urlParts[3] = directories
// urlParts[4] = filename
// urlParts[5] = parameters
var urlPartsRegex = /^((?:[a-z-]+:)?\/+?(?:[^\/\?#]*\/)|([\/\\]))?((?:[^\/\\\?#]*[\/\\])*)([^\/\\\?#]*)([#\?].*)?$/i,
urlParts = url.match(urlPartsRegex),
returner = {}, directories = [], i, baseUrlParts;
if (!urlParts) {
throw new Error("Could not parse sheet href - '"+url+"'");
}
// Stylesheets in IE don't always return the full path
if (!urlParts[1] || urlParts[2]) {
baseUrlParts = baseUrl.match(urlPartsRegex);
if (!baseUrlParts) {
throw new Error("Could not parse page url - '"+baseUrl+"'");
}
urlParts[1] = urlParts[1] || baseUrlParts[1] || "";
if (!urlParts[2]) {
urlParts[3] = baseUrlParts[3] + urlParts[3];
}
}
if (urlParts[3]) {
directories = urlParts[3].replace(/\\/g, "/").split("/");
// extract out . before .. so .. doesn't absorb a non-directory
for(i = 0; i < directories.length; i++) {
if (directories[i] === ".") {
directories.splice(i, 1);
i -= 1;
}
}
for(i = 0; i < directories.length; i++) {
if (directories[i] === ".." && i > 0) {
directories.splice(i-1, 2);
i -= 2;
}
}
}
returner.hostPart = urlParts[1];
returner.directories = directories;
returner.path = urlParts[1] + directories.join("/");
returner.fileUrl = returner.path + (urlParts[4] || "");
returner.url = returner.fileUrl + (urlParts[5] || "");
return returner;
},
doXHR: function doXHR(url, type, callback, errback) {
var xhr = getXMLHttpRequest();
var async = isFileProtocol ? less.fileAsync : less.async;
if (typeof(xhr.overrideMimeType) === 'function') {
xhr.overrideMimeType('text/css');
}
log("XHR: Getting '" + url + "'", logLevel.debug);
xhr.open('GET', url, async);
xhr.setRequestHeader('Accept', type || 'text/x-less, text/css; q=0.9, */*; q=0.5');
xhr.send(null);
function handleResponse(xhr, callback, errback) {
if (xhr.status >= 200 && xhr.status < 300) {
callback(xhr.responseText,
xhr.getResponseHeader("Last-Modified"));
} else if (typeof(errback) === 'function') {
errback(xhr.status, url);
}
}
if (isFileProtocol && !less.fileAsync) {
if (xhr.status === 0 || (xhr.status >= 200 && xhr.status < 300)) {
callback(xhr.responseText);
} else {
errback(xhr.status, url);
}
} else if (async) {
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
handleResponse(xhr, callback, errback);
}
};
} else {
handleResponse(xhr, callback, errback);
}
},
loadFile: function loadFile(env, filename, currentDirectory, callback) {
if (currentDirectory && !this.isPathAbsolute(env, filename)) {
filename = currentDirectory + filename;
}
// sheet may be set to the stylesheet for the initial load or a collection of properties including
// some env variables for imports
var hrefParts = this.extractUrlParts(filename, window.location.href);
var href = hrefParts.url;
if (env.useFileCache && fileCache[href]) {
try {
var lessText = fileCache[href];
callback(null, lessText, href, { lastModified: new Date() });
} catch (e) {
callback(e, null, href);
}
return;
}
this.doXHR(href, env.mime, function doXHRCallback(data, lastModified) {
// per file cache
fileCache[href] = data;
// Use remote copy (re-parse)
callback(null, data, href, { lastModified: lastModified });
}, function doXHRError(status, url) {
callback({ type: 'File', message: "'" + url + "' wasn't found (" + status + ")" }, null, href);
});
}
};
};