Re: [selectors-api] Consider backporting find() behavior to querySelector()

On 2012-06-18 16:45, Simon Pieters wrote:
> So http://dev.w3.org/2006/webapi/selectors-api2/ introduces the methods
> find() and findAll() in addition to querySelector() and
> querySelectorAll() and changes the scoping behavior for the former
> methods to match what people expect them to do.

The find methods introduce a number of new behaviours that are not 
present in qSA. Some of these features could be introduced into qSA in a 
backwards compatible way, but some features could only be partially 
supported.  The following is an objective look at your proposal, 
detailing what can and cannot be back-ported to the older qSA methods.

(Note: All rationale below stated to apply to documents also applies 
equally to document fragments.)


1. Support for relative selector syntax.

This features implies the :scope pseudo-class when the selector begins 
with a combinator.

   elm.find(">span") ==> elm.querySelector(":scope>span")

This feature also applies to document.find() and there is no 
compatibility problems with adding support for this to document.qSA. 
However, it is only useful when refNodes are supplied (see #3 below).

There is no compatibility problems with adding support for this to 
element.qSA, however it would have limited utility.

* It cannot work when it begins with a descendant combinator as this
   case is indistinguishable from using no combinator.  The reason this
   actually works for element.find is because of feature #3 below.

* It can never match sibling elements of the context object (see #4
   below), so element.qSA("+span") cannot match anything even if :scope
   is implied.

* The reference combinator will only be effective when the subject of
   the selector is a descendant of the context object.
   label.qSA("/for/ input") would only be useful when input is a
   descendant of the label.

* This would be effective for the child combinator.

In summary:

   elm.querySelector(" span") // Not possible to imply :scope.
   elm.querySelector("+span") // Sibling. Not possible to match.
   elm.querySelector("~span") // Sibling. Not possible to match.
   elm.querySelector("/for/ input") // Limited utility.
   elm.querySelector(">span") // Could work.


2. Support for reference nodes.

This feature allows a parameter to specify a set of elements that will 
be matched by :scope.  This does not apply to Element.find.

   var ref = [...] // Collection of elements.
   document.findAll(":scope>span", ref)

This was supported on qSA in previous drafts before the new find/findAll 
methods were added.  It was only removed from qSA because it duplicated 
the functionality of the new methods with no additional benefit.  There 
is no compatibility problems with adding support for this to document.qSA.


3. Implied :scope when a reference element is present.

Like the relative selector syntax, this is another trigger for implying 
:scope.  This applies when a non-null refNodes parameter is passed to 
the document.find methods.  The context object is used when it is an 
Element node, so :scope is always implied in that case.

   elm.find("div p")
   document.find("div p", elm)

Both of those are equivalent to using explicit :scope in:

   elm.querySelector(":scope div p")

This feature cannot be supported by Element.qSA because implying :scope 
would break the following case where the div is not a descendant of the 
context object (elm).

   elm.querySelector("div p")

This cannot be equivalent to the examples above without breaking 
compatibility.

There is no compatibility problems with adding support for this to 
document.qSA.


4. Support for returning elements that are not descendants of the
    context object.

This feature allows a selector to be constructed such that it matches an 
element anywhere in the tree relative to the context element. This 
feature is not relevant to document.find(), since it can already return 
anything from the whole tree.

   elm.find("+span")        // span is a sibling
   elm.find("/for/ input")  // input could be anywhere
   elm.find(":not(:scope)") // Everything except the context object

This feature cannot be supported on Element.qSA, even when using eplicit 
:scope, because matching elements need to be descendants of the context 
object.


5. Return type of the findAll method

Although not yet decided, there have been concerns raised about the 
NodeList return type of the querySelectorAll methods.  There has been 
suggestions that findAll instead return either an Array or an Array-like 
object that implements more useful functionality than NodeList currently 
does.  JS libraries tend to use the Array.slice() technique to convert 
node lists to arrays, which they can deal with more effectively.

It's not yet clear if this issue can be addressed by extending the 
current NodeList interface or using an alternative return type.

> I'm not convinced that doubling the API surface is a good idea. If we
> were to do that every time we find that a shipped API has suboptimal
> behavior,

We could reduce the API surface area slightly by applying all 
functionality of document.find to the document.qSA methods and either 
eliminating document.find entirely, or making them aliases.  This, 
however, would eliminate the possibility of using an alternative return 
type for the find methods (see #5 above).  This would not be a problem 
if NodeList could be usefully extended.

Doing this alone, however, would also make document.qSA inconsistent 
with element.qSA in relation to selector syntax. This inconsistency 
could be addressed by opting to support the relative selector syntax in 
element.qSA anyway despite its limited utility.

This would still leave some important functionality missing from the API 
(#3 and #4), which is provided for by the new element.find methods and 
would fail to address the serious concerns raised by web developers 
regarding the terrible method names.

-- 
Lachlan Hunt - Opera Software
http://lachy.id.au/
http://www.opera.com/

Received on Wednesday, 20 June 2012 14:15:00 UTC