@asamuzakjp/dom-selector 8.0.1 → 8.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +170 -155
- package/package.json +18 -11
- package/src/index.js +132 -66
- package/src/js/constant.js +106 -4
- package/src/js/finder.js +1149 -1049
- package/src/js/matcher.js +59 -24
- package/src/js/nwsapi.js +29 -11
- package/src/js/parser.js +16 -7
- package/src/js/selector.js +325 -0
- package/src/js/utility.js +155 -349
- package/types/index.d.ts +2 -1
- package/types/js/constant.d.ts +6 -0
- package/types/js/finder.d.ts +4 -1
- package/types/js/matcher.d.ts +2 -2
- package/types/js/parser.d.ts +1 -1
- package/types/js/selector.d.ts +12 -0
- package/types/js/utility.d.ts +5 -12
package/src/js/finder.js
CHANGED
|
@@ -13,21 +13,22 @@ import {
|
|
|
13
13
|
matchTypeSelector
|
|
14
14
|
} from './matcher.js';
|
|
15
15
|
import {
|
|
16
|
-
findAST,
|
|
17
16
|
generateCSS,
|
|
18
17
|
parseSelector,
|
|
19
18
|
sortAST,
|
|
20
19
|
unescapeSelector,
|
|
21
20
|
walkAST
|
|
22
21
|
} from './parser.js';
|
|
22
|
+
import { createHasValidator, isInvalidCombinator } from './selector.js';
|
|
23
23
|
import {
|
|
24
24
|
filterNodesByAnB,
|
|
25
|
-
|
|
25
|
+
findBestSeed,
|
|
26
26
|
generateException,
|
|
27
27
|
isCustomElement,
|
|
28
28
|
isFocusVisible,
|
|
29
29
|
isFocusableArea,
|
|
30
30
|
isVisible,
|
|
31
|
+
populateHasAllowlist,
|
|
31
32
|
resolveContent,
|
|
32
33
|
sortNodes,
|
|
33
34
|
traverseNode
|
|
@@ -41,6 +42,7 @@ import {
|
|
|
41
42
|
DOCUMENT_FRAGMENT_NODE,
|
|
42
43
|
ELEMENT_NODE,
|
|
43
44
|
FORM_PARTS,
|
|
45
|
+
HEX,
|
|
44
46
|
ID_SELECTOR,
|
|
45
47
|
INPUT_CHECK,
|
|
46
48
|
INPUT_DATE,
|
|
@@ -60,6 +62,8 @@ import {
|
|
|
60
62
|
TEXT_NODE,
|
|
61
63
|
TYPE_SELECTOR
|
|
62
64
|
} from './constant.js';
|
|
65
|
+
const ANB_FIRST = { a: 0, b: 1 };
|
|
66
|
+
const ANB_LAST = { a: 0, b: 1, reverse: true };
|
|
63
67
|
const DIR_NEXT = 'next';
|
|
64
68
|
const DIR_PREV = 'prev';
|
|
65
69
|
const KEYS_FORM = new Set([...FORM_PARTS, 'fieldset', 'form']);
|
|
@@ -105,17 +109,20 @@ const KEYS_PS_NTH_OF_TYPE = new Set([
|
|
|
105
109
|
*/
|
|
106
110
|
export class Finder {
|
|
107
111
|
/* private fields */
|
|
112
|
+
#anbCache;
|
|
108
113
|
#ast;
|
|
109
|
-
#astCache;
|
|
114
|
+
#astCache = new WeakMap();
|
|
110
115
|
#check;
|
|
116
|
+
#combinatorCache;
|
|
111
117
|
#descendant;
|
|
112
118
|
#document;
|
|
113
|
-
#documentCache;
|
|
119
|
+
#documentCache = new WeakMap();
|
|
114
120
|
#documentURL;
|
|
115
121
|
#event;
|
|
116
122
|
#eventHandlers;
|
|
117
123
|
#filterLeavesCache;
|
|
118
124
|
#focus;
|
|
125
|
+
#focusWithinCache;
|
|
119
126
|
#invalidate;
|
|
120
127
|
#invalidateResults;
|
|
121
128
|
#lastFocusVisible;
|
|
@@ -123,6 +130,17 @@ export class Finder {
|
|
|
123
130
|
#nodeWalker;
|
|
124
131
|
#nodes;
|
|
125
132
|
#noexcept;
|
|
133
|
+
#nthChildCache;
|
|
134
|
+
#nthChildOfCache;
|
|
135
|
+
#nthChildResultCache;
|
|
136
|
+
#nthOfTypeCache;
|
|
137
|
+
#nthOfTypeResultCache;
|
|
138
|
+
#psDefaultCache;
|
|
139
|
+
#psDirCache;
|
|
140
|
+
#psHasFilterCache;
|
|
141
|
+
#psIndeterminateCache;
|
|
142
|
+
#psLangCache;
|
|
143
|
+
#psValidCache;
|
|
126
144
|
#pseudoElement;
|
|
127
145
|
#results;
|
|
128
146
|
#root;
|
|
@@ -131,6 +149,7 @@ export class Finder {
|
|
|
131
149
|
#selector;
|
|
132
150
|
#selectorAST;
|
|
133
151
|
#shadow;
|
|
152
|
+
#targetWithinCache;
|
|
134
153
|
#verifyShadowHost;
|
|
135
154
|
#walkers;
|
|
136
155
|
#warn;
|
|
@@ -142,11 +161,6 @@ export class Finder {
|
|
|
142
161
|
*/
|
|
143
162
|
constructor(window) {
|
|
144
163
|
this.#window = window;
|
|
145
|
-
this.#astCache = new WeakMap();
|
|
146
|
-
this.#documentCache = new WeakMap();
|
|
147
|
-
this.#event = null;
|
|
148
|
-
this.#focus = null;
|
|
149
|
-
this.#lastFocusVisible = null;
|
|
150
164
|
this.#eventHandlers = new Set([
|
|
151
165
|
{
|
|
152
166
|
keys: ['focus', 'focusin'],
|
|
@@ -161,7 +175,6 @@ export class Finder {
|
|
|
161
175
|
handler: this._handleMouseEvent
|
|
162
176
|
}
|
|
163
177
|
]);
|
|
164
|
-
this.#filterLeavesCache = new WeakMap();
|
|
165
178
|
this._registerEventListeners();
|
|
166
179
|
this.clearResults(true);
|
|
167
180
|
}
|
|
@@ -218,7 +231,7 @@ export class Finder {
|
|
|
218
231
|
this.#node !== this.#root && this.#node.nodeType === ELEMENT_NODE;
|
|
219
232
|
this.#selector = selector;
|
|
220
233
|
this.#pseudoElement = [];
|
|
221
|
-
this.#walkers =
|
|
234
|
+
this.#walkers = null;
|
|
222
235
|
this.#nodeWalker = null;
|
|
223
236
|
this.#rootWalker = null;
|
|
224
237
|
this.#verifyShadowHost = null;
|
|
@@ -228,14 +241,29 @@ export class Finder {
|
|
|
228
241
|
|
|
229
242
|
/**
|
|
230
243
|
* Clear cached results.
|
|
231
|
-
* @param {boolean} all -
|
|
244
|
+
* @param {boolean} all - Clear all results.
|
|
232
245
|
* @returns {void}
|
|
233
246
|
*/
|
|
234
247
|
clearResults = (all = false) => {
|
|
235
|
-
this.#
|
|
248
|
+
this.#anbCache = null;
|
|
249
|
+
this.#combinatorCache = null;
|
|
250
|
+
this.#focusWithinCache = null;
|
|
251
|
+
this.#invalidateResults = null;
|
|
252
|
+
this.#nthChildCache = null;
|
|
253
|
+
this.#nthChildOfCache = null;
|
|
254
|
+
this.#nthChildResultCache = null;
|
|
255
|
+
this.#nthOfTypeCache = null;
|
|
256
|
+
this.#nthOfTypeResultCache = null;
|
|
257
|
+
this.#psDefaultCache = null;
|
|
258
|
+
this.#psDirCache = null;
|
|
259
|
+
this.#psHasFilterCache = null;
|
|
260
|
+
this.#psIndeterminateCache = null;
|
|
261
|
+
this.#psLangCache = null;
|
|
262
|
+
this.#psValidCache = null;
|
|
263
|
+
this.#targetWithinCache = null;
|
|
236
264
|
if (all) {
|
|
265
|
+
this.#filterLeavesCache = null;
|
|
237
266
|
this.#results = new WeakMap();
|
|
238
|
-
this.#filterLeavesCache = new WeakMap();
|
|
239
267
|
}
|
|
240
268
|
};
|
|
241
269
|
|
|
@@ -300,28 +328,26 @@ export class Finder {
|
|
|
300
328
|
* @private
|
|
301
329
|
* @param {Array.<Array.<object>>} branches - The branches from walkAST.
|
|
302
330
|
* @param {string} selector - The original selector for error reporting.
|
|
303
|
-
* @returns {{ast: Array, descendant: boolean}}
|
|
304
|
-
* An object with the AST, descendant flag.
|
|
331
|
+
* @returns {{ast: Array, descendant: boolean}} An object with the AST, descendant flag.
|
|
305
332
|
*/
|
|
306
333
|
_processSelectorBranches = (branches, selector) => {
|
|
307
334
|
let descendant = false;
|
|
308
335
|
const ast = [];
|
|
309
|
-
const
|
|
310
|
-
for (let i = 0; i < l; i++) {
|
|
311
|
-
const items = [...branches[i]];
|
|
336
|
+
for (const items of branches) {
|
|
312
337
|
const branch = [];
|
|
313
|
-
let
|
|
314
|
-
|
|
338
|
+
let prevType = null;
|
|
339
|
+
const itemsLen = items.length;
|
|
340
|
+
if (itemsLen) {
|
|
315
341
|
const leaves = new Set();
|
|
316
|
-
|
|
342
|
+
for (let j = 0; j < itemsLen; j++) {
|
|
343
|
+
const item = items[j];
|
|
344
|
+
const isLast = j === itemsLen - 1;
|
|
345
|
+
if (isInvalidCombinator(item.type, prevType, isLast)) {
|
|
346
|
+
const msg = `Invalid selector ${selector}`;
|
|
347
|
+
this.onError(generateException(msg, SYNTAX_ERR, this.#window));
|
|
348
|
+
return { ast: [], descendant: false, invalidate: false };
|
|
349
|
+
}
|
|
317
350
|
if (item.type === COMBINATOR) {
|
|
318
|
-
const [nextItem] = items;
|
|
319
|
-
if (!nextItem || nextItem.type === COMBINATOR) {
|
|
320
|
-
const msg = `Invalid selector ${selector}`;
|
|
321
|
-
this.onError(generateException(msg, SYNTAX_ERR, this.#window));
|
|
322
|
-
// Stop processing on invalid selector.
|
|
323
|
-
return { ast: [], descendant: false, invalidate: false };
|
|
324
|
-
}
|
|
325
351
|
if (item.name === ' ' || item.name === '>') {
|
|
326
352
|
descendant = true;
|
|
327
353
|
}
|
|
@@ -339,12 +365,10 @@ export class Finder {
|
|
|
339
365
|
}
|
|
340
366
|
leaves.add(item);
|
|
341
367
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
} else {
|
|
368
|
+
prevType = item.type;
|
|
369
|
+
if (isLast) {
|
|
345
370
|
branch.push({ combo: null, leaves: sortAST(leaves) });
|
|
346
371
|
leaves.clear();
|
|
347
|
-
break;
|
|
348
372
|
}
|
|
349
373
|
}
|
|
350
374
|
}
|
|
@@ -384,16 +408,22 @@ export class Finder {
|
|
|
384
408
|
}
|
|
385
409
|
} else {
|
|
386
410
|
this.#selectorAST = parseSelector(selector);
|
|
387
|
-
const { branches, info } = walkAST(
|
|
411
|
+
const { branches, info } = walkAST(
|
|
412
|
+
this.#selectorAST,
|
|
413
|
+
true,
|
|
414
|
+
createHasValidator(this.#window)
|
|
415
|
+
);
|
|
388
416
|
const {
|
|
389
417
|
hasHasPseudoFunc,
|
|
390
418
|
hasLogicalPseudoFunc,
|
|
391
419
|
hasNthChildOfSelector,
|
|
392
|
-
hasStatePseudoClass
|
|
420
|
+
hasStatePseudoClass,
|
|
421
|
+
hasUnsupportedPseudoClass
|
|
393
422
|
} = info;
|
|
394
423
|
this.#invalidate =
|
|
395
424
|
hasHasPseudoFunc ||
|
|
396
425
|
hasStatePseudoClass ||
|
|
426
|
+
hasUnsupportedPseudoClass ||
|
|
397
427
|
!!(hasLogicalPseudoFunc && hasNthChildOfSelector);
|
|
398
428
|
const processed = this._processSelectorBranches(branches, selector);
|
|
399
429
|
ast = processed.ast;
|
|
@@ -432,7 +462,11 @@ export class Finder {
|
|
|
432
462
|
const { force = false, whatToShow = SHOW_CONTAINER } = opt;
|
|
433
463
|
if (force) {
|
|
434
464
|
return this.#document.createTreeWalker(node, whatToShow);
|
|
435
|
-
}
|
|
465
|
+
}
|
|
466
|
+
if (!this.#walkers) {
|
|
467
|
+
this.#walkers = new WeakMap();
|
|
468
|
+
}
|
|
469
|
+
if (this.#walkers.has(node)) {
|
|
436
470
|
return this.#walkers.get(node);
|
|
437
471
|
}
|
|
438
472
|
const walker = this.#document.createTreeWalker(node, whatToShow);
|
|
@@ -499,7 +533,7 @@ export class Finder {
|
|
|
499
533
|
* @param {number} anb.a - The 'a' value.
|
|
500
534
|
* @param {number} anb.b - The 'b' value.
|
|
501
535
|
* @param {boolean} [anb.reverse] - If true, reverses the order.
|
|
502
|
-
* @param {object} [anb.selector] - The
|
|
536
|
+
* @param {object} [anb.selector] - The selector for 'of S'.
|
|
503
537
|
* @param {object} node - The Element node.
|
|
504
538
|
* @param {object} opt - Options.
|
|
505
539
|
* @returns {Set.<object>} A collection of matched nodes.
|
|
@@ -526,16 +560,48 @@ export class Finder {
|
|
|
526
560
|
}
|
|
527
561
|
return matchedNode;
|
|
528
562
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
const
|
|
538
|
-
|
|
563
|
+
if (!this.#nthChildResultCache) {
|
|
564
|
+
this.#nthChildResultCache = new WeakMap();
|
|
565
|
+
}
|
|
566
|
+
let parentResultCache = this.#nthChildResultCache.get(parentNode);
|
|
567
|
+
if (!parentResultCache) {
|
|
568
|
+
parentResultCache = new WeakMap();
|
|
569
|
+
this.#nthChildResultCache.set(parentNode, parentResultCache);
|
|
570
|
+
}
|
|
571
|
+
const cachedSet = parentResultCache.get(anb);
|
|
572
|
+
if (cachedSet) {
|
|
573
|
+
return cachedSet;
|
|
574
|
+
}
|
|
575
|
+
let siblings;
|
|
576
|
+
if (selector) {
|
|
577
|
+
if (!this.#nthChildOfCache) {
|
|
578
|
+
this.#nthChildOfCache = new WeakMap();
|
|
579
|
+
}
|
|
580
|
+
let parentOfCacheMap = this.#nthChildOfCache.get(parentNode);
|
|
581
|
+
if (!parentOfCacheMap) {
|
|
582
|
+
parentOfCacheMap = new Map();
|
|
583
|
+
this.#nthChildOfCache.set(parentNode, parentOfCacheMap);
|
|
584
|
+
}
|
|
585
|
+
siblings = parentOfCacheMap.get(selector);
|
|
586
|
+
if (!siblings) {
|
|
587
|
+
const selectorBranches = this._getSelectorBranches(selector);
|
|
588
|
+
siblings = this._getFilteredChildren(parentNode, selectorBranches, opt);
|
|
589
|
+
parentOfCacheMap.set(selector, siblings);
|
|
590
|
+
}
|
|
591
|
+
} else {
|
|
592
|
+
if (!this.#nthChildCache) {
|
|
593
|
+
this.#nthChildCache = new WeakMap();
|
|
594
|
+
}
|
|
595
|
+
siblings = this.#nthChildCache.get(parentNode);
|
|
596
|
+
if (!siblings) {
|
|
597
|
+
siblings = this._getFilteredChildren(parentNode, null, opt);
|
|
598
|
+
this.#nthChildCache.set(parentNode, siblings);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
const matchedNodes = filterNodesByAnB(siblings, anb);
|
|
602
|
+
const resultSet = new Set(matchedNodes);
|
|
603
|
+
parentResultCache.set(anb, resultSet);
|
|
604
|
+
return resultSet;
|
|
539
605
|
};
|
|
540
606
|
|
|
541
607
|
/**
|
|
@@ -549,27 +615,59 @@ export class Finder {
|
|
|
549
615
|
* @returns {Set.<object>} A collection of matched nodes.
|
|
550
616
|
*/
|
|
551
617
|
_collectNthOfType = (anb, node) => {
|
|
552
|
-
const { parentNode } = node;
|
|
618
|
+
const { localName, namespaceURI, parentNode, prefix } = node;
|
|
553
619
|
if (!parentNode) {
|
|
554
620
|
if (node === this.#root && anb.a * 1 + anb.b * 1 === 1) {
|
|
555
621
|
return new Set([node]);
|
|
556
622
|
}
|
|
557
623
|
return new Set();
|
|
558
624
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
625
|
+
if (!this.#nthOfTypeResultCache) {
|
|
626
|
+
this.#nthOfTypeResultCache = new WeakMap();
|
|
627
|
+
}
|
|
628
|
+
let parentResultCache = this.#nthOfTypeResultCache.get(parentNode);
|
|
629
|
+
if (!parentResultCache) {
|
|
630
|
+
parentResultCache = new WeakMap();
|
|
631
|
+
this.#nthOfTypeResultCache.set(parentNode, parentResultCache);
|
|
632
|
+
}
|
|
633
|
+
let typeResultMap = parentResultCache.get(anb);
|
|
634
|
+
if (!typeResultMap) {
|
|
635
|
+
typeResultMap = new Map();
|
|
636
|
+
parentResultCache.set(anb, typeResultMap);
|
|
637
|
+
}
|
|
638
|
+
const typeKey = `${namespaceURI || ''}|${prefix || ''}|${localName}`;
|
|
639
|
+
const cachedSet = typeResultMap.get(typeKey);
|
|
640
|
+
if (cachedSet) {
|
|
641
|
+
return cachedSet;
|
|
642
|
+
}
|
|
643
|
+
if (!this.#nthOfTypeCache) {
|
|
644
|
+
this.#nthOfTypeCache = new WeakMap();
|
|
645
|
+
}
|
|
646
|
+
let typeMap = this.#nthOfTypeCache.get(parentNode);
|
|
647
|
+
if (!typeMap) {
|
|
648
|
+
typeMap = new Map();
|
|
649
|
+
this.#nthOfTypeCache.set(parentNode, typeMap);
|
|
650
|
+
}
|
|
651
|
+
let typedSiblings = typeMap.get(typeKey);
|
|
652
|
+
if (!typedSiblings) {
|
|
653
|
+
typedSiblings = [];
|
|
654
|
+
let sibling = parentNode.firstElementChild;
|
|
655
|
+
while (sibling) {
|
|
656
|
+
if (
|
|
657
|
+
sibling.localName === localName &&
|
|
658
|
+
sibling.namespaceURI === namespaceURI &&
|
|
659
|
+
sibling.prefix === prefix
|
|
660
|
+
) {
|
|
661
|
+
typedSiblings.push(sibling);
|
|
662
|
+
}
|
|
663
|
+
sibling = sibling.nextElementSibling;
|
|
568
664
|
}
|
|
569
|
-
|
|
665
|
+
typeMap.set(typeKey, typedSiblings);
|
|
570
666
|
}
|
|
571
667
|
const matchedNodes = filterNodesByAnB(typedSiblings, anb);
|
|
572
|
-
|
|
668
|
+
const resultSet = new Set(matchedNodes);
|
|
669
|
+
typeResultMap.set(typeKey, resultSet);
|
|
670
|
+
return resultSet;
|
|
573
671
|
};
|
|
574
672
|
|
|
575
673
|
/**
|
|
@@ -579,53 +677,61 @@ export class Finder {
|
|
|
579
677
|
* @param {object} node - The Element node.
|
|
580
678
|
* @param {string} nthName - The name of the nth pseudo-class.
|
|
581
679
|
* @param {object} opt - Options.
|
|
582
|
-
* @returns {
|
|
680
|
+
* @returns {boolean} True if matches, otherwise false.
|
|
583
681
|
*/
|
|
584
682
|
_matchAnPlusB = (ast, node, nthName, opt) => {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
}
|
|
607
|
-
if (typeof b === 'string' && /-?\d+/.test(b)) {
|
|
608
|
-
anbMap.set('b', b * 1);
|
|
683
|
+
if (!this.#anbCache) {
|
|
684
|
+
this.#anbCache = new WeakMap();
|
|
685
|
+
}
|
|
686
|
+
let anb = this.#anbCache.get(ast);
|
|
687
|
+
if (!anb) {
|
|
688
|
+
const {
|
|
689
|
+
nth: { a, b, name: nthIdentName },
|
|
690
|
+
selector
|
|
691
|
+
} = ast;
|
|
692
|
+
const anbMap = new Map();
|
|
693
|
+
if (nthIdentName) {
|
|
694
|
+
if (nthIdentName === 'even') {
|
|
695
|
+
anbMap.set('a', 2);
|
|
696
|
+
anbMap.set('b', 0);
|
|
697
|
+
} else if (nthIdentName === 'odd') {
|
|
698
|
+
anbMap.set('a', 2);
|
|
699
|
+
anbMap.set('b', 1);
|
|
700
|
+
}
|
|
701
|
+
if (nthName.indexOf('last') > -1) {
|
|
702
|
+
anbMap.set('reverse', true);
|
|
703
|
+
}
|
|
609
704
|
} else {
|
|
610
|
-
|
|
705
|
+
if (typeof a === 'string' && /-?\d+/.test(a)) {
|
|
706
|
+
anbMap.set('a', a * 1);
|
|
707
|
+
} else {
|
|
708
|
+
anbMap.set('a', 0);
|
|
709
|
+
}
|
|
710
|
+
if (typeof b === 'string' && /-?\d+/.test(b)) {
|
|
711
|
+
anbMap.set('b', b * 1);
|
|
712
|
+
} else {
|
|
713
|
+
anbMap.set('b', 0);
|
|
714
|
+
}
|
|
715
|
+
if (nthName.indexOf('last') > -1) {
|
|
716
|
+
anbMap.set('reverse', true);
|
|
717
|
+
}
|
|
611
718
|
}
|
|
612
|
-
if (nthName
|
|
613
|
-
|
|
719
|
+
if (nthName === 'nth-child' || nthName === 'nth-last-child') {
|
|
720
|
+
if (selector) {
|
|
721
|
+
anbMap.set('selector', selector);
|
|
722
|
+
}
|
|
614
723
|
}
|
|
724
|
+
anb = Object.fromEntries(anbMap);
|
|
725
|
+
this.#anbCache.set(ast, anb);
|
|
615
726
|
}
|
|
616
727
|
if (nthName === 'nth-child' || nthName === 'nth-last-child') {
|
|
617
|
-
if (selector) {
|
|
618
|
-
anbMap.set('selector', selector);
|
|
619
|
-
}
|
|
620
|
-
const anb = Object.fromEntries(anbMap);
|
|
621
728
|
const nodes = this._collectNthChild(anb, node, opt);
|
|
622
|
-
return nodes;
|
|
729
|
+
return nodes.has(node);
|
|
623
730
|
} else if (nthName === 'nth-of-type' || nthName === 'nth-last-of-type') {
|
|
624
|
-
const anb = Object.fromEntries(anbMap);
|
|
625
731
|
const nodes = this._collectNthOfType(anb, node);
|
|
626
|
-
return nodes;
|
|
732
|
+
return nodes.has(node);
|
|
627
733
|
}
|
|
628
|
-
return
|
|
734
|
+
return false;
|
|
629
735
|
};
|
|
630
736
|
|
|
631
737
|
/**
|
|
@@ -637,55 +743,113 @@ export class Finder {
|
|
|
637
743
|
* @returns {boolean} The result.
|
|
638
744
|
*/
|
|
639
745
|
_matchHasPseudoFunc = (astLeaves, node, opt = {}) => {
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
}
|
|
654
|
-
const twigLeaves = [];
|
|
655
|
-
while (leaves.length) {
|
|
656
|
-
const [item] = leaves;
|
|
657
|
-
const { type: itemType } = item;
|
|
658
|
-
if (itemType === COMBINATOR) {
|
|
659
|
-
break;
|
|
660
|
-
} else {
|
|
661
|
-
twigLeaves.push(leaves.shift());
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
const twig = {
|
|
665
|
-
combo,
|
|
666
|
-
leaves: twigLeaves
|
|
746
|
+
const l = astLeaves.length;
|
|
747
|
+
if (!l) {
|
|
748
|
+
return false;
|
|
749
|
+
}
|
|
750
|
+
let startIndex = 0;
|
|
751
|
+
let combo;
|
|
752
|
+
if (astLeaves[0].type === COMBINATOR) {
|
|
753
|
+
combo = astLeaves[0];
|
|
754
|
+
startIndex = 1;
|
|
755
|
+
} else {
|
|
756
|
+
combo = {
|
|
757
|
+
name: ' ',
|
|
758
|
+
type: COMBINATOR
|
|
667
759
|
};
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
760
|
+
startIndex = 0;
|
|
761
|
+
}
|
|
762
|
+
const twigLeaves = [];
|
|
763
|
+
let nextComboIndex = startIndex;
|
|
764
|
+
for (; nextComboIndex < l; nextComboIndex++) {
|
|
765
|
+
if (astLeaves[nextComboIndex].type === COMBINATOR) {
|
|
766
|
+
break;
|
|
767
|
+
}
|
|
768
|
+
twigLeaves.push(astLeaves[nextComboIndex]);
|
|
769
|
+
}
|
|
770
|
+
const twig = {
|
|
771
|
+
combo,
|
|
772
|
+
leaves: twigLeaves
|
|
773
|
+
};
|
|
774
|
+
opt.dir = DIR_NEXT;
|
|
775
|
+
const nodes = this._collectCombinatorMatches(twig, node, opt, []);
|
|
776
|
+
if (nodes.length) {
|
|
777
|
+
if (nextComboIndex < l) {
|
|
778
|
+
let bool = false;
|
|
779
|
+
const remainingLeaves = astLeaves.slice(nextComboIndex);
|
|
780
|
+
for (const nextNode of nodes) {
|
|
781
|
+
bool = this._matchHasPseudoFunc(remainingLeaves, nextNode, opt);
|
|
782
|
+
if (bool) {
|
|
783
|
+
break;
|
|
678
784
|
}
|
|
679
|
-
return bool;
|
|
680
785
|
}
|
|
681
|
-
return
|
|
786
|
+
return bool;
|
|
682
787
|
}
|
|
788
|
+
return true;
|
|
683
789
|
}
|
|
684
790
|
return false;
|
|
685
791
|
};
|
|
686
792
|
|
|
687
793
|
/**
|
|
688
|
-
*
|
|
794
|
+
* Builds an Allowlist for the :has() branch using a sparse seed element.
|
|
795
|
+
* @private
|
|
796
|
+
* @param {Array} leaves - The AST leaves of the selector branch.
|
|
797
|
+
* @returns {object|null} The wrapper object containing the WeakSet, or null.
|
|
798
|
+
*/
|
|
799
|
+
_buildHasAllowlist = leaves => {
|
|
800
|
+
const { seed } = findBestSeed(leaves);
|
|
801
|
+
if (!seed) {
|
|
802
|
+
return null;
|
|
803
|
+
}
|
|
804
|
+
if (this.#shadow || this.#node.nodeType === DOCUMENT_FRAGMENT_NODE) {
|
|
805
|
+
return null;
|
|
806
|
+
}
|
|
807
|
+
let seedElements = null;
|
|
808
|
+
let isSingleNode = false;
|
|
809
|
+
if (seed.type === 'id') {
|
|
810
|
+
if (typeof this.#root.getElementById === 'function') {
|
|
811
|
+
const node = this.#root.getElementById(seed.value);
|
|
812
|
+
if (node) {
|
|
813
|
+
seedElements = node;
|
|
814
|
+
isSingleNode = true;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
} else if (seed.type === 'class') {
|
|
818
|
+
if (typeof this.#root.getElementsByClassName === 'function') {
|
|
819
|
+
seedElements = this.#root.getElementsByClassName(seed.value);
|
|
820
|
+
}
|
|
821
|
+
} else if (seed.type === 'tag') {
|
|
822
|
+
if (typeof this.#root.getElementsByTagName === 'function') {
|
|
823
|
+
seedElements = this.#root.getElementsByTagName(seed.value);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
if (!seedElements) {
|
|
827
|
+
return null;
|
|
828
|
+
}
|
|
829
|
+
const len = isSingleNode ? 1 : seedElements.length;
|
|
830
|
+
if (len === 0 || len > HEX * HEX) {
|
|
831
|
+
return null;
|
|
832
|
+
}
|
|
833
|
+
const filterResult = {
|
|
834
|
+
seeded: true,
|
|
835
|
+
set: new WeakSet()
|
|
836
|
+
};
|
|
837
|
+
const list = filterResult.set;
|
|
838
|
+
const visitedAncestors = new Set();
|
|
839
|
+
if (this.#node) {
|
|
840
|
+
list.add(this.#node);
|
|
841
|
+
}
|
|
842
|
+
for (let i = 0; i < len; i++) {
|
|
843
|
+
const current = isSingleNode ? seedElements : seedElements[i];
|
|
844
|
+
if (current) {
|
|
845
|
+
populateHasAllowlist(current, list, visitedAncestors);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
return filterResult;
|
|
849
|
+
};
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
* Evaluates :has() pseudo-class.
|
|
689
853
|
* @private
|
|
690
854
|
* @param {object} astData - The AST data.
|
|
691
855
|
* @param {object} node - The Element node.
|
|
@@ -695,9 +859,28 @@ export class Finder {
|
|
|
695
859
|
_evaluateHasPseudo = (astData, node, opt = {}) => {
|
|
696
860
|
const { branches } = astData;
|
|
697
861
|
let bool = false;
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
862
|
+
if (!this.#psHasFilterCache) {
|
|
863
|
+
this.#psHasFilterCache = new WeakMap();
|
|
864
|
+
}
|
|
865
|
+
let rootCache = this.#psHasFilterCache.get(this.#root);
|
|
866
|
+
if (!rootCache) {
|
|
867
|
+
rootCache = new WeakMap();
|
|
868
|
+
this.#psHasFilterCache.set(this.#root, rootCache);
|
|
869
|
+
}
|
|
870
|
+
for (const leaves of branches) {
|
|
871
|
+
if (!rootCache.has(leaves)) {
|
|
872
|
+
const filterResult = this._buildHasAllowlist(leaves);
|
|
873
|
+
rootCache.set(leaves, filterResult);
|
|
874
|
+
}
|
|
875
|
+
const allowlist = rootCache.get(leaves);
|
|
876
|
+
if (
|
|
877
|
+
allowlist &&
|
|
878
|
+
allowlist.seeded &&
|
|
879
|
+
node.nodeType !== DOCUMENT_FRAGMENT_NODE &&
|
|
880
|
+
!allowlist.set.has(node)
|
|
881
|
+
) {
|
|
882
|
+
continue;
|
|
883
|
+
}
|
|
701
884
|
bool = this._matchHasPseudoFunc(leaves, node, opt);
|
|
702
885
|
if (bool) {
|
|
703
886
|
break;
|
|
@@ -721,13 +904,13 @@ export class Finder {
|
|
|
721
904
|
* @param {object} astData - The AST data.
|
|
722
905
|
* @param {object} node - The Element node.
|
|
723
906
|
* @param {object} [opt] - Options.
|
|
724
|
-
* @returns {
|
|
907
|
+
* @returns {boolean} Tru if matches, otherwise false.
|
|
725
908
|
*/
|
|
726
909
|
_matchLogicalPseudoFunc = (astData, node, opt = {}) => {
|
|
727
910
|
const { astName, branches, twigBranches } = astData;
|
|
728
911
|
// Handle :has().
|
|
729
912
|
if (astName === 'has') {
|
|
730
|
-
return this._evaluateHasPseudo(astData, node, opt);
|
|
913
|
+
return this._evaluateHasPseudo(astData, node, opt) === node;
|
|
731
914
|
}
|
|
732
915
|
// Handle :is(), :not(), :where().
|
|
733
916
|
const isShadowRoot =
|
|
@@ -749,7 +932,7 @@ export class Finder {
|
|
|
749
932
|
}
|
|
750
933
|
}
|
|
751
934
|
if (invalid) {
|
|
752
|
-
return
|
|
935
|
+
return false;
|
|
753
936
|
}
|
|
754
937
|
}
|
|
755
938
|
opt.forgive = astName === 'is' || astName === 'where';
|
|
@@ -786,658 +969,376 @@ export class Finder {
|
|
|
786
969
|
}
|
|
787
970
|
}
|
|
788
971
|
if (astName === 'not') {
|
|
789
|
-
|
|
790
|
-
return null;
|
|
791
|
-
}
|
|
792
|
-
return node;
|
|
793
|
-
} else if (bool) {
|
|
794
|
-
return node;
|
|
972
|
+
return !bool;
|
|
795
973
|
}
|
|
796
|
-
return
|
|
974
|
+
return bool;
|
|
797
975
|
};
|
|
798
976
|
|
|
799
977
|
/**
|
|
800
|
-
*
|
|
978
|
+
* Evaluates logical pseudo-class selector.
|
|
801
979
|
* @private
|
|
802
|
-
* @see https://html.spec.whatwg.org/#pseudo-classes
|
|
803
980
|
* @param {object} ast - The AST.
|
|
804
981
|
* @param {object} node - The Element node.
|
|
805
982
|
* @param {object} [opt] - Options.
|
|
806
983
|
* @param {boolean} [opt.forgive] - Ignores unknown or invalid selectors.
|
|
807
984
|
* @param {boolean} [opt.warn] - If true, console warnings are enabled.
|
|
808
|
-
* @returns {
|
|
985
|
+
* @returns {boolean} True if matches, otherwise false.
|
|
809
986
|
*/
|
|
810
|
-
|
|
987
|
+
_evaluateLogicalPseudo(ast, node, opt = {}) {
|
|
811
988
|
const { children: astChildren, name: astName } = ast;
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
if (
|
|
824
|
-
astData =
|
|
989
|
+
if (!astChildren.length && astName !== 'is' && astName !== 'where') {
|
|
990
|
+
const css = generateCSS(ast);
|
|
991
|
+
const msg = `Invalid selector ${css}`;
|
|
992
|
+
this.onError(generateException(msg, SYNTAX_ERR, this.#window));
|
|
993
|
+
return false;
|
|
994
|
+
}
|
|
995
|
+
let astData;
|
|
996
|
+
if (this.#astCache.has(ast)) {
|
|
997
|
+
astData = this.#astCache.get(ast);
|
|
998
|
+
} else {
|
|
999
|
+
const { branches } = walkAST(ast);
|
|
1000
|
+
if (astName === 'has') {
|
|
1001
|
+
astData = {
|
|
1002
|
+
astName,
|
|
1003
|
+
branches
|
|
1004
|
+
};
|
|
825
1005
|
} else {
|
|
826
|
-
const
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
const
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
generateException(msg, SYNTAX_ERR, this.#window)
|
|
844
|
-
);
|
|
845
|
-
}
|
|
1006
|
+
const twigBranches = [];
|
|
1007
|
+
const l = branches.length;
|
|
1008
|
+
for (let i = 0; i < l; i++) {
|
|
1009
|
+
const leaves = branches[i];
|
|
1010
|
+
const branch = [];
|
|
1011
|
+
const leavesSet = new Set();
|
|
1012
|
+
const leavesLen = leaves.length;
|
|
1013
|
+
for (let j = 0; j < leavesLen; j++) {
|
|
1014
|
+
const item = leaves[j];
|
|
1015
|
+
if (item.type === COMBINATOR) {
|
|
1016
|
+
branch.push({
|
|
1017
|
+
combo: item,
|
|
1018
|
+
leaves: [...leavesSet]
|
|
1019
|
+
});
|
|
1020
|
+
leavesSet.clear();
|
|
1021
|
+
} else {
|
|
1022
|
+
leavesSet.add(item);
|
|
846
1023
|
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
branches
|
|
854
|
-
};
|
|
855
|
-
} else {
|
|
856
|
-
const twigBranches = [];
|
|
857
|
-
const l = branches.length;
|
|
858
|
-
for (let i = 0; i < l; i++) {
|
|
859
|
-
const [...leaves] = branches[i];
|
|
860
|
-
const branch = [];
|
|
861
|
-
const leavesSet = new Set();
|
|
862
|
-
let item = leaves.shift();
|
|
863
|
-
while (item) {
|
|
864
|
-
if (item.type === COMBINATOR) {
|
|
865
|
-
branch.push({
|
|
866
|
-
combo: item,
|
|
867
|
-
leaves: [...leavesSet]
|
|
868
|
-
});
|
|
869
|
-
leavesSet.clear();
|
|
870
|
-
} else if (item) {
|
|
871
|
-
leavesSet.add(item);
|
|
872
|
-
}
|
|
873
|
-
if (leaves.length) {
|
|
874
|
-
item = leaves.shift();
|
|
875
|
-
} else {
|
|
876
|
-
branch.push({
|
|
877
|
-
combo: null,
|
|
878
|
-
leaves: [...leavesSet]
|
|
879
|
-
});
|
|
880
|
-
leavesSet.clear();
|
|
881
|
-
break;
|
|
882
|
-
}
|
|
1024
|
+
if (j === leavesLen - 1) {
|
|
1025
|
+
branch.push({
|
|
1026
|
+
combo: null,
|
|
1027
|
+
leaves: [...leavesSet]
|
|
1028
|
+
});
|
|
1029
|
+
leavesSet.clear();
|
|
883
1030
|
}
|
|
884
|
-
twigBranches.push(branch);
|
|
885
1031
|
}
|
|
886
|
-
|
|
887
|
-
astName,
|
|
888
|
-
branches,
|
|
889
|
-
twigBranches
|
|
890
|
-
};
|
|
891
|
-
this.#astCache.set(ast, astData);
|
|
1032
|
+
twigBranches.push(branch);
|
|
892
1033
|
}
|
|
1034
|
+
astData = {
|
|
1035
|
+
astName,
|
|
1036
|
+
branches,
|
|
1037
|
+
twigBranches
|
|
1038
|
+
};
|
|
893
1039
|
}
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
1040
|
+
this.#astCache.set(ast, astData);
|
|
1041
|
+
}
|
|
1042
|
+
return this._matchLogicalPseudoFunc(astData, node, opt);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
/**
|
|
1046
|
+
* Evaluates pseudo-class function.
|
|
1047
|
+
* @private
|
|
1048
|
+
* @see https://html.spec.whatwg.org/#pseudo-classes
|
|
1049
|
+
* @param {object} ast - The AST.
|
|
1050
|
+
* @param {object} node - The Element node.
|
|
1051
|
+
* @param {object} [opt] - Options.
|
|
1052
|
+
* @param {boolean} [opt.forgive] - Ignores unknown or invalid selectors.
|
|
1053
|
+
* @param {boolean} [opt.warn] - If true, console warnings are enabled.
|
|
1054
|
+
* @returns {boolean} True if matches, otherwise false.
|
|
1055
|
+
*/
|
|
1056
|
+
_evaluatePseudoClassFunc(ast, node, opt = {}) {
|
|
1057
|
+
const { children: astChildren, name: astName } = ast;
|
|
1058
|
+
const { forgive, warn = this.#warn } = opt;
|
|
1059
|
+
// :nth-child(), :nth-last-child(), nth-of-type(), :nth-last-of-type()
|
|
1060
|
+
if (/^nth-(?:last-)?(?:child|of-type)$/.test(astName)) {
|
|
1061
|
+
if (astChildren.length !== 1) {
|
|
1062
|
+
const css = generateCSS(ast);
|
|
1063
|
+
this.onError(
|
|
1064
|
+
generateException(`Invalid selector ${css}`, SYNTAX_ERR, this.#window)
|
|
1065
|
+
);
|
|
1066
|
+
return false;
|
|
897
1067
|
}
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
1068
|
+
const [branch] = astChildren;
|
|
1069
|
+
return this._matchAnPlusB(branch, node, astName, opt);
|
|
1070
|
+
}
|
|
1071
|
+
switch (astName) {
|
|
1072
|
+
// :dir()
|
|
1073
|
+
case 'dir': {
|
|
901
1074
|
if (astChildren.length !== 1) {
|
|
902
1075
|
const css = generateCSS(ast);
|
|
903
|
-
|
|
1076
|
+
this.onError(
|
|
904
1077
|
generateException(
|
|
905
1078
|
`Invalid selector ${css}`,
|
|
906
1079
|
SYNTAX_ERR,
|
|
907
1080
|
this.#window
|
|
908
1081
|
)
|
|
909
1082
|
);
|
|
1083
|
+
return false;
|
|
910
1084
|
}
|
|
911
|
-
const [
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
} else {
|
|
915
|
-
switch (astName) {
|
|
916
|
-
// :dir()
|
|
917
|
-
case 'dir': {
|
|
918
|
-
if (astChildren.length !== 1) {
|
|
919
|
-
const css = generateCSS(ast);
|
|
920
|
-
return this.onError(
|
|
921
|
-
generateException(
|
|
922
|
-
`Invalid selector ${css}`,
|
|
923
|
-
SYNTAX_ERR,
|
|
924
|
-
this.#window
|
|
925
|
-
)
|
|
926
|
-
);
|
|
927
|
-
}
|
|
928
|
-
const [astChild] = astChildren;
|
|
929
|
-
const res = matchDirectionPseudoClass(astChild, node);
|
|
930
|
-
if (res) {
|
|
931
|
-
matched.add(node);
|
|
932
|
-
}
|
|
933
|
-
break;
|
|
934
|
-
}
|
|
935
|
-
// :lang()
|
|
936
|
-
case 'lang': {
|
|
937
|
-
if (!astChildren.length) {
|
|
938
|
-
const css = generateCSS(ast);
|
|
939
|
-
return this.onError(
|
|
940
|
-
generateException(
|
|
941
|
-
`Invalid selector ${css}`,
|
|
942
|
-
SYNTAX_ERR,
|
|
943
|
-
this.#window
|
|
944
|
-
)
|
|
945
|
-
);
|
|
946
|
-
}
|
|
947
|
-
let bool;
|
|
948
|
-
for (const astChild of astChildren) {
|
|
949
|
-
bool = matchLanguagePseudoClass(astChild, node);
|
|
950
|
-
if (bool) {
|
|
951
|
-
break;
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
if (bool) {
|
|
955
|
-
matched.add(node);
|
|
956
|
-
}
|
|
957
|
-
break;
|
|
958
|
-
}
|
|
959
|
-
// :state()
|
|
960
|
-
case 'state': {
|
|
961
|
-
if (isCustomElement(node)) {
|
|
962
|
-
const [{ value: stateValue }] = astChildren;
|
|
963
|
-
if (stateValue) {
|
|
964
|
-
if (node[stateValue]) {
|
|
965
|
-
matched.add(node);
|
|
966
|
-
} else {
|
|
967
|
-
for (const i in node) {
|
|
968
|
-
const prop = node[i];
|
|
969
|
-
if (prop instanceof this.#window.ElementInternals) {
|
|
970
|
-
if (prop?.states?.has(stateValue)) {
|
|
971
|
-
matched.add(node);
|
|
972
|
-
}
|
|
973
|
-
break;
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
break;
|
|
980
|
-
}
|
|
981
|
-
case 'current':
|
|
982
|
-
case 'heading':
|
|
983
|
-
case 'nth-col':
|
|
984
|
-
case 'nth-last-col': {
|
|
985
|
-
if (warn) {
|
|
986
|
-
this.onError(
|
|
987
|
-
generateException(
|
|
988
|
-
`Unsupported pseudo-class :${astName}()`,
|
|
989
|
-
NOT_SUPPORTED_ERR,
|
|
990
|
-
this.#window
|
|
991
|
-
)
|
|
992
|
-
);
|
|
993
|
-
}
|
|
994
|
-
break;
|
|
995
|
-
}
|
|
996
|
-
// Ignore :host() and :host-context().
|
|
997
|
-
case 'host':
|
|
998
|
-
case 'host-context': {
|
|
999
|
-
break;
|
|
1000
|
-
}
|
|
1001
|
-
// Deprecated in CSS Selectors 3.
|
|
1002
|
-
case 'contains': {
|
|
1003
|
-
if (warn) {
|
|
1004
|
-
this.onError(
|
|
1005
|
-
generateException(
|
|
1006
|
-
`Unknown pseudo-class :${astName}()`,
|
|
1007
|
-
NOT_SUPPORTED_ERR,
|
|
1008
|
-
this.#window
|
|
1009
|
-
)
|
|
1010
|
-
);
|
|
1011
|
-
}
|
|
1012
|
-
break;
|
|
1013
|
-
}
|
|
1014
|
-
default: {
|
|
1015
|
-
if (!forgive) {
|
|
1016
|
-
this.onError(
|
|
1017
|
-
generateException(
|
|
1018
|
-
`Unknown pseudo-class :${astName}()`,
|
|
1019
|
-
SYNTAX_ERR,
|
|
1020
|
-
this.#window
|
|
1021
|
-
)
|
|
1022
|
-
);
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1085
|
+
const [astChild] = astChildren;
|
|
1086
|
+
if (!this.#psDirCache) {
|
|
1087
|
+
this.#psDirCache = new WeakMap();
|
|
1025
1088
|
}
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
matched.add(node);
|
|
1030
|
-
} else if (parentNode) {
|
|
1031
|
-
switch (astName) {
|
|
1032
|
-
case 'first-of-type': {
|
|
1033
|
-
const [node1] = this._collectNthOfType(
|
|
1034
|
-
{
|
|
1035
|
-
a: 0,
|
|
1036
|
-
b: 1
|
|
1037
|
-
},
|
|
1038
|
-
node
|
|
1039
|
-
);
|
|
1040
|
-
if (node1) {
|
|
1041
|
-
matched.add(node1);
|
|
1042
|
-
}
|
|
1043
|
-
break;
|
|
1044
|
-
}
|
|
1045
|
-
case 'last-of-type': {
|
|
1046
|
-
const [node1] = this._collectNthOfType(
|
|
1047
|
-
{
|
|
1048
|
-
a: 0,
|
|
1049
|
-
b: 1,
|
|
1050
|
-
reverse: true
|
|
1051
|
-
},
|
|
1052
|
-
node
|
|
1053
|
-
);
|
|
1054
|
-
if (node1) {
|
|
1055
|
-
matched.add(node1);
|
|
1056
|
-
}
|
|
1057
|
-
break;
|
|
1058
|
-
}
|
|
1059
|
-
// 'only-of-type' is handled by default.
|
|
1060
|
-
default: {
|
|
1061
|
-
const [node1] = this._collectNthOfType(
|
|
1062
|
-
{
|
|
1063
|
-
a: 0,
|
|
1064
|
-
b: 1
|
|
1065
|
-
},
|
|
1066
|
-
node
|
|
1067
|
-
);
|
|
1068
|
-
if (node1 === node) {
|
|
1069
|
-
const [node2] = this._collectNthOfType(
|
|
1070
|
-
{
|
|
1071
|
-
a: 0,
|
|
1072
|
-
b: 1,
|
|
1073
|
-
reverse: true
|
|
1074
|
-
},
|
|
1075
|
-
node
|
|
1076
|
-
);
|
|
1077
|
-
if (node2 === node) {
|
|
1078
|
-
matched.add(node);
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1089
|
+
const res = matchDirectionPseudoClass(astChild, node, this.#psDirCache);
|
|
1090
|
+
if (res) {
|
|
1091
|
+
return true;
|
|
1082
1092
|
}
|
|
1093
|
+
break;
|
|
1083
1094
|
}
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1095
|
+
// :lang()
|
|
1096
|
+
case 'lang': {
|
|
1097
|
+
if (!astChildren.length) {
|
|
1098
|
+
const css = generateCSS(ast);
|
|
1099
|
+
this.onError(
|
|
1100
|
+
generateException(
|
|
1101
|
+
`Invalid selector ${css}`,
|
|
1102
|
+
SYNTAX_ERR,
|
|
1103
|
+
this.#window
|
|
1104
|
+
)
|
|
1105
|
+
);
|
|
1106
|
+
return false;
|
|
1093
1107
|
}
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
const isMatch = matchReadOnlyPseudoClass(astName, node);
|
|
1097
|
-
if (isMatch) {
|
|
1098
|
-
matched.add(node);
|
|
1099
|
-
}
|
|
1100
|
-
break;
|
|
1108
|
+
if (!this.#psLangCache) {
|
|
1109
|
+
this.#psLangCache = new WeakMap();
|
|
1101
1110
|
}
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
) {
|
|
1108
|
-
matched.add(node);
|
|
1111
|
+
let bool;
|
|
1112
|
+
for (const astChild of astChildren) {
|
|
1113
|
+
bool = matchLanguagePseudoClass(astChild, node, this.#psLangCache);
|
|
1114
|
+
if (bool) {
|
|
1115
|
+
break;
|
|
1109
1116
|
}
|
|
1110
|
-
break;
|
|
1111
1117
|
}
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1118
|
+
if (bool) {
|
|
1119
|
+
return true;
|
|
1120
|
+
}
|
|
1121
|
+
break;
|
|
1122
|
+
}
|
|
1123
|
+
// :state()
|
|
1124
|
+
case 'state': {
|
|
1125
|
+
if (isCustomElement(node)) {
|
|
1126
|
+
const [{ value: stateValue }] = astChildren;
|
|
1127
|
+
if (stateValue) {
|
|
1128
|
+
if (node[stateValue]) {
|
|
1129
|
+
return true;
|
|
1119
1130
|
}
|
|
1120
|
-
const
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1131
|
+
for (const i in node) {
|
|
1132
|
+
const prop = node[i];
|
|
1133
|
+
if (prop instanceof this.#window.ElementInternals) {
|
|
1134
|
+
if (prop?.states?.has(stateValue)) {
|
|
1135
|
+
return true;
|
|
1136
|
+
}
|
|
1137
|
+
break;
|
|
1138
|
+
}
|
|
1124
1139
|
}
|
|
1125
1140
|
}
|
|
1126
|
-
break;
|
|
1127
1141
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1142
|
+
break;
|
|
1143
|
+
}
|
|
1144
|
+
case 'current':
|
|
1145
|
+
case 'heading':
|
|
1146
|
+
case 'nth-col':
|
|
1147
|
+
case 'nth-last-col': {
|
|
1148
|
+
if (warn) {
|
|
1149
|
+
this.onError(
|
|
1150
|
+
generateException(
|
|
1151
|
+
`Unsupported pseudo-class :${astName}()`,
|
|
1152
|
+
NOT_SUPPORTED_ERR,
|
|
1153
|
+
this.#window
|
|
1154
|
+
)
|
|
1155
|
+
);
|
|
1131
1156
|
}
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1157
|
+
break;
|
|
1158
|
+
}
|
|
1159
|
+
// Ignore :host() and :host-context().
|
|
1160
|
+
case 'host':
|
|
1161
|
+
case 'host-context': {
|
|
1162
|
+
break;
|
|
1163
|
+
}
|
|
1164
|
+
// Deprecated in CSS Selectors 3.
|
|
1165
|
+
case 'contains': {
|
|
1166
|
+
if (warn) {
|
|
1167
|
+
this.onError(
|
|
1168
|
+
generateException(
|
|
1169
|
+
`Unknown pseudo-class :${astName}()`,
|
|
1170
|
+
NOT_SUPPORTED_ERR,
|
|
1171
|
+
this.#window
|
|
1172
|
+
)
|
|
1173
|
+
);
|
|
1142
1174
|
}
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1175
|
+
break;
|
|
1176
|
+
}
|
|
1177
|
+
default: {
|
|
1178
|
+
if (!forgive) {
|
|
1179
|
+
this.onError(
|
|
1180
|
+
generateException(
|
|
1181
|
+
`Unknown pseudo-class :${astName}()`,
|
|
1182
|
+
SYNTAX_ERR,
|
|
1183
|
+
this.#window
|
|
1184
|
+
)
|
|
1185
|
+
);
|
|
1154
1186
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
return false;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
/**
|
|
1193
|
+
* Evaluates *-of-type pseudo-class selector.
|
|
1194
|
+
* @private
|
|
1195
|
+
* @param {string} astName - The AST name.
|
|
1196
|
+
* @param {object} node - The Element node.
|
|
1197
|
+
* @returns {boolean} True if matches, otherwise false.
|
|
1198
|
+
*/
|
|
1199
|
+
|
|
1200
|
+
/**
|
|
1201
|
+
* Matches pseudo-class selector.
|
|
1202
|
+
* @private
|
|
1203
|
+
* @see https://html.spec.whatwg.org/#pseudo-classes
|
|
1204
|
+
* @param {object} ast - The AST.
|
|
1205
|
+
* @param {object} node - The Element node.
|
|
1206
|
+
* @param {object} [opt] - Options.
|
|
1207
|
+
* @param {boolean} [opt.forgive] - Ignores unknown or invalid selectors.
|
|
1208
|
+
* @param {boolean} [opt.warn] - If true, console warnings are enabled.
|
|
1209
|
+
* @returns {Set.<object>|boolean} A collection of matched nodes.
|
|
1210
|
+
*/
|
|
1211
|
+
_matchPseudoClassSelector(ast, node, opt = {}) {
|
|
1212
|
+
const { children: astChildren, name: astName } = ast;
|
|
1213
|
+
const { localName, parentNode } = node;
|
|
1214
|
+
const { forgive, warn = this.#warn } = opt;
|
|
1215
|
+
if (Array.isArray(astChildren)) {
|
|
1216
|
+
// :has(), :is(), :not(), :where()
|
|
1217
|
+
if (KEYS_LOGICAL.has(astName)) {
|
|
1218
|
+
return this._evaluateLogicalPseudo(ast, node, opt);
|
|
1219
|
+
}
|
|
1220
|
+
return this._evaluatePseudoClassFunc(ast, node, opt);
|
|
1221
|
+
}
|
|
1222
|
+
if (KEYS_PS_NTH_OF_TYPE.has(astName)) {
|
|
1223
|
+
if (!parentNode) {
|
|
1224
|
+
return node === this.#root;
|
|
1225
|
+
}
|
|
1226
|
+
switch (astName) {
|
|
1227
|
+
case 'first-of-type': {
|
|
1228
|
+
const [node1] = this._collectNthOfType(ANB_FIRST, node);
|
|
1229
|
+
return node1 === node;
|
|
1168
1230
|
}
|
|
1169
|
-
case '
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
}
|
|
1173
|
-
const { hash } = this.#documentURL;
|
|
1174
|
-
if (hash) {
|
|
1175
|
-
const id = hash.replace(/^#/, '');
|
|
1176
|
-
let current = this.#document.getElementById(id);
|
|
1177
|
-
while (current) {
|
|
1178
|
-
if (current === node) {
|
|
1179
|
-
matched.add(node);
|
|
1180
|
-
break;
|
|
1181
|
-
}
|
|
1182
|
-
current = current.parentNode;
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
break;
|
|
1231
|
+
case 'last-of-type': {
|
|
1232
|
+
const [node1] = this._collectNthOfType(ANB_LAST, node);
|
|
1233
|
+
return node1 === node;
|
|
1186
1234
|
}
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
matched.add(node);
|
|
1235
|
+
// 'only-of-type' is handled by default.
|
|
1236
|
+
default: {
|
|
1237
|
+
const [node1] = this._collectNthOfType(ANB_FIRST, node);
|
|
1238
|
+
if (node1 === node) {
|
|
1239
|
+
const [node2] = this._collectNthOfType(ANB_LAST, node);
|
|
1240
|
+
return node2 === node;
|
|
1194
1241
|
}
|
|
1195
|
-
break;
|
|
1196
1242
|
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
if (current.nodeType === DOCUMENT_FRAGMENT_NODE) {
|
|
1206
|
-
const { host } = current;
|
|
1207
|
-
if (host === activeElement) {
|
|
1208
|
-
if (isFocusableArea(node)) {
|
|
1209
|
-
matched.add(node);
|
|
1210
|
-
} else {
|
|
1211
|
-
matched.add(host);
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
break;
|
|
1215
|
-
} else {
|
|
1216
|
-
current = current.parentNode;
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
break;
|
|
1243
|
+
}
|
|
1244
|
+
return false;
|
|
1245
|
+
}
|
|
1246
|
+
switch (astName) {
|
|
1247
|
+
/* Elemental pseudo-classes */
|
|
1248
|
+
case 'defined': {
|
|
1249
|
+
if (node.hasAttribute('is') || localName.includes('-')) {
|
|
1250
|
+
return isCustomElement(node);
|
|
1221
1251
|
}
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
}
|
|
1266
|
-
} else if (eventKey) {
|
|
1267
|
-
if (
|
|
1268
|
-
(eventType === 'keydown' || eventType === 'keyup') &&
|
|
1269
|
-
!eventAltKey &&
|
|
1270
|
-
!eventCtrlKey &&
|
|
1271
|
-
!eventMetaKey &&
|
|
1272
|
-
eventTarget === node
|
|
1273
|
-
) {
|
|
1274
|
-
bool = true;
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
} else if (
|
|
1278
|
-
relatedTarget === null ||
|
|
1279
|
-
relatedTarget === this.#lastFocusVisible
|
|
1280
|
-
) {
|
|
1281
|
-
bool = true;
|
|
1282
|
-
}
|
|
1252
|
+
return (
|
|
1253
|
+
node instanceof this.#window.HTMLElement ||
|
|
1254
|
+
node instanceof this.#window.SVGElement
|
|
1255
|
+
);
|
|
1256
|
+
}
|
|
1257
|
+
/* Element display state pseudo-classes */
|
|
1258
|
+
case 'open': {
|
|
1259
|
+
// <select> and <input type="color"> are not supported.
|
|
1260
|
+
return (
|
|
1261
|
+
(localName === 'details' || localName === 'dialog') &&
|
|
1262
|
+
node.hasAttribute('open')
|
|
1263
|
+
);
|
|
1264
|
+
}
|
|
1265
|
+
case 'popover-open': {
|
|
1266
|
+
// FIXME: Not implemented in jsdom
|
|
1267
|
+
// @see https://github.com/jsdom/jsdom/issues/3721
|
|
1268
|
+
// return node.popover && isVisible(node);
|
|
1269
|
+
break;
|
|
1270
|
+
}
|
|
1271
|
+
/* Input pseudo-classes */
|
|
1272
|
+
case 'disabled':
|
|
1273
|
+
case 'enabled': {
|
|
1274
|
+
return matchDisabledPseudoClass(astName, node);
|
|
1275
|
+
}
|
|
1276
|
+
case 'read-only':
|
|
1277
|
+
case 'read-write': {
|
|
1278
|
+
return matchReadOnlyPseudoClass(astName, node);
|
|
1279
|
+
}
|
|
1280
|
+
case 'placeholder-shown': {
|
|
1281
|
+
let placeholder;
|
|
1282
|
+
if (node.placeholder) {
|
|
1283
|
+
placeholder = node.placeholder;
|
|
1284
|
+
} else if (node.hasAttribute('placeholder')) {
|
|
1285
|
+
placeholder = node.getAttribute('placeholder');
|
|
1286
|
+
}
|
|
1287
|
+
if (typeof placeholder === 'string' && !/[\r\n]/.test(placeholder)) {
|
|
1288
|
+
let targetNode;
|
|
1289
|
+
if (localName === 'textarea') {
|
|
1290
|
+
targetNode = node;
|
|
1291
|
+
} else if (localName === 'input') {
|
|
1292
|
+
if (node.hasAttribute('type')) {
|
|
1293
|
+
if (KEYS_INPUT_PLACEHOLDER.has(node.getAttribute('type'))) {
|
|
1294
|
+
targetNode = node;
|
|
1283
1295
|
}
|
|
1284
|
-
}
|
|
1285
|
-
if (bool) {
|
|
1286
|
-
this.#lastFocusVisible = node;
|
|
1287
|
-
matched.add(node);
|
|
1288
|
-
} else if (this.#lastFocusVisible === node) {
|
|
1289
|
-
this.#lastFocusVisible = null;
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
break;
|
|
1293
|
-
}
|
|
1294
|
-
case 'focus-within': {
|
|
1295
|
-
const activeElement = this.#document.activeElement;
|
|
1296
|
-
if (node.contains(activeElement) && isFocusableArea(activeElement)) {
|
|
1297
|
-
matched.add(node);
|
|
1298
|
-
} else if (activeElement.shadowRoot) {
|
|
1299
|
-
const activeShadowElement = activeElement.shadowRoot.activeElement;
|
|
1300
|
-
if (node.contains(activeShadowElement)) {
|
|
1301
|
-
matched.add(node);
|
|
1302
1296
|
} else {
|
|
1303
|
-
let current = activeShadowElement;
|
|
1304
|
-
while (current) {
|
|
1305
|
-
if (current.nodeType === DOCUMENT_FRAGMENT_NODE) {
|
|
1306
|
-
const { host } = current;
|
|
1307
|
-
if (host === activeElement && node.contains(host)) {
|
|
1308
|
-
matched.add(node);
|
|
1309
|
-
}
|
|
1310
|
-
break;
|
|
1311
|
-
} else {
|
|
1312
|
-
current = current.parentNode;
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
break;
|
|
1318
|
-
}
|
|
1319
|
-
case 'open':
|
|
1320
|
-
case 'closed': {
|
|
1321
|
-
if (localName === 'details' || localName === 'dialog') {
|
|
1322
|
-
if (node.hasAttribute('open')) {
|
|
1323
|
-
if (astName === 'open') {
|
|
1324
|
-
matched.add(node);
|
|
1325
|
-
}
|
|
1326
|
-
} else if (astName === 'closed') {
|
|
1327
|
-
matched.add(node);
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
break;
|
|
1331
|
-
}
|
|
1332
|
-
case 'placeholder-shown': {
|
|
1333
|
-
let placeholder;
|
|
1334
|
-
if (node.placeholder) {
|
|
1335
|
-
placeholder = node.placeholder;
|
|
1336
|
-
} else if (node.hasAttribute('placeholder')) {
|
|
1337
|
-
placeholder = node.getAttribute('placeholder');
|
|
1338
|
-
}
|
|
1339
|
-
if (typeof placeholder === 'string' && !/[\r\n]/.test(placeholder)) {
|
|
1340
|
-
let targetNode;
|
|
1341
|
-
if (localName === 'textarea') {
|
|
1342
1297
|
targetNode = node;
|
|
1343
|
-
} else if (localName === 'input') {
|
|
1344
|
-
if (node.hasAttribute('type')) {
|
|
1345
|
-
if (KEYS_INPUT_PLACEHOLDER.has(node.getAttribute('type'))) {
|
|
1346
|
-
targetNode = node;
|
|
1347
|
-
}
|
|
1348
|
-
} else {
|
|
1349
|
-
targetNode = node;
|
|
1350
|
-
}
|
|
1351
|
-
}
|
|
1352
|
-
if (targetNode && node.value === '') {
|
|
1353
|
-
matched.add(node);
|
|
1354
1298
|
}
|
|
1355
1299
|
}
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
case 'checked': {
|
|
1359
|
-
const attrType = node.getAttribute('type');
|
|
1360
|
-
if (
|
|
1361
|
-
(node.checked &&
|
|
1362
|
-
localName === 'input' &&
|
|
1363
|
-
(attrType === 'checkbox' || attrType === 'radio')) ||
|
|
1364
|
-
(node.selected && localName === 'option')
|
|
1365
|
-
) {
|
|
1366
|
-
matched.add(node);
|
|
1300
|
+
if (targetNode) {
|
|
1301
|
+
return node.value === '';
|
|
1367
1302
|
}
|
|
1368
|
-
break;
|
|
1369
1303
|
}
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
) {
|
|
1377
|
-
matched.add(node);
|
|
1378
|
-
} else if (
|
|
1379
|
-
localName === 'input' &&
|
|
1380
|
-
node.type === 'radio' &&
|
|
1381
|
-
!node.hasAttribute('checked')
|
|
1382
|
-
) {
|
|
1383
|
-
const nodeName = node.name;
|
|
1384
|
-
let parent = node.parentNode;
|
|
1385
|
-
while (parent) {
|
|
1386
|
-
if (parent.localName === 'form') {
|
|
1387
|
-
break;
|
|
1388
|
-
}
|
|
1389
|
-
parent = parent.parentNode;
|
|
1390
|
-
}
|
|
1391
|
-
if (!parent) {
|
|
1392
|
-
parent = this.#document.documentElement;
|
|
1393
|
-
}
|
|
1394
|
-
const walker = this._createTreeWalker(parent);
|
|
1395
|
-
let refNode = traverseNode(parent, walker);
|
|
1396
|
-
refNode = walker.firstChild();
|
|
1397
|
-
let checked;
|
|
1398
|
-
while (refNode) {
|
|
1399
|
-
if (
|
|
1400
|
-
refNode.localName === 'input' &&
|
|
1401
|
-
refNode.getAttribute('type') === 'radio'
|
|
1402
|
-
) {
|
|
1403
|
-
if (refNode.hasAttribute('name')) {
|
|
1404
|
-
if (refNode.getAttribute('name') === nodeName) {
|
|
1405
|
-
checked = !!refNode.checked;
|
|
1406
|
-
}
|
|
1407
|
-
} else {
|
|
1408
|
-
checked = !!refNode.checked;
|
|
1409
|
-
}
|
|
1410
|
-
if (checked) {
|
|
1411
|
-
break;
|
|
1412
|
-
}
|
|
1413
|
-
}
|
|
1414
|
-
refNode = walker.nextNode();
|
|
1415
|
-
}
|
|
1416
|
-
if (!checked) {
|
|
1417
|
-
matched.add(node);
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
break;
|
|
1304
|
+
break;
|
|
1305
|
+
}
|
|
1306
|
+
case 'default': {
|
|
1307
|
+
// option
|
|
1308
|
+
if (localName === 'option') {
|
|
1309
|
+
return node.hasAttribute('selected');
|
|
1421
1310
|
}
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1311
|
+
const attrType = node.getAttribute('type');
|
|
1312
|
+
// input[type="checkbox"], input[type="radio"]
|
|
1313
|
+
if (
|
|
1314
|
+
localName === 'input' &&
|
|
1315
|
+
node.hasAttribute('type') &&
|
|
1316
|
+
node.hasAttribute('checked')
|
|
1317
|
+
) {
|
|
1318
|
+
return KEYS_INPUT_CHECK.has(attrType);
|
|
1319
|
+
}
|
|
1320
|
+
// button[type="submit"], input[type="submit"], input[type="image"]
|
|
1321
|
+
if (
|
|
1322
|
+
(localName === 'button' &&
|
|
1323
|
+
!(node.hasAttribute('type') && KEYS_INPUT_RESET.has(attrType))) ||
|
|
1324
|
+
(localName === 'input' &&
|
|
1325
|
+
node.hasAttribute('type') &&
|
|
1326
|
+
KEYS_INPUT_SUBMIT.has(attrType))
|
|
1327
|
+
) {
|
|
1328
|
+
let form = node.parentNode;
|
|
1329
|
+
while (form) {
|
|
1330
|
+
if (form.localName === 'form') {
|
|
1331
|
+
break;
|
|
1332
|
+
}
|
|
1333
|
+
form = form.parentNode;
|
|
1334
|
+
}
|
|
1335
|
+
if (form) {
|
|
1336
|
+
if (!this.#psDefaultCache) {
|
|
1337
|
+
this.#psDefaultCache = new WeakMap();
|
|
1438
1338
|
}
|
|
1439
|
-
|
|
1440
|
-
|
|
1339
|
+
let defaultSubmit = this.#psDefaultCache.get(form);
|
|
1340
|
+
if (!defaultSubmit) {
|
|
1341
|
+
const walker = this._createTreeWalker(form, { force: true });
|
|
1441
1342
|
let refNode = traverseNode(form, walker);
|
|
1442
1343
|
refNode = walker.firstChild();
|
|
1443
1344
|
while (refNode) {
|
|
@@ -1455,53 +1356,117 @@ export class Finder {
|
|
|
1455
1356
|
KEYS_INPUT_SUBMIT.has(nodeAttrType);
|
|
1456
1357
|
}
|
|
1457
1358
|
if (m) {
|
|
1458
|
-
|
|
1459
|
-
matched.add(node);
|
|
1460
|
-
}
|
|
1359
|
+
defaultSubmit = refNode;
|
|
1461
1360
|
break;
|
|
1462
1361
|
}
|
|
1463
1362
|
refNode = walker.nextNode();
|
|
1464
1363
|
}
|
|
1364
|
+
this.#psDefaultCache.set(form, defaultSubmit);
|
|
1465
1365
|
}
|
|
1466
|
-
|
|
1467
|
-
} else if (
|
|
1468
|
-
localName === 'input' &&
|
|
1469
|
-
node.hasAttribute('type') &&
|
|
1470
|
-
node.hasAttribute('checked') &&
|
|
1471
|
-
KEYS_INPUT_CHECK.has(attrType)
|
|
1472
|
-
) {
|
|
1473
|
-
matched.add(node);
|
|
1474
|
-
// option
|
|
1475
|
-
} else if (localName === 'option' && node.hasAttribute('selected')) {
|
|
1476
|
-
matched.add(node);
|
|
1366
|
+
return defaultSubmit === node;
|
|
1477
1367
|
}
|
|
1478
|
-
break;
|
|
1479
1368
|
}
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1369
|
+
break;
|
|
1370
|
+
}
|
|
1371
|
+
case 'checked': {
|
|
1372
|
+
if (localName === 'option') {
|
|
1373
|
+
return node.selected;
|
|
1374
|
+
}
|
|
1375
|
+
if (localName === 'input') {
|
|
1376
|
+
const attrType = node.getAttribute('type');
|
|
1377
|
+
return (
|
|
1378
|
+
node.checked && (attrType === 'checkbox' || attrType === 'radio')
|
|
1379
|
+
);
|
|
1380
|
+
}
|
|
1381
|
+
break;
|
|
1382
|
+
}
|
|
1383
|
+
case 'indeterminate': {
|
|
1384
|
+
if (localName === 'progress') {
|
|
1385
|
+
return !node.hasAttribute('value');
|
|
1386
|
+
}
|
|
1387
|
+
if (localName === 'input' && node.type === 'checkbox') {
|
|
1388
|
+
return node.indeterminate;
|
|
1389
|
+
}
|
|
1390
|
+
if (localName === 'input' && node.type === 'radio') {
|
|
1391
|
+
if (node.checked || node.hasAttribute('checked')) {
|
|
1392
|
+
return false;
|
|
1393
|
+
}
|
|
1394
|
+
const nodeName = node.name;
|
|
1395
|
+
let parent = node.parentNode;
|
|
1396
|
+
while (parent) {
|
|
1397
|
+
if (parent.localName === 'form') {
|
|
1398
|
+
break;
|
|
1399
|
+
}
|
|
1400
|
+
parent = parent.parentNode;
|
|
1401
|
+
}
|
|
1402
|
+
if (!parent) {
|
|
1403
|
+
parent = this.#document.documentElement;
|
|
1404
|
+
}
|
|
1405
|
+
if (!this.#psIndeterminateCache) {
|
|
1406
|
+
this.#psIndeterminateCache = new WeakMap();
|
|
1407
|
+
}
|
|
1408
|
+
let parentCache = this.#psIndeterminateCache.get(parent);
|
|
1409
|
+
if (!parentCache) {
|
|
1410
|
+
parentCache = new Map();
|
|
1411
|
+
this.#psIndeterminateCache.set(parent, parentCache);
|
|
1412
|
+
}
|
|
1413
|
+
let checked = parentCache.get(nodeName);
|
|
1414
|
+
if (checked === undefined) {
|
|
1415
|
+
const walker = this._createTreeWalker(parent, { force: true });
|
|
1416
|
+
let refNode = traverseNode(parent, walker);
|
|
1417
|
+
refNode = walker.firstChild();
|
|
1418
|
+
while (refNode) {
|
|
1419
|
+
if (
|
|
1420
|
+
refNode.localName === 'input' &&
|
|
1421
|
+
refNode.getAttribute('type') === 'radio'
|
|
1422
|
+
) {
|
|
1423
|
+
if (refNode.hasAttribute('name')) {
|
|
1424
|
+
if (refNode.getAttribute('name') === nodeName) {
|
|
1425
|
+
checked = !!refNode.checked;
|
|
1426
|
+
}
|
|
1427
|
+
} else {
|
|
1428
|
+
checked = !!refNode.checked;
|
|
1429
|
+
}
|
|
1430
|
+
if (checked) {
|
|
1431
|
+
break;
|
|
1488
1432
|
}
|
|
1489
|
-
} else {
|
|
1490
|
-
valid = true;
|
|
1491
1433
|
}
|
|
1434
|
+
refNode = walker.nextNode();
|
|
1492
1435
|
}
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1436
|
+
checked = !!checked;
|
|
1437
|
+
parentCache.set(nodeName, checked);
|
|
1438
|
+
}
|
|
1439
|
+
return !checked;
|
|
1440
|
+
}
|
|
1441
|
+
break;
|
|
1442
|
+
}
|
|
1443
|
+
case 'valid':
|
|
1444
|
+
case 'invalid': {
|
|
1445
|
+
if (KEYS_FORM_PS_VALID.has(localName)) {
|
|
1446
|
+
let valid = false;
|
|
1447
|
+
if (node.checkValidity()) {
|
|
1448
|
+
if (node.maxLength >= 0) {
|
|
1449
|
+
if (node.maxLength >= node.value.length) {
|
|
1450
|
+
valid = true;
|
|
1496
1451
|
}
|
|
1497
|
-
} else
|
|
1498
|
-
|
|
1452
|
+
} else {
|
|
1453
|
+
valid = true;
|
|
1499
1454
|
}
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1455
|
+
}
|
|
1456
|
+
if (astName === 'invalid') {
|
|
1457
|
+
return !valid;
|
|
1458
|
+
}
|
|
1459
|
+
return valid;
|
|
1460
|
+
}
|
|
1461
|
+
if (localName === 'fieldset') {
|
|
1462
|
+
if (!this.#psValidCache) {
|
|
1463
|
+
this.#psValidCache = new WeakMap();
|
|
1464
|
+
}
|
|
1465
|
+
let valid = this.#psValidCache.get(node);
|
|
1466
|
+
if (valid === undefined && !this.#psValidCache.has(node)) {
|
|
1467
|
+
const walker = this._createTreeWalker(node, { force: true });
|
|
1502
1468
|
let refNode = traverseNode(node, walker);
|
|
1503
1469
|
refNode = walker.firstChild();
|
|
1504
|
-
let valid;
|
|
1505
1470
|
if (!refNode) {
|
|
1506
1471
|
valid = true;
|
|
1507
1472
|
} else {
|
|
@@ -1523,201 +1488,345 @@ export class Finder {
|
|
|
1523
1488
|
refNode = walker.nextNode();
|
|
1524
1489
|
}
|
|
1525
1490
|
}
|
|
1526
|
-
|
|
1527
|
-
if (astName === 'valid') {
|
|
1528
|
-
matched.add(node);
|
|
1529
|
-
}
|
|
1530
|
-
} else if (astName === 'invalid') {
|
|
1531
|
-
matched.add(node);
|
|
1532
|
-
}
|
|
1491
|
+
this.#psValidCache.set(node, valid);
|
|
1533
1492
|
}
|
|
1534
|
-
|
|
1493
|
+
if (astName === 'invalid') {
|
|
1494
|
+
return !valid;
|
|
1495
|
+
}
|
|
1496
|
+
return valid;
|
|
1535
1497
|
}
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
)
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1498
|
+
break;
|
|
1499
|
+
}
|
|
1500
|
+
case 'in-range':
|
|
1501
|
+
case 'out-of-range': {
|
|
1502
|
+
const attrType = node.getAttribute('type');
|
|
1503
|
+
if (
|
|
1504
|
+
localName === 'input' &&
|
|
1505
|
+
!(node.readOnly || node.hasAttribute('readonly')) &&
|
|
1506
|
+
!(node.disabled || node.hasAttribute('disabled')) &&
|
|
1507
|
+
KEYS_INPUT_RANGE.has(attrType)
|
|
1508
|
+
) {
|
|
1509
|
+
const flowed =
|
|
1510
|
+
node.validity.rangeUnderflow || node.validity.rangeOverflow;
|
|
1511
|
+
if (astName === 'out-of-range') {
|
|
1512
|
+
return flowed;
|
|
1513
|
+
}
|
|
1514
|
+
return flowed
|
|
1515
|
+
? false
|
|
1516
|
+
: node.hasAttribute('min') ||
|
|
1553
1517
|
node.hasAttribute('max') ||
|
|
1554
|
-
attrType === 'range'
|
|
1555
|
-
) {
|
|
1556
|
-
matched.add(node);
|
|
1557
|
-
}
|
|
1558
|
-
}
|
|
1559
|
-
break;
|
|
1518
|
+
attrType === 'range';
|
|
1560
1519
|
}
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
if (
|
|
1575
|
-
|
|
1576
|
-
required = true;
|
|
1577
|
-
} else {
|
|
1578
|
-
optional = true;
|
|
1579
|
-
}
|
|
1580
|
-
} else {
|
|
1581
|
-
optional = true;
|
|
1520
|
+
break;
|
|
1521
|
+
}
|
|
1522
|
+
case 'required':
|
|
1523
|
+
case 'optional': {
|
|
1524
|
+
let required = false;
|
|
1525
|
+
if (localName === 'select' || localName === 'textarea') {
|
|
1526
|
+
if (node.required || node.hasAttribute('required')) {
|
|
1527
|
+
required = true;
|
|
1528
|
+
}
|
|
1529
|
+
} else if (localName === 'input') {
|
|
1530
|
+
if (node.hasAttribute('type')) {
|
|
1531
|
+
const attrType = node.getAttribute('type');
|
|
1532
|
+
if (KEYS_INPUT_REQUIRED.has(attrType)) {
|
|
1533
|
+
if (node.required || node.hasAttribute('required')) {
|
|
1534
|
+
required = true;
|
|
1582
1535
|
}
|
|
1583
|
-
} else if (node.required || node.hasAttribute('required')) {
|
|
1584
|
-
required = true;
|
|
1585
|
-
} else {
|
|
1586
|
-
optional = true;
|
|
1587
1536
|
}
|
|
1537
|
+
} else if (node.required || node.hasAttribute('required')) {
|
|
1538
|
+
required = true;
|
|
1588
1539
|
}
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1540
|
+
}
|
|
1541
|
+
if (astName === 'optional') {
|
|
1542
|
+
return !required;
|
|
1543
|
+
}
|
|
1544
|
+
return required;
|
|
1545
|
+
}
|
|
1546
|
+
/* Location pseudo-classes */
|
|
1547
|
+
case 'any-link':
|
|
1548
|
+
case 'link': {
|
|
1549
|
+
return (
|
|
1550
|
+
(localName === 'a' || localName === 'area') &&
|
|
1551
|
+
node.hasAttribute('href')
|
|
1552
|
+
);
|
|
1553
|
+
}
|
|
1554
|
+
case 'local-link': {
|
|
1555
|
+
if (
|
|
1556
|
+
(localName === 'a' || localName === 'area') &&
|
|
1557
|
+
node.hasAttribute('href')
|
|
1558
|
+
) {
|
|
1559
|
+
if (!this.#documentURL) {
|
|
1560
|
+
this.#documentURL = new URL(this.#document.URL);
|
|
1593
1561
|
}
|
|
1594
|
-
|
|
1562
|
+
const { href, origin, pathname } = this.#documentURL;
|
|
1563
|
+
const attrURL = new URL(node.getAttribute('href'), href);
|
|
1564
|
+
return attrURL.origin === origin && attrURL.pathname === pathname;
|
|
1565
|
+
}
|
|
1566
|
+
break;
|
|
1567
|
+
}
|
|
1568
|
+
case 'visited': {
|
|
1569
|
+
// prevent fingerprinting
|
|
1570
|
+
break;
|
|
1571
|
+
}
|
|
1572
|
+
case 'target': {
|
|
1573
|
+
if (!this.#documentURL) {
|
|
1574
|
+
this.#documentURL = new URL(this.#document.URL);
|
|
1575
|
+
}
|
|
1576
|
+
const { hash } = this.#documentURL;
|
|
1577
|
+
return hash && hash === `#${node.id}` && this.#document.contains(node);
|
|
1578
|
+
}
|
|
1579
|
+
case 'scope': {
|
|
1580
|
+
if (this.#node.nodeType === ELEMENT_NODE) {
|
|
1581
|
+
return !this.#shadow && node === this.#node;
|
|
1582
|
+
}
|
|
1583
|
+
return node === this.#document.documentElement;
|
|
1584
|
+
}
|
|
1585
|
+
/* Tree-structural pseudo-classes */
|
|
1586
|
+
case 'root': {
|
|
1587
|
+
return node === this.#document.documentElement;
|
|
1588
|
+
}
|
|
1589
|
+
case 'empty': {
|
|
1590
|
+
if (!node.hasChildNodes()) {
|
|
1591
|
+
return true;
|
|
1595
1592
|
}
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1593
|
+
const walker = this._createTreeWalker(node, {
|
|
1594
|
+
force: true,
|
|
1595
|
+
whatToShow: SHOW_ALL
|
|
1596
|
+
});
|
|
1597
|
+
let refNode = walker.firstChild();
|
|
1598
|
+
let bool;
|
|
1599
|
+
while (refNode) {
|
|
1600
|
+
bool =
|
|
1601
|
+
refNode.nodeType !== ELEMENT_NODE && refNode.nodeType !== TEXT_NODE;
|
|
1602
|
+
if (!bool) {
|
|
1603
|
+
break;
|
|
1599
1604
|
}
|
|
1600
|
-
|
|
1605
|
+
refNode = walker.nextSibling();
|
|
1601
1606
|
}
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1607
|
+
return bool;
|
|
1608
|
+
}
|
|
1609
|
+
case 'first-child':
|
|
1610
|
+
case 'last-child':
|
|
1611
|
+
case 'only-child': {
|
|
1612
|
+
if (!parentNode) {
|
|
1613
|
+
return node === this.#root;
|
|
1614
|
+
}
|
|
1615
|
+
if (astName === 'first-child') {
|
|
1616
|
+
return node === parentNode.firstElementChild;
|
|
1617
|
+
}
|
|
1618
|
+
if (astName === 'last-child') {
|
|
1619
|
+
return node === parentNode.lastElementChild;
|
|
1620
|
+
}
|
|
1621
|
+
return (
|
|
1622
|
+
node === parentNode.firstElementChild &&
|
|
1623
|
+
node === parentNode.lastElementChild
|
|
1624
|
+
);
|
|
1625
|
+
}
|
|
1626
|
+
/* User action pseudo-classes */
|
|
1627
|
+
case 'hover': {
|
|
1628
|
+
const { target, type } = this.#event ?? {};
|
|
1629
|
+
return (
|
|
1630
|
+
/^(?:click|mouse(?:down|over|up))$/.test(type) &&
|
|
1631
|
+
target?.nodeType === ELEMENT_NODE &&
|
|
1632
|
+
node.contains(target)
|
|
1633
|
+
);
|
|
1634
|
+
}
|
|
1635
|
+
case 'active': {
|
|
1636
|
+
const { buttons, target, type } = this.#event ?? {};
|
|
1637
|
+
return (
|
|
1638
|
+
type === 'mousedown' &&
|
|
1639
|
+
buttons & 1 &&
|
|
1640
|
+
target?.nodeType === ELEMENT_NODE &&
|
|
1641
|
+
node.contains(target)
|
|
1642
|
+
);
|
|
1643
|
+
}
|
|
1644
|
+
case 'focus': {
|
|
1645
|
+
const activeElement = this.#document.activeElement;
|
|
1646
|
+
if (activeElement.shadowRoot) {
|
|
1647
|
+
const activeShadowElement = activeElement.shadowRoot.activeElement;
|
|
1648
|
+
let current = activeShadowElement;
|
|
1649
|
+
while (current) {
|
|
1650
|
+
if (current.nodeType === DOCUMENT_FRAGMENT_NODE) {
|
|
1651
|
+
const { host } = current;
|
|
1652
|
+
if (host === activeElement) {
|
|
1653
|
+
if (isFocusableArea(node)) {
|
|
1654
|
+
return true;
|
|
1655
|
+
}
|
|
1656
|
+
return host === node;
|
|
1616
1657
|
}
|
|
1617
|
-
refNode = walker.nextSibling();
|
|
1618
1658
|
}
|
|
1619
|
-
|
|
1620
|
-
|
|
1659
|
+
current = current.parentNode;
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
return node === activeElement && isFocusableArea(node);
|
|
1663
|
+
}
|
|
1664
|
+
case 'focus-visible': {
|
|
1665
|
+
if (node === this.#document.activeElement && isFocusableArea(node)) {
|
|
1666
|
+
let bool;
|
|
1667
|
+
if (isFocusVisible(node)) {
|
|
1668
|
+
bool = true;
|
|
1669
|
+
} else if (this.#focus) {
|
|
1670
|
+
const { relatedTarget, target: focusTarget } = this.#focus;
|
|
1671
|
+
if (focusTarget === node) {
|
|
1672
|
+
if (isFocusVisible(relatedTarget)) {
|
|
1673
|
+
bool = true;
|
|
1674
|
+
} else if (this.#event) {
|
|
1675
|
+
const {
|
|
1676
|
+
altKey: eventAltKey,
|
|
1677
|
+
ctrlKey: eventCtrlKey,
|
|
1678
|
+
key: eventKey,
|
|
1679
|
+
metaKey: eventMetaKey,
|
|
1680
|
+
target: eventTarget,
|
|
1681
|
+
type: eventType
|
|
1682
|
+
} = this.#event;
|
|
1683
|
+
// this.#event is irrelevant if eventTarget === relatedTarget
|
|
1684
|
+
if (eventTarget === relatedTarget) {
|
|
1685
|
+
if (!this.#lastFocusVisible) {
|
|
1686
|
+
bool = true;
|
|
1687
|
+
} else if (focusTarget === this.#lastFocusVisible) {
|
|
1688
|
+
bool = true;
|
|
1689
|
+
}
|
|
1690
|
+
} else if (eventKey === 'Tab') {
|
|
1691
|
+
if (
|
|
1692
|
+
(eventType === 'keydown' && eventTarget !== node) ||
|
|
1693
|
+
(eventType === 'keyup' && eventTarget === node)
|
|
1694
|
+
) {
|
|
1695
|
+
if (eventTarget === focusTarget) {
|
|
1696
|
+
if (!this.#lastFocusVisible) {
|
|
1697
|
+
bool = true;
|
|
1698
|
+
} else if (
|
|
1699
|
+
eventTarget === this.#lastFocusVisible &&
|
|
1700
|
+
relatedTarget === null
|
|
1701
|
+
) {
|
|
1702
|
+
bool = true;
|
|
1703
|
+
}
|
|
1704
|
+
} else {
|
|
1705
|
+
bool = true;
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
} else if (eventKey) {
|
|
1709
|
+
if (
|
|
1710
|
+
(eventType === 'keydown' || eventType === 'keyup') &&
|
|
1711
|
+
!eventAltKey &&
|
|
1712
|
+
!eventCtrlKey &&
|
|
1713
|
+
!eventMetaKey &&
|
|
1714
|
+
eventTarget === node
|
|
1715
|
+
) {
|
|
1716
|
+
bool = true;
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
} else if (
|
|
1720
|
+
relatedTarget === null ||
|
|
1721
|
+
relatedTarget === this.#lastFocusVisible
|
|
1722
|
+
) {
|
|
1723
|
+
bool = true;
|
|
1724
|
+
}
|
|
1621
1725
|
}
|
|
1622
|
-
} else {
|
|
1623
|
-
matched.add(node);
|
|
1624
|
-
}
|
|
1625
|
-
break;
|
|
1626
|
-
}
|
|
1627
|
-
case 'first-child': {
|
|
1628
|
-
if (
|
|
1629
|
-
(parentNode && node === parentNode.firstElementChild) ||
|
|
1630
|
-
node === this.#root
|
|
1631
|
-
) {
|
|
1632
|
-
matched.add(node);
|
|
1633
1726
|
}
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
if (
|
|
1638
|
-
(parentNode && node === parentNode.lastElementChild) ||
|
|
1639
|
-
node === this.#root
|
|
1640
|
-
) {
|
|
1641
|
-
matched.add(node);
|
|
1727
|
+
if (bool) {
|
|
1728
|
+
this.#lastFocusVisible = node;
|
|
1729
|
+
return bool;
|
|
1642
1730
|
}
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
case 'only-child': {
|
|
1646
|
-
if (
|
|
1647
|
-
(parentNode &&
|
|
1648
|
-
node === parentNode.firstElementChild &&
|
|
1649
|
-
node === parentNode.lastElementChild) ||
|
|
1650
|
-
node === this.#root
|
|
1651
|
-
) {
|
|
1652
|
-
matched.add(node);
|
|
1731
|
+
if (this.#lastFocusVisible === node) {
|
|
1732
|
+
this.#lastFocusVisible = null;
|
|
1653
1733
|
}
|
|
1654
|
-
break;
|
|
1655
1734
|
}
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1735
|
+
break;
|
|
1736
|
+
}
|
|
1737
|
+
case 'focus-within': {
|
|
1738
|
+
if (!this.#focusWithinCache) {
|
|
1739
|
+
this.#focusWithinCache = new Set();
|
|
1740
|
+
let currentFocus = this.#document.activeElement;
|
|
1741
|
+
if (currentFocus && isFocusableArea(currentFocus)) {
|
|
1742
|
+
while (currentFocus) {
|
|
1743
|
+
this.#focusWithinCache.add(currentFocus);
|
|
1744
|
+
if (currentFocus.parentNode) {
|
|
1745
|
+
currentFocus = currentFocus.parentNode;
|
|
1746
|
+
} else if (
|
|
1747
|
+
currentFocus.nodeType === DOCUMENT_FRAGMENT_NODE &&
|
|
1748
|
+
currentFocus.host
|
|
1749
|
+
) {
|
|
1750
|
+
currentFocus = currentFocus.host;
|
|
1751
|
+
} else {
|
|
1752
|
+
break;
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
} else if (currentFocus && currentFocus.shadowRoot) {
|
|
1756
|
+
let shadowFocus = currentFocus.shadowRoot.activeElement;
|
|
1757
|
+
if (shadowFocus) {
|
|
1758
|
+
while (shadowFocus) {
|
|
1759
|
+
this.#focusWithinCache.add(shadowFocus);
|
|
1760
|
+
if (shadowFocus.parentNode) {
|
|
1761
|
+
shadowFocus = shadowFocus.parentNode;
|
|
1762
|
+
} else if (
|
|
1763
|
+
shadowFocus.nodeType === DOCUMENT_FRAGMENT_NODE &&
|
|
1764
|
+
shadowFocus.host
|
|
1765
|
+
) {
|
|
1766
|
+
shadowFocus = shadowFocus.host;
|
|
1767
|
+
} else {
|
|
1768
|
+
break;
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1660
1771
|
}
|
|
1661
|
-
// NOTE: MathMLElement is not implemented in jsdom.
|
|
1662
|
-
} else if (
|
|
1663
|
-
node instanceof this.#window.HTMLElement ||
|
|
1664
|
-
node instanceof this.#window.SVGElement
|
|
1665
|
-
) {
|
|
1666
|
-
matched.add(node);
|
|
1667
|
-
}
|
|
1668
|
-
break;
|
|
1669
|
-
}
|
|
1670
|
-
case 'popover-open': {
|
|
1671
|
-
// FIXME: not implemented in jsdom
|
|
1672
|
-
// @see https://github.com/jsdom/jsdom/issues/3721
|
|
1673
|
-
/*
|
|
1674
|
-
if (node.popover && isVisible(node)) {
|
|
1675
|
-
matched.add(node);
|
|
1676
1772
|
}
|
|
1677
|
-
*/
|
|
1678
|
-
break;
|
|
1679
1773
|
}
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1774
|
+
return this.#focusWithinCache.has(node);
|
|
1775
|
+
}
|
|
1776
|
+
// Ignore :host.
|
|
1777
|
+
case 'host': {
|
|
1778
|
+
break;
|
|
1779
|
+
}
|
|
1780
|
+
// Legacy pseudo-elements.
|
|
1781
|
+
case 'after':
|
|
1782
|
+
case 'before':
|
|
1783
|
+
case 'first-letter':
|
|
1784
|
+
case 'first-line': {
|
|
1785
|
+
if (warn) {
|
|
1786
|
+
this.onError(
|
|
1787
|
+
generateException(
|
|
1788
|
+
`Unsupported pseudo-element ::${astName}`,
|
|
1789
|
+
NOT_SUPPORTED_ERR,
|
|
1790
|
+
this.#window
|
|
1791
|
+
)
|
|
1792
|
+
);
|
|
1683
1793
|
}
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1794
|
+
break;
|
|
1795
|
+
}
|
|
1796
|
+
// Not supported.
|
|
1797
|
+
case 'autofill':
|
|
1798
|
+
case 'blank':
|
|
1799
|
+
case 'buffering':
|
|
1800
|
+
case 'current':
|
|
1801
|
+
case 'fullscreen':
|
|
1802
|
+
case 'future':
|
|
1803
|
+
case 'has-slotted':
|
|
1804
|
+
case 'heading':
|
|
1805
|
+
case 'modal':
|
|
1806
|
+
case 'muted':
|
|
1807
|
+
case 'past':
|
|
1808
|
+
case 'paused':
|
|
1809
|
+
case 'picture-in-picture':
|
|
1810
|
+
case 'playing':
|
|
1811
|
+
case 'seeking':
|
|
1812
|
+
case 'stalled':
|
|
1813
|
+
case 'user-invalid':
|
|
1814
|
+
case 'user-valid':
|
|
1815
|
+
case 'volume-locked':
|
|
1816
|
+
case '-webkit-autofill': {
|
|
1817
|
+
if (warn) {
|
|
1818
|
+
this.onError(
|
|
1819
|
+
generateException(
|
|
1820
|
+
`Unsupported pseudo-class :${astName}`,
|
|
1821
|
+
NOT_SUPPORTED_ERR,
|
|
1822
|
+
this.#window
|
|
1823
|
+
)
|
|
1824
|
+
);
|
|
1699
1825
|
}
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
case 'current':
|
|
1705
|
-
case 'fullscreen':
|
|
1706
|
-
case 'future':
|
|
1707
|
-
case 'has-slotted':
|
|
1708
|
-
case 'heading':
|
|
1709
|
-
case 'modal':
|
|
1710
|
-
case 'muted':
|
|
1711
|
-
case 'past':
|
|
1712
|
-
case 'paused':
|
|
1713
|
-
case 'picture-in-picture':
|
|
1714
|
-
case 'playing':
|
|
1715
|
-
case 'seeking':
|
|
1716
|
-
case 'stalled':
|
|
1717
|
-
case 'user-invalid':
|
|
1718
|
-
case 'user-valid':
|
|
1719
|
-
case 'volume-locked':
|
|
1720
|
-
case '-webkit-autofill': {
|
|
1826
|
+
break;
|
|
1827
|
+
}
|
|
1828
|
+
default: {
|
|
1829
|
+
if (astName.startsWith('-webkit-')) {
|
|
1721
1830
|
if (warn) {
|
|
1722
1831
|
this.onError(
|
|
1723
1832
|
generateException(
|
|
@@ -1727,32 +1836,18 @@ export class Finder {
|
|
|
1727
1836
|
)
|
|
1728
1837
|
);
|
|
1729
1838
|
}
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
this
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
NOT_SUPPORTED_ERR,
|
|
1739
|
-
this.#window
|
|
1740
|
-
)
|
|
1741
|
-
);
|
|
1742
|
-
}
|
|
1743
|
-
} else if (!forgive) {
|
|
1744
|
-
this.onError(
|
|
1745
|
-
generateException(
|
|
1746
|
-
`Unknown pseudo-class :${astName}`,
|
|
1747
|
-
SYNTAX_ERR,
|
|
1748
|
-
this.#window
|
|
1749
|
-
)
|
|
1750
|
-
);
|
|
1751
|
-
}
|
|
1839
|
+
} else if (!forgive) {
|
|
1840
|
+
this.onError(
|
|
1841
|
+
generateException(
|
|
1842
|
+
`Unknown pseudo-class :${astName}`,
|
|
1843
|
+
SYNTAX_ERR,
|
|
1844
|
+
this.#window
|
|
1845
|
+
)
|
|
1846
|
+
);
|
|
1752
1847
|
}
|
|
1753
1848
|
}
|
|
1754
1849
|
}
|
|
1755
|
-
return
|
|
1850
|
+
return false;
|
|
1756
1851
|
}
|
|
1757
1852
|
|
|
1758
1853
|
/**
|
|
@@ -1761,7 +1856,7 @@ export class Finder {
|
|
|
1761
1856
|
* @param {Array.<object>} leaves - The AST leaves.
|
|
1762
1857
|
* @param {object} host - The host element.
|
|
1763
1858
|
* @param {object} ast - The original AST for error reporting.
|
|
1764
|
-
* @returns {boolean} True if
|
|
1859
|
+
* @returns {boolean} True if matches, otherwise false.
|
|
1765
1860
|
*/
|
|
1766
1861
|
_evaluateHostPseudo = (leaves, host, ast) => {
|
|
1767
1862
|
const l = leaves.length;
|
|
@@ -1773,7 +1868,7 @@ export class Finder {
|
|
|
1773
1868
|
this.onError(generateException(msg, SYNTAX_ERR, this.#window));
|
|
1774
1869
|
return false;
|
|
1775
1870
|
}
|
|
1776
|
-
if (!this._matchSelector(leaf, host)
|
|
1871
|
+
if (!this._matchSelector(leaf, host)) {
|
|
1777
1872
|
return false;
|
|
1778
1873
|
}
|
|
1779
1874
|
}
|
|
@@ -1801,7 +1896,7 @@ export class Finder {
|
|
|
1801
1896
|
this.onError(generateException(msg, SYNTAX_ERR, this.#window));
|
|
1802
1897
|
return false;
|
|
1803
1898
|
}
|
|
1804
|
-
bool = this._matchSelector(leaf, parent)
|
|
1899
|
+
bool = this._matchSelector(leaf, parent);
|
|
1805
1900
|
if (!bool) {
|
|
1806
1901
|
break;
|
|
1807
1902
|
}
|
|
@@ -1815,44 +1910,48 @@ export class Finder {
|
|
|
1815
1910
|
};
|
|
1816
1911
|
|
|
1817
1912
|
/**
|
|
1818
|
-
*
|
|
1913
|
+
* Evaluates shadow host pseudo-classes.
|
|
1819
1914
|
* @private
|
|
1820
1915
|
* @param {object} ast - The AST.
|
|
1821
1916
|
* @param {object} node - The DocumentFragment node.
|
|
1822
|
-
* @returns {
|
|
1917
|
+
* @returns {boolean} True if matches, otherwise false.
|
|
1823
1918
|
*/
|
|
1824
|
-
|
|
1919
|
+
_evaluateShadowHost = (ast, node) => {
|
|
1825
1920
|
const { children: astChildren, name: astName } = ast;
|
|
1826
1921
|
// Handle simple pseudo-class (no arguments).
|
|
1827
1922
|
if (!Array.isArray(astChildren)) {
|
|
1828
1923
|
if (astName === 'host') {
|
|
1829
|
-
return
|
|
1924
|
+
return true;
|
|
1830
1925
|
}
|
|
1831
1926
|
const msg = `Invalid selector :${astName}`;
|
|
1832
|
-
|
|
1927
|
+
this.onError(generateException(msg, SYNTAX_ERR, this.#window));
|
|
1928
|
+
return false;
|
|
1833
1929
|
}
|
|
1834
1930
|
// Handle functional pseudo-class like :host(...).
|
|
1835
1931
|
if (astName !== 'host' && astName !== 'host-context') {
|
|
1836
1932
|
const msg = `Invalid selector :${astName}()`;
|
|
1837
|
-
|
|
1933
|
+
this.onError(generateException(msg, SYNTAX_ERR, this.#window));
|
|
1934
|
+
return false;
|
|
1838
1935
|
}
|
|
1839
1936
|
if (astChildren.length !== 1) {
|
|
1840
1937
|
const css = generateCSS(ast);
|
|
1841
1938
|
const msg = `Invalid selector ${css}`;
|
|
1842
|
-
|
|
1939
|
+
this.onError(generateException(msg, SYNTAX_ERR, this.#window));
|
|
1940
|
+
return false;
|
|
1843
1941
|
}
|
|
1844
1942
|
const { host } = node;
|
|
1845
1943
|
const { branches } = walkAST(astChildren[0]);
|
|
1846
1944
|
const [branch] = branches;
|
|
1847
1945
|
const [...leaves] = branch;
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1946
|
+
if (astName === 'host' && this._evaluateHostPseudo(leaves, host, ast)) {
|
|
1947
|
+
return true;
|
|
1948
|
+
} else if (
|
|
1949
|
+
astName === 'host-context' &&
|
|
1950
|
+
this._evaluateHostContextPseudo(leaves, host, ast)
|
|
1951
|
+
) {
|
|
1952
|
+
return true;
|
|
1854
1953
|
}
|
|
1855
|
-
return
|
|
1954
|
+
return false;
|
|
1856
1955
|
};
|
|
1857
1956
|
|
|
1858
1957
|
/**
|
|
@@ -1861,39 +1960,26 @@ export class Finder {
|
|
|
1861
1960
|
* @param {object} ast - The AST.
|
|
1862
1961
|
* @param {object} node - The Element node.
|
|
1863
1962
|
* @param {object} opt - Options.
|
|
1864
|
-
* @returns {
|
|
1963
|
+
* @returns {boolean} True if matches, otherwise false.
|
|
1865
1964
|
*/
|
|
1866
1965
|
_matchSelectorForElement = (ast, node, opt) => {
|
|
1867
1966
|
const { type: astType } = ast;
|
|
1868
1967
|
const astName = unescapeSelector(ast.name);
|
|
1869
|
-
const matched = new Set();
|
|
1870
1968
|
switch (astType) {
|
|
1871
1969
|
case ATTR_SELECTOR: {
|
|
1872
|
-
|
|
1873
|
-
matched.add(node);
|
|
1874
|
-
}
|
|
1875
|
-
break;
|
|
1970
|
+
return matchAttributeSelector(ast, node, opt);
|
|
1876
1971
|
}
|
|
1877
1972
|
case ID_SELECTOR: {
|
|
1878
|
-
|
|
1879
|
-
matched.add(node);
|
|
1880
|
-
}
|
|
1881
|
-
break;
|
|
1973
|
+
return node.id === astName;
|
|
1882
1974
|
}
|
|
1883
1975
|
case CLASS_SELECTOR: {
|
|
1884
|
-
|
|
1885
|
-
matched.add(node);
|
|
1886
|
-
}
|
|
1887
|
-
break;
|
|
1976
|
+
return node.classList.contains(astName);
|
|
1888
1977
|
}
|
|
1889
1978
|
case PS_CLASS_SELECTOR: {
|
|
1890
1979
|
return this._matchPseudoClassSelector(ast, node, opt);
|
|
1891
1980
|
}
|
|
1892
1981
|
case TYPE_SELECTOR: {
|
|
1893
|
-
|
|
1894
|
-
matched.add(node);
|
|
1895
|
-
}
|
|
1896
|
-
break;
|
|
1982
|
+
return matchTypeSelector(ast, node, opt);
|
|
1897
1983
|
}
|
|
1898
1984
|
// PS_ELEMENT_SELECTOR is handled by default.
|
|
1899
1985
|
default: {
|
|
@@ -1901,7 +1987,7 @@ export class Finder {
|
|
|
1901
1987
|
if (this.#check) {
|
|
1902
1988
|
const css = generateCSS(ast);
|
|
1903
1989
|
this.#pseudoElement.push(css);
|
|
1904
|
-
|
|
1990
|
+
return true;
|
|
1905
1991
|
} else {
|
|
1906
1992
|
matchPseudoElementSelector(astName, astType, opt);
|
|
1907
1993
|
}
|
|
@@ -1910,7 +1996,7 @@ export class Finder {
|
|
|
1910
1996
|
}
|
|
1911
1997
|
}
|
|
1912
1998
|
}
|
|
1913
|
-
return
|
|
1999
|
+
return false;
|
|
1914
2000
|
};
|
|
1915
2001
|
|
|
1916
2002
|
/**
|
|
@@ -1919,7 +2005,7 @@ export class Finder {
|
|
|
1919
2005
|
* @param {object} ast - The AST.
|
|
1920
2006
|
* @param {object} node - The DocumentFragment node.
|
|
1921
2007
|
* @param {object} [opt] - Options.
|
|
1922
|
-
* @returns {
|
|
2008
|
+
* @returns {boolean} True if matches, otherwise false.
|
|
1923
2009
|
*/
|
|
1924
2010
|
_matchSelectorForShadowRoot = (ast, node, opt = {}) => {
|
|
1925
2011
|
const { name: astName } = ast;
|
|
@@ -1927,15 +2013,14 @@ export class Finder {
|
|
|
1927
2013
|
opt.isShadowRoot = true;
|
|
1928
2014
|
return this._matchPseudoClassSelector(ast, node, opt);
|
|
1929
2015
|
}
|
|
1930
|
-
const matched = new Set();
|
|
1931
2016
|
if (astName === 'host' || astName === 'host-context') {
|
|
1932
|
-
const
|
|
1933
|
-
if (
|
|
2017
|
+
const matches = this._evaluateShadowHost(ast, node, opt);
|
|
2018
|
+
if (matches) {
|
|
1934
2019
|
this.#verifyShadowHost = true;
|
|
1935
|
-
|
|
2020
|
+
return true;
|
|
1936
2021
|
}
|
|
1937
2022
|
}
|
|
1938
|
-
return
|
|
2023
|
+
return false;
|
|
1939
2024
|
};
|
|
1940
2025
|
|
|
1941
2026
|
/**
|
|
@@ -1944,7 +2029,7 @@ export class Finder {
|
|
|
1944
2029
|
* @param {object} ast - The AST.
|
|
1945
2030
|
* @param {object} node - The Document, DocumentFragment, or Element node.
|
|
1946
2031
|
* @param {object} opt - Options.
|
|
1947
|
-
* @returns {
|
|
2032
|
+
* @returns {boolean} True if matches, otherwise false.
|
|
1948
2033
|
*/
|
|
1949
2034
|
_matchSelector = (ast, node, opt) => {
|
|
1950
2035
|
if (node.nodeType === ELEMENT_NODE) {
|
|
@@ -1957,7 +2042,7 @@ export class Finder {
|
|
|
1957
2042
|
) {
|
|
1958
2043
|
return this._matchSelectorForShadowRoot(ast, node, opt);
|
|
1959
2044
|
}
|
|
1960
|
-
return
|
|
2045
|
+
return false;
|
|
1961
2046
|
};
|
|
1962
2047
|
|
|
1963
2048
|
/**
|
|
@@ -1969,6 +2054,9 @@ export class Finder {
|
|
|
1969
2054
|
* @returns {boolean} The result.
|
|
1970
2055
|
*/
|
|
1971
2056
|
_matchLeaves = (leaves, node, opt) => {
|
|
2057
|
+
if (!this.#invalidateResults) {
|
|
2058
|
+
this.#invalidateResults = new WeakMap();
|
|
2059
|
+
}
|
|
1972
2060
|
const results = this.#invalidate ? this.#invalidateResults : this.#results;
|
|
1973
2061
|
let result = results.get(leaves);
|
|
1974
2062
|
if (result && result.has(node)) {
|
|
@@ -1999,7 +2087,7 @@ export class Finder {
|
|
|
1999
2087
|
// No action needed for other types.
|
|
2000
2088
|
}
|
|
2001
2089
|
}
|
|
2002
|
-
bool = this._matchSelector(leaf, node, opt)
|
|
2090
|
+
bool = this._matchSelector(leaf, node, opt);
|
|
2003
2091
|
if (!bool) {
|
|
2004
2092
|
break;
|
|
2005
2093
|
}
|
|
@@ -2023,6 +2111,9 @@ export class Finder {
|
|
|
2023
2111
|
* @returns {Array.<object>} The filtered leaves.
|
|
2024
2112
|
*/
|
|
2025
2113
|
_getFilterLeaves = leaves => {
|
|
2114
|
+
if (!this.#filterLeavesCache) {
|
|
2115
|
+
this.#filterLeavesCache = new WeakMap();
|
|
2116
|
+
}
|
|
2026
2117
|
if (this.#filterLeavesCache.has(leaves)) {
|
|
2027
2118
|
return this.#filterLeavesCache.get(leaves);
|
|
2028
2119
|
}
|
|
@@ -2105,7 +2196,7 @@ export class Finder {
|
|
|
2105
2196
|
};
|
|
2106
2197
|
|
|
2107
2198
|
/**
|
|
2108
|
-
* Collects combinator matches into an array
|
|
2199
|
+
* Collects combinator matches into an array.
|
|
2109
2200
|
* @private
|
|
2110
2201
|
* @param {object} twig - The twig object.
|
|
2111
2202
|
* @param {object} node - The Element node.
|
|
@@ -2132,12 +2223,36 @@ export class Finder {
|
|
|
2132
2223
|
break;
|
|
2133
2224
|
}
|
|
2134
2225
|
case '~': {
|
|
2226
|
+
const parentNode = node.parentNode;
|
|
2227
|
+
if (!parentNode) {
|
|
2228
|
+
break;
|
|
2229
|
+
}
|
|
2230
|
+
if (!this.#combinatorCache) {
|
|
2231
|
+
this.#combinatorCache = new WeakMap();
|
|
2232
|
+
}
|
|
2233
|
+
let cacheMap = this.#combinatorCache.get(parentNode);
|
|
2234
|
+
if (!cacheMap) {
|
|
2235
|
+
cacheMap = new Map();
|
|
2236
|
+
this.#combinatorCache.set(parentNode, cacheMap);
|
|
2237
|
+
}
|
|
2238
|
+
let matchedSet = cacheMap.get(leaves);
|
|
2239
|
+
if (!matchedSet) {
|
|
2240
|
+
matchedSet = new Set();
|
|
2241
|
+
let child = parentNode.firstElementChild;
|
|
2242
|
+
while (child) {
|
|
2243
|
+
if (this._matchLeaves(leaves, child, opt)) {
|
|
2244
|
+
matchedSet.add(child);
|
|
2245
|
+
}
|
|
2246
|
+
child = child.nextElementSibling;
|
|
2247
|
+
}
|
|
2248
|
+
cacheMap.set(leaves, matchedSet);
|
|
2249
|
+
}
|
|
2135
2250
|
let refNode =
|
|
2136
2251
|
dir === DIR_NEXT
|
|
2137
2252
|
? node.nextElementSibling
|
|
2138
2253
|
: node.previousElementSibling;
|
|
2139
2254
|
while (refNode) {
|
|
2140
|
-
if (
|
|
2255
|
+
if (matchedSet.has(refNode)) {
|
|
2141
2256
|
matched.push(refNode);
|
|
2142
2257
|
}
|
|
2143
2258
|
refNode =
|
|
@@ -2298,8 +2413,8 @@ export class Finder {
|
|
|
2298
2413
|
this.#nodeWalker = this._createTreeWalker(this.#node);
|
|
2299
2414
|
}
|
|
2300
2415
|
return this._traverseAndCollectNodes(this.#nodeWalker, leaves, {
|
|
2301
|
-
|
|
2302
|
-
|
|
2416
|
+
...traversalOpts,
|
|
2417
|
+
startNode: node
|
|
2303
2418
|
});
|
|
2304
2419
|
};
|
|
2305
2420
|
|
|
@@ -2487,13 +2602,18 @@ export class Finder {
|
|
|
2487
2602
|
let pending = false;
|
|
2488
2603
|
if (targetType !== TARGET_LINEAL && /host(?:-context)?/.test(leaf.name)) {
|
|
2489
2604
|
let shadowRoot = null;
|
|
2490
|
-
if (
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2605
|
+
if (
|
|
2606
|
+
this.#shadow &&
|
|
2607
|
+
this.#node.nodeType === DOCUMENT_FRAGMENT_NODE &&
|
|
2608
|
+
this._evaluateShadowHost(leaf, this.#node)
|
|
2609
|
+
) {
|
|
2610
|
+
shadowRoot = this.#node;
|
|
2611
|
+
} else if (
|
|
2612
|
+
filterLeaves.length &&
|
|
2613
|
+
this.#node.nodeType === ELEMENT_NODE &&
|
|
2614
|
+
this._evaluateShadowHost(leaf, this.#node.shadowRoot)
|
|
2615
|
+
) {
|
|
2616
|
+
shadowRoot = this.#node.shadowRoot;
|
|
2497
2617
|
}
|
|
2498
2618
|
if (shadowRoot) {
|
|
2499
2619
|
let bool = true;
|
|
@@ -2503,19 +2623,11 @@ export class Finder {
|
|
|
2503
2623
|
switch (filterLeaf.name) {
|
|
2504
2624
|
case 'host':
|
|
2505
2625
|
case 'host-context': {
|
|
2506
|
-
|
|
2507
|
-
filterLeaf,
|
|
2508
|
-
shadowRoot
|
|
2509
|
-
);
|
|
2510
|
-
bool = matchedNode === shadowRoot;
|
|
2626
|
+
bool = this._evaluateShadowHost(filterLeaf, shadowRoot);
|
|
2511
2627
|
break;
|
|
2512
2628
|
}
|
|
2513
2629
|
case 'has': {
|
|
2514
|
-
bool = this._matchPseudoClassSelector(
|
|
2515
|
-
filterLeaf,
|
|
2516
|
-
shadowRoot,
|
|
2517
|
-
{}
|
|
2518
|
-
).has(shadowRoot);
|
|
2630
|
+
bool = this._matchPseudoClassSelector(filterLeaf, shadowRoot, {});
|
|
2519
2631
|
break;
|
|
2520
2632
|
}
|
|
2521
2633
|
default: {
|
|
@@ -2630,31 +2742,20 @@ export class Finder {
|
|
|
2630
2742
|
const {
|
|
2631
2743
|
leaves: [{ name: lastName, type: lastType }]
|
|
2632
2744
|
} = lastTwig;
|
|
2633
|
-
const { combo: firstCombo } = firstTwig;
|
|
2634
2745
|
if (
|
|
2635
2746
|
this.#selector.includes(':scope') ||
|
|
2636
2747
|
lastType === PS_ELEMENT_SELECTOR ||
|
|
2637
2748
|
lastType === ID_SELECTOR
|
|
2638
2749
|
) {
|
|
2639
2750
|
return { dir: DIR_PREV, twig: lastTwig };
|
|
2640
|
-
}
|
|
2641
|
-
if (firstType === ID_SELECTOR) {
|
|
2751
|
+
} else if (firstType === ID_SELECTOR) {
|
|
2642
2752
|
return { dir: DIR_NEXT, twig: firstTwig };
|
|
2643
|
-
}
|
|
2644
|
-
if (firstName === '*' && firstType === TYPE_SELECTOR) {
|
|
2753
|
+
} else if (firstName === '*' && firstType === TYPE_SELECTOR) {
|
|
2645
2754
|
return { dir: DIR_PREV, twig: lastTwig };
|
|
2646
|
-
}
|
|
2647
|
-
if (lastName === '*' && lastType === TYPE_SELECTOR) {
|
|
2755
|
+
} else if (lastName === '*' && lastType === TYPE_SELECTOR) {
|
|
2648
2756
|
return { dir: DIR_NEXT, twig: firstTwig };
|
|
2649
|
-
}
|
|
2650
|
-
|
|
2651
|
-
if (targetType === TARGET_FIRST) {
|
|
2652
|
-
return { dir: DIR_PREV, twig: lastTwig };
|
|
2653
|
-
}
|
|
2654
|
-
const { name: comboName } = firstCombo;
|
|
2655
|
-
if (comboName === '+' || comboName === '~') {
|
|
2656
|
-
return { dir: DIR_PREV, twig: lastTwig };
|
|
2657
|
-
}
|
|
2757
|
+
} else if (branchLen === 1 || branchLen === 2) {
|
|
2758
|
+
return { dir: DIR_PREV, twig: lastTwig };
|
|
2658
2759
|
} else if (branchLen > 2 && this.#scoped && targetType === TARGET_FIRST) {
|
|
2659
2760
|
if (lastType === TYPE_SELECTOR) {
|
|
2660
2761
|
return { dir: DIR_PREV, twig: lastTwig };
|
|
@@ -2879,7 +2980,6 @@ export class Finder {
|
|
|
2879
2980
|
const matchedNodes = new Set();
|
|
2880
2981
|
const branchLen = branch.length;
|
|
2881
2982
|
const lastIndex = branchLen - 1;
|
|
2882
|
-
|
|
2883
2983
|
if (dir === DIR_NEXT) {
|
|
2884
2984
|
const { combo: firstCombo } = branch[0];
|
|
2885
2985
|
for (const node of entryNodes) {
|