Re: [css3-selectors] Grouping

On Sat, Jan 9, 2010 at 2:17 AM, Nikita Popov <privat@ni-po.com> wrote:
> Compare with:
> .add (#author, #publications) (input, select, textarea)
> Clean, short, effective.
> Or same with :any, though not :matches, cause :matches makes it complicated
> again, because breaking it down to simple rules is impossible (Any ideas on
> that?)

:any() and :matches() are the exact same thing.  They're just
different names that have been suggested.  You'd just do it as:

.add :matches(#author, #publications) :matches(input,select,textarea)

This means "match an input, select, or textarea that is a descendant
of #author or #publications, which is a descendant of something with
class=add.

> And now it gets really wrong. If the rendering engines would really do that,
> oh my god!
> Sure enough the rendering engines would first jump to the ID #publications
> (which is fast, cause it's a lookup, not a loop), check that the class is
> .special (again a lookup, therefore fast) ans only then check for one of the
> children being a form-interaction-element. So, not 3000 checks, but only
> 302, as many as with your @set-variant!

As Andrew said, CSS engines *do* work like that.  They match
'backwards', starting from the end and moving backwards in the
selector.

There's a very good reason for this - it's really efficient in the
most common matching case.  The vast majority of CSS matching takes
place when the page first loads.  In order to make things look good,
browsers do 'progressive rendering' - displaying parts of the page
that they've downloaded before they've finished downloading the whole
thing.  These partial bits of pages get fed to the CSS engine to see
what rules match against it.

Now, think about how an HTML page is written.  Take this example:
<p><b>foo <span>bar</span></b> <i>baz</i></p>
If you walk through that character by character, what happens?  First,
you notice that a <p> element is opening, but you don't know where it
ends yet.  Then you see a <b> open, then a <span>.  Then the <span>
closes, the <b> closes, and an <i> opens.  Finally, the <i> closes, as
does the <p>.

Look at the way CSS works.  When you string together a selector, each
successive piece is either a child or descendant of what came before,
or a following sibling.  If you read it in reverse, then each
successive element is either a parent/ancestor of what came before, or
a previous sibling.  Frex, in "p span", if you read it backwards you
start with a "span", and then you know for a fact that the "p" has to
be either an ancestor or a previous sibling.

Now, combine these two.  The browser has a rule, "p span" that it
wants to match ASAP, so it can tell the rendering engine what to show
as fast as possible.  As the browser reads the HTML
character-by-character, it eventually reaches the </span> tag.  At
this point it knows precisely where the span is, and more important,
*it knows that it has a <b> parent and a <p> grandparent*.  Because of
this, it can immediately say "Yup, the rule matches." - all the
information it needs is right there.  That's the secret of browser CSS
optimizations - if you start at the end of the selector, then as soon
as you find something that matches the last bit, you have *all* of the
information you need to check if the rest of the selector matches as
well.  You don't have to wait for anything, and can report back to the
rendering engine very quickly.

:has breaks this optimization.  You can do, say, "b:has(+i)".  This
means "match a <b> that has an <i> sibling following after it.".  In
this case, you would *like* to immediately report to the rendering
engine as soon as you hit the </b> tag and know precisely where the
<b> is.  But you can't - you have to wait until you check the next
sibling to see if it's an <i>.  This slows things down and complicates
stuff significantly.

So, the end result of this is that using rules that *start* with an
#id isn't particular efficient.  You're not allowing the browser to
search only a portion of the page - it still has to check that rule
against every element in the page, and then later check if the #id is
on some ancestor.  I still do this plenty, though, because it's
obviously helpful - the CSS engine may still have to check the whole
page, but I still know that it's *impossible* for it to match outside
of the subtree rooted by the #id element, so I can be sure the rules
won't get applied somewhere weird.

~TJ

Received on Sunday, 10 January 2010 00:13:10 UTC