Skip to content

Instantly share code, notes, and snippets.

@ydaniv
Created July 2, 2012 12:32
Show Gist options
  • Star 24 You must be signed in to star a gist
  • Fork 10 You must be signed in to fork a gist
  • Save ydaniv/3033012 to your computer and use it in GitHub Desktop.
Save ydaniv/3033012 to your computer and use it in GitHub Desktop.
A Gecko only polyfill for Webkit's window.getMatchedCSSRules
// polyfill window.getMatchedCSSRules() in FireFox 6+
if ( typeof window.getMatchedCSSRules !== 'function' ) {
var ELEMENT_RE = /[\w-]+/g,
ID_RE = /#[\w-]+/g,
CLASS_RE = /\.[\w-]+/g,
ATTR_RE = /\[[^\]]+\]/g,
// :not() pseudo-class does not add to specificity, but its content does as if it was outside it
PSEUDO_CLASSES_RE = /\:(?!not)[\w-]+(\(.*\))?/g,
PSEUDO_ELEMENTS_RE = /\:\:?(after|before|first-letter|first-line|selection)/g;
// convert an array-like object to array
function toArray (list) {
return [].slice.call(list);
}
// handles extraction of `cssRules` as an `Array` from a stylesheet or something that behaves the same
function getSheetRules (stylesheet) {
var sheet_media = stylesheet.media && stylesheet.media.mediaText;
// if this sheet is disabled skip it
if ( stylesheet.disabled ) return [];
// if this sheet's media is specified and doesn't match the viewport then skip it
if ( sheet_media && sheet_media.length && ! window.matchMedia(sheet_media).matches ) return [];
// get the style rules of this sheet
return toArray(stylesheet.cssRules);
}
function _find (string, re) {
var matches = string.match(re);
return re ? re.length : 0;
}
// calculates the specificity of a given `selector`
function calculateScore (selector) {
var score = [0,0,0],
parts = selector.split(' '),
part, match;
//TODO: clean the ':not' part since the last ELEMENT_RE will pick it up
while ( part = parts.shift(), typeof part == 'string' ) {
// find all pseudo-elements
match = _find(part, PSEUDO_ELEMENTS_RE);
score[2] = match;
// and remove them
match && (part = part.replace(PSEUDO_ELEMENTS_RE, ''));
// find all pseudo-classes
match = _find(part, PSEUDO_CLASSES_RE);
score[1] = match;
// and remove them
match && (part = part.replace(PSEUDO_CLASSES_RE, ''));
// find all attributes
match = _find(part, ATTR_RE);
score[1] += match;
// and remove them
match && (part = part.replace(ATTR_RE, ''));
// find all IDs
match = _find(part, ID_RE);
score[0] = match;
// and remove them
match && (part = part.replace(ID_RE, ''));
// find all classes
match = _find(part, CLASS_RE);
score[1] += match;
// and remove them
match && (part = part.replace(CLASS_RE, ''));
// find all elements
score[2] += _find(part, ELEMENT_RE);
}
return parseInt(score.join(''), 10);
}
// returns the heights possible specificity score an element can get from a give rule's selectorText
function getSpecificityScore (element, selector_text) {
var selectors = selector_text.split(','),
selector, score, result = 0;
while ( selector = selectors.shift() ) {
if ( element.mozMatchesSelector(selector) ) {
score = calculateScore(selector);
result = score > result ? score : result;
}
}
return result;
}
function sortBySpecificity (element, rules) {
// comparing function that sorts CSSStyleRules according to specificity of their `selectorText`
function compareSpecificity (a, b) {
return getSpecificityScore(element, b.selectorText) - getSpecificityScore(element, a.selectorText);
}
return rules.sort(compareSpecificity);
}
//TODO: not supporting 2nd argument for selecting pseudo elements
//TODO: not supporting 3rd argument for checking author style sheets only
window.getMatchedCSSRules = function (element /*, pseudo, author_only*/) {
var style_sheets, sheet, sheet_media,
rules, rule,
result = [];
// get stylesheets and convert to a regular Array
style_sheets = toArray(window.document.styleSheets);
// assuming the browser hands us stylesheets in order of appearance
// we iterate them from the beginning to follow proper cascade order
while ( sheet = style_sheets.shift() ) {
// get the style rules of this sheet
rules = getSheetRules(sheet);
// loop the rules in order of appearance
while ( rule = rules.shift() ) {
// if this is an @import rule
if ( rule.styleSheet ) {
// insert the imported stylesheet's rules at the beginning of this stylesheet's rules
rules = getSheetRules(rule.styleSheet).concat(rules);
// and skip this rule
continue;
}
// if there's no stylesheet attribute BUT there IS a media attribute it's a media rule
else if ( rule.media ) {
// insert the contained rules of this media rule to the beginning of this stylesheet's rules
rules = getSheetRules(rule).concat(rules);
// and skip it
continue
}
//TODO: for now only polyfilling Gecko
// check if this element matches this rule's selector
if ( element.mozMatchesSelector(rule.selectorText) ) {
// push the rule to the results set
result.push(rule);
}
}
}
// sort according to specificity
return sortBySpecificity(element, result);
};
}
@kiteroa
Copy link

