]> source.dussan.org Git - jquery.git/commitdiff
Selector: reduce size, simplify setDocument
authorMichał Gołębiowski-Owczarek <m.goleb@gmail.com>
Mon, 26 Aug 2019 17:15:53 +0000 (19:15 +0200)
committerGitHub <noreply@github.com>
Mon, 26 Aug 2019 17:15:53 +0000 (19:15 +0200)
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

src/selector.js
src/selector/rbuggyQSA.js [new file with mode: 0644]
test/unit/selector.js

index 3e187a159f6e80a76dc89a4ba0560cc1f2487c29..847d3a448cfd96b8e94dc932d6daa20caca88ca5 100644 (file)
@@ -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 (file)
index 0000000..eee151a
--- /dev/null
@@ -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;
+
+} );
index 779b554e76568da7785ba669113113be0c7d4708..f015fc933fc20f366d51488909b8e16b36ea9bad 100644 (file)
@@ -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 );