jquery: Selector `:contains` within `:has` seems broken on Chrome

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>

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 9
  • Comments: 31 (18 by maintainers)

Commits related to this issue

Most upvoted comments

How serious and widespread is this issue?

Just adding another +1 to this issue. I think this is pretty widespread, especially in testing tools that are relying on jQuery under the hood… e.g Cypress.

How serious and widespread is this issue?

Just adding another +1 to this issue. I think this is pretty widespread, especially in testing tools that are relying on jQuery under the hood… e.g Cypress.

pretty widespread is a understatement it hits all jquery version from the last 9 years with :has in the selector thats to complex 😃

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()")

This workaround is perfect for the moment. It completly resolved all my issues, and it also produces the same output on 104 chromium versions (105 was the first version where this bug came up), so it’s backwards compatible in a way.

Thank you!

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)")

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

@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.