52

Is there a precedence to combinators like

a > b ~ c d

(Note the space between c and d is the descendant combinator)

Or is it just read left-to-right, like

((a > b) ~ c) d

?

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
mpen
  • 272,448
  • 266
  • 850
  • 1,236

3 Answers3

58

No, there is no notion of precedence in combinators. However, there is a notion of order of elements in a complex selector.

Any complex selector can be read in any direction that makes sense to you, but this does not imply that combinators are distributive or commutative, as they indicate a relationship between two elements, e.g. ancestor descendant and previous + next. This is why the order of elements is what matters.

According to Google, however, browsers implement their selector engines such that they evaluate complex selectors from right to left:

The engine [Gecko] evaluates each rule from right to left, starting from the rightmost selector (called the "key") and moving through each selector until it finds a match or discards the rule.

Mozilla's article, Writing Efficient CSS for use in the Mozilla UI has a section that describes how their CSS engine evaluates selectors. This is XUL-specific, but the same layout engine is used both for Firefox's UI and pages that display in Firefox's viewport. (dead link)

As described by Google in the above quote, the key selector simply refers to the right-most simple selector sequence, so again it's from right to left:

The style system matches rules by starting with the key selector, then moving to the left (looking for any ancestors in the rule’s selector). As long as the selector’s subtree continues to check out, the style system continues moving to the left until it either matches the rule, or abandons because of a mismatch.

