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

Selector :contains within :has seems broken on Chrome #5098

Closed
Rinzwind opened this issue Aug 31, 2022 · 31 comments · Fixed by #5107 or jquery/sizzle#486
Closed

Selector :contains within :has seems broken on Chrome #5098

Rinzwind opened this issue Aug 31, 2022 · 31 comments · Fixed by #5107 or jquery/sizzle#486
Assignees
Milestone

Comments

@Rinzwind
Copy link

Using the following example, the console shows 1,1,1,1 on Safari (15.6.1) and Firefox (104.0.1), but 1,1,0,0 on Chrome (105.0.5195.52). I would expect the result to also be 1,1,1,1 on Chrome. Based on our usage in Selenium, I think the result actually was also 1,1,1,1 on previous versions of Chrome (≤ 104).

<html>
<head>
	<script src="https://code.jquery.com/jquery-3.6.1.min.js"></script>
</head>
<body>
	<ul>
		<li>Item</li>
	</ul>
	<script>
		console.log("" + [
			$("li:contains('Item')").length,
			$("ul:has(li)").length,
			$("ul:has(li:contains('Item'))").length,
			$("ul:has(> li:contains('Item'))").length ]);
	</script>
</body>
</html>
@mgol
Copy link
Member

mgol commented Aug 31, 2022

Thanks for the report!

The way the jQuery selector engine works, the selector is tried against querySelectorAll with minor modifications and if it fails, it goes through the internal selector engine. It's either-or.

Chrome 105 doesn't support :contains as it's a non-native selector but it does support :has. I'd expect that to still fail the internal check but, surprisingly, document.querySelectorAll("ul:has(li:contains('Item'))") no longer throws an error in Chrome 105. It looks like it must be doing some lax internal parsing?

This, indeed, sounds problematic. I wonder if we should detect :contains presence in the selector and default to the internal jQuery engine in such a case? An interesting issue.

@miketaylr @paulirish could you have a look? This change in Chrome 105 has potential compatibility issues.

@mgol mgol added Selector Discuss in Meeting Reserved for Issues and PRs that anyone would like to discuss in the weekly meeting. labels Aug 31, 2022
@mgol
Copy link
Member

mgol commented Aug 31, 2022

cc @gibson042 for ideas on how to potentially handle it on jQuery side. That won't resolve compatibility issues in jQuery versions in the wild, though.

@mgol
Copy link
Member

mgol commented Aug 31, 2022

Worth noting is that this change makes Chrome fail both the jQuery test suite on main:
Screen Shot 2022-08-31 at 11 48 42

and the Sizzle one:
Screen Shot 2022-08-31 at 11 48 52

It'd help if Chrome was running its builds against the jQuery test suite periodically...

@mgol
Copy link
Member

mgol commented Aug 31, 2022

For reference, the jQuery failure can be reporduced just by opening the following URL in Chrome 105:
https://swarm.jquery.org/builds/jquery/016872ffe03ab9107b1bc62fae674a4809c3b23f/test/index.html?module=selector

@mgol
Copy link
Member

mgol commented Aug 31, 2022

I guess we should just be able to run exactly the failing tests above as support tests to account for the difference in the selector engine. Worth noting is that it doesn't only affect :has with :contains but also more complex :has - here, nested with another :has & :not. But we still need to detect :has in a selector passed.

But, of course, older jQuery versions will remain with the broken behavior.

@Rinzwind
Copy link
Author

Thanks for the explanation! Particularly the point about the use of querySelectorAll versus the internal selector engine: could I, as a temporary workaround, force jQuery to always use the internal one?

@mgol
Copy link
Member

mgol commented Aug 31, 2022

The primitives keeping buggy selectors are not exposed so there's no official way to defer to the internal engine just for problematic :has selectors. We're not keen on exposing those primitives as we plan to change how the selector engine works to always depend on qSA and just mix it with jQuery-specific pseudos so in the future there wouldn't be a way to opt out of the qSA path - but, at the same time, it's more likely that mixed native+jQuery selectors will then work.

For now, the best hack that comes to my mind is to null qSA before running the query and then restore it:

let origQSA = document.querySelectorAll;
document.querySelectorAll = null;
let result = $("ul:has(li:contains('Item'))");
document.querySelectorAll = origQSA;

If you want, you can patch $.find to always do this if you're not afraid of worse performance or skipping qSA in jQuery via something like:

let origDocumentQSA = document.querySelectorAll;
let origElementQSA = Element.prototype.querySelectorAll;
let origFind = $.find;
$.find = function () {
    document.querySelectorAll = null;
    Element.prototype.querySelectorAll = null;
    let result = origFind.apply(this, arguments);
    document.querySelectorAll = origDocumentQSA;
    Element.prototype.querySelectorAll = origElementQSA;
    return result;
};

$.find is used by jQuery internally for selection so this will also handle your use case of $("ul:has(li:contains('Item'))") or similar. The API is undocumented but it's been assigned to Sizzle for ages (and now, in the 4.x line, its fork) so it will at least work in jQuery 3.x & 4.x.

EDIT: updated the workaround to account for element qSA

@mgol
Copy link
Member

mgol commented Aug 31, 2022

