@asamuzakjp/dom-selector 8.0.2 → 8.1.2
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 +5 -4
- package/package.json +15 -9
- package/src/index.js +68 -38
- package/src/js/constant.js +7 -4
- package/src/js/finder.js +1134 -1017
- package/src/js/matcher.js +59 -24
- package/src/js/nwsapi.js +29 -11
- package/src/js/selector.js +11 -22
- package/src/js/utility.js +173 -33
- package/types/index.d.ts +1 -1
- package/types/js/constant.d.ts +2 -0
- package/types/js/finder.d.ts +4 -1
- package/types/js/matcher.d.ts +2 -2
- package/types/js/utility.d.ts +5 -2
package/src/js/finder.js
CHANGED
|
@@ -22,11 +22,13 @@ import {
|
|
|
22
22
|
import { createHasValidator, isInvalidCombinator } from './selector.js';
|
|
23
23
|
import {
|
|
24
24
|
filterNodesByAnB,
|
|
25
|
+
findBestSeed,
|
|
25
26
|
generateException,
|
|
26
27
|
isCustomElement,
|
|
27
28
|
isFocusVisible,
|
|
28
29
|
isFocusableArea,
|
|
29
30
|
isVisible,
|
|
31
|
+
populateHasAllowlist,
|
|
30
32
|
resolveContent,
|
|
31
33
|
sortNodes,
|
|
32
34
|
traverseNode
|
|
@@ -40,6 +42,7 @@ import {
|
|
|
40
42
|
DOCUMENT_FRAGMENT_NODE,
|
|
41
43
|
ELEMENT_NODE,
|
|
42
44
|
FORM_PARTS,
|
|
45
|
+
HEX,
|
|
43
46
|
ID_SELECTOR,
|
|
44
47
|
INPUT_CHECK,
|
|
45
48
|
INPUT_DATE,
|
|
@@ -59,6 +62,8 @@ import {
|
|
|
59
62
|
TEXT_NODE,
|
|
60
63
|
TYPE_SELECTOR
|
|
61
64
|
} from './constant.js';
|
|
65
|
+
const ANB_FIRST = { a: 0, b: 1 };
|
|
66
|
+
const ANB_LAST = { a: 0, b: 1, reverse: true };
|
|
62
67
|
const DIR_NEXT = 'next';
|
|
63
68
|
const DIR_PREV = 'prev';
|
|
64
69
|
const KEYS_FORM = new Set([...FORM_PARTS, 'fieldset', 'form']);
|
|
@@ -104,17 +109,20 @@ const KEYS_PS_NTH_OF_TYPE = new Set([
|
|
|
104
109
|
*/
|
|
105
110
|
export class Finder {
|
|
106
111
|
/* private fields */
|
|
112
|
+
#anbCache;
|
|
107
113
|
#ast;
|
|
108
|
-
#astCache;
|
|
114
|
+
#astCache = new WeakMap();
|
|
109
115
|
#check;
|
|
116
|
+
#combinatorCache;
|
|
110
117
|
#descendant;
|
|
111
118
|
#document;
|
|
112
|
-
#documentCache;
|
|
119
|
+
#documentCache = new WeakMap();
|
|
113
120
|
#documentURL;
|
|
114
121
|
#event;
|
|
115
122
|
#eventHandlers;
|
|
116
123
|
#filterLeavesCache;
|
|
117
124
|
#focus;
|
|
125
|
+
#focusWithinCache;
|
|
118
126
|
#invalidate;
|
|
119
127
|
#invalidateResults;
|
|
120
128
|
#lastFocusVisible;
|
|
@@ -122,6 +130,17 @@ export class Finder {
|
|
|
122
130
|
#nodeWalker;
|
|
123
131
|
#nodes;
|
|
124
132
|
#noexcept;
|
|
133
|
+
#nthChildCache;
|
|
134
|
+
#nthChildOfCache;
|
|
135
|
+
#nthChildResultCache;
|
|
136
|
+
#nthOfTypeCache;
|
|
137
|
+
#nthOfTypeResultCache;
|
|
138
|
+
#psDefaultCache;
|
|
139
|
+
#psDirCache;
|
|
140
|
+
#psHasFilterCache;
|
|
141
|
+
#psIndeterminateCache;
|
|
142
|
+
#psLangCache;
|
|
143
|
+
#psValidCache;
|
|
125
144
|
#pseudoElement;
|
|
126
145
|
#results;
|
|
127
146
|
#root;
|
|
@@ -130,6 +149,7 @@ export class Finder {
|
|
|
130
149
|
#selector;
|
|
131
150
|
#selectorAST;
|
|
132
151
|
#shadow;
|
|
152
|
+
#targetWithinCache;
|
|
133
153
|
#verifyShadowHost;
|
|
134
154
|
#walkers;
|
|
135
155
|
#warn;
|
|
@@ -141,11 +161,6 @@ export class Finder {
|
|
|
141
161
|
*/
|
|
142
162
|
constructor(window) {
|
|
143
163
|
this.#window = window;
|
|
144
|
-
this.#astCache = new WeakMap();
|
|
145
|
-
this.#documentCache = new WeakMap();
|
|
146
|
-
this.#event = null;
|
|
147
|
-
this.#focus = null;
|
|
148
|
-
this.#lastFocusVisible = null;
|
|
149
164
|
this.#eventHandlers = new Set([
|
|
150
165
|
{
|
|
151
166
|
keys: ['focus', 'focusin'],
|
|
@@ -160,7 +175,6 @@ export class Finder {
|
|
|
160
175
|
handler: this._handleMouseEvent
|
|
161
176
|
}
|
|
162
177
|
]);
|
|
163
|
-
this.#filterLeavesCache = new WeakMap();
|
|
164
178
|
this._registerEventListeners();
|
|
165
179
|
this.clearResults(true);
|
|
166
180
|
}
|
|
@@ -217,7 +231,7 @@ export class Finder {
|
|
|
217
231
|
this.#node !== this.#root && this.#node.nodeType === ELEMENT_NODE;
|
|
218
232
|
this.#selector = selector;
|
|
219
233
|
this.#pseudoElement = [];
|
|
220
|
-
this.#walkers =
|
|
234
|
+
this.#walkers = null;
|
|
221
235
|
this.#nodeWalker = null;
|
|
222
236
|
this.#rootWalker = null;
|
|
223
237
|
this.#verifyShadowHost = null;
|
|
@@ -227,14 +241,29 @@ export class Finder {
|
|
|
227
241
|
|
|
228
242
|
/**
|
|
229
243
|
* Clear cached results.
|
|
230
|
-
* @param {boolean} all -
|
|
244
|
+
* @param {boolean} all - Clear all results.
|
|
231
245
|
* @returns {void}
|
|
232
246
|
*/
|
|
233
247
|
clearResults = (all = false) => {
|
|
234
|
-
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;
|
|
235
264
|
if (all) {
|
|
265
|
+
this.#filterLeavesCache = null;
|
|
236
266
|
this.#results = new WeakMap();
|
|
237
|
-
this.#filterLeavesCache = new WeakMap();
|
|
238
267
|
}
|
|
239
268
|
};
|
|
240
269
|
|
|
@@ -299,22 +328,20 @@ export class Finder {
|
|
|
299
328
|
* @private
|
|
300
329
|
* @param {Array.<Array.<object>>} branches - The branches from walkAST.
|
|
301
330
|
* @param {string} selector - The original selector for error reporting.
|
|
302
|
-
* @returns {{ast: Array, descendant: boolean}}
|
|
303
|
-
* An object with the AST, descendant flag.
|
|
331
|
+
* @returns {{ast: Array, descendant: boolean}} An object with the AST, descendant flag.
|
|
304
332
|
*/
|
|
305
333
|
_processSelectorBranches = (branches, selector) => {
|
|
306
334
|
let descendant = false;
|
|
307
335
|
const ast = [];
|
|
308
|
-
const
|
|
309
|
-
for (let i = 0; i < l; i++) {
|
|
310
|
-
const items = [...branches[i]];
|
|
336
|
+
for (const items of branches) {
|
|
311
337
|
const branch = [];
|
|
312
338
|
let prevType = null;
|
|
313
|
-
|
|
314
|
-
if (
|
|
339
|
+
const itemsLen = items.length;
|
|
340
|
+
if (itemsLen) {
|
|
315
341
|
const leaves = new Set();
|
|
316
|
-
|
|
317
|
-
const
|
|
342
|
+
for (let j = 0; j < itemsLen; j++) {
|
|
343
|
+
const item = items[j];
|
|
344
|
+
const isLast = j === itemsLen - 1;
|
|
318
345
|
if (isInvalidCombinator(item.type, prevType, isLast)) {
|
|
319
346
|
const msg = `Invalid selector ${selector}`;
|
|
320
347
|
this.onError(generateException(msg, SYNTAX_ERR, this.#window));
|
|
@@ -339,12 +366,9 @@ export class Finder {
|
|
|
339
366
|
leaves.add(item);
|
|
340
367
|
}
|
|
341
368
|
prevType = item.type;
|
|
342
|
-
if (
|
|
343
|
-
item = items.shift();
|
|
344
|
-
} else {
|
|
369
|
+
if (isLast) {
|
|
345
370
|
branch.push({ combo: null, leaves: sortAST(leaves) });
|
|
346
371
|
leaves.clear();
|
|
347
|
-
break;
|
|
348
372
|
}
|
|
349
373
|
}
|
|
350
374
|
}
|
|
@@ -438,7 +462,11 @@ export class Finder {
|
|
|
438
462
|
const { force = false, whatToShow = SHOW_CONTAINER } = opt;
|
|
439
463
|
if (force) {
|
|
440
464
|
return this.#document.createTreeWalker(node, whatToShow);
|
|
441
|
-
}
|
|
465
|
+
}
|
|
466
|
+
if (!this.#walkers) {
|
|
467
|
+
this.#walkers = new WeakMap();
|
|
468
|
+
}
|
|
469
|
+
if (this.#walkers.has(node)) {
|
|
442
470
|
return this.#walkers.get(node);
|
|
443
471
|
}
|
|
444
472
|
const walker = this.#document.createTreeWalker(node, whatToShow);
|
|
@@ -505,7 +533,7 @@ export class Finder {
|
|
|
505
533
|
* @param {number} anb.a - The 'a' value.
|
|
506
534
|
* @param {number} anb.b - The 'b' value.
|
|
507
535
|
* @param {boolean} [anb.reverse] - If true, reverses the order.
|
|
508
|
-
* @param {object} [anb.selector] - The
|
|
536
|
+
* @param {object} [anb.selector] - The selector for 'of S'.
|
|
509
537
|
* @param {object} node - The Element node.
|
|
510
538
|
* @param {object} opt - Options.
|
|
511
539
|
* @returns {Set.<object>} A collection of matched nodes.
|
|
@@ -532,16 +560,48 @@ export class Finder {
|
|
|
532
560
|
}
|
|
533
561
|
return matchedNode;
|
|
534
562
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
const
|
|
544
|
-
|
|
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;
|
|
545
605
|
};
|
|
546
606
|
|
|
547
607
|
/**
|
|
@@ -555,27 +615,59 @@ export class Finder {
|
|
|
555
615
|
* @returns {Set.<object>} A collection of matched nodes.
|
|
556
616
|
*/
|
|
557
617
|
_collectNthOfType = (anb, node) => {
|
|
558
|
-
const { parentNode } = node;
|
|
618
|
+
const { localName, namespaceURI, parentNode, prefix } = node;
|
|
559
619
|
if (!parentNode) {
|
|
560
620
|
if (node === this.#root && anb.a * 1 + anb.b * 1 === 1) {
|
|
561
621
|
return new Set([node]);
|
|
562
622
|
}
|
|
563
623
|
return new Set();
|
|
564
624
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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;
|
|
574
664
|
}
|
|
575
|
-
|
|
665
|
+
typeMap.set(typeKey, typedSiblings);
|
|
576
666
|
}
|
|
577
667
|
const matchedNodes = filterNodesByAnB(typedSiblings, anb);
|
|
578
|
-
|
|
668
|
+
const resultSet = new Set(matchedNodes);
|
|
669
|
+
typeResultMap.set(typeKey, resultSet);
|
|
670
|
+
return resultSet;
|
|
579
671
|
};
|
|
580
672
|
|
|
581
673
|
/**
|
|
@@ -585,53 +677,61 @@ export class Finder {
|
|
|
585
677
|
* @param {object} node - The Element node.
|
|
586
678
|
* @param {string} nthName - The name of the nth pseudo-class.
|
|
587
679
|
* @param {object} opt - Options.
|
|
588
|
-
* @returns {
|
|
680
|
+
* @returns {boolean} True if matches, otherwise false.
|
|
589
681
|
*/
|
|
590
682
|
_matchAnPlusB = (ast, node, nthName, opt) => {
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
}
|
|
613
|
-
if (typeof b === 'string' && /-?\d+/.test(b)) {
|
|
614
|
-
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
|
+
}
|
|
615
704
|
} else {
|
|
616
|
-
|
|
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
|
+
}
|
|
617
718
|
}
|
|
618
|
-
if (nthName
|
|
619
|
-
|
|
719
|
+
if (nthName === 'nth-child' || nthName === 'nth-last-child') {
|
|
720
|
+
if (selector) {
|
|
721
|
+
anbMap.set('selector', selector);
|
|
722
|
+
}
|
|
620
723
|
}
|
|
724
|
+
anb = Object.fromEntries(anbMap);
|
|
725
|
+
this.#anbCache.set(ast, anb);
|
|
621
726
|
}
|
|
622
727
|
if (nthName === 'nth-child' || nthName === 'nth-last-child') {
|
|
623
|
-
if (selector) {
|
|
624
|
-
anbMap.set('selector', selector);
|
|
625
|
-
}
|
|
626
|
-
const anb = Object.fromEntries(anbMap);
|
|
627
728
|
const nodes = this._collectNthChild(anb, node, opt);
|
|
628
|
-
return nodes;
|
|
729
|
+
return nodes.has(node);
|
|
629
730
|
} else if (nthName === 'nth-of-type' || nthName === 'nth-last-of-type') {
|
|
630
|
-
const anb = Object.fromEntries(anbMap);
|
|
631
731
|
const nodes = this._collectNthOfType(anb, node);
|
|
632
|
-
return nodes;
|
|
732
|
+
return nodes.has(node);
|
|
633
733
|
}
|
|
634
|
-
return
|
|
734
|
+
return false;
|
|
635
735
|
};
|
|
636
736
|
|
|
637
737
|
/**
|
|
@@ -643,55 +743,113 @@ export class Finder {
|
|
|
643
743
|
* @returns {boolean} The result.
|
|
644
744
|
*/
|
|
645
745
|
_matchHasPseudoFunc = (astLeaves, node, opt = {}) => {
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
}
|
|
660
|
-
const twigLeaves = [];
|
|
661
|
-
while (leaves.length) {
|
|
662
|
-
const [item] = leaves;
|
|
663
|
-
const { type: itemType } = item;
|
|
664
|
-
if (itemType === COMBINATOR) {
|
|
665
|
-
break;
|
|
666
|
-
} else {
|
|
667
|
-
twigLeaves.push(leaves.shift());
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
const twig = {
|
|
671
|
-
combo,
|
|
672
|
-
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
|
|
673
759
|
};
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
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;
|
|
684
784
|
}
|
|
685
|
-
return bool;
|
|
686
785
|
}
|
|
687
|
-
return
|
|
786
|
+
return bool;
|
|
688
787
|
}
|
|
788
|
+
return true;
|
|
689
789
|
}
|
|
690
790
|
return false;
|
|
691
791
|
};
|
|
692
792
|
|
|
693
793
|
/**
|
|
694
|
-
*
|
|
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.
|
|
695
853
|
* @private
|
|
696
854
|
* @param {object} astData - The AST data.
|
|
697
855
|
* @param {object} node - The Element node.
|
|
@@ -701,9 +859,28 @@ export class Finder {
|
|
|
701
859
|
_evaluateHasPseudo = (astData, node, opt = {}) => {
|
|
702
860
|
const { branches } = astData;
|
|
703
861
|
let bool = false;
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
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
|
+
}
|
|
707
884
|
bool = this._matchHasPseudoFunc(leaves, node, opt);
|
|
708
885
|
if (bool) {
|
|
709
886
|
break;
|
|
@@ -727,13 +904,13 @@ export class Finder {
|
|
|
727
904
|
* @param {object} astData - The AST data.
|
|
728
905
|
* @param {object} node - The Element node.
|
|
729
906
|
* @param {object} [opt] - Options.
|
|
730
|
-
* @returns {
|
|
907
|
+
* @returns {boolean} Tru if matches, otherwise false.
|
|
731
908
|
*/
|
|
732
909
|
_matchLogicalPseudoFunc = (astData, node, opt = {}) => {
|
|
733
910
|
const { astName, branches, twigBranches } = astData;
|
|
734
911
|
// Handle :has().
|
|
735
912
|
if (astName === 'has') {
|
|
736
|
-
return this._evaluateHasPseudo(astData, node, opt);
|
|
913
|
+
return this._evaluateHasPseudo(astData, node, opt) === node;
|
|
737
914
|
}
|
|
738
915
|
// Handle :is(), :not(), :where().
|
|
739
916
|
const isShadowRoot =
|
|
@@ -755,7 +932,7 @@ export class Finder {
|
|
|
755
932
|
}
|
|
756
933
|
}
|
|
757
934
|
if (invalid) {
|
|
758
|
-
return
|
|
935
|
+
return false;
|
|
759
936
|
}
|
|
760
937
|
}
|
|
761
938
|
opt.forgive = astName === 'is' || astName === 'where';
|
|
@@ -792,635 +969,376 @@ export class Finder {
|
|
|
792
969
|
}
|
|
793
970
|
}
|
|
794
971
|
if (astName === 'not') {
|
|
795
|
-
|
|
796
|
-
return null;
|
|
797
|
-
}
|
|
798
|
-
return node;
|
|
799
|
-
} else if (bool) {
|
|
800
|
-
return node;
|
|
972
|
+
return !bool;
|
|
801
973
|
}
|
|
802
|
-
return
|
|
974
|
+
return bool;
|
|
803
975
|
};
|
|
804
976
|
|
|
805
977
|
/**
|
|
806
|
-
*
|
|
978
|
+
* Evaluates logical pseudo-class selector.
|
|
807
979
|
* @private
|
|
808
|
-
* @see https://html.spec.whatwg.org/#pseudo-classes
|
|
809
980
|
* @param {object} ast - The AST.
|
|
810
981
|
* @param {object} node - The Element node.
|
|
811
982
|
* @param {object} [opt] - Options.
|
|
812
983
|
* @param {boolean} [opt.forgive] - Ignores unknown or invalid selectors.
|
|
813
984
|
* @param {boolean} [opt.warn] - If true, console warnings are enabled.
|
|
814
|
-
* @returns {
|
|
985
|
+
* @returns {boolean} True if matches, otherwise false.
|
|
815
986
|
*/
|
|
816
|
-
|
|
987
|
+
_evaluateLogicalPseudo(ast, node, opt = {}) {
|
|
817
988
|
const { children: astChildren, name: astName } = ast;
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
if (
|
|
830
|
-
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
|
+
};
|
|
831
1005
|
} else {
|
|
832
|
-
const
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
if (leaves.length) {
|
|
857
|
-
item = leaves.shift();
|
|
858
|
-
} else {
|
|
859
|
-
branch.push({
|
|
860
|
-
combo: null,
|
|
861
|
-
leaves: [...leavesSet]
|
|
862
|
-
});
|
|
863
|
-
leavesSet.clear();
|
|
864
|
-
break;
|
|
865
|
-
}
|
|
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);
|
|
1023
|
+
}
|
|
1024
|
+
if (j === leavesLen - 1) {
|
|
1025
|
+
branch.push({
|
|
1026
|
+
combo: null,
|
|
1027
|
+
leaves: [...leavesSet]
|
|
1028
|
+
});
|
|
1029
|
+
leavesSet.clear();
|
|
866
1030
|
}
|
|
867
|
-
twigBranches.push(branch);
|
|
868
1031
|
}
|
|
869
|
-
|
|
870
|
-
astName,
|
|
871
|
-
branches,
|
|
872
|
-
twigBranches
|
|
873
|
-
};
|
|
874
|
-
this.#astCache.set(ast, astData);
|
|
1032
|
+
twigBranches.push(branch);
|
|
875
1033
|
}
|
|
1034
|
+
astData = {
|
|
1035
|
+
astName,
|
|
1036
|
+
branches,
|
|
1037
|
+
twigBranches
|
|
1038
|
+
};
|
|
876
1039
|
}
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
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;
|
|
880
1067
|
}
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
1068
|
+
const [branch] = astChildren;
|
|
1069
|
+
return this._matchAnPlusB(branch, node, astName, opt);
|
|
1070
|
+
}
|
|
1071
|
+
switch (astName) {
|
|
1072
|
+
// :dir()
|
|
1073
|
+
case 'dir': {
|
|
884
1074
|
if (astChildren.length !== 1) {
|
|
885
1075
|
const css = generateCSS(ast);
|
|
886
|
-
|
|
1076
|
+
this.onError(
|
|
887
1077
|
generateException(
|
|
888
1078
|
`Invalid selector ${css}`,
|
|
889
1079
|
SYNTAX_ERR,
|
|
890
1080
|
this.#window
|
|
891
1081
|
)
|
|
892
1082
|
);
|
|
1083
|
+
return false;
|
|
893
1084
|
}
|
|
894
|
-
const [
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
} else {
|
|
898
|
-
switch (astName) {
|
|
899
|
-
// :dir()
|
|
900
|
-
case 'dir': {
|
|
901
|
-
if (astChildren.length !== 1) {
|
|
902
|
-
const css = generateCSS(ast);
|
|
903
|
-
return this.onError(
|
|
904
|
-
generateException(
|
|
905
|
-
`Invalid selector ${css}`,
|
|
906
|
-
SYNTAX_ERR,
|
|
907
|
-
this.#window
|
|
908
|
-
)
|
|
909
|
-
);
|
|
910
|
-
}
|
|
911
|
-
const [astChild] = astChildren;
|
|
912
|
-
const res = matchDirectionPseudoClass(astChild, node);
|
|
913
|
-
if (res) {
|
|
914
|
-
matched.add(node);
|
|
915
|
-
}
|
|
916
|
-
break;
|
|
917
|
-
}
|
|
918
|
-
// :lang()
|
|
919
|
-
case 'lang': {
|
|
920
|
-
if (!astChildren.length) {
|
|
921
|
-
const css = generateCSS(ast);
|
|
922
|
-
return this.onError(
|
|
923
|
-
generateException(
|
|
924
|
-
`Invalid selector ${css}`,
|
|
925
|
-
SYNTAX_ERR,
|
|
926
|
-
this.#window
|
|
927
|
-
)
|
|
928
|
-
);
|
|
929
|
-
}
|
|
930
|
-
let bool;
|
|
931
|
-
for (const astChild of astChildren) {
|
|
932
|
-
bool = matchLanguagePseudoClass(astChild, node);
|
|
933
|
-
if (bool) {
|
|
934
|
-
break;
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
if (bool) {
|
|
938
|
-
matched.add(node);
|
|
939
|
-
}
|
|
940
|
-
break;
|
|
941
|
-
}
|
|
942
|
-
// :state()
|
|
943
|
-
case 'state': {
|
|
944
|
-
if (isCustomElement(node)) {
|
|
945
|
-
const [{ value: stateValue }] = astChildren;
|
|
946
|
-
if (stateValue) {
|
|
947
|
-
if (node[stateValue]) {
|
|
948
|
-
matched.add(node);
|
|
949
|
-
} else {
|
|
950
|
-
for (const i in node) {
|
|
951
|
-
const prop = node[i];
|
|
952
|
-
if (prop instanceof this.#window.ElementInternals) {
|
|
953
|
-
if (prop?.states?.has(stateValue)) {
|
|
954
|
-
matched.add(node);
|
|
955
|
-
}
|
|
956
|
-
break;
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
break;
|
|
963
|
-
}
|
|
964
|
-
case 'current':
|
|
965
|
-
case 'heading':
|
|
966
|
-
case 'nth-col':
|
|
967
|
-
case 'nth-last-col': {
|
|
968
|
-
if (warn) {
|
|
969
|
-
this.onError(
|
|
970
|
-
generateException(
|
|
971
|
-
`Unsupported pseudo-class :${astName}()`,
|
|
972
|
-
NOT_SUPPORTED_ERR,
|
|
973
|
-
this.#window
|
|
974
|
-
)
|
|
975
|
-
);
|
|
976
|
-
}
|
|
977
|
-
break;
|
|
978
|
-
}
|
|
979
|
-
// Ignore :host() and :host-context().
|
|
980
|
-
case 'host':
|
|
981
|
-
case 'host-context': {
|
|
982
|
-
break;
|
|
983
|
-
}
|
|
984
|
-
// Deprecated in CSS Selectors 3.
|
|
985
|
-
case 'contains': {
|
|
986
|
-
if (warn) {
|
|
987
|
-
this.onError(
|
|
988
|
-
generateException(
|
|
989
|
-
`Unknown pseudo-class :${astName}()`,
|
|
990
|
-
NOT_SUPPORTED_ERR,
|
|
991
|
-
this.#window
|
|
992
|
-
)
|
|
993
|
-
);
|
|
994
|
-
}
|
|
995
|
-
break;
|
|
996
|
-
}
|
|
997
|
-
default: {
|
|
998
|
-
if (!forgive) {
|
|
999
|
-
this.onError(
|
|
1000
|
-
generateException(
|
|
1001
|
-
`Unknown pseudo-class :${astName}()`,
|
|
1002
|
-
SYNTAX_ERR,
|
|
1003
|
-
this.#window
|
|
1004
|
-
)
|
|
1005
|
-
);
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1085
|
+
const [astChild] = astChildren;
|
|
1086
|
+
if (!this.#psDirCache) {
|
|
1087
|
+
this.#psDirCache = new WeakMap();
|
|
1008
1088
|
}
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
matched.add(node);
|
|
1013
|
-
} else if (parentNode) {
|
|
1014
|
-
switch (astName) {
|
|
1015
|
-
case 'first-of-type': {
|
|
1016
|
-
const [node1] = this._collectNthOfType(
|
|
1017
|
-
{
|
|
1018
|
-
a: 0,
|
|
1019
|
-
b: 1
|
|
1020
|
-
},
|
|
1021
|
-
node
|
|
1022
|
-
);
|
|
1023
|
-
if (node1) {
|
|
1024
|
-
matched.add(node1);
|
|
1025
|
-
}
|
|
1026
|
-
break;
|
|
1027
|
-
}
|
|
1028
|
-
case 'last-of-type': {
|
|
1029
|
-
const [node1] = this._collectNthOfType(
|
|
1030
|
-
{
|
|
1031
|
-
a: 0,
|
|
1032
|
-
b: 1,
|
|
1033
|
-
reverse: true
|
|
1034
|
-
},
|
|
1035
|
-
node
|
|
1036
|
-
);
|
|
1037
|
-
if (node1) {
|
|
1038
|
-
matched.add(node1);
|
|
1039
|
-
}
|
|
1040
|
-
break;
|
|
1041
|
-
}
|
|
1042
|
-
// 'only-of-type' is handled by default.
|
|
1043
|
-
default: {
|
|
1044
|
-
const [node1] = this._collectNthOfType(
|
|
1045
|
-
{
|
|
1046
|
-
a: 0,
|
|
1047
|
-
b: 1
|
|
1048
|
-
},
|
|
1049
|
-
node
|
|
1050
|
-
);
|
|
1051
|
-
if (node1 === node) {
|
|
1052
|
-
const [node2] = this._collectNthOfType(
|
|
1053
|
-
{
|
|
1054
|
-
a: 0,
|
|
1055
|
-
b: 1,
|
|
1056
|
-
reverse: true
|
|
1057
|
-
},
|
|
1058
|
-
node
|
|
1059
|
-
);
|
|
1060
|
-
if (node2 === node) {
|
|
1061
|
-
matched.add(node);
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1089
|
+
const res = matchDirectionPseudoClass(astChild, node, this.#psDirCache);
|
|
1090
|
+
if (res) {
|
|
1091
|
+
return true;
|
|
1065
1092
|
}
|
|
1093
|
+
break;
|
|
1066
1094
|
}
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
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;
|
|
1076
1107
|
}
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
const isMatch = matchReadOnlyPseudoClass(astName, node);
|
|
1080
|
-
if (isMatch) {
|
|
1081
|
-
matched.add(node);
|
|
1082
|
-
}
|
|
1083
|
-
break;
|
|
1108
|
+
if (!this.#psLangCache) {
|
|
1109
|
+
this.#psLangCache = new WeakMap();
|
|
1084
1110
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
) {
|
|
1091
|
-
matched.add(node);
|
|
1111
|
+
let bool;
|
|
1112
|
+
for (const astChild of astChildren) {
|
|
1113
|
+
bool = matchLanguagePseudoClass(astChild, node, this.#psLangCache);
|
|
1114
|
+
if (bool) {
|
|
1115
|
+
break;
|
|
1092
1116
|
}
|
|
1093
|
-
break;
|
|
1094
1117
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
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;
|
|
1102
1130
|
}
|
|
1103
|
-
const
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
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
|
+
}
|
|
1107
1139
|
}
|
|
1108
1140
|
}
|
|
1109
|
-
break;
|
|
1110
1141
|
}
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
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
|
+
);
|
|
1114
1156
|
}
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
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
|
+
);
|
|
1125
1174
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
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
|
+
);
|
|
1137
1186
|
}
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
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;
|
|
1151
1230
|
}
|
|
1152
|
-
case '
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
}
|
|
1156
|
-
const { hash } = this.#documentURL;
|
|
1157
|
-
if (hash) {
|
|
1158
|
-
const id = hash.replace(/^#/, '');
|
|
1159
|
-
let current = this.#document.getElementById(id);
|
|
1160
|
-
while (current) {
|
|
1161
|
-
if (current === node) {
|
|
1162
|
-
matched.add(node);
|
|
1163
|
-
break;
|
|
1164
|
-
}
|
|
1165
|
-
current = current.parentNode;
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
break;
|
|
1231
|
+
case 'last-of-type': {
|
|
1232
|
+
const [node1] = this._collectNthOfType(ANB_LAST, node);
|
|
1233
|
+
return node1 === node;
|
|
1169
1234
|
}
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
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;
|
|
1177
1241
|
}
|
|
1178
|
-
break;
|
|
1179
1242
|
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
if (current.nodeType === DOCUMENT_FRAGMENT_NODE) {
|
|
1189
|
-
const { host } = current;
|
|
1190
|
-
if (host === activeElement) {
|
|
1191
|
-
if (isFocusableArea(node)) {
|
|
1192
|
-
matched.add(node);
|
|
1193
|
-
} else {
|
|
1194
|
-
matched.add(host);
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
break;
|
|
1198
|
-
} else {
|
|
1199
|
-
current = current.parentNode;
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
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);
|
|
1204
1251
|
}
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
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
|
-
} else if (eventKey) {
|
|
1250
|
-
if (
|
|
1251
|
-
(eventType === 'keydown' || eventType === 'keyup') &&
|
|
1252
|
-
!eventAltKey &&
|
|
1253
|
-
!eventCtrlKey &&
|
|
1254
|
-
!eventMetaKey &&
|
|
1255
|
-
eventTarget === node
|
|
1256
|
-
) {
|
|
1257
|
-
bool = true;
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
} else if (
|
|
1261
|
-
relatedTarget === null ||
|
|
1262
|
-
relatedTarget === this.#lastFocusVisible
|
|
1263
|
-
) {
|
|
1264
|
-
bool = true;
|
|
1265
|
-
}
|
|
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;
|
|
1266
1295
|
}
|
|
1267
|
-
}
|
|
1268
|
-
if (bool) {
|
|
1269
|
-
this.#lastFocusVisible = node;
|
|
1270
|
-
matched.add(node);
|
|
1271
|
-
} else if (this.#lastFocusVisible === node) {
|
|
1272
|
-
this.#lastFocusVisible = null;
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
break;
|
|
1276
|
-
}
|
|
1277
|
-
case 'focus-within': {
|
|
1278
|
-
const activeElement = this.#document.activeElement;
|
|
1279
|
-
if (node.contains(activeElement) && isFocusableArea(activeElement)) {
|
|
1280
|
-
matched.add(node);
|
|
1281
|
-
} else if (activeElement.shadowRoot) {
|
|
1282
|
-
const activeShadowElement = activeElement.shadowRoot.activeElement;
|
|
1283
|
-
if (node.contains(activeShadowElement)) {
|
|
1284
|
-
matched.add(node);
|
|
1285
1296
|
} else {
|
|
1286
|
-
let current = activeShadowElement;
|
|
1287
|
-
while (current) {
|
|
1288
|
-
if (current.nodeType === DOCUMENT_FRAGMENT_NODE) {
|
|
1289
|
-
const { host } = current;
|
|
1290
|
-
if (host === activeElement && node.contains(host)) {
|
|
1291
|
-
matched.add(node);
|
|
1292
|
-
}
|
|
1293
|
-
break;
|
|
1294
|
-
} else {
|
|
1295
|
-
current = current.parentNode;
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
break;
|
|
1301
|
-
}
|
|
1302
|
-
case 'open':
|
|
1303
|
-
case 'closed': {
|
|
1304
|
-
if (localName === 'details' || localName === 'dialog') {
|
|
1305
|
-
if (node.hasAttribute('open')) {
|
|
1306
|
-
if (astName === 'open') {
|
|
1307
|
-
matched.add(node);
|
|
1308
|
-
}
|
|
1309
|
-
} else if (astName === 'closed') {
|
|
1310
|
-
matched.add(node);
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
break;
|
|
1314
|
-
}
|
|
1315
|
-
case 'placeholder-shown': {
|
|
1316
|
-
let placeholder;
|
|
1317
|
-
if (node.placeholder) {
|
|
1318
|
-
placeholder = node.placeholder;
|
|
1319
|
-
} else if (node.hasAttribute('placeholder')) {
|
|
1320
|
-
placeholder = node.getAttribute('placeholder');
|
|
1321
|
-
}
|
|
1322
|
-
if (typeof placeholder === 'string' && !/[\r\n]/.test(placeholder)) {
|
|
1323
|
-
let targetNode;
|
|
1324
|
-
if (localName === 'textarea') {
|
|
1325
1297
|
targetNode = node;
|
|
1326
|
-
} else if (localName === 'input') {
|
|
1327
|
-
if (node.hasAttribute('type')) {
|
|
1328
|
-
if (KEYS_INPUT_PLACEHOLDER.has(node.getAttribute('type'))) {
|
|
1329
|
-
targetNode = node;
|
|
1330
|
-
}
|
|
1331
|
-
} else {
|
|
1332
|
-
targetNode = node;
|
|
1333
|
-
}
|
|
1334
|
-
}
|
|
1335
|
-
if (targetNode && node.value === '') {
|
|
1336
|
-
matched.add(node);
|
|
1337
1298
|
}
|
|
1338
1299
|
}
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
case 'checked': {
|
|
1342
|
-
const attrType = node.getAttribute('type');
|
|
1343
|
-
if (
|
|
1344
|
-
(node.checked &&
|
|
1345
|
-
localName === 'input' &&
|
|
1346
|
-
(attrType === 'checkbox' || attrType === 'radio')) ||
|
|
1347
|
-
(node.selected && localName === 'option')
|
|
1348
|
-
) {
|
|
1349
|
-
matched.add(node);
|
|
1300
|
+
if (targetNode) {
|
|
1301
|
+
return node.value === '';
|
|
1350
1302
|
}
|
|
1351
|
-
break;
|
|
1352
1303
|
}
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
) {
|
|
1360
|
-
matched.add(node);
|
|
1361
|
-
} else if (
|
|
1362
|
-
localName === 'input' &&
|
|
1363
|
-
node.type === 'radio' &&
|
|
1364
|
-
!node.hasAttribute('checked')
|
|
1365
|
-
) {
|
|
1366
|
-
const nodeName = node.name;
|
|
1367
|
-
let parent = node.parentNode;
|
|
1368
|
-
while (parent) {
|
|
1369
|
-
if (parent.localName === 'form') {
|
|
1370
|
-
break;
|
|
1371
|
-
}
|
|
1372
|
-
parent = parent.parentNode;
|
|
1373
|
-
}
|
|
1374
|
-
if (!parent) {
|
|
1375
|
-
parent = this.#document.documentElement;
|
|
1376
|
-
}
|
|
1377
|
-
const walker = this._createTreeWalker(parent);
|
|
1378
|
-
let refNode = traverseNode(parent, walker);
|
|
1379
|
-
refNode = walker.firstChild();
|
|
1380
|
-
let checked;
|
|
1381
|
-
while (refNode) {
|
|
1382
|
-
if (
|
|
1383
|
-
refNode.localName === 'input' &&
|
|
1384
|
-
refNode.getAttribute('type') === 'radio'
|
|
1385
|
-
) {
|
|
1386
|
-
if (refNode.hasAttribute('name')) {
|
|
1387
|
-
if (refNode.getAttribute('name') === nodeName) {
|
|
1388
|
-
checked = !!refNode.checked;
|
|
1389
|
-
}
|
|
1390
|
-
} else {
|
|
1391
|
-
checked = !!refNode.checked;
|
|
1392
|
-
}
|
|
1393
|
-
if (checked) {
|
|
1394
|
-
break;
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
|
-
refNode = walker.nextNode();
|
|
1398
|
-
}
|
|
1399
|
-
if (!checked) {
|
|
1400
|
-
matched.add(node);
|
|
1401
|
-
}
|
|
1402
|
-
}
|
|
1403
|
-
break;
|
|
1304
|
+
break;
|
|
1305
|
+
}
|
|
1306
|
+
case 'default': {
|
|
1307
|
+
// option
|
|
1308
|
+
if (localName === 'option') {
|
|
1309
|
+
return node.hasAttribute('selected');
|
|
1404
1310
|
}
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
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();
|
|
1421
1338
|
}
|
|
1422
|
-
|
|
1423
|
-
|
|
1339
|
+
let defaultSubmit = this.#psDefaultCache.get(form);
|
|
1340
|
+
if (!defaultSubmit) {
|
|
1341
|
+
const walker = this._createTreeWalker(form, { force: true });
|
|
1424
1342
|
let refNode = traverseNode(form, walker);
|
|
1425
1343
|
refNode = walker.firstChild();
|
|
1426
1344
|
while (refNode) {
|
|
@@ -1438,53 +1356,117 @@ export class Finder {
|
|
|
1438
1356
|
KEYS_INPUT_SUBMIT.has(nodeAttrType);
|
|
1439
1357
|
}
|
|
1440
1358
|
if (m) {
|
|
1441
|
-
|
|
1442
|
-
matched.add(node);
|
|
1443
|
-
}
|
|
1359
|
+
defaultSubmit = refNode;
|
|
1444
1360
|
break;
|
|
1445
1361
|
}
|
|
1446
1362
|
refNode = walker.nextNode();
|
|
1447
1363
|
}
|
|
1364
|
+
this.#psDefaultCache.set(form, defaultSubmit);
|
|
1448
1365
|
}
|
|
1449
|
-
|
|
1450
|
-
} else if (
|
|
1451
|
-
localName === 'input' &&
|
|
1452
|
-
node.hasAttribute('type') &&
|
|
1453
|
-
node.hasAttribute('checked') &&
|
|
1454
|
-
KEYS_INPUT_CHECK.has(attrType)
|
|
1455
|
-
) {
|
|
1456
|
-
matched.add(node);
|
|
1457
|
-
// option
|
|
1458
|
-
} else if (localName === 'option' && node.hasAttribute('selected')) {
|
|
1459
|
-
matched.add(node);
|
|
1366
|
+
return defaultSubmit === node;
|
|
1460
1367
|
}
|
|
1461
|
-
break;
|
|
1462
1368
|
}
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
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;
|
|
1471
1432
|
}
|
|
1472
|
-
} else {
|
|
1473
|
-
valid = true;
|
|
1474
1433
|
}
|
|
1434
|
+
refNode = walker.nextNode();
|
|
1475
1435
|
}
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
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;
|
|
1479
1451
|
}
|
|
1480
|
-
} else
|
|
1481
|
-
|
|
1452
|
+
} else {
|
|
1453
|
+
valid = true;
|
|
1482
1454
|
}
|
|
1483
|
-
}
|
|
1484
|
-
|
|
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 });
|
|
1485
1468
|
let refNode = traverseNode(node, walker);
|
|
1486
1469
|
refNode = walker.firstChild();
|
|
1487
|
-
let valid;
|
|
1488
1470
|
if (!refNode) {
|
|
1489
1471
|
valid = true;
|
|
1490
1472
|
} else {
|
|
@@ -1506,201 +1488,345 @@ export class Finder {
|
|
|
1506
1488
|
refNode = walker.nextNode();
|
|
1507
1489
|
}
|
|
1508
1490
|
}
|
|
1509
|
-
|
|
1510
|
-
if (astName === 'valid') {
|
|
1511
|
-
matched.add(node);
|
|
1512
|
-
}
|
|
1513
|
-
} else if (astName === 'invalid') {
|
|
1514
|
-
matched.add(node);
|
|
1515
|
-
}
|
|
1491
|
+
this.#psValidCache.set(node, valid);
|
|
1516
1492
|
}
|
|
1517
|
-
|
|
1493
|
+
if (astName === 'invalid') {
|
|
1494
|
+
return !valid;
|
|
1495
|
+
}
|
|
1496
|
+
return valid;
|
|
1518
1497
|
}
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
)
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
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') ||
|
|
1536
1517
|
node.hasAttribute('max') ||
|
|
1537
|
-
attrType === 'range'
|
|
1538
|
-
) {
|
|
1539
|
-
matched.add(node);
|
|
1540
|
-
}
|
|
1541
|
-
}
|
|
1542
|
-
break;
|
|
1518
|
+
attrType === 'range';
|
|
1543
1519
|
}
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
if (
|
|
1558
|
-
|
|
1559
|
-
required = true;
|
|
1560
|
-
} else {
|
|
1561
|
-
optional = true;
|
|
1562
|
-
}
|
|
1563
|
-
} else {
|
|
1564
|
-
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;
|
|
1565
1535
|
}
|
|
1566
|
-
} else if (node.required || node.hasAttribute('required')) {
|
|
1567
|
-
required = true;
|
|
1568
|
-
} else {
|
|
1569
|
-
optional = true;
|
|
1570
1536
|
}
|
|
1537
|
+
} else if (node.required || node.hasAttribute('required')) {
|
|
1538
|
+
required = true;
|
|
1571
1539
|
}
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
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);
|
|
1576
1561
|
}
|
|
1577
|
-
|
|
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;
|
|
1578
1592
|
}
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
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;
|
|
1582
1604
|
}
|
|
1583
|
-
|
|
1605
|
+
refNode = walker.nextSibling();
|
|
1584
1606
|
}
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
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;
|
|
1599
1657
|
}
|
|
1600
|
-
refNode = walker.nextSibling();
|
|
1601
1658
|
}
|
|
1602
|
-
|
|
1603
|
-
|
|
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
|
+
}
|
|
1604
1725
|
}
|
|
1605
|
-
} else {
|
|
1606
|
-
matched.add(node);
|
|
1607
|
-
}
|
|
1608
|
-
break;
|
|
1609
|
-
}
|
|
1610
|
-
case 'first-child': {
|
|
1611
|
-
if (
|
|
1612
|
-
(parentNode && node === parentNode.firstElementChild) ||
|
|
1613
|
-
node === this.#root
|
|
1614
|
-
) {
|
|
1615
|
-
matched.add(node);
|
|
1616
1726
|
}
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
if (
|
|
1621
|
-
(parentNode && node === parentNode.lastElementChild) ||
|
|
1622
|
-
node === this.#root
|
|
1623
|
-
) {
|
|
1624
|
-
matched.add(node);
|
|
1727
|
+
if (bool) {
|
|
1728
|
+
this.#lastFocusVisible = node;
|
|
1729
|
+
return bool;
|
|
1625
1730
|
}
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
case 'only-child': {
|
|
1629
|
-
if (
|
|
1630
|
-
(parentNode &&
|
|
1631
|
-
node === parentNode.firstElementChild &&
|
|
1632
|
-
node === parentNode.lastElementChild) ||
|
|
1633
|
-
node === this.#root
|
|
1634
|
-
) {
|
|
1635
|
-
matched.add(node);
|
|
1731
|
+
if (this.#lastFocusVisible === node) {
|
|
1732
|
+
this.#lastFocusVisible = null;
|
|
1636
1733
|
}
|
|
1637
|
-
break;
|
|
1638
1734
|
}
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
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
|
+
}
|
|
1643
1771
|
}
|
|
1644
|
-
// NOTE: MathMLElement is not implemented in jsdom.
|
|
1645
|
-
} else if (
|
|
1646
|
-
node instanceof this.#window.HTMLElement ||
|
|
1647
|
-
node instanceof this.#window.SVGElement
|
|
1648
|
-
) {
|
|
1649
|
-
matched.add(node);
|
|
1650
|
-
}
|
|
1651
|
-
break;
|
|
1652
|
-
}
|
|
1653
|
-
case 'popover-open': {
|
|
1654
|
-
// FIXME: not implemented in jsdom
|
|
1655
|
-
// @see https://github.com/jsdom/jsdom/issues/3721
|
|
1656
|
-
/*
|
|
1657
|
-
if (node.popover && isVisible(node)) {
|
|
1658
|
-
matched.add(node);
|
|
1659
1772
|
}
|
|
1660
|
-
*/
|
|
1661
|
-
break;
|
|
1662
1773
|
}
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
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
|
+
);
|
|
1666
1793
|
}
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
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
|
+
);
|
|
1682
1825
|
}
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
case 'current':
|
|
1688
|
-
case 'fullscreen':
|
|
1689
|
-
case 'future':
|
|
1690
|
-
case 'has-slotted':
|
|
1691
|
-
case 'heading':
|
|
1692
|
-
case 'modal':
|
|
1693
|
-
case 'muted':
|
|
1694
|
-
case 'past':
|
|
1695
|
-
case 'paused':
|
|
1696
|
-
case 'picture-in-picture':
|
|
1697
|
-
case 'playing':
|
|
1698
|
-
case 'seeking':
|
|
1699
|
-
case 'stalled':
|
|
1700
|
-
case 'user-invalid':
|
|
1701
|
-
case 'user-valid':
|
|
1702
|
-
case 'volume-locked':
|
|
1703
|
-
case '-webkit-autofill': {
|
|
1826
|
+
break;
|
|
1827
|
+
}
|
|
1828
|
+
default: {
|
|
1829
|
+
if (astName.startsWith('-webkit-')) {
|
|
1704
1830
|
if (warn) {
|
|
1705
1831
|
this.onError(
|
|
1706
1832
|
generateException(
|
|
@@ -1710,32 +1836,18 @@ export class Finder {
|
|
|
1710
1836
|
)
|
|
1711
1837
|
);
|
|
1712
1838
|
}
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
this
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
NOT_SUPPORTED_ERR,
|
|
1722
|
-
this.#window
|
|
1723
|
-
)
|
|
1724
|
-
);
|
|
1725
|
-
}
|
|
1726
|
-
} else if (!forgive) {
|
|
1727
|
-
this.onError(
|
|
1728
|
-
generateException(
|
|
1729
|
-
`Unknown pseudo-class :${astName}`,
|
|
1730
|
-
SYNTAX_ERR,
|
|
1731
|
-
this.#window
|
|
1732
|
-
)
|
|
1733
|
-
);
|
|
1734
|
-
}
|
|
1839
|
+
} else if (!forgive) {
|
|
1840
|
+
this.onError(
|
|
1841
|
+
generateException(
|
|
1842
|
+
`Unknown pseudo-class :${astName}`,
|
|
1843
|
+
SYNTAX_ERR,
|
|
1844
|
+
this.#window
|
|
1845
|
+
)
|
|
1846
|
+
);
|
|
1735
1847
|
}
|
|
1736
1848
|
}
|
|
1737
1849
|
}
|
|
1738
|
-
return
|
|
1850
|
+
return false;
|
|
1739
1851
|
}
|
|
1740
1852
|
|
|
1741
1853
|
/**
|
|
@@ -1744,7 +1856,7 @@ export class Finder {
|
|
|
1744
1856
|
* @param {Array.<object>} leaves - The AST leaves.
|
|
1745
1857
|
* @param {object} host - The host element.
|
|
1746
1858
|
* @param {object} ast - The original AST for error reporting.
|
|
1747
|
-
* @returns {boolean} True if
|
|
1859
|
+
* @returns {boolean} True if matches, otherwise false.
|
|
1748
1860
|
*/
|
|
1749
1861
|
_evaluateHostPseudo = (leaves, host, ast) => {
|
|
1750
1862
|
const l = leaves.length;
|
|
@@ -1756,7 +1868,7 @@ export class Finder {
|
|
|
1756
1868
|
this.onError(generateException(msg, SYNTAX_ERR, this.#window));
|
|
1757
1869
|
return false;
|
|
1758
1870
|
}
|
|
1759
|
-
if (!this._matchSelector(leaf, host)
|
|
1871
|
+
if (!this._matchSelector(leaf, host)) {
|
|
1760
1872
|
return false;
|
|
1761
1873
|
}
|
|
1762
1874
|
}
|
|
@@ -1784,7 +1896,7 @@ export class Finder {
|
|
|
1784
1896
|
this.onError(generateException(msg, SYNTAX_ERR, this.#window));
|
|
1785
1897
|
return false;
|
|
1786
1898
|
}
|
|
1787
|
-
bool = this._matchSelector(leaf, parent)
|
|
1899
|
+
bool = this._matchSelector(leaf, parent);
|
|
1788
1900
|
if (!bool) {
|
|
1789
1901
|
break;
|
|
1790
1902
|
}
|
|
@@ -1798,44 +1910,48 @@ export class Finder {
|
|
|
1798
1910
|
};
|
|
1799
1911
|
|
|
1800
1912
|
/**
|
|
1801
|
-
*
|
|
1913
|
+
* Evaluates shadow host pseudo-classes.
|
|
1802
1914
|
* @private
|
|
1803
1915
|
* @param {object} ast - The AST.
|
|
1804
1916
|
* @param {object} node - The DocumentFragment node.
|
|
1805
|
-
* @returns {
|
|
1917
|
+
* @returns {boolean} True if matches, otherwise false.
|
|
1806
1918
|
*/
|
|
1807
|
-
|
|
1919
|
+
_evaluateShadowHost = (ast, node) => {
|
|
1808
1920
|
const { children: astChildren, name: astName } = ast;
|
|
1809
1921
|
// Handle simple pseudo-class (no arguments).
|
|
1810
1922
|
if (!Array.isArray(astChildren)) {
|
|
1811
1923
|
if (astName === 'host') {
|
|
1812
|
-
return
|
|
1924
|
+
return true;
|
|
1813
1925
|
}
|
|
1814
1926
|
const msg = `Invalid selector :${astName}`;
|
|
1815
|
-
|
|
1927
|
+
this.onError(generateException(msg, SYNTAX_ERR, this.#window));
|
|
1928
|
+
return false;
|
|
1816
1929
|
}
|
|
1817
1930
|
// Handle functional pseudo-class like :host(...).
|
|
1818
1931
|
if (astName !== 'host' && astName !== 'host-context') {
|
|
1819
1932
|
const msg = `Invalid selector :${astName}()`;
|
|
1820
|
-
|
|
1933
|
+
this.onError(generateException(msg, SYNTAX_ERR, this.#window));
|
|
1934
|
+
return false;
|
|
1821
1935
|
}
|
|
1822
1936
|
if (astChildren.length !== 1) {
|
|
1823
1937
|
const css = generateCSS(ast);
|
|
1824
1938
|
const msg = `Invalid selector ${css}`;
|
|
1825
|
-
|
|
1939
|
+
this.onError(generateException(msg, SYNTAX_ERR, this.#window));
|
|
1940
|
+
return false;
|
|
1826
1941
|
}
|
|
1827
1942
|
const { host } = node;
|
|
1828
1943
|
const { branches } = walkAST(astChildren[0]);
|
|
1829
1944
|
const [branch] = branches;
|
|
1830
1945
|
const [...leaves] = branch;
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
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;
|
|
1837
1953
|
}
|
|
1838
|
-
return
|
|
1954
|
+
return false;
|
|
1839
1955
|
};
|
|
1840
1956
|
|
|
1841
1957
|
/**
|
|
@@ -1844,39 +1960,26 @@ export class Finder {
|
|
|
1844
1960
|
* @param {object} ast - The AST.
|
|
1845
1961
|
* @param {object} node - The Element node.
|
|
1846
1962
|
* @param {object} opt - Options.
|
|
1847
|
-
* @returns {
|
|
1963
|
+
* @returns {boolean} True if matches, otherwise false.
|
|
1848
1964
|
*/
|
|
1849
1965
|
_matchSelectorForElement = (ast, node, opt) => {
|
|
1850
1966
|
const { type: astType } = ast;
|
|
1851
1967
|
const astName = unescapeSelector(ast.name);
|
|
1852
|
-
const matched = new Set();
|
|
1853
1968
|
switch (astType) {
|
|
1854
1969
|
case ATTR_SELECTOR: {
|
|
1855
|
-
|
|
1856
|
-
matched.add(node);
|
|
1857
|
-
}
|
|
1858
|
-
break;
|
|
1970
|
+
return matchAttributeSelector(ast, node, opt);
|
|
1859
1971
|
}
|
|
1860
1972
|
case ID_SELECTOR: {
|
|
1861
|
-
|
|
1862
|
-
matched.add(node);
|
|
1863
|
-
}
|
|
1864
|
-
break;
|
|
1973
|
+
return node.id === astName;
|
|
1865
1974
|
}
|
|
1866
1975
|
case CLASS_SELECTOR: {
|
|
1867
|
-
|
|
1868
|
-
matched.add(node);
|
|
1869
|
-
}
|
|
1870
|
-
break;
|
|
1976
|
+
return node.classList.contains(astName);
|
|
1871
1977
|
}
|
|
1872
1978
|
case PS_CLASS_SELECTOR: {
|
|
1873
1979
|
return this._matchPseudoClassSelector(ast, node, opt);
|
|
1874
1980
|
}
|
|
1875
1981
|
case TYPE_SELECTOR: {
|
|
1876
|
-
|
|
1877
|
-
matched.add(node);
|
|
1878
|
-
}
|
|
1879
|
-
break;
|
|
1982
|
+
return matchTypeSelector(ast, node, opt);
|
|
1880
1983
|
}
|
|
1881
1984
|
// PS_ELEMENT_SELECTOR is handled by default.
|
|
1882
1985
|
default: {
|
|
@@ -1884,7 +1987,7 @@ export class Finder {
|
|
|
1884
1987
|
if (this.#check) {
|
|
1885
1988
|
const css = generateCSS(ast);
|
|
1886
1989
|
this.#pseudoElement.push(css);
|
|
1887
|
-
|
|
1990
|
+
return true;
|
|
1888
1991
|
} else {
|
|
1889
1992
|
matchPseudoElementSelector(astName, astType, opt);
|
|
1890
1993
|
}
|
|
@@ -1893,7 +1996,7 @@ export class Finder {
|
|
|
1893
1996
|
}
|
|
1894
1997
|
}
|
|
1895
1998
|
}
|
|
1896
|
-
return
|
|
1999
|
+
return false;
|
|
1897
2000
|
};
|
|
1898
2001
|
|
|
1899
2002
|
/**
|
|
@@ -1902,7 +2005,7 @@ export class Finder {
|
|
|
1902
2005
|
* @param {object} ast - The AST.
|
|
1903
2006
|
* @param {object} node - The DocumentFragment node.
|
|
1904
2007
|
* @param {object} [opt] - Options.
|
|
1905
|
-
* @returns {
|
|
2008
|
+
* @returns {boolean} True if matches, otherwise false.
|
|
1906
2009
|
*/
|
|
1907
2010
|
_matchSelectorForShadowRoot = (ast, node, opt = {}) => {
|
|
1908
2011
|
const { name: astName } = ast;
|
|
@@ -1910,15 +2013,14 @@ export class Finder {
|
|
|
1910
2013
|
opt.isShadowRoot = true;
|
|
1911
2014
|
return this._matchPseudoClassSelector(ast, node, opt);
|
|
1912
2015
|
}
|
|
1913
|
-
const matched = new Set();
|
|
1914
2016
|
if (astName === 'host' || astName === 'host-context') {
|
|
1915
|
-
const
|
|
1916
|
-
if (
|
|
2017
|
+
const matches = this._evaluateShadowHost(ast, node, opt);
|
|
2018
|
+
if (matches) {
|
|
1917
2019
|
this.#verifyShadowHost = true;
|
|
1918
|
-
|
|
2020
|
+
return true;
|
|
1919
2021
|
}
|
|
1920
2022
|
}
|
|
1921
|
-
return
|
|
2023
|
+
return false;
|
|
1922
2024
|
};
|
|
1923
2025
|
|
|
1924
2026
|
/**
|
|
@@ -1927,7 +2029,7 @@ export class Finder {
|
|
|
1927
2029
|
* @param {object} ast - The AST.
|
|
1928
2030
|
* @param {object} node - The Document, DocumentFragment, or Element node.
|
|
1929
2031
|
* @param {object} opt - Options.
|
|
1930
|
-
* @returns {
|
|
2032
|
+
* @returns {boolean} True if matches, otherwise false.
|
|
1931
2033
|
*/
|
|
1932
2034
|
_matchSelector = (ast, node, opt) => {
|
|
1933
2035
|
if (node.nodeType === ELEMENT_NODE) {
|
|
@@ -1940,7 +2042,7 @@ export class Finder {
|
|
|
1940
2042
|
) {
|
|
1941
2043
|
return this._matchSelectorForShadowRoot(ast, node, opt);
|
|
1942
2044
|
}
|
|
1943
|
-
return
|
|
2045
|
+
return false;
|
|
1944
2046
|
};
|
|
1945
2047
|
|
|
1946
2048
|
/**
|
|
@@ -1952,6 +2054,9 @@ export class Finder {
|
|
|
1952
2054
|
* @returns {boolean} The result.
|
|
1953
2055
|
*/
|
|
1954
2056
|
_matchLeaves = (leaves, node, opt) => {
|
|
2057
|
+
if (!this.#invalidateResults) {
|
|
2058
|
+
this.#invalidateResults = new WeakMap();
|
|
2059
|
+
}
|
|
1955
2060
|
const results = this.#invalidate ? this.#invalidateResults : this.#results;
|
|
1956
2061
|
let result = results.get(leaves);
|
|
1957
2062
|
if (result && result.has(node)) {
|
|
@@ -1982,7 +2087,7 @@ export class Finder {
|
|
|
1982
2087
|
// No action needed for other types.
|
|
1983
2088
|
}
|
|
1984
2089
|
}
|
|
1985
|
-
bool = this._matchSelector(leaf, node, opt)
|
|
2090
|
+
bool = this._matchSelector(leaf, node, opt);
|
|
1986
2091
|
if (!bool) {
|
|
1987
2092
|
break;
|
|
1988
2093
|
}
|
|
@@ -2006,6 +2111,9 @@ export class Finder {
|
|
|
2006
2111
|
* @returns {Array.<object>} The filtered leaves.
|
|
2007
2112
|
*/
|
|
2008
2113
|
_getFilterLeaves = leaves => {
|
|
2114
|
+
if (!this.#filterLeavesCache) {
|
|
2115
|
+
this.#filterLeavesCache = new WeakMap();
|
|
2116
|
+
}
|
|
2009
2117
|
if (this.#filterLeavesCache.has(leaves)) {
|
|
2010
2118
|
return this.#filterLeavesCache.get(leaves);
|
|
2011
2119
|
}
|
|
@@ -2088,7 +2196,7 @@ export class Finder {
|
|
|
2088
2196
|
};
|
|
2089
2197
|
|
|
2090
2198
|
/**
|
|
2091
|
-
* Collects combinator matches into an array
|
|
2199
|
+
* Collects combinator matches into an array.
|
|
2092
2200
|
* @private
|
|
2093
2201
|
* @param {object} twig - The twig object.
|
|
2094
2202
|
* @param {object} node - The Element node.
|
|
@@ -2115,12 +2223,36 @@ export class Finder {
|
|
|
2115
2223
|
break;
|
|
2116
2224
|
}
|
|
2117
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
|
+
}
|
|
2118
2250
|
let refNode =
|
|
2119
2251
|
dir === DIR_NEXT
|
|
2120
2252
|
? node.nextElementSibling
|
|
2121
2253
|
: node.previousElementSibling;
|
|
2122
2254
|
while (refNode) {
|
|
2123
|
-
if (
|
|
2255
|
+
if (matchedSet.has(refNode)) {
|
|
2124
2256
|
matched.push(refNode);
|
|
2125
2257
|
}
|
|
2126
2258
|
refNode =
|
|
@@ -2281,8 +2413,8 @@ export class Finder {
|
|
|
2281
2413
|
this.#nodeWalker = this._createTreeWalker(this.#node);
|
|
2282
2414
|
}
|
|
2283
2415
|
return this._traverseAndCollectNodes(this.#nodeWalker, leaves, {
|
|
2284
|
-
|
|
2285
|
-
|
|
2416
|
+
...traversalOpts,
|
|
2417
|
+
startNode: node
|
|
2286
2418
|
});
|
|
2287
2419
|
};
|
|
2288
2420
|
|
|
@@ -2470,13 +2602,18 @@ export class Finder {
|
|
|
2470
2602
|
let pending = false;
|
|
2471
2603
|
if (targetType !== TARGET_LINEAL && /host(?:-context)?/.test(leaf.name)) {
|
|
2472
2604
|
let shadowRoot = null;
|
|
2473
|
-
if (
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
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;
|
|
2480
2617
|
}
|
|
2481
2618
|
if (shadowRoot) {
|
|
2482
2619
|
let bool = true;
|
|
@@ -2486,19 +2623,11 @@ export class Finder {
|
|
|
2486
2623
|
switch (filterLeaf.name) {
|
|
2487
2624
|
case 'host':
|
|
2488
2625
|
case 'host-context': {
|
|
2489
|
-
|
|
2490
|
-
filterLeaf,
|
|
2491
|
-
shadowRoot
|
|
2492
|
-
);
|
|
2493
|
-
bool = matchedNode === shadowRoot;
|
|
2626
|
+
bool = this._evaluateShadowHost(filterLeaf, shadowRoot);
|
|
2494
2627
|
break;
|
|
2495
2628
|
}
|
|
2496
2629
|
case 'has': {
|
|
2497
|
-
bool = this._matchPseudoClassSelector(
|
|
2498
|
-
filterLeaf,
|
|
2499
|
-
shadowRoot,
|
|
2500
|
-
{}
|
|
2501
|
-
).has(shadowRoot);
|
|
2630
|
+
bool = this._matchPseudoClassSelector(filterLeaf, shadowRoot, {});
|
|
2502
2631
|
break;
|
|
2503
2632
|
}
|
|
2504
2633
|
default: {
|
|
@@ -2613,31 +2742,20 @@ export class Finder {
|
|
|
2613
2742
|
const {
|
|
2614
2743
|
leaves: [{ name: lastName, type: lastType }]
|
|
2615
2744
|
} = lastTwig;
|
|
2616
|
-
const { combo: firstCombo } = firstTwig;
|
|
2617
2745
|
if (
|
|
2618
2746
|
this.#selector.includes(':scope') ||
|
|
2619
2747
|
lastType === PS_ELEMENT_SELECTOR ||
|
|
2620
2748
|
lastType === ID_SELECTOR
|
|
2621
2749
|
) {
|
|
2622
2750
|
return { dir: DIR_PREV, twig: lastTwig };
|
|
2623
|
-
}
|
|
2624
|
-
if (firstType === ID_SELECTOR) {
|
|
2751
|
+
} else if (firstType === ID_SELECTOR) {
|
|
2625
2752
|
return { dir: DIR_NEXT, twig: firstTwig };
|
|
2626
|
-
}
|
|
2627
|
-
if (firstName === '*' && firstType === TYPE_SELECTOR) {
|
|
2753
|
+
} else if (firstName === '*' && firstType === TYPE_SELECTOR) {
|
|
2628
2754
|
return { dir: DIR_PREV, twig: lastTwig };
|
|
2629
|
-
}
|
|
2630
|
-
if (lastName === '*' && lastType === TYPE_SELECTOR) {
|
|
2755
|
+
} else if (lastName === '*' && lastType === TYPE_SELECTOR) {
|
|
2631
2756
|
return { dir: DIR_NEXT, twig: firstTwig };
|
|
2632
|
-
}
|
|
2633
|
-
|
|
2634
|
-
if (targetType === TARGET_FIRST) {
|
|
2635
|
-
return { dir: DIR_PREV, twig: lastTwig };
|
|
2636
|
-
}
|
|
2637
|
-
const { name: comboName } = firstCombo;
|
|
2638
|
-
if (comboName === '+' || comboName === '~') {
|
|
2639
|
-
return { dir: DIR_PREV, twig: lastTwig };
|
|
2640
|
-
}
|
|
2757
|
+
} else if (branchLen === 1 || branchLen === 2) {
|
|
2758
|
+
return { dir: DIR_PREV, twig: lastTwig };
|
|
2641
2759
|
} else if (branchLen > 2 && this.#scoped && targetType === TARGET_FIRST) {
|
|
2642
2760
|
if (lastType === TYPE_SELECTOR) {
|
|
2643
2761
|
return { dir: DIR_PREV, twig: lastTwig };
|
|
@@ -2862,7 +2980,6 @@ export class Finder {
|
|
|
2862
2980
|
const matchedNodes = new Set();
|
|
2863
2981
|
const branchLen = branch.length;
|
|
2864
2982
|
const lastIndex = branchLen - 1;
|
|
2865
|
-
|
|
2866
2983
|
if (dir === DIR_NEXT) {
|
|
2867
2984
|
const { combo: firstCombo } = branch[0];
|
|
2868
2985
|
for (const node of entryNodes) {
|