- From: David Greenspan <david@meteor.com>
- Date: Sun, 16 Sep 2012 14:40:28 -0700
- To: public-webapps@w3.org
- Message-ID: <CABJCAJ_B9nj0jvnvC_Pt4uMyb3kz=RVzCePm_0Z6x1feauSvwg@mail.gmail.com>
I've been reading the CSS/selector specs lately. I'm interested both as a framework implementer, designing and implementing jQuery-like functionality for Meteor, and as an app developer who looks forward to the days when browsers provide the APIs we want. Coming into the new Selectors API, I was really hoping to discover that the proposed find and findAll methods behaved essentially like the jQuery calls. Put simply, in running elem.find("div p"), both the "div" and the "p" in question would have to be descendants of elem (as well as each other) in order to match. In running elem.find("> div p"), there would be the additional constraint that only immediate descendants of elem would be considered as candidates for the "div". Unfortunately, this didn't seem to be the case... until I read the spec about a half dozen more times, and I realized it was! If I'm reading it right, the spec does work this way and is additionally much more flexible about the relationship between the element, the scoping element (or elements), and the selector. From reading the list archives, I get the feeling that few web developers who come across the spec understand this much. First of all, I want to offer some encouragement. The names "find" and "findAll" are great, and their functionality is exactly what web developers will expect and appreciate. It's familiar and practical. And as long as in basic cases like element.find("a b c + d"), it's required that all of a, b, c, and d descend from the element, I am happy with whatever scope selectors and edge cases are necessary or desirable. However, the spec is very hard to understand. I've read several specs cover-to-cover (Java VM, ECMAScript, JPEG) and I think there is a lot of low-hanging fruit in this short document (Selectors API Level 2) -- ways to improve its clarity and enlist your readers more in support of this spec. Some feedback from a reader, for what it's worth. 6.2 - "... first matching Element node within the subtrees..." Don't define a new term "subtrees" which just means "descendants." Say, "... first matching Element node that is a descendant of the context object." The main take-away from section 6.2 should be that find/findAll use a *different algorithm* from querySelector/querySelectorAll, or the same algorithm with a different setting (relative=true). This point is almost entirely lost. I think the intent is for the reader to perform a sort of "dispatch on type" when the algorithm in 6.5 is invoked, but it's confusing because you can't really look at a selector and a relative selector and tell them apart, and certainly not before parsing them. Isn't a relative selector (string) just a selector (string) optionally preceded by a combinator? It seems to me that find() and querySelector() *do different things*, conceptually; for example, the former will add an implicit :scope to your selector and the latter won't. It's nothing to do with their arguments. Given the same arguments, they will do different things. To make it concrete by analogy, how would you interpret this spec (or short of that, how does it feel to read and try to understand)? === Definitions: "Chairs" are made of wood. "Generalized Chairs" are made of wood, metal or plastic. The VARNISH function accepts a chair. The PAINT function accepts a generalized chair. VARNISH must coat its input with transparent, Acme brand varnish. PAINT must coat its input with liquid. When either method is invoked, the implementation must perform Algorithm Coat. Algorithm Coat: 1) If the input is a wooden chair, coat it with varnish. 2) Otherwise, if the input is a generalized chair, coat it with paint whose color matches the chair's referential color context. === Here are the stumbling blocks: - If you try to simulate the algorithm, it's a head-scratcher. Aren't wooden chairs also generalized chairs? How do we get to step 2 to paint a wooden chair? Are we really supposed to varnish or paint based on whether the chair is known-to-be-wood or merely incidentally-wood, or is painting wooden chairs not possible? - Presumably, painting was invented to support new chair materials and achieve color-coordinated finishes, yet there is barely a functional description of what PAINT does, let alone the intent. "PAINT must coat its input with liquid" is the closest thing we have to a definition. - We're describing these two functions along three axes, but the information is so spread out that it is hard to integrate. The ideal in clarity, which admittedly is not always possible with the need to specify algorithms and share them between sections, is something like: === The VARNISH function accepts a chair and coats it with transparent, Acme brand varnish. The PAINT function accepts a generalized chair and coats it with paint whose color matches the chair's referential color context. === That's a breath of fresh air. However, there are many other options, like: === The VARNISH function accepts a chair as its argument. The PAINT function accepts a generalized chair as its argument. Let the boolean variable "colorful" be true if we are PAINTing and false if we are VARNISHing. When either method is invoked, perform Algorithm Coat with two inputs, the chair or generalized chair to coat and the variable "colorful". Algorithm Coat: 1) If the "colorful" boolean variable from the caller is false, coat the input chair with transparent, Acme brand varnish. 2) If "colorful" is true, coat the input generalized chair with paint whose color matches the chair's referential color context. === Ok, enough about chairs. The point is that a simple refactoring of the explanation can beget huge readability gains. Also, I'm still not 100% sure if find("div p") will parse as a non-relative selector and run with no implicit :scope, or if it is always a relative selector by virtue of being find's argument. I'm 90% sure it's the latter. Other text that made me go "Wah??": 6.2 - "When either method is invoked..." - there are four methods, not 2 6.5 - "... begins with a combinator and that combinator is not ' ' (space)" - how can a relative selector, in a comma-separated list with whitespace, physically start with a space, i.e. a descendant combinator?? Supposing it can (at the beginning of the string, perhaps), why would you special-case it and prepend what's presumably a second descendant combinator in step 5? It's kind of hard to tell what the intent is here except to prepend :scope if :scope doesn't already appear in the selector. I can guess that's all there is to it, but I'm not sure. 7 - "contextual reference element" - This term is revealed to mean simply a potential :scope element. I would call it something like a "scope candidate" -- or basically anything besides what it's called. I swear, the definition of a + b in this writing style would at some point say: "Conforming implementations MUST perform the context operation on the context number and the contextual reference number in the current context." Or else some of these terms would be given unusual names and defined in external documents. 7 - "The :scope pseudo-class MUST match any element that is in the contextual reference element set." Does it also have to not match elements that aren't in the set, or are implementations allowed to match :scope with anything? It's strange to see RFC MUST paired with loose language. 7 - "Specifications intending for this pseudo-class to match specific elements other than the document's root element must define a contextual reference element set." - Whoa, there are deep, philosophical questions here. The previous paragraphs already defined :scope, CRES, and their relationship. Is a spec stronger when it mandates that other specs conform to it? Is it enough to define an apple as red, or do I also have to say that other specs that define apples must define them as red too, perhaps contingent on their "intent"? I'm going to take the easy way out here and interpret this sentence as a helpful hint to other standards writers that if you want to override the default specification of :scope in some context defined in another standard, you have to do it by overriding the default specification of what goes in the CRES in that context. (But there is that small-caps MUST again, inviting us to imagine what sort of non-conforming W3C spec we might find in the wild violating this requirement.) 8 - Feature Strings - Change last paragraph to: "Conforming implementations must return true ... if they are perfectly compliant, and false if they are not." 9 - Examples of find() and :scope MUST be added here. Seriously. This section is the last chance to push that lucky reader over the final threshold to feeling like they fully understand the spec. In closing, after reading the spec and perusing the mailing list, I don't really see any significant unresolved technical points, only lots of confusion about what the spec says. I can't find a single blog post, article, or other basic explanation of this API on the entire Web. I think if I got 20 top web developers from the Valley together and managed to explain the spec to them on a chalkboard as I understand it, they'd all approve it in about 5 minutes and go home. If I instead told them to sit down individually and try to decode the spec, 19 of them would probably give up. That's terrible PR if nothing else. Since computers unfortunately can't read specs, all standards have to be written down in clear English at some point for implementers and software authors to understand, whether the standard in question is simple and airtight like JSON or long and exhaustively enumerated like chip instruction sets. That's what specs are for. Yet even after dissecting this document in such detail, I don't feel like it's intended to be read and understood by me or any human I know. Thanks for reading, David
Received on Monday, 17 September 2012 10:16:33 UTC