Remember that the Sizzle doesn't implement the CSS Selector level 4 APIs (see jquery/sizzle#237) so this workaround may break some of your selectors if you depend on them.

@Rinzwind
Copy link
Author

Ok thanks for the tip! I tried nulling querySelectorAll, but I’m a bit confused at the moment about the result in the following example being 0,1,1 in Chrome (it’s 1,1,1 in Safari).

<html>
<head>
	<script src="https://code.jquery.com/jquery-3.6.1.min.js"></script>
</head>
<body>
	<ul>
		<li>Item</li>
	</ul>
	<script>
		document.querySelectorAll = null;
		var selector = "ul:has(> li:contains('Item'))";
		console.log("" + [
			$("body").find(selector).length,
			$(selector).length,
			$("body").find(selector).length ]);
	</script>
</body>
</html>

@mgol
Copy link
Member

mgol commented Aug 31, 2022

Right, that's what happens when running untested code. ;) qSA is run on the context, so in this case you also need to null the Element version of qSA:

document.querySelectorAll = null;
Element.prototype.querySelectorAll = null;

@miketaylr
Copy link

Based on our usage in Selenium, I think the result actually was also 1,1,1,1 on previous versions of Chrome (≤ 104).

The difference would be shipping a native :has implementation, right? https://chromestatus.com/feature/5794378545102848

@Rinzwind have you filed a bug at crbug.com?

@Rinzwind
Copy link
Author

Rinzwind commented Sep 1, 2022

@miketaylr no I haven’t also filed a bug there.

@mgol Thanks again! Didn’t think of the fact there’s more than one querySelectorAll. We use Selenium (through Parasol) for our tests. Various tests use a variation of #findElementByCSSSelector: which allows passing a jQuery selector. I’ve applied the temporary nulling of querySelectorAll to that method, making those tests pass again, while also giving some confidence that only the jQuery selectors used in the tests are affected by this issue but not those in the application itself.

@mgol
Copy link
Member

mgol commented Sep 1, 2022

@miketaylr Based on this discussion, I filed https://crbug.com/1358953.

@lilles
Copy link

lilles commented Sep 2, 2022

Safari doesn't implement forgiving selector list inside :has() according to spec which explains the difference between Chrome and Safari. I have reported https://bugs.webkit.org/show_bug.cgi?id=244708

@lilles
Copy link

lilles commented Sep 2, 2022

How serious and widespread is this issue?

@mgol
Copy link
Member

mgol commented Sep 2, 2022

@lilles This will break every jQuery usage with a :has selector that has anything jQuery-specific in it, returning 0 results instead of what was intended.

I have no idea how widespread such usage is. But jQuery has supported :has and this specific behavior for querySelectorAll since jQuery 1.3 released in 2009. So there are a lot of versions affected.

@mgol
Copy link
Member

mgol commented Sep 2, 2022

The Chromium issue was very quickly fixed as Won't fix so I opened a spec one instead: w3c/csswg-drafts#7676

I understand if the decision is going to be "no changes in the spec are planned" but it'd be good to have this discussion and the decision made knowing the consequences.

@lilles
Copy link

lilles commented Sep 2, 2022

Ok. I've re-opened the Chrome issue.

@lilles
Copy link

lilles commented Sep 2, 2022

Is :is() a thing in jquery? I assume not, since shipping :is(), also taking a forgiving list, did not break jQuery?

@mgol
Copy link
Member

mgol commented Sep 2, 2022

No, jQuery has a the .is() method but AFAIK it has never had an :is pseudo.

@jehhynes
Copy link

jehhynes commented Sep 2, 2022

If anyone needs a quick workaround for a particular selector, you can add a jQuery-only selector outside the :has which would cause a fallback to the jQuery implementation. So for instance, change:

$("#orders tr:has(:checkbox)")

to

$("#orders:not(:password) tr:has(:checkbox)")

@mgol
Copy link
Member

mgol commented Sep 2, 2022

To add to what @jehhynes wrote, you can also add an empty :contains() at the end of your selector, that should always match and it will also crash in qSA, falling back to the jQuery engine. For the example from this bug report, instead of:

$("ul:has(li:contains('Item'))")

you can use:

$("ul:has(li:contains('Item')):contains()")

@mgol mgol self-assigned this Sep 5, 2022
@mgol mgol added this to the 3.6.2 milestone Sep 5, 2022
mgol added a commit to mgol/jquery that referenced this issue Sep 5, 2022
jQuery has followed the following logic for selector handling for ages:
1. Modify the selector to adhere to scoping rules jQuery mandates.
2. Try `qSA` on the modified selector. If it succeeds, use the results.
3. If `qSA` threw an error, run the jQuery custom traversal instead.

It worked fine so far but now CSS has a concept of forgiving selector lists that
some selectors like `:is()` & `:has()` use. That means providing unrecognized
selectors as parameters to `:is()` & `:has()` no longer throws an error, it will
just return no results. That made browsers with native `:has()` support break
selectors using jQuery extensions inside, e.g. `:has(:contains("Item"))`.

Detecting support for selectors can also be done via:

