You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Selector :contains within :has seems broken on Chrome #5098
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).
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.
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.
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.
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?
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:
$.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
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.
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).
@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.
Activity
mgol commentedon Aug 31, 2022
Thanks for the report!
The way the jQuery selector engine works, the selector is tried against
querySelectorAllwith minor modifications and if it fails, it goes through the internal selector engine. It's either-or.Chrome 105 doesn't support
:containsas 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
:containspresence 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 commentedon 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 commentedon Aug 31, 2022
Worth noting is that this change makes Chrome fail both the jQuery test suite on

main:and the Sizzle one:

It'd help if Chrome was running its builds against the jQuery test suite periodically...
mgol commentedon 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 commentedon 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
:haswith:containsbut also more complex:has- here, nested with another:has&:not. But we still need to detect:hasin a selector passed.But, of course, older jQuery versions will remain with the broken behavior.
Rinzwind commentedon Aug 31, 2022
Thanks for the explanation! Particularly the point about the use of
querySelectorAllversus the internal selector engine: could I, as a temporary workaround, force jQuery to always use the internal one?mgol commentedon 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
:hasselectors. We're not keen on exposing those primitives as we plan to change how the selector engine works to always depend onqSAand just mix it with jQuery-specific pseudos so in the future there wouldn't be a way to opt out of theqSApath - 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
qSAbefore running the query and then restore it:If you want, you can patch
$.findto always do this if you're not afraid of worse performance or skippingqSAin jQuery via something like:$.findis 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 toSizzlefor 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
qSAmgol commentedon 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 commentedon Aug 31, 2022
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 being0,1,1in Chrome (it’s1,1,1in Safari).mgol commentedon Aug 31, 2022
Right, that's what happens when running untested code. ;)
qSAis run on the context, so in this case you also need to null theElementversion ofqSA:miketaylr commentedon Sep 1, 2022
The difference would be shipping a native
:hasimplementation, right? https://chromestatus.com/feature/5794378545102848@Rinzwind have you filed a bug at crbug.com?
Rinzwind commentedon 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 ofquerySelectorAllto 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 commentedon Sep 1, 2022
@miketaylr Based on this discussion, I filed https://crbug.com/1358953.
63 remaining items
Selector: Make selector lists work with `qSA` again
Selector: Make selector lists work with `qSA` again
Selector: Make selector lists work with `qSA` again
Selector: Make selector lists work with `qSA` again
Selector: Stop relying on CSS.supports( "selector(...)" )
Selector: Stop relying on CSS.supports( "selector(...)" )
Selector: Stop relying on CSS.supports( "selector(...)" )
Selector: Stop relying on CSS.supports( "selector(...)" )
Selector: Stop relying on CSS.supports( "selector(...)" )
Selector: Stop relying on CSS.supports( "selector(...)" )
Selector: Stop relying on CSS.supports( "selector(...)" )
Selector: Stop relying on CSS.supports( "selector(...)" )
Selector: Stop relying on CSS.supports( "selector(...)" )
Selector: Stop relying on CSS.supports( "selector(...)" )