kiteroa commented Oct 22, 2012

part.replace(ATTR_RE, '');

replace returns a new string: so needs to be 'part = part.replace(ATTR_RE, '');'
match (regExp) does not return no of matches on the regexp: need to use 'matches.length'

@ydaniv
Copy link
Author

ydaniv commented Nov 8, 2012

You're right! Fixed

Copy link

ghost commented Jul 11, 2013

You could have strings in the attributes like a[href="asd]"]. You see the square barackets inside the string? Your RE will match it. Also, I haven't tested it and I don't understand all that happens but it seems to me like your REs are greedy so a[atr1][atr2] will match from first [ to last ] and I don't know if this is desired.

@ragulka
Copy link

ragulka commented Aug 31, 2013

In my Firefox I get a security error: Security Error: the operation is insecure. This is triggered by https://gist.github.com/ydaniv/3033012/#file-mozgetmatchedcssrules-js-L23

@Tomas-M
Copy link

Tomas-M commented Aug 31, 2013

I've added this polyfill as an external .js file loaded before bootstrap-tokenfield.js gets loaded, and everything seems to be working fine in FireFox, no errors at all. (firefox version 23.0.1, newest as of today)

@Tomas-M
Copy link

Tomas-M commented Sep 4, 2013

Seems like Internet Explorer doesn't know element.mozMatchesSelector.

@marcelopm
Copy link

I'm getting the same a security error as ragulka

@ydaniv
Copy link
Author

ydaniv commented Apr 2, 2014

Wow, I haven't been around here for quite some time! Too bad Github doesn't send notifications for Gists.

@bobef you're probably right, I'm open to suggestions.

@ragulka I never got it, can you confirm if this is still happening and what OS and version?

@Tomas-M well obviously, moz* stands for Mozilla, also see the title:

Gecko only polyfill ...

@MichaelLawton
Copy link

I'm getting the security error using Firefox 28.0 on Windows 8.1. I think it may be related to these questions:
http://stackoverflow.com/questions/5323604/firefox-not-able-to-enumerate-document-stylesheets-cssrules
http://stackoverflow.com/questions/3211536/accessing-cross-domain-stylesheet-with-cssrules

None of my stylesheets are from another domain though.

@ydaniv
Copy link
Author

ydaniv commented Apr 6, 2014

@flangefrog can you verify there's not @import from a different domain in your stylesheets?

@numtel
Copy link

numtel commented Nov 5, 2014

This can work in IE9+ as well. Somebody already forked it and implemented it it looks like: https://gist.github.com/ssafejava/6605832

@ydaniv
Copy link
Author

ydaniv commented Nov 8, 2014

cool!
I guess the webkit part is redundant since this is a polyfill to a webkit feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment