Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not throw for query() and queryAll() #39

Closed
appsforartists opened this issue Jun 2, 2015 · 35 comments
Closed

Do not throw for query() and queryAll() #39

appsforartists opened this issue Jun 2, 2015 · 35 comments

Comments

@appsforartists
Copy link

As the Web evolves, new selectors will be invented. (The CSS4 wiki contains 14 selector proposals.) As that happens, there will be selectors that are understood by some browsers but not yet others.

The current spec commands that browsers throw a SyntaxError when they don't understand a selector. Unless there is an actual syntax error (e.g. a mismatched paren), this should be handled more gracefully. Otherwise, because errors halt JS execution, any site that adopts a new selector is going to break in old browsers unless the developer knew and remembered to wrap the statement in a try/catch.

This makes code both harder to read and harder to write without bugs. For instance, what should be this simple:

var autofilled = node.matches(":autofill, :-webkit-autofill, :-moz-autofill");

becomes this mess:

var autofilled = false;

try {
  autofilled = node.matches(":autofill");

} catch (error) {                                      
  try {
    autofilled = node.matches(":-webkit-autofill");

  } catch (error) {                                      
    try {
      autofilled = node.matches(":-moz-autofill");

    } catch (error) {}
  }
}  

under the current spec.

The simplest solution would be to change the errors to warnings. This would be a welcome change, though it would make it harder to programmatically distinguish between unmatched and unsupported selectors. Perhaps a navigator.supportsSelector API should be added to make this differentiation easier.

Whatever the solution, throwing an error on an unsupported selector is hostile to the future of the web. It should be fixed.

@appsforartists
Copy link
Author

@bzbarsky
Copy link

bzbarsky commented Jun 2, 2015

How do you envision jQuery's $(":visible") working with this change? Or are you assuming that all sites across the web will update to a newer jQuery version before the change you propose is made in browsers?

@appsforartists
Copy link
Author

Though admittedly this isn't an exhaustive search, I'm having a hard time finding a popular JS library that relies on selectors throwing an error to implement their custom functionality.

For instance, jQuery and MooTools both return from within the try if matches returns a value and try their own implementation if it doesn't. (The catch block is empty.) Moreover, jQuery's the only one I found in my quick search that implements non-DOM selectors.

As with any proposed change, there is a compatibility risk to assess, but at first blush, I don't see any so large that this isn't worth discussing.

@bzbarsky
Copy link

bzbarsky commented Jun 2, 2015