```js
CSS.supports( "selector(SELECTOR_TO_BE_TESTED)" )
```
which returns a boolean. There was a recent spec change requiring this API to
always use non-forgiving parsing:
w3c/csswg-drafts#7280 (comment)
However, no browsers have implemented this change so far.

To solve this, two changes are being made:
1. In browsers supports the new spec change to `CSS.supports( "selector()" )`,
   use it before trying `qSA`.
2. Otherwise, add `:has` to the buggy selectors list.

Fixes jquerygh-5098
@mgol
Copy link
Member

mgol commented Sep 5, 2022

PR: #5107

mgol added a commit to mgol/jquery that referenced this issue Dec 1, 2022
jQuery has followed the following logic for selector handling for ages:
1. Modify the selector to adhere to scoping rules jQuery mandates.
2. Try `qSA` on the modified selector. If it succeeds, use the results.
3. If `qSA` threw an error, run the jQuery custom traversal instead.

It worked fine so far but now CSS has a concept of forgiving selector lists that
some selectors like `:is()` & `:has()` use. That means providing unrecognized
selectors as parameters to `:is()` & `:has()` no longer throws an error, it will
just return no results. That made browsers with native `:has()` support break
selectors using jQuery extensions inside, e.g. `:has(:contains("Item"))`.

Detecting support for selectors can also be done via:

```js
CSS.supports( "selector(SELECTOR_TO_BE_TESTED)" )
```
which returns a boolean. There was a recent spec change requiring this API to
always use non-forgiving parsing:
w3c/csswg-drafts#7280 (comment)
However, no browsers have implemented this change so far.

To solve this, two changes are being made:
1. In browsers supports the new spec change to `CSS.supports( "selector()" )`,
   use it before trying `qSA`.
2. Otherwise, add `:has` to the buggy selectors list.

Ref jquerygh-5098
Ref jquerygh-5107
Ref jquery/sizzle#486
Ref w3c/csswg-drafts#7676
mgol added a commit to mgol/jquery that referenced this issue Dec 13, 2022
jQuery has followed the following logic for selector handling for ages:
1. Modify the selector to adhere to scoping rules jQuery mandates.
2. Try `qSA` on the modified selector. If it succeeds, use the results.
3. If `qSA` threw an error, run the jQuery custom traversal instead.

It worked fine so far but now CSS has a concept of forgiving selector lists that
some selectors like `:is()` & `:has()` use. That means providing unrecognized
selectors as parameters to `:is()` & `:has()` no longer throws an error, it will
just return no results. That made browsers with native `:has()` support break
selectors using jQuery extensions inside, e.g. `:has(:contains("Item"))`.

Detecting support for selectors can also be done via:

```js
CSS.supports( "selector(SELECTOR_TO_BE_TESTED)" )
```
which returns a boolean. There was a recent spec change requiring this API to
always use non-forgiving parsing:
w3c/csswg-drafts#7280 (comment)
However, no browsers have implemented this change so far.

To solve this, two changes are being made:
1. In browsers supports the new spec change to `CSS.supports( "selector()" )`,
   use it before trying `qSA`.
2. Otherwise, add `:has` to the buggy selectors list.

Ref jquerygh-5098
Ref jquerygh-5107
Ref jquery/sizzle#486
Ref w3c/csswg-drafts#7676
mgol added a commit to mgol/jquery that referenced this issue Dec 17, 2022
jQuery 3.6.2 started using `CSS.supports( "selector(SELECTOR)" )` before using
`querySelectorAll` on the selector. This was to solve jquerygh-5098 - some selectors,
like `:has()`, now have their parameters parsed in a forgiving way, meaning
that `:has(:fakepseudo)` no longer throws but just returns 0 results, breaking
that jQuery mechanism.

A recent spec change]() made `CSS.supports( "selector(SELECTOR)" )` always use
non-forgiving parsing, allowing us to use this API for what we've used
`try-catch` before.

To solve the issue on the spec side for older jQuery versions, `:has()`
parameters are no longer using forgiving parsing in the latest spec update
but our new mechanism is more future-proof anyway.

However, the jQuery implementation has a bug - in
`CSS.supports( "selector(SELECTOR)" )`, `SELECTOR` needs to be
a `<complex-selector>` and not a `<complex-selector-list>`. Which means that
selector lists now skip `qSA` and go to the jQuery custom traversal:
```js
CSS.supports("selector(div:valid, span)"); // false
CSS.supports("selector(div:valid)"); // true
CSS.supports("selector(span)"); // true
```

To solve this, this commit wraps the selector list passed to
`CSS.supports( "selector(:is(SELECTOR))" )` with `:is`, making it a single
selector again.

See:
* https://w3c.github.io/csswg-drafts/css-conditional-4/#at-supports-ext
* https://w3c.github.io/csswg-drafts/selectors-4/#typedef-complex-selector
* https://w3c.github.io/csswg-drafts/selectors-4/#typedef-complex-selector-list

Fixes jquerygh-5177
Ref w3c/csswg-drafts#7280
mgol added a commit to mgol/jquery that referenced this issue Dec 17, 2022
jQuery 3.6.2 started using `CSS.supports( "selector(SELECTOR)" )` before using
`querySelectorAll` on the selector. This was to solve jquerygh-5098 - some selectors,
like `:has()`, now have their parameters parsed in a forgiving way, meaning
that `:has(:fakepseudo)` no longer throws but just returns 0 results, breaking
that jQuery mechanism.

