Skip to main content

Bug Tracker

Side navigation

#11115 closed bug (fixed)

Opened December 29, 2011 10:26PM UTC

Closed December 16, 2012 04:20AM UTC

".is()" and ".filter()" disagree on attribute selector "[checked]"

Reported by: Pointy Owned by: gibson042
Priority: blocker Milestone: 1.9
Component: selector Version: 1.7.1
Keywords: 1.9-discuss Cc:
Blocked by: Blocking:
Description

The selector "[checked]" is treated differently by ".is()" and ".filter()", in a strange and mysterious way when the "checked" property (not attribute) is set; that is, on checkbox inputs that in the DOM lack the "checked" attribute but which have been checked by user action (or possibly by explicit setting of the "checked" property). If you've got a jQuery object matching one DOM node (and just one), then ".is()" and ".filter()" are in agreement. That is, if a checkbox is checked only after the DOM is built, then both ".is()" and ".filter()" agree that the selector "[checked]" should not match the node.

However, when the jQuery object selects more than one checkbox, then the behavior of ".filter()" changes. Though each checkbox, if tested individually with ".is('[checked]')" would appear to not match, a call to ".filter('checked')" will nevertheless include them in the result if they've been dynamically updated.

Here is a jsfiddle to explore the issue:

http://jsfiddle.net/mgfxe/2/

Now I realize that "[checked]" is not really correct or useful or anything, but the behavior does seem inconsistent; I would imagine that any node that makes it through a selector-based ".filter()" should be guaranteed to pass a ".is()" test for the same selector. The funny thing is that the implementation boils down to calls to "Sizzle.matches()" or "Sizzle.matchesSelector()" (I think) and both of those are nearly identical.

Attachments (0)
Change History (18)

Changed December 30, 2011 02:46AM UTC by dcherman comment:1

_comment0: Here's my take: \ \ In browsers that support it, Sizzle.matchesSelector is redefined to \ \ html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; \ \ When one of the checkboxes is run through elem.webkitMatchesSelector( "checked" ), the result is false. \ \ \ When the code is run through Sizzle's ATTR filter, it hits https://github.com/jquery/sizzle/blob/423f35af8bf43f3f07bb17284e21608f08372c22/sizzle.js#L860-861 which is actually checking the property, so it returns true. \ \ If your jQuery object has more than one element, it'll follow the "normal" Sizzle path which happens to check the elements' property. With a single element, it follows the .matchesSelector path which has been shown to return false. \ \ Bit longwinded, but that was interesting :) \ \ A simple workaround might be to use the :checked pseudo selector which always checks the property rather than the attribute.1325213251318101

Here's my take:

In browsers that support it, Sizzle.matchesSelector is redefined to

html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;

When one of the checkboxes is run through elem.webkitMatchesSelector( "[checked]" ), the result is false.

When the code is run through Sizzle's ATTR filter, it hits https://github.com/jquery/sizzle/blob/423f35af8bf43f3f07bb17284e21608f08372c22/sizzle.js#L860-861 which is actually checking the property, so it returns true.

If your jQuery object has more than one element, it'll follow the "normal" Sizzle path which happens to check the elements' property. With a single element, it follows the .matchesSelector path which has been shown to return false.

Bit longwinded, but that was interesting :)

A simple workaround might be to use the :checked pseudo selector which always checks the property rather than the attribute.

Changed December 30, 2011 03:08AM UTC by Pointy comment:2

Totally agree that this is an academic issue, but it's pretty hard to think of it as anything but incorrect behavior.

Changed December 30, 2011 03:41PM UTC by timmywil comment:3

component: unfiledselector
priority: undecidedlow
status: newopen

Removing the boolhook would fix this issue, but I don't think we can remove it for a while. Nevertheless, the ideal way to check if a checkbox is checked in a selector is with http://api.jquery.com/checked-selector/.

Changed April 28, 2012 02:51PM UTC by dmethvin comment:4

#11667 is a duplicate of this ticket.

Changed September 17, 2012 05:50PM UTC by gibson042 comment:5

keywords: → 1.9-discuss

Changed September 24, 2012 05:06PM UTC by dmethvin comment:6

So are we going to attempt to remove boolHook in 1.9? If not I think we have to assume boolHook will be there forever and this will need to be fixed some other way or closed wontfix.

Changed September 27, 2012 12:08PM UTC by gibson042 comment:7

boolHook has been on notice since 1.6, right? I think it's time.

Changed October 26, 2012 01:21PM UTC by mikesherov comment:8

+1, I'm not for removing the boolHook, but I am for making is and filter consistent.

Changed October 29, 2012 01:35PM UTC by timmywil comment:9

#12795 is a duplicate of this ticket.

Changed October 29, 2012 05:27PM UTC by mikesherov comment:10

milestone: None1.9

Changed November 19, 2012 05:28PM UTC by timmywil comment:11

owner: → timmywil
status: openassigned

Changed November 21, 2012 08:54PM UTC by gibson042 comment:12

#12830 is a duplicate of this ticket.

Changed November 21, 2012 11:56PM UTC by Allen Schmidt <cobrasoft@gmail.com> comment:13

I did some investigating, and I'm guessing this is an issue with querySelectorAll(). It seems that "[selected]" and "[checked]" return elements that initially had this attribute on page load. This is only an issue when used like "option[selected]" since jQuery selects that using querySelectorAll() when available. When using filter("[selected]"), it is not using querySelectorAll(), and therefore returns the correct results.

Changed November 22, 2012 04:44AM UTC by gibson042 comment:14

Replying to [comment:13 Allen Schmidt <cobrasoft@…>]:

I did some investigating, and I'm guessing this is an issue with querySelectorAll(). It seems that "[selected]" and "[checked]" return elements that initially had this attribute on page load. This is only an issue when used like "option[selected]" since jQuery selects that using querySelectorAll() when available. When using filter("[selected]"), it is not using querySelectorAll(), and therefore returns the correct results.

You've got it exactly backwards... [selected] is an ''attribute'' selector, and jQuery incorrectly treats elements having true analogous ''properties'' as matching it, while querySelectorAll correctly ignores them.

Changed November 29, 2012 05:57AM UTC by gibson042 comment:15

_comment0: (In #12600) The expectations in the comments of that fiddle are erroneous... `select[value=…]` shouldn't match anything, because most select elements—and those in particular—don't have attributes named "value". \ \ This ticket is the `valHook` equivalent of #11115: native Sizzle always gets it right, jQuery only does so when `jQuery.filter` calls a qSA-backed `matchesSelector` in the single-element case: http://jsfiddle.net/Mj23Q/43/1354169136709145
blockedby: → 12600

(In #12600) The expectations in the comments of that fiddle are erroneous... select[value=…] shouldn't match anything, because most select elements—and those in particular—don't have attributes named "value".

This ticket is the attrHooks equivalent of #11115: native Sizzle always gets it right, jQuery only does so when jQuery.filter calls a qSA-backed matchesSelector in the single-element case: http://jsfiddle.net/Mj23Q/43/

Changed December 04, 2012 05:00AM UTC by gibson042 comment:16

priority: lowblocker

The hook should be moved to https://github.com/jquery/jquery-compat/.

Changed December 10, 2012 08:59AM UTC by gibson042 comment:17

owner: timmywilgibson042

Changed December 16, 2012 04:20AM UTC by Richard Gibson comment:18

resolution: → fixed
status: assignedclosed

Fix #11115: Normalize boolean attributes/properties. Close gh-1066.

Changeset: a763ae72775f69509d0a8a6b50702bcf2c7e6fbf