Skip to content

unsupported pseudo: valid with selector list #5177

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

Closed
antti-manninen-vmv opened this issue Dec 16, 2022 · 8 comments · Fixed by #5178 or jquery/sizzle#491
Closed

unsupported pseudo: valid with selector list #5177

antti-manninen-vmv opened this issue Dec 16, 2022 · 8 comments · Fixed by #5178 or jquery/sizzle#491
Assignees
Milestone

Comments

@antti-manninen-vmv
Copy link

antti-manninen-vmv commented Dec 16, 2022

Description

After upgrading to jQuery 3.6.2 pseudo selector :valid does not work in selector list with Firefox and Safari. Everything works with Chrome.

This worked in 3.6.1

jQuery('body').find('div:valid, span')

In 3.6.2 it gives following error:

Uncaught Error: Syntax error, unrecognized expression: unsupported pseudo: valid

This still works with old and new versions with Firefox and Safari:

jQuery('body').find('div:valid')
@mgol
Copy link
Member

mgol commented Dec 17, 2022

Thanks for the report!

jQuery has a mechanism of - simplifying a bit - trying a provided selector against querySelectorAll in a try-catch and, if it fails, falling back to its own traversal mechanism inherited from Sizzle.

In jQuery 3.6.2 we started using CSS.supports( "selector(SELECTOR)" ) (for a proper SELECTOR) before using querySelectorAll on the selector. This was to solve #5098 - some selectors, like :is(), now have their parameters parsed in a forgiving way, meaning that :is(: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. This change has only arrived in Firefox stable so far, though.

Now, apparently, our implementation has a bug - in CSS.supports( "selector(SELECTOR)" ), SELECTOR needs to be a <complex-selector> and not a <complex-selector-list>. Which means:

CSS.supports("selector(div:valid, span)"); // false
CSS.supports("selector(div:valid)"); // true
CSS.supports("selector(span)"); // true

and explains your observations. See the live test case: https://jsbin.com/vesureh/1/edit?html,js,console

Now, I only see this behavior in Firefox as that's the only browser passing our support test. In which version of Safari are you also seeing it? Please share a test case that breaks there.

I see two ways of fixing this:

  1. Use CSS.supports() earlier, when we still have the selector split into groups.
  2. Use CSS.supports( "selector(:is(SELECTOR))" ) instead of CSS.supports( "selector(SELECTOR)" )

The second solution definitely only makes sense only in browsers that implement non-forgiving parsing in CSS.supports( "selector(...)" ) but we're already limiting our usage to such browsers so we should be fine with it, I think.

I'm tagging it for 3.6.3, we'll discuss this on our next team meeting.

@mgol mgol self-assigned this Dec 17, 2022
@mgol mgol added Selector Discuss in Meeting Reserved for Issues and PRs that anyone would like to discuss in the weekly meeting. labels Dec 17, 2022
@mgol mgol added this to the 3.6.3 milestone Dec 17, 2022
@mgol
Copy link
Member

mgol commented Dec 17, 2022

BTW, in many cases, the only consequence of this bug would be performance issues as more selectors go through the jQuery selector engines instead of querySelectorAll. This one surfaced because jQuery doesn't support the :valid pseudo-class so such a selector can only work if the qSA code path is used.

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
Copy link
Member

mgol commented Dec 17, 2022

PRs:

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
@antti-manninen-vmv
Copy link
Author

My mistake, Safari does not have this problem. I only saw it with Firefox.

Thanks for quick handling of the problem!

@timmywil timmywil removed the Discuss in Meeting Reserved for Issues and PRs that anyone would like to discuss in the weekly meeting. label Dec 19, 2022
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
Copy link
Member

mgol commented Dec 19, 2022

The fix landed on main (future jQuery 4.0.0) in 09d988b and on 3.x-stable (future jQuery 3.7.0) in 848de62.

It also landed in Sizzle in jquery/sizzle@7781b65. Then, Sizzle got updated on 3.6-stable (future jQuery 3.6.3) in 8989500.

The fix & all the backports are done.

@mgol
Copy link
Member

mgol commented Dec 20, 2022

@antti-manninen-vmv jQuery 3.6.3 containing the fix for this issue has been released: https://blog.jquery.com/2022/12/20/jquery-3-6-3-released-a-quick-selector-fix/. That's the only source difference from 3.6.2.

Thanks for reporting it!

@khetaramsb
Copy link

After upgrading jQuery from 3.6.0 to3.6.3 pseudo selector :before does not work in selector list with Firefox. Everything works with Chrome.

This worked in 3.6.0
jQuery(".inline-dialog-content:before").css({'left': 40});

In 3.6.3 it gives following error:

Uncaught Error: Syntax error, unrecognized expression: unsupported pseudo: before

@mgol
Copy link
Member

mgol commented Jan 13, 2023

@khetaramsb let's move the discussion to #5194.

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