A recent spec change]() made `CSS.supports( "selector(SELECTOR)" )` always use
non-forgiving parsing, allowing us to use this API for what we've used
`try-catch` before.

To solve the issue on the spec side for older jQuery versions, `:has()`
parameters are no longer using forgiving parsing in the latest spec update
but our new mechanism is more future-proof anyway.

However, the jQuery implementation has a bug - in
`CSS.supports( "selector(SELECTOR)" )`, `SELECTOR` needs to be
a `<complex-selector>` and not a `<complex-selector-list>`. Which means that
selector lists now skip `qSA` and go to the jQuery custom traversal:
```js
CSS.supports("selector(div:valid, span)"); // false
CSS.supports("selector(div:valid)"); // true
CSS.supports("selector(span)"); // true
```

To solve this, this commit wraps the selector list passed to
`CSS.supports( "selector(:is(SELECTOR))" )` with `:is`, making it a single
selector again.

See:
* https://w3c.github.io/csswg-drafts/css-conditional-4/#at-supports-ext
* https://w3c.github.io/csswg-drafts/selectors-4/#typedef-complex-selector
* https://w3c.github.io/csswg-drafts/selectors-4/#typedef-complex-selector-list

Fixes jquerygh-5177
Ref w3c/csswg-drafts#7280
mgol added a commit to mgol/jquery that referenced this issue Dec 17, 2022
jQuery 3.6.2 started using `CSS.supports( "selector(SELECTOR)" )` before using
`querySelectorAll` on the selector. This was to solve jquerygh-5098 - some selectors,
like `:has()`, now have their parameters parsed in a forgiving way, meaning
that `:has(:fakepseudo)` no longer throws but just returns 0 results, breaking
that jQuery mechanism.

A recent spec change]() made `CSS.supports( "selector(SELECTOR)" )` always use
non-forgiving parsing, allowing us to use this API for what we've used
`try-catch` before.

To solve the issue on the spec side for older jQuery versions, `:has()`
parameters are no longer using forgiving parsing in the latest spec update
but our new mechanism is more future-proof anyway.

However, the jQuery implementation has a bug - in
`CSS.supports( "selector(SELECTOR)" )`, `SELECTOR` needs to be
a `<complex-selector>` and not a `<complex-selector-list>`. Which means that
selector lists now skip `qSA` and go to the jQuery custom traversal:
```js
CSS.supports("selector(div:valid, span)"); // false
CSS.supports("selector(div:valid)"); // true
CSS.supports("selector(span)"); // true
```

To solve this, this commit wraps the selector list passed to
`CSS.supports( "selector(:is(SELECTOR))" )` with `:is`, making it a single
selector again.

See:
* https://w3c.github.io/csswg-drafts/css-conditional-4/#at-supports-ext
* https://w3c.github.io/csswg-drafts/selectors-4/#typedef-complex-selector
* https://w3c.github.io/csswg-drafts/selectors-4/#typedef-complex-selector-list

Fixes jquerygh-5177
Ref w3c/csswg-drafts#7280
mgol added a commit to mgol/jquery that referenced this issue Dec 17, 2022
jQuery 3.6.2 started using `CSS.supports( "selector(SELECTOR)" )` before using
`querySelectorAll` on the selector. This was to solve jquerygh-5098 - some selectors,
like `:has()`, now had their parameters parsed in a forgiving way, meaning
that `:has(:fakepseudo)` no longer throws but just returns 0 results, breaking
that jQuery mechanism.

A recent spec change made `CSS.supports( "selector(SELECTOR)" )` always use
non-forgiving parsing, allowing us to use this API for what we've used
`try-catch` before.

To solve the issue on the spec side for older jQuery versions, `:has()`
parameters are no longer using forgiving parsing in the latest spec update
but our new mechanism is more future-proof anyway.

However, the jQuery implementation has a bug - in
`CSS.supports( "selector(SELECTOR)" )`, `SELECTOR` needs to be
a `<complex-selector>` and not a `<complex-selector-list>`. Which means that
selector lists now skip `qSA` and go to the jQuery custom traversal:
```js
CSS.supports("selector(div:valid, span)"); // false
CSS.supports("selector(div:valid)"); // true
CSS.supports("selector(span)"); // true
```

To solve this, this commit wraps the selector list passed to
`CSS.supports( "selector(:is(SELECTOR))" )` with `:is`, making it a single
selector again.

See:
* https://w3c.github.io/csswg-drafts/css-conditional-4/#at-supports-ext
* https://w3c.github.io/csswg-drafts/selectors-4/#typedef-complex-selector
* https://w3c.github.io/csswg-drafts/selectors-4/#typedef-complex-selector-list

