Re: QSA, the problem with :scope, and naming
On 2011-10-20 10:14, Sean Hogan wrote: The primary use-case for matchesSelector() has been event-delegation, and this is the same for matches(). More specifically, consider the following scenario: jQuery adds a new event registration method that uses event delegation to mimic the behavior of: $(elem).find( div .thinger).bind(eventType, fn); The new method is called proxybind(), and the equivalent of the above is: $(elem).proxybind( div .thinger, eventType, fn); I cannot find any documentation for proxybind() and it doesn't seem to be in JQuery 1.7.1. I found a proxy() method, but it doesn't seem to match what you're talking about. http://api.jquery.com/jQuery.proxy/ Also, the JQuery.is() method, which is the method similar to .matchesSelector(), does not support any context node, and so it does not work with .is(.foo, context). Could you provide some documentation for, or at least a version of JQuery that implements proxybind? -- Lachlan Hunt - Opera Software http://lachy.id.au/ http://www.opera.com/
Re: QSA, the problem with :scope, and naming
Right now, the spec does however handle that use case by doing this: document.querySelectorAll(:scope .foo, x); Where x is either an individual element, or an Array, NodeList or numerically indexed object containing 0 or more Elements. (It does however limit the result only to elements that are in the document, and any disconnected elements in the collection x would not be found.) What spec are you referring to? I've never seen that and I am having trouble finding it now.
Re: QSA, the problem with :scope, and naming
On 2011-11-15 15:13, Brian Kardell wrote: Right now, the spec does however handle that use case by doing this: document.querySelectorAll(:scope .foo, x); Where x is either an individual element, or an Array, NodeList or numerically indexed object containing 0 or more Elements. (It does however limit the result only to elements that are in the document, and any disconnected elements in the collection x would not be found.) What spec are you referring to? I've never seen that and I am having trouble finding it now. It's in the draft of Selectors API Level 2. http://dev.w3.org/2006/webapi/selectors-api2/#nodeselector See the refElement/refNodes parameters which specify elements matched by :scope. -- Lachlan Hunt - Opera Software http://lachy.id.au/ http://www.opera.com/
Re: QSA, the problem with :scope, and naming
This has been an interesting debate, but I'm still a little confused with the outcome (if any). Will someone summarize the current position on these issues: 1. Should find() and findAll() follow style scoped rules or not and how? 2. How does the presence of :scope affect find() and findAll() 3. Should invalid selectors be allowed (those that start with a combinator)? (I'm not quite sure where that landed) And finally... When can we expect to see a draft proposal of find() and findAll() in Selectors API L2? -Travis
Re: QSA, the problem with :scope, and naming
On Thu, Nov 10, 2011 at 2:57 PM, Travis Leithead travis.leith...@microsoft.com wrote: This has been an interesting debate, but I'm still a little confused with the outcome (if any). Will someone summarize the current position on these issues: 1. Should find() and findAll() follow style scoped rules or not and how? 2. How does the presence of :scope affect find() and findAll() 3. Should invalid selectors be allowed (those that start with a combinator)? (I'm not quite sure where that landed) And finally... When can we expect to see a draft proposal of find() and findAll() in Selectors API L2? This is based on my understanding of the thread's conclusion. 1. Almost, though with a few small differences due to slightly different affordances. Specifically: a. Normal selectors (without :scope in them) will *not* match the context element - you have to use :scope specifically to refer to the context. This was chosen so that the common case of el.find('div') won't just return the context element when it's a div. style scoped goes the opposite way largely for forwards compat, so people can use the #id of the context during the interim period b. You can select elements outside of the context element. The search starts at the context, but you can use a sibling or reference combinator to select outside the subtree. This is because it's useful, and also because it allows one to interrupt a selector at any point, do some work on the set, and then continue with the rest of the selector, regardless of what the selector's structure was. 2. The presence of :scope lets you select the context element, or specialize on it. It hasn't been fully decided whether :scope is required to be at the start of the selector when it's present or if it can occur anywhere (in which case it switches the selector to matching over the whole document). 3. Yes, they should be. Everyone's used to them from jQuery, so why not? ~TJ
Re: QSA, the problem with :scope, and naming
On Thu, Nov 10, 2011 at 2:57 PM, Travis Leithead travis.leith...@microsoft.com wrote: This has been an interesting debate, but I'm still a little confused with the outcome (if any). Will someone summarize the current position on these issues: 1. Should find() and findAll() follow style scoped rules or not and how? While related, I think the question of how style scoped rules are parsed is somewhat independent. 2. How does the presence of :scope affect find() and findAll() 3. Should invalid selectors be allowed (those that start with a combinator)? (I'm not quite sure where that landed) Here is my detailed proposal based on the previous thread for how selector parsing should work in the context of .find/.findAll: 1. Start with an empty list 2. Skip any whitespace at the beginning of the string. 3. If the next character is a , a + or a ~, insert a simple selector[1] containing just the :scope pseudo selector at the beginning of the selector. 4. Parse as a selector[1]. If there's a parse error throw an SyntaxError exception. 5. If during parsing a :scope pseudo class was parsed, and if a :scope pseudo selector was added in step 2, throw a SyntaxError exception. 6. If during parsing no :scope pseudo class was parsed, and if no :scope pseudo selector was added in step 2, add a simple selector containing just the :scope pseudo class to the beginning of the list, and use a descendant selector as combinator to the rest of the selector. 7. Add selector to the end of list created in step 1. 8. If, after skipping any whitespace, the next character is a comma, skip the comma and go to step 2. The resulting selector list is evaluated against the document node, but with :scope matching the node that .find/.findAll was called on. .findAll returns all nodes which match the selector in document order. .find returns the first node in the list of nodes that .findAll returns, or null if the list is empty. Now, as for what type .findAll should return is a so far unresolved question. I'll start a separate thread on that. / Jonas
Re: QSA, the problem with :scope, and naming
On Thu, Nov 10, 2011 at 11:33 PM, Jonas Sicking jo...@sicking.cc wrote: On Thu, Nov 10, 2011 at 2:57 PM, Travis Leithead travis.leith...@microsoft.com wrote: This has been an interesting debate, but I'm still a little confused with the outcome (if any). Will someone summarize the current position on these issues: 1. Should find() and findAll() follow style scoped rules or not and how? While related, I think the question of how style scoped rules are parsed is somewhat independent. 2. How does the presence of :scope affect find() and findAll() 3. Should invalid selectors be allowed (those that start with a combinator)? (I'm not quite sure where that landed) Here is my detailed proposal based on the previous thread for how selector parsing should work in the context of .find/.findAll: 1. Start with an empty list 2. Skip any whitespace at the beginning of the string. 3. If the next character is a , a + or a ~, insert a simple selector[1] containing just the :scope pseudo selector at the beginning of the selector. 4. Parse as a selector[1]. If there's a parse error throw an SyntaxError exception. 5. If during parsing a :scope pseudo class was parsed, and if a :scope pseudo selector was added in step 2, throw a SyntaxError exception. 6. If during parsing no :scope pseudo class was parsed, and if no :scope pseudo selector was added in step 2, add a simple selector containing just the :scope pseudo class to the beginning of the list, and use a descendant selector as combinator to the rest of the selector. 7. Add selector to the end of list created in step 1. 8. If, after skipping any whitespace, the next character is a comma, skip the comma and go to step 2. The resulting selector list is evaluated against the document node, but with :scope matching the node that .find/.findAll was called on. .findAll returns all nodes which match the selector in document order. .find returns the first node in the list of nodes that .findAll returns, or null if the list is empty. Now, as for what type .findAll should return is a so far unresolved question. I'll start a separate thread on that. And I forgot to include: [1] as defined by the Selector spec. / Jonas
Re: QSA, the problem with :scope, and naming
On Fri, Oct 21, 2011 at 12:41 AM, Jonas Sicking jo...@sicking.cc wrote: On Thu, Oct 20, 2011 at 2:33 PM, Lachlan Hunt lachlan.h...@lachy.id.au wrote: Not necessarily. It depends what exactly it means for a selector to contain :scope for determining whether or not to enable the implied :scope behaviour. Consider: foo.find(:not(:scope)); Ooh, this is an interesting case too. So here's the full list of cases which we need defined behavior for (again looking at Alex and Yehuda here). In the following DOM body id=3 div id=0/div div id=context foo=bar div id=1/div div class=class id=2/div div class=withChildren id=3div class=child id=4/div/div /div div id=5/div div id=6/div /body What would each of the following .findAll calls return. I've included my guessed based on the discussions so far: var e = document.getElementById('context'); e.findAll(div) // returns ids 1,2,3,4 e.findAll() // returns an empty list e.findAll(#3) // returns id 3, but not the body node e.findAll( div) // returns ids 1,2,3 e.findAll([foo=bar]) // returns nothing e.findAll([id=1]) // returns id 1 e.findAll(:first-child) // returns id 1 e.findAll(+ div) // returns id 5 e.findAll(~ div) // returns id 5, 6 e.findAll(:scope) Returns the context. e.findAll(div:scope) Returns the context. e.findAll([foo=bar]:scope) Returns the context. e.findAll(:scope div) 1, 2, 3, 4 e.findAll(div:scope div) 1, 2, 3, 4 e.findAll(div:scope #3) 3 e.findAll(body :scope div) 1, 2, 3, 4 e.findAll(div, :scope) context, 1, 2, 3, 4 e.findAll(body :scope div, :scope) context, 1, 2, 3, 4 e.findAll(:not(:scope)) empty set
Re: QSA, the problem with :scope, and naming
What Tab said = ) On Sun, Oct 30, 2011 at 9:23 AM, Tab Atkins Jr. jackalm...@gmail.com wrote: On Sat, Oct 29, 2011 at 8:28 PM, Cameron McCormack c...@mcc.id.au wrote: On 20/10/11 3:50 AM, Alex Russell wrote: I strongly agree that it should be an Array *type*, but I think just returning a plain Array is the wrong resolution to our NodeList problem. WebIDL should specify that DOM List types *are* Array types. It's insane that we even have a NodeList type which isn't a real array at all. Adding a parallel system when we could just fix the one we have (and preserve the value of a separate prototype for extension) is wonky to me. That said, I'd *also* support the ability to have some sort of decorator mechanism before return on .find() or a way to re-route the prototype of the returned Array. +heycam to debate this point. Late replying here again, apologies, but I agree with others who say that an actual Array object should be returned from this new API given that it is not meant to be live. What benefit is there from returning a NodeList? If it's a NodeList (or something else that *subclasses* Array) we can do fun things like add .find to it, which returns the sorted union of calling .find on all the elements within it. Returning a plain Array doesn't let us do that. ~TJ
Re: QSA, the problem with :scope, and naming
On Sun, Oct 30, 2011 at 1:23 PM, Rick Waldron waldron.r...@gmail.com wrote: On Sat, Oct 29, 2011 at 11:28 PM, Cameron McCormack c...@mcc.id.au wrote: On 20/10/11 3:50 AM, Alex Russell wrote: I strongly agree that it should be an Array *type*, but I think just returning a plain Array is the wrong resolution to our NodeList problem. WebIDL should specify that DOM List types *are* Array types. It's insane that we even have a NodeList type which isn't a real array at all. Adding a parallel system when we could just fix the one we have (and preserve the value of a separate prototype for extension) is wonky to me. That said, I'd *also* support the ability to have some sort of decorator mechanism before return on .find() or a way to re-route the prototype of the returned Array. +heycam to debate this point. Late replying here again, apologies, but I agree with others who say that an actual Array object should be returned from this new API given that it is not meant to be live. What benefit is there from returning a NodeList? As much as I hate saying this: introducing a third return type would be counter-productive, as you'd now have live NodeList, static NodeList and subclassed Array. Congratulations, the cluster-f*ck continues in true form. Live NodeList instances don't need to be considered here. They're the result of an API which generates them, and that API can be described in terms of Proxies. No need to complicate NodeList or imply that we need a different type. Making NodeList instances real array unifies them all. We can get that done too.
Re: QSA, the problem with :scope, and naming
On 31/10/11 1:56 PM, Alex Russell wrote: Live NodeList instances don't need to be considered here. They're the result of an API which generates them, and that API can be described in terms of Proxies. No need to complicate NodeList or imply that we need a different type. Making NodeList instances real array unifies them all. We can get that done too. Don't live and static NodeLists use the same prototype? If they are separate, I don't see any problem with making them real arrays, but I am not sure what the implications of that are. Array.isArray == true, I guess? Do we have that ability within the bounds of ECMAScript yet? Note that we can already make NodeList.prototype === Array.prototype if we want, using appropriate Web IDL annotations.
Re: QSA, the problem with :scope, and naming
On 10/31/11 2:03 PM, Cameron McCormack wrote: On 31/10/11 1:56 PM, Alex Russell wrote: Live NodeList instances don't need to be considered here. They're the result of an API which generates them, and that API can be described in terms of Proxies. No need to complicate NodeList or imply that we need a different type. Making NodeList instances real array unifies them all. We can get that done too. Don't live and static NodeLists use the same prototype? If they are separate, I don't see any problem with making them real arrays, but I am not sure what the implications of that are. Array.isArray == true, I guess? Do we have that ability within the bounds of ECMAScript yet? Note that we can already make NodeList.prototype === Array.prototype if we want, using appropriate Web IDL annotations. Array seems to work fine in WebKit: document.getElementsByTagName('div').__proto__.__proto__ = Array.prototype; dojo just reimplements NodeList as an array: http://dojotoolkit.org/reference-guide/dojo/NodeList.html I don't understand what real array means, other than the prototype equivalence. If NodeList were an array, what's the behavior of running push on NodeList? The list may end up with non-node objects if push is not supplemented. -Charles
Re: QSA, the problem with :scope, and naming
Ok, so we're down to not having full agreement on the following selectors: On Mon, Oct 31, 2011 at 1:55 PM, Alex Russell slightly...@google.com wrote: On Fri, Oct 21, 2011 at 12:41 AM, Jonas Sicking jo...@sicking.cc wrote: e.findAll(body :scope div) 1, 2, 3, 4 e.findAll(body :scope div, :scope) context, 1, 2, 3, 4 I'm hoping that you just made a mistake here? 4 isn't a child of the context node. So in both of these I would think that 4 should be removed based on your answers to the other questions. e.findAll(div, :scope) context, 1, 2, 3, 4 Yehuda had a very different suggestion here but so far hasn't motivated why. Personally I think Alex answer is the more useful one. We just need to decide on something e.findAll(:not(:scope)) empty set Again, Yehuda had a different answer here. Though in this case I think Yehuda's answer is more useful and consistent. But I'm all ears for what the logic used to get to your answer and why you think this is a better (more consistent? more useful?) answer. / Jonas
Re: QSA, the problem with :scope, and naming
On 31/10/11 2:18 PM, Charles Pritchard wrote: I don't understand what real array means, other than the prototype equivalence. There's also the [[DefineOwnProperty]] behaviour which is different from normal objects. That kind of behaviour doesn't *need* to be handled by making NodeLists real arrays, it can be done by making them proxies. Some people might prefer not to do require that though, if it is indeed possible to make them real arrays.
Re: QSA, the problem with :scope, and naming
On Mon, Oct 31, 2011 at 2:03 PM, Cameron McCormack c...@mcc.id.au wrote: On 31/10/11 1:56 PM, Alex Russell wrote: Live NodeList instances don't need to be considered here. They're the result of an API which generates them, and that API can be described in terms of Proxies. No need to complicate NodeList or imply that we need a different type. Making NodeList instances real array unifies them all. We can get that done too. Don't live and static NodeLists use the same prototype? Yes, I envision they would. The restrictions on live lists are probably going to be created by a proxy that wraps them and manages their semantics. If they are separate, I don't see any problem with making them real arrays, but I am not sure what the implications of that are. Array.isArray == true, I guess? For JS, it just means having a working .length property (in all the ways that Arrays allow it to be used). Arrays are identical to other objects in all other respects. Do we have that ability within the bounds of ECMAScript yet? Note that we can already make NodeList.prototype === Array.prototype if we want, using appropriate Web IDL annotations. We'll need ES 6 proxies to get the working .length thing today. Not ideal.
Re: QSA, the problem with :scope, and naming
On Mon, Oct 31, 2011 at 2:18 PM, Charles Pritchard ch...@jumis.com wrote: On 10/31/11 2:03 PM, Cameron McCormack wrote: On 31/10/11 1:56 PM, Alex Russell wrote: Live NodeList instances don't need to be considered here. They're the result of an API which generates them, and that API can be described in terms of Proxies. No need to complicate NodeList or imply that we need a different type. Making NodeList instances real array unifies them all. We can get that done too. Don't live and static NodeLists use the same prototype? If they are separate, I don't see any problem with making them real arrays, but I am not sure what the implications of that are. Array.isArray == true, I guess? Do we have that ability within the bounds of ECMAScript yet? Note that we can already make NodeList.prototype === Array.prototype if we want, using appropriate Web IDL annotations. Array seems to work fine in WebKit: document.getElementsByTagName('div').__proto__.__proto__ = Array.prototype; dojo just reimplements NodeList as an array: http://dojotoolkit.org/reference-guide/dojo/NodeList.html The reason we did it that way is because there's no other way to create an intermediate type with the magic .length property. I don't understand what real array means, other than the prototype equivalence. If NodeList were an array, what's the behavior of running push on NodeList? The list may end up with non-node objects if push is not supplemented. -Charles
Re: QSA, the problem with :scope, and naming
On 10/31/11 7:25 PM, Alex Russell wrote: For JS, it just means having a working .length property (in all the ways that Arrays allow it to be used). Arrays are identical to other objects in all other respects. No, they're not. For example, off the top of my head, they get serialized differently by JSON.stringify. Note that this is based off of the [[Class]] (ES5 section 15.12.3 definition of Str() item 10a). So if you want your object to behave like an array for JSON serialization, it needs to have the right [[Class]]. I'd bet money there are other such things in ES5, for what it's worth. We'll need ES 6 proxies to get the working .length thing today. Not ideal. I think we should just start assuming ES 6 proxies or equivalent mechanism. It's needed for all sorts of stuff and UAs are implementing it anyway. If we want to avoid assuming it, we could just specify things in terms of ES5 internal classes and hooks. -Boris
Re: QSA, the problem with :scope, and naming
On 10/31/11 7:25 PM, Alex Russell wrote: For JS, it just means having a working .length property (in all the ways that Arrays allow it to be used). Arrays are identical to other objects in all other respects. No, they're not. For example, off the top of my head, they get serialized differently by JSON.stringify. Note that this is based off of the [[Class]] (ES5 section 15.12.3 definition of Str() item 10a). So if you want your object to behave like an array for JSON serialization, it needs to have the right [[Class]]. I'd bet money there are other such things in ES5, for what it's worth. Structured cloning fails for NodeList, same as postMessage, because of circular structure. With .join, you get the .toString of the DOM Nodes. There is no JSON serialization for DOM elements.
Re: QSA, the problem with :scope, and naming
On 10/31/11 9:32 PM, Charles Pritchard wrote: Structured cloning fails for NodeList, same as postMessage, because of circular structure. What circular structure? Structured clone can handle that. It fails because it's a host object (based on its [[Class]]) that's not whitelisted in the structured clone algorithm. With .join, you get the .toString of the DOM Nodes. Yes, as you would for an array of DOM nodes. A quick skim through the ES spec shows that Array.prototype.concat also considers the [[Class]] of its arguments, by the way. And indeed, this testcase: script var l = document.getElementsByTagName(html); var a = Array.prototype.slice.call(l); l.__proto__.__proto__ = Array.prototype; var test1 = [].concat(l); var test2 = [].concat(a); alert(test1[0] instanceof HTMLHtmlElement); alert(test2[0] instanceof HTMLHtmlElement); /script alerts false followed by true in UAs. So we really do need to decide what it means for a return value to be an Array. For example, how does it behave with concat()? -Boris
Re: QSA, the problem with :scope, and naming
On Sat, Oct 29, 2011 at 8:28 PM, Cameron McCormack c...@mcc.id.au wrote: On 20/10/11 3:50 AM, Alex Russell wrote: I strongly agree that it should be an Array *type*, but I think just returning a plain Array is the wrong resolution to our NodeList problem. WebIDL should specify that DOM List types *are* Array types. It's insane that we even have a NodeList type which isn't a real array at all. Adding a parallel system when we could just fix the one we have (and preserve the value of a separate prototype for extension) is wonky to me. That said, I'd *also* support the ability to have some sort of decorator mechanism before return on .find() or a way to re-route the prototype of the returned Array. +heycam to debate this point. Late replying here again, apologies, but I agree with others who say that an actual Array object should be returned from this new API given that it is not meant to be live. What benefit is there from returning a NodeList? If it's a NodeList (or something else that *subclasses* Array) we can do fun things like add .find to it, which returns the sorted union of calling .find on all the elements within it. Returning a plain Array doesn't let us do that. ~TJ
Re: QSA, the problem with :scope, and naming
On 30/10/11 2:28 PM, Cameron McCormack wrote: On 20/10/11 3:50 AM, Alex Russell wrote: I strongly agree that it should be an Array *type*, but I think just returning a plain Array is the wrong resolution to our NodeList problem. WebIDL should specify that DOM List types *are* Array types. It's insane that we even have a NodeList type which isn't a real array at all. Adding a parallel system when we could just fix the one we have (and preserve the value of a separate prototype for extension) is wonky to me. That said, I'd *also* support the ability to have some sort of decorator mechanism before return on .find() or a way to re-route the prototype of the returned Array. +heycam to debate this point. Late replying here again, apologies, but I agree with others who say that an actual Array object should be returned from this new API given that it is not meant to be live. What benefit is there from returning a NodeList? I think it's already been said, but if StaticNodeList (or whatever) inherits from Array then it receives array methods plus it can still have .item() for people who are assuming it is like NodeList. Sean
Re: QSA, the problem with :scope, and naming
On Sat, Oct 29, 2011 at 11:28 PM, Cameron McCormack c...@mcc.id.au wrote: On 20/10/11 3:50 AM, Alex Russell wrote: I strongly agree that it should be an Array *type*, but I think just returning a plain Array is the wrong resolution to our NodeList problem. WebIDL should specify that DOM List types *are* Array types. It's insane that we even have a NodeList type which isn't a real array at all. Adding a parallel system when we could just fix the one we have (and preserve the value of a separate prototype for extension) is wonky to me. That said, I'd *also* support the ability to have some sort of decorator mechanism before return on .find() or a way to re-route the prototype of the returned Array. +heycam to debate this point. Late replying here again, apologies, but I agree with others who say that an actual Array object should be returned from this new API given that it is not meant to be live. What benefit is there from returning a NodeList? As much as I hate saying this: introducing a third return type would be counter-productive, as you'd now have live NodeList, static NodeList and subclassed Array. Congratulations, the cluster-f*ck continues in true form.
Re: QSA, the problem with :scope, and naming
On 20/10/11 3:50 AM, Alex Russell wrote: I strongly agree that it should be an Array *type*, but I think just returning a plain Array is the wrong resolution to our NodeList problem. WebIDL should specify that DOM List types *are* Array types. It's insane that we even have a NodeList type which isn't a real array at all. Adding a parallel system when we could just fix the one we have (and preserve the value of a separate prototype for extension) is wonky to me. That said, I'd *also* support the ability to have some sort of decorator mechanism before return on .find() or a way to re-route the prototype of the returned Array. +heycam to debate this point. Late replying here again, apologies, but I agree with others who say that an actual Array object should be returned from this new API given that it is not meant to be live. What benefit is there from returning a NodeList?
Re: QSA, the problem with :scope, and naming
Yeah, I have to agree with the list here. If you allow one its unintuitive to not allow it the same way in a group. The more exceptions and complexity you add, the harder it is for someone to learn. On Oct 25, 2011 10:16 PM, Bjoern Hoehrmann derhoe...@gmx.net wrote: * Tab Atkins Jr. wrote: On Tue, Oct 25, 2011 at 4:56 PM, Ojan Vafai o...@chromium.org wrote: On Tue, Oct 25, 2011 at 4:44 PM, Bjoern Hoehrmann derhoe...@gmx.net wrote: * Tab Atkins Jr. wrote: Did you not understand my example? el.find(+ foo, + bar) feels really weird and I don't like it. I'm okay with a single selector starting with a combinator, like el.find(+ foo), but not a selector list. Allowing + foo but not + foo, + bar would be really weird. Tab, what specifically is weird about el.find(+ foo, + bar)? Seeing a combinator immediately after a comma just seems weird to me. A list of abbreviated selectors is a more intuitive concept than a list of selectors where the first and only the first selector may be abbreviated. List of type versus special case and arbitrary limit. If one abbreviated selector isn't weird, then two shouldn't be either if two selectors aren't weird on their own. -- Björn Höhrmann · mailto:bjo...@hoehrmann.de · http://bjoern.hoehrmann.de Am Badedeich 7 · Telefon: +49(0)160/4415681 · http://www.bjoernsworld.de 25899 Dagebüll · PGP Pub. KeyID: 0xA4357E78 · http://www.websitedev.de/
Re: QSA, the problem with :scope, and naming
On Wed, Oct 26, 2011 at 1:47 PM, Jonas Sicking jo...@sicking.cc wrote: On Tue, Oct 25, 2011 at 10:43 AM, Yehuda Katz wyc...@gmail.com wrote: e.findAll(div, :scope) // 0,context,1,2,3,4,5,6 Huh, why 0 and 6? What's the logic there? I would have expected it to be a sorted union of the results returned from the individual parts. I.e. something like: sortedUnion(e.findAll(div), e.findAll(:scope)) Which would yield [context, 1, 2, 3, 4] Agreed. Any behavior switch based on the contents of a complex selector should be limited to that complex selector, rather than polluting the entire selector list. e.findAll(:not(:scope)) // all elements except context What do you mean by all elements? All elements in the whole document (except the context node). Including the body and any siblings it might have (and their descendants)? Yes to both. It would be identical to giving the context element a unique id, and then just doing a normal full-document query for :not(#unique). ~TJ
Re: QSA, the problem with :scope, and naming
Your guesses are all right in terms of existing jQuery but one: 'div': [1, 2, 3, 4] '': [] '#3': [3] ' div': [1, 2, 3] '[foo=bar]': [] '[id=1]': [1] ':first-child': [1, 4] '+ div': [5] '~ div': [5, 6] You can see the results live at http://jsfiddle.net/Dj3Ab/. The basic rule is that if there is no combinator, a descendent combinator is implied. jQuery returns an empty list with an empty String, but querySelectorAll throws an exception (the spec says that the selector SHOULD match a slightly loosened version of the selector production in http://www.w3.org/TR/2009/PR-css3-selectors-20091215/#w3cselgrammar, which disallows the empty string). It probably makes sense for find or findAll to raise an exception too, but an empty list is fine too. I'm not as sure about the :scope cases. I can make some guesses: e.findAll(:scope) // context e.findAll(div:scope) // context e.findAll([foo=bar]:scope) // context e.findAll(:scope div) // 1,2,3,4 e.findAll(div:scope div) // 1,2,3,4 e.findAll(div:scope #3) // 3 e.findAll(body :scope div) // 1,2,3 e.findAll(div, :scope) // 0,context,1,2,3,4,5,6 e.findAll(body :scope div, :scope) // context,1,2,3 e.findAll(:not(:scope)) // all elements except context In cases where :scope is used, I would say that the selector behaves identically to the id hack used by most JavaScript libraries. In particular, the selector is relative to the root element, and the :scope element simply represents the context element. So you could change the :scope rules to be: e.findAll(#context) // context e.findAll(div#context) // context e.findAll([foo=bar]#context) // context e.findAll(#context div) // 1,2,3,4 e.findAll(div#context div) // 1,2,3,4 e.findAll(div#context #3) // 3 e.findAll(body #context div) // 1,2,3 e.findAll(div, #context) // 0,context,1,2,3,4,5,6 e.findAll(body #context div, #context) // context,1,2,3 e.findAll(:not(#context)) // all elements except context This also means that the above (non-:scope) rule could (I think) be restated as: - Each selector in a selector group passed to find or findAll has an implied leading :scope - If no initial combinator is supplied, a descendent combinator is implied Yehuda Katz (ph) 718.877.1325 On Fri, Oct 21, 2011 at 12:41 AM, Jonas Sicking jo...@sicking.cc wrote: On Thu, Oct 20, 2011 at 2:33 PM, Lachlan Hunt lachlan.h...@lachy.id.au wrote: Not necessarily. It depends what exactly it means for a selector to contain :scope for determining whether or not to enable the implied :scope behaviour. Consider: foo.find(:not(:scope)); Ooh, this is an interesting case too. So here's the full list of cases which we need defined behavior for (again looking at Alex and Yehuda here). In the following DOM body id=3 div id=0/div div id=context foo=bar div id=1/div div class=class id=2/div div class=withChildren id=3div class=child id=4/div/div /div div id=5/div div id=6/div /body What would each of the following .findAll calls return. I've included my guessed based on the discussions so far: var e = document.getElementById('context'); e.findAll(div) // returns ids 1,2,3,4 e.findAll() // returns an empty list e.findAll(#3) // returns id 3, but not the body node e.findAll( div) // returns ids 1,2,3 e.findAll([foo=bar]) // returns nothing e.findAll([id=1]) // returns id 1 e.findAll(:first-child) // returns id 1 e.findAll(+ div) // returns id 5 e.findAll(~ div) // returns id 5, 6 e.findAll(:scope) e.findAll(div:scope) e.findAll([foo=bar]:scope) e.findAll(:scope div) e.findAll(div:scope div) e.findAll(div:scope #3) e.findAll(body :scope div) e.findAll(div, :scope) e.findAll(body :scope div, :scope) e.findAll(:not(:scope)) / Jonas
Re: QSA, the problem with :scope, and naming
On Tue, Oct 25, 2011 at 10:43 AM, Yehuda Katz wyc...@gmail.com wrote: Your guesses are all right in terms of existing jQuery but one: 'div': [1, 2, 3, 4] '': [] '#3': [3] ' div': [1, 2, 3] '[foo=bar]': [] '[id=1]': [1] ':first-child': [1, 4] '+ div': [5] '~ div': [5, 6] You can see the results live at http://jsfiddle.net/Dj3Ab/. The basic rule is that if there is no combinator, a descendent combinator is implied. jQuery returns an empty list with an empty String, but querySelectorAll throws an exception (the spec says that the selector SHOULD match a slightly loosened version of the selector production in http://www.w3.org/TR/2009/PR-css3-selectors-20091215/#w3cselgrammar, which disallows the empty string). It probably makes sense for find or findAll to raise an exception too, but an empty list is fine too. I'm not as sure about the :scope cases. I can make some guesses: e.findAll(:scope) // context e.findAll(div:scope) // context e.findAll([foo=bar]:scope) // context e.findAll(:scope div) // 1,2,3,4 e.findAll(div:scope div) // 1,2,3,4 e.findAll(div:scope #3) // 3 e.findAll(body :scope div) // 1,2,3 e.findAll(div, :scope) // 0,context,1,2,3,4,5,6 e.findAll(body :scope div, :scope) // context,1,2,3 e.findAll(:not(:scope)) // all elements except context In cases where :scope is used, I would say that the selector behaves identically to the id hack used by most JavaScript libraries. In particular, the selector is relative to the root element, and the :scope element simply represents the context element. So you could change the :scope rules to be: e.findAll(#context) // context e.findAll(div#context) // context e.findAll([foo=bar]#context) // context e.findAll(#context div) // 1,2,3,4 e.findAll(div#context div) // 1,2,3,4 e.findAll(div#context #3) // 3 e.findAll(body #context div) // 1,2,3 e.findAll(div, #context) // 0,context,1,2,3,4,5,6 e.findAll(body #context div, #context) // context,1,2,3 e.findAll(:not(#context)) // all elements except context This also means that the above (non-:scope) rule could (I think) be restated as: Each selector in a selector group passed to find or findAll has an implied leading :scope If no initial combinator is supplied, a descendent combinator is implied I'm no longer strongly convinced that we need to support :scope not at the front of the selector. el.find(foo :scope bar) is equivalent to first doing el.matches(foo :scope), then running el.find(bar) if it does. This is slightly inconvenient with individual elements, but in the ideal future when .find returns a nice NodeList class that's a subtype of Array, the NodeList can have its own .filter and .find functions on it that automatically runs the operations over all the children and unions the results into a new NodeList. Then you can do elems.filter(foo :scope).find(bar), and you're done. ~TJ
Re: QSA, the problem with :scope, and naming
On 26/10/11 4:43 AM, Yehuda Katz wrote: Your guesses are all right in terms of existing jQuery but one: 'div': [1, 2, 3, 4] '': [] '#3': [3] ' div': [1, 2, 3] '[foo=bar]': [] '[id=1]': [1] ':first-child': [1, 4] '+ div': [5] '~ div': [5, 6] You can see the results live at http://jsfiddle.net/Dj3Ab/. The basic rule is that if there is no combinator, a descendent combinator is implied. jQuery returns an empty list with an empty String, but querySelectorAll throws an exception (the spec says that the selector SHOULD match a slightly loosened version of the selector production in http://www.w3.org/TR/2009/PR-css3-selectors-20091215/#w3cselgrammar, which disallows the empty string). It probably makes sense for find or findAll to raise an exception too, but an empty list is fine too. I'm not as sure about the :scope cases. I can make some guesses: e.findAll(:scope) // context e.findAll(div:scope) // context e.findAll([foo=bar]:scope) // context e.findAll(:scope div) // 1,2,3,4 e.findAll(div:scope div) // 1,2,3,4 e.findAll(div:scope #3) // 3 e.findAll(body :scope div) // 1,2,3 e.findAll(div, :scope) // 0,context,1,2,3,4,5,6 e.findAll(body :scope div, :scope) // context,1,2,3 e.findAll(:not(:scope)) // all elements except context In cases where :scope is used, I would say that the selector behaves identically to the id hack used by most JavaScript libraries. In particular, the selector is relative to the root element, and the :scope element simply represents the context element. I think allowing explicit :scope in findAll() will be perpetually confusing. I can imagine someone looking at old code like: e.findAll(div.foo span, div.bar :scope span) and asking themselves what's the rule again? If there's :scope in the selector then there's no :scope implied? Or was that just on each single selector? Or is :scope always implied at the start of the whole selector list and that's why it's explicit in the second part? Dammit, why didn't we just use querySelectorAll() if we wanted explicit :scope? So you could change the :scope rules to be: e.findAll(#context) // context e.findAll(div#context) // context e.findAll([foo=bar]#context) // context e.findAll(#context div) // 1,2,3,4 e.findAll(div#context div) // 1,2,3,4 e.findAll(div#context #3) // 3 e.findAll(body #context div) // 1,2,3 e.findAll(div, #context) // 0,context,1,2,3,4,5,6 e.findAll(body #context div, #context) // context,1,2,3 e.findAll(:not(#context)) // all elements except context This also means that the above (non-:scope) rule could (I think) be restated as: * Each selector in a selector group passed to find or findAll has an implied leading :scope * If no initial combinator is supplied, a descendent combinator is implied Yehuda Katz (ph) 718.877.1325 On Fri, Oct 21, 2011 at 12:41 AM, Jonas Sicking jo...@sicking.cc wrote: On Thu, Oct 20, 2011 at 2:33 PM, Lachlan Hunt lachlan.h...@lachy.id.au mailto:lachlan.h...@lachy.id.au wrote: Not necessarily. It depends what exactly it means for a selector to contain :scope for determining whether or not to enable the implied :scope behaviour. Consider: foo.find(:not(:scope)); Ooh, this is an interesting case too. So here's the full list of cases which we need defined behavior for (again looking at Alex and Yehuda here). In the following DOM body id=3 div id=0/div div id=context foo=bar div id=1/div div class=class id=2/div div class=withChildren id=3div class=child id=4/div/div /div div id=5/div div id=6/div /body What would each of the following .findAll calls return. I've included my guessed based on the discussions so far: var e = document.getElementById('context'); e.findAll(div) // returns ids 1,2,3,4 e.findAll() // returns an empty list e.findAll(#3) // returns id 3, but not the body node e.findAll( div) // returns ids 1,2,3 e.findAll([foo=bar]) // returns nothing e.findAll([id=1]) // returns id 1 e.findAll(:first-child) // returns id 1 e.findAll(+ div) // returns id 5 e.findAll(~ div) // returns id 5, 6 e.findAll(:scope) e.findAll(div:scope) e.findAll([foo=bar]:scope) e.findAll(:scope div) e.findAll(div:scope div) e.findAll(div:scope #3) e.findAll(body :scope div) e.findAll(div, :scope) e.findAll(body :scope div, :scope) e.findAll(:not(:scope)) / Jonas
Re: QSA, the problem with :scope, and naming
On Tue, Oct 25, 2011 at 1:47 PM, Sean Hogan shogu...@westnet.com.au wrote: I think allowing explicit :scope in findAll() will be perpetually confusing. I can imagine someone looking at old code like: e.findAll(div.foo span, div.bar :scope span) and asking themselves what's the rule again? If there's :scope in the selector then there's no :scope implied? Or was that just on each single selector? Or is :scope always implied at the start of the whole selector list and that's why it's explicit in the second part? Dammit, why didn't we just use querySelectorAll() if we wanted explicit :scope? Using :scope explicitly at the beginning of selectors is necessary if we want a sane way to have selector lists chain off of the scoping element. I'm okay with the string starting with a combinator when it's a single selector like + foo, but not when it's a selector list like + foo, + bar. ~TJ
Re: QSA, the problem with :scope, and naming
On 26/10/11 7:51 AM, Tab Atkins Jr. wrote: On Tue, Oct 25, 2011 at 1:47 PM, Sean Hoganshogu...@westnet.com.au wrote: I think allowing explicit :scope in findAll() will be perpetually confusing. I can imagine someone looking at old code like: e.findAll(div.foo span, div.bar :scope span) and asking themselves what's the rule again? If there's :scope in the selector then there's no :scope implied? Or was that just on each single selector? Or is :scope always implied at the start of the whole selector list and that's why it's explicit in the second part? Dammit, why didn't we just use querySelectorAll() if we wanted explicit :scope? Using :scope explicitly at the beginning of selectors is necessary if we want a sane way to have selector lists chain off of the scoping element. I'm okay with the string starting with a combinator when it's a single selector like + foo, but not when it's a selector list like + foo, + bar. ~TJ I didn't follow that. Why does findAll() need to support explicit :scope? It is simpler if querySelectorAll() supports explicit :scope and findAll() doesn't. Plus it means findAll() matches how js libs currently work. Sean
Re: QSA, the problem with :scope, and naming
The only case I can think of where explicit scope might be useful would be to filter out certain cases entirely: elem.findAll(:scope:visible div); elem.findAll(#contents :scope [data-foo]) It's probably fine to just say that you should do a match first in that case, given the additional complexity of remembering all the rules. Alex, what do you think? Yehuda Katz (ph) 718.877.1325 On Tue, Oct 25, 2011 at 2:33 PM, Sean Hogan shogu...@westnet.com.au wrote: On 26/10/11 7:51 AM, Tab Atkins Jr. wrote: On Tue, Oct 25, 2011 at 1:47 PM, Sean Hoganshogu...@westnet.com.au wrote: I think allowing explicit :scope in findAll() will be perpetually confusing. I can imagine someone looking at old code like: e.findAll(div.foo span, div.bar :scope span) and asking themselves what's the rule again? If there's :scope in the selector then there's no :scope implied? Or was that just on each single selector? Or is :scope always implied at the start of the whole selector list and that's why it's explicit in the second part? Dammit, why didn't we just use querySelectorAll() if we wanted explicit :scope? Using :scope explicitly at the beginning of selectors is necessary if we want a sane way to have selector lists chain off of the scoping element. I'm okay with the string starting with a combinator when it's a single selector like + foo, but not when it's a selector list like + foo, + bar. ~TJ I didn't follow that. Why does findAll() need to support explicit :scope? It is simpler if querySelectorAll() supports explicit :scope and findAll() doesn't. Plus it means findAll() matches how js libs currently work. Sean
Re: QSA, the problem with :scope, and naming
On Tue, Oct 25, 2011 at 2:33 PM, Sean Hogan shogu...@westnet.com.au wrote: On 26/10/11 7:51 AM, Tab Atkins Jr. wrote: On Tue, Oct 25, 2011 at 1:47 PM, Sean Hoganshogu...@westnet.com.au wrote: I think allowing explicit :scope in findAll() will be perpetually confusing. I can imagine someone looking at old code like: e.findAll(div.foo span, div.bar :scope span) and asking themselves what's the rule again? If there's :scope in the selector then there's no :scope implied? Or was that just on each single selector? Or is :scope always implied at the start of the whole selector list and that's why it's explicit in the second part? Dammit, why didn't we just use querySelectorAll() if we wanted explicit :scope? Using :scope explicitly at the beginning of selectors is necessary if we want a sane way to have selector lists chain off of the scoping element. I'm okay with the string starting with a combinator when it's a single selector like + foo, but not when it's a selector list like + foo, + bar. I didn't follow that. Why does findAll() need to support explicit :scope? Did you not understand my example? el.find(+ foo, + bar) feels really weird and I don't like it. I'm okay with a single selector starting with a combinator, like el.find(+ foo), but not a selector list. ~TJ
Re: QSA, the problem with :scope, and naming
On 26/10/11 9:28 AM, Tab Atkins Jr. wrote: On Tue, Oct 25, 2011 at 2:33 PM, Sean Hoganshogu...@westnet.com.au wrote: On 26/10/11 7:51 AM, Tab Atkins Jr. wrote: On Tue, Oct 25, 2011 at 1:47 PM, Sean Hoganshogu...@westnet.com.au wrote: I think allowing explicit :scope in findAll() will be perpetually confusing. I can imagine someone looking at old code like: e.findAll(div.foo span, div.bar :scope span) and asking themselves what's the rule again? If there's :scope in the selector then there's no :scope implied? Or was that just on each single selector? Or is :scope always implied at the start of the whole selector list and that's why it's explicit in the second part? Dammit, why didn't we just use querySelectorAll() if we wanted explicit :scope? Using :scope explicitly at the beginning of selectors is necessary if we want a sane way to have selector lists chain off of the scoping element. I'm okay with the string starting with a combinator when it's a single selector like + foo, but not when it's a selector list like + foo, + bar. I didn't follow that. Why does findAll() need to support explicit :scope? Did you not understand my example? el.find(+ foo, + bar) feels really weird and I don't like it. I'm okay with a single selector starting with a combinator, like el.find(+ foo), but not a selector list. So +foo becomes :scope +foo But +foo, +bar throws an error? What about: +foo, bar :scope+foo, bar body :scope+foo, :scope+bar What do JS lib selectors do? Do any support :scope? Do any not support +foo, +bar, foo, bar? Sean
Re: QSA, the problem with :scope, and naming
The CSS grammar (http://www.w3.org/TR/selectors/#w3cselgrammar) has a production for selector_group and selector. I would think that :scope would be prepended to each selector, not the entire selector group. -- Yehuda Katz On Tuesday, October 25, 2011, Tab Atkins Jr. wrote: On Tue, Oct 25, 2011 at 2:33 PM, Sean Hogan shogu...@westnet.com.aujavascript:; wrote: On 26/10/11 7:51 AM, Tab Atkins Jr. wrote: On Tue, Oct 25, 2011 at 1:47 PM, Sean Hoganshogu...@westnet.com.aujavascript:; wrote: I think allowing explicit :scope in findAll() will be perpetually confusing. I can imagine someone looking at old code like: e.findAll(div.foo span, div.bar :scope span) and asking themselves what's the rule again? If there's :scope in the selector then there's no :scope implied? Or was that just on each single selector? Or is :scope always implied at the start of the whole selector list and that's why it's explicit in the second part? Dammit, why didn't we just use querySelectorAll() if we wanted explicit :scope? Using :scope explicitly at the beginning of selectors is necessary if we want a sane way to have selector lists chain off of the scoping element. I'm okay with the string starting with a combinator when it's a single selector like + foo, but not when it's a selector list like + foo, + bar. I didn't follow that. Why does findAll() need to support explicit :scope? Did you not understand my example? el.find(+ foo, + bar) feels really weird and I don't like it. I'm okay with a single selector starting with a combinator, like el.find(+ foo), but not a selector list. ~TJ -- Yehuda Katz (ph) 718.877.1325
Re: QSA, the problem with :scope, and naming
* Tab Atkins Jr. wrote: Did you not understand my example? el.find(+ foo, + bar) feels really weird and I don't like it. I'm okay with a single selector starting with a combinator, like el.find(+ foo), but not a selector list. Allowing + foo but not + foo, + bar would be really weird. -- Björn Höhrmann · mailto:bjo...@hoehrmann.de · http://bjoern.hoehrmann.de Am Badedeich 7 · Telefon: +49(0)160/4415681 · http://www.bjoernsworld.de 25899 Dagebüll · PGP Pub. KeyID: 0xA4357E78 · http://www.websitedev.de/
Re: QSA, the problem with :scope, and naming
On Tue, Oct 25, 2011 at 4:56 PM, Ojan Vafai o...@chromium.org wrote: On Tue, Oct 25, 2011 at 4:44 PM, Bjoern Hoehrmann derhoe...@gmx.net wrote: * Tab Atkins Jr. wrote: Did you not understand my example? el.find(+ foo, + bar) feels really weird and I don't like it. I'm okay with a single selector starting with a combinator, like el.find(+ foo), but not a selector list. Allowing + foo but not + foo, + bar would be really weird. Tab, what specifically is weird about el.find(+ foo, + bar)? Seeing a combinator immediately after a comma just seems weird to me. This may just be a personal prejudice. ~TJ
Re: QSA, the problem with :scope, and naming
fwiw, jQuery doesn't properly handle the comma-separated case, but this is most definitely a bug, caused by the naïve implementation that Alex showed early on in this thread. -- Yehuda On Tuesday, October 25, 2011, Tab Atkins Jr. wrote: On Tue, Oct 25, 2011 at 4:56 PM, Ojan Vafai o...@chromium.orgjavascript:; wrote: On Tue, Oct 25, 2011 at 4:44 PM, Bjoern Hoehrmann derhoe...@gmx.netjavascript:; wrote: * Tab Atkins Jr. wrote: Did you not understand my example? el.find(+ foo, + bar) feels really weird and I don't like it. I'm okay with a single selector starting with a combinator, like el.find(+ foo), but not a selector list. Allowing + foo but not + foo, + bar would be really weird. Tab, what specifically is weird about el.find(+ foo, + bar)? Seeing a combinator immediately after a comma just seems weird to me. This may just be a personal prejudice. ~TJ -- Yehuda Katz (ph) 718.877.1325
Re: QSA, the problem with :scope, and naming
On 26/10/11 11:03 AM, Yehuda Katz wrote: fwiw, jQuery doesn't properly handle the comma-separated case, but this is most definitely a bug, caused by the naïve implementation that Alex showed early on in this thread. So no-one is actually using +foo, +bar? How common is +foo usage? Sean
Re: QSA, the problem with :scope, and naming
On Tue, Oct 25, 2011 at 4:58 PM, Tab Atkins Jr. jackalm...@gmail.comwrote: On Tue, Oct 25, 2011 at 4:56 PM, Ojan Vafai o...@chromium.org wrote: On Tue, Oct 25, 2011 at 4:44 PM, Bjoern Hoehrmann derhoe...@gmx.net wrote: * Tab Atkins Jr. wrote: Did you not understand my example? el.find(+ foo, + bar) feels really weird and I don't like it. I'm okay with a single selector starting with a combinator, like el.find(+ foo), but not a selector list. Allowing + foo but not + foo, + bar would be really weird. Tab, what specifically is weird about el.find(+ foo, + bar)? Seeing a combinator immediately after a comma just seems weird to me. This may just be a personal prejudice. With my web developer hat on, I would expect the selector list version to just be a comma-separated list of any valid single selectors. We should either allow it in both cases or neither IMO. My preference is to allow it. Ojan
Re: QSA, the problem with :scope, and naming
* Tab Atkins Jr. wrote: On Tue, Oct 25, 2011 at 4:56 PM, Ojan Vafai o...@chromium.org wrote: On Tue, Oct 25, 2011 at 4:44 PM, Bjoern Hoehrmann derhoe...@gmx.net wrote: * Tab Atkins Jr. wrote: Did you not understand my example? el.find(+ foo, + bar) feels really weird and I don't like it. I'm okay with a single selector starting with a combinator, like el.find(+ foo), but not a selector list. Allowing + foo but not + foo, + bar would be really weird. Tab, what specifically is weird about el.find(+ foo, + bar)? Seeing a combinator immediately after a comma just seems weird to me. A list of abbreviated selectors is a more intuitive concept than a list of selectors where the first and only the first selector may be abbreviated. List of type versus special case and arbitrary limit. If one abbreviated selector isn't weird, then two shouldn't be either if two selectors aren't weird on their own. -- Björn Höhrmann · mailto:bjo...@hoehrmann.de · http://bjoern.hoehrmann.de Am Badedeich 7 · Telefon: +49(0)160/4415681 · http://www.bjoernsworld.de 25899 Dagebüll · PGP Pub. KeyID: 0xA4357E78 · http://www.websitedev.de/
Re: QSA, the problem with :scope, and naming
On 23/10/11 5:44 AM, Timmy wrote: On Oct 21, 2011, at 7:57 PM, Sean Hogan wrote: It was definitely not a design flaw in QSA. As Alex's sample code shows it is possible to get findAll() behavior using QSA. I think that further supports my argument. JS libraries have commonly considered this to be an oversight in the design of QSA, hence the need for ID hack. That is still true whether it was actually a design flaw or not. To do the reverse would involve calling document.findAll() then filtering for nodes that are descendants of the invoking node. I don't think anyone is interested in implementing the reverse in selector engines. The most useful thing would be elem.findAll(). No manual filtering required. rant This sounds like: Although X is more composable than Y, this is irrelevant because no-one would use X if Y is available. This was the rationale used for removing matchesSelector() from the original Selectors API spec. In my experience, when someone (me, for instance) or a committee or work-group can't think of a benefit of a more composable function it merely indicates a limited experience or imagination. Once it has been decided that an API will be provided for a particular feature, the more primitive methods should be available by default. To *not* include them should require good reasons. More specific methods should require use-cases and a rationale for why they shouldn't be composed from other methods. Anyway, the use-case for element.querySelectorAll() - apart from being called from selector engines in js libs - is quite straight-forward. Say I want to augment all elements in a page that match a given selector. After the page has loaded I can find all the elements by calling document.querySelectorAll(). When new content is added into the page I don't want to call document.querySelectorAll() and filter out already augmented elements. But if I only had element.findAll() then I might miss elements that match the selector because for findAll() the *whole* selector must match within the scope of element. This is similar to the way jQuery.fn.delegate() works. One of the strengths of .delegate() is that it can handle events for elements that match the given selector even if the element wasn't in the document when delegate() was called. But .delegate() scopes selectors to the whole document, not the invoking nodes. /rant But if findAll() is implemented they can advertise that avoiding non-standard pseudo selectors gives virtually native performance (on supporting platforms). I imagine this would be almost equivalent to deprecating them, which would be a win. This is extraneous. The implementation of custom pseudo selectors would be identical. Well, no-one can prevent JS libs implementing custom pseudo selectors, just as they can't be prevented from implementing custom DOM methods, e.g. HTMLElement.prototype.awesome(). But they both have the same negatives, most especially that if the method name / pseudo-selector name is one day implemented in browsers but with a different definition then code that was relying on the library will start receiving the different native behavior. Of course, there are counter-arguments to the negatives. Never-the-less it is considered good practice (good web-citizenship if you like) to use vendor prefixes for custom methods and custom selectors. Sean
Re: QSA, the problem with :scope, and naming
On Oct 21, 2011, at 7:57 PM, Sean Hogan wrote: It was definitely not a design flaw in QSA. As Alex's sample code shows it is possible to get findAll() behavior using QSA. I think that further supports my argument. JS libraries have commonly considered this to be an oversight in the design of QSA, hence the need for ID hack. That is still true whether it was actually a design flaw or not. To do the reverse would involve calling document.findAll() then filtering for nodes that are descendants of the invoking node. I don't think anyone is interested in implementing the reverse in selector engines. The most useful thing would be elem.findAll(). No manual filtering required. But if findAll() is implemented they can advertise that avoiding non-standard pseudo selectors gives virtually native performance (on supporting platforms). I imagine this would be almost equivalent to deprecating them, which would be a win. This is extraneous. The implementation of custom pseudo selectors would be identical. - Timmy
Re: QSA, the problem with :scope, and naming
On Thu, Oct 20, 2011 at 2:33 PM, Lachlan Hunt lachlan.h...@lachy.id.au wrote: Not necessarily. It depends what exactly it means for a selector to contain :scope for determining whether or not to enable the implied :scope behaviour. Consider: foo.find(:not(:scope)); Ooh, this is an interesting case too. So here's the full list of cases which we need defined behavior for (again looking at Alex and Yehuda here). In the following DOM body id=3 div id=0/div div id=context foo=bar div id=1/div div class=class id=2/div div class=withChildren id=3div class=child id=4/div/div /div div id=5/div div id=6/div /body What would each of the following .findAll calls return. I've included my guessed based on the discussions so far: var e = document.getElementById('context'); e.findAll(div) // returns ids 1,2,3,4 e.findAll() // returns an empty list e.findAll(#3) // returns id 3, but not the body node e.findAll( div) // returns ids 1,2,3 e.findAll([foo=bar]) // returns nothing e.findAll([id=1]) // returns id 1 e.findAll(:first-child) // returns id 1 e.findAll(+ div) // returns id 5 e.findAll(~ div) // returns id 5, 6 e.findAll(:scope) e.findAll(div:scope) e.findAll([foo=bar]:scope) e.findAll(:scope div) e.findAll(div:scope div) e.findAll(div:scope #3) e.findAll(body :scope div) e.findAll(div, :scope) e.findAll(body :scope div, :scope) e.findAll(:not(:scope)) / Jonas
Re: QSA, the problem with :scope, and naming
On Oct 20, 2011, at 4:34 PM, Tab Atkins Jr. wrote: On Thu, Oct 20, 2011 at 12:09 PM, Jonas Sicking jo...@sicking.cc wrote: Let's do the general discussion about how live and non-live NodeLists should behave in a separate thread. Yes, let's. ^_^ The immediate question here is how should the returned object from .findAll behave? Should it be mutable? Should you be able to insert non-Nodes into it? Should it have all of the functions of Array.prototype or just some subset? Should it have any additional functions? Since .findAll is a new function we have absolutely no constraints as far as how NodeLists behave, we can simply return something that isn't a NodeList. It should absolutely have all the Array functions. I know that I want to be able to slice, append, forEach, map, and reduce the list returned by .find. IMHO, the most useful thing would be to just return an Array of nodes so no further adjustment of the return value is required in selector engines. ~TJ
Re: QSA, the problem with :scope, and naming
On 20/10/11 12:39 AM, Timmy Willison wrote: From the perspective of building a selector engine, I think all selector engines need something like .findAll, and not something like :scope. On Tue, Oct 18, 2011 at 8:00 PM, Alex Russell slightly...@google.com mailto:slightly...@google.com wrote: No need to wait. We had something nearly identical for this in Dojo using an ID prefix hack. It looked something like this: (function(){ var ctr = 0; query = function(query, root){ root = root||document; var rootIsDoc = (root.nodeType == 9); var doc = rootIsDoc ? root : (root.ownerDocment||document); if(!rootIsDoc || (~+.indexOf(query.charAt(0)) = 0)){ // Generate an ID prefix for the selector root.id http://root.id = root.id http://root.id||(qUnique+(ctr++)); query = #+root.id http://root.id+ +query; } return Array.prototype.slice.call( doc.querySelectorAll(query) ); }; })(); This is exactly the same dance that :scope does. Sizzle and Slick do the same thing. As far as I can tell, nwmatcher doesn't deal with it. We can't just add :scope to all selections (for many reasons) and adding just before QSA would require the same logic that Alex has demonstrated above. All of the selector engines do predictions at loadtime on whether QSA will work. They continue differently beyond that, but one thing every library has in common is a try/catch around the call to QSA that falls back to manual parsing if it throws an exception (intentionally avoiding the need for complete parsing before calling QSA). The point is it is a misconception that selector engines parse selectors before delegating to QSA. The number of things libraries want to do before getting to the QSA call is very minimal. The one that hurts us all the most is this need for scoping and ':scope' would simply never be used in a selector engine, since the id trick already works everywhere. The case Alex wrote above is pretty much the only case where the selector is parsed beyond checking for tag only, id only, or class only and it is due to what all of the js libraries has considered a design flaw in QSA. A method like findAll would fix that, leaving as much parsing as possible in the hands of the browser. It was definitely not a design flaw in QSA. As Alex's sample code shows it is possible to get findAll() behavior using QSA. To do the reverse would involve calling document.findAll() then filtering for nodes that are descendants of the invoking node. Clearly JS libs aren't going to switch from implied :scope to explicit :scope. But if findAll() is implemented they can advertise that avoiding non-standard pseudo selectors gives virtually native performance (on supporting platforms). I imagine this would be almost equivalent to deprecating them, which would be a win. PS - I should say I don't necessarily think the name 'findAll' would work. I agree it should be short. The equivalent of querySelector would be find and in library land 'find' selects more than one thing, but I'm not as concerned about the name.
Re: QSA, the problem with :scope, and naming
On 20/10/11 1:07 PM, Jonas Sicking wrote: On Tue, Oct 18, 2011 at 9:42 AM, Alex Russellslightly...@google.com wrote: Lachlan and I have been having an...um...*spirited* twitter discussion regarding querySelectorAll, the (deceased?) queryScopedSelectorAll, and :scope. He asked me to continue here, so I'll try to keep it short: The rooted forms of querySelector and querySelectorAll are mis-designed. I like the general idea here. And since we're changing behavior, I think it's a good opportunity to come up with shorter names. Naming is really hard. The shorter names we use, the more likely it is that we're going to break webpages which are messing around with the prototype chain and it increases the risk that we'll regret it later when we come up with even better functions which should use those names. Say that we come up with an even better query language than selectors, at that point .find will simply not be available to us. However, it does seem like selectors are here to stay. And as much as they have shortcomings, people seem to really like them for querying. So with that out of the way, I agree that the CSS working group shouldn't be what is holding us back. I don't agree with Selectors API supporting invalid selectors, but I guess the discussion is more appropriate here than there. However we do need a precise definition of what the new function does. Is prepending :scope and then parsing as a normal selector always going to give the behavior we want? This is actually what I think we got stuck on when the original querySelector was designed. So let's get into specifics about how things should work. According to your proposal of simply prepending a conceptual :scope to each selector group, for the following DOM: body id=3 div id=context foo=bar div id=1/div div class=class id=2/div div class=withChildren id=3div class=child id=4/div/div /div /body you'd get the following behavior: .findAll(div) // returns ids 1,2,3,4 .findAll() // returns the context node itself. This was indicated undesirable .findAll(body :scope div) // returns nothing .findAll(#3) // returns id 3, but not the body node .findAll( div) // returns ids 1,2,3 .findAll([foo=bar]) // returns nothing .findAll([id=1]) // returns id 1 .findAll(:first-child) // returns id 1 Is this desired behavior in all cases except the empty string? If so this seems very doable to me. We can easily make an exception for the case when the passed in string contains no selectors and make that an error or some such. I know everyone knows this, but... These specific examples (where the selector is not a comma separated list) plus most instances of selector lists (e.g. th, td, ul li, ol li) can be trivially supported by a tiny wrapper around querySelectorAll() as defined in Selectors API v2. In fact, I've never seen a selector list that couldn't be successfully split on , and I wouldn't be surprised if they are never used outside of stylesheets. I do however like the idea that if :scope appears in the selector, then this removes the prepending of :scope to that selector group. Is there a reason not to do that? 1. Already supported (in the draft spec) by querySelectorAll(). 2. Not supported by JS libs. 3. No use cases requiring it. Additionally it seems to me that we could allow the same syntax for style scoped. But maybe others disagree? Surely it is both or neither. You don't want to set a precedent for DOM selectors not matching CSS selectors. I think appropriate optimizations as well as extensible functions should be out-of-scope for this thread. They are both big subjects on their own and we're approaching 50 emails in this thread.
Re: QSA, the problem with :scope, and naming
On Wed, Oct 19, 2011 at 11:14 PM, Sean Hogan shogu...@westnet.com.au wrote: I do however like the idea that if :scope appears in the selector, then this removes the prepending of :scope to that selector group. Is there a reason not to do that? 1. Already supported (in the draft spec) by querySelectorAll(). 2. Not supported by JS libs. 3. No use cases requiring it. It's annoying if querying engines have to work with two different query methods (.findAll and .querySelectorAll) and know when to call which. So I don't think 1 is a particularly good point. However 3 is a very good point. If there aren't use cases, then we shouldn't support it. And 2 is a good indicator that there aren't use cases. But if someone knows of use cases then I'm all ears. It's also something that can be added at a later point if use cases arise. / Jonas
Re: QSA, the problem with :scope, and naming
On 20/10/11 5:41 PM, Jonas Sicking wrote: On Wed, Oct 19, 2011 at 11:14 PM, Sean Hoganshogu...@westnet.com.au wrote: I do however like the idea that if :scope appears in the selector, then this removes the prepending of :scope to that selector group. Is there a reason not to do that? 1. Already supported (in the draft spec) by querySelectorAll(). 2. Not supported by JS libs. 3. No use cases requiring it. It's annoying if querying engines have to work with two different query methods (.findAll and .querySelectorAll) and know when to call which. So I don't think 1 is a particularly good point. I don't follow that. If you want style scoped behavior you call findAll(). If not you call querySelectorAll(). Sean
Re: QSA, the problem with :scope, and naming
On Thu, Oct 20, 2011 at 14:08, Tab Atkins Jr. jackalm...@gmail.com wrote: style scoped should (I think) have three cases: 1. Selector without :scope - same as .find 2. Selector with :scope - Same as #1, but also including the context node. 3. Selector in @global - run the selector across the entire document, filter the results to only be the context node and its descendants. (Some people disagree with me on this, and think that #1 and #2 should be merged to always include the context node. That's acceptable, but I don't like it as much.) The - very valid IMHO - main argument for style scoped to always include the scoping element was to allow for easy migration. I.e., where currently you'd use style #menu .foo { color: green } /style div id=menu div class=foo Will be green /div /div div class=foo Will NOT be green /div You could just stick the stylesheet under the div and add 'scoped': div id=menu style scoped #menu .foo { color: green } /style div class=foo Will be green /div /div div class=foo Will NOT be green /div In browsers that don't support 'scoped', this would still work. Where 'scoped' is supported, this doesn't change much per se, except that those style rules don't need to be checked outside the scope. Once a majority of browsers support style scoped one can then proceed to simplify the rules and remove '#menu' (admitted caveat: where this then doesn't create an ambiguity with the scoping div). - Roland
Re: QSA, the problem with :scope, and naming
On Thu, Oct 20, 2011 at 14:52, Jonas Sicking jo...@sicking.cc wrote: style scoped should (I think) have three cases: 1. Selector without :scope - same as .find 2. Selector with :scope - Same as #1, but also including the context node. 3. Selector in @global - run the selector across the entire document, filter the results to only be the context node and its descendants. (Some people disagree with me on this, and think that #1 and #2 should be merged to always include the context node. That's acceptable, but I don't like it as much.) I think it's perfectly okay that these two APIs have different cases. I'm not sure I understand what you are proposing here. Are you saying that div style scoped :scope { background: green; } /style /div should set the background of the div green? This does seem intuitive I agree, but it might also lead to strange behavior since the rendering of the div will change once the stylesheet is parsed. In other words, it's very easy to get flash-of-unstyled-content behavior. Hixie's - again valid IMHO - counterargument for this was that, with the above proposal: div { background-color-green } would not color the scoping element, while the more specific (!) div:scope { background-color: green } would. I.e., a more specific selector suddenly selecting MORE elements than a not so specific one. - Roland
Re: QSA, the problem with :scope, and naming
On 20/10/11 1:07 PM, Jonas Sicking wrote: On Tue, Oct 18, 2011 at 9:42 AM, Alex Russellslightly...@google.com wrote: Lachlan and I have been having an...um...*spirited* twitter discussion regarding querySelectorAll, the (deceased?) queryScopedSelectorAll, and :scope. He asked me to continue here, so I'll try to keep it short: The rooted forms of querySelector and querySelectorAll are mis-designed. I'd like to instead propose that we shorten all of this up and kill both stones by introducing a new API pair, find and findAll, that are rooted as JS devs expect. The above becomes: element.findAll( div .thinger); I like the general idea here. I think appropriate optimizations as well as extensible functions should be out-of-scope for this thread. They are both big subjects on their own and we're approaching 50 emails in this thread. If find / findAll are added to the spec there should also be an equivalent of matchesSelector that handles implicitly scoped selector, e.g. div .thinger. To aid discussion I will call this matches(), but I don't think it is a good final choice. The primary use-case for matchesSelector() has been event-delegation, and this is the same for matches(). More specifically, consider the following scenario: jQuery adds a new event registration method that uses event delegation to mimic the behavior of: $(elem).find( div .thinger).bind(eventType, fn); The new method is called proxybind(), and the equivalent of the above is: $(elem).proxybind( div .thinger, eventType, fn); The event handling for proxybind() would invoke matches( div .thinger, [elem]) on elements between the event target and elem to find matching elements. Sean
Re: QSA, the problem with :scope, and naming
On Thu, Oct 20, 2011 at 1:14 AM, Sean Hogan shogu...@westnet.com.au wrote: On 20/10/11 1:07 PM, Jonas Sicking wrote: On Tue, Oct 18, 2011 at 9:42 AM, Alex Russellslightly...@google.com wrote: Lachlan and I have been having an...um...*spirited* twitter discussion regarding querySelectorAll, the (deceased?) queryScopedSelectorAll, and :scope. He asked me to continue here, so I'll try to keep it short: The rooted forms of querySelector and querySelectorAll are mis-designed. I'd like to instead propose that we shorten all of this up and kill both stones by introducing a new API pair, find and findAll, that are rooted as JS devs expect. The above becomes: element.findAll( div .thinger); I like the general idea here. I think appropriate optimizations as well as extensible functions should be out-of-scope for this thread. They are both big subjects on their own and we're approaching 50 emails in this thread. If find / findAll are added to the spec there should also be an equivalent of matchesSelector that handles implicitly scoped selector, e.g. div .thinger. To aid discussion I will call this matches(), but I don't think it is a good final choice. How would .matches() work? For .findAll we basically prepend a :scope selector step where the :scope pseudo-class matches the element on which .findAll was called. If we did the same for .matches() then elem.matches(foo) would try to match elem against the selector :scope foo where :scope only matches elem and thus the selector only matches elements which are descendants of the element on which .matches() was called. In other words, .matches() would never match anything. Clearly you must have some other behavior in mind as a function which always returns false isn't particularly interesting. / Jonas
Re: QSA, the problem with :scope, and naming
On 20/10/11 7:32 PM, Jonas Sicking wrote: On Thu, Oct 20, 2011 at 1:14 AM, Sean Hoganshogu...@westnet.com.au wrote: On 20/10/11 1:07 PM, Jonas Sicking wrote: On Tue, Oct 18, 2011 at 9:42 AM, Alex Russellslightly...@google.com wrote: Lachlan and I have been having an...um...*spirited* twitter discussion regarding querySelectorAll, the (deceased?) queryScopedSelectorAll, and :scope. He asked me to continue here, so I'll try to keep it short: The rooted forms of querySelector and querySelectorAll are mis-designed. I'd like to instead propose that we shorten all of this up and kill both stones by introducing a new API pair, find and findAll, that are rooted as JS devs expect. The above becomes: element.findAll(div.thinger); I like the general idea here. I think appropriate optimizations as well as extensible functions should be out-of-scope for this thread. They are both big subjects on their own and we're approaching 50 emails in this thread. If find / findAll are added to the spec there should also be an equivalent of matchesSelector that handles implicitly scoped selector, e.g. div .thinger. To aid discussion I will call this matches(), but I don't think it is a good final choice. How would .matches() work? For .findAll we basically prepend a :scope selector step where the :scope pseudo-class matches the element on which .findAll was called. If we did the same for .matches() then elem.matches(foo) would try to match elem against the selector :scope foo where :scope only matches elem and thus the selector only matches elements which are descendants of the element on which .matches() was called. In other words, .matches() would never match anything. Clearly you must have some other behavior in mind as a function which always returns false isn't particularly interesting. / Jonas See the definition of matchesSelector(selector, [ refNodes ]) in the spec: http://www.w3.org/TR/selectors-api2/#matchtesting
Re: QSA, the problem with :scope, and naming
On Thu, Oct 20, 2011 at 2:13 AM, Lachlan Hunt lachlan.h...@lachy.id.au wrote: On 2011-10-20 07:52, Jonas Sicking wrote: I'm not sure I understand what you are proposing here. Are you saying that div style scoped :scope { background: green; } /style /div should set the background of thediv green? This does seem intuitive I agree, but it might also lead to strange behavior since the rendering of thediv will change once the stylesheet is parsed. In other words, it's very easy to get flash-of-unstyled-content behavior. In the majority of cases, that's a very easy problem for authors to avoid by always putting style scoped as the first child of the element. Since a div is invisible in most cases without any content or other styles, any change in rendering from invisible to visible wouldn't be any different from normal incremental rendering. You'd also get the same effect since earlier siblings of the style scoped would be affected, right? I.e. in the following markup, both spans would be blue and the div would be green. div spantext here/span style scoped :scope span { background: green } :scope { background: red } /style spanmore text here/span /div Another problem though is one of performance. Allowing style elements which are later in the DOM affect earlier nodes means that you have to walk significantly more nodes to look for which sheet could apply. You'll have to both look at the elements children, as well as all following siblings. If style scoped elements only affect nodes which are later in DOM order, it's much easier to keep a list of all currently applying style scoped elements as you walk through the DOM tree. However it's possible that this can be optimized satisfactory. But it's something that we need implementation feedback on. / Jonas
Re: QSA, the problem with :scope, and naming
On 20/10/11 8:42 PM, Lachlan Hunt wrote: On 2011-10-20 10:14, Sean Hogan wrote: The primary use-case for matchesSelector() has been event-delegation, and this is the same for matches(). More specifically, consider the following scenario: jQuery adds a new event registration method that uses event delegation to mimic the behavior of: $(elem).find( div .thinger).bind(eventType, fn); The new method is called proxybind(), and the equivalent of the above is: $(elem).proxybind( div .thinger, eventType, fn); The event handling for proxybind() would invoke matches( div .thinger, [elem]) on elements between the event target and elem to find matching elements. It may not be too late to introduce that behaviour into matchesSelector, with a switch based on the presence or absence of the refNodes/refElement parameter. As currently specified, calling the following doesn't and shouldn't prepend :scope. el.matchesSelector(div .foo); This one also matches the prefixed implementations in browsers, since most haven't started supporting :scope yet, and I don't believe Mozilla's experimental implementation [1] has landed yet. As currently specified, calling this: el.matchesSelector(div .foo, ref); Also doesn't prepend :scope automatically, but in that case, the ref nodes do nothing useful. But this selector can still match elements. Admittedly I can't think of a use-case for this, but it is conceivable for someone to expect this to work without an implied :scope. Authors have to use :scope explicitly for them to be useful as in something like: el.matchesSelector(:scope div .foo, ref); Or el.matchesSelector(div:scope .foo, ref); One thing we could possibly do is define that if ref nodes are passed, and the selector doesn't explicitly use :scope, then effectively prepend :scope . This would be exactly the same behaviour as that discussed for .findAll(); That wouldn't break compatibility with anything, optimises for a common case and avoids introducing two separate match methods. I don't see the need for findAll(), but if it is added I think it should always imply :scope at the start of a selector, and I think a separate match method that does the same should be added. To do otherwise seems too ambiguous for a DOM API. e.g. el.matchesSelector(div .foo); // No ref, no magic :scope el.matchesSelector(div .foo, ref);// Implied, magic :scope el.matchesSelector(+.foo, ref); // Implied, magic :scope el.matchesSelector(:scope div .foo, ref); // Explicit, no magic :scope el.matchesSelector(div:scope .foo, ref); // Explicit, no magic :scope [1] https://bugzilla.mozilla.org/show_bug.cgi?id=648722
Re: QSA, the problem with :scope, and naming
On Wed, Oct 19, 2011 at 7:01 PM, Lachlan Hunt lachlan.h...@lachy.id.au wrote: On 2011-10-19 16:08, Alex Russell wrote: On Wed, Oct 19, 2011 at 1:54 PM, Lachlan Huntlachlan.h...@lachy.id.au wrote: I have attempted to address this problem before and the algorithm for parsing a *scoped selector string* (basically what you're calling a rootedSelector) existed in an old draft [1]. That draft also allowed the flexibility of including an explicit :scope pseudo-class in the selector, which allows for conditional expressions to be built into the selector itself that can be used to check the state of the scope element or any of its ancestors. We could accomodate that by looking at the passed selector and trying to determine if it includes a :scope term. If so, avoid prefixing. Yes, that's exactly what the draft specified. Great! So if we specify this behavior for .find() too, I think we're in good shape. That'd allow this sort of flexibility for folks who want to write things out long-hand or target the scope root in the selector, possibly returning itself. I don't see a use case for wanting the proposed method to be able to return the element itself. The case where it's useful for elements matching :scope to be the subject of a selector is where you're trying to filter a list of elements. e.g. document.querySelectorAll(.foo:scope, list); // Returns all elements from list that match. But this wouldn't make sense el.find(.foo:scope) // Return itself if it matches. Ok, I'm fine with not allowing that. That result seems effectively like a less efficient boolean check that is already handled by el.matchesSelector(.foo). matchesSelector...really? We've gotta get a better name for that = ) I''d also support a resolution for this sort of power-tool that forces people to use document.qsa(...,scopeEl) to get at that sort of thing. If there was no special handling to check for an explicit :scope, that would mean that any selector that does include :scope explicitly would not match anything at all. e.g. el.findAll(:scopep); yeah, that occurred to me after sending the last mail. That would be equivalent to: document.querySelectorAll(:scope :scopep, el); Which won't match anything. That might keep things simpler from an implementation perspective and doesn't sacrifice any functionality being requested. Eh, I'm not sure it's sane though. Putting in checking for :scope in the selector and not prefixing if it occurs seems the only reasonable thing. There's a corner case I haven't formed an opinion on though: el.find(div span :scope .whatevs); ...does what? I think it's an error. :scope will need to occur in the first term or not at all for .find(). -- Lachlan Hunt - Opera Software http://lachy.id.au/ http://www.opera.com/
Re: QSA, the problem with :scope, and naming
On Thu, Oct 20, 2011 at 3:07 AM, Jonas Sicking jo...@sicking.cc wrote: On Tue, Oct 18, 2011 at 9:42 AM, Alex Russell slightly...@google.com wrote: Lachlan and I have been having an...um...*spirited* twitter discussion regarding querySelectorAll, the (deceased?) queryScopedSelectorAll, and :scope. He asked me to continue here, so I'll try to keep it short: The rooted forms of querySelector and querySelectorAll are mis-designed. Discussions about a Scoped variant or :scope pseudo tacitly acknowledge this, and the JS libraries are proof in their own right: no major JS library exposes the QSA semantic, instead choosing to implement a rooted search. Related and equally important, that querySelector and querySelectorAll are often referred to by the abbreviation QSA suggests that its name is bloated and improved versions should have shorter names. APIs gain use both through naming and through use. On today's internet -- the one where 50% of all websites include jQuery -- you could even go with element.$(selector) and everyone would know what you mean: it's clearly a search rooted at the element on the left-hand side of the dot. Ceteris peribus, shorter is better. When there's a tie that needs to be broken, the more frequently used the API, the shorter the name it deserves -- i.e., the larger the component of its meaning it will gain through use and repetition and not naming and documentation. I know some on this list might disagree, but all of the above is incredibly non-controversial today. Even if there may have been debates about scoping or naming when QSA was originally designed, history has settled them. And QSA lost on both counts. I therefore believe that this group's current design for scoped selection could be improved significantly. If I understand the latest draft (http://www.w3.org/TR/selectors-api2/#the-scope-pseudo-class) correctly, a scoped search for multiple elements would be written as: element.querySelectorAll(:scope div .thinger); Both then name and the need to specify :scope are punitive to readers and writers of this code. The selector is *obviously* happening in relationship to element somehow. The only sane relationship (from a modern JS hacker's perspective) is that it's where our selector starts from. I'd like to instead propose that we shorten all of this up and kill both stones by introducing a new API pair, find and findAll, that are rooted as JS devs expect. The above becomes: element.findAll( div .thinger); Out come the knives! You can't start a selector with a combinator! Ah, but we don't need to care what CSS thinks of our DOM-only API. We can live and let live by building on :scope and specifying find* as syntactic sugar, defined as: HTMLDocument.prototype.find = HTMLElement.prototype.find = function(rootedSelector) { return this.querySelector(:scope + rootedSelector); } HTMLDocument.prototype.findAll = HTMLElement.prototype.findAll = function(rootedSelector) { return this.querySelectorAll(:scope + rootedSelector); } Of course, :scope in this case is just a special case of the ID rooting hack, but if we're going to have it, we can kill both birds with it. Obvious follow up questions: Q.) Why do we need this at all? Don't the toolkits already just do this internally? A.) Are you saying everyone, everywhere, all the time should need to use a toolkit to get sane behavior from the DOM? If so, what are we doing here, exactly? Q.) Shorter names? Those are for weaklings! A.) And humans. Who still constitute most of our developers. Won't someone please think of the humans? Q.) You're just duplicating things! A.) If you ignore all of the things that are different, then that's true. If not, well, then no. This is a change. And a good one for the reasons listed above. Thoughts? I like the general idea here. And since we're changing behavior, I think it's a good opportunity to come up with shorter names. Naming is really hard. The shorter names we use, the more likely it is that we're going to break webpages which are messing around with the prototype chain and it increases the risk that we'll regret it later when we come up with even better functions which should use those names. So long as the slots are still writable, no loss. Their patches into the prototype chain still exist. Being afraid of this when we're on top seems really, *REALLY* strange to me. Say that we come up with an even better query language than selectors, at that point .find will simply not be available to us. Premature optimization. And $ is still available ;-) However, it does seem like selectors are here to stay. And as much as they have shortcomings, people seem to really like them for querying. So with that out of the way, I agree that the CSS working group shouldn't be what is holding us back. However we do need a precise definition of what the new function does. Is prepending
Re: QSA, the problem with :scope, and naming
On Thu, Oct 20, 2011 at 6:55 AM, Jonas Sicking jo...@sicking.cc wrote: On Tue, Oct 18, 2011 at 9:42 AM, Alex Russell slightly...@google.com wrote: Lachlan and I have been having an...um...*spirited* twitter discussion regarding querySelectorAll, the (deceased?) queryScopedSelectorAll, and :scope. He asked me to continue here, so I'll try to keep it short: The rooted forms of querySelector and querySelectorAll are mis-designed. Discussions about a Scoped variant or :scope pseudo tacitly acknowledge this, and the JS libraries are proof in their own right: no major JS library exposes the QSA semantic, instead choosing to implement a rooted search. Related and equally important, that querySelector and querySelectorAll are often referred to by the abbreviation QSA suggests that its name is bloated and improved versions should have shorter names. APIs gain use both through naming and through use. On today's internet -- the one where 50% of all websites include jQuery -- you could even go with element.$(selector) and everyone would know what you mean: it's clearly a search rooted at the element on the left-hand side of the dot. Ceteris peribus, shorter is better. When there's a tie that needs to be broken, the more frequently used the API, the shorter the name it deserves -- i.e., the larger the component of its meaning it will gain through use and repetition and not naming and documentation. I know some on this list might disagree, but all of the above is incredibly non-controversial today. Even if there may have been debates about scoping or naming when QSA was originally designed, history has settled them. And QSA lost on both counts. I therefore believe that this group's current design for scoped selection could be improved significantly. If I understand the latest draft (http://www.w3.org/TR/selectors-api2/#the-scope-pseudo-class) correctly, a scoped search for multiple elements would be written as: element.querySelectorAll(:scope div .thinger); Both then name and the need to specify :scope are punitive to readers and writers of this code. The selector is *obviously* happening in relationship to element somehow. The only sane relationship (from a modern JS hacker's perspective) is that it's where our selector starts from. I'd like to instead propose that we shorten all of this up and kill both stones by introducing a new API pair, find and findAll, that are rooted as JS devs expect. The above becomes: element.findAll( div .thinger); Out come the knives! You can't start a selector with a combinator! Ah, but we don't need to care what CSS thinks of our DOM-only API. We can live and let live by building on :scope and specifying find* as syntactic sugar, defined as: HTMLDocument.prototype.find = HTMLElement.prototype.find = function(rootedSelector) { return this.querySelector(:scope + rootedSelector); } HTMLDocument.prototype.findAll = HTMLElement.prototype.findAll = function(rootedSelector) { return this.querySelectorAll(:scope + rootedSelector); } Of course, :scope in this case is just a special case of the ID rooting hack, but if we're going to have it, we can kill both birds with it. Obvious follow up questions: Q.) Why do we need this at all? Don't the toolkits already just do this internally? A.) Are you saying everyone, everywhere, all the time should need to use a toolkit to get sane behavior from the DOM? If so, what are we doing here, exactly? Q.) Shorter names? Those are for weaklings! A.) And humans. Who still constitute most of our developers. Won't someone please think of the humans? Q.) You're just duplicating things! A.) If you ignore all of the things that are different, then that's true. If not, well, then no. This is a change. And a good one for the reasons listed above. Thoughts? Oh, and as a separate issue. I think .findAll should return a plain old JS Array. Not a NodeList or any other type of host object. I strongly agree that it should be an Array *type*, but I think just returning a plain Array is the wrong resolution to our NodeList problem. WebIDL should specify that DOM List types *are* Array types. It's insane that we even have a NodeList type which isn't a real array at all. Adding a parallel system when we could just fix the one we have (and preserve the value of a separate prototype for extension) is wonky to me. That said, I'd *also* support the ability to have some sort of decorator mechanism before return on .find() or a way to re-route the prototype of the returned Array. +heycam to debate this point. One of the use cases is being able to mutate the returned value. This is useful if you're for example doing multiple .findAll calls (possibly with different context nodes) and want to merge the resulting lists into a single list. Agreed. An end to the Array.slice() hacks would be great.
Re: QSA, the problem with :scope, and naming
On 2011-10-20 12:50, Alex Russell wrote: On Thu, Oct 20, 2011 at 6:55 AM, Jonas Sickingjo...@sicking.cc wrote: Oh, and as a separate issue. I think .findAll should return a plain old JS Array. Not a NodeList or any other type of host object. I strongly agree that it should be an Array *type*, but I think just returning a plain Array is the wrong resolution to our NodeList problem. WebIDL should specify that DOM List types *are* Array types. We need NodeList separate from Array where they are live lists. I forget the reason we originally opted for a static NodeList rather than Array when this issue was originally discussed a few years ago. -- Lachlan Hunt - Opera Software http://lachy.id.au/ http://www.opera.com/
Re: QSA, the problem with :scope, and naming
On Thu, Oct 20, 2011 at 12:05 PM, Lachlan Hunt lachlan.h...@lachy.id.au wrote: On 2011-10-20 12:50, Alex Russell wrote: On Thu, Oct 20, 2011 at 6:55 AM, Jonas Sickingjo...@sicking.cc wrote: Oh, and as a separate issue. I think .findAll should return a plain old JS Array. Not a NodeList or any other type of host object. I strongly agree that it should be an Array *type*, but I think just returning a plain Array is the wrong resolution to our NodeList problem. WebIDL should specify that DOM List types *are* Array types. We need NodeList separate from Array where they are live lists. No we don't. The fact that there's someone else who has a handle to the list and can mutate it underneath you is a documentation issue, not a question of type...unless the argument is that the slots should be non-configurable, non-writable except by the browser that's also holding a ref to it. I forget the reason we originally opted for a static NodeList rather than Array when this issue was originally discussed a few years ago.
Re: QSA, the problem with :scope, and naming
On 20/10/11 10:05 PM, Lachlan Hunt wrote: On 2011-10-20 12:50, Alex Russell wrote: On Thu, Oct 20, 2011 at 6:55 AM, Jonas Sickingjo...@sicking.cc wrote: Oh, and as a separate issue. I think .findAll should return a plain old JS Array. Not a NodeList or any other type of host object. I strongly agree that it should be an Array *type*, but I think just returning a plain Array is the wrong resolution to our NodeList problem. WebIDL should specify that DOM List types *are* Array types. We need NodeList separate from Array where they are live lists. I forget the reason we originally opted for a static NodeList rather than Array when this issue was originally discussed a few years ago. I wonder if anyone is relying on querySelectorAll() returning a StaticNodeList?
Re: QSA, the problem with :scope, and naming
On 2011-10-20 13:18, Alex Russell wrote: On Thu, Oct 20, 2011 at 12:05 PM, Lachlan Huntlachlan.h...@lachy.id.au wrote: We need NodeList separate from Array where they are live lists. No we don't. The fact that there's someone else who has a handle to the list and can mutate it underneath you is a documentation issue, not a question of type...unless the argument is that the slots should be non-configurable, non-writable except by the browser that's also holding a ref to it. The author cannot be allowed to directly modify a live list, as such it must be an immutable object from the script's perspective. Otherwise, things would get really complicated if this happened: var p = document.getElementsByTagName(p); p.reverse(); p.push(x); p.shift(); document.body.insertBefore(document.createElement(p), p[2]); Where in the array would that new P element get added? NodeLists are supposed to be live and in document order. Ordinarily, that new P element would be inserted into the document and the change reflected in the NodeList. By allowing an author to modify the list in some way, that completely breaks the way NodeLists are defined to work. Now while it's arguable that live node lists were a mistake and that it would have been better if static Arrays were returned, we are stuck with them and cannot change that. -- Lachlan Hunt - Opera Software http://lachy.id.au/ http://www.opera.com/
Re: QSA, the problem with :scope, and naming
On Thu, Oct 20, 2011 at 3:39 AM, Alex Russell slightly...@google.com wrote: There's a corner case I haven't formed an opinion on though: el.find(div span :scope .whatevs); ...does what? I think it's an error. :scope will need to occur in the first term or not at all for .find(). Disagree. If :scope appears in the selector, just match across the whole document. It's simple and useful. (It's equivalent to document.querySelector(div span :scope .whatevs, el), except shorter and amenable to chaining.) ~TJ
Re: QSA, the problem with :scope, and naming
On 2011-10-20 13:35, Sean Hogan wrote: I wonder if anyone is relying on querySelectorAll() returning a StaticNodeList? Only if there are people out there using list.item(n) instead of list[n], or people extending the NodeList interface and expecting such methods to be available on the result. Though I suspect the former is very rare, and the latter doesn't work in all browsers. -- Lachlan Hunt - Opera Software http://lachy.id.au/ http://www.opera.com/
Re: QSA, the problem with :scope, and naming
On 20/10/11 10:49 PM, Lachlan Hunt wrote: On 2011-10-20 13:35, Sean Hogan wrote: I wonder if anyone is relying on querySelectorAll() returning a StaticNodeList? Only if there are people out there using list.item(n) instead of list[n], or people extending the NodeList interface and expecting such methods to be available on the result. Though I suspect the former is very rare, and the latter doesn't work in all browsers. And I wonder if one of the browser vendors would be willing to silently change the behavior and see if they get any bug reports.
Re: QSA, the problem with :scope, and naming
On 10/20/11 6:50 AM, Alex Russell wrote: Oh, and as a separate issue. I think .findAll should return a plain old JS Array. Not a NodeList or any other type of host object. I strongly agree that it should be an Array *type*, but I think just returning a plain Array is the wrong resolution to our NodeList problem. WebIDL should specify that DOM List types *are* Array types. You missed the point of Jonas's suggestion. DOM NodeLists are not mutable via direct manipulation of indexed properties. Jonas is saying that we want the return value here to be thus mutable. Making all DOM NodeLists mutable won't really work because a bunch of them are live; there's no sane way to combine liveness and mutability. The non-live lists (e.g. the one involved here) could be made mutable, but then why make then nodelists at all? What's the point? It's insane that we even have a NodeList type which isn't a real array at all. It's not at all insane for the live lists. See previous discussion about this. I think any solution here that tries to treat live and non-live lists identically is doomed to failure. -Boris
Re: QSA, the problem with :scope, and naming
On 10/20/11 7:18 AM, Alex Russell wrote: No we don't. The fact that there's someone else who has a handle to the list and can mutate it underneath you There is no sane way to mutate the list on the part of the browser if someone else is also messing with it, because the someone else can violate basic invariants the browser's behavior needs to maintain. unless the argument is that the slots should be non-configurable, non-writable except by the browser that's also holding a ref to it. Yes. Though I don't know what slots you're talking about; the only sane JS implementation of live nodelists is as a proxy. There's no way to get the behaviors that browsers have for them otherwise. -Boris
Re: QSA, the problem with :scope, and naming
On Thu, Oct 20, 2011 at 3:14 PM, Boris Zbarsky bzbar...@mit.edu wrote: On 10/20/11 7:18 AM, Alex Russell wrote: No we don't. The fact that there's someone else who has a handle to the list and can mutate it underneath you There is no sane way to mutate the list on the part of the browser if someone else is also messing with it, because the someone else can violate basic invariants the browser's behavior needs to maintain. Right. So you need to vend an apparently-immutable Array, one which can only be changed by the browser. I think that could be accomplished in terms of Proxies. But it's still an Array type. unless the argument is that the slots should be non-configurable, non-writable except by the browser that's also holding a ref to it. Yes. Though I don't know what slots you're talking about; the only sane JS implementation of live nodelists is as a proxy. There's no way to get the behaviors that browsers have for them otherwise. But it can be a Proxy to an *Array*, not to some weird non-Array type.
Re: QSA, the problem with :scope, and naming
On 10/20/11 10:23 AM, Alex Russell wrote: On Thu, Oct 20, 2011 at 3:14 PM, Boris Zbarskybzbar...@mit.edu wrote: Right. So you need to vend an apparently-immutable Array, one which can only be changed by the browser. I think that could be accomplished in terms of Proxies. But it's still an Array type. I have no problem with Array being on the prototype chain or whatnot. But it's not an array in the sense that you can't do a bunch of things with it that people do with arrays. Though I don't know what slots you're talking about; the only sane JS implementation of live nodelists is as a proxy. There's no way to get the behaviors that browsers have for them otherwise. But it can be a Proxy to an *Array*, not to some weird non-Array type. Why does it matter what it's a proxy to? The whole point of being a proxy is that you can't tell what it's proxying. Case in point, in Gecko it's a proxy to something that's not a JS object at all and not even implementable in JS (because it uses internal engine information that's not available to JS). So what exactly do you want here other than nodelists having Array.prototype on their prototype chain, which is discussed elsewhere? And again, for static nodelists none of this applies; there's absolutely no reason I can think of to not make them arrays, unless you really want a .item() on them or unless you really think the length getter should be hookable. -Boris
Re: QSA, the problem with :scope, and naming
On 2011-10-20 16:23, Boris Zbarsky wrote: On 10/20/11 1:08 AM, Tab Atkins Jr. wrote: I disagree. It's extremely useful and natural for .find(:scope + div) to match sibling of the context node. I really don't think it is. If you want that, use document.find(:scope + div, context). Basically, the presence of :scope would turn off *all* the limitations That's a _really_ bizarre behavior. So in this case: foo.find(:scope + div, div) what all divs in the document would be found? Or is the oh, ignore the reference node except for matching :scope meant to only apply on a per-selector basis inside the selector list? That has its own issues, especially with performance (e.g. merging nodesets while preserving DOM order). As it was specified in the old draft of queryScopedSelector (which is the definition I start with if find/findAll get introduced), it was done on a per selector basis, so the above would be equivalent to: document.querySelector(:scope + div, :scope div, foo); -- Lachlan Hunt - Opera Software http://lachy.id.au/ http://www.opera.com/
Re: QSA, the problem with :scope, and naming
On Thu, Oct 20, 2011 at 04:18, Alex Russell slightly...@google.com wrote: No we don't. The fact that there's someone else who has a handle to the list and can mutate it underneath you is a documentation issue, not a question of type...unless the argument is that the slots should be non-configurable, non-writable except by the browser that's also holding a ref to it. That is an ES violation. A non configurable, non writable data property is not allowed to change its value. var descr = Object.getOwnPropertyDescription(object, name); if (!descr.configurable !decsr.writable ('value' in descr)) { var value = descr.value; setInterval(function() { // Must never change assert(object[name] === value); }); } Therefore there is no such thing as an immutable live NodeList. There are ways around this. 1. Use a getter 2. Make it configurable -- erik
Re: QSA, the problem with :scope, and naming
On Thu, Oct 20, 2011 at 04:37, Lachlan Hunt lachlan.h...@lachy.id.au wrote: On 2011-10-20 13:18, Alex Russell wrote: On Thu, Oct 20, 2011 at 12:05 PM, Lachlan Huntlachlan.h...@lachy.id.au wrote: We need NodeList separate from Array where they are live lists. No we don't. The fact that there's someone else who has a handle to the list and can mutate it underneath you is a documentation issue, not a question of type...unless the argument is that the slots should be non-configurable, non-writable except by the browser that's also holding a ref to it. The author cannot be allowed to directly modify a live list, as such it must be an immutable object from the script's perspective. Otherwise, things would get really complicated if this happened: var p = document.getElementsByTagName(p); p.reverse(); Just define [[Put]] to throw (by only having a getter). Since reverse is defined using [[Put]] things would work as expected. -- erik
Re: QSA, the problem with :scope, and naming
On Thu, Oct 20, 2011 at 04:49, Lachlan Hunt lachlan.h...@lachy.id.au wrote: On 2011-10-20 13:35, Sean Hogan wrote: I wonder if anyone is relying on querySelectorAll() returning a StaticNodeList? Only if there are people out there using list.item(n) instead of list[n], or people extending the NodeList interface and expecting such methods to be available on the result. Though I suspect the former is very rare, and the latter doesn't work in all browsers. Both are rare but they do happen http://codesearch.google.com/#search/q=%5CsNodeList%5C.prototype%5C.(%5Cw%2B)%5Cs*=type=cs What is funny is that code search only found one instance of item being used directly after querySelectorAll(...). Of course, that search does not tell the whole story. http://codesearch.google.com/#SjGak5n5VAM/trunk/parsehtml_util.pyq=querySelectorAll%5C(%5B%5E)%5D%2B%5C)%5C.item%20-file:layoutteststype=csl=24 -- erik
Re: QSA, the problem with :scope, and naming
On 10/20/11 12:02 PM, Erik Arvidsson wrote: That is an ES violation. A non configurable, non writable data property is not allowed to change its value. It's not clear what that means in proxy-land; esp. since it's not clear whether proxies can even have non-configurable properties... or did that discussion come to a conclusion? -Boris
Re: QSA, the problem with :scope, and naming
On Thu, Oct 20, 2011 at 09:28, Boris Zbarsky bzbar...@mit.edu wrote: On 10/20/11 12:02 PM, Erik Arvidsson wrote: That is an ES violation. A non configurable, non writable data property is not allowed to change its value. It's not clear what that means in proxy-land; esp. since it's not clear whether proxies can even have non-configurable properties... or did that discussion come to a conclusion? We have a solution to that: http://wiki.ecmascript.org/doku.php?id=strawman:direct_proxies -- erik
Re: QSA, the problem with :scope, and naming
On Thu, Oct 20, 2011 at 3:46 AM, Alex Russell slightly...@google.com wrote: However, it does seem like selectors are here to stay. And as much as they have shortcomings, people seem to really like them for querying. So with that out of the way, I agree that the CSS working group shouldn't be what is holding us back. However we do need a precise definition of what the new function does. Is prepending :scope and then parsing as a normal selector always going to give the behavior we want? This is actually what I think we got stuck on when the original querySelector was designed. So let's get into specifics about how things should work. According to your proposal of simply prepending a conceptual :scope to each selector group, for the following DOM: body id=3 div id=context foo=bar div id=1/div div class=class id=2/div div class=withChildren id=3div class=child id=4/div/div /div /body you'd get the following behavior: .findAll(div) // returns ids 1,2,3,4 .findAll() // returns the context node itself. This was indicated undesirable And, in follow-up mail, we talked extensively about why I didn't *really* mean just prepend the string ':scope '. I think empty string is a special case that we should treat as return an empty list. Sounds reasonable (sorry to bring this case up again, I just wanted to be comprehensive, though I failed at that, see below) .findAll(body :scope div) // returns nothing I suggest we treat :scope occurring after the first term of the selector as an error. So how should it work in the first term? I.e. what should .findAll(:scope) .findAll(div:scope) .findAll([foo=bar]:scope) .findAll(:scope div) .findAll(div:scope div) .findAll(div:scope #3) return? Also, why should :scope appearing in the first term be different from appearing in any other term? What is the use case? Do libraries have anything equivalent today? .findAll(#3) // returns id 3, but not the body node Correct. Assuming the query is document.find(#context).findAll(#3), which is what I think you mean for the root to be in these examples? Yup. / Jonas
Re: QSA, the problem with :scope, and naming
On Thu, Oct 20, 2011 at 3:50 AM, Alex Russell slightly...@google.com wrote: On Thu, Oct 20, 2011 at 6:55 AM, Jonas Sicking jo...@sicking.cc wrote: On Tue, Oct 18, 2011 at 9:42 AM, Alex Russell slightly...@google.com wrote: Lachlan and I have been having an...um...*spirited* twitter discussion regarding querySelectorAll, the (deceased?) queryScopedSelectorAll, and :scope. He asked me to continue here, so I'll try to keep it short: The rooted forms of querySelector and querySelectorAll are mis-designed. Discussions about a Scoped variant or :scope pseudo tacitly acknowledge this, and the JS libraries are proof in their own right: no major JS library exposes the QSA semantic, instead choosing to implement a rooted search. Related and equally important, that querySelector and querySelectorAll are often referred to by the abbreviation QSA suggests that its name is bloated and improved versions should have shorter names. APIs gain use both through naming and through use. On today's internet -- the one where 50% of all websites include jQuery -- you could even go with element.$(selector) and everyone would know what you mean: it's clearly a search rooted at the element on the left-hand side of the dot. Ceteris peribus, shorter is better. When there's a tie that needs to be broken, the more frequently used the API, the shorter the name it deserves -- i.e., the larger the component of its meaning it will gain through use and repetition and not naming and documentation. I know some on this list might disagree, but all of the above is incredibly non-controversial today. Even if there may have been debates about scoping or naming when QSA was originally designed, history has settled them. And QSA lost on both counts. I therefore believe that this group's current design for scoped selection could be improved significantly. If I understand the latest draft (http://www.w3.org/TR/selectors-api2/#the-scope-pseudo-class) correctly, a scoped search for multiple elements would be written as: element.querySelectorAll(:scope div .thinger); Both then name and the need to specify :scope are punitive to readers and writers of this code. The selector is *obviously* happening in relationship to element somehow. The only sane relationship (from a modern JS hacker's perspective) is that it's where our selector starts from. I'd like to instead propose that we shorten all of this up and kill both stones by introducing a new API pair, find and findAll, that are rooted as JS devs expect. The above becomes: element.findAll( div .thinger); Out come the knives! You can't start a selector with a combinator! Ah, but we don't need to care what CSS thinks of our DOM-only API. We can live and let live by building on :scope and specifying find* as syntactic sugar, defined as: HTMLDocument.prototype.find = HTMLElement.prototype.find = function(rootedSelector) { return this.querySelector(:scope + rootedSelector); } HTMLDocument.prototype.findAll = HTMLElement.prototype.findAll = function(rootedSelector) { return this.querySelectorAll(:scope + rootedSelector); } Of course, :scope in this case is just a special case of the ID rooting hack, but if we're going to have it, we can kill both birds with it. Obvious follow up questions: Q.) Why do we need this at all? Don't the toolkits already just do this internally? A.) Are you saying everyone, everywhere, all the time should need to use a toolkit to get sane behavior from the DOM? If so, what are we doing here, exactly? Q.) Shorter names? Those are for weaklings! A.) And humans. Who still constitute most of our developers. Won't someone please think of the humans? Q.) You're just duplicating things! A.) If you ignore all of the things that are different, then that's true. If not, well, then no. This is a change. And a good one for the reasons listed above. Thoughts? Oh, and as a separate issue. I think .findAll should return a plain old JS Array. Not a NodeList or any other type of host object. I strongly agree that it should be an Array *type*, but I think just returning a plain Array is the wrong resolution to our NodeList problem. WebIDL should specify that DOM List types *are* Array types. It's insane that we even have a NodeList type which isn't a real array at all. Adding a parallel system when we could just fix the one we have (and preserve the value of a separate prototype for extension) is wonky to me. That said, I'd *also* support the ability to have some sort of decorator mechanism before return on .find() or a way to re-route the prototype of the returned Array. +heycam to debate this point. How would this new Array-type be different from an Array? Would it mutable (your answer below seems to indicate 'yes')? Would it allow inserting things that aren't Nodes? One of the use cases is being able to mutate the returned value. This is useful if you're for
Re: QSA, the problem with :scope, and naming
On Thu, Oct 20, 2011 at 7:23 AM, Alex Russell slightly...@google.com wrote: On Thu, Oct 20, 2011 at 3:14 PM, Boris Zbarsky bzbar...@mit.edu wrote: On 10/20/11 7:18 AM, Alex Russell wrote: No we don't. The fact that there's someone else who has a handle to the list and can mutate it underneath you There is no sane way to mutate the list on the part of the browser if someone else is also messing with it, because the someone else can violate basic invariants the browser's behavior needs to maintain. Right. So you need to vend an apparently-immutable Array, one which can only be changed by the browser. I think that could be accomplished in terms of Proxies. But it's still an Array type. unless the argument is that the slots should be non-configurable, non-writable except by the browser that's also holding a ref to it. Yes. Though I don't know what slots you're talking about; the only sane JS implementation of live nodelists is as a proxy. There's no way to get the behaviors that browsers have for them otherwise. But it can be a Proxy to an *Array*, not to some weird non-Array type. Let's do the general discussion about how live and non-live NodeLists should behave in a separate thread. The immediate question here is how should the returned object from .findAll behave? Should it be mutable? Should you be able to insert non-Nodes into it? Should it have all of the functions of Array.prototype or just some subset? Should it have any additional functions? Since .findAll is a new function we have absolutely no constraints as far as how NodeLists behave, we can simply return something that isn't a NodeList. / Jonas
Re: QSA, the problem with :scope, and naming
On Thu, Oct 20, 2011 at 7:23 AM, Boris Zbarsky bzbar...@mit.edu wrote: On 10/20/11 1:08 AM, Tab Atkins Jr. wrote: I disagree. It's extremely useful and natural for .find(:scope + div) to match sibling of the context node. I really don't think it is. If you want that, use document.find(:scope + div, context). Why do that, when the previous one is shorter and simpler, and unambiguous? The whole point is that we *know* the behavior I suggest is well-known and easy to use, because jQuery (and probably other selector engines?) does it already. I know for a fact that I've appreciated that behavior in my own coding. It would have been very annoying to me had I been forced to break my chaining just to select a sibling, when the exact same style works fine to select a child. It's intuitive and useful. The behavior is useful in jQuery because it lets me evaluate a selector, do some work to the matched elements, and then just continue the selector to grab more, regardless of what form the continuation takes. Forcing me to think about the continuation's form (and even worse, completely rearrange the call structure) is just mean. ^_^ Basically, the presence of :scope would turn off *all* the limitations That's a _really_ bizarre behavior. So in this case: foo.find(:scope + div, div) what all divs in the document would be found? Or is the oh, ignore the reference node except for matching :scope meant to only apply on a per-selector basis inside the selector list? That has its own issues, especially with performance (e.g. merging nodesets while preserving DOM order). Per-selector basis; we're not talking about naive string manipulation here. Your example would return divs that are descendants or an adjacent sibling of the scoping element. I don't really see the performance issues. If you use + or ~ off of :scope, you know for a fact that all the nodes come *after* any selectors that don't have :scope. If you use the subject indicator or the reference combinator that's not necessarily true, but those selectors will be slow already. Even then, sorting them into DOM order should be relatively easy: 1. Run the :scope-carrying selectors across the document together, automatically yielding a dom-ordered list. 2. Run the :scope-absent selectors together, automatically yielding a dom-ordered list. 3. Find where the scoping element would be inserted in the #1 list, and insert the entire #2 list there. There's no further interleaving that could cause trouble. ~TJ
Re: QSA, the problem with :scope, and naming
On Thu, Oct 20, 2011 at 12:09 PM, Jonas Sicking jo...@sicking.cc wrote: Let's do the general discussion about how live and non-live NodeLists should behave in a separate thread. Yes, let's. ^_^ The immediate question here is how should the returned object from .findAll behave? Should it be mutable? Should you be able to insert non-Nodes into it? Should it have all of the functions of Array.prototype or just some subset? Should it have any additional functions? Since .findAll is a new function we have absolutely no constraints as far as how NodeLists behave, we can simply return something that isn't a NodeList. It should absolutely have all the Array functions. I know that I want to be able to slice, append, forEach, map, and reduce the list returned by .find. ~TJ
Re: QSA, the problem with :scope, and naming
* Alex Russell wrote: I strongly agree that it should be an Array *type*, but I think just returning a plain Array is the wrong resolution to our NodeList problem. WebIDL should specify that DOM List types *are* Array types. It's insane that we even have a NodeList type which isn't a real array at all. It is quite normal to consider lists and arrays to be different things. In Perl for instance you can use list operations like `grep` on arrays, but you cannot use array operations like `push` on lists. For JavaScript programmers it actually seems common to confuse the two, like with var node_list = document.getElementsByTagName('example'); for (var ix = 0; ix node_list.length; ++ix) node_list[ix].parentNode.removeChild(node_list[ix]); which would remove all the children if node_list was an array like any other. Pretending node lists are arrays in nomenclature would likely add to that. -- Björn Höhrmann · mailto:bjo...@hoehrmann.de · http://bjoern.hoehrmann.de Am Badedeich 7 · Telefon: +49(0)160/4415681 · http://www.bjoernsworld.de 25899 Dagebüll · PGP Pub. KeyID: 0xA4357E78 · http://www.websitedev.de/
Re: QSA, the problem with :scope, and naming
On 10/20/11 4:32 PM, Tab Atkins Jr. wrote: I don't really see the performance issues. If you use + or ~ off of :scope, you know for a fact that all the nodes come *after* any selectors that don't have :scope. Yes. 1. Run the :scope-carrying selectors across the document together, automatically yielding a dom-ordered list. 2. Run the :scope-absent selectors together, automatically yielding a dom-ordered list. 3. Find where the scoping element would be inserted in the #1 list, and insert the entire #2 list there. foo.find(:scope + div, :scope div) begs to differ. So does: foo.find(span :scope ~ div, span :scope div) (which is not quite as trivial to analyze). You could try to look at the combinator following the part(s) that have :scope, but that can get tricky. And worse yet, the current :scope proposals allow an arbitrary nodeset to be specified as matching :scope, at which point this whole thing is out the window. And yes, if you use a subject indicator then performance goes out the window too; you basically have to search the whole DOM. As long as you're ok with searching the whole DOM any time anything funny is happening, of course, there's no other performance issue here. But then I suspect this will be slow to start with -Boris
Re: QSA, the problem with :scope, and naming
On Thu, Oct 20, 2011 at 2:04 PM, Boris Zbarsky bzbar...@mit.edu wrote: On 10/20/11 4:32 PM, Tab Atkins Jr. wrote: 1. Run the :scope-carrying selectors across the document together, automatically yielding a dom-ordered list. 2. Run the :scope-absent selectors together, automatically yielding a dom-ordered list. 3. Find where the scoping element would be inserted in the #1 list, and insert the entire #2 list there. foo.find(:scope + div, :scope div) begs to differ. Well, that would only cause a problem if there were also a :scope-absent selector in the list, like: foo.find(:scope + div, :scope div, span) If they *all* carry :scope, then you can just run them all over the whole tree and get the ordered set in the normal fashion. So does: foo.find(span :scope ~ div, span :scope div) (which is not quite as trivial to analyze). Same here. Yeah, it's possible to make it necessary to interleave the :scope-carrying and :scope-absent sets, if you run the two separately (perhaps because you can optimize the :scope-absent ones to fail when the search would escape the subtree). If you internally modify the :scope-absent selectors to start with :scope and a descendant combinator (in other words, do the add a unique id trick that selector engines do in this situation), and then run it with all the rest, though, that disappears. You might lose some possible optimization (based on knowing you only need to search a subtree), but not necessarily, and you're avoiding a potential slowdown from having to interleave. You could try to look at the combinator following the part(s) that have :scope, but that can get tricky. Yeah, I can see some possibilities, but they're not exhaustive. And worse yet, the current :scope proposals allow an arbitrary nodeset to be specified as matching :scope, at which point this whole thing is out the window. QSA allows that (or plans to?). Alex's find() proposal does not. The scoping element is solely the 'this' in .find. And yes, if you use a subject indicator then performance goes out the window too; you basically have to search the whole DOM. As long as you're ok with searching the whole DOM any time anything funny is happening, of course, there's no other performance issue here. But then I suspect this will be slow to start with It was good enough for jQuery in the pre-QSA days, and it's still good enough for jQuery now when it can't use QSA, I don't see why it's not good enough for the rest of us. We can do at least as good, and probably still better. ~TJ
Re: QSA, the problem with :scope, and naming
On 2011-10-20 22:32, Tab Atkins Jr. wrote: On Thu, Oct 20, 2011 at 7:23 AM, Boris Zbarskybzbar...@mit.edu wrote: On 10/20/11 1:08 AM, Tab Atkins Jr. wrote: Basically, the presence of :scope would turn off *all* the limitations That's a _really_ bizarre behavior. So in this case: foo.find(:scope + div, div) what all divs in the document would be found? Or is the oh, ignore the reference node except for matching :scope meant to only apply on a per-selector basis inside the selector list? That has its own issues, especially with performance (e.g. merging nodesets while preserving DOM order). Per-selector basis; we're not talking about naive string manipulation here. Your example would return divs that are descendants or an adjacent sibling of the scoping element. Not necessarily. It depends what exactly it means for a selector to contain :scope for determining whether or not to enable the implied :scope behaviour. Consider: foo.find(:not(:scope)); If that is deemed to contain :scope and turn off the prepending of scope, making it equivalent to: document.querySelectorAll(:not(:scope), foo); Then it matches every element in the document except the context node. Otherwise, if it we decide that containing :scope means that it contains a :scope selector that is not within a functional notation pseudo-element, then it would prepend :scope, equivalent to: document.querySelectorAll(:scope :not(:scope), foo) Then it matches all descendants of the context element. In the latter case, then it would only ever be possible for matches to be found as descendants, siblings or descendants of siblings of the context element. That would even be true in cases like: foo.find(section:scope+div, div, ~p span, .x :scopeh1+span) With the selector pre-processing, that selector becomes section:scope+div, :scope div, :scope~p span, .x :scopeh1+span -- Lachlan Hunt - Opera Software http://lachy.id.au/ http://www.opera.com/
Re: QSA, the problem with :scope, and naming
On Thu, Oct 20, 2011 at 2:33 PM, Lachlan Hunt lachlan.h...@lachy.id.au wrote: On 2011-10-20 22:32, Tab Atkins Jr. wrote: On Thu, Oct 20, 2011 at 7:23 AM, Boris Zbarskybzbar...@mit.edu wrote: On 10/20/11 1:08 AM, Tab Atkins Jr. wrote: Basically, the presence of :scope would turn off *all* the limitations That's a _really_ bizarre behavior. So in this case: foo.find(:scope + div, div) what all divs in the document would be found? Or is the oh, ignore the reference node except for matching :scope meant to only apply on a per-selector basis inside the selector list? That has its own issues, especially with performance (e.g. merging nodesets while preserving DOM order). Per-selector basis; we're not talking about naive string manipulation here. Your example would return divs that are descendants or an adjacent sibling of the scoping element. Not necessarily. It depends what exactly it means for a selector to contain :scope for determining whether or not to enable the implied :scope behaviour. Consider: foo.find(:not(:scope)); If that is deemed to contain :scope and turn off the prepending of scope, making it equivalent to: document.querySelectorAll(:not(:scope), foo); Then it matches every element in the document except the context node. This seems perfectly fine, since if you just want all the elements *underneath* the scoping element, you can instead do the much simpler: foo.find(*) Otherwise, if it we decide that containing :scope means that it contains a :scope selector that is not within a functional notation pseudo-element, then it would prepend :scope, equivalent to: document.querySelectorAll(:scope :not(:scope), foo) Then it matches all descendants of the context element. This prevents us from doing things like :matches(:scope, #foo), which seems potentially useful. (Plus, :matches(X) should always be equivalent to just X, possibly modulo specificity differences.) In the latter case, then it would only ever be possible for matches to be found as descendants, siblings or descendants of siblings of the context element. That would even be true in cases like: foo.find(section:scope+div, div, ~p span, .x :scopeh1+span) With the selector pre-processing, that selector becomes section:scope+div, :scope div, :scope~p span, .x :scopeh1+span Unless you use the reference combinator or the subject indicator, or something else we come up with in the future that lets us do more complicated searching. ~TJ
Re: QSA, the problem with :scope, and naming
On 10/20/11 5:15 PM, Tab Atkins Jr. wrote: If they *all* carry :scope, then you can just run them all over the whole tree and get the ordered set in the normal fashion. You can just prepend :scope to the ones missing it and run them over the whole tree too. But that means that now you're doing work proportional to the size of your whole DOM, not the subtree rooted at the context element, which is a pretty big difference. And worse yet, the current :scope proposals allow an arbitrary nodeset to be specified as matching :scope, at which point this whole thing is out the window. QSA allows that (or plans to?). Alex's find() proposal does not. The scoping element is solely the 'this' in .find. I was assuming we were discussing find() in preference to the QSA extensions. Maybe I was confused? It was good enough for jQuery in the pre-QSA days, and it's still good enough for jQuery now when it can't use QSA, I don't see why it's not good enough for the rest of us. jQuery takes some shortcuts we can't take (note the getElementById comments elsewhere in this thread). Maybe it'll be ok. Maybe not. I'd rather not paint ourselves into the not corner if we can avoid it -Boris
Re: QSA, the problem with :scope, and naming
On Wed, Oct 19, 2011 at 2:26 AM, Boris Zbarsky bzbar...@mit.edu wrote: On 10/18/11 8:08 PM, Alex Russell wrote: The other excuse is that adding special cases (which is what you're asking for) slows down all the non-special-case codepaths. That may be fine for _your_ usage of querySelectorAll, where you use it with a particular limited set of selectors, but it's not obvious that this is always a win. Most browsers try to optimize what is common. Yes, but what is common for Yehuda may well not be globally common. Yehuda is representing jQuery. I'll take his opinion as the global view unless he choses to say he's representing a personal opinion. There's also the question of premature optimization. Again, I'd love to see a non-synthetic situation where any of this matters. That would be a much more useful point to reason from than some sort of hypothetical faith-based optimization. The jQuery team did look to see what selector are hottest against their engine at some point and explicitly optimize short selectors as a result. The simple forms seem to be the most common. Regards
Re: QSA, the problem with :scope, and naming
On Wed, Oct 19, 2011 at 4:39 AM, Ojan Vafai o...@chromium.org wrote: Overall, I wholeheartedly support the proposal. I don't really see the benefit of allowing starting with a combinator. I think it's a rare case that you actually care about the scope element and in those cases, using :scope is fine. Instead of element.findAll( div .thinger), you use element.findAll(:scope div .thinger). That said, I don't object to considering the :scope implied if the selector starts with a combinator. Right, I think the argument for allowing a combinator start is two-fold: 1.) the libraries allow it, so should DOM 2.) we know the thing on the left, it's the implicit scope. Shorter is better, so allowing the implicitness here is a win on that basis I have a mild preference for argument #2. Shorter, without loss of clarity, for common stuff should nearly always win. On Tue, Oct 18, 2011 at 6:15 PM, Boris Zbarsky bzbar...@mit.edu wrote: On 10/18/11 7:38 PM, Alex Russell wrote: The resolution I think is most natural is to split on , That fails with :any, with the expanded :not syntax, on attr selectors, etc. You can split on ',' while observing proper paren and quote nesting, but that can get pretty complicated. Can we define it as a sequence of selectors and be done with it? That way it can be defined as using the same parsing as CSS. A minor point is how to order the items in the returned flattened list are ordered (document order? the natural result of concat()?). Document order. Definitely. -Boris
Re: QSA, the problem with :scope, and naming
On Wed, 19 Oct 2011 17:22:46 +0900, Alex Russell slightly...@google.com wrote: Yehuda is representing jQuery. I'll take his opinion as the global view unless he choses to say he's representing a personal opinion. You misunderstand. Boris is contrasting with CSS. Selectors are used in more than just querySelectorAll() and their usage differs wildly. -- Anne van Kesteren http://annevankesteren.nl/
Re: QSA, the problem with :scope, and naming
Yehuda Katz (ph) 718.877.1325 On Wed, Oct 19, 2011 at 1:22 AM, Alex Russell slightly...@google.comwrote: On Wed, Oct 19, 2011 at 2:26 AM, Boris Zbarsky bzbar...@mit.edu wrote: On 10/18/11 8:08 PM, Alex Russell wrote: The other excuse is that adding special cases (which is what you're asking for) slows down all the non-special-case codepaths. That may be fine for _your_ usage of querySelectorAll, where you use it with a particular limited set of selectors, but it's not obvious that this is always a win. Most browsers try to optimize what is common. Yes, but what is common for Yehuda may well not be globally common. Yehuda is representing jQuery. I'll take his opinion as the global view unless he choses to say he's representing a personal opinion. Right. I'm representing the position of jQuery. Sizzle (John's selector engine, used by jQuery) chose to optimize certain common selectors after an analysis of selectors used by jQuery found that a large percentage of all selectors used were a few simple forms that were amenable to getElement(s)By* optimizations. I provided a link to the code that implements this earlier in this thread. There's also the question of premature optimization. Again, I'd love to see a non-synthetic situation where any of this matters. That would be a much more useful point to reason from than some sort of hypothetical faith-based optimization. The jQuery team did look to see what selector are hottest against their engine at some point and explicitly optimize short selectors as a result. The simple forms seem to be the most common. Yep. Regards
Re: QSA, the problem with :scope, and naming
On Wed, Oct 19, 2011 at 9:29 AM, Anne van Kesteren ann...@opera.com wrote: On Wed, 19 Oct 2011 17:22:46 +0900, Alex Russell slightly...@google.com wrote: Yehuda is representing jQuery. I'll take his opinion as the global view unless he choses to say he's representing a personal opinion. You misunderstand. Boris is contrasting with CSS. Selectors are used in more than just querySelectorAll() and their usage differs wildly. Sure, of course, but suggesting that the optimizations for both need to be the same is also a strange place to start the discussion from. The QSA or find() implementation *should* differ to the extent that it provides developer value and is a real-world bottleneck.
Re: QSA, the problem with :scope, and naming
On 19/10/11 2:39 PM, Ojan Vafai wrote: Overall, I wholeheartedly support the proposal. I don't really see the benefit of allowing starting with a combinator. I think it's a rare case that you actually care about the scope element and in those cases, using :scope is fine. Instead of element.findAll( div .thinger), you use element.findAll(:scope div .thinger). That said, I don't object to considering the :scope implied if the selector starts with a combinator. I can think of two reasons one might ponder allowing :scope to be explicit. 1. so that the selector string can be a valid CSS selector string. (:scopediv.thinger instead of div.thinger). But if this is important then :scope should always be explicit, in which case we can just use querySelectorAll(). 2. to allow break-out behavior. e.g. div.findAll(body div span); // finds nothing div.findAll(body div:scope span); // finds span's that are descendants of div In this scenario, the :scope pseudo allows ancestors of div to be matched against. (No-one would use body in this context, but it is easy to imagine them using a .class selector which matches an ancestor of div.) But if you want break-out behavior you might not know which part of the selector to put the :scope pseudo on. Could it be: body div:scope span body *:scope div span body div *:scope span So for break-out behavior just use querySelectorAll(). I'm pretty sure previous discussions (before this thread) have covered this more thoroughly, and shown that it has to be all or nothing with the :scope pseudo-attribute. That is, either a) :scope MUST be explicit, in which case just use querySelectorAll() b) :scope MUST be implied at the start of every selector chain. Sean
Re: QSA, the problem with :scope, and naming
On 2011-10-19 01:58, Tab Atkins Jr. wrote: Based on discussion on the mailing list,style scoped will be changing to the latter behavior as well, with the ability to invoke the former behavior in the rare circumstances when you explicitly want it. Despite some similarities in appearance, the proposed changes to style scoped will still use selectors differently from that proposed here for a new findAll() method. 1. Syntax In style scoped, selectors still can't begin with a combinator, but in the proposed API, they can. The @global at-rule was proposed to 2. Matching the Context Element In scoped stylesheets, the context element itself can be the subject of a selector. But the proposed API will never return the element itself in the result. div.findAll(div) // Does not match the element itself (same as querySelectorAll() in this case) div style scoped div { ... } /* Matches the context element */ /style /div 3. The Subject of Selectors In scoped stylesheets, the potential matches of a selector will only include: * The context element itself * Descendants of the context element In the proposed API, the potential matches will include: * Descendants of the context element * Siblings of the context element In the existing API, the potential matches include: * Descendants of the context element only div.findAll(+p) // Matches sibling p elements div.querySelectorAll(:scope+p) // Matches nothing document.querySelectorAll(:scope+p, div) // Matches sibling p elements div style scoped :scope+p { ... } /* Matches nothing */ /style div p.../p -- Lachlan Hunt - Opera Software http://lachy.id.au/ http://www.opera.com/
Re: QSA, the problem with :scope, and naming
On 2011-10-18 18:42, Alex Russell wrote: Related and equally important, that querySelector and querySelectorAll are often referred to by the abbreviation QSA suggests that its name is bloated and improved versions should have shorter names. I know the names suck. The names we ended up with certainly weren't the first choice of names we were going for, but sadly ended up with after a long drawn out naming debate and a misguided consensus poll to override what should have been an editorial decision. So, if we do introduce new methods, personally I'd be happy to use sensible names for any them, if the rest of the group will allow it this time. I therefore believe that this group's current design for scoped selection could be improved significantly. If I understand the latest draft (http://www.w3.org/TR/selectors-api2/#the-scope-pseudo-class) correctly, a scoped search for multiple elements would be written as: element.querySelectorAll(:scope div .thinger); Both then name and the need to specify :scope are punitive to readers and writers of this code. The selector is *obviously* happening in relationship to element somehow. The only sane relationship (from a modern JS hacker's perspective) is that it's where our selector starts from. The current design is capable of handling many more use cases than the single use case that you are trying to optimise for here. Ah, but we don't need to care what CSS thinks of our DOM-only API. We can live and let live by building on :scope and specifying find* as syntactic sugar, defined as: HTMLDocument.prototype.find = HTMLElement.prototype.find = function(rootedSelector) { return this.querySelector(:scope + rootedSelector); } HTMLDocument.prototype.findAll = HTMLElement.prototype.findAll = function(rootedSelector) { return this.querySelectorAll(:scope + rootedSelector); } This is an incomplete way of dealing with the problem, as it doesn't correctly handle comma separated lists of selectors, so the parsing problem cannot be as trivial as prepending :scope . It would also give a strange result if the author passed an empty string findAll(); :scope + = :scope = meaning to return itself. In another email, you wrote: The resolution I think is most natural is to split on , and assume that all selectors in the list are :scope prefixed and that. Simple string processing to split on , is also ineffective as it doesn't correctly deal with commas within functional notation pseudo-classes, attribute selectors, etc. I have attempted to address this problem before and the algorithm for parsing a *scoped selector string* (basically what you're calling a rootedSelector) existed in an old draft [1]. That draft also allowed the flexibility of including an explicit :scope pseudo-class in the selector, which allows for conditional expressions to be built into the selector itself that can be used to check the state of the scope element or any of its ancestors. (But that draft isn't perfect. It has a few known bugs in the definition, including one that would also make it return the context node itself under certain circumstances where an explicit :scope selector is used.) [1] http://dev.w3.org/cvsweb/~checkout~/2006/webapi/selectors-api2/Overview.html?rev=1.29;content-type=text%2Fhtml#processing-selectors -- Lachlan Hunt - Opera Software http://lachy.id.au/ http://www.opera.com/
Re: QSA, the problem with :scope, and naming
On Wed, Oct 19, 2011 at 1:54 PM, Lachlan Hunt lachlan.h...@lachy.id.au wrote: On 2011-10-18 18:42, Alex Russell wrote: Related and equally important, that querySelector and querySelectorAll are often referred to by the abbreviation QSA suggests that its name is bloated and improved versions should have shorter names. I know the names suck. The names we ended up with certainly weren't the first choice of names we were going for, but sadly ended up with after a long drawn out naming debate and a misguided consensus poll to override what should have been an editorial decision. So, if we do introduce new methods, personally I'd be happy to use sensible names for any them, if the rest of the group will allow it this time. It should *still* be an editorial decision. Shorter is better. This is well-trod ground. We have plenty of evidence for what JS devs really want. Lets get on with it. I therefore believe that this group's current design for scoped selection could be improved significantly. If I understand the latest draft (http://www.w3.org/TR/selectors-api2/#the-scope-pseudo-class) correctly, a scoped search for multiple elements would be written as: element.querySelectorAll(:scope div .thinger); Both then name and the need to specify :scope are punitive to readers and writers of this code. The selector is *obviously* happening in relationship to element somehow. The only sane relationship (from a modern JS hacker's perspective) is that it's where our selector starts from. The current design is capable of handling many more use cases than the single use case that you are trying to optimise for here. That's OK. I'm not stoning the current design. See below. I'm suggesting we build on it and provide the API people are making heavy use of today. This cow path deserves not just paving, but streetlights, wide shoulders, and a bike lane. Ah, but we don't need to care what CSS thinks of our DOM-only API. We can live and let live by building on :scope and specifying find* as syntactic sugar, defined as: HTMLDocument.prototype.find = HTMLElement.prototype.find = function(rootedSelector) { return this.querySelector(:scope + rootedSelector); } HTMLDocument.prototype.findAll = HTMLElement.prototype.findAll = function(rootedSelector) { return this.querySelectorAll(:scope + rootedSelector); } This is an incomplete way of dealing with the problem, as it doesn't correctly handle comma separated lists of selectors, so the parsing problem cannot be as trivial as prepending :scope . It would also give a strange result if the author passed an empty string findAll(); :scope + = :scope = meaning to return itself. Yes, yes. Pseudo-code. I snipped other code I posted to not handle obvious corner cases to prevent posting eye-watering walls of code as well. Happy to draft a longer/more-complete straw-man, but nobody's *actually* going to implement it this way in any case. As an aside, it's shocking how nit-picky and anti-collaborative this group is. *sigh* In another email, you wrote: The resolution I think is most natural is to split on , and assume that all selectors in the list are :scope prefixed and that. Simple string processing to split on , is also ineffective as it doesn't correctly deal with commas within functional notation pseudo-classes, attribute selectors, etc. See, again, subsequent follow-ups. I have attempted to address this problem before and the algorithm for parsing a *scoped selector string* (basically what you're calling a rootedSelector) existed in an old draft [1]. That draft also allowed the flexibility of including an explicit :scope pseudo-class in the selector, which allows for conditional expressions to be built into the selector itself that can be used to check the state of the scope element or any of its ancestors. We could accomodate that by looking at the passed selector and trying to determine if it includes a :scope term. If so, avoid prefixing. That'd allow this sort of flexibility for folks who want to write things out long-hand or target the scope root in the selector, possibly returning itself. I''d also support a resolution for this sort of power-tool that forces people to use document.qsa(..., scopeEl) to get at that sort of thing. (But that draft isn't perfect. It has a few known bugs in the definition, including one that would also make it return the context node itself under certain circumstances where an explicit :scope selector is used.) [1] http://dev.w3.org/cvsweb/~checkout~/2006/webapi/selectors-api2/Overview.html?rev=1.29;content-type=text%2Fhtml#processing-selectors
Re: QSA, the problem with :scope, and naming
On 10/19/11 4:22 AM, Alex Russell wrote: Yehuda is representing jQuery. I'll take his opinion as the global view unless he choses to say he's representing a personal opinion. Global jQuery view, yes? I stand by a slightly statement that what is common and needs to be fast for Yehuda may not be common and needing to be fast in general. In particular, lots of jQuery selector usage is not in fact performance-sensitive. Some obviously is. Again, I'd love to see data on the cases where performance matters, both when jQuery is involved and when it's not. I should note that the larger and more complicated a web app the less likely it is to use jQuery from what I've seen I'm absolutely sure that simple selectors dominate complicated ones in all contexts, but again I'd really like to have data on what _sort_ of simple selectors really need optimizing. The jQuery team did look to see what selector are hottest against their engine Yes. See above. -Boris
Re: QSA, the problem with :scope, and naming
On 10/19/11 4:29 AM, Anne van Kesteren wrote: On Wed, 19 Oct 2011 17:22:46 +0900, Alex Russell slightly...@google.com wrote: Yehuda is representing jQuery. I'll take his opinion as the global view unless he choses to say he's representing a personal opinion. You misunderstand. Boris is contrasting with CSS. No, I'm talking purely about querySelector. The fact that at least Gecko and WebKit implement querySelector in a braindead way because that lets them reuse their selector matching code is a somewhat separate kettle of fish. What we're discussing her, in particular, are optimizations that make use of the differences in use case between CSS selector matching (match one node to a bazillion selectors) and querySelector (match one selectors to possibly a bazillion nodes). There are ways to optimize the latter by examining the structure of the selector and making use of existing cached information in the browser that make no sense in the CSS context and would be implemented as a preprocessing pass before falling back on actual selector matching. WebKit does a few of these, of varying utility. I've considered doing some in Gecko, but again want to have hard data that they're actually needed before adding complexity. -Boris
Re: QSA, the problem with :scope, and naming
On 10/19/11 11:17 AM, Boris Zbarsky wrote: 4) Mapping Sizzle(#id) with document a context to getElementById(id). This isn't a valid optimization for querySelector because there can be multiple elements with the same id; And just as a note, since someone asked me off-list how this can possibly be true... Given this markup: div id=x div id=y/div div id=y/div /div calling jQuery.find(#y) returns an array with one element in it while calling jQuery.find(#y, document.getElementById(x)) returns an array with two elements. I have no idea whether this is purposeful behavior or just a bug in Sizzle brought on by the optimization listed above. -Boris
Re: QSA, the problem with :scope, and naming
On 2011-10-19 16:08, Alex Russell wrote: On Wed, Oct 19, 2011 at 1:54 PM, Lachlan Huntlachlan.h...@lachy.id.au wrote: I have attempted to address this problem before and the algorithm for parsing a *scoped selector string* (basically what you're calling a rootedSelector) existed in an old draft [1]. That draft also allowed the flexibility of including an explicit :scope pseudo-class in the selector, which allows for conditional expressions to be built into the selector itself that can be used to check the state of the scope element or any of its ancestors. We could accomodate that by looking at the passed selector and trying to determine if it includes a :scope term. If so, avoid prefixing. Yes, that's exactly what the draft specified. That'd allow this sort of flexibility for folks who want to write things out long-hand or target the scope root in the selector, possibly returning itself. I don't see a use case for wanting the proposed method to be able to return the element itself. The case where it's useful for elements matching :scope to be the subject of a selector is where you're trying to filter a list of elements. e.g. document.querySelectorAll(.foo:scope, list); // Returns all elements from list that match. But this wouldn't make sense el.find(.foo:scope) // Return itself if it matches. That result seems effectively like a less efficient boolean check that is already handled by el.matchesSelector(.foo). I''d also support a resolution for this sort of power-tool that forces people to use document.qsa(...,scopeEl) to get at that sort of thing. If there was no special handling to check for an explicit :scope, that would mean that any selector that does include :scope explicitly would not match anything at all. e.g. el.findAll(:scopep); That would be equivalent to: document.querySelectorAll(:scope :scopep, el); Which won't match anything. That might keep things simpler from an implementation perspective and doesn't sacrifice any functionality being requested. -- Lachlan Hunt - Opera Software http://lachy.id.au/ http://www.opera.com/
Re: QSA, the problem with :scope, and naming
On 2011-10-18 18:42, Alex Russell wrote: HTMLDocument.prototype.find = HTMLElement.prototype.find = function(rootedSelector) { return this.querySelector(:scope + rootedSelector); } HTMLDocument.prototype.findAll = HTMLElement.prototype.findAll = function(rootedSelector) { return this.querySelectorAll(:scope + rootedSelector); } What exactly does it mean to have a rootedSelector applied to the Document object? As I understand it, the scoping problem explained only seems to apply to running the query on elements, whereas the existing document.qsa already behaves as expected by authors. It doesn't seem to make sense to try and prepend :scope to selectors in that case. e.g. document.find(html) shouldn't be equivalent to document.querySelector(:scope html); So, either we introduce the new method only for elements, or we use a similarly named method on document for a similar, but slightly different purpose. A previous use case discussed on this list is the ability to take a collection of elements, and execute the same selector on all all of them, as if iterating the list, collecting the results and returning a single merged collection. The current API handles this use case with document.querySelectorAll, explicitly specifying :scope and passing a collection of refNodes. e.g. var list = ...; // Elements (Array, NodeList or indexed object) // Find the sibling p elements of all elements in the list document.querySelectorAll(:scope+p, list); Thus, if we do introduce the proposed method, should it behave similarly, but with the implied rather than explicit :scope? e.g. document.findAll(+p, list); -- Lachlan Hunt - Opera Software http://lachy.id.au/ http://www.opera.com/