diff options
author | Michał Gołębiowski-Owczarek <m.goleb@gmail.com> | 2019-08-26 19:15:53 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-08-26 19:15:53 +0200 |
commit | 29a9544a4fb743491a42f827a6cf8627b7b99e0f (patch) | |
tree | 7c700ac6a669717e3245b5292ab46be8eefdfe86 | |
parent | abdc89ac2e581392b800c0364e0f5f2b6a82cdc6 (diff) | |
download | jquery-29a9544a4fb743491a42f827a6cf8627b7b99e0f.tar.gz jquery-29a9544a4fb743491a42f827a6cf8627b7b99e0f.zip |
Selector: reduce size, simplify setDocument
With new selector code doing less convoluted support tests, it was possible
to extract a lot of logic out of setDocument & also reduce size.
This commit also backports jquery/sizzle#439 that was reverted by mistake
during a switch from JSHint + JSCS to ESLint.
Closes gh-4462
Ref jquery/sizzle#442
Ref jquery/sizzle#439
-rw-r--r-- | src/selector.js | 210 | ||||
-rw-r--r-- | src/selector/rbuggyQSA.js | 30 | ||||
-rw-r--r-- | test/unit/selector.js | 18 |
3 files changed, 128 insertions, 130 deletions
diff --git a/src/selector.js b/src/selector.js index 3e187a159..847d3a448 100644 --- a/src/selector.js +++ b/src/selector.js @@ -1,20 +1,23 @@ define( [ "./core", "./var/document", + "./var/documentElement", "./var/indexOf", "./var/pop", "./var/push", + "./selector/rbuggyQSA", "./selector/support", // The following utils are attached directly to the jQuery object. "./selector/contains", "./selector/escapeSelector", "./selector/uniqueSort" -], function( jQuery, document, indexOf, pop, push, support ) { +], function( jQuery, document, documentElement, indexOf, pop, push, rbuggyQSA, support ) { "use strict"; -var preferredDoc = document; +var preferredDoc = document, + matches = documentElement.matches || documentElement.msMatchesSelector; ( function() { @@ -24,10 +27,8 @@ var i, // Local document vars document, - docElem, + documentElement, documentIsHTML, - rbuggyQSA, - matches, // Instance-specific data expando = jQuery.expando, @@ -86,20 +87,20 @@ var i, ridentifier = new RegExp( "^" + identifier + "$" ), matchExpr = { - "ID": new RegExp( "^#(" + identifier + ")" ), - "CLASS": new RegExp( "^\\.(" + identifier + ")" ), - "TAG": new RegExp( "^(" + identifier + "|[*])" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( + ID: new RegExp( "^#(" + identifier + ")" ), + CLASS: new RegExp( "^\\.(" + identifier + ")" ), + TAG: new RegExp( "^(" + identifier + "|[*])" ), + ATTR: new RegExp( "^" + attributes ), + PSEUDO: new RegExp( "^" + pseudos ), + CHILD: new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + bool: new RegExp( "^(?:" + booleans + ")$", "i" ), // For use in libraries implementing .is() // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + + needsContext: new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) }, @@ -190,10 +191,8 @@ function find( selector, context, results, seed ) { if ( nodeType === 9 ) { if ( ( elem = context.getElementById( m ) ) ) { results.push( elem ); - return results; - } else { - return results; } + return results; // Element context } else { @@ -420,20 +419,19 @@ function testContext( context ) { /** * Sets document-related variables once based on the current document * @param {Element|Object} [node] An element or document object to use to set the document - * @returns {Object} Returns the current document */ function setDocument( node ) { var subWindow, doc = node ? node.ownerDocument || node : preferredDoc; // Return early if doc is invalid or already selected - if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { - return document; + if ( doc === document || doc.nodeType !== 9 ) { + return; } // Update global variables document = doc; - docElem = document.documentElement; + documentElement = document.documentElement; documentIsHTML = !jQuery.isXMLDoc( document ); // Support: IE 9 - 11+, Edge 12 - 18+ @@ -444,77 +442,6 @@ function setDocument( node ) { // Support: IE 9 - 11+, Edge 12 - 18+ subWindow.addEventListener( "unload", unloadHandler ); } - - // ID filter and find - Expr.filter.ID = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - return elem.getAttribute( "id" ) === attrId; - }; - }; - Expr.find.ID = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var elem = context.getElementById( id ); - return elem ? [ elem ] : []; - } - }; - - // Tag - Expr.find.TAG = function( tag, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( tag ); - - // DocumentFragment nodes don't have gEBTN - } else { - return context.querySelectorAll( tag ); - } - }; - - // Class - Expr.find.CLASS = function( className, context ) { - if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { - return context.getElementsByClassName( className ); - } - }; - - /* QSA/matchesSelector - ---------------------------------------------------------------------- */ - - // QSA and matchesSelector support - - rbuggyQSA = []; - - var testEl = document.createElement( "fieldset" ); - - testEl.innerHTML = "<a href='' disabled='disabled'></a>" + - "<select disabled='disabled'><option/></select>"; - - // Support: Windows 8 Native Apps - // The type and name attributes are restricted during .innerHTML assignment - var input = document.createElement( "input" ); - input.setAttribute( "type", "hidden" ); - testEl.appendChild( input ).setAttribute( "name", "D" ); - - // Support: Chrome 74+ - // :enabled/:disabled and hidden elements (hidden elements are still enabled) - if ( testEl.querySelectorAll( ":enabled" ).length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: IE 9 - 11+ - // IE's :disabled selector does not pick up the children of disabled fieldsets - docElem.appendChild( testEl ).disabled = true; - if ( testEl.querySelectorAll( ":disabled" ).length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - docElem.removeChild( testEl ); - - matches = docElem.matches || docElem.msMatchesSelector; - - rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); - - return document; } find.matches = function( expr, elements ) { @@ -551,7 +478,30 @@ Expr = jQuery.expr = { match: matchExpr, - find: {}, + find: { + ID: function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }, + + TAG: function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else { + return context.querySelectorAll( tag ); + } + }, + + CLASS: function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + } + }, relative: { ">": { dir: "parentNode", first: true }, @@ -561,7 +511,7 @@ Expr = jQuery.expr = { }, preFilter: { - "ATTR": function( match ) { + ATTR: function( match ) { match[ 1 ] = match[ 1 ].replace( runescape, funescape ); // Move the given value to match[3] whether quoted or unquoted @@ -575,7 +525,7 @@ Expr = jQuery.expr = { return match.slice( 0, 4 ); }, - "CHILD": function( match ) { + CHILD: function( match ) { /* matches from matchExpr["CHILD"] 1 type (only|nth|...) @@ -612,7 +562,7 @@ Expr = jQuery.expr = { return match; }, - "PSEUDO": function( match ) { + PSEUDO: function( match ) { var excess, unquoted = !match[ 6 ] && match[ 2 ]; @@ -644,8 +594,14 @@ Expr = jQuery.expr = { }, filter: { + ID: function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute( "id" ) === attrId; + }; + }, - "TAG": function( nodeNameSelector ) { + TAG: function( nodeNameSelector ) { var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); return nodeNameSelector === "*" ? function() { @@ -656,7 +612,7 @@ Expr = jQuery.expr = { }; }, - "CLASS": function( className ) { + CLASS: function( className ) { var pattern = classCache[ className + " " ]; return pattern || @@ -672,7 +628,7 @@ Expr = jQuery.expr = { } ); }, - "ATTR": function( name, operator, check ) { + ATTR: function( name, operator, check ) { return function( elem ) { var result = jQuery.attr( elem, name ); @@ -712,7 +668,7 @@ Expr = jQuery.expr = { }; }, - "CHILD": function( type, what, _argument, first, last ) { + CHILD: function( type, what, _argument, first, last ) { var simple = type.slice( 0, 3 ) !== "nth", forward = type.slice( -4 ) !== "last", ofType = what === "of-type"; @@ -822,7 +778,7 @@ Expr = jQuery.expr = { }; }, - "PSEUDO": function( pseudo, argument ) { + PSEUDO: function( pseudo, argument ) { // pseudo-class names are case-insensitive // http://www.w3.org/TR/selectors/#pseudo-classes @@ -864,7 +820,7 @@ Expr = jQuery.expr = { pseudos: { // Potentially complex pseudos - "not": markFunction( function( selector ) { + not: markFunction( function( selector ) { // Trim the selector passed to compile // to avoid treating leading and trailing @@ -896,13 +852,13 @@ Expr = jQuery.expr = { }; } ), - "has": markFunction( function( selector ) { + has: markFunction( function( selector ) { return function( elem ) { return find( selector, elem ).length > 0; }; } ), - "contains": markFunction( function( text ) { + contains: markFunction( function( text ) { text = text.replace( runescape, funescape ); return function( elem ) { return ( elem.textContent || jQuery.text( elem ) ).indexOf( text ) > -1; @@ -916,7 +872,7 @@ Expr = jQuery.expr = { // The matching of C against the element's language value is performed case-insensitively. // The identifier C does not have to be a valid language name." // http://www.w3.org/TR/selectors/#lang-pseudo - "lang": markFunction( function( lang ) { + lang: markFunction( function( lang ) { // lang value must be a valid identifier if ( !ridentifier.test( lang || "" ) ) { @@ -939,26 +895,26 @@ Expr = jQuery.expr = { } ), // Miscellaneous - "target": function( elem ) { + target: function( elem ) { var hash = window.location && window.location.hash; return hash && hash.slice( 1 ) === elem.id; }, - "root": function( elem ) { - return elem === docElem; + root: function( elem ) { + return elem === documentElement; }, - "focus": function( elem ) { + focus: function( elem ) { return elem === document.activeElement && - ( !document.hasFocus || document.hasFocus() ) && + document.hasFocus() && !!( elem.type || elem.href || ~elem.tabIndex ); }, // Boolean properties - "enabled": createDisabledPseudo( false ), - "disabled": createDisabledPseudo( true ), + enabled: createDisabledPseudo( false ), + disabled: createDisabledPseudo( true ), - "checked": function( elem ) { + checked: function( elem ) { // In CSS3, :checked should return both checked and selected elements // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked @@ -967,7 +923,7 @@ Expr = jQuery.expr = { ( nodeName === "option" && !!elem.selected ); }, - "selected": function( elem ) { + selected: function( elem ) { // Support: IE <=11+ // Accessing the selectedIndex property @@ -982,7 +938,7 @@ Expr = jQuery.expr = { }, // Contents - "empty": function( elem ) { + empty: function( elem ) { // http://www.w3.org/TR/selectors/#empty-pseudo // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), @@ -996,43 +952,43 @@ Expr = jQuery.expr = { return true; }, - "parent": function( elem ) { + parent: function( elem ) { return !Expr.pseudos.empty( elem ); }, // Element/input types - "header": function( elem ) { + header: function( elem ) { return rheader.test( elem.nodeName ); }, - "input": function( elem ) { + input: function( elem ) { return rinputs.test( elem.nodeName ); }, - "button": function( elem ) { + button: function( elem ) { var name = elem.nodeName.toLowerCase(); return name === "input" && elem.type === "button" || name === "button"; }, - "text": function( elem ) { + text: function( elem ) { return elem.nodeName.toLowerCase() === "input" && elem.type === "text"; }, // Position-in-collection - "first": createPositionalPseudo( function() { + first: createPositionalPseudo( function() { return [ 0 ]; } ), - "last": createPositionalPseudo( function( _matchIndexes, length ) { + last: createPositionalPseudo( function( _matchIndexes, length ) { return [ length - 1 ]; } ), - "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { + eq: createPositionalPseudo( function( _matchIndexes, length, argument ) { return [ argument < 0 ? argument + length : argument ]; } ), - "even": createPositionalPseudo( function( matchIndexes, length ) { + even: createPositionalPseudo( function( matchIndexes, length ) { var i = 0; for ( ; i < length; i += 2 ) { matchIndexes.push( i ); @@ -1040,7 +996,7 @@ Expr = jQuery.expr = { return matchIndexes; } ), - "odd": createPositionalPseudo( function( matchIndexes, length ) { + odd: createPositionalPseudo( function( matchIndexes, length ) { var i = 1; for ( ; i < length; i += 2 ) { matchIndexes.push( i ); @@ -1048,7 +1004,7 @@ Expr = jQuery.expr = { return matchIndexes; } ), - "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { + lt: createPositionalPseudo( function( matchIndexes, length, argument ) { var i; if ( argument < 0 ) { @@ -1065,7 +1021,7 @@ Expr = jQuery.expr = { return matchIndexes; } ), - "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { + gt: createPositionalPseudo( function( matchIndexes, length, argument ) { var i = argument < 0 ? argument + length : argument; for ( ; ++i < length; ) { matchIndexes.push( i ); @@ -1672,8 +1628,6 @@ setDocument(); jQuery.find = find; -/* eslint-enable */ - } )(); } ); diff --git a/src/selector/rbuggyQSA.js b/src/selector/rbuggyQSA.js new file mode 100644 index 000000000..eee151afb --- /dev/null +++ b/src/selector/rbuggyQSA.js @@ -0,0 +1,30 @@ +define( [ + "../var/document", + "../var/isIE" +], function( document, isIE ) { + +"use strict"; + +var rbuggyQSA = [], + testEl = document.createElement( "div" ); + +testEl.innerHTML = "<a href=''></a>"; + +// Support: Chrome 38 - 77 only +// Chrome considers anchor elements with href to match ":enabled" +// See https://bugs.chromium.org/p/chromium/issues/detail?id=993387 +if ( testEl.querySelectorAll( ":enabled" ).length ) { + rbuggyQSA.push( ":enabled" ); +} + +// Support: IE 9 - 11+ +// IE's :disabled selector does not pick up the children of disabled fieldsets +if ( isIE ) { + rbuggyQSA.push( ":enabled", ":disabled" ); +} + +rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + +return rbuggyQSA; + +} ); diff --git a/test/unit/selector.js b/test/unit/selector.js index 779b554e7..f015fc933 100644 --- a/test/unit/selector.js +++ b/test/unit/selector.js @@ -1408,8 +1408,6 @@ QUnit.test( "pseudo - fieldset:(dis|en)abled", function( assert ) { QUnit.test( "pseudo - :disabled by ancestry", function( assert ) { assert.expect( 1 ); - // Don't test for presence of select - // IE6 doesn't visibly or functionally disable them when the fieldset is disabled assert.t( "Inputs inherit disabled from fieldset", "#disabled-fieldset :disabled", @@ -1418,6 +1416,22 @@ QUnit.test( "pseudo - :disabled by ancestry", function( assert ) { ); } ); +QUnit.test( "pseudo - a:(dis|en)abled", function( assert ) { + assert.expect( 2 ); + + var enabled, disabled, + container = jQuery( "<div/>" ), + anchor = jQuery( "<a href='#'>Link</a>" ); + + container.appendTo( "#qunit-fixture" ); + + enabled = container.find( "a:enabled" ); + disabled = container.find( "a:disabled" ); + + assert.strictEqual( enabled.length, 0, ":enabled doesn't match anchor elements" ); + assert.strictEqual( disabled.length, 0, ":disabled doesn't match anchor elements" ); +} ); + QUnit.test( "pseudo - :target and :root", function( assert ) { assert.expect( 2 ); |