Fixes jquerygh-5177
Ref w3c/csswg-drafts#7280
mgol added a commit to mgol/sizzle that referenced this issue Dec 17, 2022
Sizzle 2.3.7 started using `CSS.supports( "selector(SELECTOR)" )` before using
`querySelectorAll` on the selector. This was to solve jquery/jquery#5098 -
some selectors, like `:has()`, now had their parameters parsed in a forgiving
way, meaning that `:has(:fakepseudo)` no longer throws but just returns
0 results, breaking that Sizzle mechanism.

A recent spec change made `CSS.supports( "selector(SELECTOR)" )` always use
non-forgiving parsing, allowing us to use this API for what we've used
`try-catch` before.

To solve the issue on the spec side for older jQuery versions, `:has()`
parameters are no longer using forgiving parsing in the latest spec update
but our new mechanism is more future-proof anyway.

However, the Sizzle implementation has a bug - in
`CSS.supports( "selector(SELECTOR)" )`, `SELECTOR` needs to be
a `<complex-selector>` and not a `<complex-selector-list>`. Which means that
selector lists now skip `qSA` and go to the Sizzle custom traversal:
```js
CSS.supports("selector(div:valid, span)"); // false
CSS.supports("selector(div:valid)"); // true
CSS.supports("selector(span)"); // true
```

To solve this, this commit wraps the selector list passed to
`CSS.supports( "selector(:is(SELECTOR))" )` with `:is`, making it a single
selector again.

See:
* https://w3c.github.io/csswg-drafts/css-conditional-4/#at-supports-ext
* https://w3c.github.io/csswg-drafts/selectors-4/#typedef-complex-selector
* https://w3c.github.io/csswg-drafts/selectors-4/#typedef-complex-selector-list

Fixes jquery/jquery#5177
Ref w3c/csswg-drafts#7280
mgol added a commit to mgol/sizzle that referenced this issue Dec 17, 2022
Sizzle 2.3.7 started using `CSS.supports( "selector(SELECTOR)" )` before using
`querySelectorAll` on the selector. This was to solve jquery/jquery#5098 -
some selectors, like `:has()`, now had their parameters parsed in a forgiving
way, meaning that `:has(:fakepseudo)` no longer throws but just returns
0 results, breaking that Sizzle mechanism.

A recent spec change made `CSS.supports( "selector(SELECTOR)" )` always use
non-forgiving parsing, allowing us to use this API for what we've used
`try-catch` before.

To solve the issue on the spec side for older jQuery versions, `:has()`
parameters are no longer using forgiving parsing in the latest spec update
but our new mechanism is more future-proof anyway.

However, the Sizzle implementation has a bug - in
`CSS.supports( "selector(SELECTOR)" )`, `SELECTOR` needs to be
a `<complex-selector>` and not a `<complex-selector-list>`. Which means that
selector lists now skip `qSA` and go to the Sizzle custom traversal:
```js
CSS.supports("selector(div:valid, span)"); // false
CSS.supports("selector(div:valid)"); // true
CSS.supports("selector(span)"); // true
```

To solve this, this commit wraps the selector list passed to
`CSS.supports( "selector(:is(SELECTOR))" )` with `:is`, making it a single
selector again.

See:
* https://w3c.github.io/csswg-drafts/css-conditional-4/#at-supports-ext
* https://w3c.github.io/csswg-drafts/selectors-4/#typedef-complex-selector
* https://w3c.github.io/csswg-drafts/selectors-4/#typedef-complex-selector-list

Fixes jquery/jquery#5177
Ref w3c/csswg-drafts#7280
mgol added a commit to mgol/sizzle that referenced this issue Dec 17, 2022
Sizzle 2.3.7 started using `CSS.supports( "selector(SELECTOR)" )` before using
`querySelectorAll` on the selector. This was to solve jquery/jquery#5098 -
some selectors, like `:has()`, now had their parameters parsed in a forgiving
way, meaning that `:has(:fakepseudo)` no longer throws but just returns
0 results, breaking that Sizzle mechanism.

A recent spec change made `CSS.supports( "selector(SELECTOR)" )` always use
non-forgiving parsing, allowing us to use this API for what we've used
`try-catch` before.

To solve the issue on the spec side for older jQuery versions, `:has()`
parameters are no longer using forgiving parsing in the latest spec update
but our new mechanism is more future-proof anyway.

However, the Sizzle implementation has a bug - in
`CSS.supports( "selector(SELECTOR)" )`, `SELECTOR` needs to be
a `<complex-selector>` and not a `<complex-selector-list>`. Which means that
selector lists now skip `qSA` and go to the Sizzle custom traversal:
```js
CSS.supports("selector(div:valid, span)"); // false
CSS.supports("selector(div:valid)"); // true
CSS.supports("selector(span)"); // true
```

To solve this, this commit wraps the selector list passed to
`CSS.supports( "selector(:is(SELECTOR))" )` with `:is`, making it a single
selector again.

See:
* https://w3c.github.io/csswg-drafts/css-conditional-4/#at-supports-ext
* https://w3c.github.io/csswg-drafts/selectors-4/#typedef-complex-selector
* https://w3c.github.io/csswg-drafts/selectors-4/#typedef-complex-selector-list

