2504 lines
70 KiB
JavaScript
Executable File
2504 lines
70 KiB
JavaScript
Executable File
/*------------------------------------------------------------------------------
|
|
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<sLen; s++ )
|
|
{
|
|
// add the stylesheet
|
|
addStyleSheet( stylesheets[s] );
|
|
}
|
|
// cssText is truly unreliable, we need to rely on XHR
|
|
if ( sLen > 0 )
|
|
{
|
|
__xhr = TRUE;
|
|
}
|
|
}
|
|
function parseStyles()
|
|
{
|
|
var s=0, sLen=__stylesheets.length, media, m, mLen;
|
|
for ( ; s<sLen; s++ )
|
|
{
|
|
// determine the media type
|
|
media = determineMedia( __stylesheets[s] );
|
|
createMediaContainers( media );
|
|
determinePath( __stylesheets[s] );
|
|
}
|
|
getCSSFiles();
|
|
}
|
|
function validateCache()
|
|
{
|
|
if ( __no_cache || __local ){ return; }
|
|
// check the xhr headers against what we read from the cache
|
|
var key, cached = __local_cache.xhr, i=0, j=0;
|
|
eCSStender.cache = TRUE;
|
|
for ( key in __modified )
|
|
{
|
|
i++;
|
|
if ( ! isInheritedProperty( __modified, key ) &&
|
|
__modified[key] != NULL )
|
|
{
|
|
j++;
|
|
if ( ! defined( cached[key] ) ||
|
|
cached[key] != __modified[key] )
|
|
{
|
|
eCSStender.cache = FALSE;
|
|
}
|
|
// update the cache
|
|
__local_cache.xhr[key] = __modified[key];
|
|
}
|
|
}
|
|
if ( i>j || ( 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<sLen; s++ )
|
|
{
|
|
selector = sorted_styles[s][SELECTOR];
|
|
// loop the extensions
|
|
eLoop:
|
|
for ( e in __eCSStensions )
|
|
{
|
|
// safety for users who are using Prototype or any code that extends Object
|
|
if ( ! isInheritedProperty( __eCSStensions, e ) )
|
|
{
|
|
e_media = __eCSStensions[e][MEDIA];
|
|
// verify any media restrictions
|
|
if ( defined( e_media ) &&
|
|
e_media != ALL )
|
|
{
|
|
e_media = e_media.split(REGEXP_COMMA);
|
|
if ( medium != ALL &&
|
|
! in_object( medium, e_media ) ){
|
|
continue;
|
|
}
|
|
}
|
|
e_find_by = __eCSStensions[e][FIND_BY];
|
|
e_lookup = __eCSStensions[e][LOOKUP];
|
|
lLen = e_lookup.length;
|
|
// eCSStension is triggered by a selector
|
|
if ( e_find_by == SELECTOR )
|
|
{
|
|
for ( l=0; l<lLen; l++ )
|
|
{
|
|
if ( selectorMatches( selector, e_lookup[l] ) )
|
|
{
|
|
trackCallback( e, medium, selector );
|
|
continue eLoop;
|
|
}
|
|
}
|
|
}
|
|
// eCSStension uses a property
|
|
else if ( e_find_by == PROPERTY )
|
|
{
|
|
for ( l=0; l<lLen; l++ )
|
|
{
|
|
if ( defined( styles[selector][e_lookup[l]] ) )
|
|
{
|
|
trackCallback( e, medium, selector );
|
|
continue eLoop;
|
|
}
|
|
}
|
|
}
|
|
// eCSStension uses a fragment or prefix
|
|
else if ( e_find_by == FRAGMENT ||
|
|
e_find_by == PREFIX )
|
|
{
|
|
lookup = ( e_find_by == FRAGMENT ) ? ANYTHING + e_lookup + ANYTHING
|
|
: HYPHEN + e_lookup + HYPHEN_ANYTHING;
|
|
lookup = newRegExp( lookup );
|
|
for ( property in styles[selector] )
|
|
{
|
|
if ( ! isInheritedProperty( styles, selector ) && // fix for tainted Object
|
|
! in_object( property, __ignored_props ) &&
|
|
property.match( lookup ) )
|
|
{
|
|
trackCallback( e, medium, selector );
|
|
continue eLoop;
|
|
}
|
|
}
|
|
} // end if eCSStension uses a fragment or prefix
|
|
} // end extended object test
|
|
} // end eCSStensions loop
|
|
} // end styles loop
|
|
} // end extended object test
|
|
} // end medium loop
|
|
} // end if no cache
|
|
}
|
|
// log callbacks
|
|
function trackCallback( fingerprint, medium, selector )
|
|
{
|
|
var selector_id = medium + PIPES + selector;
|
|
writeToLocalCache( EXTENSION, EXTENSION + ( __t_count++ ), fingerprint + PIPES + selector_id, FALSE );
|
|
}
|
|
// callback functionality
|
|
function triggerCallbacks()
|
|
{
|
|
var s = 0, e, extension, style_rule, selector_id, properties, specificity, oLen, result;
|
|
for ( ; s<__t_count; s++ )
|
|
{
|
|
e = __local_cache[EXTENSION][EXTENSION+s].split(PIPES);
|
|
extension = __eCSStensions[e[0]];
|
|
if ( defined( __style_objects[e[1]] ) )
|
|
{
|
|
style_rule = __style_objects[e[1]][e[2]];
|
|
selector_id = e[1] + PIPES + e[2];
|
|
if ( ! defined( extension ) ||
|
|
! defined( style_rule ) ||
|
|
in_object( selector_id, extension[PROCESSED] ) ||
|
|
// apply any filters
|
|
( defined( extension[FILTER] ) &&
|
|
! filtersMatched( style_rule, extension[FILTER] ) ) ) { continue; }
|
|
specificity = ( ! eCSStender.cache ) ? style_rule[SPECIFICITY]
|
|
: getSpecificity( e[2] );
|
|
properties = extractProperties( e[1], e[2], extension[PROPERTIES] );
|
|
result = extension[CALLBACK]( e[2], properties, e[1], specificity );
|
|
// allow on-the-fly re-definition of callback
|
|
if ( is( result, FUNCTION ) )
|
|
{
|
|
__eCSStensions[e[0]][CALLBACK] = result;
|
|
}
|
|
__eCSStensions[e[0]][PROCESSED].push( selector_id );
|
|
}
|
|
}
|
|
}
|
|
function triggerOnCompletes()
|
|
{
|
|
var o = __on_complete.length;
|
|
while ( o-- )
|
|
{
|
|
__on_complete[o]();
|
|
}
|
|
}
|
|
|
|
/*------------------------------------*
|
|
* Private Utils *
|
|
*------------------------------------*/
|
|
function findImportedStylesheets( stylesheet )
|
|
{
|
|
// W3C
|
|
if ( ! defined( stylesheet.imports ) )
|
|
{
|
|
findImportedStylesheets = function( stylesheet ){
|
|
var
|
|
blocks = stylesheet.cssRules || stylesheet.rules,
|
|
i = 0, iLen;
|
|
// for a strange Chrome error
|
|
if ( blocks === NULL ){ return; }
|
|
for ( iLen = blocks.length; i<iLen; i++ )
|
|
{
|
|
// imports must come first, so when we don't find one, return
|
|
if ( blocks[i].type != 3 ){ return; }
|
|
|
|
// add the stylesheet
|
|
addStyleSheet( blocks[i].styleSheet );
|
|
}
|
|
// no need to XHR stylesheets that only import other stylesheets
|
|
if ( i === iLen &&
|
|
stylesheet.href )
|
|
{
|
|
__ignored_css.push( stylesheet.href.replace( REGEXP_FILE, CAPTURE ) );
|
|
}
|
|
};
|
|
}
|
|
// IE (old)
|
|
else
|
|
{
|
|
findImportedStylesheets = function( stylesheet ){
|
|
var imports = stylesheet.imports,
|
|
i = 0, iLen = imports.length;
|
|
for ( ; i<iLen; i++ )
|
|
{
|
|
// add the stylesheet
|
|
addStyleSheet( imports[i] );
|
|
}
|
|
};
|
|
}
|
|
findImportedStylesheets( stylesheet );
|
|
}
|
|
function addStyleSheet( stylesheet )
|
|
{
|
|
var href = stylesheet.href;
|
|
// skip if disabled
|
|
if ( stylesheet.disabled ||
|
|
( href &&
|
|
// or foreign
|
|
( determinePath( stylesheet ).indexOf( __location ) == -1 ||
|
|
// or ignored
|
|
in_object( href.replace( REGEXP_FILE, CAPTURE ), __ignored_css ) ) ) ){ return; }
|
|
// does it have imports?
|
|
findImportedStylesheets( stylesheet );
|
|
// push the current stylesheet to the collection
|
|
__stylesheets.push( stylesheet );
|
|
}
|
|
function getSortedArray( styles )
|
|
{
|
|
var arr = [], temp, selector;
|
|
for ( selector in styles )
|
|
{
|
|
// fix for tainated Object
|
|
if ( ! isInheritedProperty( styles, selector ) )
|
|
{
|
|
// continue
|
|
temp = styles[selector];
|
|
temp[SELECTOR] = selector;
|
|
temp[SPECIFICITY] = getSpecificity( selector );
|
|
arr.push( temp );
|
|
}
|
|
}
|
|
arr.sort( sortBySpecificity );
|
|
return arr;
|
|
}
|
|
function sortBySpecificity( a, b )
|
|
{
|
|
var x = a[SPECIFICITY], y = b[SPECIFICITY];
|
|
return ( ( x < y ) ? -1 : ( ( x > 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<bLen; b++ )
|
|
{
|
|
// separate the selector and the properties
|
|
blocks[b] = blocks[b].split(OPEN_CURLY);
|
|
// gather the properties
|
|
props = gatherProperties( blocks[b][1] );
|
|
// build the selectors (which are part of the master object)
|
|
selector = blocks[b][0];
|
|
|
|
// single selector
|
|
if ( selector.indexOf( PH_ATMEDIA ) != -1 )
|
|
{
|
|
expandAtMedia( props.id );
|
|
}
|
|
else
|
|
{
|
|
arr = selector.split(REGEXP_COMMA);
|
|
for ( a=0, aLen=arr.length; a<aLen; a++ )
|
|
{
|
|
selector = trim( arr[a] );
|
|
for ( m=0; m<mLen; m++ )
|
|
{
|
|
medium = media[m];
|
|
// normal run
|
|
if ( ! defined( delayed_id ) )
|
|
{
|
|
if ( ! defined( __style_objects[medium][selector] ) )
|
|
{
|
|
__style_objects[medium][selector] = {};
|
|
}
|
|
for ( prop in props )
|
|
{
|
|
if ( ! isInheritedProperty( props, prop ) )
|
|
{
|
|
__style_objects[medium][selector][prop] = props[prop];
|
|
}
|
|
}
|
|
}
|
|
// delayed run
|
|
else
|
|
{
|
|
if ( ! defined( __delayed[delayed_id][selector] ) )
|
|
{
|
|
__delayed[delayed_id][selector] = {};
|
|
}
|
|
for ( prop in props )
|
|
{
|
|
if ( ! isInheritedProperty( props, prop ) )
|
|
{
|
|
__delayed[delayed_id][selector][prop] = props[prop];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function gatherProperties( properties )
|
|
{
|
|
if ( ! is( properties, STRING ) ){ return {}; }
|
|
properties = properties.split(SEMICOLON);
|
|
var props = {},
|
|
p = 0, pLen = properties.length,
|
|
property, arr, prop, val;
|
|
for ( ; p<pLen; p++ )
|
|
{
|
|
property = trim( properties[p] );
|
|
// skip empties
|
|
if ( property == EMPTY ){ continue; }
|
|
arr = property.split(REGEXP_P_V);
|
|
prop = arr.shift();
|
|
val = arr.join(COLON);
|
|
props[trim(prop)] = trim( val );
|
|
}
|
|
return props;
|
|
}
|
|
function determineProperties( lookup, requested_properties )
|
|
{
|
|
var properties = [], property, fragment, prefix, i, iLen;
|
|
// properties is set
|
|
if ( ! is_false( requested_properties ) )
|
|
{
|
|
// user doesn't want everything
|
|
if ( requested_properties != STAR )
|
|
{
|
|
// gather requested properties
|
|
if ( is( requested_properties, STRING ) )
|
|
{
|
|
properties.push( requested_properties );
|
|
}
|
|
else if ( is( requested_properties, ARRAY ) )
|
|
{
|
|
for ( i=0, iLen=requested_properties.length; i<iLen; i++ )
|
|
{
|
|
properties.push( requested_properties[i] );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
properties = requested_properties;
|
|
}
|
|
}
|
|
// now for the remainder
|
|
if ( requested_properties != STAR )
|
|
{
|
|
// retrieve properties that were explicitly looked up
|
|
property = lookup[PROPERTY];
|
|
fragment = lookup[FRAGMENT];
|
|
prefix = lookup[PREFIX];
|
|
if ( defined( property ) )
|
|
{
|
|
if ( is( property, STRING ) )
|
|
{
|
|
properties.push( property );
|
|
}
|
|
else if ( is( property, ARRAY ) )
|
|
{
|
|
for ( i=0, iLen=property.length; i<iLen; i++ )
|
|
{
|
|
properties.push( property[i] );
|
|
}
|
|
}
|
|
}
|
|
// retrieve fragment matches
|
|
else if ( defined( fragment ) )
|
|
{
|
|
properties.push( newRegExp( ANYTHING + fragment + ANYTHING ) );
|
|
}
|
|
// retrieve prefix matches
|
|
else if ( defined( prefix ) )
|
|
{
|
|
properties.push( newRegExp( HYPHEN + prefix + HYPHEN_ANYTHING ) );
|
|
}
|
|
}
|
|
return properties;
|
|
}
|
|
function extractProperties( medium, selector, requested_properties )
|
|
{
|
|
var requested_property, property, properties = {}, p = 0, pLen,
|
|
style_rule = __style_objects[medium][selector];
|
|
// grab the requested properties
|
|
if ( is( requested_properties, ARRAY ) )
|
|
{
|
|
for ( pLen=requested_properties.length; p<pLen; p++ )
|
|
{
|
|
requested_property = requested_properties[p];
|
|
if ( is( requested_property, REGEXP ) )
|
|
{
|
|
for ( property in style_rule )
|
|
{
|
|
if ( ! isInheritedProperty( style_rule, property ) && // fix for tainted Object
|
|
! in_object( property, __ignored_props ) &&
|
|
property.match( requested_property ) != NULL )
|
|
{
|
|
properties[property] = style_rule[property];
|
|
}
|
|
}
|
|
}
|
|
else if ( is( requested_property, STRING ) &&
|
|
defined( style_rule[requested_property] ) )
|
|
{
|
|
properties[requested_property] = style_rule[requested_property];
|
|
}
|
|
}
|
|
}
|
|
// all properties (*)
|
|
else
|
|
{
|
|
for ( property in style_rule )
|
|
{
|
|
if ( ! isInheritedProperty( style_rule, property ) &&
|
|
! in_object( property, __ignored_props ) )
|
|
{
|
|
properties[property] = style_rule[property];
|
|
}
|
|
}
|
|
}
|
|
return properties;
|
|
}
|
|
function arrayify( something )
|
|
{
|
|
var arr=addPush([]), i=0, iLen, temp, t, tLen;
|
|
if ( ! is( something, ARRAY ) )
|
|
{
|
|
if ( is( something, OBJECT ) &&
|
|
! is( something, REGEXP ) )
|
|
{
|
|
for ( i in something )
|
|
{
|
|
if ( ! isInheritedProperty( something, i ) )
|
|
{
|
|
arr.push( something[i] );
|
|
}
|
|
}
|
|
}
|
|
else if ( is( something, STRING ) &&
|
|
something.indexOf(',') != -1 )
|
|
{
|
|
temp = something.split( REGEXP_COMMA );
|
|
for ( iLen=temp.length; i<iLen; i++ )
|
|
{
|
|
arr.push( temp[i] );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
arr = [ something ];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for ( iLen=something.length; i<iLen; i++ )
|
|
{
|
|
if ( is( something[i], STRING ) &&
|
|
something[i].indexOf(',') != -1 )
|
|
{
|
|
temp = something[i].split( REGEXP_COMMA );
|
|
for ( t=0, tLen=temp.length; t<tLen; t++ )
|
|
{
|
|
arr.push( temp[t] );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
arr.push( something[i] );
|
|
}
|
|
}
|
|
}
|
|
return arr;
|
|
}
|
|
function createMediaContainers( media )
|
|
{
|
|
if ( ! is( media, ARRAY ) )
|
|
{
|
|
media = ( media + EMPTY ).split( REGEXP_COMMA );
|
|
}
|
|
for ( var m=0, mLen=media.length; m<mLen; m++ )
|
|
{
|
|
if ( ! defined( __style_objects[media[m]] ) )
|
|
{
|
|
__style_objects[media[m]] = {};
|
|
}
|
|
}
|
|
}
|
|
function selectorMatches( selector, test )
|
|
{
|
|
var useless = /\*(?!\s|>|\+)/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<iLen; i++ )
|
|
{
|
|
if ( ! in_object( __stylesheets[i].actual_path.replace( REGEXP_FILE, CAPTURE ), __ignored_css ) )
|
|
{
|
|
readCSS( __stylesheets[i].cssText, __stylesheets[i].actual_media );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function xhrHandler( e )
|
|
{
|
|
if ( __xhr.readyState == 4 )
|
|
{
|
|
var status = __xhr.status;
|
|
if ( status == 0 || // local
|
|
( status >= 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<iLen; i++ )
|
|
{
|
|
c1 = str.charCodeAt(i);
|
|
if ( c1 < 128 )
|
|
{
|
|
newstr += charAt( c1 );
|
|
}
|
|
else if ( (c1 > 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<sLen; s++ )
|
|
{
|
|
// check the selector
|
|
selector = sorted_styles[s][SELECTOR];
|
|
block = styles[selector];
|
|
if ( defined( l_selector ) )
|
|
{
|
|
found = FALSE;
|
|
for ( i=0, iLen=l_selector.length; i<iLen; i++ )
|
|
{
|
|
if ( selectorMatches( selector, l_selector[i] ) )
|
|
{
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if ( is_false( found ) )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
// check properties
|
|
else if ( defined( l_property ) )
|
|
{
|
|
found = FALSE;
|
|
for ( i=0, iLen=l_property.length; i<iLen; i++ )
|
|
{
|
|
if ( defined( block[l_property[i]] ) )
|
|
{
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if ( is_false( found ) )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
// check fragments, and/or prefixes
|
|
else if ( defined( l_fragment ) ||
|
|
defined( l_prefix ) )
|
|
{
|
|
found = FALSE;
|
|
test = ( defined( l_fragment ) ) ? ANYTHING + l_fragment + ANYTHING
|
|
: HYPHEN + l_prefix + HYPHEN_ANYTHING;
|
|
test = newRegExp( test );
|
|
for ( property in block )
|
|
{
|
|
if ( ! isInheritedProperty( styles, selector ) && // fix for tainted Object
|
|
! in_object( property, __ignored_props ) &&
|
|
property.match( test ) )
|
|
{
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if ( is_false( found ) )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
// check the specificity
|
|
if ( defined( l_specificity ) )
|
|
{
|
|
if ( block[SPECIFICITY] < min ||
|
|
block[SPECIFICITY] > 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<iLen; i++ )
|
|
{
|
|
__ignored_css.push( sheets[i] );
|
|
}
|
|
};
|
|
|
|
/**
|
|
* eCSStender::disableCache()
|
|
* tells eCSStender not to cache or work from the cache
|
|
*/
|
|
eCSStender.disableCache = function()
|
|
{
|
|
__no_cache = TRUE;
|
|
};
|
|
|
|
/**
|
|
* eCSStender::trim()
|
|
* trims a string
|
|
*
|
|
* @param str str - the string to trim
|
|
*
|
|
* @return str - the trimmed string
|
|
*/
|
|
function trim( str )
|
|
{
|
|
if ( is( str, STRING ) )
|
|
{
|
|
return str.replace( /^\s+|\s+$/g, EMPTY );
|
|
}
|
|
return str;
|
|
}
|
|
eCSStender.trim = trim;
|
|
|
|
/**
|
|
* eCSStender::loadScript()
|
|
* dynamically loads a JavaScript file without loading the same one twice
|
|
*
|
|
* @param str src - the path to the JavaScript
|
|
* @param fn callback - optional callback to run when script is loaded
|
|
*/
|
|
__script.setAttribute( TYPE, 'text/javascript' );
|
|
eCSStender.loadScript = function( src, callback )
|
|
{
|
|
var
|
|
scripts = DOCUMENT.getElementsByTagName('script'),
|
|
i = scripts.length,
|
|
script = __script.cloneNode( TRUE ),
|
|
loaded = FALSE;
|
|
callback = callback || EMPTY_FN;
|
|
while ( i-- )
|
|
{
|
|
if ( scripts[i].src == src )
|
|
{
|
|
script = FALSE;
|
|
}
|
|
}
|
|
if ( script )
|
|
{
|
|
script.onload = script.onreadystatechange = function(){
|
|
if ( ! loaded &&
|
|
( ! defined( script.readyState ) ||
|
|
script.readyState == 'loaded' ||
|
|
script.readyState == COMPLETE ) )
|
|
{
|
|
loaded = TRUE;
|
|
script.onload = script.onreadystatechange = NULL;
|
|
callback();
|
|
}
|
|
};
|
|
script.setAttribute( 'src', src );
|
|
__head.appendChild( script );
|
|
}
|
|
else
|
|
{
|
|
setTimeout( callback, 100 );
|
|
}
|
|
};
|
|
|
|
/**
|
|
* eCSStender::isInheritedProperty()
|
|
* tests whether the given property is inherited
|
|
*
|
|
* @param obj obj - the object to test
|
|
* @param str prop - the property to look for
|
|
*
|
|
* @return bool - TRUE is property is inherited, FALSE is it isn't
|
|
*/
|
|
function isInheritedProperty( obj, prop )
|
|
{
|
|
if ( obj.hasOwnProperty )
|
|
{
|
|
isInheritedProperty = function( obj, prop )
|
|
{
|
|
return ! obj.hasOwnProperty(prop);
|
|
};
|
|
}
|
|
else
|
|
{
|
|
isInheritedProperty = function( obj, prop )
|
|
{
|
|
var c = obj.constructor;
|
|
if ( c &&
|
|
c.prototype )
|
|
{
|
|
return obj[prop] === c.prototype[prop];
|
|
}
|
|
return TRUE;
|
|
};
|
|
}
|
|
eCSStender.isInheritedProperty = isInheritedProperty;
|
|
return isInheritedProperty( obj, prop );
|
|
}
|
|
|
|
/**
|
|
* eCSStender::getCSSValue()
|
|
* gets the computed value of a CSS property
|
|
*
|
|
* @param obj el - the element
|
|
* @param str prop - the property name
|
|
*
|
|
* @return str - the value
|
|
*/
|
|
function getCSSValue( el, prop )
|
|
{
|
|
var computed = WINDOW.getComputedStyle;
|
|
if ( el.currentStyle )
|
|
{
|
|
getCSSValue = function( el, prop )
|
|
{
|
|
return el.currentStyle[camelize( prop )];
|
|
};
|
|
}
|
|
else if ( computed )
|
|
{
|
|
getCSSValue = function( el, prop )
|
|
{
|
|
return computed( el, NULL ).getPropertyValue( prop );
|
|
};
|
|
}
|
|
else
|
|
{
|
|
getCSSValue = function()
|
|
{
|
|
return FALSE;
|
|
};
|
|
}
|
|
eCSStender.getCSSValue = getCSSValue;
|
|
return getCSSValue( el, prop );
|
|
}
|
|
/**
|
|
* eCSStender::makeUniqueClass()
|
|
* creates a unique class for an element
|
|
*
|
|
* @return str - the unique class
|
|
*/
|
|
eCSStender.makeUniqueClass = function()
|
|
{
|
|
var start = new Date();
|
|
start = start.getTime();
|
|
function init()
|
|
{
|
|
return ECSSTENDER + HYPHEN + start++;
|
|
}
|
|
eCSStender.makeUniqueClass = init;
|
|
return init();
|
|
};
|
|
/**
|
|
* eCSStender::addClass()
|
|
* adds a class to an element
|
|
*
|
|
* @param obj el - the element to have its class augmented
|
|
* @param str the_class - the class to add
|
|
* @param RegExp re - a regular expression to match the class (optional)
|
|
*/
|
|
function addClass( el, the_class, re )
|
|
{
|
|
re = re || makeClassRegExp( the_class );
|
|
if ( ! hasClass( el, the_class, re ) )
|
|
{
|
|
el.className += SPACE + the_class;
|
|
}
|
|
}
|
|
eCSStender.addClass = addClass;
|
|
/**
|
|
* eCSStender::removeClass()
|
|
* removes a class from an element
|
|
*
|
|
* @param obj el - the element to have its class augmented
|
|
* @param str the_class - the class to add
|
|
* @param RegExp re - a regular expression to match the class (optional)
|
|
*/
|
|
function removeClass( el, the_class, re )
|
|
{
|
|
re = re || makeClassRegExp( the_class );
|
|
if ( hasClass( el, the_class, re ) )
|
|
{
|
|
el.className = trim( el.className.replace( re, SPACE ) );
|
|
}
|
|
};
|
|
eCSStender.removeClass = removeClass;
|
|
/**
|
|
* eCSStender::hasClass()
|
|
* checks to see if an element has the given class
|
|
*
|
|
* @param obj el - the element to have its class augmented
|
|
* @param str the_class - the class to add
|
|
* @param RegExp re - a regular expression to match the class (optional)
|
|
*/
|
|
function hasClass( el, the_class, re )
|
|
{
|
|
re = re || makeClassRegExp( the_class );
|
|
return el.className.match( re );
|
|
};
|
|
eCSStender.hasClass = hasClass;
|
|
/**
|
|
* eCSStender::toggleClass()
|
|
* adds or removes a class based on whether it's already there
|
|
*
|
|
* @param obj el - the element to have its class augmented
|
|
* @param str the_class - the class to add
|
|
*/
|
|
function toggleClass( el, the_class )
|
|
{
|
|
var re = makeClassRegExp( the_class );
|
|
if ( hasClass( el, the_class, re ) )
|
|
{
|
|
removeClass( el, the_class, re );
|
|
}
|
|
else
|
|
{
|
|
addClass( el, the_class, re );
|
|
}
|
|
};
|
|
eCSStender.toggleClass = toggleClass;
|
|
|
|
/*-------------------------------------*
|
|
* DOM Loaded Trigger *
|
|
* Based on jQuery's *
|
|
*-------------------------------------*/
|
|
(function(){
|
|
var
|
|
DCL = 'DOMContentLoaded',
|
|
ORC = 'onreadystatechange',
|
|
__old_onload = WINDOW.onload,
|
|
doScroll = DOCUMENT.documentElement.doScroll;
|
|
|
|
// for Mozilla/Safari/Opera9
|
|
if ( DOCUMENT.addEventListener )
|
|
{
|
|
DOCUMENT.addEventListener( DCL, function(){
|
|
DOCUMENT.removeEventListener( DCL, arguments.callee, FALSE );
|
|
initialize();
|
|
}, FALSE );
|
|
}
|
|
// If IE event model is used
|
|
else if ( DOCUMENT.attachEvent )
|
|
{
|
|
// ensure firing before onload, maybe late but safe also for iframes
|
|
DOCUMENT.attachEvent( ORC, function(){
|
|
if ( DOCUMENT.readyState === COMPLETE ) {
|
|
DOCUMENT.detachEvent( ORC, arguments.callee );
|
|
initialize();
|
|
}
|
|
});
|
|
|
|
// If IE and not an iframe, continually check to see if the document is ready
|
|
if ( doScroll &&
|
|
WINDOW == WINDOW.top )
|
|
{
|
|
(function(){
|
|
try {
|
|
// If IE is used, use the trick by Diego Perini
|
|
// http://javascript.nwbox.com/IEContentLoaded/
|
|
doScroll('left');
|
|
}
|
|
catch( error )
|
|
{
|
|
setTimeout( arguments.callee, 0 );
|
|
return;
|
|
}
|
|
// and execute any waiting functions
|
|
initialize();
|
|
})();
|
|
}
|
|
}
|
|
})();
|
|
|
|
})(); |