/*------------------------------------------------------------------------------ Function: eCSStender() Author: Aaron Gustafson (aaron at easy-designs dot net) Creation Date: 2006-12-03 Version: 1.2.6.4 Homepage: http://eCSStender.org License: MIT License (see homepage) ------------------------------------------------------------------------------*/ (function(){ var // common references UNDEFINED, TRUE = true, FALSE = false, NULL = null, STRING = 'string', NUMBER = 'number', OBJECT = 'object', ARRAY = Array, FUNCTION = Function, REGEXP = RegExp, DOCUMENT = document, WINDOW = window, LOCATION = WINDOW.location.href, EMPTY_FN = function(){}, // Common Strings ECSSTENDER = 'eCSStender', EXTENSION = 'extension', SELECTOR = 'selector', PROPERTY = 'property', SPECIFICITY = 'specificity', SCREEN = 'screen', ALL = 'all', MEDIA = 'media', FIND_BY = 'find_by', TEST = 'test', LOOKUP = 'lookup', FRAGMENT = 'fragment', PREFIX = 'prefix', PROPERTIES = 'properties', CALLBACK = 'callback', FILTER = 'filter', PROCESSED = 'processed', FINGERPRINT = 'fingerprint', PIPES = '||||', EMPTY = '', SPACE = ' ', STAR = '*', SLASH = '/', COLON = ':', SEMICOLON = ';', HYPHEN = '-', OPEN_CURLY = '{', CLOSE_CURLY = '}', DIV = 'div', TYPE = 'type', COMPLETE = 'complete', // Regex Bits ANYTHING = '.*?', HYPHEN_ANYTHING = '-.*', CAPTURE = '$1', // placeholders PH_ATMEDIA = '!' + ECSSTENDER + '-media-placeholder!', // internals __eCSStensions = {}, // eCSStensions for parsing __e_count = 0, // count of registered extensions __t_count = 0, // count of triggered extensions __stylesheets = [], // stylesheets to parse __s = 0, // index of current stylesheet __style_objects = {}, // style rules to track __media_groups = {}, __xhr = NULL, __initialized = FALSE, __ignored_css = [], __ignored_props = [ SELECTOR, SPECIFICITY ], __location = LOCATION.replace( /^\w+:\/\/\/?(.*?)\/.*/, CAPTURE ), __local = ( LOCATION.indexOf( 'http' ) !== 0 ), __delayed = {}, // delayed stylesheets to write __on_complete = [], // expando handling (for IE) __expando = DOCUMENT.expando, toggleExpando = EMPTY_FN, // for embedding stylesheets __head = DOCUMENT.getElementsByTagName( 'head' )[0], __body = NULL, __style = newElement( 'style' ), __embedded_css = [], addRules = EMPTY_FN, // caching COUNT = '-count', __modified = {}, __no_cache = FALSE, __cached_out = FALSE, __local_cache = { xhr: {}, extension: {}, selector: {}, property: {} }, __headers = {}, __cache_object, readFromBrowserCache = EMPTY_FN, writeToBrowserCache = EMPTY_FN, clearBrowserCache = EMPTY_FN, // other stuff __script = newElement( 'script' ), // useful RegExps // for breaking on commas REGEXP_COMMA = /\s*,\s*/, // for getting file names REGEXP_FILE = /.*\/(.*?\..*?)(?:\?.*)?$/, // generalized @ rules REGEXP_ATRULE = /@([\w-]+)(.*?)\{([^}]*)\}/ig, // for splitting properties from values REGEXP_P_V = /:(?!\/\/)/, // eCSStender Object eCSStender = { name: ECSSTENDER, version: '1.2.6.4', fonts: [], pages: {}, at: {}, methods: {}, cache: FALSE, exec_time: 0 }; // window object WINDOW.eCSStender = eCSStender; /*------------------------------------* * Private Methods * *------------------------------------*/ function initialize() { // keep us from going off more than 1x if ( __initialized ){ return; } __initialized = TRUE; // performance logging eCSStender.exec_time = now(); // DOM Access __body = DOCUMENT.getElementsByTagName( 'body' )[0]; // innards readBrowserCache(); getActiveStylesheets(); parseStyles(); } function wrapUp() { validateCache(); runTests(); eCSStend(); triggerCallbacks(); writeBrowserCache(); // performance logging eCSStender.exec_time = ( now() - eCSStender.exec_time ) * .001; triggerOnCompletes(); } function getActiveStylesheets() { var stylesheets = DOCUMENT.styleSheets, s = 0, sLen = stylesheets.length; for ( ; s 0 ) { __xhr = TRUE; } } function parseStyles() { var s=0, sLen=__stylesheets.length, media, m, mLen; for ( ; sj || ( i === 0 && j === 0 ) ){ eCSStender.cache = FALSE; } } function runTests() { if ( eCSStender.cache ){ return; } var temp = {}, e, count=0, extension_test; for ( e in __eCSStensions ) { if ( ! isInheritedProperty( __eCSStensions, e ) ) { extension_test = __eCSStensions[e][TEST]; // verify test (if any) if ( ! defined( extension_test ) || ( is( extension_test, FUNCTION ) && extension_test() ) ) { // if no test or test is passed, push to the temp array temp[e] = __eCSStensions[e]; count++; } } } // reset the __eCSStensions array __eCSStensions = temp; __e_count = count; } function eCSStend() { // see if there's anything to do if ( __e_count < 1 ){ return; } var medium, styles, sorted_styles, s, sLen, selector, eLoop, e, e_media, e_lookup, e_find_by, lookups, l, lLen, temp, t, lookup, property; // no cache if ( ! eCSStender.cache ) { // parse by medium for ( medium in __style_objects ) { // safety for users who are using Prototype or any code that extends Object if ( ! isInheritedProperty( __style_objects, medium ) ) { // start the processing in earnest styles = __style_objects[medium]; sorted_styles = getSortedArray( styles ); for ( s=0, sLen=sorted_styles.length; s y ) ? 1 : 0 ) ); } function getSpecificity( selector ) { var s = 0, matches; // replace all child and adjascent sibling selectors selector = selector.replace( /\s*\+\s*|\s*\>\s*/, SPACE ); // adjust :not() to simplify calculations (since it counts toward specificity, as do its contents) selector = selector.replace( /(:not)\((.*)\)/, '$1 $2' ); // match id selectors (weight: 100) matches = selector.match( /#/ ); if ( matches != NULL ) s += ( matches.length * 100 ); selector = selector.replace( /#[\w-_]+/, EMPTY ); // remove (to keep the regexs simple) // match class, pseudo-class, and attribute selectors (weight: 10) matches = selector.match( /::|:|\.|\[.*?\]/ ); if ( matches != NULL ) s += ( matches.length * 10 ); selector = selector.replace( /(?:::|:|\.)[\w-_()]+|\[.*?\]/, EMPTY ); // remove // match element selectors (weight: 1) - they should be all that's left matches = trim( selector ) != EMPTY ? selector.split( SPACE ) : []; s += matches.length; return s; } function determinePath( stylesheet ) { var // for determining if it's a fully-executed path REGEXP_URL = /\w+?\:\/\//, actual_path = stylesheet.actual_path, css_path = actual_path || stylesheet.href, parent = stylesheet.parentStyleSheet, parent_path = EMPTY, prefix = EMPTY, full_url = FALSE, curr_path, path_last_slash, file_name; if ( ! css_path ) { css_path = NULL; } else { full_url = css_path.match( REGEXP_URL ); if ( is( full_url, ARRAY ) ) { full_url = ( full_url.length > 0 ); } } // we only want sheets if ( ! actual_path && // that don't already have a path ! full_url ) // that don't have a full URL { if ( css_path.indexOf( SLASH ) === 0 ) { css_path = css_path.substring( 1 ); } curr_path = LOCATION.substring( 0, LOCATION.lastIndexOf( SLASH ) ); path_last_slash = css_path.lastIndexOf( SLASH ); file_name = css_path.substring( path_last_slash + 1 ); // check for an owner if ( parent == NULL ) { if ( defined( stylesheet.ownerNode ) && defined( CSSImportRule ) && is( stylesheet.ownerRule, CSSImportRule ) ) { parent = stylesheet.ownerRule.parentStyleSheet; } } // still no parent, use the css path itself if ( parent == NULL ) { prefix = curr_path + SLASH + css_path.substring( 0, path_last_slash ); } // get the owner's path else { parent_path = determinePath( parent ); prefix = parent_path.substring( 0, parent_path.lastIndexOf( SLASH ) ); } css_path = prefix + SLASH + file_name; } stylesheet.actual_path = css_path; return css_path; } function determineMedia( stylesheet ) { var media = stylesheet.media; // W3C compliant if ( ! is( media, STRING ) ) { determineMedia = function( stylesheet ){ var media = stylesheet.media, owner = stylesheet.ownerRule, mediaText = FALSE; if ( ! is( media, STRING ) ) { // imported if ( owner != NULL ) { // media assignment in the import mediaText = owner.media.mediaText; if ( ! mediaText ) { // no media assignment... inherit mediaText = determineMedia( owner.parentStyleSheet ); } } // media is defined else { mediaText = media.mediaText; } } // default = screen stylesheet.actual_media = mediaText ? mediaText : SCREEN; if ( is( stylesheet.actual_media, STRING ) ) { stylesheet.actual_media = stylesheet.actual_media.split( REGEXP_COMMA ); } return stylesheet.actual_media; }; } // old school else { determineMedia = function( stylesheet ){ var mediaText = stylesheet.media; // default = screen stylesheet.actual_media = mediaText ? mediaText : SCREEN; if ( is( stylesheet.actual_media, STRING ) ) { stylesheet.actual_media = stylesheet.actual_media.split( REGEXP_COMMA ); } return stylesheet.actual_media; }; } return determineMedia( stylesheet ); } function extractAtBlocks( css ) { // extract @font-face css = extractFonts( css ); // extract @page css = extractPages( css ); // handle @media css = handleMediaGroups( css ); // extract other @ rules css = extractOtherAtRules( css ); // return the string return css; } function extractFonts( css ) { var REGEXP_FONTS = /@font-face\s*?\{(.*?)\s*?\}/ig, match; while ( ( match = REGEXP_FONTS.exec( css ) ) != NULL ) { eCSStender.fonts.push( gatherProperties( match[1] ) ); } return css.replace( REGEXP_FONTS, EMPTY ); } function extractPages( css ) { // TODO: make pages media-aware var PAGES = 'pages', AT = '@', match, page, box, content, group, props, prop, /* Regex must match all of the following @page { ... } @page :left { ... } @page :right { ... } @page LandscapeTable { ... } @page CompanyLetterHead:first { ... } @page:first { ... } Plus margin boxes inside: @top-left-corner, @top-left, @top-center, @top-right, @top-right-corner, @bottom-left-corner, @bottom-left, @bottom-center, @bottom-right, @bottom-right-corner, @left-top, @left-middle, @left-bottom, @right-top, @right-middle, @right-bottom For example @page{margin:0;@top-left{content:title}padding:2px;} */ REGEXP_PAGES = /@page\s*?([\w:]*){0,1}\{\s*?((?:@[\w-]+\{[^\}]*\}|[\w-]+:[^;]+;)*)\s*?\}/ig; while ( ( match = REGEXP_PAGES.exec( css ) ) != NULL ) { page = match[1]; if ( ! defined( page ) || page == EMPTY ) { page = ALL; } else if ( page.indexOf(COLON) == 0 ) { page = page.replace( COLON, EMPTY ); } content = match[2]; if ( ! defined( eCSStender[PAGES][page] ) ) { eCSStender[PAGES][page] = {}; } // Margin boxes while ( ( box = REGEXP_ATRULE.exec( content ) ) != NULL ) { group = box[1]; props = gatherProperties( box[3] ); if ( ! defined( eCSStender[PAGES][page][AT] ) ) { eCSStender[PAGES][page][AT] = {}; } if ( ! defined( eCSStender[PAGES][page][AT][group] ) ) { eCSStender[PAGES][page][AT][group] = props; } else { for ( prop in props ) { if ( ! isInheritedProperty( props, prop ) ) { eCSStender[PAGES][page][AT][group][prop] = props[prop]; } } } content = content.replace( box[0], EMPTY ); } // properties props = gatherProperties( content ); for ( prop in props ) { if ( ! isInheritedProperty( props, prop ) ) { eCSStender[PAGES][page][prop] = props[prop]; } } } return css.replace( REGEXP_PAGES, EMPTY ); } function handleMediaGroups( css ) { // TODO: @media can contain @page, not just declaration blocks. var REGEXP_MEDIA = /@media\s*(.*?)\s*\{(.*?})\}/ig, match, media, m, mLen, styles, id = 0; while ( ( match = REGEXP_MEDIA.exec( css ) ) != NULL ) { css = collapseAtMedia( css, match, id ); id++; } return css; } function extractOtherAtRules( css ) { var match, group, keys, k, props, prop; while ( ( match = REGEXP_ATRULE.exec( css ) ) != NULL ) { group = match[1]; keys = trim( match[2] ); keys = ( keys == EMPTY ) ? FALSE : keys.split( REGEXP_COMMA ); props = gatherProperties( match[3] ); if ( ! defined( eCSStender.at[group] ) ) { eCSStender.at[group] = ! keys ? addPush( [] ) : {}; } if ( ! keys ) { eCSStender.at[group].push( props ); } else { k = keys.length; while ( k-- ) { if ( ! defined( eCSStender.at[group][keys[k]] ) ) { eCSStender.at[group][keys[k]] = props; } else { for ( prop in props ) { if ( ! isInheritedProperty( props, prop ) ) { eCSStender.at[group][keys[k]][prop] = props[prop]; } } } } } } return css.replace( REGEXP_ATRULE, EMPTY ); } function collapseAtMedia( css, match, id ) { var media = match[1].split(REGEXP_COMMA), styles = match[2]; createMediaContainers( media ); __media_groups[id] = { media: media, styles: styles }; return css.replace( match[0], PH_ATMEDIA + '{id:' + id + CLOSE_CURLY ); } function expandAtMedia( id ) { var media_group = __media_groups[id]; extractStyleBlocks( media_group.media, media_group.styles ); __media_groups[id] = NULL; } function extractStyleBlocks( media, css, delayed_id ) { media = arrayify(media); // parse it into blocks & remove the last item (which is empty) var blocks = css.split(CLOSE_CURLY), b=0, m=0, a=0, bLen, mLen = media.length, props, prop, selector, medium, arr, aLen; blocks.pop(); // loop for ( bLen=blocks.length; b|\+)/g; return ( ( is( test, REGEXP ) && selector.match( test ) != NULL ) || ( is( test, FUNCTION ) && test.call( selector ) === TRUE ) || ( is( test, STRING ) && selector.indexOf( trim( test.replace( useless, EMPTY ) ) ) != -1 ) ); } function filtersMatched( properties, filters ) { var count, required_count, prop, filter; for ( prop in properties ) { if ( ! isInheritedProperty( properties, prop ) && ! in_object( prop, __ignored_props ) ) { count = required_count = 0; for ( filter in filters ) { if ( ! isInheritedProperty( filters, filter ) ) { required_count++; if ( filter == PROPERTY ) { if ( prop.match( filters[filter] ) ){ count++; } } else if ( filter == 'value' ) { if ( properties[prop].match( filters[filter] ) ){ count++; } } } } } if ( count == required_count ){ return TRUE; } } return FALSE; } function clean( css ) { var // IE returns all uppercase tags and property names (except on XHR) // low_function = low, // lowercase = /(?:\s?([^.#:]+).*?[,{]|\s?([^:]+):)/ig, html_comments = /\s*(?:\<\!--|--\>)\s*/g, // strip HTML comments css_comments = /\/\*(?:.|\s)*?\*\//g, // strip CSS comments // remove returns and indenting whitespace (not not spaces before pseudo-element & pseudo-class selectors) whitespace = /\s*([,{};]|:(?!nth|first|last|only|empty|checked|(dis|en)abled))\s*/g, at_imports = /@import.*?;/g; // axe imports return css.replace( html_comments, EMPTY ) .replace( css_comments, EMPTY ) .replace( whitespace, CAPTURE ) .replace( at_imports, EMPTY ); // removed with removal of IE's cssText // .replace( lowercase, low_function ); } function in_object( needle, haystack ) { for ( var key in haystack ) { if ( haystack[key] == needle ){ return TRUE; } } return FALSE; } function now() { return new Date().getTime(); } function is( obj, test ) { var r = FALSE; try { r = obj instanceof test; } catch ( e ) { r = ( typeof( test ) == STRING && typeof( obj ) == test ); } return r; } function is_false( a ) { return a === FALSE; } function defined( a ) { return a != UNDEFINED; } function charAt( str ) { return String.fromCharCode( str ); } function getCSSFiles() { if ( __xhr ) { getCSSFiles = function() { var stylesheet, file, css, status; if ( stylesheet = __stylesheets[__s] ) { if ( file = stylesheet.actual_path ) { if ( file === NULL || in_object( file.replace( REGEXP_FILE, CAPTURE ), __ignored_css ) ) { __s++; getCSSFiles(); } else { __xhr = new XHR(); __xhr.onreadystatechange = xhrHandler; __xhr.open( 'GET', file, FALSE ); __xhr.send( NULL ); try { if ( __xhr.onreadystatechange != xhrHandler ) { __xhr.onreadystatechange = xhrHandler(); } } catch ( e ) { } } } else { readCSS( extract( stylesheet ), stylesheet.actual_media ); __s++; getCSSFiles(); } } else { wrapUp(); } }; getCSSFiles(); } else { for ( var i=0, iLen=__stylesheets; i= 200 && status < 300 ) || // good status == 304 ) // cached { readCSS( __xhr.responseText, __stylesheets[__s].actual_media ); __modified[fingerprint( __stylesheets[__s].actual_path )] = __xhr.getResponseHeader('Last-Modified'); } __s++; getCSSFiles(); } } function readCSS( css, media ) { css = clean( css ); css = extractAtBlocks( css ); // handle remaining rules extractStyleBlocks( media, css ); } function extract( stylesheet ) { var r; try { r = stylesheet.ownerNode.innerHTML; extract = function( stylesheet ) { return stylesheet.ownerNode.innerHTML; }; } catch ( e ) { r = stylesheet.owningElement.innerHTML; extract = function( stylesheet ) { return stylesheet.owningElement.innerHTML; }; } return r; } function low( w ) { return is( w, STRING ) ? w.toLowerCase() : w; } function camelize( str ) { var bits = str.split(HYPHEN), len = bits.length, new_str, i = 1; if ( len == 1 ) { return bits[0]; } if ( str.charAt(0) == HYPHEN ) { new_str = bits[0].charAt(0).toUpperCase() + bits[0].substring(1); } else { new_str = bits[0]; } while ( i < len ) { new_str += bits[i].charAt(0).toUpperCase() + bits[i].substring(1); i++; } return new_str; } function zero_out( str ) { if ( is( str, STRING ) ) { str = str.replace( /(\s0)px/g, CAPTURE ); } return str; } function addInlineStyle( el, property, value ) { try { el.style[property] = value; el.style[camelize( property )] = value; } catch( e ){ return FALSE; } return TRUE; } function XHR() { var connection; try { connection = new XMLHttpRequest(); } catch( e ){ try { connection = new ActiveXObject('Msxml2.XMLHTTP'); } catch( e ){ try { connection = new ActiveXObject('Microsoft.XMLHTTP'); } catch( e ){ connection = FALSE; } } } return ( ! connection ) ? NULL : connection; } function newElement( el ) { return DOCUMENT.createElement( el ); } function newRegExp( rxp ) { return new RegExp( rxp ); } function makeClassRegExp( the_class ) { return newRegExp( '(\\s|^)' + the_class + '(\\s|$)' ); } // push handling function addPush( arr ) { return arr; } if ( Array.prototype.push == NULL ) { var push = function( obj ) { this[this.length] = obj; return this.length; }; eCSStender.fonts.push = __eCSStensions.push = __stylesheets.push = __embedded_css.push = __on_complete.push = push; addPush = function( arr ) { if ( typeof( arr ) == ARRAY ) { arr.push = push; } return arr; }; } // expando handling if ( defined( __expando ) ) { toggleExpando = function() { if ( ! defined( DOCUMENT.old_expando ) || DOCUMENT.old_expando == NULL ) { DOCUMENT.old_expando = DOCUMENT.expando; DOCUMENT.expando = FALSE; } else { DOCUMENT.expando = DOCUMENT.old_expando; DOCUMENT.old_expando = NULL; } }; } /*-------------------------------------* * Delayed Writing *-------------------------------------*/ function writeStyleSheets() { var id, style, styles, selector; for ( id in __delayed ) { if ( ! isInheritedProperty( __delayed, id ) ) { style = DOCUMENT.getElementById( id ); styles = ''; for ( selector in __delayed[id] ) { if ( ! isInheritedProperty( __delayed, id ) ) { styles += selector + OPEN_CURLY + styleObjToString( __delayed[id][selector], selector ) + CLOSE_CURLY; } } addRules( style, styles ); } } } __on_complete.push( writeStyleSheets ); /*-------------------------------------* * Caching *-------------------------------------*/ function enableCache() { // HTML5 and/or Mozilla if ( defined( WINDOW.localStorage ) ) { __cache_object = WINDOW.localStorage; clearBrowserCache = function() { var i = __cache_object.length, key; // cherry-pick only our own items in the cache while ( i-- ) { key = __cache_object.key(i); if ( key && key.indexOf( ECSSTENDER ) === 0 ) { delete( __cache_object[key] ); } } }; readFromBrowserCache = function( cache, key ) { // make sure our cached objects are prefixed if ( cache != ECSSTENDER ) { cache = ECSSTENDER + HYPHEN + cache; } return __cache_object.getItem( cache + HYPHEN + key ); }; writeToBrowserCache = function( cache, key, value ) { // make sure our cached objects are prefixed if ( cache != ECSSTENDER ) { cache = ECSSTENDER + HYPHEN + cache; } __cache_object.setItem( cache + HYPHEN + key, value ); }; } // IE (old school) else { var div = newElement(DIV), tomorrow = new Date(); div.style.behavior = 'url(#default#userData)'; __body.appendChild(div); if ( defined( div.XMLDocument ) ) { __cache_object = div; __cache_object.load( ECSSTENDER ); // set the expiration for 1 week tomorrow.setMinutes( tomorrow.getMinutes() + 10080 ); tomorrow = tomorrow.toUTCString(); __cache_object.expires = tomorrow; clearBrowserCache = function() { var attr = __cache_object.XMLDocument.firstChild.attributes, i = attr.length; while ( i-- ) { __cache_object.removeAttribute( attr[i].nodeName ); } __cache_object.save( ECSSTENDER ); }; readFromBrowserCache = function( cache, key ) { return __cache_object.getAttribute( cache + HYPHEN + key ); }; writeToBrowserCache = function( cache, key, value ) { __cache_object.setAttribute( cache + HYPHEN + key, value ); __cache_object.save( ECSSTENDER ); }; } } } function readBrowserCache() { if ( __no_cache || __local ){ return; } enableCache(); var cache_group, item, count, version = 'version', cache_version = readFromBrowserCache( ECSSTENDER, version ); // only use the cache if it was created from the same version of eCSStender // this allows us to tweak the cache going forward if ( cache_version == eCSStender[version] ) { for ( cache_group in __local_cache ) { if ( ! isInheritedProperty( __local_cache, cache_group ) && defined( cache_group ) ) { count = readFromBrowserCache( ECSSTENDER, cache_group + COUNT ); if ( defined( count ) ) { if ( cache_group == EXTENSION ) { __t_count = count; if ( count < 1 ){ eCSStender.cache = FALSE; } } while ( count >= 0 ) { item = readFromBrowserCache( cache_group, count ); if ( item != NULL ) { if ( cache_group == EXTENSION ) { __local_cache[cache_group][EXTENSION+count] = item; } else { item = item.split(PIPES); if ( item[1] == 'true' ){ item[1] = TRUE; } if ( item[1] == 'false' ){ item[1] = FALSE; } __local_cache[cache_group][item[0]] = item[1]; } } count--; } } } } } clearBrowserCache(); } function writeBrowserCache() { if ( __no_cache || __local ){ return; } var cache_group, key, count, extension; for ( cache_group in __local_cache ) { if ( ! isInheritedProperty( __local_cache, cache_group ) && defined( cache_group ) ) { count = 0; for ( key in __local_cache[cache_group] ) { if ( ! isInheritedProperty( __local_cache[cache_group], key ) && defined( cache_group ) ) { if ( cache_group == EXTENSION ) { extension = __local_cache[cache_group][key]; extension[PROCESSED] = []; writeToBrowserCache( cache_group, count, extension ); } else { writeToBrowserCache( cache_group, count, key + PIPES + __local_cache[cache_group][key] ); } count++; } } writeToBrowserCache( ECSSTENDER, cache_group + COUNT, count ); } } writeToBrowserCache( ECSSTENDER, 'version', eCSStender.version ); __cached_out = TRUE; } function styleObjToString( obj ) { var str = EMPTY, key; for ( key in obj ) { if ( ! isInheritedProperty( obj, key ) ) { str += key + COLON + obj[key] + SEMICOLON; } } return str; } function stringToStyleObj( str ) { var matches, obj = FALSE; if ( ( matches = str.exec( /^\{(.*?)\}$/ ) ) != NULL ) { obj = gatherProperties( matches[1] ); } return obj; } function fingerprint( str ) { // UTF8 encode str = str.replace(/\r\n/g,'\n'); var keystr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', i, iLen = str.length, newstr = EMPTY, c1, c2, c3, e1, e2, e3, e4; for ( i=0; i 127) && (c1 < 2048) ) { newstr += charAt( (c1 >> 6) | 192 ); newstr += charAt( (c1 & 63) | 128 ); } else { newstr += charAt( (c1 >> 12) | 224 ); newstr += charAt( ((c1 >> 6) & 63) | 128 ); newstr += charAt( (c1 & 63) | 128 ); } } str = newstr; newstr = EMPTY; // base64 i = 0; iLen = str.length; while ( i < iLen ) { c1 = str.charCodeAt(i++); c2 = str.charCodeAt(i++); c3 = str.charCodeAt(i++); e1 = c1 >> 2; e2 = ((c1 & 3) << 4) | (c2 >> 4); e3 = ((c2 & 15) << 2) | (c3 >> 6); e4 = c3 & 63; if ( isNaN(c2) ) { e3 = e4 = 64; } else if ( isNaN(c3) ) { e4 = 64; } newstr += keystr.charAt(e1) + keystr.charAt(e2) + keystr.charAt(e3) + keystr.charAt(e4); } return newstr; } function writeToLocalCache( group, key, value, encode ) { encode = ! defined( encode ) ? TRUE : encode; if ( ! is_false( encode ) ){ key = fingerprint(key); } __local_cache[group][key] = value; if ( __cached_out ) { writeToBrowserCache( group, key, value ); } } function readFromLocalCache( group, key, encode ) { encode = ! defined( encode ) ? TRUE : encode; if ( ! is_false( encode ) ){ key = fingerprint(key); } var value = __local_cache[group][key]; return ! defined( value ) ? UNDEFINED : value; } /*-------------------------------------* * Public Methods * *-------------------------------------*/ /** * eCSStender::register() * registers an extension * * @param obj keys - define the lookup; supports the following properties: * * Mutually exclusive lookup methods: * * 'fragment' (str) - a portion of the property to look up * * 'prefix' (str) - to lookup by vendor-specific prefix * * 'property' (mixed) * * to lookup by property, use a string * * to lookup by multiple, use an array of strings * * 'selector' (mixed) - to find by selector; accepts * * compound selectors in a string, separated by commas * * a RegExp to match * * an array of selector strings or RegExp objects to match * * 'filter' (obj) - allows you to filter the lookup results by property name or value * * 'property' (mixed) - string or regex to match for the extension to apply * * 'value' (mixed) - string or regex to match for the extension to apply * * 'media' (str) - restricts the media to which you want to * restrict the extension * * 'test' (fn) - a test function to run that will determine * whether or not the extension should run; should return * TRUE if the extension SHOULD run, FALSE if it SHOULD NOT. * * 'fingerprint' (str) - a unique ID for the extension (not required) * @param mixed properties - the properties you want back; supports for: * * FALSE - returns only the looked-up property or properties with * the given fragment or prefix * * '*' (str) - returns all properties of the selected elements * * single property (str) - returns the supplied property * * multiple properties (arr) - returns the requested properties * @param fn callback - the function to be called when the extension * finds a subject */ eCSStender.register = function( keys, properties, callback ) { var eCSStension = {}, lookups, l, temp, t, props=[], key, id=EMPTY; // set the lookup type if ( defined( keys[SELECTOR] ) ) { eCSStension[FIND_BY] = SELECTOR; eCSStension[LOOKUP] = keys[SELECTOR]; } else if ( defined( keys[PROPERTY] ) ) { eCSStension[FIND_BY] = PROPERTY; eCSStension[LOOKUP] = keys[PROPERTY]; } else if ( defined( keys[FRAGMENT] ) ) { eCSStension[FIND_BY] = FRAGMENT; eCSStension[LOOKUP] = keys[FRAGMENT]; } else if ( defined( keys[PREFIX] ) ) { eCSStension[FIND_BY] = PREFIX; eCSStension[LOOKUP] = keys[PREFIX]; } // convert some lookups to arrays to simplify things later on if ( eCSStension[FIND_BY] == SELECTOR || eCSStension[FIND_BY] == PROPERTY ) { eCSStension[LOOKUP] = arrayify( eCSStension[LOOKUP] ); } // filter? if ( defined( keys[FILTER] ) ) { eCSStension[FILTER] = keys[FILTER]; } // media restriction? if ( defined( keys[MEDIA] ) ) { eCSStension[MEDIA] = keys[MEDIA]; } // test first? if ( defined( keys[TEST] ) ) { eCSStension[TEST] = keys[TEST]; } // set the properties to capture eCSStension[PROPERTIES] = determineProperties( keys, properties ); // create the fingerprint if ( ! defined( keys[FINGERPRINT] ) ) { for ( key in eCSStension ) { if ( ! isInheritedProperty( eCSStension, key ) ) { id += key + COLON + eCSStension[key].toString() + SEMICOLON; } } id = fingerprint(id+'::'+__e_count); } else { id = keys[FINGERPRINT]; } // set the callback eCSStension[CALLBACK] = callback; // create the processed array eCSStension[PROCESSED] = []; // save the extension __eCSStensions[id] = eCSStension; __e_count++; }; /** * eCSStender::lookup() * looks up and returns styles based on the supplied info * * @param obj lookup - define the lookup; supports the following properties: * * Mutually exclusive lookup methods: * * FRAGMENT (str) - a portion of the property to look up * * PREFIX (str) - to lookup by vendor-specific prefix * * PROPERTY (mixed) * * to lookup by property, use a string * * to lookup by multiple, use an array of strings * * SELECTOR (mixed) - to find by selector; accepts * * compound selectors in a string, separated by commas * * a RegExp to match * * an array of selector strings or RegExp objects to match * * a function that returns TRUE for a positive match * * MEDIA (str) - restricts the media within which you want to search * * SPECIFICITY (mixed) - the desired specificity parameters * * if an integer, implies maximum specificity * * if an object, use keys to set the thresholds: * * 'max' (int) - maximum specificity to match * * 'min' (int) - minimum specificity to match * @param mixed properties - the properties you want back; support for: * * FALSE - returns only the looked-up property or properties with * the given fragment or prefix * * '*' (str) - returns all properties of the selected elements * * single property (str) - returns the supplied property * * multiple properties (arr) - returns the requested properties * * @return arr of style objects, each with the following keys: * * 'medium' - the medium of the match * * PROPERTIES - the properties requested * * SELECTOR - the selector matched * * SPECIFICITY - the specificity of the selector */ eCSStender.lookup = function( lookup, properties ) { var l_specificity = lookup[SPECIFICITY], l_selector = lookup[SELECTOR], l_property = lookup[PROPERTY], l_fragment = lookup[FRAGMENT], l_prefix = lookup[PREFIX], l_media = lookup[MEDIA], props, property, min, max, medium, e_media, sLoop, styles, sorted_styles, s, sLen, selector, block, found, i, iLen, test, matches = []; // figure out specificity params if ( defined( l_specificity ) ) { if ( is( l_specificity, NUMBER ) ) { max = l_specificity; min = 0; } else if ( is( l_specificity, OBJECT ) ) { max = l_specificity['max']; min = l_specificity['min']; } } // make the selector setup consistent if ( defined( l_selector ) ) { l_selector = arrayify( l_selector ); } else if ( defined( l_property ) ) { l_property = arrayify( l_property ); } // figure out properties to return props = determineProperties( lookup, properties ); // loop for ( medium in __style_objects ) { // safety for users who are using Prototype or any code that extends Object if ( ! isInheritedProperty( __style_objects, medium ) ) { // verify any media restrictions if ( defined( l_media ) && l_media != ALL ) { e_media = l_media.split(REGEXP_COMMA); if ( medium != ALL && ! in_object( medium, e_media ) ) { continue; } } // start the processing in earnest styles = __style_objects[medium]; sorted_styles = getSortedArray( styles ); sLoop: for ( s=0, sLen=sorted_styles.length; s max ) { continue; } } // if you made it this far, you passed the tests matches.push({ medium: medium, properties: extractProperties( medium, selector, props ), selector: selector, specificity: block[SPECIFICITY] }); } // end styles loop } } // end medium loop // back what we found return matches; }; /** * eCSStender::addMethod() * sets a custom JavaScript method in eCSStender's methods object * * @param str name - a name for the method * @param func the_function - the function to store */ eCSStender.addMethod = function( name, the_function ) { if ( ! defined( eCSStender.methods[name] ) ) { eCSStender.methods[name] = the_function; } }; /** * eCSStender::onComplete() * adds a function to be called on completion of eCSStender's run * * @param func the_function - the function to store */ eCSStender.onComplete = function( the_function ) { __on_complete.push( the_function ); }; /** * eCSStender::embedCSS() * embeds styles to the appropriate media * * @param str styles - the styles to embed * @param str media - the media to apply the stylesheet to (optional) * @param bool delay - whether or not to delay the writing of the stylesheet (default = true) * * @return obj - the STYLE element */ eCSStender.embedCSS = function( styles, media, delay ) { // determine the medium media = media || ALL; // determine whether to delay the write or not delay = defined( delay ) ? delay : TRUE; // determine the id var id = 'eCSStension-' + media, style; // find or create the embedded stylesheet if ( ! in_object( media, __embedded_css ) ) { // make the new style element style = newStyleElement( media, id, delay ); // store the medium __embedded_css.push( media ); } else { style = DOCUMENT.getElementById( id ); } // add the rules to the sheet if ( style != NULL ) { if ( ! delay ) { addRules( style, styles ); } else { extractStyleBlocks( media, styles, id ); } } // return the style element return style; }; /** * eCSStender::newStyleElement() * adds a new stylesheet to the document * * @param str media - the media to apply the stylesheet to * @param str id - the id to give the stylesheet (optional) * @param bool delay - whether or not to delay the writing of the stylesheet (default = true) * * @return obj - the STYLE element */ function newStyleElement( media, id, delay ) { // clone the model style element var style = __style.cloneNode( TRUE ); // set the media type & id media = media || ALL; style.setAttribute( MEDIA, media ); id = id || 'temp-' + Math.round( Math.random() * 2 + 1 ); style.setAttribute( 'id', id ); // determine whether to delay the write or not delay = defined( delay ) ? delay : TRUE; if ( delay ) { __delayed[id] = {}; //style.disabled = TRUE; } __head.appendChild( style ); // return the element reference return style; } eCSStender.newStyleElement = newStyleElement; /** * eCSStender::addRules() * adds rules to a specific stylesheet * * @param obj el - the stylesheet * @param str styles - the style rules to add */ __style.setAttribute( TYPE, 'text/css' ); if ( defined( __style.styleSheet ) ) { addRules = function( el, styles ) { el.styleSheet.cssText += styles; }; } else { addRules = function( el, styles ) { el.appendChild( DOCUMENT.createTextNode( styles ) ); }; } eCSStender.addRules = addRules; /** * eCSStender::isSupported() * tests support for properties and selectors * * Option 1: Selector test * eCSStender::isSupported( type, selector, html, el ) * @param str type - 'property' * @param str selector - the selector * @param obj html - HTML to test against * @param obj el - the element the selector should select * @return bool - TRUE for success, FALSE for failure * * Option 2: Property Test (simple) * eCSStender::isSupported( type, test ) * @param str type - 'selector' * @param str test - the property: value pair to test * @return bool - TRUE for success, FALSE for failure * * Option 3: Property Test (complex) * eCSStender::isSupported( type, property, value ) * @param str type - 'selector' * @param str property - the property to test * @param mixed value - the string value or an array of possible values * @return bool - TRUE for success, FALSE for failure * * Option 3: Storage * eCSStender::isSupported( type, what, result ) * @param str type - 'selector' * @param str what - the key to store * @param bool result - the result of the test you want stored * @return bool - the result you passed in */ eCSStender.isSupported = function( type ) { var result, arg = arguments, aLen = arg.length, // global test vars what = arg[1], value = arg[2] || NULL, html = value, el = arg[3] || NULL, // property test vars property, settable = TRUE, i, computed = WINDOW.getComputedStyle, VISIBILITY = 'visibility', HIDDEN = 'hidden', // selector test vars style; // check for cached value if ( defined( result = readFromLocalCache( type, what ) ) ) { // we'll return the result at the end } // actual tests else { result = FALSE; // just caching the value for later if ( is( value, 'boolean' ) ) { result = value; } // property test else if ( type == PROPERTY ) { // test element el = newElement(DIV); // are property and value flowing in separately? if ( value ) { property = what; value = arrayify( value ); } else { what = what.split(REGEXP_P_V); property = what[0]; value = [ trim( what[1] ) ]; // reset what for the cache what = arg[1]; } __body.appendChild( el ); toggleExpando(); if ( ! addInlineStyle( el, property, value[0] ) ) { settable = FALSE; } toggleExpando(); if ( settable ) { i = value.length; while ( i-- && ! result ) { result = ( zero_out( getCSSValue( el, property ) ) == value[i] ); } } // cleanup __body.removeChild( el ); } // selector test else if ( type == SELECTOR ) { // append the test markup (if it exists) and the test style element if ( html ) { __body.appendChild( html ); } style = newStyleElement( SCREEN, FALSE, FALSE ); // if the browser doesn't support the selector, it should error out try { addRules( style, what + OPEN_CURLY + VISIBILITY + COLON + HIDDEN + SEMICOLON + CLOSE_CURLY ); // if it succeeds, we don't want to run the eCSStension if ( getCSSValue( el, VISIBILITY ) == HIDDEN ) { result = TRUE; } } catch( e ){} // cleanup if ( html ) { __body.removeChild( html ); } style.parentNode.removeChild( style ); } // write it to the cache and clean up writeToLocalCache( type, what, result ); value = html = el = style = NULL; } return result; }; /** * eCSStender::applyWeightedStyle() * apply a weighted inline style (based on specificity) * * @param obj el - the element to apply the property to * @param obj properties - a hash of the properties to assign * @param int specificity - the specificity of the selector */ eCSStender.applyWeightedStyle = function( el, properties, specificity ) { if ( ! defined( el.inlineStyles ) ) { el.inlineStyles = {}; } var prop, styles = el.inlineStyles; for ( prop in properties ) { if ( ! isInheritedProperty( properties, prop ) && ( ! defined( styles[prop] ) || styles[prop] <= specificity ) ) { addInlineStyle( el, prop, properties[prop] ); el.inlineStyles[prop] = specificity; } } }; /** * eCSStender::ignore() * tells eCSStendet to ignore a file or files * * @param mixed sheets - a string or array of stylesheet paths to ignore */ eCSStender.ignore = function( sheets ) { if ( is( sheets, STRING ) ) { sheets = [ sheets ]; } else if ( ! is( sheets, ARRAY ) ) { return; } for ( var i=0, iLen=sheets.length; i