Selector: Backport jQuery selection context logic to selector-native
[jquery.git] / src / selector-native.js
blob07da6f37ac879dd88ec2f50f839833313a2840d2
1 /*
2  * Optional limited selector module for custom builds.
3  *
4  * Note that this DOES NOT SUPPORT many documented jQuery
5  * features in exchange for its smaller size:
6  *
7  * * Attribute not equal selector (!=)
8  * * Positional selectors (:first; :eq(n); :odd; etc.)
9  * * Type selectors (:input; :checkbox; :button; etc.)
10  * * State-based selectors (:animated; :visible; :hidden; etc.)
11  * * :has(selector) in browsers without native support
12  * * :not(complex selector) in IE
13  * * custom selectors via jQuery extensions
14  * * Reliable functionality on XML fragments
15  * * Matching against non-elements
16  * * Reliable sorting of disconnected nodes
17  * * querySelectorAll bug fixes (e.g., unreliable :focus on WebKit)
18  *
19  * If any of these are unacceptable tradeoffs, either use the full
20  * selector engine or  customize this stub for the project's specific
21  * needs.
22  */
24 import jQuery from "./core.js";
25 import document from "./var/document.js";
26 import whitespace from "./var/whitespace.js";
28 // The following utils are attached directly to the jQuery object.
29 import "./selector/escapeSelector.js";
30 import "./selector/uniqueSort.js";
31 import isIE from "./var/isIE.js";
32 import booleans from "./selector/var/booleans.js";
33 import rleadingCombinator from "./selector/var/rleadingCombinator.js";
34 import rdescend from "./selector/var/rdescend.js";
35 import rsibling from "./selector/var/rsibling.js";
36 import matches from "./selector/var/matches.js";
37 import testContext from "./selector/testContext.js";
38 import filterMatchExpr from "./selector/filterMatchExpr.js";
39 import preFilter from "./selector/preFilter.js";
40 import tokenize from "./selector/tokenize.js";
41 import toSelector from "./selector/toSelector.js";
43 var matchExpr = jQuery.extend( {
44         bool: new RegExp( "^(?:" + booleans + ")$", "i" ),
45         needsContext: new RegExp( "^" + whitespace + "*[>+~]" )
46 }, filterMatchExpr );
48 jQuery.extend( {
49         find: function( selector, context, results, seed ) {
50                 var elem, nid, groups, newSelector,
51                         newContext = context && context.ownerDocument,
53                         // nodeType defaults to 9, since context defaults to document
54                         nodeType = context ? context.nodeType : 9,
55                         i = 0;
57                 results = results || [];
58                 context = context || document;
60                 // Same basic safeguard as in the full selector module
61                 if ( !selector || typeof selector !== "string" ) {
62                         return results;
63                 }
65                 // Early return if context is not an element, document or document fragment
66                 if ( nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
67                         return [];
68                 }
70                 if ( seed ) {
71                         while ( ( elem = seed[ i++ ] ) ) {
72                                 if ( jQuery.find.matchesSelector( elem, selector ) ) {
73                                         results.push( elem );
74                                 }
75                         }
76                 } else {
78                         newSelector = selector;
79                         newContext = context;
81                         // qSA considers elements outside a scoping root when evaluating child or
82                         // descendant combinators, which is not what we want.
83                         // In such cases, we work around the behavior by prefixing every selector in the
84                         // list with an ID selector referencing the scope context.
85                         // The technique has to be used as well when a leading combinator is used
86                         // as such selectors are not recognized by querySelectorAll.
87                         // Thanks to Andrew Dupont for this technique.
88                         if ( nodeType === 1 &&
89                                 ( rdescend.test( selector ) || rleadingCombinator.test( selector ) ) ) {
91                                 // Expand context for sibling selectors
92                                 newContext = rsibling.test( selector ) &&
93                                         testContext( context.parentNode ) ||
94                                         context;
96                                 // Outside of IE, if we're not changing the context we can
97                                 // use :scope instead of an ID.
98                                 if ( newContext !== context || isIE ) {
100                                         // Capture the context ID, setting it first if necessary
101                                         if ( ( nid = context.getAttribute( "id" ) ) ) {
102                                                 nid = jQuery.escapeSelector( nid );
103                                         } else {
104                                                 context.setAttribute( "id", ( nid = jQuery.expando ) );
105                                         }
106                                 }
108                                 // Prefix every selector in the list
109                                 groups = tokenize( selector );
110                                 i = groups.length;
111                                 while ( i-- ) {
112                                         groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " +
113                                                 toSelector( groups[ i ] );
114                                 }
115                                 newSelector = groups.join( "," );
116                         }
118                         try {
119                                 jQuery.merge( results, newContext.querySelectorAll( newSelector ) );
120                         } finally {
121                                 if ( nid === jQuery.expando ) {
122                                         context.removeAttribute( "id" );
123                                 }
124                         }
125                 }
127                 return results;
128         },
129         expr: {
131                 // Can be adjusted by the user
132                 cacheLength: 50,
134                 match: matchExpr,
135                 preFilter: preFilter
136         }
137 } );
139 jQuery.extend( jQuery.find, {
140         matches: function( expr, elements ) {
141                 return jQuery.find( expr, null, null, elements );
142         },
143         matchesSelector: function( elem, expr ) {
144                 return matches.call( elem, expr );
145         }
146 } );