In which direction do selector engines read, exactly?

However I’ve read recently that most CSS selector engines read from right to left, in which case wouldn’t the first example actually be slower?

Which way to CSS selector engines read in general? Left to right or right to left? And if they generally read right to left could someone please offer me an explanation as to why (I can’t see how it makes sense to read right to left in terms of a selector engine)?

Frankly, it’s nigh impossible to tell which selector will be slower in a given browser, much less across browsers. Performance tends to fluctuate and be unpredictable, especially at such microscopic scales and with unpredictable document structures. Even if we talk about theoretical performance, it ultimately depends on the implementation.

Having said that, as shown in Boris Zbarsky’s answer to this other question and in Guffa’s answer to yours, a typical browser (this is currently true of all major layout engines) takes an element and evaluates all the candidate selectors to see which ones it matches, rather than finding a set of elements that match a given selector. This is a subtle but very important difference. Boris offers a technical explanation that’s not only incredibly detailed, but also authoritative (as he works on Gecko, the engine used by Firefox), so I highly suggest reading it.

But I thought I should address what seems to be another concern in your question:

As the selector engine would simply find every element with a class of name, and then have to identify which of those were divs?

As well as Patrick McElhaney’s comment:

The linked question explains why selectors are read right-to-left in general, so #foo ul.round.fancy li.current is read li.current, ul.round.fancy, #foo, but is it really read right-to-left within each element (.current, li, .fancy, .round, ul, #foo)? Should it be?

I have never implemented CSS, nor have I seen how other browsers implement it. We do know from the answers linked above that browsers use right-to-left matching to walk across combinators within selectors, such as the > combinators in this example:

section > div.second > div.third

If an element isn’t a div.third, then there is no point checking if its parent is a div.second whose parent is a section.

However, I don’t believe that this right-to-left order drills all the way down to the simple selector level. In other words, I don’t believe that browsers use right-to-left evaluation for each part of a simple selector sequence (also known as a compound selector) within the right-to-left evaluation across a series of compound selectors separated by combinators.

For example, consider this contrived and highly exaggerated selector:

div.name[data-foo="bar"]:nth-child(5):hover::after

Now, there’s no guarantee a browser will necessarily check these conditions for an element in the following order:

  1. Is the pointer over this element?
  2. Is this element the 5th child of its parent?
  3. Does this element have a data-foo attribute with the value bar?
  4. Does this element have a name class?
  5. Is this a div element?

Nor would this selector, which is functionally identical to the above except with its simple selectors jumbled around, necessarily be evaluated in the following order:

div:hover[data-foo="bar"].name:nth-child(5)::after
  1. Is this element the 5th child of its parent?
  2. Does this element have a name class?
  3. Does this element have a data-foo attribute with the value bar?
  4. Is the pointer over this element?
  5. Is this a div element?

There is simply no reason that such an order would be enforced for performance reasons. In fact, I’d think that performance would be enhanced by picking at certain kinds of simple selectors first, no matter where they are in a sequence. (You’ll also notice that the ::after is not accounted for — that’s because pseudo-elements are not simple selectors and never even enter into the matching equation.)

For example, it’s very well-known that ID selectors are the fastest. Well, Boris says this in the last paragraph of his answer to the linked question:

Note also that there are other optimizations browsers already do to avoid even trying to match rules that definitely won’t match. For example, if the rightmost selector has an id and that id doesn’t match the element’s id, then there will be no attempt to match that selector against that element at all in Gecko: the set of “selectors with IDs” that are attempted comes from a hashtable lookup on the element’s ID. So this is 70% of the rules which have a pretty good chance of matching that still don’t match after considering just the tag/class/id of the rightmost selector.

In other words, whether you have a selector that looks like this:

div#foo.bar:first-child

Or this:

div.bar:first-child#foo

Gecko will always check the ID and the class first, regardless of where it is positioned in the sequence. If the element doesn’t have an ID and a class that matches the selector then it’s instantly discarded. Pretty darn quick if you ask me.

That was just Gecko as an example. This may differ between implementations as well (e.g. Gecko and WebKit may do it differently from Trident or even Presto). There are strategies and approaches that are generally agreed upon by vendors, of course (there isn’t likely to be a difference in checking IDs first), but the little details may differ.

Leave a Comment