| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435 | /* * 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 <link> 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 <style> load method  var styleCnt = 0;  var curStyle;  cssAPI.inject = function(css) {    if (styleCnt < 31) {      curStyle = document.createElement('style');      curStyle.type = 'text/css';      head.appendChild(curStyle);      styleCnt++;    }    if (curStyle.styleSheet)      curStyle.styleSheet.cssText += css;    else      curStyle.appendChild(document.createTextNode(css));  }    // NB add @media query support for media imports  var importRegEx = /@import\s*(url)?\s*(('([^']*)'|"([^"]*)")|\(('([^']*)'|"([^"]*)"|([^\)]*))\))\s*;?/g;  var pathname = window.location.pathname.split('/');  pathname.pop();  pathname = pathname.join('/') + '/';  var loadCSS = function(fileUrl, callback, errback) {    //make file url absolute    if (!fileUrl.match(absUrlRegEx))      fileUrl = '/' + normalize.convertURIBase(fileUrl, pathname, '/');    get(fileUrl, function(css) {      // normalize the css (except import statements)      css = normalize(css, fileUrl, pathname);      // detect all import statements in the css and normalize      var importUrls = [];      var importIndex = [];      var importLength = [];      var match;      while (match = importRegEx.exec(css)) {        var importUrl = match[4] || match[5] || match[7] || match[8] || match[9];        importUrls.push(importUrl);        importIndex.push(importRegEx.lastIndex - match[0].length);        importLength.push(match[0].length);      }      // load the import stylesheets and substitute into the css      var completeCnt = 0;      for (var i = 0; i < importUrls.length; i++)        (function(i) {          loadCSS(importUrls[i], function(importCSS) {            css = css.substr(0, importIndex[i]) + importCSS + css.substr(importIndex[i] + importLength[i]);            var lenDiff = importCSS.length - importLength[i];            for (var j = i + 1; j < importUrls.length; j++)              importIndex[j] += lenDiff;            completeCnt++;            if (completeCnt == importUrls.length) {              callback(css);            }          }, errback);        })(i);      if (importUrls.length == 0)        callback(css);    }, errback);  }    cssAPI.normalize = function(name, normalize) {    if (name.substr(name.length - 4, 4) == '.css')      name = name.substr(0, name.length - 4);        return normalize(name);  }    var waitSeconds;  var alerted = false;  cssAPI.load = function(cssId, req, load, config, parse) {        waitSeconds = waitSeconds || config.waitSeconds || 7;    var resourceId = cssId + (!parse ? '.css' : '.less');    // attach the load function to a buffer if there is one in registration    // if not, we do a full injection load    if (cssAPI.attachBuffer(resourceId, load))      return;    fileUrl = req.toUrl(resourceId);        if (!alerted && testing) {      alert(hackLinks ? 'hacking links' : 'not hacking');      alerted = true;    }    if (!parse) {      cssAPI.linkLoad(fileUrl, load);    }    else {      loadCSS(fileUrl, function(css) {        // run parsing after normalization - since less is a CSS subset this works fine        if (parse)          css = parse(css, function(css) {            cssAPI.inject(css);            setTimeout(load, 7);          });      });    }  }  if (testing)    cssAPI.inspect = function() {      if (stylesheet.styleSheet)        return stylesheet.styleSheet.cssText;      else if (stylesheet.innerHTML)        return stylesheet.innerHTML;    }    return cssAPI;});
 |