@@ -29,7 +29,7 @@ function matchesPseudos (target, exp) {
29
29
30
30
if ( ! exp . pseudos || exp . pseudos . reduce ( ( result , pseudo ) => {
31
31
if ( pseudo . name === 'not' ) {
32
- return result && ! matchExpressions ( [ target ] , pseudo . expressions , false ) . length ;
32
+ return result && ! matchExpressions ( [ target ] , pseudo . expressions , false , target . shadowId ) . length ;
33
33
}
34
34
throw new Error ( 'the pseudo selector ' + pseudo . name + ' has not yet been implemented' ) ;
35
35
} , true ) ) {
@@ -38,27 +38,6 @@ function matchesPseudos (target, exp) {
38
38
return false ;
39
39
}
40
40
41
- function matchSelector ( targets , exp , recurse ) {
42
- var result = [ ] ;
43
-
44
- targets = Array . isArray ( targets ) ? targets : [ targets ] ;
45
- targets . forEach ( ( target ) => {
46
- if ( matchesTag ( target . actualNode , exp ) &&
47
- matchesClasses ( target . actualNode , exp ) &&
48
- matchesAttributes ( target . actualNode , exp ) &&
49
- matchesId ( target . actualNode , exp ) &&
50
- matchesPseudos ( target , exp ) ) {
51
- result . push ( target ) ;
52
- }
53
- if ( recurse ) {
54
- result = result . concat ( matchSelector ( target . children . filter ( ( child ) => {
55
- return ! exp . id || child . shadowId === target . shadowId ;
56
- } ) , exp , recurse ) ) ;
57
- }
58
- } ) ;
59
- return result ;
60
- }
61
-
62
41
var escapeRegExp = ( function ( ) {
63
42
/*! Credit: XRegExp 0.6.1 (c) 2007-2008 Steven Levithan <http://stevenlevithan.com/regex/xregexp/> MIT License */
64
43
var from = / (? = [ \- \[ \] { } ( ) * + ? . \\ \^ $ | , # \s ] ) / g;
@@ -194,27 +173,80 @@ convertExpressions = function (expressions) {
194
173
} ) ;
195
174
} ;
196
175
197
- matchExpressions = function ( domTree , expressions , recurse ) {
198
- return expressions . reduce ( ( collected , exprArr ) => {
199
- var candidates = domTree ;
200
- exprArr . forEach ( ( exp , index ) => {
201
- recurse = exp . combinator === '>' ? false : recurse ;
202
- if ( [ ' ' , '>' ] . includes ( exp . combinator ) === false ) {
203
- throw new Error ( 'axe.utils.querySelectorAll does not support the combinator: ' + exp . combinator ) ;
176
+ function createLocalVariables ( nodes , anyLevel , thisLevel , parentShadowId ) {
177
+ let retVal = {
178
+ nodes : nodes . slice ( ) ,
179
+ anyLevel : anyLevel ,
180
+ thisLevel : thisLevel ,
181
+ parentShadowId : parentShadowId
182
+ } ;
183
+ retVal . nodes . reverse ( ) ;
184
+ return retVal ;
185
+ }
186
+
187
+ function matchesSelector ( node , exp ) {
188
+ return ( matchesTag ( node . actualNode , exp [ 0 ] ) &&
189
+ matchesClasses ( node . actualNode , exp [ 0 ] ) &&
190
+ matchesAttributes ( node . actualNode , exp [ 0 ] ) &&
191
+ matchesId ( node . actualNode , exp [ 0 ] ) &&
192
+ matchesPseudos ( node , exp [ 0 ] )
193
+ ) ;
194
+ }
195
+
196
+ matchExpressions = function ( domTree , expressions , recurse , parentShadowId , filter ) {
197
+ //jshint maxstatements:34
198
+ //jshint maxcomplexity:15
199
+ let stack = [ ] ;
200
+ let nodes = Array . isArray ( domTree ) ? domTree : [ domTree ] ;
201
+ let currentLevel = createLocalVariables ( nodes , expressions , [ ] , parentShadowId ) ;
202
+ let result = [ ] ;
203
+
204
+ while ( currentLevel . nodes . length ) {
205
+ let node = currentLevel . nodes . pop ( ) ;
206
+ let childOnly = [ ] ; // we will add hierarchical '>' selectors here
207
+ let childAny = [ ] ;
208
+ let combined = currentLevel . anyLevel . slice ( ) . concat ( currentLevel . thisLevel ) ;
209
+ let added = false ;
210
+ // see if node matches
211
+ for ( let i = 0 ; i < combined . length ; i ++ ) {
212
+ let exp = combined [ i ] ;
213
+ if ( matchesSelector ( node , exp ) &&
214
+ ( ! exp [ 0 ] . id || node . shadowId === currentLevel . parentShadowId ) ) {
215
+ if ( exp . length === 1 ) {
216
+ if ( ! added && ( ! filter || filter ( node ) ) ) {
217
+ result . push ( node ) ;
218
+ added = true ;
219
+ }
220
+ } else {
221
+ let rest = exp . slice ( 1 ) ;
222
+ if ( [ ' ' , '>' ] . includes ( rest [ 0 ] . combinator ) === false ) {
223
+ throw new Error ( 'axe.utils.querySelectorAll does not support the combinator: ' + exp [ 1 ] . combinator ) ;
224
+ }
225
+ if ( rest [ 0 ] . combinator === '>' ) {
226
+ // add the rest to the childOnly array
227
+ childOnly . push ( rest ) ;
228
+ } else {
229
+ // add the rest to the childAny array
230
+ childAny . push ( rest ) ;
231
+ }
232
+ }
204
233
}
205
- candidates = candidates . reduce ( ( result , node ) => {
206
- return result . concat ( matchSelector ( index ? node . children : node , exp , recurse ) ) ;
207
- } , [ ] ) ;
208
- } ) ;
209
-
210
- // Ensure elements aren't added multiple times
211
- return candidates . reduce ( ( collected , candidate ) => {
212
- if ( collected . includes ( candidate ) === false ) {
213
- collected . push ( candidate ) ;
234
+ if ( currentLevel . anyLevel . includes ( exp ) &&
235
+ ( ! exp [ 0 ] . id || node . shadowId === currentLevel . parentShadowId ) ) {
236
+ childAny . push ( exp ) ;
214
237
}
215
- return collected ;
216
- } , collected ) ;
217
- } , [ ] ) ;
238
+ }
239
+ // "recurse"
240
+ if ( node . children && node . children . length && recurse ) {
241
+ stack . push ( currentLevel ) ;
242
+ currentLevel = createLocalVariables ( node . children , childAny , childOnly , node . shadowId ) ;
243
+ }
244
+ // check for "return"
245
+ while ( ! currentLevel . nodes . length && stack . length ) {
246
+ currentLevel = stack . pop ( ) ;
247
+ }
248
+ }
249
+ return result ;
218
250
} ;
219
251
220
252
/**
@@ -227,9 +259,26 @@ matchExpressions = function (domTree, expressions, recurse) {
227
259
* @return {NodeList } Elements matched by any of the selectors
228
260
*/
229
261
axe . utils . querySelectorAll = function ( domTree , selector ) {
262
+ return axe . utils . querySelectorAllFilter ( domTree , selector ) ;
263
+ } ;
264
+
265
+ /**
266
+ * querySelectorAllFilter implements querySelectorAll on the virtual DOM with
267
+ * ability to filter the returned nodes using an optional supplied filter function
268
+ *
269
+ * @method querySelectorAll
270
+ * @memberof axe.utils
271
+ * @instance
272
+ * @param {NodeList } domTree flattened tree collection to search
273
+ * @param {String } selector String containing one or more CSS selectors separated by commas
274
+ * @param {Function } filter function (optional)
275
+ * @return {NodeList } Elements matched by any of the selectors and filtered by the filter function
276
+ */
277
+
278
+ axe . utils . querySelectorAllFilter = function ( domTree , selector , filter ) {
230
279
domTree = Array . isArray ( domTree ) ? domTree : [ domTree ] ;
231
280
var expressions = axe . utils . cssParser . parse ( selector ) ;
232
281
expressions = expressions . selectors ? expressions . selectors : [ expressions ] ;
233
282
expressions = convertExpressions ( expressions ) ;
234
- return matchExpressions ( domTree , expressions , true ) ;
283
+ return matchExpressions ( domTree , expressions , true , domTree [ 0 ] . shadowId , filter ) ;
235
284
} ;
0 commit comments