If I look at the currently-most-recent jQuery 2 version (code at http://code.jquery.com/jquery-2.1.4.js) then on line 841 there is a querySelectorAll call. If that succeeds, the result is returned. If it throws, the code falls through to the custom select call at line 855.

Similarly the most recent jQuery 1 version at http://code.jquery.com/jquery-1.11.3.js has the same sort of setup, on lines 879 and 893 respectively.

Looking at the file you linked to for jQuery, https://github.com/jquery/sizzle/blob/a7020477b0433ac08d66baf9eebb3f9d24868e21/src/sizzle.js#L308 has the corresponding call. If that succeeds, then results is returned, which will always be truthy (it's an empty array, worst-case, which is still truthy) so the code you quoted will go ahead and return the empty array.

As for mootools, the matching function you point to would probably still work, yes (it's using matchesSelector, which doesn't even exist, not querySelector), but the code at https://github.com/mootools/slick/blob/f9503a24e69e442fa926e34ff85d1cdb874b97bd/Source/Slick.Finder.js#L381-387 wouldn't.

@annevk
Copy link
Member

annevk commented Jun 9, 2015

Yeah I don't think we can change this. We could change it for the new methods though...

@bzbarsky
Copy link

bzbarsky commented Jun 9, 2015

For the new methods not throwing on parse error might be pretty nice. As long as we also provide an explicit "is this selector supported?" API. And perhaps a general parsed-selector API...

@annevk
Copy link
Member

annevk commented Jun 9, 2015

Yeah, I think I discussed a Selector object with @tabatkins at some point. I guess the question is if the performance is really that great and worth the extra complexity?

@bzbarsky
Copy link

bzbarsky commented Jun 9, 2015

We could do performance measurements.

The performance improvement from using a pre-parsed selector is quite noticeable in real-world cases. So browsers have internal parsed-selector caches, possibly with some sort of eviction heuristics, for the existing querySelectorAll methods.

Exposing a Selector would mostly just allow avoiding an extra hashtable lookup in this cache in the "test whether it's supported, then if it is query" use case. So yeah, maybe not that useful.

@annevk
Copy link
Member

annevk commented Jun 9, 2015

Yeah, I think that's the conclusion we ended up with last time around, since UAs already have that hashtable for strings anyway.

@appsforartists
Copy link
Author

Avoiding the landmine that is trying a new selector in an old browser sounds useful to me.

@annevk
Copy link
Member

annevk commented Jul 28, 2015

@tabatkins and @foolip, would appreciate your input here.

@foolip
Copy link
Member

foolip commented Jul 28, 2015

I don't have much of an opinion on the API ergonomics of this. The biggest issue indeed would be code that depends on the exception being thrown, which normally isn't on the radar, but in this case looks like it is.

We could measure how often the exception is thrown in Blink, but given how widely used the API itself is I'd be somewhat surprised if it's negligible.

I'm not a big fan of making the prefixed and unprefixed APIs different, there's so much code assuming that they're equivalent, a difference here might be annoying to debug.

Perhaps an API that returns null if the selector is supported and something non-null that can be passed to matches((DOMString or SomethingNotNull) selectors) would solve the problem? The overhead of making that SomethingNotNull be accessible from scripts might not be negligible though, it's not a given that it would be an improvement at least.

@annevk
Copy link
Member

annevk commented Jul 28, 2015

@foolip note that this would only affect the new APIs that are not implemented yet. query() and queryAll(). And if we did that we'd need some way to find out if a selector were supported. Perhaps on the CSS global somewhere.

@foolip
Copy link
Member

foolip commented Jul 28, 2015

Oh, I thought matches() was still considered "new" :)

@tabatkins
Copy link
Contributor

I'm somewhat in favor of a Selector object, but if we're avoiding that, then having query() and queryAll() just not throw would be fine.

Re: a way to find if a selector is supported, I like foolip's idea - a CSS.cleanSelector() that returns the selector (with error clauses dropped) or null (if all comma-separated clauses were dropped). Then maybe we can alter matches(), qS(), and qSA() to accept a DOMString? and just return false/an empty nodelist when passed a null, so you could write el.matches(CSS.cleanSelector("foo")) and it'll just work, without having to gate it behind an if statement.

@annevk
Copy link
Member

annevk commented Jul 30, 2015

  1. I think we're avoiding a Selector object because implementations intern the string version anyway, so there's not really anything meaningful to be gained.
  2. Is null better than the empty string? Also, we could update matches(), qS(), and qSA(), but I doubt the user agent will know when they're passed a "clean selector" so you'll still have the perf-cost from them potentially throwing. But it might be nice for authors to recognize null/the empty string there.
  3. q() and qA() can of course do the right thing either way with support for a "clean selector".

@tabatkins
Copy link
Contributor

Ah, yeah, empty string is better than null.

@annevk
Copy link
Member

annevk commented Aug 3, 2015

Also, did error handling change? ::invalid, body{background:red} still results in no background in Gecko. Which at least used to be correct.

@annevk annevk changed the title matches, querySelector, etc. shouldn't throw on an unrecognized selector Do not throw for query() and queryAll() Nov 12, 2015
@annevk annevk added the later label Nov 12, 2015
@rsp
Copy link

rsp commented Jul 7, 2016

This issue is still open but I don't see query() or queryAll() in the current dom.html. Was it removed?

@foolip
Copy link
Member

foolip commented Jul 7, 2016

They were commented out in 10b6cf1.