Fixes jquery/jquery#5177
Ref w3c/csswg-drafts#7280
mgol added a commit to jquery/sizzle that referenced this issue Dec 19, 2022
Sizzle 2.3.7 started using `CSS.supports( "selector(SELECTOR)" )` before using
`querySelectorAll` on the selector. This was to solve jquery/jquery#5098 -
some selectors, like `:has()`, now had their parameters parsed in a forgiving
way, meaning that `:has(:fakepseudo)` no longer throws but just returns
0 results, breaking that Sizzle mechanism.

A recent spec change made `CSS.supports( "selector(SELECTOR)" )` always use
non-forgiving parsing, allowing us to use this API for what we've used
`try-catch` before.

To solve the issue on the spec side for older jQuery versions, `:has()`
parameters are no longer using forgiving parsing in the latest spec update
but our new mechanism is more future-proof anyway.

However, the Sizzle implementation has a bug - in
`CSS.supports( "selector(SELECTOR)" )`, `SELECTOR` needs to be
a `<complex-selector>` and not a `<complex-selector-list>`. Which means that
selector lists now skip `qSA` and go to the Sizzle custom traversal:
```js
CSS.supports("selector(div:valid, span)"); // false
CSS.supports("selector(div:valid)"); // true
CSS.supports("selector(span)"); // true
```

To solve this, this commit wraps the selector list passed to
`CSS.supports( "selector(:is(SELECTOR))" )` with `:is`, making it a single
selector again.

See:
* https://w3c.github.io/csswg-drafts/css-conditional-4/#at-supports-ext
* https://w3c.github.io/csswg-drafts/selectors-4/#typedef-complex-selector
* https://w3c.github.io/csswg-drafts/selectors-4/#typedef-complex-selector-list

Fixes jquery/jquery#5177
Closes gh-491
Ref w3c/csswg-drafts#7280
mgol added a commit that referenced this issue Dec 19, 2022
jQuery 3.6.2 started using `CSS.supports( "selector(SELECTOR)" )` before using
`querySelectorAll` on the selector. This was to solve gh-5098 - some selectors,
like `:has()`, now had their parameters parsed in a forgiving way, meaning
that `:has(:fakepseudo)` no longer throws but just returns 0 results, breaking
that jQuery mechanism.

A recent spec change made `CSS.supports( "selector(SELECTOR)" )` always use
non-forgiving parsing, allowing us to use this API for what we've used
`try-catch` before.

To solve the issue on the spec side for older jQuery versions, `:has()`
parameters are no longer using forgiving parsing in the latest spec update
but our new mechanism is more future-proof anyway.

However, the jQuery implementation has a bug - in
`CSS.supports( "selector(SELECTOR)" )`, `SELECTOR` needs to be
a `<complex-selector>` and not a `<complex-selector-list>`. Which means that
selector lists now skip `qSA` and go to the jQuery custom traversal:
```js
CSS.supports("selector(div:valid, span)"); // false
CSS.supports("selector(div:valid)"); // true
CSS.supports("selector(span)"); // true
```

To solve this, this commit wraps the selector list passed to
`CSS.supports( "selector(:is(SELECTOR))" )` with `:is`, making it a single
selector again.

See:
* https://w3c.github.io/csswg-drafts/css-conditional-4/#at-supports-ext
* https://w3c.github.io/csswg-drafts/selectors-4/#typedef-complex-selector
* https://w3c.github.io/csswg-drafts/selectors-4/#typedef-complex-selector-list

Fixes gh-5177
Closes gh-5178
Ref w3c/csswg-drafts#7280
mgol added a commit that referenced this issue Dec 19, 2022
jQuery 3.6.2 started using `CSS.supports( "selector(SELECTOR)" )` before using
`querySelectorAll` on the selector. This was to solve gh-5098 - some selectors,
like `:has()`, now had their parameters parsed in a forgiving way, meaning
that `:has(:fakepseudo)` no longer throws but just returns 0 results, breaking
that jQuery mechanism.

A recent spec change made `CSS.supports( "selector(SELECTOR)" )` always use
non-forgiving parsing, allowing us to use this API for what we've used
`try-catch` before.

To solve the issue on the spec side for older jQuery versions, `:has()`
parameters are no longer using forgiving parsing in the latest spec update
but our new mechanism is more future-proof anyway.

However, the jQuery implementation has a bug - in
`CSS.supports( "selector(SELECTOR)" )`, `SELECTOR` needs to be
a `<complex-selector>` and not a `<complex-selector-list>`. Which means that
selector lists now skip `qSA` and go to the jQuery custom traversal:
```js
CSS.supports("selector(div:valid, span)"); // false
CSS.supports("selector(div:valid)"); // true
CSS.supports("selector(span)"); // true
```

To solve this, this commit wraps the selector list passed to
`CSS.supports( "selector(:is(SELECTOR))" )` with `:is`, making it a single
selector again.

See:
* https://w3c.github.io/csswg-drafts/css-conditional-4/#at-supports-ext
* https://w3c.github.io/csswg-drafts/selectors-4/#typedef-complex-selector
* https://w3c.github.io/csswg-drafts/selectors-4/#typedef-complex-selector-list

Fixes gh-5177
Closes gh-5178
Ref w3c/csswg-drafts#7280

