|
| 1 | +// polyfill window.getMatchedCSSRules() in FireFox 6+ |
| 2 | +/* eslint-disable */ |
| 3 | +if ( typeof window.getMatchedCSSRules !== 'function' ) { |
| 4 | + var ELEMENT_RE = /[\w-]+/g, |
| 5 | + ID_RE = /#[\w-]+/g, |
| 6 | + CLASS_RE = /\.[\w-]+/g, |
| 7 | + ATTR_RE = /\[[^\]]+\]/g, |
| 8 | + // :not() pseudo-class does not add to specificity, but its content does as if it was outside it |
| 9 | + PSEUDO_CLASSES_RE = /\:(?!not)[\w-]+(\(.*\))?/g, |
| 10 | + PSEUDO_ELEMENTS_RE = /\:\:?(after|before|first-letter|first-line|selection)/g; |
| 11 | + // convert an array-like object to array |
| 12 | + function toArray (list) { |
| 13 | + return [].slice.call(list); |
| 14 | + } |
| 15 | + |
| 16 | + // handles extraction of `cssRules` as an `Array` from a stylesheet or something that behaves the same |
| 17 | + function getSheetRules (stylesheet) { |
| 18 | + var sheet_media = stylesheet.media && stylesheet.media.mediaText; |
| 19 | + // if this sheet is disabled skip it |
| 20 | + if ( stylesheet.disabled ) return []; |
| 21 | + // if this sheet's media is specified and doesn't match the viewport then skip it |
| 22 | + if ( sheet_media && sheet_media.length && ! window.matchMedia(sheet_media).matches ) return []; |
| 23 | + // get the style rules of this sheet |
| 24 | + return toArray(stylesheet.cssRules); |
| 25 | + } |
| 26 | + |
| 27 | + function _find (string, re) { |
| 28 | + var matches = string.match(re); |
| 29 | + return re ? re.length : 0; |
| 30 | + } |
| 31 | + |
| 32 | + // calculates the specificity of a given `selector` |
| 33 | + function calculateScore (selector) { |
| 34 | + var score = [0,0,0], |
| 35 | + parts = selector.split(' '), |
| 36 | + part, match; |
| 37 | + //TODO: clean the ':not' part since the last ELEMENT_RE will pick it up |
| 38 | + while ( part = parts.shift(), typeof part == 'string' ) { |
| 39 | + // find all pseudo-elements |
| 40 | + match = _find(part, PSEUDO_ELEMENTS_RE); |
| 41 | + score[2] = match; |
| 42 | + // and remove them |
| 43 | + match && (part = part.replace(PSEUDO_ELEMENTS_RE, '')); |
| 44 | + // find all pseudo-classes |
| 45 | + match = _find(part, PSEUDO_CLASSES_RE); |
| 46 | + score[1] = match; |
| 47 | + // and remove them |
| 48 | + match && (part = part.replace(PSEUDO_CLASSES_RE, '')); |
| 49 | + // find all attributes |
| 50 | + match = _find(part, ATTR_RE); |
| 51 | + score[1] += match; |
| 52 | + // and remove them |
| 53 | + match && (part = part.replace(ATTR_RE, '')); |
| 54 | + // find all IDs |
| 55 | + match = _find(part, ID_RE); |
| 56 | + score[0] = match; |
| 57 | + // and remove them |
| 58 | + match && (part = part.replace(ID_RE, '')); |
| 59 | + // find all classes |
| 60 | + match = _find(part, CLASS_RE); |
| 61 | + score[1] += match; |
| 62 | + // and remove them |
| 63 | + match && (part = part.replace(CLASS_RE, '')); |
| 64 | + // find all elements |
| 65 | + score[2] += _find(part, ELEMENT_RE); |
| 66 | + } |
| 67 | + return parseInt(score.join(''), 10); |
| 68 | + } |
| 69 | + |
| 70 | + // returns the heights possible specificity score an element can get from a give rule's selectorText |
| 71 | + function getSpecificityScore (element, selector_text) { |
| 72 | + var selectors = selector_text.split(','), |
| 73 | + selector, score, result = 0; |
| 74 | + while ( selector = selectors.shift() ) { |
| 75 | + if ( element.mozMatchesSelector(selector) ) { |
| 76 | + score = calculateScore(selector); |
| 77 | + result = score > result ? score : result; |
| 78 | + } |
| 79 | + } |
| 80 | + return result; |
| 81 | + } |
| 82 | + |
| 83 | + function sortBySpecificity (element, rules) { |
| 84 | + // comparing function that sorts CSSStyleRules according to specificity of their `selectorText` |
| 85 | + function compareSpecificity (a, b) { |
| 86 | + return getSpecificityScore(element, b.selectorText) - getSpecificityScore(element, a.selectorText); |
| 87 | + } |
| 88 | + |
| 89 | + return rules.sort(compareSpecificity); |
| 90 | + } |
| 91 | + |
| 92 | + //TODO: not supporting 2nd argument for selecting pseudo elements |
| 93 | + //TODO: not supporting 3rd argument for checking author style sheets only |
| 94 | + window.getMatchedCSSRules = function (element /*, pseudo, author_only*/) { |
| 95 | + var style_sheets, sheet, sheet_media, |
| 96 | + rules, rule, |
| 97 | + result = []; |
| 98 | + // get stylesheets and convert to a regular Array |
| 99 | + style_sheets = toArray(window.document.styleSheets); |
| 100 | + |
| 101 | + // assuming the browser hands us stylesheets in order of appearance |
| 102 | + // we iterate them from the beginning to follow proper cascade order |
| 103 | + while ( sheet = style_sheets.shift() ) { |
| 104 | + // get the style rules of this sheet |
| 105 | + rules = getSheetRules(sheet); |
| 106 | + // loop the rules in order of appearance |
| 107 | + while ( rule = rules.shift() ) { |
| 108 | + // if this is an @import rule |
| 109 | + if ( rule.styleSheet ) { |
| 110 | + // insert the imported stylesheet's rules at the beginning of this stylesheet's rules |
| 111 | + rules = getSheetRules(rule.styleSheet).concat(rules); |
| 112 | + // and skip this rule |
| 113 | + continue; |
| 114 | + } |
| 115 | + // if there's no stylesheet attribute BUT there IS a media attribute it's a media rule |
| 116 | + else if ( rule.media ) { |
| 117 | + // insert the contained rules of this media rule to the beginning of this stylesheet's rules |
| 118 | + rules = getSheetRules(rule).concat(rules); |
| 119 | + // and skip it |
| 120 | + continue |
| 121 | + } |
| 122 | + //TODO: for now only polyfilling Gecko |
| 123 | + // check if this element matches this rule's selector |
| 124 | + if ( element.mozMatchesSelector(rule.selectorText) ) { |
| 125 | + // push the rule to the results set |
| 126 | + result.push(rule); |
| 127 | + } |
| 128 | + } |
| 129 | + } |
| 130 | + // sort according to specificity |
| 131 | + return sortBySpecificity(element, result); |
| 132 | + }; |
| 133 | +} |
0 commit comments