Skip to content

Commit 7999a01

Browse files
committed
Selection: Check for ancestor fieldsets when matching :enabled/:disabled
SemVer patch Fixes gh-174 Ref https://www.w3.org/Bugs/Public/show_bug.cgi?id=26622 Closes gh-283
1 parent 7de7363 commit 7999a01

File tree

6 files changed

+284
-109
lines changed

6 files changed

+284
-109
lines changed

dist/sizzle.js

Lines changed: 96 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* Released under the MIT license
77
* http://jquery.org/license
88
*
9-
* Date: 2015-12-18
9+
* Date: 2016-01-04
1010
*/
1111
(function( window ) {
1212

@@ -175,7 +175,14 @@ var i,
175175
// error in IE
176176
unloadHandler = function() {
177177
setDocument();
178-
};
178+
},
179+
180+
disabledAncestor = addCombinator(
181+
function( elem ) {
182+
return elem.disabled === true;
183+
},
184+
{ dir: "parentNode", next: "legend" }
185+
);
179186

180187
// Optimize for push.apply( _, NodeList )
181188
try {
@@ -370,22 +377,22 @@ function markFunction( fn ) {
370377

371378
/**
372379
* Support testing using an element
373-
* @param {Function} fn Passed the created div and expects a boolean result
380+
* @param {Function} fn Passed the created element and returns a boolean result
374381
*/
375382
function assert( fn ) {
376-
var div = document.createElement("div");
383+
var el = document.createElement("fieldset");
377384

378385
try {
379-
return !!fn( div );
386+
return !!fn( el );
380387
} catch (e) {
381388
return false;
382389
} finally {
383390
// Remove from its parent by default
384-
if ( div.parentNode ) {
385-
div.parentNode.removeChild( div );
391+
if ( el.parentNode ) {
392+
el.parentNode.removeChild( el );
386393
}
387394
// release memory in IE
388-
div = null;
395+
el = null;
389396
}
390397
}
391398

@@ -453,6 +460,34 @@ function createButtonPseudo( type ) {
453460
};
454461
}
455462

463+
/**
464+
* Returns a function to use in pseudos for :enabled/:disabled
465+
* @param {Boolean} disabled true for :disabled; false for :enabled
466+
*/
467+
function createDisabledPseudo( disabled ) {
468+
// Known :disabled false positives:
469+
// IE: *[disabled]:not(button, input, select, textarea, optgroup, option, menuitem, fieldset)
470+
// not IE: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable
471+
return function( elem ) {
472+
473+
// Check form elements and option elements for explicit disabling
474+
return "label" in elem && elem.disabled === disabled ||
475+
"form" in elem && elem.disabled === disabled ||
476+
477+
// Check non-disabled form elements for fieldset[disabled] ancestors
478+
"form" in elem && elem.disabled === false && (
479+
// Support: IE6-11+
480+
// Ancestry is covered for us
481+
elem.isDisabled === disabled ||
482+
483+
// Otherwise, assume any non-<option> under fieldset[disabled] is disabled
484+
/* jshint -W018 */
485+
elem.isDisabled !== !disabled &&
486+
("label" in elem || !disabledAncestor( elem )) !== disabled
487+
);
488+
};
489+
}
490+
456491
/**
457492
* Returns a function to use in pseudos for positionals
458493
* @param {Function} fn
@@ -539,18 +574,18 @@ setDocument = Sizzle.setDocument = function( node ) {
539574
// Support: IE<8
540575
// Verify that getAttribute really returns attributes and not properties
541576
// (excepting IE8 booleans)
542-
support.attributes = assert(function( div ) {
543-
div.className = "i";
544-
return !div.getAttribute("className");
577+
support.attributes = assert(function( el ) {
578+
el.className = "i";
579+
return !el.getAttribute("className");
545580
});
546581

547582
/* getElement(s)By*
548583
---------------------------------------------------------------------- */
549584

550585
// Check if getElementsByTagName("*") returns only elements
551-
support.getElementsByTagName = assert(function( div ) {
552-
div.appendChild( document.createComment("") );
553-
return !div.getElementsByTagName("*").length;
586+
support.getElementsByTagName = assert(function( el ) {
587+
el.appendChild( document.createComment("") );
588+
return !el.getElementsByTagName("*").length;
554589
});
555590

556591
// Support: IE<9
@@ -560,8 +595,8 @@ setDocument = Sizzle.setDocument = function( node ) {
560595
// Check if getElementById returns elements by name
561596
// The broken getElementById methods don't pick up programmatically-set names,
562597
// so use a roundabout getElementsByName test
563-
support.getById = assert(function( div ) {
564-
docElem.appendChild( div ).id = expando;
598+
support.getById = assert(function( el ) {
599+
docElem.appendChild( el ).id = expando;
565600
return !document.getElementsByName || !document.getElementsByName( expando ).length;
566601
});
567602

@@ -651,71 +686,81 @@ setDocument = Sizzle.setDocument = function( node ) {
651686
if ( (support.qsa = rnative.test( document.querySelectorAll )) ) {
652687
// Build QSA regex
653688
// Regex strategy adopted from Diego Perini
654-
assert(function( div ) {
689+
assert(function( el ) {
655690
// Select is set to empty string on purpose
656691
// This is to test IE's treatment of not explicitly
657692
// setting a boolean content attribute,
658693
// since its presence should be enough
659694
// https://bugs.jquery.com/ticket/12359
660-
docElem.appendChild( div ).innerHTML = "<a id='" + expando + "'></a>" +
695+
docElem.appendChild( el ).innerHTML = "<a id='" + expando + "'></a>" +
661696
"<select id='" + expando + "-\r\\' msallowcapture=''>" +
662697
"<option selected=''></option></select>";
663698

664699
// Support: IE8, Opera 11-12.16
665700
// Nothing should be selected when empty strings follow ^= or $= or *=
666701
// The test attribute must be unknown in Opera but "safe" for WinRT
667702
// https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
668-
if ( div.querySelectorAll("[msallowcapture^='']").length ) {
703+
if ( el.querySelectorAll("[msallowcapture^='']").length ) {
669704
rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
670705
}
671706

672707
// Support: IE8
673708
// Boolean attributes and "value" are not treated correctly
674-
if ( !div.querySelectorAll("[selected]").length ) {
709+
if ( !el.querySelectorAll("[selected]").length ) {
675710
rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
676711
}
677712

678713
// Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+
679-
if ( !div.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
714+
if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
680715
rbuggyQSA.push("~=");
681716
}
682717

683718
// Webkit/Opera - :checked should return selected option elements
684719
// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
685720
// IE8 throws error here and will not see later tests
686-
if ( !div.querySelectorAll(":checked").length ) {
721+
if ( !el.querySelectorAll(":checked").length ) {
687722
rbuggyQSA.push(":checked");
688723
}
689724

690725
// Support: Safari 8+, iOS 8+
691726
// https://bugs.webkit.org/show_bug.cgi?id=136851
692727
// In-page `selector#id sibling-combinator selector` fails
693-
if ( !div.querySelectorAll( "a#" + expando + "+*" ).length ) {
728+
if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) {
694729
rbuggyQSA.push(".#.+[+~]");
695730
}
696731
});
697732

698-
assert(function( div ) {
733+
assert(function( el ) {
734+
el.innerHTML = "<a href='' disabled='disabled'></a>" +
735+
"<select disabled='disabled'><option/></select>";
736+
699737
// Support: Windows 8 Native Apps
700738
// The type and name attributes are restricted during .innerHTML assignment
701739
var input = document.createElement("input");
702740
input.setAttribute( "type", "hidden" );
703-
div.appendChild( input ).setAttribute( "name", "D" );
741+
el.appendChild( input ).setAttribute( "name", "D" );
704742

705743
// Support: IE8
706744
// Enforce case-sensitivity of name attribute
707-
if ( div.querySelectorAll("[name=d]").length ) {
745+
if ( el.querySelectorAll("[name=d]").length ) {
708746
rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
709747
}
710748

711749
// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
712750
// IE8 throws error here and will not see later tests
713-
if ( !div.querySelectorAll(":enabled").length ) {
751+
if ( el.querySelectorAll(":enabled").length !== 2 ) {
752+
rbuggyQSA.push( ":enabled", ":disabled" );
753+
}
754+
755+
// Support: IE9-11+
756+
// IE's :disabled selector does not pick up the children of disabled fieldsets
757+
docElem.appendChild( el ).disabled = true;
758+
if ( el.querySelectorAll(":disabled").length !== 2 ) {
714759
rbuggyQSA.push( ":enabled", ":disabled" );
715760
}
716761

717762
// Opera 10-11 does not throw on post-comma invalid pseudos
718-
div.querySelectorAll("*,:x");
763+
el.querySelectorAll("*,:x");
719764
rbuggyQSA.push(",.*:");
720765
});
721766
}
@@ -726,14 +771,14 @@ setDocument = Sizzle.setDocument = function( node ) {
726771
docElem.oMatchesSelector ||
727772
docElem.msMatchesSelector) )) ) {
728773

729-
assert(function( div ) {
774+
assert(function( el ) {
730775
// Check to see if it's possible to do matchesSelector
731776
// on a disconnected node (IE 9)
732-
support.disconnectedMatch = matches.call( div, "div" );
777+
support.disconnectedMatch = matches.call( el, "*" );
733778

734779
// This should fail with an exception
735780
// Gecko does not error, returns false instead
736-
matches.call( div, "[s!='']:x" );
781+
matches.call( el, "[s!='']:x" );
737782
rbuggyMatches.push( "!=", pseudos );
738783
});
739784
}
@@ -1406,13 +1451,8 @@ Expr = Sizzle.selectors = {
14061451
},
14071452

14081453
// Boolean properties
1409-
"enabled": function( elem ) {
1410-
return elem.disabled === false;
1411-
},
1412-
1413-
"disabled": function( elem ) {
1414-
return elem.disabled === true;
1415-
},
1454+
"enabled": createDisabledPseudo( false ),
1455+
"disabled": createDisabledPseudo( true ),
14161456

14171457
"checked": function( elem ) {
14181458
// In CSS3, :checked should return both checked and selected elements
@@ -1614,7 +1654,9 @@ function toSelector( tokens ) {
16141654

16151655
function addCombinator( matcher, combinator, base ) {
16161656
var dir = combinator.dir,
1617-
checkNonElements = base && dir === "parentNode",
1657+
skip = combinator.next,
1658+
key = skip || dir,
1659+
checkNonElements = base && key === "parentNode",
16181660
doneName = done++;
16191661

16201662
return combinator.first ?
@@ -1650,14 +1692,16 @@ function addCombinator( matcher, combinator, base ) {
16501692
// Defend against cloned attroperties (jQuery gh-1709)
16511693
uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {});
16521694

1653-
if ( (oldCache = uniqueCache[ dir ]) &&
1695+
if ( skip && skip === elem.nodeName.toLowerCase() ) {
1696+
elem = elem[ dir ] || elem;
1697+
} else if ( (oldCache = uniqueCache[ key ]) &&
16541698
oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
16551699

16561700
// Assign to newCache so results back-propagate to previous elements
16571701
return (newCache[ 2 ] = oldCache[ 2 ]);
16581702
} else {
16591703
// Reuse newcache so results back-propagate to previous elements
1660-
uniqueCache[ dir ] = newCache;
1704+
uniqueCache[ key ] = newCache;
16611705

16621706
// A match means we're done; a fail means we have to keep checking
16631707
if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
@@ -2100,17 +2144,17 @@ setDocument();
21002144

21012145
// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
21022146
// Detached nodes confoundingly follow *each other*
2103-
support.sortDetached = assert(function( div1 ) {
2147+
support.sortDetached = assert(function( el ) {
21042148
// Should return 1, but returns 4 (following)
2105-
return div1.compareDocumentPosition( document.createElement("div") ) & 1;
2149+
return el.compareDocumentPosition( document.createElement("fieldset") ) & 1;
21062150
});
21072151

21082152
// Support: IE<8
21092153
// Prevent attribute/property "interpolation"
21102154
// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
2111-
if ( !assert(function( div ) {
2112-
div.innerHTML = "<a href='#'></a>";
2113-
return div.firstChild.getAttribute("href") === "#" ;
2155+
if ( !assert(function( el ) {
2156+
el.innerHTML = "<a href='#'></a>";
2157+
return el.firstChild.getAttribute("href") === "#" ;
21142158
}) ) {
21152159
addHandle( "type|href|height|width", function( elem, name, isXML ) {
21162160
if ( !isXML ) {
@@ -2121,10 +2165,10 @@ if ( !assert(function( div ) {
21212165

21222166
// Support: IE<9
21232167
// Use defaultValue in place of getAttribute("value")
2124-
if ( !support.attributes || !assert(function( div ) {
2125-
div.innerHTML = "<input/>";
2126-
div.firstChild.setAttribute( "value", "" );
2127-
return div.firstChild.getAttribute( "value" ) === "";
2168+
if ( !support.attributes || !assert(function( el ) {
2169+
el.innerHTML = "<input/>";
2170+
el.firstChild.setAttribute( "value", "" );
2171+
return el.firstChild.getAttribute( "value" ) === "";
21282172
}) ) {
21292173
addHandle( "value", function( elem, name, isXML ) {
21302174
if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
@@ -2135,8 +2179,8 @@ if ( !support.attributes || !assert(function( div ) {
21352179

21362180
// Support: IE<9
21372181
// Use getAttributeNode to fetch booleans when getAttribute lies
2138-
if ( !assert(function( div ) {
2139-
return div.getAttribute("disabled") == null;
2182+
if ( !assert(function( el ) {
2183+
return el.getAttribute("disabled") == null;
21402184
}) ) {
21412185
addHandle( booleans, function( elem, name, isXML ) {
21422186
var val;

0 commit comments

Comments
 (0)