(cherry picked from commit 09d988b)
mgol added a commit to mgol/jquery that referenced this issue Feb 9, 2023
`CSS.supports( "selector(...)" )` has different semantics than selectors passed
to `querySelectorAll`. Apart from the fact that the former returns `false` for
unrecognized selectors and the latter throws, `qSA` is more forgiving and
accepts some invalid selectors, auto-correcting them where needed - for
example, mismatched brackers are auto-closed. This behavior difference is
breaking for many users.

To add to that, a recent CSSWG resolution made `:is()` & `:where()` the only
pseudos with forgiving parsing; browsers are in the process of making `:has()`
parsing unforgiving.

Taking all that into account, we go back to our previous try-catch approach
without relying on `CSS.supports( "selector(...)" )`. The only difference
is we detect forgiving parsing in `:has()` and mark the selector as buggy.

The PR also updates `playwright-webkit` so that we test against a version
of WebKit that already has non-forgiving `:has()`.

Fixes jquerygh-5194
Ref jquerygh-5098
Ref jquerygh-5107
Ref w3c/csswg-drafts#7676
mgol added a commit to mgol/jquery that referenced this issue Feb 9, 2023
`CSS.supports( "selector(...)" )` has different semantics than selectors passed
to `querySelectorAll`. Apart from the fact that the former returns `false` for
unrecognized selectors and the latter throws, `qSA` is more forgiving and
accepts some invalid selectors, auto-correcting them where needed - for
example, mismatched brackers are auto-closed. This behavior difference is
breaking for many users.

To add to that, a recent CSSWG resolution made `:is()` & `:where()` the only
pseudos with forgiving parsing; browsers are in the process of making `:has()`
parsing unforgiving.

Taking all that into account, we go back to our previous try-catch approach
without relying on `CSS.supports( "selector(...)" )`. The only difference
is we detect forgiving parsing in `:has()` and mark the selector as buggy.

The PR also updates `playwright-webkit` so that we test against a version
of WebKit that already has non-forgiving `:has()`.

Fixes jquerygh-5194
Ref jquerygh-5098
Ref jquerygh-5107
Ref w3c/csswg-drafts#7676
mgol added a commit to mgol/jquery that referenced this issue Feb 9, 2023
`CSS.supports( "selector(...)" )` has different semantics than selectors passed
to `querySelectorAll`. Apart from the fact that the former returns `false` for
unrecognized selectors and the latter throws, `qSA` is more forgiving and
accepts some invalid selectors, auto-correcting them where needed - for
example, mismatched brackers are auto-closed. This behavior difference is
breaking for many users.

To add to that, a recent CSSWG resolution made `:is()` & `:where()` the only
pseudos with forgiving parsing; browsers are in the process of making `:has()`
parsing unforgiving.

Taking all that into account, we go back to our previous try-catch approach
without relying on `CSS.supports( "selector(...)" )`. The only difference
is we detect forgiving parsing in `:has()` and mark the selector as buggy.

The PR also updates `playwright-webkit` so that we test against a version
of WebKit that already has non-forgiving `:has()`.

Fixes jquerygh-5194
Ref jquerygh-5098
Ref jquerygh-5107
Ref w3c/csswg-drafts#7676
mgol added a commit to mgol/sizzle that referenced this issue Feb 10, 2023
`CSS.supports( "selector(...)" )` has different semantics than selectors passed
to `querySelectorAll`. Apart from the fact that the former returns `false` for
unrecognized selectors and the latter throws, `qSA` is more forgiving and
accepts some invalid selectors, auto-correcting them where needed - for
example, mismatched brackers are auto-closed. This behavior difference is
breaking for many users.

To add to that, a recent CSSWG resolution made `:is()` & `:where()` the only
pseudos with forgiving parsing; browsers are in the process of making `:has()`
parsing unforgiving.

Taking all that into account, we go back to our previous try-catch approach
without relying on `CSS.supports( "selector(...)" )`. The only difference
is we detect forgiving parsing in `:has()` and mark the selector as buggy.

Fixes jquery/jquery#5194
Ref jquery/jquery#5098
Ref jquerygh-486
Ref w3c/csswg-drafts#7676
mgol added a commit to mgol/jquery that referenced this issue Feb 13, 2023
`CSS.supports( "selector(...)" )` has different semantics than selectors passed
to `querySelectorAll`. Apart from the fact that the former returns `false` for
unrecognized selectors and the latter throws, `qSA` is more forgiving and
accepts some invalid selectors, auto-correcting them where needed - for
example, mismatched brackers are auto-closed. This behavior difference is
breaking for many users.

To add to that, a recent CSSWG resolution made `:is()` & `:where()` the only
pseudos with forgiving parsing; browsers are in the process of making `:has()`
parsing unforgiving.

Taking all that into account, we go back to our previous try-catch approach
without relying on `CSS.supports( "selector(...)" )`. The only difference
is we detect forgiving parsing in `:has()` and mark the selector as buggy.

The PR also updates `playwright-webkit` so that we test against a version
of WebKit that already has non-forgiving `:has()`.