@rsp
Copy link

rsp commented Jul 7, 2016

@foolip thanks. Any chances this is coming back or is it not likely? (the only info I see is this comment). I'd like to update my answer on Stack Overflow on the difference between queryAll and querySelectorAll. If there is any information on how likely is queryAll to come back to the spec, I'd like to add it to that answer. Thanks.

@foolip
Copy link
Member

foolip commented Jul 7, 2016

I suppose that implementer interest is needed. If the only difference between queryAll and querySelectorAll were support for relative selectors, then it probably wouldn't be very hard to do, but the whole Elements extends Array bit is presumably the hard part.

Also, one answer to the Stack Overflow question says that "queryAll() returns a live Elements[] array", but is that really true, @annevk?

@annevk
Copy link
Member

annevk commented Jul 7, 2016

Yes, we are waiting for subclassing support for built-ins in browsers and in IDL. Elements cannot be live as it is an Array subclass and we wouldn't mutate an Array on the fly, that'd be a little out there.

@foolip
Copy link
Member

foolip commented Jul 7, 2016

Indeed, anything live is unlikely to get implemented ever I think :)

@rsp
Copy link

rsp commented Jul 7, 2016

@foolip @annevk OK, I'll write on Stack Overflow that it's unlikely to get implemented. Thanks for the info.

@annevk
Copy link
Member

annevk commented Jul 7, 2016

That's the wrong conclusion. It's just taking a little longer as JavaScript subclassing of built-ins isn't baked yet.

@rsp
Copy link

rsp commented Jul 7, 2016

I finally wrote that query and queryAll will not be available until the JavaScript subclassing of built-ins is implemented in the browsers and even then it will be unlikely to return a live Elements[] array. (I also changed all of the w3.org links to whatwg.org as pointed out by @domenic). I hope it is fairly accurate now. Thanks for all the feedback.

@sindresorhus
Copy link

sindresorhus commented Jan 13, 2018

@annevk Now that subclassing of built-ins is supported in most browsers, are there any plans to bring query and queryAll back?

@annevk
Copy link
Member

annevk commented Jan 13, 2018

I think the main thing we're still waiting for is IDL support. There's whatwg/webidl#345 though that got hijacked a bit by a different issue.

@bzbarsky @domenic @tobie might know more.

@domenic
Copy link
Member

domenic commented Jan 22, 2018

I don't know if IDL support is the real blocker. We could make it work, if there were implementer interest. I'm just not sure this better-ergonomics API is as high a priority for all browsers, sad to say :(. I'd welcome being corrected.

@annevk
Copy link
Member

annevk commented Mar 13, 2018

I realized this issue is actually invalid as filed. The example given

var autofilled = node.matches(":autofill, :-webkit-autofill, :-moz-autofill");

never matches anything due to how selector parsing works. If a given selector in the comma-separated list is invalid, it's always a non-match. That's also true when you use such a selector in CSS.

Combined with the seeming non-interest in query/queryAll, let's close this.

@annevk annevk closed this as completed Mar 13, 2018
annevk added a commit that referenced this issue Mar 13, 2018
See #39 for some additional context.
@bzbarsky
Copy link

If a given selector in the comma-separated list is invalid, it's always a non-match.

If a given selector in CSS is invalid the entire rule is thrown out.

That said, we could define different behavior from selectors API things, assuming the selector parsing spec gave us a way to parse a comma-separated selector list while only throwing out parts of the list. Or is the point that it doesn't?

@annevk
Copy link
Member

annevk commented Mar 13, 2018

Yeah, it doesn't.

@tabatkins
Copy link
Contributor

I'd be fine with it doing something different. The Selectors error-recovery behavior is considered a legacy mistake; if we were writing it today we'd split on top-level commas and throw out only the entries we didn't recognize. querySelector() (or a future version) could do the same.

@annevk
Copy link
Member

annevk commented Mar 14, 2018

Okay, worth keeping in mind I suppose if we ever add new methods.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

8 participants