From 5d1dfe747403c093cc0e837651dcf80027387fc6 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Tue, 26 Mar 2013 21:20:27 -0400 Subject: [PATCH] Fix #13539: Utilize Sizzle hooks. Close gh-1215. (cherry picked from commit 4ef516903e6e48bce388ca47c1ed88a447199fa1) --- Gruntfile.js | 2 +- src/attributes.js | 125 ++++++++++++++++++++++------------------ src/sizzle | 2 +- src/sizzle-jquery.js | 2 - src/traversing.js | 57 +++++++++--------- test/unit/css.js | 4 +- test/unit/selector.js | 9 +-- test/unit/traversing.js | 39 +++++++++++-- 8 files changed, 134 insertions(+), 106 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 2317544ad..809ea3552 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -41,6 +41,7 @@ module.exports = function( grunt ) { src: [ "src/intro.js", "src/core.js", + { flag: "sizzle", src: "src/selector-sizzle.js", alt: "src/selector-native.js" }, "src/callbacks.js", "src/deferred.js", "src/support.js", @@ -48,7 +49,6 @@ module.exports = function( grunt ) { "src/queue.js", "src/attributes.js", "src/event.js", - { flag: "sizzle", src: "src/selector-sizzle.js", alt: "src/selector-native.js" }, "src/traversing.js", "src/manipulation.js", { flag: "css", src: "src/css.js" }, diff --git a/src/attributes.js b/src/attributes.js index f116f8396..57257eba3 100644 --- a/src/attributes.js +++ b/src/attributes.js @@ -2,8 +2,7 @@ var nodeHook, boolHook, rclass = /[\t\r\n]/g, rreturn = /\r/g, rfocusable = /^(?:input|select|textarea|button|object)$/i, - rclickable = /^(?:a|area)$/i, - rboolean = /^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i; + rclickable = /^(?:a|area)$/i; jQuery.fn.extend({ attr: function( name, value ) { @@ -291,7 +290,7 @@ jQuery.extend({ }, attr: function( elem, name, value ) { - var ret, hooks, notxml, + var hooks, ret, nType = elem.nodeType; // don't get/set attributes on text, comment and attribute nodes @@ -304,13 +303,12 @@ jQuery.extend({ return jQuery.prop( elem, name, value ); } - notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); - // All attributes are lowercase // Grab necessary hook if one is defined - if ( notxml ) { + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { name = name.toLowerCase(); - hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); + hooks = jQuery.attrHooks[ name ] || + ( jQuery.expr.match.boolean.test( name ) ? boolHook : nodeHook ); } if ( value !== undefined ) { @@ -318,7 +316,7 @@ jQuery.extend({ if ( value === null ) { jQuery.removeAttr( elem, name ); - } else if ( hooks && notxml && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + } else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { return ret; } else { @@ -326,16 +324,11 @@ jQuery.extend({ return value; } - } else if ( hooks && notxml && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { + } else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { return ret; } else { - - // In IE9+, Flash objects don't have .getAttribute (#12945) - // Support: IE9+ - if ( typeof elem.getAttribute !== core_strundefined ) { - ret = elem.getAttribute( name ); - } + ret = jQuery.find.attr( elem, name ); // Non-existent attributes return null, we normalize to undefined return ret == null ? @@ -354,8 +347,8 @@ jQuery.extend({ propName = jQuery.propFix[ name ] || name; // Boolean attributes get special treatment (#10870) - // Set corresponding property to false for boolean attributes - if ( rboolean.test( name ) ) { + if ( jQuery.expr.match.boolean.test( name ) ) { + // Set corresponding property to false elem[ propName ] = false; } @@ -382,18 +375,8 @@ jQuery.extend({ }, propFix: { - tabindex: "tabIndex", - readonly: "readOnly", "for": "htmlFor", - "class": "className", - maxlength: "maxLength", - cellspacing: "cellSpacing", - cellpadding: "cellPadding", - rowspan: "rowSpan", - colspan: "colSpan", - usemap: "useMap", - frameborder: "frameBorder", - contenteditable: "contentEditable" + "class": "className" }, prop: function( elem, name, value ) { @@ -448,13 +431,8 @@ jQuery.extend({ } }); -// Hook for boolean attributes +// Hooks for boolean attributes boolHook = { - get: function( elem, name ) { - return elem.getAttribute( name ) !== null ? - name.toLowerCase() : - undefined; - }, set: function( elem, value, name ) { if ( value === false ) { // Remove boolean attributes when set to false @@ -465,32 +443,32 @@ boolHook = { return name; } }; +jQuery.each( jQuery.expr.match.boolean.source.match( /\w+/g ), function( i, name ) { + var getter = jQuery.expr.attrHandle[ name ] || jQuery.find.attr; -// Radios and checkboxes getter/setter -if ( !jQuery.support.checkOn ) { - jQuery.each([ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = { - get: function( elem ) { - // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified - return elem.getAttribute("value") === null ? "on" : elem.value; - } - }; - }); -} -jQuery.each([ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { - set: function( elem, value ) { - if ( jQuery.isArray( value ) ) { - return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); - } - } - }); + jQuery.expr.attrHandle[ name ] = function( elem, name, isXML ) { + var fn = jQuery.expr.attrHandle[ name ], + ret = isXML ? + undefined : + /* jshint eqeqeq: false */ + // Temporarily disable this handler to check existence + (jQuery.expr.attrHandle[ name ] = undefined) != + getter( elem, name, isXML ) ? + + name.toLowerCase() : + null; + + // Restore handler + jQuery.expr.attrHandle[ name ] = fn; + + return ret; + }; }); -// IE9/10 do not see a selected option inside an optgroup unless you access it -// Support: IE9, IE10 +// Support: IE9+ +// Selectedness for an option in an optgroup can be inaccurate if ( !jQuery.support.optSelected ) { - jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { + jQuery.propHooks.selected = { get: function( elem ) { var parent = elem.parentNode; if ( parent && parent.parentNode ) { @@ -498,5 +476,38 @@ if ( !jQuery.support.optSelected ) { } return null; } - }); + }; } + +jQuery.each([ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +}); + +// Radios and checkboxes getter/setter +jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); + } + } + }; + if ( !jQuery.support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + // Support: Webkit + // "" is returned instead of "on" if a value isn't specified + return elem.getAttribute("value") === null ? "on" : elem.value; + }; + } +}); diff --git a/src/sizzle b/src/sizzle index a9ab22e77..cf19fa8dd 160000 --- a/src/sizzle +++ b/src/sizzle @@ -1 +1 @@ -Subproject commit a9ab22e770ab94c4c8b426e2f46f6ab91a19639d +Subproject commit cf19fa8dd6b7fda254bb3434f63d590845eaa7bf diff --git a/src/sizzle-jquery.js b/src/sizzle-jquery.js index 2370b4493..fa3196543 100644 --- a/src/sizzle-jquery.js +++ b/src/sizzle-jquery.js @@ -1,5 +1,3 @@ -// Override sizzle attribute retrieval -Sizzle.attr = jQuery.attr; jQuery.find = Sizzle; jQuery.expr = Sizzle.selectors; jQuery.expr[":"] = jQuery.expr.pseudos; diff --git a/src/traversing.js b/src/traversing.js index 81c1e5b57..a063a4612 100644 --- a/src/traversing.js +++ b/src/traversing.js @@ -1,6 +1,4 @@ -var runtil = /Until$/, - rparentsprev = /^(?:parents|prev(?:Until|All))/, - isSimple = /^.[^:#\[\.,]*$/, +var isSimple = /^.[^:#\[\.,]*$/, rneedsContext = jQuery.expr.match.needsContext, // methods guaranteed to produce a unique set when starting from a unique set guaranteedUnique = { @@ -52,11 +50,11 @@ jQuery.fn.extend({ }, not: function( selector ) { - return this.pushStack( winnow(this, selector, false) ); + return this.pushStack( winnow(this, selector || [], true) ); }, filter: function( selector ) { - return this.pushStack( winnow(this, selector, true) ); + return this.pushStack( winnow(this, selector || [], false) ); }, is: function( selector ) { @@ -186,7 +184,7 @@ jQuery.each({ jQuery.fn[ name ] = function( until, selector ) { var matched = jQuery.map( this, fn, until ); - if ( !runtil.test( name ) ) { + if ( name.slice( -5 ) !== "Until" ) { selector = until; } @@ -195,11 +193,13 @@ jQuery.each({ } if ( this.length > 1 ) { + // Remove duplicates if ( !guaranteedUnique[ name ] ) { jQuery.unique( matched ); } - if ( rparentsprev.test( name ) ) { + // Reverse order for parents* and prev* + if ( name[ 0 ] === "p" ) { matched.reverse(); } } @@ -210,13 +210,17 @@ jQuery.each({ jQuery.extend({ filter: function( expr, elems, not ) { + var elem = elems[ 0 ]; + if ( not ) { expr = ":not(" + expr + ")"; } - return elems.length === 1 ? - jQuery.find.matchesSelector( elems[ 0 ], expr ) ? [ elems[ 0 ] ] : [] : - jQuery.find.matches( expr, elems ); + return elems.length === 1 && elem.nodeType === 1 ? + jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] : + jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + })); }, dir: function( elem, dir, until ) { @@ -248,40 +252,31 @@ jQuery.extend({ }); // Implement the identical functionality for filter and not -function winnow( elements, qualifier, keep ) { - - // Can't pass null or undefined to indexOf - // Set to 0 to skip string check - qualifier = qualifier || 0; - - var filtered; - +function winnow( elements, qualifier, not ) { if ( jQuery.isFunction( qualifier ) ) { - return jQuery.grep(elements, function( elem, i ) { - var retVal = !!qualifier.call( elem, i, elem ); - return retVal === keep; + return jQuery.grep( elements, function( elem, i ) { + /* jshint -W018 */ + return !!qualifier.call( elem, i, elem ) !== not; }); + } if ( qualifier.nodeType ) { - return jQuery.grep(elements, function( elem ) { - return ( elem === qualifier ) === keep; + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; }); + } if ( typeof qualifier === "string" ) { - filtered = jQuery.grep(elements, function( elem ) { - return elem.nodeType === 1; - }); - if ( isSimple.test( qualifier ) ) { - return jQuery.filter( qualifier, filtered, !keep ); + return jQuery.filter( qualifier, elements, not ); } - qualifier = jQuery.filter( qualifier, filtered ); + qualifier = jQuery.filter( qualifier, elements ); } - return jQuery.grep(elements, function( elem ) { - return ( core_indexOf.call( qualifier, elem ) >= 0 ) === keep; + return jQuery.grep( elements, function( elem ) { + return ( core_indexOf.call( qualifier, elem ) >= 0 ) !== not; }); } diff --git a/test/unit/css.js b/test/unit/css.js index e8ce75b02..dc851f745 100644 --- a/test/unit/css.js +++ b/test/unit/css.js @@ -864,8 +864,8 @@ test( ":visible/:hidden selectors", function() { ok( !jQuery("#nothiddendiv").is(":visible"), "Modified CSS display: Assert element is hidden" ); jQuery("#nothiddendiv").css({"display": "block"}); ok( jQuery("#nothiddendiv").is(":visible"), "Modified CSS display: Assert element is visible"); - ok( jQuery(window).is(":visible"), "Calling is(':visible') on window does not throw an error in IE."); - ok( jQuery(document).is(":visible"), "Calling is(':visible') on document does not throw an error in IE."); + ok( jQuery(window).is(":visible") || true, "Calling is(':visible') on window does not throw an exception (#10267)"); + ok( jQuery(document).is(":visible") || true, "Calling is(':visible') on document does not throw an exception (#10267)"); ok( jQuery("#nothiddendiv").is(":visible"), "Modifying CSS display: Assert element is visible"); jQuery("#nothiddendiv").css("display", "none"); diff --git a/test/unit/selector.js b/test/unit/selector.js index 0cfba2834..440e7167b 100644 --- a/test/unit/selector.js +++ b/test/unit/selector.js @@ -31,17 +31,10 @@ test("class - jQuery only", function() { }); test("attributes - jQuery only", function() { - expect( 6 ); + expect( 5 ); t( "Find elements with a tabindex attribute", "[tabindex]", ["listWithTabIndex", "foodWithNegativeTabIndex", "linkWithTabIndex", "linkWithNegativeTabIndex", "linkWithNoHrefWithTabIndex", "linkWithNoHrefWithNegativeTabIndex"] ); - // #12523 - deepEqual( - jQuery.find( "[title]", null, null, jQuery("#qunit-fixture a").get().concat( document.createTextNode("") ) ), - q("google"), - "Text nodes fail attribute tests without exception" - ); - // #12600 ok( jQuery("") diff --git a/test/unit/traversing.js b/test/unit/traversing.js index 0e6439300..1060e7e40 100644 --- a/test/unit/traversing.js +++ b/test/unit/traversing.js @@ -78,10 +78,29 @@ test("is(String|undefined)", function() { ok( jQuery("#en").is("[lang=\"de\"] , [lang=\"en\"]"), "Comma-separated; Check for lang attribute: Expect en or de" ); }); -test("is() against window|document (#10178)", function() { - expect(2); - ok( !jQuery(window).is("a"), "Checking is on a window does not throw an exception" ); - ok( !jQuery(document).is("a"), "Checking is on a document does not throw an exception" ); +test("is() against non-elements (#10178)", function() { + expect(14); + + var label, i, test, + collection = jQuery( document ), + tests = [ "a", "*" ], + nonelements = { + text: document.createTextNode(""), + comment: document.createComment(""), + document: document, + window: window, + array: [], + "plain object": {}, + "function": function() {} + }; + + for ( label in nonelements ) { + collection[ 0 ] = nonelements[ label ]; + for ( i = 0; i < tests.length; i++ ) { + test = tests[ i ]; + ok( !collection.is( test ), label + " does not match \"" + test + "\"" ); + } + } }); test("is(jQuery)", function() { @@ -716,3 +735,15 @@ test("index(no arg) #10977", function() { equal( jQuery( div ).index(), 0, "If jQuery#index called on element whose parent is fragment, it still should work correctly" ); }); + +test("traversing non-elements with attribute filters (#12523)", function() { + expect(5); + + var nonnodes = jQuery("#nonnodes").contents(); + + equal( nonnodes.filter("[id]").length, 1, ".filter" ); + equal( nonnodes.find("[id]").length, 0, ".find" ); + strictEqual( nonnodes.is("[id]"), true, ".is" ); + deepEqual( nonnodes.closest("[id='nonnodes']").get(), q("nonnodes"), ".closest" ); + deepEqual( nonnodes.parents("[id='nonnodes']").get(), q("nonnodes"), ".parents" ); +}); -- 2.39.5