Fixes jquerygh-5194
Ref jquerygh-5098
Ref jquerygh-5107
Ref w3c/csswg-drafts#7676
mgol added a commit to mgol/jquery that referenced this issue Feb 13, 2023
`CSS.supports( "selector(...)" )` has different semantics than selectors passed
to `querySelectorAll`. Apart from the fact that the former returns `false` for
unrecognized selectors and the latter throws, `qSA` is more forgiving and
accepts some invalid selectors, auto-correcting them where needed - for
example, mismatched brackers are auto-closed. This behavior difference is
breaking for many users.

To add to that, a recent CSSWG resolution made `:is()` & `:where()` the only
pseudos with forgiving parsing; browsers are in the process of making `:has()`
parsing unforgiving.

Taking all that into account, we go back to our previous try-catch approach
without relying on `CSS.supports( "selector(...)" )`. The only difference
is we detect forgiving parsing in `:has()` and mark the selector as buggy.

The PR also updates `playwright-webkit` so that we test against a version
of WebKit that already has non-forgiving `:has()`.

Fixes jquerygh-5194
Ref jquerygh-5098
Ref jquerygh-5107
Ref w3c/csswg-drafts#7676
mgol added a commit to mgol/sizzle that referenced this issue Feb 13, 2023
`CSS.supports( "selector(...)" )` has different semantics than selectors passed
to `querySelectorAll`. Apart from the fact that the former returns `false` for
unrecognized selectors and the latter throws, `qSA` is more forgiving and
accepts some invalid selectors, auto-correcting them where needed - for
example, mismatched brackers are auto-closed. This behavior difference is
breaking for many users.

To add to that, a recent CSSWG resolution made `:is()` & `:where()` the only
pseudos with forgiving parsing; browsers are in the process of making `:has()`
parsing unforgiving.

Taking all that into account, we go back to our previous try-catch approach
without relying on `CSS.supports( "selector(...)" )`. The only difference
is we detect forgiving parsing in `:has()` and mark the selector as buggy.

Fixes jquery/jquery#5194
Ref jquery/jquery#5098
Ref jquerygh-486
Ref w3c/csswg-drafts#7676
mgol added a commit that referenced this issue Feb 14, 2023
`CSS.supports( "selector(...)" )` has different semantics than selectors passed
to `querySelectorAll`. Apart from the fact that the former returns `false` for
unrecognized selectors and the latter throws, `qSA` is more forgiving and
accepts some invalid selectors, auto-correcting them where needed - for
example, mismatched brackers are auto-closed. This behavior difference is
breaking for many users.

To add to that, a recent CSSWG resolution made `:is()` & `:where()` the only
pseudos with forgiving parsing; browsers are in the process of making `:has()`
parsing unforgiving.

Taking all that into account, we go back to our previous try-catch approach
without relying on `CSS.supports( "selector(...)" )`. The only difference
is we detect forgiving parsing in `:has()` and mark the selector as buggy.

The PR also updates `playwright-webkit` so that we test against a version
of WebKit that already has non-forgiving `:has()`.

Fixes gh-5194
Closes gh-5206
Ref gh-5098
Ref gh-5107
Ref w3c/csswg-drafts#7676

Co-authored-by: Richard Gibson <richard.gibson@gmail.com>
mgol added a commit that referenced this issue Feb 14, 2023
`CSS.supports( "selector(...)" )` has different semantics than selectors passed
to `querySelectorAll`. Apart from the fact that the former returns `false` for
unrecognized selectors and the latter throws, `qSA` is more forgiving and
accepts some invalid selectors, auto-correcting them where needed - for
example, mismatched brackers are auto-closed. This behavior difference is
breaking for many users.

To add to that, a recent CSSWG resolution made `:is()` & `:where()` the only
pseudos with forgiving parsing; browsers are in the process of making `:has()`
parsing unforgiving.

Taking all that into account, we go back to our previous try-catch approach
without relying on `CSS.supports( "selector(...)" )`. The only difference
is we detect forgiving parsing in `:has()` and mark the selector as buggy.

The PR also updates `playwright-webkit` so that we test against a version
of WebKit that already has non-forgiving `:has()`.

Fixes gh-5194
Closes gh-5207
Ref gh-5206
Ref gh-5098
Ref gh-5107
Ref w3c/csswg-drafts#7676
mgol added a commit to jquery/sizzle that referenced this issue Feb 14, 2023
`CSS.supports( "selector(...)" )` has different semantics than selectors passed
to `querySelectorAll`. Apart from the fact that the former returns `false` for
unrecognized selectors and the latter throws, `qSA` is more forgiving and
accepts some invalid selectors, auto-correcting them where needed - for
example, mismatched brackers are auto-closed. This behavior difference is
breaking for many users.

To add to that, a recent CSSWG resolution made `:is()` & `:where()` the only
pseudos with forgiving parsing; browsers are in the process of making `:has()`
parsing unforgiving.

Taking all that into account, we go back to our previous try-catch approach
without relying on `CSS.supports( "selector(...)" )`. The only difference
is we detect forgiving parsing in `:has()` and mark the selector as buggy.

Fixes jquery/jquery#5194
Closes gh-493
Ref jquery/jquery#5098
Ref jquery/jquery#5206
Ref jquery/jquery#5207
Ref gh-486
Ref w3c/csswg-drafts#7676
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment