/*
* Require-CSS RequireJS css! loader plugin
* Guy Bedford 2013
* MIT
*/
/*
*
* Usage:
* require(['css!./mycssFile']);
*
* NB leave out the '.css' extension.
*
* - Fully supports cross origin CSS loading
* - Works with builds
*
* Tested and working in (up to latest versions as of March 2013):
* Android
* iOS 6
* IE 6 - 10
* Chome 3 - 26
* Firefox 3.5 - 19
* Opera 10 - 12
*
* browserling.com used for virtual testing environment
*
* Credit to B Cavalier & J Hann for the elegant IE 6 - 9 hack.
*
* Sources that helped along the way:
* - https://developer.mozilla.org/en-US/docs/Browser_detection_using_the_user_agent
* - http://www.phpied.com/when-is-a-stylesheet-really-loaded/
* - https://github.com/cujojs/curl/blob/master/src/curl/plugin/css.js
*
*/
define(['./normalize'], function(normalize) {
function indexOf(a, e) { for (var i=0, l=a.length; i < l; i++) if (a[i] === e) return i; return -1 }
if (typeof window == 'undefined')
return { load: function(n, r, load){ load() } };
// set to true to enable test prompts for device testing
var testing = false;
var head = document.getElementsByTagName('head')[0];
var engine = window.navigator.userAgent.match(/Trident\/([^ ;]*)|AppleWebKit\/([^ ;]*)|Opera\/([^ ;]*)|rv\:([^ ;]*)(.*?)Gecko\/([^ ;]*)|MSIE\s([^ ;]*)/);
var hackLinks = false;
if (!engine) {}
else if (engine[1] || engine[7]) {
hackLinks = parseInt(engine[1]) < 6 || parseInt(engine[7]) <= 9;
engine = 'trident';
}
else if (engine[2]) {
// unfortunately style querying still doesnt work with onload callback in webkit
hackLinks = true;
engine = 'webkit';
}
else if (engine[3]) {
// engine = 'opera';
}
else if (engine[4]) {
hackLinks = parseInt(engine[4]) < 18;
engine = 'gecko';
}
else if (testing)
alert('Engine detection failed');
//main api object
var cssAPI = {};
var absUrlRegEx = /^\/|([^\:\/]*:)/;
cssAPI.pluginBuilder = './css-builder';
// used by layer builds to register their css buffers
// the current layer buffer items (from addBuffer)
var curBuffer = [];
// the callbacks for buffer loads
var onBufferLoad = {};
// the full list of resources in the buffer
var bufferResources = [];
cssAPI.addBuffer = function(resourceId) {
// just in case layer scripts are included twice, also check
// against the previous buffers
if (indexOf(curBuffer, resourceId) != -1)
return;
if (indexOf(bufferResources, resourceId) != -1)
return;
curBuffer.push(resourceId);
bufferResources.push(resourceId);
}
cssAPI.setBuffer = function(css, isLess) {
var pathname = window.location.pathname.split('/');
pathname.pop();
pathname = pathname.join('/') + '/';
var baseParts = require.toUrl('base_url').split('/');
baseParts.pop();
var baseUrl = baseParts.join('/') + '/';
baseUrl = normalize.convertURIBase(baseUrl, pathname, '/');
if (!baseUrl.match(absUrlRegEx))
baseUrl = '/' + baseUrl;
if (baseUrl.substr(baseUrl.length - 1, 1) != '/')
baseUrl = baseUrl + '/';
cssAPI.inject(normalize(css, baseUrl, pathname));
// set up attach callback if registered
// clear the current buffer for the next layer
// (just the less or css part as we have two buffers in one effectively)
for (var i = 0; i < curBuffer.length; i++) {
// find the resources in the less or css buffer dependening which one this is
if ((isLess && curBuffer[i].substr(curBuffer[i].length - 5, 5) == '.less') ||
(!isLess && curBuffer[i].substr(curBuffer[i].length - 4, 4) == '.css')) {
(function(resourceId) {
// mark that the onBufferLoad is about to be called (set to true if not already a callback function)
onBufferLoad[resourceId] = onBufferLoad[resourceId] || true;
// set a short timeout (as injection isn't instant in Chrome), then call the load
setTimeout(function() {
if (typeof onBufferLoad[resourceId] == 'function')
onBufferLoad[resourceId]();
// remove from onBufferLoad to indicate loaded
delete onBufferLoad[resourceId];
}, 7);
})(curBuffer[i]);
// remove the current resource from the buffer
curBuffer.splice(i--, 1);
}
}
}
cssAPI.attachBuffer = function(resourceId, load) {
// attach can happen during buffer collecting, or between injection and callback
// we assume it is not possible to attach multiple callbacks
// requirejs plugin load function ensures this by queueing duplicate calls
// check if the resourceId is in the current buffer
for (var i = 0; i < curBuffer.length; i++)
if (curBuffer[i] == resourceId) {
onBufferLoad[resourceId] = load;
return true;
}
// check if the resourceId is waiting for injection callback
// (onBufferLoad === true is a shortcut indicator for this)
if (onBufferLoad[resourceId] === true) {
onBufferLoad[resourceId] = load;
return true;
}
// if it's in the full buffer list and not either of the above, its loaded already
if (indexOf(bufferResources, resourceId) != -1) {
load();
return true;
}
}
var webkitLoadCheck = function(link, callback) {
setTimeout(function() {
for (var i = 0; i < document.styleSheets.length; i++) {
var sheet = document.styleSheets[i];
if (sheet.href == link.href)
return callback();
}
webkitLoadCheck(link, callback);
}, 10);
}
var mozillaLoadCheck = function(style, callback) {
setTimeout(function() {
try {
style.sheet.cssRules;
return callback();
} catch (e){}
mozillaLoadCheck(style, callback);
}, 10);
}
// ie link detection, as adapted from https://github.com/cujojs/curl/blob/master/src/curl/plugin/css.js
if (engine == 'trident' && hackLinks) {
var ieStyles = [],
ieQueue = [],
ieStyleCnt = 0;
var ieLoad = function(url, callback) {
var style;
ieQueue.push({
url: url,
cb: callback
});
style = ieStyles.shift();
if (!style && ieStyleCnt++ < 12) {
style = document.createElement('style');
head.appendChild(style);
}
ieLoadNextImport(style);
}
var ieLoadNextImport = function(style) {
var curImport = ieQueue.shift();
if (!curImport) {
style.onload = noop;
ieStyles.push(style);
return;
}
style.onload = function() {
curImport.cb(curImport.ss);
ieLoadNextImport(style);
};
var curSheet = style.styleSheet;
curImport.ss = curSheet.imports[curSheet.addImport(curImport.url)];
}
}
// uses the load method
var createLink = function(url) {
var link = document.createElement('link');
link.type = 'text/css';
link.rel = 'stylesheet';
link.href = url;
return link;
}
var noop = function(){}
cssAPI.linkLoad = function(url, callback) {
var timeout = setTimeout(function() {
if (testing) alert('timeout');
callback();
}, waitSeconds * 1000 - 100);
var _callback = function() {
clearTimeout(timeout);
if (link)
link.onload = noop;
// for style querying, a short delay still seems necessary
setTimeout(callback, 7);
}
if (!hackLinks) {
var link = createLink(url);
link.onload = _callback;
head.appendChild(link);
}
// hacks
else {
if (engine == 'webkit') {
var link = createLink(url);
webkitLoadCheck(link, _callback);
head.appendChild(link);
}
else if (engine == 'gecko') {
var style = document.createElement('style');
style.textContent = '@import "' + url + '"';
mozillaLoadCheck(style, _callback);
head.appendChild(style);
}
else if (engine == 'trident')
ieLoad(url, _callback);
}
}
/* injection api */
var progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'];
var fileCache = {};
var get = function(url, callback, errback) {
if (fileCache[url]) {
callback(fileCache[url]);
return;
}
var xhr, i, progId;
if (typeof XMLHttpRequest !== 'undefined')
xhr = new XMLHttpRequest();
else if (typeof ActiveXObject !== 'undefined')
for (i = 0; i < 3; i += 1) {
progId = progIds[i];
try {
xhr = new ActiveXObject(progId);
}
catch (e) {}
if (xhr) {
progIds = [progId]; // so faster next time
break;
}
}
xhr.open('GET', url, requirejs.inlineRequire ? false : true);
xhr.onreadystatechange = function (evt) {
var status, err;
//Do not explicitly handle errors, those should be
//visible via console output in the browser.
if (xhr.readyState === 4) {
status = xhr.status;
if (status > 399 && status < 600) {
//An http 4xx or 5xx error. Signal an error.
err = new Error(url + ' HTTP status: ' + status);
err.xhr = xhr;
errback(err);
}
else {
fileCache[url] = xhr.responseText;
callback(xhr.responseText);
}
}
};
xhr.send(null);
}
//uses the