3

I've written this code that iterates over all global style sheet rules and stores them in an array/object. I use this dictionary-like object later to change global rules rather than setting styles on individual elements.

Following code breaks in IE8 but works fine in Firefox3.7 and Chrome4.

var allRules;

$(function() {
    var fileRules;
    allRules = [];
    $.each(document.styleSheets, function() {
        // get rules for any browser (IE uses rules array)
        fileRules = this.cssRules || this.rules;
        $.each(fileRules, function() {
            allRules[this.selectorText] = this;
        });
    });
});

I get Invalid procedure call or argument error. When I try to debug it, this code sucessfully iterates through two CSS style sheet files with rules but when the second one's iteration is done, it fails.

I can't seem to find an error in this code.

Nick Craver
  • 623,446
  • 136
  • 1,297
  • 1,155
Robert Koritnik
  • 103,639
  • 52
  • 277
  • 404

3 Answers3

13

The problem

After thorough testing I found out that document.styleSheets isn't a regular array in IE. That's why it breaks in $.each() call when it reaches the end.

If we take a look at jQuery function itself it has a for loop to iterate over an object that has a length property, falsely believing it's an array. document.styleSheets does have length property, but it's obviously not an array. So when this for loop in $.each() is executed:

for (var value = object[0];
     i < length && callback.call( value, i, value ) !== false;
     value = object[++i]){}

it fails after the last element has been iterated over. As we may see this for loop doesn't increment i on its own but rather increments it while assigning a new value to value.

We can check this manually as well. Write these two lines in any browser's address bar:

javascript:var a=[1,2,3];alert(a[3]);void(0);
javascript:alert(document.styleSheets[document.styleSheets.length]);void(0);

The first one runs fine in all browsers, but the second one fails in IE.

The solution

We have to rewrite the iteration over style sheets

var allRules;

$(function() {
    var fileRules;
    allRules = {};
    // can't use $.each() over document.styleSheets because it's not an array in IE
    for (var i = 0; i < document.styleSheets.length; i++)
    {
        fileRules = document.styleSheets[i].cssRules || document.styleSheets[i].rules;
        $.each(fileRules, function() {
            allRules[this.selectorText] = this;
        });
    }
});
Robert Koritnik
  • 103,639
  • 52
  • 277
  • 404
0

Could it be that parsing rule itself is failing? Try experimenting with different stylesheets and reorder the rules to ensure that there isn't a problem parsing the rule for some reason.

Dave Swersky
  • 34,502
  • 9
  • 78
  • 118
-2

code true is:

var fileRules;
(function ($) {
    allRules = {};
    for (var i = 0; i < document.styleSheets.length; i++) {
        fileRules = document.styleSheets[i].cssRules || document.styleSheets[i].rules;
        $.each(fileRules, function () {
            allRules[this.selectorText] = this;
        })(jQuery);
    }
});
Robert Koritnik
  • 103,639
  • 52
  • 277
  • 404
popo
  • 1
  • 2
    This code surely doesn't work... Sorry. You're adding `(jQuery)` to `$.each`... Why did you add it in the first place? You probably wanted to make the main function immediately executable. But even though... Then this code should run the last in the document. Using solution's approach by running this on document ready is much more transparent. Don't you think so? – Robert Koritnik Jun 28 '11 at 09:02
  • 1
    Not to mention the fact that you've made `allRules` a closure variable, so you won't be able to access those rules you've read into... – Robert Koritnik Jun 28 '11 at 09:03