views:

528

answers:

2

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.

A: 

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
but I'm not parsing the rule in any way shape or form... I'm just storing their names and rules against them...
Robert Koritnik
+3  A: 

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
Great documentation of the problem and solution. +1
Greg
@Greg: I thought it might be useful to someone else as well so I documented it more thoroughly. Thanks for the upvote.
Robert Koritnik