While trying to reduce the HTML size of a web page, I've come across suggestions by Google and the PageSpeed Firefox Add-On regading efficiency of CSS selectors that (almost) made me reconsider the changes:
http://code.google.com/intl/de-DE/speed/page-speed/docs/rendering.html#UseEfficientCSSSelectors
Specifically, descendant selectors are great for selecting a whole block (e.g. DIV) using an ID or CLASS attribute and then keeping all of its child elements free of CLASS/ID attributes. But if the order of traversal for applying the rules is as described by Google, they should not be used:
Descendant selectors are inefficient because, for each element that matches the key, the browser must also traverse up the DOM tree, evaluating every ancestor element until it finds a match or reaches the root element. The less specific the key, the greater the number of nodes that need to be evaluated.
I very much doubt that browsers use such an inefficient order of traversal, surely they would only process subtrees of elements that match the top selector component, i.e. in #foo span {...}
only elements below #foo should be checked and not every single span. Can anyone who has looked at recent browser code confirm/deny this?
The second questionable suggestion is about overly qualified selectors:
ID selectors are unique by definition. Including tag or class qualifiers just adds redundant information that needs to be evaluated needlessly.
If ID selectors are unique by definition, why would browsers need to check the redundant information? I know that they do because for example,
div#foo { color: black; } #foo { color: white; }
will result in black text in a <div id=foo>
, but a) it should not be done(? W3C reference needed) and b) I don't see why it would be noticeably slower when it results in a simple O(1) check of the element's tag name.
Can anyone on good terms with modern browsers' source code shed some light on these claims? Since most modern sites use descendant selectors (including SO) and they have clear advantages, I'd very much like to use them...
Edit:
I've experimented a bit with generated pages and it seems that the browsers' handling of descendant selectors is indeed pitiful:
A page consisting of (abbreviated):
#top a {text-decoration: none;}
#foo1 a.foo {color: red;}
#foo2 a.foo {color: red;}
[... repeated 10000 times]
<body id=top>
<div>...[nested 50 times]<a href=foo>bla</a></div>[...]
[previous line repeated 10000 times]
(basically 10000 lines with 50 nested divs each to traverse till the root node and 1 selector that matches out of 10000)
loads and renders (time till window.onload()
executes) in 2.2 seconds using Safari 5 and just under 10 seconds with Firefox 3.6.10.
When the .foo
class selector is removed from the non-applying rules, the page takes around 200 seconds with Safari 5 and 96 seconds with Firefox 3.6.10. This illustrates how badly the descendant selectors are implemented (in that case, each of the 10000 rules probably causes a traversal till #top, where the rule fails).
How do child selectors fare? #foo > span > div > div > div > div > div a {color: red;}
(also never matches but forces traversal of 6 parent nodes) takes 27 seconds with Safari 5 and 31 seconds with Firefox 3.6.10.
Conclusion
Descendant and child selectors both suck currently on major browsers. It's still better to add ugly class/id attributes to all your styled tags if you care about speed, at least for very common HTML tags (such as a, img, div etc.).