Bear in mind two things:

  • These are documented based on implementation details; at heart, a selector is a selector, and all it is intended to do is to match an element that satisfies a certain condition (laid out by the components of the selector). In which direction it is read is up to the implementation; as pointed out by another answer, the spec does not say anything about what order to evaluate a selector in or about combinator precedence.

  • Neither article implies that each simple selector is evaluated from left to right within its simple selector sequence (see this answer for why I believe this isn't the case). What the articles are saying is that a browser engine will evaluate the key selector sequence to figure out if its working DOM element matches it, then if it does, progress onto the next selector sequence by following the combinator and check for any elements that match that sequence, then rinse and repeat until either completion or failure.


With all that said, if you were to ask me to read selectors and describe what they select in plain English, I would read them from right to left too (not that I'm certain whether this is relevant to implementation details though!).

So, the selector:

a > b ~ c d

would mean:

Select any d element
that is a descendant of a c element
that is a sibling of, and comes after, a b element
that is a child (direct descendant) of an a element.

devio
  • 36,858
  • 7
  • 80
  • 143
BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
  • 3
    Yea. Right-to-left because the last simple selector (d) is the one of which the result is a subset of. The other simple selectors (a, b, and c) are present just to narrow down the result. – Šime Vidas Oct 03 '10 at 22:10
  • But say I have two divs `#A` and `#B`, and `#A` has 100 spans inside, `#B` just one. Then evaluating `div#A span` from left to right would be much easier and probably faster then getting all spans first, or not? – poke Oct 03 '10 at 22:24
  • @poke: I agree it would be faster, but it appears that's not how it's evaluated by engines as we know them. At least it's faster than, say, checking every span to see if it's a descendant of every div... – BoltClock Oct 03 '10 at 22:33
  • 3
    If I remember correctly from a lecture on Youtube I once watched, ID selectors get special treatment. If the selector string contains ID selector(s), then there may be a divergence from the right-to-left rule. Also, browsers have references to all elements with ID's (so that they don't have to search for them for every selector they encounter). – Šime Vidas Oct 03 '10 at 22:52
  • @Šime: Ah, that's very interesting. +1 – BoltClock Oct 03 '10 at 22:55
  • 2
    Here, this video is related to this issue: http://www.youtube.com/watch?v=a2_6bGNZ7bA – Šime Vidas Oct 03 '10 at 22:55
  • Arghhh! I tried parsing right-to-left something like `body > span > a` but what I ended up doing was finding all `a` s and then filtering them to those that had a `span` parent, and then filtering those (the filtered `a` s) to the ones that had a `body` parent... doesn't exactly work :p Shouldn't this be left-to-right? You can safely drop off the `body` after you've filtered the `span` s, but you can't do it the other way around. – mpen Oct 06 '10 at 18:40
  • @Mark: I wonder if adding an illustration to my answer of how *I* parse it would help any :S – BoltClock Oct 06 '10 at 18:57
  • @BoltClock: Naw, it's okay. I figured it out. Silly me :) Recursion was the answer. – mpen Oct 06 '10 at 19:31
  • 1
    Right-to-left makes sense if you are trying to determine if the tag you just read in needs to have any CSS rules applied to it before showing it to the user. It's a pre-rendering trick that can be highly complex to calculate properly. This is why some of the portions of CSS4 such as :has() will throw a major monkey wrench into CSS3 engines. If you want to play around with a CSS3 parser, I've got one here: https://github.com/cubiclesoft/ultimate-web-scraper/blob/master/support/tag_filter.php TagFilter::ParseSelector() tokenizes CSS3 selectors into optimal order. – CubicleSoft Feb 12 '17 at 05:50
  • @CubicleSoft: Thanks for sharing! I've been meaning to look into selector parsing myself and writing my own implementation for education purposes. – BoltClock Feb 12 '17 at 08:11
  • You're really citing the wrong spec here, since this answer only really makes sense given the (currently unstable) Level 4 selectors spec. Firstly, you use terminology here ("complex selector") that doesn't exist in the Level 3 spec. Secondly, a key point that makes complex selectors' meaning well defined - that a combinator *"represents a particular kind of relationship between the compound selectors on either side"* - is missing from the Level 3 spec, theoretically leaving the choice of how to handle selectors like `a + b c` up to browser vendors. – Mark Amery May 22 '17 at 22:44
  • You try to capture the meaning conveyed by *"represents a particular kind of relationship between the compound selectors on either side"* wording by noting that Level 3 combinators express a relationship between *elements* on either side, but that's *not unambiguously the same thing*. It could be perfectly validly interpreted to mean that `a + b c` is equivalent to `a + (b c)`, if we view `a` and `b c` as being the elements on either side of the `+` combinator. As far as I can tell, this is a spec bug fixed in Level 4 and the behaviour of `a + b c` under the Level 3 spec is not well-defined. – Mark Amery May 22 '17 at 22:48
  • @Mark Amery: L3 doesn't define combinators to represent relationships between two compound selectors in either Syntax or Combinators, but every combinator that appears there is defined to represent a relationship between two sequences of simple selectors on either side. The L4 terms "complex selector" and "compound selector" are renames of "selector" and "sequence of simple selectors" from L3 respectively, so these terms have been well-defined since L3 (pseudo-elements notwithstanding). So no, the selector `a + b c` can never represent the same thing as `b a + c` (as implied by `a + (b c)`). – BoltClock May 23 '17 at 02:10
  • @Mark Amery: The only reason I favour L4 terminology is that it's much less ambiguous. The term "selector" is well-defined in L3, but is confusing as all get out unless the definition is included in context anyway, and the term "sequence of simple selectors" is just a mouthful. And while the rest of L4 is unstable, I'm not worried about the definitions of complex and compound selectors changing anytime soon (again, pseudo-elements notwithstanding). – BoltClock May 23 '17 at 02:14
  • @BoltClock *"every combinator that appears there is defined to represent a relationship between two sequences of simple selectors on either side"* - not as far as I can see. Their *syntax* is defined such that they must separate two sequences of simple selectors, but their *semantics* are never defined in terms of sequences of simple selectors except in one bit in *4. Selector Syntax* which is itself ambiguously worded. (Upon a reread I do notice that there are *examples* in L3 that demonstrate this behaviour, though, so I think you're basically right and L3 is effectively well-defined.) – Mark Amery May 23 '17 at 09:18
  • @Martin: I hadn't noticed your edit over that other one; rest assured my rollback note wasn't referring to you. Your edit was helpful. Kirill's edits on the other hand were a head-scratching waste of my time (and presumably yours too). – BoltClock Nov 09 '19 at 16:50
  • @BoltClock no worries, thank you for the comment. I agree the changes by the other user didn't actually improve anything (and adding "only" which I read as being inappropriate in this context) . – Martin Nov 09 '19 at 20:06
2

It doesn't matter.

a > b c 

will match the same elements regardless of whether you do it

(a > b) c

or

a > (b c)

I think that browsers go right-to-left.

Šime Vidas
  • 182,163
  • 62
  • 281
  • 385
-7

the spec doesn't mention anything about precedence (that I can find) but I believe it's strictly left -to- right evaluation

Scott Evernden
  • 39,136
  • 15
  • 78
  • 84