Skip to content

Instantly share code, notes, and snippets.

@ydaniv
Created July 2, 2012 12:32
Show Gist options
  • Select an option

  • Save ydaniv/3033012 to your computer and use it in GitHub Desktop.

Select an option

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
Copy Markdown

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
Copy Markdown
Author

ydaniv commented Nov 8, 2012

You're right! Fixed

Copy link
Copy Markdown

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
Copy Markdown

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
Copy Markdown

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
Copy Markdown

Tomas-M commented Sep 4, 2013

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

@sk33wiff
Copy link
Copy Markdown

sk33wiff commented Oct 7, 2013

I'm getting the same a security error as ragulka

@ydaniv
Copy link
Copy Markdown
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
Copy Markdown

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
Copy Markdown
Author

ydaniv commented Apr 6, 2014

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

@numtel
Copy link
Copy Markdown

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
Copy Markdown
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