@asamuzakjp/dom-selector 8.1.5 → 8.2.0
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/package.json +6 -6
- package/src/index.js +3 -0
- package/src/js/evaluator.js +2149 -0
- package/src/js/finder.js +477 -2572
- package/src/js/nwsapi.js +1 -1
- package/src/js/utility.js +0 -37
- package/types/js/evaluator.d.ts +58 -0
- package/types/js/finder.d.ts +4 -43
- package/types/js/utility.d.ts +0 -5
package/src/js/finder.js
CHANGED
|
@@ -3,15 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
/* import */
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
matchDirectionPseudoClass,
|
|
9
|
-
matchDisabledPseudoClass,
|
|
10
|
-
matchLanguagePseudoClass,
|
|
11
|
-
matchPseudoElementSelector,
|
|
12
|
-
matchReadOnlyPseudoClass,
|
|
13
|
-
matchTypeSelector
|
|
14
|
-
} from './matcher.js';
|
|
6
|
+
import { Evaluator } from './evaluator.js';
|
|
7
|
+
import { matchPseudoElementSelector } from './matcher.js';
|
|
15
8
|
import {
|
|
16
9
|
generateCSS,
|
|
17
10
|
parseSelector,
|
|
@@ -20,2285 +13,195 @@ import {
|
|
|
20
13
|
walkAST
|
|
21
14
|
} from './parser.js';
|
|
22
15
|
import { createHasValidator, isInvalidCombinator } from './selector.js';
|
|
23
|
-
import {
|
|
24
|
-
filterNodesByAnB,
|
|
25
|
-
findBestSeed,
|
|
26
|
-
generateException,
|
|
27
|
-
isCustomElement,
|
|
28
|
-
isFocusVisible,
|
|
29
|
-
isFocusableArea,
|
|
30
|
-
isVisible,
|
|
31
|
-
populateHasAllowlist,
|
|
32
|
-
resolveContent,
|
|
33
|
-
sortNodes,
|
|
34
|
-
traverseNode
|
|
35
|
-
} from './utility.js';
|
|
16
|
+
import { generateException, sortNodes, traverseNode } from './utility.js';
|
|
36
17
|
|
|
37
18
|
/* constants */
|
|
38
19
|
import {
|
|
39
|
-
ATTR_SELECTOR,
|
|
40
20
|
CLASS_SELECTOR,
|
|
41
21
|
COMBINATOR,
|
|
42
22
|
DOCUMENT_FRAGMENT_NODE,
|
|
43
23
|
ELEMENT_NODE,
|
|
44
|
-
FORM_PARTS,
|
|
45
|
-
HEX,
|
|
46
24
|
ID_SELECTOR,
|
|
47
|
-
INPUT_CHECK,
|
|
48
|
-
INPUT_DATE,
|
|
49
|
-
INPUT_EDIT,
|
|
50
|
-
INPUT_TEXT,
|
|
51
|
-
KEYS_LOGICAL,
|
|
52
|
-
NOT_SUPPORTED_ERR,
|
|
53
|
-
PS_CLASS_SELECTOR,
|
|
54
25
|
PS_ELEMENT_SELECTOR,
|
|
55
|
-
SHOW_ALL,
|
|
56
|
-
SHOW_CONTAINER,
|
|
57
26
|
SYNTAX_ERR,
|
|
58
27
|
TARGET_ALL,
|
|
59
28
|
TARGET_FIRST,
|
|
60
29
|
TARGET_LINEAL,
|
|
61
30
|
TARGET_SELF,
|
|
62
|
-
TEXT_NODE,
|
|
63
31
|
TYPE_SELECTOR
|
|
64
32
|
} from './constant.js';
|
|
65
|
-
const ANB_FIRST = { a: 0, b: 1 };
|
|
66
|
-
const ANB_LAST = { a: 0, b: 1, reverse: true };
|
|
67
33
|
const DIR_NEXT = 'next';
|
|
68
34
|
const DIR_PREV = 'prev';
|
|
69
|
-
const KEYS_FORM = new Set([...FORM_PARTS, 'fieldset', 'form']);
|
|
70
|
-
const KEYS_FORM_PS_VALID = new Set([...FORM_PARTS, 'form']);
|
|
71
|
-
const KEYS_INPUT_CHECK = new Set(INPUT_CHECK);
|
|
72
|
-
const KEYS_INPUT_PLACEHOLDER = new Set([...INPUT_TEXT, 'number']);
|
|
73
|
-
const KEYS_INPUT_RANGE = new Set([...INPUT_DATE, 'number', 'range']);
|
|
74
|
-
const KEYS_INPUT_REQUIRED = new Set([...INPUT_CHECK, ...INPUT_EDIT, 'file']);
|
|
75
|
-
const KEYS_INPUT_RESET = new Set(['button', 'reset']);
|
|
76
|
-
const KEYS_INPUT_SUBMIT = new Set(['image', 'submit']);
|
|
77
|
-
const KEYS_MODIFIER = new Set([
|
|
78
|
-
'Alt',
|
|
79
|
-
'AltGraph',
|
|
80
|
-
'CapsLock',
|
|
81
|
-
'Control',
|
|
82
|
-
'Fn',
|
|
83
|
-
'FnLock',
|
|
84
|
-
'Hyper',
|
|
85
|
-
'Meta',
|
|
86
|
-
'NumLock',
|
|
87
|
-
'ScrollLock',
|
|
88
|
-
'Shift',
|
|
89
|
-
'Super',
|
|
90
|
-
'Symbol',
|
|
91
|
-
'SymbolLock'
|
|
92
|
-
]);
|
|
93
|
-
const KEYS_PS_UNCACHE = new Set([
|
|
94
|
-
'any-link',
|
|
95
|
-
'defined',
|
|
96
|
-
'dir',
|
|
97
|
-
'link',
|
|
98
|
-
'scope'
|
|
99
|
-
]);
|
|
100
|
-
const KEYS_PS_NTH_OF_TYPE = new Set([
|
|
101
|
-
'first-of-type',
|
|
102
|
-
'last-of-type',
|
|
103
|
-
'only-of-type'
|
|
104
|
-
]);
|
|
105
35
|
|
|
106
36
|
/**
|
|
107
37
|
* Finder
|
|
108
38
|
* NOTE: #ast[i] corresponds to #nodes[i]
|
|
109
39
|
*/
|
|
110
|
-
export class Finder {
|
|
40
|
+
export class Finder extends Evaluator {
|
|
111
41
|
/* private fields */
|
|
112
|
-
#anbCache;
|
|
113
42
|
#ast;
|
|
114
|
-
#astCache = new WeakMap();
|
|
115
|
-
#check;
|
|
116
|
-
#descendant;
|
|
117
|
-
#document;
|
|
118
|
-
#documentCache = new WeakMap();
|
|
119
|
-
#documentURL;
|
|
120
|
-
#event;
|
|
121
|
-
#eventHandlers;
|
|
122
|
-
#filterLeavesCache;
|
|
123
|
-
#focus;
|
|
124
|
-
#focusWithinCache;
|
|
125
|
-
#invalidate;
|
|
126
|
-
#invalidateResults;
|
|
127
|
-
#lastFocusVisible;
|
|
128
|
-
#node;
|
|
129
43
|
#nodeWalker;
|
|
130
44
|
#nodes;
|
|
131
|
-
#noexcept;
|
|
132
|
-
#nthChildCache;
|
|
133
|
-
#nthChildOfCache;
|
|
134
|
-
#nthChildResultCache;
|
|
135
|
-
#nthOfTypeCache;
|
|
136
|
-
#nthOfTypeResultCache;
|
|
137
|
-
#psDefaultCache;
|
|
138
|
-
#psDirCache;
|
|
139
|
-
#psHasFilterCache;
|
|
140
|
-
#psIndeterminateCache;
|
|
141
|
-
#psLangCache;
|
|
142
|
-
#psValidCache;
|
|
143
|
-
#pseudoElement;
|
|
144
|
-
#results;
|
|
145
|
-
#root;
|
|
146
45
|
#rootWalker;
|
|
147
46
|
#scoped;
|
|
148
47
|
#selector;
|
|
149
48
|
#selectorAST;
|
|
150
|
-
#shadow;
|
|
151
|
-
#targetWithinCache;
|
|
152
|
-
#verifyShadowHost;
|
|
153
|
-
#walkers;
|
|
154
|
-
#warn;
|
|
155
|
-
#window;
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* constructor
|
|
159
|
-
* @param {object} window - The window object.
|
|
160
|
-
*/
|
|
161
|
-
constructor(window) {
|
|
162
|
-
this.#window = window;
|
|
163
|
-
this.#eventHandlers = new Set([
|
|
164
|
-
{
|
|
165
|
-
keys: ['focus', 'focusin'],
|
|
166
|
-
handler: this._handleFocusEvent
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
keys: ['keydown', 'keyup'],
|
|
170
|
-
handler: this._handleKeyboardEvent
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
keys: ['mouseover', 'mousedown', 'mouseup', 'click', 'mouseout'],
|
|
174
|
-
handler: this._handleMouseEvent
|
|
175
|
-
}
|
|
176
|
-
]);
|
|
177
|
-
this._registerEventListeners();
|
|
178
|
-
this.clearResults(true);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Handles errors.
|
|
183
|
-
* @param {Error} e - The error object.
|
|
184
|
-
* @param {object} [opt] - Options.
|
|
185
|
-
* @param {boolean} [opt.noexcept] - If true, exceptions are not thrown.
|
|
186
|
-
* @throws {Error} Throws an error.
|
|
187
|
-
* @returns {void}
|
|
188
|
-
*/
|
|
189
|
-
onError = (e, opt = {}) => {
|
|
190
|
-
const noexcept = opt.noexcept ?? this.#noexcept;
|
|
191
|
-
if (noexcept) {
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
const isDOMException =
|
|
195
|
-
e instanceof DOMException || e instanceof this.#window.DOMException;
|
|
196
|
-
if (isDOMException) {
|
|
197
|
-
if (e.name === NOT_SUPPORTED_ERR) {
|
|
198
|
-
if (this.#warn) {
|
|
199
|
-
console.warn(e.message);
|
|
200
|
-
}
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
throw new this.#window.DOMException(e.message, e.name);
|
|
204
|
-
}
|
|
205
|
-
if (e.name in this.#window) {
|
|
206
|
-
throw new this.#window[e.name](e.message, { cause: e });
|
|
207
|
-
}
|
|
208
|
-
throw e;
|
|
209
|
-
};
|
|
210
49
|
|
|
211
50
|
/**
|
|
212
51
|
* Sets up the finder.
|
|
213
52
|
* @param {string} selector - The CSS selector.
|
|
214
53
|
* @param {object} node - Document, DocumentFragment, or Element.
|
|
215
54
|
* @param {object} [opt] - Options.
|
|
216
|
-
* @param {boolean} [opt.check] -
|
|
217
|
-
* @param {boolean} [opt.noexcept] -
|
|
218
|
-
* @param {boolean} [opt.warn] -
|
|
55
|
+
* @param {boolean} [opt.check] - True if running in internal check.
|
|
56
|
+
* @param {boolean} [opt.noexcept] - True to suppress exceptions.
|
|
57
|
+
* @param {boolean} [opt.warn] - True to enable console warnings.
|
|
219
58
|
* @returns {object} The finder instance.
|
|
220
|
-
*/
|
|
221
|
-
setup
|
|
222
|
-
|
|
223
|
-
this.#
|
|
224
|
-
this.#
|
|
225
|
-
this.#
|
|
226
|
-
|
|
227
|
-
this.#
|
|
228
|
-
this.#
|
|
229
|
-
this.#
|
|
230
|
-
|
|
231
|
-
this
|
|
232
|
-
|
|
233
|
-
this.#walkers = null;
|
|
234
|
-
this.#nodeWalker = null;
|
|
235
|
-
this.#rootWalker = null;
|
|
236
|
-
this.#verifyShadowHost = null;
|
|
237
|
-
this.clearResults();
|
|
238
|
-
return this;
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Clear cached results.
|
|
243
|
-
* @param {boolean} all - Clear all results.
|
|
244
|
-
* @returns {void}
|
|
245
|
-
*/
|
|
246
|
-
clearResults = (all = false) => {
|
|
247
|
-
this.#anbCache = null;
|
|
248
|
-
this.#focusWithinCache = null;
|
|
249
|
-
this.#invalidateResults = null;
|
|
250
|
-
this.#nthChildCache = null;
|
|
251
|
-
this.#nthChildOfCache = null;
|
|
252
|
-
this.#nthChildResultCache = null;
|
|
253
|
-
this.#nthOfTypeCache = null;
|
|
254
|
-
this.#nthOfTypeResultCache = null;
|
|
255
|
-
this.#psDefaultCache = null;
|
|
256
|
-
this.#psDirCache = null;
|
|
257
|
-
this.#psHasFilterCache = null;
|
|
258
|
-
this.#psIndeterminateCache = null;
|
|
259
|
-
this.#psLangCache = null;
|
|
260
|
-
this.#psValidCache = null;
|
|
261
|
-
this.#targetWithinCache = null;
|
|
262
|
-
if (all) {
|
|
263
|
-
this.#filterLeavesCache = null;
|
|
264
|
-
this.#results = new WeakMap();
|
|
265
|
-
}
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Handles focus events.
|
|
270
|
-
* @private
|
|
271
|
-
* @param {Event} evt - The event object.
|
|
272
|
-
* @returns {void}
|
|
273
|
-
*/
|
|
274
|
-
_handleFocusEvent = evt => {
|
|
275
|
-
this.#focus = evt;
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Handles keyboard events.
|
|
280
|
-
* @private
|
|
281
|
-
* @param {Event} evt - The event object.
|
|
282
|
-
* @returns {void}
|
|
283
|
-
*/
|
|
284
|
-
_handleKeyboardEvent = evt => {
|
|
285
|
-
const { key } = evt;
|
|
286
|
-
if (!KEYS_MODIFIER.has(key)) {
|
|
287
|
-
this.#event = evt;
|
|
288
|
-
}
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* Handles mouse events.
|
|
293
|
-
* @private
|
|
294
|
-
* @param {Event} evt - The event object.
|
|
295
|
-
* @returns {void}
|
|
296
|
-
*/
|
|
297
|
-
_handleMouseEvent = evt => {
|
|
298
|
-
this.#event = evt;
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* Registers event listeners.
|
|
303
|
-
* @private
|
|
304
|
-
* @returns {Array.<void>} An array of return values from addEventListener.
|
|
305
|
-
*/
|
|
306
|
-
_registerEventListeners = () => {
|
|
307
|
-
const func = [];
|
|
308
|
-
for (const eventHandler of this.#eventHandlers) {
|
|
309
|
-
const { keys, handler } = eventHandler;
|
|
310
|
-
const l = keys.length;
|
|
311
|
-
for (let i = 0; i < l; i++) {
|
|
312
|
-
const key = keys[i];
|
|
313
|
-
func.push(
|
|
314
|
-
this.#window.addEventListener(key, handler, {
|
|
315
|
-
capture: true,
|
|
316
|
-
passive: true
|
|
317
|
-
})
|
|
318
|
-
);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
return func;
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Processes selector branches into the internal AST structure.
|
|
326
|
-
* @private
|
|
327
|
-
* @param {Array.<Array.<object>>} branches - The branches from walkAST.
|
|
328
|
-
* @param {string} selector - The original selector for error reporting.
|
|
329
|
-
* @returns {{ast: Array, descendant: boolean}} An object with the AST, descendant flag.
|
|
330
|
-
*/
|
|
331
|
-
_processSelectorBranches = (branches, selector) => {
|
|
332
|
-
let descendant = false;
|
|
333
|
-
const ast = [];
|
|
334
|
-
for (const items of branches) {
|
|
335
|
-
const branch = [];
|
|
336
|
-
let prevType = null;
|
|
337
|
-
const itemsLen = items.length;
|
|
338
|
-
if (itemsLen) {
|
|
339
|
-
const leaves = new Set();
|
|
340
|
-
for (let j = 0; j < itemsLen; j++) {
|
|
341
|
-
const item = items[j];
|
|
342
|
-
const isLast = j === itemsLen - 1;
|
|
343
|
-
if (isInvalidCombinator(item.type, prevType, isLast)) {
|
|
344
|
-
const msg = `Invalid selector ${selector}`;
|
|
345
|
-
this.onError(generateException(msg, SYNTAX_ERR, this.#window));
|
|
346
|
-
return { ast: [], descendant: false, invalidate: false };
|
|
347
|
-
}
|
|
348
|
-
if (item.type === COMBINATOR) {
|
|
349
|
-
if (item.name === ' ' || item.name === '>') {
|
|
350
|
-
descendant = true;
|
|
351
|
-
}
|
|
352
|
-
branch.push({ combo: item, leaves: sortAST(leaves) });
|
|
353
|
-
leaves.clear();
|
|
354
|
-
} else {
|
|
355
|
-
if (item.name && typeof item.name === 'string') {
|
|
356
|
-
const unescapedName = unescapeSelector(item.name);
|
|
357
|
-
if (unescapedName !== item.name) {
|
|
358
|
-
item.name = unescapedName;
|
|
359
|
-
}
|
|
360
|
-
if (/[|:]/.test(unescapedName)) {
|
|
361
|
-
item.namespace = true;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
leaves.add(item);
|
|
365
|
-
}
|
|
366
|
-
prevType = item.type;
|
|
367
|
-
if (isLast) {
|
|
368
|
-
branch.push({ combo: null, leaves: sortAST(leaves) });
|
|
369
|
-
leaves.clear();
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
ast.push({ branch, dir: null, filtered: false, find: false });
|
|
374
|
-
}
|
|
375
|
-
return { ast, descendant };
|
|
376
|
-
};
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* Corresponds AST and nodes.
|
|
380
|
-
* @private
|
|
381
|
-
* @param {string} selector - The CSS selector.
|
|
382
|
-
* @returns {Array.<Array.<object>>} An array with the AST and nodes.
|
|
383
|
-
*/
|
|
384
|
-
_correspond = selector => {
|
|
385
|
-
const nodes = [];
|
|
386
|
-
this.#descendant = false;
|
|
387
|
-
this.#invalidate = false;
|
|
388
|
-
let ast;
|
|
389
|
-
if (this.#documentCache.has(this.#document)) {
|
|
390
|
-
const cachedItem = this.#documentCache.get(this.#document);
|
|
391
|
-
if (cachedItem && cachedItem.has(`${selector}`)) {
|
|
392
|
-
const item = cachedItem.get(`${selector}`);
|
|
393
|
-
ast = item.ast;
|
|
394
|
-
this.#descendant = item.descendant;
|
|
395
|
-
this.#invalidate = item.invalidate;
|
|
396
|
-
this.#selectorAST = item.selectorAST;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
if (ast) {
|
|
400
|
-
const l = ast.length;
|
|
401
|
-
for (let i = 0; i < l; i++) {
|
|
402
|
-
ast[i].dir = null;
|
|
403
|
-
ast[i].filtered = false;
|
|
404
|
-
ast[i].find = false;
|
|
405
|
-
nodes[i] = [];
|
|
406
|
-
}
|
|
407
|
-
} else {
|
|
408
|
-
this.#selectorAST = parseSelector(selector);
|
|
409
|
-
const { branches, info } = walkAST(
|
|
410
|
-
this.#selectorAST,
|
|
411
|
-
true,
|
|
412
|
-
createHasValidator(this.#window)
|
|
413
|
-
);
|
|
414
|
-
const {
|
|
415
|
-
hasHasPseudoFunc,
|
|
416
|
-
hasLogicalPseudoFunc,
|
|
417
|
-
hasNthChildOfSelector,
|
|
418
|
-
hasStatePseudoClass,
|
|
419
|
-
hasUnsupportedPseudoClass
|
|
420
|
-
} = info;
|
|
421
|
-
this.#invalidate =
|
|
422
|
-
hasHasPseudoFunc ||
|
|
423
|
-
hasStatePseudoClass ||
|
|
424
|
-
hasUnsupportedPseudoClass ||
|
|
425
|
-
!!(hasLogicalPseudoFunc && hasNthChildOfSelector);
|
|
426
|
-
const processed = this._processSelectorBranches(branches, selector);
|
|
427
|
-
ast = processed.ast;
|
|
428
|
-
this.#descendant = processed.descendant;
|
|
429
|
-
let cachedItem;
|
|
430
|
-
if (this.#documentCache.has(this.#document)) {
|
|
431
|
-
cachedItem = this.#documentCache.get(this.#document);
|
|
432
|
-
} else {
|
|
433
|
-
cachedItem = new Map();
|
|
434
|
-
}
|
|
435
|
-
cachedItem.set(`${selector}`, {
|
|
436
|
-
ast,
|
|
437
|
-
descendant: this.#descendant,
|
|
438
|
-
invalidate: this.#invalidate,
|
|
439
|
-
selectorAST: this.#selectorAST
|
|
440
|
-
});
|
|
441
|
-
this.#documentCache.set(this.#document, cachedItem);
|
|
442
|
-
// Initialize nodes array for each branch.
|
|
443
|
-
for (let i = 0; i < ast.length; i++) {
|
|
444
|
-
nodes[i] = [];
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
return [ast, nodes];
|
|
448
|
-
};
|
|
449
|
-
|
|
450
|
-
/**
|
|
451
|
-
* Creates a TreeWalker.
|
|
452
|
-
* @private
|
|
453
|
-
* @param {object} node - The Document, DocumentFragment, or Element node.
|
|
454
|
-
* @param {object} [opt] - Options.
|
|
455
|
-
* @param {boolean} [opt.force] - Force creation of a new TreeWalker.
|
|
456
|
-
* @param {number} [opt.whatToShow] - The NodeFilter whatToShow value.
|
|
457
|
-
* @returns {object} The TreeWalker object.
|
|
458
|
-
*/
|
|
459
|
-
_createTreeWalker = (node, opt = {}) => {
|
|
460
|
-
const { force = false, whatToShow = SHOW_CONTAINER } = opt;
|
|
461
|
-
if (force) {
|
|
462
|
-
return this.#document.createTreeWalker(node, whatToShow);
|
|
463
|
-
}
|
|
464
|
-
if (!this.#walkers) {
|
|
465
|
-
this.#walkers = new WeakMap();
|
|
466
|
-
}
|
|
467
|
-
if (this.#walkers.has(node)) {
|
|
468
|
-
return this.#walkers.get(node);
|
|
469
|
-
}
|
|
470
|
-
const walker = this.#document.createTreeWalker(node, whatToShow);
|
|
471
|
-
this.#walkers.set(node, walker);
|
|
472
|
-
return walker;
|
|
473
|
-
};
|
|
474
|
-
|
|
475
|
-
/**
|
|
476
|
-
* Gets selector branches from cache or parses them.
|
|
477
|
-
* @private
|
|
478
|
-
* @param {object} selector - The AST.
|
|
479
|
-
* @returns {Array.<Array.<object>>} The selector branches.
|
|
480
|
-
*/
|
|
481
|
-
_getSelectorBranches = selector => {
|
|
482
|
-
if (this.#astCache.has(selector)) {
|
|
483
|
-
return this.#astCache.get(selector);
|
|
484
|
-
}
|
|
485
|
-
const { branches } = walkAST(selector);
|
|
486
|
-
this.#astCache.set(selector, branches);
|
|
487
|
-
return branches;
|
|
488
|
-
};
|
|
489
|
-
|
|
490
|
-
/**
|
|
491
|
-
* Gets the children of a node, optionally filtered by a selector.
|
|
492
|
-
* @private
|
|
493
|
-
* @param {object} parentNode - The parent element.
|
|
494
|
-
* @param {Array.<Array.<object>>} selectorBranches - The selector branches.
|
|
495
|
-
* @param {object} opt - Options.
|
|
496
|
-
* @returns {Array.<object>} An array of child nodes.
|
|
497
|
-
*/
|
|
498
|
-
_getFilteredChildren = (parentNode, selectorBranches, opt) => {
|
|
499
|
-
const children = [];
|
|
500
|
-
let childNode = parentNode.firstElementChild;
|
|
501
|
-
while (childNode) {
|
|
502
|
-
if (selectorBranches) {
|
|
503
|
-
let isMatch = false;
|
|
504
|
-
const l = selectorBranches.length;
|
|
505
|
-
for (let i = 0; i < l; i++) {
|
|
506
|
-
const leaves = selectorBranches[i];
|
|
507
|
-
if (this._matchLeaves(leaves, childNode, opt)) {
|
|
508
|
-
isMatch = true;
|
|
509
|
-
break;
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
if (isMatch) {
|
|
513
|
-
if (this.#node === childNode) {
|
|
514
|
-
children.push(childNode);
|
|
515
|
-
} else if (isVisible(childNode)) {
|
|
516
|
-
children.push(childNode);
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
} else {
|
|
520
|
-
children.push(childNode);
|
|
521
|
-
}
|
|
522
|
-
childNode = childNode.nextElementSibling;
|
|
523
|
-
}
|
|
524
|
-
return children;
|
|
525
|
-
};
|
|
526
|
-
|
|
527
|
-
/**
|
|
528
|
-
* Collects nth-child nodes.
|
|
529
|
-
* @private
|
|
530
|
-
* @param {object} anb - An+B options.
|
|
531
|
-
* @param {number} anb.a - The 'a' value.
|
|
532
|
-
* @param {number} anb.b - The 'b' value.
|
|
533
|
-
* @param {boolean} [anb.reverse] - If true, reverses the order.
|
|
534
|
-
* @param {object} [anb.selector] - The selector for 'of S'.
|
|
535
|
-
* @param {object} node - The Element node.
|
|
536
|
-
* @param {object} opt - Options.
|
|
537
|
-
* @returns {Set.<object>} A collection of matched nodes.
|
|
538
|
-
*/
|
|
539
|
-
_collectNthChild = (anb, node, opt) => {
|
|
540
|
-
const { a, b, selector } = anb;
|
|
541
|
-
const { parentNode } = node;
|
|
542
|
-
if (!parentNode) {
|
|
543
|
-
const matchedNode = new Set();
|
|
544
|
-
if (node === this.#root && a * 1 + b * 1 === 1) {
|
|
545
|
-
if (selector) {
|
|
546
|
-
const selectorBranches = this._getSelectorBranches(selector);
|
|
547
|
-
const l = selectorBranches.length;
|
|
548
|
-
for (let i = 0; i < l; i++) {
|
|
549
|
-
const leaves = selectorBranches[i];
|
|
550
|
-
if (this._matchLeaves(leaves, node, opt)) {
|
|
551
|
-
matchedNode.add(node);
|
|
552
|
-
break;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
} else {
|
|
556
|
-
matchedNode.add(node);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
return matchedNode;
|
|
560
|
-
}
|
|
561
|
-
if (!this.#nthChildResultCache) {
|
|
562
|
-
this.#nthChildResultCache = new WeakMap();
|
|
563
|
-
}
|
|
564
|
-
let parentResultCache = this.#nthChildResultCache.get(parentNode);
|
|
565
|
-
if (!parentResultCache) {
|
|
566
|
-
parentResultCache = new WeakMap();
|
|
567
|
-
this.#nthChildResultCache.set(parentNode, parentResultCache);
|
|
568
|
-
}
|
|
569
|
-
const cachedSet = parentResultCache.get(anb);
|
|
570
|
-
if (cachedSet) {
|
|
571
|
-
return cachedSet;
|
|
572
|
-
}
|
|
573
|
-
let siblings;
|
|
574
|
-
if (selector) {
|
|
575
|
-
if (!this.#nthChildOfCache) {
|
|
576
|
-
this.#nthChildOfCache = new WeakMap();
|
|
577
|
-
}
|
|
578
|
-
let parentOfCacheMap = this.#nthChildOfCache.get(parentNode);
|
|
579
|
-
if (!parentOfCacheMap) {
|
|
580
|
-
parentOfCacheMap = new Map();
|
|
581
|
-
this.#nthChildOfCache.set(parentNode, parentOfCacheMap);
|
|
582
|
-
}
|
|
583
|
-
siblings = parentOfCacheMap.get(selector);
|
|
584
|
-
if (!siblings) {
|
|
585
|
-
const selectorBranches = this._getSelectorBranches(selector);
|
|
586
|
-
siblings = this._getFilteredChildren(parentNode, selectorBranches, opt);
|
|
587
|
-
parentOfCacheMap.set(selector, siblings);
|
|
588
|
-
}
|
|
589
|
-
} else {
|
|
590
|
-
if (!this.#nthChildCache) {
|
|
591
|
-
this.#nthChildCache = new WeakMap();
|
|
592
|
-
}
|
|
593
|
-
siblings = this.#nthChildCache.get(parentNode);
|
|
594
|
-
if (!siblings) {
|
|
595
|
-
siblings = this._getFilteredChildren(parentNode, null, opt);
|
|
596
|
-
this.#nthChildCache.set(parentNode, siblings);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
const matchedNodes = filterNodesByAnB(siblings, anb);
|
|
600
|
-
const resultSet = new Set(matchedNodes);
|
|
601
|
-
parentResultCache.set(anb, resultSet);
|
|
602
|
-
return resultSet;
|
|
603
|
-
};
|
|
604
|
-
|
|
605
|
-
/**
|
|
606
|
-
* Collects nth-of-type nodes.
|
|
607
|
-
* @private
|
|
608
|
-
* @param {object} anb - An+B options.
|
|
609
|
-
* @param {number} anb.a - The 'a' value.
|
|
610
|
-
* @param {number} anb.b - The 'b' value.
|
|
611
|
-
* @param {boolean} [anb.reverse] - If true, reverses the order.
|
|
612
|
-
* @param {object} node - The Element node.
|
|
613
|
-
* @returns {Set.<object>} A collection of matched nodes.
|
|
614
|
-
*/
|
|
615
|
-
_collectNthOfType = (anb, node) => {
|
|
616
|
-
const { localName, namespaceURI, parentNode, prefix } = node;
|
|
617
|
-
if (!parentNode) {
|
|
618
|
-
if (node === this.#root && anb.a * 1 + anb.b * 1 === 1) {
|
|
619
|
-
return new Set([node]);
|
|
620
|
-
}
|
|
621
|
-
return new Set();
|
|
622
|
-
}
|
|
623
|
-
if (!this.#nthOfTypeResultCache) {
|
|
624
|
-
this.#nthOfTypeResultCache = new WeakMap();
|
|
625
|
-
}
|
|
626
|
-
let parentResultCache = this.#nthOfTypeResultCache.get(parentNode);
|
|
627
|
-
if (!parentResultCache) {
|
|
628
|
-
parentResultCache = new WeakMap();
|
|
629
|
-
this.#nthOfTypeResultCache.set(parentNode, parentResultCache);
|
|
630
|
-
}
|
|
631
|
-
let typeResultMap = parentResultCache.get(anb);
|
|
632
|
-
if (!typeResultMap) {
|
|
633
|
-
typeResultMap = new Map();
|
|
634
|
-
parentResultCache.set(anb, typeResultMap);
|
|
635
|
-
}
|
|
636
|
-
const typeKey = `${namespaceURI || ''}|${prefix || ''}|${localName}`;
|
|
637
|
-
const cachedSet = typeResultMap.get(typeKey);
|
|
638
|
-
if (cachedSet) {
|
|
639
|
-
return cachedSet;
|
|
640
|
-
}
|
|
641
|
-
if (!this.#nthOfTypeCache) {
|
|
642
|
-
this.#nthOfTypeCache = new WeakMap();
|
|
643
|
-
}
|
|
644
|
-
let typeMap = this.#nthOfTypeCache.get(parentNode);
|
|
645
|
-
if (!typeMap) {
|
|
646
|
-
typeMap = new Map();
|
|
647
|
-
this.#nthOfTypeCache.set(parentNode, typeMap);
|
|
648
|
-
}
|
|
649
|
-
let typedSiblings = typeMap.get(typeKey);
|
|
650
|
-
if (!typedSiblings) {
|
|
651
|
-
typedSiblings = [];
|
|
652
|
-
let sibling = parentNode.firstElementChild;
|
|
653
|
-
while (sibling) {
|
|
654
|
-
if (
|
|
655
|
-
sibling.localName === localName &&
|
|
656
|
-
sibling.namespaceURI === namespaceURI &&
|
|
657
|
-
sibling.prefix === prefix
|
|
658
|
-
) {
|
|
659
|
-
typedSiblings.push(sibling);
|
|
660
|
-
}
|
|
661
|
-
sibling = sibling.nextElementSibling;
|
|
662
|
-
}
|
|
663
|
-
typeMap.set(typeKey, typedSiblings);
|
|
664
|
-
}
|
|
665
|
-
const matchedNodes = filterNodesByAnB(typedSiblings, anb);
|
|
666
|
-
const resultSet = new Set(matchedNodes);
|
|
667
|
-
typeResultMap.set(typeKey, resultSet);
|
|
668
|
-
return resultSet;
|
|
669
|
-
};
|
|
670
|
-
|
|
671
|
-
/**
|
|
672
|
-
* Matches An+B.
|
|
673
|
-
* @private
|
|
674
|
-
* @param {object} ast - The AST.
|
|
675
|
-
* @param {object} node - The Element node.
|
|
676
|
-
* @param {string} nthName - The name of the nth pseudo-class.
|
|
677
|
-
* @param {object} opt - Options.
|
|
678
|
-
* @returns {boolean} True if matches, otherwise false.
|
|
679
|
-
*/
|
|
680
|
-
_matchAnPlusB = (ast, node, nthName, opt) => {
|
|
681
|
-
if (!this.#anbCache) {
|
|
682
|
-
this.#anbCache = new WeakMap();
|
|
683
|
-
}
|
|
684
|
-
let anb = this.#anbCache.get(ast);
|
|
685
|
-
if (!anb) {
|
|
686
|
-
const {
|
|
687
|
-
nth: { a, b, name: nthIdentName },
|
|
688
|
-
selector
|
|
689
|
-
} = ast;
|
|
690
|
-
const anbMap = new Map();
|
|
691
|
-
if (nthIdentName) {
|
|
692
|
-
if (nthIdentName === 'even') {
|
|
693
|
-
anbMap.set('a', 2);
|
|
694
|
-
anbMap.set('b', 0);
|
|
695
|
-
} else if (nthIdentName === 'odd') {
|
|
696
|
-
anbMap.set('a', 2);
|
|
697
|
-
anbMap.set('b', 1);
|
|
698
|
-
}
|
|
699
|
-
if (nthName.indexOf('last') > -1) {
|
|
700
|
-
anbMap.set('reverse', true);
|
|
701
|
-
}
|
|
702
|
-
} else {
|
|
703
|
-
if (typeof a === 'string' && /-?\d+/.test(a)) {
|
|
704
|
-
anbMap.set('a', a * 1);
|
|
705
|
-
} else {
|
|
706
|
-
anbMap.set('a', 0);
|
|
707
|
-
}
|
|
708
|
-
if (typeof b === 'string' && /-?\d+/.test(b)) {
|
|
709
|
-
anbMap.set('b', b * 1);
|
|
710
|
-
} else {
|
|
711
|
-
anbMap.set('b', 0);
|
|
712
|
-
}
|
|
713
|
-
if (nthName.indexOf('last') > -1) {
|
|
714
|
-
anbMap.set('reverse', true);
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
if (nthName === 'nth-child' || nthName === 'nth-last-child') {
|
|
718
|
-
if (selector) {
|
|
719
|
-
anbMap.set('selector', selector);
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
anb = Object.fromEntries(anbMap);
|
|
723
|
-
this.#anbCache.set(ast, anb);
|
|
724
|
-
}
|
|
725
|
-
if (nthName === 'nth-child' || nthName === 'nth-last-child') {
|
|
726
|
-
const nodes = this._collectNthChild(anb, node, opt);
|
|
727
|
-
return nodes.has(node);
|
|
728
|
-
} else if (nthName === 'nth-of-type' || nthName === 'nth-last-of-type') {
|
|
729
|
-
const nodes = this._collectNthOfType(anb, node);
|
|
730
|
-
return nodes.has(node);
|
|
731
|
-
}
|
|
732
|
-
return false;
|
|
733
|
-
};
|
|
734
|
-
|
|
735
|
-
/**
|
|
736
|
-
* Matches the :has() pseudo-class function.
|
|
737
|
-
* @private
|
|
738
|
-
* @param {Array.<object>} astLeaves - The AST leaves.
|
|
739
|
-
* @param {object} node - The Element node.
|
|
740
|
-
* @param {object} [opt] - Options.
|
|
741
|
-
* @returns {boolean} The result.
|
|
742
|
-
*/
|
|
743
|
-
_matchHasPseudoFunc = (astLeaves, node, opt = {}) => {
|
|
744
|
-
const l = astLeaves.length;
|
|
745
|
-
if (!l) {
|
|
746
|
-
return false;
|
|
747
|
-
}
|
|
748
|
-
let startIndex = 0;
|
|
749
|
-
let combo;
|
|
750
|
-
if (astLeaves[0].type === COMBINATOR) {
|
|
751
|
-
combo = astLeaves[0];
|
|
752
|
-
startIndex = 1;
|
|
753
|
-
} else {
|
|
754
|
-
combo = {
|
|
755
|
-
name: ' ',
|
|
756
|
-
type: COMBINATOR
|
|
757
|
-
};
|
|
758
|
-
startIndex = 0;
|
|
759
|
-
}
|
|
760
|
-
const twigLeaves = [];
|
|
761
|
-
let nextComboIndex = startIndex;
|
|
762
|
-
for (; nextComboIndex < l; nextComboIndex++) {
|
|
763
|
-
if (astLeaves[nextComboIndex].type === COMBINATOR) {
|
|
764
|
-
break;
|
|
765
|
-
}
|
|
766
|
-
twigLeaves.push(astLeaves[nextComboIndex]);
|
|
767
|
-
}
|
|
768
|
-
const twig = {
|
|
769
|
-
combo,
|
|
770
|
-
leaves: twigLeaves
|
|
771
|
-
};
|
|
772
|
-
opt.dir = DIR_NEXT;
|
|
773
|
-
const nodes = this._collectCombinatorMatches(twig, node, opt, []);
|
|
774
|
-
if (nodes.length) {
|
|
775
|
-
if (nextComboIndex < l) {
|
|
776
|
-
let bool = false;
|
|
777
|
-
const remainingLeaves = astLeaves.slice(nextComboIndex);
|
|
778
|
-
for (const nextNode of nodes) {
|
|
779
|
-
bool = this._matchHasPseudoFunc(remainingLeaves, nextNode, opt);
|
|
780
|
-
if (bool) {
|
|
781
|
-
break;
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
return bool;
|
|
785
|
-
}
|
|
786
|
-
return true;
|
|
787
|
-
}
|
|
788
|
-
return false;
|
|
789
|
-
};
|
|
790
|
-
|
|
791
|
-
/**
|
|
792
|
-
* Builds an Allowlist for the :has() branch using a sparse seed element.
|
|
793
|
-
* @private
|
|
794
|
-
* @param {Array} leaves - The AST leaves of the selector branch.
|
|
795
|
-
* @returns {object|null} The wrapper object containing the WeakSet, or null.
|
|
796
|
-
*/
|
|
797
|
-
_buildHasAllowlist = leaves => {
|
|
798
|
-
const { seed } = findBestSeed(leaves);
|
|
799
|
-
if (!seed) {
|
|
800
|
-
return null;
|
|
801
|
-
}
|
|
802
|
-
if (this.#shadow || this.#node.nodeType === DOCUMENT_FRAGMENT_NODE) {
|
|
803
|
-
return null;
|
|
804
|
-
}
|
|
805
|
-
let seedElements = null;
|
|
806
|
-
let isSingleNode = false;
|
|
807
|
-
if (seed.type === 'id') {
|
|
808
|
-
if (typeof this.#root.getElementById === 'function') {
|
|
809
|
-
const node = this.#root.getElementById(seed.value);
|
|
810
|
-
if (node) {
|
|
811
|
-
seedElements = node;
|
|
812
|
-
isSingleNode = true;
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
} else if (seed.type === 'class') {
|
|
816
|
-
if (typeof this.#root.getElementsByClassName === 'function') {
|
|
817
|
-
seedElements = this.#root.getElementsByClassName(seed.value);
|
|
818
|
-
}
|
|
819
|
-
} else if (seed.type === 'tag') {
|
|
820
|
-
if (typeof this.#root.getElementsByTagName === 'function') {
|
|
821
|
-
seedElements = this.#root.getElementsByTagName(seed.value);
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
if (!seedElements) {
|
|
825
|
-
return null;
|
|
826
|
-
}
|
|
827
|
-
const len = isSingleNode ? 1 : seedElements.length;
|
|
828
|
-
if (len === 0 || len > HEX * HEX) {
|
|
829
|
-
return null;
|
|
830
|
-
}
|
|
831
|
-
const filterResult = {
|
|
832
|
-
seeded: true,
|
|
833
|
-
set: new WeakSet()
|
|
834
|
-
};
|
|
835
|
-
const list = filterResult.set;
|
|
836
|
-
const visitedAncestors = new Set();
|
|
837
|
-
if (this.#node) {
|
|
838
|
-
list.add(this.#node);
|
|
839
|
-
}
|
|
840
|
-
for (let i = 0; i < len; i++) {
|
|
841
|
-
const current = isSingleNode ? seedElements : seedElements[i];
|
|
842
|
-
if (current) {
|
|
843
|
-
populateHasAllowlist(current, list, visitedAncestors);
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
return filterResult;
|
|
847
|
-
};
|
|
848
|
-
|
|
849
|
-
/**
|
|
850
|
-
* Evaluates :has() pseudo-class.
|
|
851
|
-
* @private
|
|
852
|
-
* @param {object} astData - The AST data.
|
|
853
|
-
* @param {object} node - The Element node.
|
|
854
|
-
* @param {object} [opt] - Options.
|
|
855
|
-
* @returns {?object} The matched node.
|
|
856
|
-
*/
|
|
857
|
-
_evaluateHasPseudo = (astData, node, opt = {}) => {
|
|
858
|
-
const { branches } = astData;
|
|
859
|
-
let bool = false;
|
|
860
|
-
if (!this.#psHasFilterCache) {
|
|
861
|
-
this.#psHasFilterCache = new WeakMap();
|
|
862
|
-
}
|
|
863
|
-
let rootCache = this.#psHasFilterCache.get(this.#root);
|
|
864
|
-
if (!rootCache) {
|
|
865
|
-
rootCache = new WeakMap();
|
|
866
|
-
this.#psHasFilterCache.set(this.#root, rootCache);
|
|
867
|
-
}
|
|
868
|
-
for (const leaves of branches) {
|
|
869
|
-
if (!rootCache.has(leaves)) {
|
|
870
|
-
const filterResult = this._buildHasAllowlist(leaves);
|
|
871
|
-
rootCache.set(leaves, filterResult);
|
|
872
|
-
}
|
|
873
|
-
const allowlist = rootCache.get(leaves);
|
|
874
|
-
if (
|
|
875
|
-
allowlist &&
|
|
876
|
-
allowlist.seeded &&
|
|
877
|
-
node.nodeType !== DOCUMENT_FRAGMENT_NODE &&
|
|
878
|
-
!allowlist.set.has(node)
|
|
879
|
-
) {
|
|
880
|
-
continue;
|
|
881
|
-
}
|
|
882
|
-
bool = this._matchHasPseudoFunc(leaves, node, opt);
|
|
883
|
-
if (bool) {
|
|
884
|
-
break;
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
if (!bool) {
|
|
888
|
-
return null;
|
|
889
|
-
}
|
|
890
|
-
if (
|
|
891
|
-
(opt.isShadowRoot || this.#shadow) &&
|
|
892
|
-
node.nodeType === DOCUMENT_FRAGMENT_NODE
|
|
893
|
-
) {
|
|
894
|
-
return this.#verifyShadowHost ? node : null;
|
|
895
|
-
}
|
|
896
|
-
return node;
|
|
897
|
-
};
|
|
898
|
-
|
|
899
|
-
/**
|
|
900
|
-
* Matches logical pseudo-class functions.
|
|
901
|
-
* @private
|
|
902
|
-
* @param {object} astData - The AST data.
|
|
903
|
-
* @param {object} node - The Element node.
|
|
904
|
-
* @param {object} [opt] - Options.
|
|
905
|
-
* @returns {boolean} Tru if matches, otherwise false.
|
|
906
|
-
*/
|
|
907
|
-
_matchLogicalPseudoFunc = (astData, node, opt = {}) => {
|
|
908
|
-
const { astName, branches, twigBranches } = astData;
|
|
909
|
-
// Handle :has().
|
|
910
|
-
if (astName === 'has') {
|
|
911
|
-
return this._evaluateHasPseudo(astData, node, opt) === node;
|
|
912
|
-
}
|
|
913
|
-
// Handle :is(), :not(), :where().
|
|
914
|
-
const isShadowRoot =
|
|
915
|
-
(opt.isShadowRoot || this.#shadow) &&
|
|
916
|
-
node.nodeType === DOCUMENT_FRAGMENT_NODE;
|
|
917
|
-
// Check for invalid shadow root.
|
|
918
|
-
if (isShadowRoot) {
|
|
919
|
-
let invalid = false;
|
|
920
|
-
for (const branch of branches) {
|
|
921
|
-
if (branch.length > 1) {
|
|
922
|
-
invalid = true;
|
|
923
|
-
break;
|
|
924
|
-
} else if (astName === 'not') {
|
|
925
|
-
const [{ type: childAstType }] = branch;
|
|
926
|
-
if (childAstType !== PS_CLASS_SELECTOR) {
|
|
927
|
-
invalid = true;
|
|
928
|
-
break;
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
if (invalid) {
|
|
933
|
-
return false;
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
opt.forgive = astName === 'is' || astName === 'where';
|
|
937
|
-
const l = twigBranches.length;
|
|
938
|
-
let bool;
|
|
939
|
-
for (let i = 0; i < l; i++) {
|
|
940
|
-
const branch = twigBranches[i];
|
|
941
|
-
const lastIndex = branch.length - 1;
|
|
942
|
-
const { leaves } = branch[lastIndex];
|
|
943
|
-
bool = this._matchLeaves(leaves, node, opt);
|
|
944
|
-
if (bool && lastIndex > 0) {
|
|
945
|
-
let nextNodes = new Set([node]);
|
|
946
|
-
for (let j = lastIndex - 1; j >= 0; j--) {
|
|
947
|
-
const twig = branch[j];
|
|
948
|
-
const arr = [];
|
|
949
|
-
opt.dir = DIR_PREV;
|
|
950
|
-
for (const nextNode of nextNodes) {
|
|
951
|
-
this._collectCombinatorMatches(twig, nextNode, opt, arr);
|
|
952
|
-
}
|
|
953
|
-
if (arr.length) {
|
|
954
|
-
if (j === 0) {
|
|
955
|
-
bool = true;
|
|
956
|
-
} else {
|
|
957
|
-
nextNodes = new Set(arr);
|
|
958
|
-
}
|
|
959
|
-
} else {
|
|
960
|
-
bool = false;
|
|
961
|
-
break;
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
if (bool) {
|
|
966
|
-
break;
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
if (astName === 'not') {
|
|
970
|
-
return !bool;
|
|
971
|
-
}
|
|
972
|
-
return bool;
|
|
973
|
-
};
|
|
974
|
-
|
|
975
|
-
/**
|
|
976
|
-
* Evaluates logical pseudo-class selector.
|
|
977
|
-
* @private
|
|
978
|
-
* @param {object} ast - The AST.
|
|
979
|
-
* @param {object} node - The Element node.
|
|
980
|
-
* @param {object} [opt] - Options.
|
|
981
|
-
* @param {boolean} [opt.forgive] - Ignores unknown or invalid selectors.
|
|
982
|
-
* @param {boolean} [opt.warn] - If true, console warnings are enabled.
|
|
983
|
-
* @returns {boolean} True if matches, otherwise false.
|
|
984
|
-
*/
|
|
985
|
-
_evaluateLogicalPseudo(ast, node, opt = {}) {
|
|
986
|
-
const { children: astChildren, name: astName } = ast;
|
|
987
|
-
if (!astChildren.length && astName !== 'is' && astName !== 'where') {
|
|
988
|
-
const css = generateCSS(ast);
|
|
989
|
-
const msg = `Invalid selector ${css}`;
|
|
990
|
-
this.onError(generateException(msg, SYNTAX_ERR, this.#window));
|
|
991
|
-
return false;
|
|
992
|
-
}
|
|
993
|
-
let astData;
|
|
994
|
-
if (this.#astCache.has(ast)) {
|
|
995
|
-
astData = this.#astCache.get(ast);
|
|
996
|
-
} else {
|
|
997
|
-
const { branches } = walkAST(ast);
|
|
998
|
-
if (astName === 'has') {
|
|
999
|
-
astData = {
|
|
1000
|
-
astName,
|
|
1001
|
-
branches
|
|
1002
|
-
};
|
|
1003
|
-
} else {
|
|
1004
|
-
const twigBranches = [];
|
|
1005
|
-
const l = branches.length;
|
|
1006
|
-
for (let i = 0; i < l; i++) {
|
|
1007
|
-
const leaves = branches[i];
|
|
1008
|
-
const branch = [];
|
|
1009
|
-
const leavesSet = new Set();
|
|
1010
|
-
const leavesLen = leaves.length;
|
|
1011
|
-
for (let j = 0; j < leavesLen; j++) {
|
|
1012
|
-
const item = leaves[j];
|
|
1013
|
-
if (item.type === COMBINATOR) {
|
|
1014
|
-
branch.push({
|
|
1015
|
-
combo: item,
|
|
1016
|
-
leaves: [...leavesSet]
|
|
1017
|
-
});
|
|
1018
|
-
leavesSet.clear();
|
|
1019
|
-
} else {
|
|
1020
|
-
leavesSet.add(item);
|
|
1021
|
-
}
|
|
1022
|
-
if (j === leavesLen - 1) {
|
|
1023
|
-
branch.push({
|
|
1024
|
-
combo: null,
|
|
1025
|
-
leaves: [...leavesSet]
|
|
1026
|
-
});
|
|
1027
|
-
leavesSet.clear();
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
twigBranches.push(branch);
|
|
1031
|
-
}
|
|
1032
|
-
astData = {
|
|
1033
|
-
astName,
|
|
1034
|
-
branches,
|
|
1035
|
-
twigBranches
|
|
1036
|
-
};
|
|
1037
|
-
}
|
|
1038
|
-
this.#astCache.set(ast, astData);
|
|
1039
|
-
}
|
|
1040
|
-
return this._matchLogicalPseudoFunc(astData, node, opt);
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
/**
|
|
1044
|
-
* Evaluates pseudo-class function.
|
|
1045
|
-
* @private
|
|
1046
|
-
* @see https://html.spec.whatwg.org/#pseudo-classes
|
|
1047
|
-
* @param {object} ast - The AST.
|
|
1048
|
-
* @param {object} node - The Element node.
|
|
1049
|
-
* @param {object} [opt] - Options.
|
|
1050
|
-
* @param {boolean} [opt.forgive] - Ignores unknown or invalid selectors.
|
|
1051
|
-
* @param {boolean} [opt.warn] - If true, console warnings are enabled.
|
|
1052
|
-
* @returns {boolean} True if matches, otherwise false.
|
|
1053
|
-
*/
|
|
1054
|
-
_evaluatePseudoClassFunc(ast, node, opt = {}) {
|
|
1055
|
-
const { children: astChildren, name: astName } = ast;
|
|
1056
|
-
const { forgive, warn = this.#warn } = opt;
|
|
1057
|
-
// :nth-child(), :nth-last-child(), nth-of-type(), :nth-last-of-type()
|
|
1058
|
-
if (/^nth-(?:last-)?(?:child|of-type)$/.test(astName)) {
|
|
1059
|
-
if (astChildren.length !== 1) {
|
|
1060
|
-
const css = generateCSS(ast);
|
|
1061
|
-
this.onError(
|
|
1062
|
-
generateException(`Invalid selector ${css}`, SYNTAX_ERR, this.#window)
|
|
1063
|
-
);
|
|
1064
|
-
return false;
|
|
1065
|
-
}
|
|
1066
|
-
const [branch] = astChildren;
|
|
1067
|
-
return this._matchAnPlusB(branch, node, astName, opt);
|
|
1068
|
-
}
|
|
1069
|
-
switch (astName) {
|
|
1070
|
-
// :dir()
|
|
1071
|
-
case 'dir': {
|
|
1072
|
-
if (astChildren.length !== 1) {
|
|
1073
|
-
const css = generateCSS(ast);
|
|
1074
|
-
this.onError(
|
|
1075
|
-
generateException(
|
|
1076
|
-
`Invalid selector ${css}`,
|
|
1077
|
-
SYNTAX_ERR,
|
|
1078
|
-
this.#window
|
|
1079
|
-
)
|
|
1080
|
-
);
|
|
1081
|
-
return false;
|
|
1082
|
-
}
|
|
1083
|
-
const [astChild] = astChildren;
|
|
1084
|
-
if (!this.#psDirCache) {
|
|
1085
|
-
this.#psDirCache = new WeakMap();
|
|
1086
|
-
}
|
|
1087
|
-
const res = matchDirectionPseudoClass(astChild, node, this.#psDirCache);
|
|
1088
|
-
if (res) {
|
|
1089
|
-
return true;
|
|
1090
|
-
}
|
|
1091
|
-
break;
|
|
1092
|
-
}
|
|
1093
|
-
// :lang()
|
|
1094
|
-
case 'lang': {
|
|
1095
|
-
if (!astChildren.length) {
|
|
1096
|
-
const css = generateCSS(ast);
|
|
1097
|
-
this.onError(
|
|
1098
|
-
generateException(
|
|
1099
|
-
`Invalid selector ${css}`,
|
|
1100
|
-
SYNTAX_ERR,
|
|
1101
|
-
this.#window
|
|
1102
|
-
)
|
|
1103
|
-
);
|
|
1104
|
-
return false;
|
|
1105
|
-
}
|
|
1106
|
-
if (!this.#psLangCache) {
|
|
1107
|
-
this.#psLangCache = new WeakMap();
|
|
1108
|
-
}
|
|
1109
|
-
let bool;
|
|
1110
|
-
for (const astChild of astChildren) {
|
|
1111
|
-
bool = matchLanguagePseudoClass(astChild, node, this.#psLangCache);
|
|
1112
|
-
if (bool) {
|
|
1113
|
-
break;
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
if (bool) {
|
|
1117
|
-
return true;
|
|
1118
|
-
}
|
|
1119
|
-
break;
|
|
1120
|
-
}
|
|
1121
|
-
// :state()
|
|
1122
|
-
case 'state': {
|
|
1123
|
-
if (isCustomElement(node)) {
|
|
1124
|
-
const [{ value: stateValue }] = astChildren;
|
|
1125
|
-
if (stateValue) {
|
|
1126
|
-
if (node[stateValue]) {
|
|
1127
|
-
return true;
|
|
1128
|
-
}
|
|
1129
|
-
for (const i in node) {
|
|
1130
|
-
const prop = node[i];
|
|
1131
|
-
if (prop instanceof this.#window.ElementInternals) {
|
|
1132
|
-
if (prop?.states?.has(stateValue)) {
|
|
1133
|
-
return true;
|
|
1134
|
-
}
|
|
1135
|
-
break;
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
break;
|
|
1141
|
-
}
|
|
1142
|
-
case 'current':
|
|
1143
|
-
case 'heading':
|
|
1144
|
-
case 'nth-col':
|
|
1145
|
-
case 'nth-last-col': {
|
|
1146
|
-
if (warn) {
|
|
1147
|
-
this.onError(
|
|
1148
|
-
generateException(
|
|
1149
|
-
`Unsupported pseudo-class :${astName}()`,
|
|
1150
|
-
NOT_SUPPORTED_ERR,
|
|
1151
|
-
this.#window
|
|
1152
|
-
)
|
|
1153
|
-
);
|
|
1154
|
-
}
|
|
1155
|
-
break;
|
|
1156
|
-
}
|
|
1157
|
-
// Ignore :host() and :host-context().
|
|
1158
|
-
case 'host':
|
|
1159
|
-
case 'host-context': {
|
|
1160
|
-
break;
|
|
1161
|
-
}
|
|
1162
|
-
// Deprecated in CSS Selectors 3.
|
|
1163
|
-
case 'contains': {
|
|
1164
|
-
if (warn) {
|
|
1165
|
-
this.onError(
|
|
1166
|
-
generateException(
|
|
1167
|
-
`Unknown pseudo-class :${astName}()`,
|
|
1168
|
-
NOT_SUPPORTED_ERR,
|
|
1169
|
-
this.#window
|
|
1170
|
-
)
|
|
1171
|
-
);
|
|
1172
|
-
}
|
|
1173
|
-
break;
|
|
1174
|
-
}
|
|
1175
|
-
default: {
|
|
1176
|
-
if (!forgive) {
|
|
1177
|
-
this.onError(
|
|
1178
|
-
generateException(
|
|
1179
|
-
`Unknown pseudo-class :${astName}()`,
|
|
1180
|
-
SYNTAX_ERR,
|
|
1181
|
-
this.#window
|
|
1182
|
-
)
|
|
1183
|
-
);
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
return false;
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
|
-
/**
|
|
1191
|
-
* Evaluates *-of-type pseudo-class selector.
|
|
1192
|
-
* @private
|
|
1193
|
-
* @param {string} astName - The AST name.
|
|
1194
|
-
* @param {object} node - The Element node.
|
|
1195
|
-
* @returns {boolean} True if matches, otherwise false.
|
|
1196
|
-
*/
|
|
1197
|
-
|
|
1198
|
-
/**
|
|
1199
|
-
* Matches pseudo-class selector.
|
|
1200
|
-
* @private
|
|
1201
|
-
* @see https://html.spec.whatwg.org/#pseudo-classes
|
|
1202
|
-
* @param {object} ast - The AST.
|
|
1203
|
-
* @param {object} node - The Element node.
|
|
1204
|
-
* @param {object} [opt] - Options.
|
|
1205
|
-
* @param {boolean} [opt.forgive] - Ignores unknown or invalid selectors.
|
|
1206
|
-
* @param {boolean} [opt.warn] - If true, console warnings are enabled.
|
|
1207
|
-
* @returns {Set.<object>|boolean} A collection of matched nodes.
|
|
1208
|
-
*/
|
|
1209
|
-
_matchPseudoClassSelector(ast, node, opt = {}) {
|
|
1210
|
-
const { children: astChildren, name: astName } = ast;
|
|
1211
|
-
const { localName, parentNode } = node;
|
|
1212
|
-
const { forgive, warn = this.#warn } = opt;
|
|
1213
|
-
if (Array.isArray(astChildren)) {
|
|
1214
|
-
// :has(), :is(), :not(), :where()
|
|
1215
|
-
if (KEYS_LOGICAL.has(astName)) {
|
|
1216
|
-
return this._evaluateLogicalPseudo(ast, node, opt);
|
|
1217
|
-
}
|
|
1218
|
-
return this._evaluatePseudoClassFunc(ast, node, opt);
|
|
1219
|
-
}
|
|
1220
|
-
if (KEYS_PS_NTH_OF_TYPE.has(astName)) {
|
|
1221
|
-
if (!parentNode) {
|
|
1222
|
-
return node === this.#root;
|
|
1223
|
-
}
|
|
1224
|
-
switch (astName) {
|
|
1225
|
-
case 'first-of-type': {
|
|
1226
|
-
const [node1] = this._collectNthOfType(ANB_FIRST, node);
|
|
1227
|
-
return node1 === node;
|
|
1228
|
-
}
|
|
1229
|
-
case 'last-of-type': {
|
|
1230
|
-
const [node1] = this._collectNthOfType(ANB_LAST, node);
|
|
1231
|
-
return node1 === node;
|
|
1232
|
-
}
|
|
1233
|
-
// 'only-of-type' is handled by default.
|
|
1234
|
-
default: {
|
|
1235
|
-
const [node1] = this._collectNthOfType(ANB_FIRST, node);
|
|
1236
|
-
if (node1 === node) {
|
|
1237
|
-
const [node2] = this._collectNthOfType(ANB_LAST, node);
|
|
1238
|
-
return node2 === node;
|
|
1239
|
-
}
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
return false;
|
|
1243
|
-
}
|
|
1244
|
-
switch (astName) {
|
|
1245
|
-
/* Elemental pseudo-classes */
|
|
1246
|
-
case 'defined': {
|
|
1247
|
-
if (node.hasAttribute('is') || localName.includes('-')) {
|
|
1248
|
-
return isCustomElement(node);
|
|
1249
|
-
}
|
|
1250
|
-
return (
|
|
1251
|
-
node instanceof this.#window.HTMLElement ||
|
|
1252
|
-
node instanceof this.#window.SVGElement
|
|
1253
|
-
);
|
|
1254
|
-
}
|
|
1255
|
-
/* Element display state pseudo-classes */
|
|
1256
|
-
case 'open': {
|
|
1257
|
-
// <select> and <input type="color"> are not supported.
|
|
1258
|
-
return (
|
|
1259
|
-
(localName === 'details' || localName === 'dialog') &&
|
|
1260
|
-
node.hasAttribute('open')
|
|
1261
|
-
);
|
|
1262
|
-
}
|
|
1263
|
-
case 'popover-open': {
|
|
1264
|
-
// FIXME: Not implemented in jsdom
|
|
1265
|
-
// @see https://github.com/jsdom/jsdom/issues/3721
|
|
1266
|
-
// return node.popover && isVisible(node);
|
|
1267
|
-
break;
|
|
1268
|
-
}
|
|
1269
|
-
/* Input pseudo-classes */
|
|
1270
|
-
case 'disabled':
|
|
1271
|
-
case 'enabled': {
|
|
1272
|
-
return matchDisabledPseudoClass(astName, node);
|
|
1273
|
-
}
|
|
1274
|
-
case 'read-only':
|
|
1275
|
-
case 'read-write': {
|
|
1276
|
-
return matchReadOnlyPseudoClass(astName, node);
|
|
1277
|
-
}
|
|
1278
|
-
case 'placeholder-shown': {
|
|
1279
|
-
let placeholder;
|
|
1280
|
-
if (node.placeholder) {
|
|
1281
|
-
placeholder = node.placeholder;
|
|
1282
|
-
} else if (node.hasAttribute('placeholder')) {
|
|
1283
|
-
placeholder = node.getAttribute('placeholder');
|
|
1284
|
-
}
|
|
1285
|
-
if (typeof placeholder === 'string' && !/[\r\n]/.test(placeholder)) {
|
|
1286
|
-
let targetNode;
|
|
1287
|
-
if (localName === 'textarea') {
|
|
1288
|
-
targetNode = node;
|
|
1289
|
-
} else if (localName === 'input') {
|
|
1290
|
-
if (node.hasAttribute('type')) {
|
|
1291
|
-
if (KEYS_INPUT_PLACEHOLDER.has(node.getAttribute('type'))) {
|
|
1292
|
-
targetNode = node;
|
|
1293
|
-
}
|
|
1294
|
-
} else {
|
|
1295
|
-
targetNode = node;
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
if (targetNode) {
|
|
1299
|
-
return node.value === '';
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
break;
|
|
1303
|
-
}
|
|
1304
|
-
case 'default': {
|
|
1305
|
-
// option
|
|
1306
|
-
if (localName === 'option') {
|
|
1307
|
-
return node.hasAttribute('selected');
|
|
1308
|
-
}
|
|
1309
|
-
const attrType = node.getAttribute('type');
|
|
1310
|
-
// input[type="checkbox"], input[type="radio"]
|
|
1311
|
-
if (
|
|
1312
|
-
localName === 'input' &&
|
|
1313
|
-
node.hasAttribute('type') &&
|
|
1314
|
-
node.hasAttribute('checked')
|
|
1315
|
-
) {
|
|
1316
|
-
return KEYS_INPUT_CHECK.has(attrType);
|
|
1317
|
-
}
|
|
1318
|
-
// button[type="submit"], input[type="submit"], input[type="image"]
|
|
1319
|
-
if (
|
|
1320
|
-
(localName === 'button' &&
|
|
1321
|
-
!(node.hasAttribute('type') && KEYS_INPUT_RESET.has(attrType))) ||
|
|
1322
|
-
(localName === 'input' &&
|
|
1323
|
-
node.hasAttribute('type') &&
|
|
1324
|
-
KEYS_INPUT_SUBMIT.has(attrType))
|
|
1325
|
-
) {
|
|
1326
|
-
let form = node.parentNode;
|
|
1327
|
-
while (form) {
|
|
1328
|
-
if (form.localName === 'form') {
|
|
1329
|
-
break;
|
|
1330
|
-
}
|
|
1331
|
-
form = form.parentNode;
|
|
1332
|
-
}
|
|
1333
|
-
if (form) {
|
|
1334
|
-
if (!this.#psDefaultCache) {
|
|
1335
|
-
this.#psDefaultCache = new WeakMap();
|
|
1336
|
-
}
|
|
1337
|
-
let defaultSubmit = this.#psDefaultCache.get(form);
|
|
1338
|
-
if (!defaultSubmit) {
|
|
1339
|
-
const walker = this._createTreeWalker(form, { force: true });
|
|
1340
|
-
let refNode = traverseNode(form, walker);
|
|
1341
|
-
refNode = walker.firstChild();
|
|
1342
|
-
while (refNode) {
|
|
1343
|
-
const nodeName = refNode.localName;
|
|
1344
|
-
const nodeAttrType = refNode.getAttribute('type');
|
|
1345
|
-
let m;
|
|
1346
|
-
if (nodeName === 'button') {
|
|
1347
|
-
m = !(
|
|
1348
|
-
refNode.hasAttribute('type') &&
|
|
1349
|
-
KEYS_INPUT_RESET.has(nodeAttrType)
|
|
1350
|
-
);
|
|
1351
|
-
} else if (nodeName === 'input') {
|
|
1352
|
-
m =
|
|
1353
|
-
refNode.hasAttribute('type') &&
|
|
1354
|
-
KEYS_INPUT_SUBMIT.has(nodeAttrType);
|
|
1355
|
-
}
|
|
1356
|
-
if (m) {
|
|
1357
|
-
defaultSubmit = refNode;
|
|
1358
|
-
break;
|
|
1359
|
-
}
|
|
1360
|
-
refNode = walker.nextNode();
|
|
1361
|
-
}
|
|
1362
|
-
this.#psDefaultCache.set(form, defaultSubmit);
|
|
1363
|
-
}
|
|
1364
|
-
return defaultSubmit === node;
|
|
1365
|
-
}
|
|
1366
|
-
}
|
|
1367
|
-
break;
|
|
1368
|
-
}
|
|
1369
|
-
case 'checked': {
|
|
1370
|
-
if (localName === 'option') {
|
|
1371
|
-
return node.selected;
|
|
1372
|
-
}
|
|
1373
|
-
if (localName === 'input') {
|
|
1374
|
-
const attrType = node.getAttribute('type');
|
|
1375
|
-
return (
|
|
1376
|
-
node.checked && (attrType === 'checkbox' || attrType === 'radio')
|
|
1377
|
-
);
|
|
1378
|
-
}
|
|
1379
|
-
break;
|
|
1380
|
-
}
|
|
1381
|
-
case 'indeterminate': {
|
|
1382
|
-
if (localName === 'progress') {
|
|
1383
|
-
return !node.hasAttribute('value');
|
|
1384
|
-
}
|
|
1385
|
-
if (localName === 'input' && node.type === 'checkbox') {
|
|
1386
|
-
return node.indeterminate;
|
|
1387
|
-
}
|
|
1388
|
-
if (localName === 'input' && node.type === 'radio') {
|
|
1389
|
-
if (node.checked || node.hasAttribute('checked')) {
|
|
1390
|
-
return false;
|
|
1391
|
-
}
|
|
1392
|
-
const nodeName = node.name;
|
|
1393
|
-
let parent = node.parentNode;
|
|
1394
|
-
while (parent) {
|
|
1395
|
-
if (parent.localName === 'form') {
|
|
1396
|
-
break;
|
|
1397
|
-
}
|
|
1398
|
-
parent = parent.parentNode;
|
|
1399
|
-
}
|
|
1400
|
-
if (!parent) {
|
|
1401
|
-
parent = this.#document.documentElement;
|
|
1402
|
-
}
|
|
1403
|
-
if (!this.#psIndeterminateCache) {
|
|
1404
|
-
this.#psIndeterminateCache = new WeakMap();
|
|
1405
|
-
}
|
|
1406
|
-
let parentCache = this.#psIndeterminateCache.get(parent);
|
|
1407
|
-
if (!parentCache) {
|
|
1408
|
-
parentCache = new Map();
|
|
1409
|
-
this.#psIndeterminateCache.set(parent, parentCache);
|
|
1410
|
-
}
|
|
1411
|
-
let checked = parentCache.get(nodeName);
|
|
1412
|
-
if (checked === undefined) {
|
|
1413
|
-
const walker = this._createTreeWalker(parent, { force: true });
|
|
1414
|
-
let refNode = traverseNode(parent, walker);
|
|
1415
|
-
refNode = walker.firstChild();
|
|
1416
|
-
while (refNode) {
|
|
1417
|
-
if (
|
|
1418
|
-
refNode.localName === 'input' &&
|
|
1419
|
-
refNode.getAttribute('type') === 'radio'
|
|
1420
|
-
) {
|
|
1421
|
-
if (refNode.hasAttribute('name')) {
|
|
1422
|
-
if (refNode.getAttribute('name') === nodeName) {
|
|
1423
|
-
checked = !!refNode.checked;
|
|
1424
|
-
}
|
|
1425
|
-
} else {
|
|
1426
|
-
checked = !!refNode.checked;
|
|
1427
|
-
}
|
|
1428
|
-
if (checked) {
|
|
1429
|
-
break;
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
refNode = walker.nextNode();
|
|
1433
|
-
}
|
|
1434
|
-
checked = !!checked;
|
|
1435
|
-
parentCache.set(nodeName, checked);
|
|
1436
|
-
}
|
|
1437
|
-
return !checked;
|
|
1438
|
-
}
|
|
1439
|
-
break;
|
|
1440
|
-
}
|
|
1441
|
-
case 'valid':
|
|
1442
|
-
case 'invalid': {
|
|
1443
|
-
if (KEYS_FORM_PS_VALID.has(localName)) {
|
|
1444
|
-
let valid = false;
|
|
1445
|
-
if (node.checkValidity()) {
|
|
1446
|
-
if (node.maxLength >= 0) {
|
|
1447
|
-
if (node.maxLength >= node.value.length) {
|
|
1448
|
-
valid = true;
|
|
1449
|
-
}
|
|
1450
|
-
} else {
|
|
1451
|
-
valid = true;
|
|
1452
|
-
}
|
|
1453
|
-
}
|
|
1454
|
-
if (astName === 'invalid') {
|
|
1455
|
-
return !valid;
|
|
1456
|
-
}
|
|
1457
|
-
return valid;
|
|
1458
|
-
}
|
|
1459
|
-
if (localName === 'fieldset') {
|
|
1460
|
-
if (!this.#psValidCache) {
|
|
1461
|
-
this.#psValidCache = new WeakMap();
|
|
1462
|
-
}
|
|
1463
|
-
let valid = this.#psValidCache.get(node);
|
|
1464
|
-
if (valid === undefined && !this.#psValidCache.has(node)) {
|
|
1465
|
-
const walker = this._createTreeWalker(node, { force: true });
|
|
1466
|
-
let refNode = traverseNode(node, walker);
|
|
1467
|
-
refNode = walker.firstChild();
|
|
1468
|
-
if (!refNode) {
|
|
1469
|
-
valid = true;
|
|
1470
|
-
} else {
|
|
1471
|
-
while (refNode) {
|
|
1472
|
-
if (KEYS_FORM_PS_VALID.has(refNode.localName)) {
|
|
1473
|
-
if (refNode.checkValidity()) {
|
|
1474
|
-
if (refNode.maxLength >= 0) {
|
|
1475
|
-
valid = refNode.maxLength >= refNode.value.length;
|
|
1476
|
-
} else {
|
|
1477
|
-
valid = true;
|
|
1478
|
-
}
|
|
1479
|
-
} else {
|
|
1480
|
-
valid = false;
|
|
1481
|
-
}
|
|
1482
|
-
if (!valid) {
|
|
1483
|
-
break;
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
refNode = walker.nextNode();
|
|
1487
|
-
}
|
|
1488
|
-
}
|
|
1489
|
-
this.#psValidCache.set(node, valid);
|
|
1490
|
-
}
|
|
1491
|
-
if (astName === 'invalid') {
|
|
1492
|
-
return !valid;
|
|
1493
|
-
}
|
|
1494
|
-
return valid;
|
|
1495
|
-
}
|
|
1496
|
-
break;
|
|
1497
|
-
}
|
|
1498
|
-
case 'in-range':
|
|
1499
|
-
case 'out-of-range': {
|
|
1500
|
-
const attrType = node.getAttribute('type');
|
|
1501
|
-
if (
|
|
1502
|
-
localName === 'input' &&
|
|
1503
|
-
!(node.readOnly || node.hasAttribute('readonly')) &&
|
|
1504
|
-
!(node.disabled || node.hasAttribute('disabled')) &&
|
|
1505
|
-
KEYS_INPUT_RANGE.has(attrType)
|
|
1506
|
-
) {
|
|
1507
|
-
const flowed =
|
|
1508
|
-
node.validity.rangeUnderflow || node.validity.rangeOverflow;
|
|
1509
|
-
if (astName === 'out-of-range') {
|
|
1510
|
-
return flowed;
|
|
1511
|
-
}
|
|
1512
|
-
return flowed
|
|
1513
|
-
? false
|
|
1514
|
-
: node.hasAttribute('min') ||
|
|
1515
|
-
node.hasAttribute('max') ||
|
|
1516
|
-
attrType === 'range';
|
|
1517
|
-
}
|
|
1518
|
-
break;
|
|
1519
|
-
}
|
|
1520
|
-
case 'required':
|
|
1521
|
-
case 'optional': {
|
|
1522
|
-
let required = false;
|
|
1523
|
-
if (localName === 'select' || localName === 'textarea') {
|
|
1524
|
-
if (node.required || node.hasAttribute('required')) {
|
|
1525
|
-
required = true;
|
|
1526
|
-
}
|
|
1527
|
-
} else if (localName === 'input') {
|
|
1528
|
-
if (node.hasAttribute('type')) {
|
|
1529
|
-
const attrType = node.getAttribute('type');
|
|
1530
|
-
if (KEYS_INPUT_REQUIRED.has(attrType)) {
|
|
1531
|
-
if (node.required || node.hasAttribute('required')) {
|
|
1532
|
-
required = true;
|
|
1533
|
-
}
|
|
1534
|
-
}
|
|
1535
|
-
} else if (node.required || node.hasAttribute('required')) {
|
|
1536
|
-
required = true;
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
if (astName === 'optional') {
|
|
1540
|
-
return !required;
|
|
1541
|
-
}
|
|
1542
|
-
return required;
|
|
1543
|
-
}
|
|
1544
|
-
/* Location pseudo-classes */
|
|
1545
|
-
case 'any-link':
|
|
1546
|
-
case 'link': {
|
|
1547
|
-
return (
|
|
1548
|
-
(localName === 'a' || localName === 'area') &&
|
|
1549
|
-
node.hasAttribute('href')
|
|
1550
|
-
);
|
|
1551
|
-
}
|
|
1552
|
-
case 'local-link': {
|
|
1553
|
-
if (
|
|
1554
|
-
(localName === 'a' || localName === 'area') &&
|
|
1555
|
-
node.hasAttribute('href')
|
|
1556
|
-
) {
|
|
1557
|
-
if (!this.#documentURL) {
|
|
1558
|
-
this.#documentURL = new URL(this.#document.URL);
|
|
1559
|
-
}
|
|
1560
|
-
const { href, origin, pathname } = this.#documentURL;
|
|
1561
|
-
const attrURL = new URL(node.getAttribute('href'), href);
|
|
1562
|
-
return attrURL.origin === origin && attrURL.pathname === pathname;
|
|
1563
|
-
}
|
|
1564
|
-
break;
|
|
1565
|
-
}
|
|
1566
|
-
case 'visited': {
|
|
1567
|
-
// prevent fingerprinting
|
|
1568
|
-
break;
|
|
1569
|
-
}
|
|
1570
|
-
case 'target': {
|
|
1571
|
-
if (!this.#documentURL) {
|
|
1572
|
-
this.#documentURL = new URL(this.#document.URL);
|
|
1573
|
-
}
|
|
1574
|
-
const { hash } = this.#documentURL;
|
|
1575
|
-
return hash && hash === `#${node.id}` && this.#document.contains(node);
|
|
1576
|
-
}
|
|
1577
|
-
case 'scope': {
|
|
1578
|
-
if (this.#node.nodeType === ELEMENT_NODE) {
|
|
1579
|
-
return !this.#shadow && node === this.#node;
|
|
1580
|
-
}
|
|
1581
|
-
return node === this.#document.documentElement;
|
|
1582
|
-
}
|
|
1583
|
-
/* Tree-structural pseudo-classes */
|
|
1584
|
-
case 'root': {
|
|
1585
|
-
return node === this.#document.documentElement;
|
|
1586
|
-
}
|
|
1587
|
-
case 'empty': {
|
|
1588
|
-
if (!node.hasChildNodes()) {
|
|
1589
|
-
return true;
|
|
1590
|
-
}
|
|
1591
|
-
const walker = this._createTreeWalker(node, {
|
|
1592
|
-
force: true,
|
|
1593
|
-
whatToShow: SHOW_ALL
|
|
1594
|
-
});
|
|
1595
|
-
let refNode = walker.firstChild();
|
|
1596
|
-
let bool;
|
|
1597
|
-
while (refNode) {
|
|
1598
|
-
bool =
|
|
1599
|
-
refNode.nodeType !== ELEMENT_NODE && refNode.nodeType !== TEXT_NODE;
|
|
1600
|
-
if (!bool) {
|
|
1601
|
-
break;
|
|
1602
|
-
}
|
|
1603
|
-
refNode = walker.nextSibling();
|
|
1604
|
-
}
|
|
1605
|
-
return bool;
|
|
1606
|
-
}
|
|
1607
|
-
case 'first-child':
|
|
1608
|
-
case 'last-child':
|
|
1609
|
-
case 'only-child': {
|
|
1610
|
-
if (!parentNode) {
|
|
1611
|
-
return node === this.#root;
|
|
1612
|
-
}
|
|
1613
|
-
if (astName === 'first-child') {
|
|
1614
|
-
return node === parentNode.firstElementChild;
|
|
1615
|
-
}
|
|
1616
|
-
if (astName === 'last-child') {
|
|
1617
|
-
return node === parentNode.lastElementChild;
|
|
1618
|
-
}
|
|
1619
|
-
return (
|
|
1620
|
-
node === parentNode.firstElementChild &&
|
|
1621
|
-
node === parentNode.lastElementChild
|
|
1622
|
-
);
|
|
1623
|
-
}
|
|
1624
|
-
/* User action pseudo-classes */
|
|
1625
|
-
case 'hover': {
|
|
1626
|
-
const { target, type } = this.#event ?? {};
|
|
1627
|
-
return (
|
|
1628
|
-
/^(?:click|mouse(?:down|over|up))$/.test(type) &&
|
|
1629
|
-
target?.nodeType === ELEMENT_NODE &&
|
|
1630
|
-
node.contains(target)
|
|
1631
|
-
);
|
|
1632
|
-
}
|
|
1633
|
-
case 'active': {
|
|
1634
|
-
const { buttons, target, type } = this.#event ?? {};
|
|
1635
|
-
return (
|
|
1636
|
-
type === 'mousedown' &&
|
|
1637
|
-
buttons & 1 &&
|
|
1638
|
-
target?.nodeType === ELEMENT_NODE &&
|
|
1639
|
-
node.contains(target)
|
|
1640
|
-
);
|
|
1641
|
-
}
|
|
1642
|
-
case 'focus': {
|
|
1643
|
-
const activeElement = this.#document.activeElement;
|
|
1644
|
-
if (activeElement.shadowRoot) {
|
|
1645
|
-
const activeShadowElement = activeElement.shadowRoot.activeElement;
|
|
1646
|
-
let current = activeShadowElement;
|
|
1647
|
-
while (current) {
|
|
1648
|
-
if (current.nodeType === DOCUMENT_FRAGMENT_NODE) {
|
|
1649
|
-
const { host } = current;
|
|
1650
|
-
if (host === activeElement) {
|
|
1651
|
-
if (isFocusableArea(node)) {
|
|
1652
|
-
return true;
|
|
1653
|
-
}
|
|
1654
|
-
return host === node;
|
|
1655
|
-
}
|
|
1656
|
-
}
|
|
1657
|
-
current = current.parentNode;
|
|
1658
|
-
}
|
|
1659
|
-
}
|
|
1660
|
-
return node === activeElement && isFocusableArea(node);
|
|
1661
|
-
}
|
|
1662
|
-
case 'focus-visible': {
|
|
1663
|
-
if (node === this.#document.activeElement && isFocusableArea(node)) {
|
|
1664
|
-
let bool;
|
|
1665
|
-
if (isFocusVisible(node)) {
|
|
1666
|
-
bool = true;
|
|
1667
|
-
} else if (this.#focus) {
|
|
1668
|
-
const { relatedTarget, target: focusTarget } = this.#focus;
|
|
1669
|
-
if (focusTarget === node) {
|
|
1670
|
-
if (isFocusVisible(relatedTarget)) {
|
|
1671
|
-
bool = true;
|
|
1672
|
-
} else if (this.#event) {
|
|
1673
|
-
const {
|
|
1674
|
-
altKey: eventAltKey,
|
|
1675
|
-
ctrlKey: eventCtrlKey,
|
|
1676
|
-
key: eventKey,
|
|
1677
|
-
metaKey: eventMetaKey,
|
|
1678
|
-
target: eventTarget,
|
|
1679
|
-
type: eventType
|
|
1680
|
-
} = this.#event;
|
|
1681
|
-
// this.#event is irrelevant if eventTarget === relatedTarget
|
|
1682
|
-
if (eventTarget === relatedTarget) {
|
|
1683
|
-
if (!this.#lastFocusVisible) {
|
|
1684
|
-
bool = true;
|
|
1685
|
-
} else if (focusTarget === this.#lastFocusVisible) {
|
|
1686
|
-
bool = true;
|
|
1687
|
-
}
|
|
1688
|
-
} else if (eventKey === 'Tab') {
|
|
1689
|
-
if (
|
|
1690
|
-
(eventType === 'keydown' && eventTarget !== node) ||
|
|
1691
|
-
(eventType === 'keyup' && eventTarget === node)
|
|
1692
|
-
) {
|
|
1693
|
-
if (eventTarget === focusTarget) {
|
|
1694
|
-
if (!this.#lastFocusVisible) {
|
|
1695
|
-
bool = true;
|
|
1696
|
-
} else if (
|
|
1697
|
-
eventTarget === this.#lastFocusVisible &&
|
|
1698
|
-
relatedTarget === null
|
|
1699
|
-
) {
|
|
1700
|
-
bool = true;
|
|
1701
|
-
}
|
|
1702
|
-
} else {
|
|
1703
|
-
bool = true;
|
|
1704
|
-
}
|
|
1705
|
-
}
|
|
1706
|
-
} else if (eventKey) {
|
|
1707
|
-
if (
|
|
1708
|
-
(eventType === 'keydown' || eventType === 'keyup') &&
|
|
1709
|
-
!eventAltKey &&
|
|
1710
|
-
!eventCtrlKey &&
|
|
1711
|
-
!eventMetaKey &&
|
|
1712
|
-
eventTarget === node
|
|
1713
|
-
) {
|
|
1714
|
-
bool = true;
|
|
1715
|
-
}
|
|
1716
|
-
}
|
|
1717
|
-
} else if (
|
|
1718
|
-
relatedTarget === null ||
|
|
1719
|
-
relatedTarget === this.#lastFocusVisible
|
|
1720
|
-
) {
|
|
1721
|
-
bool = true;
|
|
1722
|
-
}
|
|
1723
|
-
}
|
|
1724
|
-
}
|
|
1725
|
-
if (bool) {
|
|
1726
|
-
this.#lastFocusVisible = node;
|
|
1727
|
-
return bool;
|
|
1728
|
-
}
|
|
1729
|
-
if (this.#lastFocusVisible === node) {
|
|
1730
|
-
this.#lastFocusVisible = null;
|
|
1731
|
-
}
|
|
1732
|
-
}
|
|
1733
|
-
break;
|
|
1734
|
-
}
|
|
1735
|
-
case 'focus-within': {
|
|
1736
|
-
if (!this.#focusWithinCache) {
|
|
1737
|
-
this.#focusWithinCache = new Set();
|
|
1738
|
-
let currentFocus = this.#document.activeElement;
|
|
1739
|
-
if (currentFocus && isFocusableArea(currentFocus)) {
|
|
1740
|
-
while (currentFocus) {
|
|
1741
|
-
this.#focusWithinCache.add(currentFocus);
|
|
1742
|
-
if (currentFocus.parentNode) {
|
|
1743
|
-
currentFocus = currentFocus.parentNode;
|
|
1744
|
-
} else if (
|
|
1745
|
-
currentFocus.nodeType === DOCUMENT_FRAGMENT_NODE &&
|
|
1746
|
-
currentFocus.host
|
|
1747
|
-
) {
|
|
1748
|
-
currentFocus = currentFocus.host;
|
|
1749
|
-
} else {
|
|
1750
|
-
break;
|
|
1751
|
-
}
|
|
1752
|
-
}
|
|
1753
|
-
} else if (currentFocus && currentFocus.shadowRoot) {
|
|
1754
|
-
let shadowFocus = currentFocus.shadowRoot.activeElement;
|
|
1755
|
-
if (shadowFocus) {
|
|
1756
|
-
while (shadowFocus) {
|
|
1757
|
-
this.#focusWithinCache.add(shadowFocus);
|
|
1758
|
-
if (shadowFocus.parentNode) {
|
|
1759
|
-
shadowFocus = shadowFocus.parentNode;
|
|
1760
|
-
} else if (
|
|
1761
|
-
shadowFocus.nodeType === DOCUMENT_FRAGMENT_NODE &&
|
|
1762
|
-
shadowFocus.host
|
|
1763
|
-
) {
|
|
1764
|
-
shadowFocus = shadowFocus.host;
|
|
1765
|
-
} else {
|
|
1766
|
-
break;
|
|
1767
|
-
}
|
|
1768
|
-
}
|
|
1769
|
-
}
|
|
1770
|
-
}
|
|
1771
|
-
}
|
|
1772
|
-
return this.#focusWithinCache.has(node);
|
|
1773
|
-
}
|
|
1774
|
-
// Ignore :host.
|
|
1775
|
-
case 'host': {
|
|
1776
|
-
break;
|
|
1777
|
-
}
|
|
1778
|
-
// Legacy pseudo-elements.
|
|
1779
|
-
case 'after':
|
|
1780
|
-
case 'before':
|
|
1781
|
-
case 'first-letter':
|
|
1782
|
-
case 'first-line': {
|
|
1783
|
-
if (warn) {
|
|
1784
|
-
this.onError(
|
|
1785
|
-
generateException(
|
|
1786
|
-
`Unsupported pseudo-element ::${astName}`,
|
|
1787
|
-
NOT_SUPPORTED_ERR,
|
|
1788
|
-
this.#window
|
|
1789
|
-
)
|
|
1790
|
-
);
|
|
1791
|
-
}
|
|
1792
|
-
break;
|
|
1793
|
-
}
|
|
1794
|
-
// Not supported.
|
|
1795
|
-
case 'autofill':
|
|
1796
|
-
case 'blank':
|
|
1797
|
-
case 'buffering':
|
|
1798
|
-
case 'current':
|
|
1799
|
-
case 'fullscreen':
|
|
1800
|
-
case 'future':
|
|
1801
|
-
case 'has-slotted':
|
|
1802
|
-
case 'heading':
|
|
1803
|
-
case 'modal':
|
|
1804
|
-
case 'muted':
|
|
1805
|
-
case 'past':
|
|
1806
|
-
case 'paused':
|
|
1807
|
-
case 'picture-in-picture':
|
|
1808
|
-
case 'playing':
|
|
1809
|
-
case 'seeking':
|
|
1810
|
-
case 'stalled':
|
|
1811
|
-
case 'user-invalid':
|
|
1812
|
-
case 'user-valid':
|
|
1813
|
-
case 'volume-locked':
|
|
1814
|
-
case '-webkit-autofill': {
|
|
1815
|
-
if (warn) {
|
|
1816
|
-
this.onError(
|
|
1817
|
-
generateException(
|
|
1818
|
-
`Unsupported pseudo-class :${astName}`,
|
|
1819
|
-
NOT_SUPPORTED_ERR,
|
|
1820
|
-
this.#window
|
|
1821
|
-
)
|
|
1822
|
-
);
|
|
1823
|
-
}
|
|
1824
|
-
break;
|
|
1825
|
-
}
|
|
1826
|
-
default: {
|
|
1827
|
-
if (astName.startsWith('-webkit-')) {
|
|
1828
|
-
if (warn) {
|
|
1829
|
-
this.onError(
|
|
1830
|
-
generateException(
|
|
1831
|
-
`Unsupported pseudo-class :${astName}`,
|
|
1832
|
-
NOT_SUPPORTED_ERR,
|
|
1833
|
-
this.#window
|
|
1834
|
-
)
|
|
1835
|
-
);
|
|
1836
|
-
}
|
|
1837
|
-
} else if (!forgive) {
|
|
1838
|
-
this.onError(
|
|
1839
|
-
generateException(
|
|
1840
|
-
`Unknown pseudo-class :${astName}`,
|
|
1841
|
-
SYNTAX_ERR,
|
|
1842
|
-
this.#window
|
|
1843
|
-
)
|
|
1844
|
-
);
|
|
1845
|
-
}
|
|
1846
|
-
}
|
|
1847
|
-
}
|
|
1848
|
-
return false;
|
|
1849
|
-
}
|
|
1850
|
-
|
|
1851
|
-
/**
|
|
1852
|
-
* Evaluates the :host() pseudo-class.
|
|
1853
|
-
* @private
|
|
1854
|
-
* @param {Array.<object>} leaves - The AST leaves.
|
|
1855
|
-
* @param {object} host - The host element.
|
|
1856
|
-
* @param {object} ast - The original AST for error reporting.
|
|
1857
|
-
* @returns {boolean} True if matches, otherwise false.
|
|
1858
|
-
*/
|
|
1859
|
-
_evaluateHostPseudo = (leaves, host, ast) => {
|
|
1860
|
-
const l = leaves.length;
|
|
1861
|
-
for (let i = 0; i < l; i++) {
|
|
1862
|
-
const leaf = leaves[i];
|
|
1863
|
-
if (leaf.type === COMBINATOR) {
|
|
1864
|
-
const css = generateCSS(ast);
|
|
1865
|
-
const msg = `Invalid selector ${css}`;
|
|
1866
|
-
this.onError(generateException(msg, SYNTAX_ERR, this.#window));
|
|
1867
|
-
return false;
|
|
1868
|
-
}
|
|
1869
|
-
if (!this._matchSelector(leaf, host)) {
|
|
1870
|
-
return false;
|
|
1871
|
-
}
|
|
1872
|
-
}
|
|
1873
|
-
return true;
|
|
1874
|
-
};
|
|
1875
|
-
|
|
1876
|
-
/**
|
|
1877
|
-
* Evaluates the :host-context() pseudo-class.
|
|
1878
|
-
* @private
|
|
1879
|
-
* @param {Array.<object>} leaves - The AST leaves.
|
|
1880
|
-
* @param {object} host - The host element.
|
|
1881
|
-
* @param {object} ast - The original AST for error reporting.
|
|
1882
|
-
* @returns {boolean} True if matched.
|
|
1883
|
-
*/
|
|
1884
|
-
_evaluateHostContextPseudo = (leaves, host, ast) => {
|
|
1885
|
-
let parent = host;
|
|
1886
|
-
while (parent) {
|
|
1887
|
-
let bool;
|
|
1888
|
-
const l = leaves.length;
|
|
1889
|
-
for (let i = 0; i < l; i++) {
|
|
1890
|
-
const leaf = leaves[i];
|
|
1891
|
-
if (leaf.type === COMBINATOR) {
|
|
1892
|
-
const css = generateCSS(ast);
|
|
1893
|
-
const msg = `Invalid selector ${css}`;
|
|
1894
|
-
this.onError(generateException(msg, SYNTAX_ERR, this.#window));
|
|
1895
|
-
return false;
|
|
1896
|
-
}
|
|
1897
|
-
bool = this._matchSelector(leaf, parent);
|
|
1898
|
-
if (!bool) {
|
|
1899
|
-
break;
|
|
1900
|
-
}
|
|
1901
|
-
}
|
|
1902
|
-
if (bool) {
|
|
1903
|
-
return true;
|
|
1904
|
-
}
|
|
1905
|
-
parent = parent.parentNode;
|
|
1906
|
-
}
|
|
1907
|
-
return false;
|
|
1908
|
-
};
|
|
1909
|
-
|
|
1910
|
-
/**
|
|
1911
|
-
* Evaluates shadow host pseudo-classes.
|
|
1912
|
-
* @private
|
|
1913
|
-
* @param {object} ast - The AST.
|
|
1914
|
-
* @param {object} node - The DocumentFragment node.
|
|
1915
|
-
* @returns {boolean} True if matches, otherwise false.
|
|
1916
|
-
*/
|
|
1917
|
-
_evaluateShadowHost = (ast, node) => {
|
|
1918
|
-
const { children: astChildren, name: astName } = ast;
|
|
1919
|
-
// Handle simple pseudo-class (no arguments).
|
|
1920
|
-
if (!Array.isArray(astChildren)) {
|
|
1921
|
-
if (astName === 'host') {
|
|
1922
|
-
return true;
|
|
1923
|
-
}
|
|
1924
|
-
const msg = `Invalid selector :${astName}`;
|
|
1925
|
-
this.onError(generateException(msg, SYNTAX_ERR, this.#window));
|
|
1926
|
-
return false;
|
|
1927
|
-
}
|
|
1928
|
-
// Handle functional pseudo-class like :host(...).
|
|
1929
|
-
if (astName !== 'host' && astName !== 'host-context') {
|
|
1930
|
-
const msg = `Invalid selector :${astName}()`;
|
|
1931
|
-
this.onError(generateException(msg, SYNTAX_ERR, this.#window));
|
|
1932
|
-
return false;
|
|
1933
|
-
}
|
|
1934
|
-
if (astChildren.length !== 1) {
|
|
1935
|
-
const css = generateCSS(ast);
|
|
1936
|
-
const msg = `Invalid selector ${css}`;
|
|
1937
|
-
this.onError(generateException(msg, SYNTAX_ERR, this.#window));
|
|
1938
|
-
return false;
|
|
1939
|
-
}
|
|
1940
|
-
const { host } = node;
|
|
1941
|
-
const { branches } = walkAST(astChildren[0]);
|
|
1942
|
-
const [branch] = branches;
|
|
1943
|
-
const [...leaves] = branch;
|
|
1944
|
-
if (astName === 'host' && this._evaluateHostPseudo(leaves, host, ast)) {
|
|
1945
|
-
return true;
|
|
1946
|
-
} else if (
|
|
1947
|
-
astName === 'host-context' &&
|
|
1948
|
-
this._evaluateHostContextPseudo(leaves, host, ast)
|
|
1949
|
-
) {
|
|
1950
|
-
return true;
|
|
1951
|
-
}
|
|
1952
|
-
return false;
|
|
1953
|
-
};
|
|
1954
|
-
|
|
1955
|
-
/**
|
|
1956
|
-
* Matches a selector for element nodes.
|
|
1957
|
-
* @private
|
|
1958
|
-
* @param {object} ast - The AST.
|
|
1959
|
-
* @param {object} node - The Element node.
|
|
1960
|
-
* @param {object} opt - Options.
|
|
1961
|
-
* @returns {boolean} True if matches, otherwise false.
|
|
1962
|
-
*/
|
|
1963
|
-
_matchSelectorForElement = (ast, node, opt) => {
|
|
1964
|
-
const { type: astType } = ast;
|
|
1965
|
-
const astName = unescapeSelector(ast.name);
|
|
1966
|
-
switch (astType) {
|
|
1967
|
-
case ATTR_SELECTOR: {
|
|
1968
|
-
return matchAttributeSelector(ast, node, opt);
|
|
1969
|
-
}
|
|
1970
|
-
case ID_SELECTOR: {
|
|
1971
|
-
return node.id === astName;
|
|
1972
|
-
}
|
|
1973
|
-
case CLASS_SELECTOR: {
|
|
1974
|
-
return node.classList.contains(astName);
|
|
1975
|
-
}
|
|
1976
|
-
case PS_CLASS_SELECTOR: {
|
|
1977
|
-
return this._matchPseudoClassSelector(ast, node, opt);
|
|
1978
|
-
}
|
|
1979
|
-
case TYPE_SELECTOR: {
|
|
1980
|
-
return matchTypeSelector(ast, node, opt);
|
|
1981
|
-
}
|
|
1982
|
-
// PS_ELEMENT_SELECTOR is handled by default.
|
|
1983
|
-
default: {
|
|
1984
|
-
try {
|
|
1985
|
-
if (this.#check) {
|
|
1986
|
-
const css = generateCSS(ast);
|
|
1987
|
-
this.#pseudoElement.push(css);
|
|
1988
|
-
return true;
|
|
1989
|
-
} else {
|
|
1990
|
-
matchPseudoElementSelector(astName, astType, opt);
|
|
1991
|
-
}
|
|
1992
|
-
} catch (e) {
|
|
1993
|
-
this.onError(e);
|
|
1994
|
-
}
|
|
1995
|
-
}
|
|
1996
|
-
}
|
|
1997
|
-
return false;
|
|
1998
|
-
};
|
|
1999
|
-
|
|
2000
|
-
/**
|
|
2001
|
-
* Matches a selector for a shadow root.
|
|
2002
|
-
* @private
|
|
2003
|
-
* @param {object} ast - The AST.
|
|
2004
|
-
* @param {object} node - The DocumentFragment node.
|
|
2005
|
-
* @param {object} [opt] - Options.
|
|
2006
|
-
* @returns {boolean} True if matches, otherwise false.
|
|
2007
|
-
*/
|
|
2008
|
-
_matchSelectorForShadowRoot = (ast, node, opt = {}) => {
|
|
2009
|
-
const { name: astName } = ast;
|
|
2010
|
-
if (KEYS_LOGICAL.has(astName)) {
|
|
2011
|
-
opt.isShadowRoot = true;
|
|
2012
|
-
return this._matchPseudoClassSelector(ast, node, opt);
|
|
2013
|
-
}
|
|
2014
|
-
if (astName === 'host' || astName === 'host-context') {
|
|
2015
|
-
const matches = this._evaluateShadowHost(ast, node, opt);
|
|
2016
|
-
if (matches) {
|
|
2017
|
-
this.#verifyShadowHost = true;
|
|
2018
|
-
return true;
|
|
2019
|
-
}
|
|
2020
|
-
}
|
|
2021
|
-
return false;
|
|
2022
|
-
};
|
|
2023
|
-
|
|
2024
|
-
/**
|
|
2025
|
-
* Matches a selector.
|
|
2026
|
-
* @private
|
|
2027
|
-
* @param {object} ast - The AST.
|
|
2028
|
-
* @param {object} node - The Document, DocumentFragment, or Element node.
|
|
2029
|
-
* @param {object} opt - Options.
|
|
2030
|
-
* @returns {boolean} True if matches, otherwise false.
|
|
2031
|
-
*/
|
|
2032
|
-
_matchSelector = (ast, node, opt) => {
|
|
2033
|
-
if (node.nodeType === ELEMENT_NODE) {
|
|
2034
|
-
return this._matchSelectorForElement(ast, node, opt);
|
|
2035
|
-
}
|
|
2036
|
-
if (
|
|
2037
|
-
this.#shadow &&
|
|
2038
|
-
node.nodeType === DOCUMENT_FRAGMENT_NODE &&
|
|
2039
|
-
ast.type === PS_CLASS_SELECTOR
|
|
2040
|
-
) {
|
|
2041
|
-
return this._matchSelectorForShadowRoot(ast, node, opt);
|
|
2042
|
-
}
|
|
2043
|
-
return false;
|
|
2044
|
-
};
|
|
2045
|
-
|
|
2046
|
-
/**
|
|
2047
|
-
* Matches leaves.
|
|
2048
|
-
* @private
|
|
2049
|
-
* @param {Array.<object>} leaves - The AST leaves.
|
|
2050
|
-
* @param {object} node - The node.
|
|
2051
|
-
* @param {object} opt - Options.
|
|
2052
|
-
* @returns {boolean} The result.
|
|
2053
|
-
*/
|
|
2054
|
-
_matchLeaves = (leaves, node, opt) => {
|
|
2055
|
-
if (!this.#invalidateResults) {
|
|
2056
|
-
this.#invalidateResults = new WeakMap();
|
|
2057
|
-
}
|
|
2058
|
-
const results = this.#invalidate ? this.#invalidateResults : this.#results;
|
|
2059
|
-
let result = results.get(leaves);
|
|
2060
|
-
if (result && result.has(node)) {
|
|
2061
|
-
const { matched } = result.get(node);
|
|
2062
|
-
return matched;
|
|
2063
|
-
}
|
|
2064
|
-
let cacheable = true;
|
|
2065
|
-
if (node.nodeType === ELEMENT_NODE && KEYS_FORM.has(node.localName)) {
|
|
2066
|
-
cacheable = false;
|
|
2067
|
-
}
|
|
2068
|
-
let bool;
|
|
2069
|
-
const l = leaves.length;
|
|
2070
|
-
for (let i = 0; i < l; i++) {
|
|
2071
|
-
const leaf = leaves[i];
|
|
2072
|
-
switch (leaf.type) {
|
|
2073
|
-
case ATTR_SELECTOR:
|
|
2074
|
-
case ID_SELECTOR: {
|
|
2075
|
-
cacheable = false;
|
|
2076
|
-
break;
|
|
2077
|
-
}
|
|
2078
|
-
case PS_CLASS_SELECTOR: {
|
|
2079
|
-
if (KEYS_PS_UNCACHE.has(leaf.name)) {
|
|
2080
|
-
cacheable = false;
|
|
2081
|
-
}
|
|
2082
|
-
break;
|
|
2083
|
-
}
|
|
2084
|
-
default: {
|
|
2085
|
-
// No action needed for other types.
|
|
2086
|
-
}
|
|
2087
|
-
}
|
|
2088
|
-
bool = this._matchSelector(leaf, node, opt);
|
|
2089
|
-
if (!bool) {
|
|
2090
|
-
break;
|
|
2091
|
-
}
|
|
2092
|
-
}
|
|
2093
|
-
if (cacheable) {
|
|
2094
|
-
if (!result) {
|
|
2095
|
-
result = new WeakMap();
|
|
2096
|
-
}
|
|
2097
|
-
result.set(node, {
|
|
2098
|
-
matched: bool
|
|
2099
|
-
});
|
|
2100
|
-
results.set(leaves, result);
|
|
2101
|
-
}
|
|
2102
|
-
return bool;
|
|
2103
|
-
};
|
|
2104
|
-
|
|
2105
|
-
/**
|
|
2106
|
-
* Returns a cached slice of the leaves array (excluding the first item).
|
|
2107
|
-
* @private
|
|
2108
|
-
* @param {Array.<object>} leaves - The original AST leaves array.
|
|
2109
|
-
* @returns {Array.<object>} The filtered leaves.
|
|
2110
|
-
*/
|
|
2111
|
-
_getFilterLeaves = leaves => {
|
|
2112
|
-
if (!this.#filterLeavesCache) {
|
|
2113
|
-
this.#filterLeavesCache = new WeakMap();
|
|
2114
|
-
}
|
|
2115
|
-
if (this.#filterLeavesCache.has(leaves)) {
|
|
2116
|
-
return this.#filterLeavesCache.get(leaves);
|
|
2117
|
-
}
|
|
2118
|
-
const filterLeaves = leaves.slice(1);
|
|
2119
|
-
this.#filterLeavesCache.set(leaves, filterLeaves);
|
|
2120
|
-
return filterLeaves;
|
|
2121
|
-
};
|
|
2122
|
-
|
|
2123
|
-
/**
|
|
2124
|
-
* Traverses all descendant nodes and collects matches.
|
|
2125
|
-
* @private
|
|
2126
|
-
* @param {object} baseNode - The base Element node or Element.shadowRoot.
|
|
2127
|
-
* @param {Array.<object>} leaves - The AST leaves.
|
|
2128
|
-
* @param {object} opt - Options.
|
|
2129
|
-
* @returns {Set.<object>} A collection of matched nodes.
|
|
2130
|
-
*/
|
|
2131
|
-
_traverseAllDescendants = (baseNode, leaves, opt) => {
|
|
2132
|
-
const walker = this._createTreeWalker(baseNode);
|
|
2133
|
-
traverseNode(baseNode, walker);
|
|
2134
|
-
let currentNode = walker.firstChild();
|
|
2135
|
-
const nodes = new Set();
|
|
2136
|
-
while (currentNode) {
|
|
2137
|
-
if (this._matchLeaves(leaves, currentNode, opt)) {
|
|
2138
|
-
nodes.add(currentNode);
|
|
2139
|
-
}
|
|
2140
|
-
currentNode = walker.nextNode();
|
|
2141
|
-
}
|
|
2142
|
-
return nodes;
|
|
2143
|
-
};
|
|
59
|
+
*/
|
|
60
|
+
setup(selector, node, opt = {}) {
|
|
61
|
+
super.setup(selector, node, opt);
|
|
62
|
+
this.#ast = null;
|
|
63
|
+
this.#nodes = null;
|
|
64
|
+
this.#scoped =
|
|
65
|
+
this.node !== this.root && this.node.nodeType === ELEMENT_NODE;
|
|
66
|
+
this.#selector = selector;
|
|
67
|
+
this.#selectorAST = null;
|
|
68
|
+
this.#nodeWalker = null;
|
|
69
|
+
this.#rootWalker = null;
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
2144
72
|
|
|
2145
73
|
/**
|
|
2146
|
-
*
|
|
74
|
+
* Processes selector branches into the internal AST structure.
|
|
2147
75
|
* @private
|
|
2148
|
-
* @param {Array.<object>}
|
|
2149
|
-
* @param {
|
|
2150
|
-
* @
|
|
2151
|
-
* @returns {Set.<object>} A collection of matched nodes.
|
|
76
|
+
* @param {Array.<object>} branches - The selector branches to process.
|
|
77
|
+
* @param {string} selector - The CSS selector string.
|
|
78
|
+
* @returns {object} Object containing ast and descendant flags.
|
|
2152
79
|
*/
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
const
|
|
2156
|
-
const
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
const
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
if (
|
|
2174
|
-
!isCompoundSelector ||
|
|
2175
|
-
this._matchLeaves(filterLeaves, foundNode, opt)
|
|
2176
|
-
) {
|
|
2177
|
-
nodes.add(foundNode);
|
|
80
|
+
_processSelectorBranches = (branches, selector) => {
|
|
81
|
+
let descendant = false;
|
|
82
|
+
const ast = [];
|
|
83
|
+
for (const items of branches) {
|
|
84
|
+
const branch = [];
|
|
85
|
+
let prevType = null;
|
|
86
|
+
const itemsLen = items.length;
|
|
87
|
+
if (itemsLen) {
|
|
88
|
+
const leaves = new Set();
|
|
89
|
+
for (let j = 0; j < itemsLen; j++) {
|
|
90
|
+
const item = items[j];
|
|
91
|
+
const isLast = j === itemsLen - 1;
|
|
92
|
+
if (isInvalidCombinator(item.type, prevType, isLast)) {
|
|
93
|
+
const msg = `Invalid selector ${selector}`;
|
|
94
|
+
this.onError(generateException(msg, SYNTAX_ERR, this.window));
|
|
95
|
+
return { ast: [], descendant: false, invalidate: false };
|
|
96
|
+
}
|
|
97
|
+
if (item.type === COMBINATOR) {
|
|
98
|
+
if (item.name === ' ' || item.name === '>') {
|
|
99
|
+
descendant = true;
|
|
2178
100
|
}
|
|
101
|
+
branch.push({ combo: item, leaves: sortAST(leaves) });
|
|
102
|
+
leaves.clear();
|
|
103
|
+
} else {
|
|
104
|
+
if (item.name && typeof item.name === 'string') {
|
|
105
|
+
const unescapedName = unescapeSelector(item.name);
|
|
106
|
+
if (unescapedName !== item.name) {
|
|
107
|
+
item.name = unescapedName;
|
|
108
|
+
}
|
|
109
|
+
if (/[|:]/.test(unescapedName)) {
|
|
110
|
+
item.namespace = true;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
leaves.add(item);
|
|
114
|
+
}
|
|
115
|
+
prevType = item.type;
|
|
116
|
+
if (isLast) {
|
|
117
|
+
branch.push({ combo: null, leaves: sortAST(leaves) });
|
|
118
|
+
leaves.clear();
|
|
2179
119
|
}
|
|
2180
|
-
return nodes;
|
|
2181
120
|
}
|
|
2182
|
-
// Fallback to default traversal if fast path is not applicable.
|
|
2183
|
-
return this._traverseAllDescendants(baseNode, leaves, opt);
|
|
2184
|
-
}
|
|
2185
|
-
case PS_ELEMENT_SELECTOR: {
|
|
2186
|
-
const leafName = unescapeSelector(leaf.name);
|
|
2187
|
-
matchPseudoElementSelector(leafName, leafType, opt);
|
|
2188
|
-
return new Set();
|
|
2189
|
-
}
|
|
2190
|
-
default: {
|
|
2191
|
-
return this._traverseAllDescendants(baseNode, leaves, opt);
|
|
2192
121
|
}
|
|
122
|
+
ast.push({ branch, dir: null, filtered: false, find: false });
|
|
2193
123
|
}
|
|
124
|
+
return { ast, descendant };
|
|
2194
125
|
};
|
|
2195
126
|
|
|
2196
127
|
/**
|
|
2197
|
-
*
|
|
128
|
+
* Corresponds AST and DOM nodes for the given selector.
|
|
2198
129
|
* @private
|
|
2199
|
-
* @param {
|
|
2200
|
-
* @
|
|
2201
|
-
* @param {object} [opt] - Options.
|
|
2202
|
-
* @param {string} [opt.dir] - The find direction.
|
|
2203
|
-
* @param {Array.<object>} matched - The collector array.
|
|
2204
|
-
* @returns {Array.<object>} The collector array.
|
|
130
|
+
* @param {string} selector - The CSS selector string.
|
|
131
|
+
* @returns {Array} An array containing the AST and empty nodes array.
|
|
2205
132
|
*/
|
|
2206
|
-
|
|
2207
|
-
const
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
const
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
matched.push(refNode);
|
|
2220
|
-
}
|
|
2221
|
-
break;
|
|
133
|
+
_correspond = selector => {
|
|
134
|
+
const nodes = [];
|
|
135
|
+
let descendant = false;
|
|
136
|
+
this.invalidate = false;
|
|
137
|
+
let ast;
|
|
138
|
+
if (this.documentCache.has(this.document)) {
|
|
139
|
+
const cachedItem = this.documentCache.get(this.document);
|
|
140
|
+
if (cachedItem && cachedItem.has(`${selector}`)) {
|
|
141
|
+
const item = cachedItem.get(`${selector}`);
|
|
142
|
+
ast = item.ast;
|
|
143
|
+
descendant = item.descendant;
|
|
144
|
+
this.invalidate = item.invalidate;
|
|
145
|
+
this.#selectorAST = item.selectorAST;
|
|
2222
146
|
}
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
}
|
|
2232
|
-
refNode =
|
|
2233
|
-
dir === DIR_NEXT
|
|
2234
|
-
? refNode.nextElementSibling
|
|
2235
|
-
: refNode.previousElementSibling;
|
|
2236
|
-
}
|
|
2237
|
-
break;
|
|
147
|
+
}
|
|
148
|
+
if (ast) {
|
|
149
|
+
const l = ast.length;
|
|
150
|
+
for (let i = 0; i < l; i++) {
|
|
151
|
+
ast[i].dir = null;
|
|
152
|
+
ast[i].filtered = false;
|
|
153
|
+
ast[i].find = false;
|
|
154
|
+
nodes[i] = [];
|
|
2238
155
|
}
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
156
|
+
} else {
|
|
157
|
+
this.#selectorAST = parseSelector(selector);
|
|
158
|
+
const { branches, info } = walkAST(
|
|
159
|
+
this.#selectorAST,
|
|
160
|
+
true,
|
|
161
|
+
createHasValidator(this.window)
|
|
162
|
+
);
|
|
163
|
+
const {
|
|
164
|
+
hasHasPseudoFunc,
|
|
165
|
+
hasLogicalPseudoFunc,
|
|
166
|
+
hasNthChildOfSelector,
|
|
167
|
+
hasStatePseudoClass,
|
|
168
|
+
hasUnsupportedPseudoClass
|
|
169
|
+
} = info;
|
|
170
|
+
this.invalidate =
|
|
171
|
+
hasHasPseudoFunc ||
|
|
172
|
+
hasStatePseudoClass ||
|
|
173
|
+
hasUnsupportedPseudoClass ||
|
|
174
|
+
!!(hasLogicalPseudoFunc && hasNthChildOfSelector);
|
|
175
|
+
const processed = this._processSelectorBranches(branches, selector);
|
|
176
|
+
ast = processed.ast;
|
|
177
|
+
descendant = processed.descendant;
|
|
178
|
+
let cachedItem;
|
|
179
|
+
if (this.documentCache.has(this.document)) {
|
|
180
|
+
cachedItem = this.documentCache.get(this.document);
|
|
181
|
+
} else {
|
|
182
|
+
cachedItem = new Map();
|
|
2255
183
|
}
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
while (refNode) {
|
|
2266
|
-
if (this._matchLeaves(leaves, refNode, opt)) {
|
|
2267
|
-
ancestors.push(refNode);
|
|
2268
|
-
}
|
|
2269
|
-
refNode = refNode.parentNode;
|
|
2270
|
-
}
|
|
2271
|
-
if (ancestors.length) {
|
|
2272
|
-
matched.push(...ancestors.reverse());
|
|
2273
|
-
}
|
|
2274
|
-
}
|
|
184
|
+
cachedItem.set(`${selector}`, {
|
|
185
|
+
ast,
|
|
186
|
+
descendant,
|
|
187
|
+
invalidate: this.invalidate,
|
|
188
|
+
selectorAST: this.#selectorAST
|
|
189
|
+
});
|
|
190
|
+
this.documentCache.set(this.document, cachedItem);
|
|
191
|
+
for (let i = 0; i < ast.length; i++) {
|
|
192
|
+
nodes[i] = [];
|
|
2275
193
|
}
|
|
2276
194
|
}
|
|
2277
|
-
return
|
|
195
|
+
return [ast, nodes];
|
|
2278
196
|
};
|
|
2279
197
|
|
|
2280
198
|
/**
|
|
2281
|
-
*
|
|
2282
|
-
* @private
|
|
2283
|
-
* @param {object} twig - The twig object.
|
|
2284
|
-
* @param {object} node - The Element node.
|
|
2285
|
-
* @param {object} opt - Options.
|
|
2286
|
-
* @returns {Set.<object>} A collection of matched nodes.
|
|
2287
|
-
*/
|
|
2288
|
-
_matchCombinator = (twig, node, opt) =>
|
|
2289
|
-
new Set(this._collectCombinatorMatches(twig, node, opt));
|
|
2290
|
-
|
|
2291
|
-
/**
|
|
2292
|
-
* Traverses with a TreeWalker and collects nodes matching the leaves.
|
|
199
|
+
* Traverses and collects nodes matching leaves.
|
|
2293
200
|
* @private
|
|
2294
|
-
* @param {
|
|
2295
|
-
* @param {Array} leaves - The AST leaves to match
|
|
2296
|
-
* @param {object} [opt] -
|
|
2297
|
-
* @
|
|
2298
|
-
* @param {boolean} [opt.force] - Force traversal to the next node.
|
|
2299
|
-
* @param {Node} [opt.startNode] - The node to start traversal from.
|
|
2300
|
-
* @param {string} [opt.targetType] - The type of target ('all' or 'first').
|
|
2301
|
-
* @returns {Array.<Node>} An array of matched nodes.
|
|
201
|
+
* @param {object} walker - The TreeWalker instance.
|
|
202
|
+
* @param {Array.<object>} leaves - The AST leaves to match.
|
|
203
|
+
* @param {object} [opt] - Options for traversal.
|
|
204
|
+
* @returns {Array.<object>} An array of collected nodes.
|
|
2302
205
|
*/
|
|
2303
206
|
_traverseAndCollectNodes = (walker, leaves, opt = {}) => {
|
|
2304
207
|
const { boundaryNode, force, startNode, targetType } = opt;
|
|
@@ -2310,14 +213,10 @@ export class Finder {
|
|
|
2310
213
|
// Adjust starting node.
|
|
2311
214
|
if (currentNode.nodeType !== ELEMENT_NODE) {
|
|
2312
215
|
currentNode = walker.nextNode();
|
|
2313
|
-
} else if (currentNode === startNode && currentNode !== this
|
|
216
|
+
} else if (currentNode === startNode && currentNode !== this.root) {
|
|
2314
217
|
currentNode = walker.nextNode();
|
|
2315
218
|
}
|
|
2316
|
-
const matchOpt = {
|
|
2317
|
-
warn: this.#warn
|
|
2318
|
-
};
|
|
2319
219
|
while (currentNode) {
|
|
2320
|
-
// Stop when we reach the boundary.
|
|
2321
220
|
if (boundaryNode) {
|
|
2322
221
|
if (currentNode === boundaryNode) {
|
|
2323
222
|
break;
|
|
@@ -2329,11 +228,10 @@ export class Finder {
|
|
|
2329
228
|
}
|
|
2330
229
|
}
|
|
2331
230
|
if (
|
|
2332
|
-
this.
|
|
2333
|
-
currentNode !== this
|
|
231
|
+
this.matchLeaves(leaves, currentNode, this.matchOpts) &&
|
|
232
|
+
currentNode !== this.node
|
|
2334
233
|
) {
|
|
2335
234
|
collectedNodes.push(currentNode);
|
|
2336
|
-
// Stop after the first match if not collecting all.
|
|
2337
235
|
if (targetType !== TARGET_ALL) {
|
|
2338
236
|
break;
|
|
2339
237
|
}
|
|
@@ -2344,47 +242,44 @@ export class Finder {
|
|
|
2344
242
|
};
|
|
2345
243
|
|
|
2346
244
|
/**
|
|
2347
|
-
* Finds
|
|
245
|
+
* Finds matching nodes preceding the current node.
|
|
2348
246
|
* @private
|
|
2349
|
-
* @param {Array.<object>} leaves - The AST leaves.
|
|
2350
|
-
* @param {object} node - The node
|
|
2351
|
-
* @param {object} [opt] - Options.
|
|
2352
|
-
* @
|
|
2353
|
-
* @param {string} [opt.targetType] - The target type.
|
|
2354
|
-
* @returns {Array.<object>} A collection of matched nodes.
|
|
247
|
+
* @param {Array.<object>} leaves - The AST leaves to match.
|
|
248
|
+
* @param {object} node - The starting node.
|
|
249
|
+
* @param {object} [opt] - Options for finding.
|
|
250
|
+
* @returns {Array.<object>} An array of matched nodes.
|
|
2355
251
|
*/
|
|
2356
252
|
_findPrecede = (leaves, node, opt = {}) => {
|
|
2357
253
|
const { force, targetType } = opt;
|
|
2358
254
|
if (!this.#rootWalker) {
|
|
2359
|
-
this.#rootWalker = this.
|
|
255
|
+
this.#rootWalker = this.createTreeWalker(this.root);
|
|
2360
256
|
}
|
|
2361
257
|
return this._traverseAndCollectNodes(this.#rootWalker, leaves, {
|
|
2362
258
|
force,
|
|
2363
259
|
targetType,
|
|
2364
|
-
boundaryNode: this
|
|
260
|
+
boundaryNode: this.node,
|
|
2365
261
|
startNode: node
|
|
2366
262
|
});
|
|
2367
263
|
};
|
|
2368
264
|
|
|
2369
265
|
/**
|
|
2370
|
-
* Finds
|
|
266
|
+
* Finds matching nodes using TreeWalker.
|
|
2371
267
|
* @private
|
|
2372
268
|
* @param {Array.<object>} leaves - The AST leaves.
|
|
2373
|
-
* @param {object} node - The node
|
|
2374
|
-
* @param {object} [opt] -
|
|
2375
|
-
* @
|
|
2376
|
-
* @returns {Array.<object>} A collection of matched nodes.
|
|
269
|
+
* @param {object} node - The starting node.
|
|
270
|
+
* @param {object} [opt] - Traversal options.
|
|
271
|
+
* @returns {Array.<object>} An array of matched nodes.
|
|
2377
272
|
*/
|
|
2378
273
|
_findNodeWalker = (leaves, node, opt = {}) => {
|
|
2379
274
|
const { precede, ...traversalOpts } = opt;
|
|
2380
275
|
if (precede) {
|
|
2381
|
-
const precedeNodes = this._findPrecede(leaves, this
|
|
276
|
+
const precedeNodes = this._findPrecede(leaves, this.root, opt);
|
|
2382
277
|
if (precedeNodes.length) {
|
|
2383
278
|
return precedeNodes;
|
|
2384
279
|
}
|
|
2385
280
|
}
|
|
2386
281
|
if (!this.#nodeWalker) {
|
|
2387
|
-
this.#nodeWalker = this.
|
|
282
|
+
this.#nodeWalker = this.createTreeWalker(this.node);
|
|
2388
283
|
}
|
|
2389
284
|
return this._traverseAndCollectNodes(this.#nodeWalker, leaves, {
|
|
2390
285
|
...traversalOpts,
|
|
@@ -2393,40 +288,38 @@ export class Finder {
|
|
|
2393
288
|
};
|
|
2394
289
|
|
|
2395
290
|
/**
|
|
2396
|
-
* Matches the node itself.
|
|
291
|
+
* Matches the current node itself against leaves.
|
|
2397
292
|
* @private
|
|
2398
|
-
* @param {Array} leaves - The AST leaves.
|
|
2399
|
-
* @returns {Array}
|
|
293
|
+
* @param {Array.<object>} leaves - The AST leaves.
|
|
294
|
+
* @returns {Array} Array with nodes, match boolean, and pseudo-elements.
|
|
2400
295
|
*/
|
|
2401
296
|
_matchSelf = leaves => {
|
|
2402
|
-
const matched = this.
|
|
2403
|
-
check: this
|
|
2404
|
-
warn: this
|
|
297
|
+
const matched = this.matchLeaves(leaves, this.node, {
|
|
298
|
+
check: this.check,
|
|
299
|
+
warn: this.warn
|
|
2405
300
|
});
|
|
2406
|
-
const nodes = matched ? [this
|
|
2407
|
-
return [nodes, matched, this
|
|
301
|
+
const nodes = matched ? [this.node] : [];
|
|
302
|
+
return [nodes, matched, this.pseudoElements];
|
|
2408
303
|
};
|
|
2409
304
|
|
|
2410
305
|
/**
|
|
2411
|
-
* Finds lineal nodes (self and ancestors).
|
|
306
|
+
* Finds lineal matching nodes (self and ancestors).
|
|
2412
307
|
* @private
|
|
2413
|
-
* @param {Array} leaves - The AST leaves.
|
|
2414
|
-
* @param {object} [opt] - Options.
|
|
2415
|
-
* @
|
|
2416
|
-
* @returns {Array} An array containing [nodes, filtered].
|
|
308
|
+
* @param {Array.<object>} leaves - The AST leaves.
|
|
309
|
+
* @param {object} [opt] - Options like complex flag.
|
|
310
|
+
* @returns {Array} Array containing nodes and filtered boolean.
|
|
2417
311
|
*/
|
|
2418
312
|
_findLineal = (leaves, opt = {}) => {
|
|
2419
313
|
const { complex } = opt;
|
|
2420
314
|
const nodes = [];
|
|
2421
|
-
const
|
|
2422
|
-
const selfMatched = this._matchLeaves(leaves, this.#node, matchOpts);
|
|
315
|
+
const selfMatched = this.matchLeaves(leaves, this.node, this.matchOpts);
|
|
2423
316
|
if (selfMatched) {
|
|
2424
|
-
nodes.push(this
|
|
317
|
+
nodes.push(this.node);
|
|
2425
318
|
}
|
|
2426
319
|
if (!selfMatched || complex) {
|
|
2427
|
-
let currentNode = this
|
|
320
|
+
let currentNode = this.node.parentNode;
|
|
2428
321
|
while (currentNode) {
|
|
2429
|
-
if (this.
|
|
322
|
+
if (this.matchLeaves(leaves, currentNode, this.matchOpts)) {
|
|
2430
323
|
nodes.push(currentNode);
|
|
2431
324
|
}
|
|
2432
325
|
currentNode = currentNode.parentNode;
|
|
@@ -2437,157 +330,202 @@ export class Finder {
|
|
|
2437
330
|
};
|
|
2438
331
|
|
|
2439
332
|
/**
|
|
2440
|
-
* Finds entry nodes for pseudo-
|
|
333
|
+
* Finds entry nodes for pseudo-elements.
|
|
2441
334
|
* @private
|
|
2442
|
-
* @param {object} leaf - The
|
|
2443
|
-
* @param {Array.<object>} filterLeaves - Leaves for
|
|
2444
|
-
* @param {string} targetType - The
|
|
2445
|
-
* @returns {object}
|
|
335
|
+
* @param {object} leaf - The AST leaf.
|
|
336
|
+
* @param {Array.<object>} filterLeaves - Leaves for filtering.
|
|
337
|
+
* @param {string} targetType - The target type.
|
|
338
|
+
* @returns {object} Object with nodes, filtered, and pending flags.
|
|
2446
339
|
*/
|
|
2447
340
|
_findEntryNodesForPseudoElement = (leaf, filterLeaves, targetType) => {
|
|
2448
|
-
|
|
2449
|
-
let filtered = false;
|
|
2450
|
-
if (targetType === TARGET_SELF && this.#check) {
|
|
341
|
+
if (targetType === TARGET_SELF && this.check) {
|
|
2451
342
|
const css = generateCSS(leaf);
|
|
2452
|
-
this
|
|
343
|
+
this.pseudoElements.push(css);
|
|
2453
344
|
if (filterLeaves.length) {
|
|
2454
|
-
[nodes, filtered] = this._matchSelf(filterLeaves);
|
|
2455
|
-
|
|
2456
|
-
nodes.push(this.#node);
|
|
2457
|
-
filtered = true;
|
|
345
|
+
const [nodes, filtered] = this._matchSelf(filterLeaves);
|
|
346
|
+
return { nodes, filtered, pending: false };
|
|
2458
347
|
}
|
|
2459
|
-
|
|
2460
|
-
matchPseudoElementSelector(leaf.name, leaf.type, { warn: this.#warn });
|
|
348
|
+
return { nodes: [this.node], filtered: true, pending: false };
|
|
2461
349
|
}
|
|
2462
|
-
|
|
350
|
+
matchPseudoElementSelector(leaf.name, leaf.type, this.matchOpts);
|
|
351
|
+
return { nodes: [], filtered: false, pending: false };
|
|
2463
352
|
};
|
|
2464
353
|
|
|
2465
354
|
/**
|
|
2466
|
-
* Finds entry nodes
|
|
355
|
+
* Finds entry nodes using ID selector strategy.
|
|
2467
356
|
* @private
|
|
2468
|
-
* @param {object} twig - The
|
|
2469
|
-
* @param {string} targetType - The
|
|
2470
|
-
* @param {object} [opt] -
|
|
2471
|
-
* @
|
|
2472
|
-
* @param {boolean} [opt.precede] - If true, finds preceding nodes.
|
|
2473
|
-
* @returns {object} The result { nodes, filtered, pending }.
|
|
357
|
+
* @param {object} twig - The twig object containing leaves.
|
|
358
|
+
* @param {string} targetType - The target type.
|
|
359
|
+
* @param {object} [opt] - Strategy options.
|
|
360
|
+
* @returns {object} Result object with nodes and flags.
|
|
2474
361
|
*/
|
|
2475
362
|
_findEntryNodesForId = (twig, targetType, opt = {}) => {
|
|
2476
363
|
const { leaves } = twig;
|
|
2477
|
-
const
|
|
2478
|
-
const filterLeaves = this._getFilterLeaves(leaves);
|
|
364
|
+
const filterLeaves = this.getFilterLeaves(leaves);
|
|
2479
365
|
const { complex, precede } = opt;
|
|
2480
|
-
let nodes = [];
|
|
2481
|
-
let filtered = false;
|
|
2482
366
|
if (targetType === TARGET_SELF) {
|
|
2483
|
-
[nodes, filtered] = this._matchSelf(leaves);
|
|
367
|
+
const [nodes, filtered] = this._matchSelf(leaves);
|
|
368
|
+
return { nodes, filtered, pending: false };
|
|
2484
369
|
} else if (targetType === TARGET_LINEAL) {
|
|
2485
|
-
[nodes, filtered] = this._findLineal(leaves, { complex });
|
|
370
|
+
const [nodes, filtered] = this._findLineal(leaves, { complex });
|
|
371
|
+
return { nodes, filtered, pending: false };
|
|
2486
372
|
} else if (
|
|
2487
373
|
targetType === TARGET_FIRST &&
|
|
2488
|
-
this
|
|
374
|
+
this.root.nodeType !== ELEMENT_NODE
|
|
2489
375
|
) {
|
|
2490
|
-
const
|
|
376
|
+
const [leaf] = leaves;
|
|
377
|
+
const node = this.root.getElementById(leaf.name);
|
|
378
|
+
const nodes = [];
|
|
2491
379
|
if (node) {
|
|
2492
380
|
if (filterLeaves.length) {
|
|
2493
|
-
if (this.
|
|
381
|
+
if (this.matchLeaves(filterLeaves, node, this.matchOpts)) {
|
|
2494
382
|
nodes.push(node);
|
|
2495
|
-
filtered = true;
|
|
2496
383
|
}
|
|
2497
384
|
} else {
|
|
2498
385
|
nodes.push(node);
|
|
2499
|
-
filtered = true;
|
|
2500
386
|
}
|
|
2501
387
|
}
|
|
2502
|
-
|
|
2503
|
-
nodes = this._findNodeWalker(leaves, this.#node, { precede, targetType });
|
|
2504
|
-
filtered = nodes.length > 0;
|
|
388
|
+
return { nodes, filtered: nodes.length > 0, pending: false };
|
|
2505
389
|
}
|
|
2506
|
-
|
|
390
|
+
const nodes = this._findNodeWalker(leaves, this.node, {
|
|
391
|
+
precede,
|
|
392
|
+
targetType
|
|
393
|
+
});
|
|
394
|
+
return { nodes, filtered: nodes.length > 0, pending: false };
|
|
2507
395
|
};
|
|
2508
396
|
|
|
2509
397
|
/**
|
|
2510
|
-
* Finds entry nodes
|
|
398
|
+
* Finds entry nodes using class selector strategy.
|
|
2511
399
|
* @private
|
|
2512
|
-
* @param {Array.<object>} leaves - The AST leaves
|
|
2513
|
-
* @param {string} targetType - The
|
|
2514
|
-
* @param {object} [opt] -
|
|
2515
|
-
* @
|
|
2516
|
-
* @param {boolean} [opt.precede] - If true, finds preceding nodes.
|
|
2517
|
-
* @returns {object} The result { nodes, filtered, pending }.
|
|
400
|
+
* @param {Array.<object>} leaves - The AST leaves.
|
|
401
|
+
* @param {string} targetType - The target type.
|
|
402
|
+
* @param {object} [opt] - Strategy options.
|
|
403
|
+
* @returns {object} Result object with nodes and flags.
|
|
2518
404
|
*/
|
|
2519
405
|
_findEntryNodesForClass = (leaves, targetType, opt = {}) => {
|
|
2520
406
|
const { complex, precede } = opt;
|
|
2521
|
-
let nodes = [];
|
|
2522
|
-
let filtered = false;
|
|
2523
407
|
if (targetType === TARGET_SELF) {
|
|
2524
|
-
[nodes, filtered] = this._matchSelf(leaves);
|
|
408
|
+
const [nodes, filtered] = this._matchSelf(leaves);
|
|
409
|
+
return { nodes, filtered, pending: false };
|
|
2525
410
|
} else if (targetType === TARGET_LINEAL) {
|
|
2526
|
-
[nodes, filtered] = this._findLineal(leaves, { complex });
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
411
|
+
const [nodes, filtered] = this._findLineal(leaves, { complex });
|
|
412
|
+
return { nodes, filtered, pending: false };
|
|
413
|
+
} else if (
|
|
414
|
+
targetType !== TARGET_FIRST &&
|
|
415
|
+
!precede &&
|
|
416
|
+
typeof this.node.getElementsByClassName === 'function'
|
|
417
|
+
) {
|
|
418
|
+
this.matchLeaves(leaves, this.node, this.matchOpts);
|
|
419
|
+
const [leaf] = leaves;
|
|
420
|
+
const className = unescapeSelector(leaf.name);
|
|
421
|
+
const collection = this.node.getElementsByClassName(className);
|
|
422
|
+
const len = collection.length;
|
|
423
|
+
const filterLeaves = this.getFilterLeaves(leaves);
|
|
424
|
+
const hasFilter = filterLeaves.length > 0;
|
|
425
|
+
const nodeArray = [];
|
|
426
|
+
for (let i = 0; i < len; i++) {
|
|
427
|
+
const currentNode = collection[i];
|
|
428
|
+
if (
|
|
429
|
+
!hasFilter ||
|
|
430
|
+
this.matchLeaves(filterLeaves, currentNode, this.matchOpts)
|
|
431
|
+
) {
|
|
432
|
+
nodeArray.push(currentNode);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return {
|
|
436
|
+
nodes: nodeArray,
|
|
437
|
+
filtered: nodeArray.length > 0,
|
|
438
|
+
pending: false
|
|
439
|
+
};
|
|
2530
440
|
}
|
|
2531
|
-
|
|
441
|
+
const nodes = this._findNodeWalker(leaves, this.node, {
|
|
442
|
+
precede,
|
|
443
|
+
targetType
|
|
444
|
+
});
|
|
445
|
+
return { nodes, filtered: nodes.length > 0, pending: false };
|
|
2532
446
|
};
|
|
2533
447
|
|
|
2534
448
|
/**
|
|
2535
|
-
* Finds entry nodes
|
|
449
|
+
* Finds entry nodes using type selector strategy.
|
|
2536
450
|
* @private
|
|
2537
|
-
* @param {Array.<object>} leaves - The AST leaves
|
|
2538
|
-
* @param {string} targetType - The
|
|
2539
|
-
* @param {object} [opt] -
|
|
2540
|
-
* @
|
|
2541
|
-
* @param {boolean} [opt.precede] - If true, finds preceding nodes.
|
|
2542
|
-
* @returns {object} The result { nodes, filtered, pending }.
|
|
451
|
+
* @param {Array.<object>} leaves - The AST leaves.
|
|
452
|
+
* @param {string} targetType - The target type.
|
|
453
|
+
* @param {object} [opt] - Strategy options.
|
|
454
|
+
* @returns {object} Result object with nodes and flags.
|
|
2543
455
|
*/
|
|
2544
456
|
_findEntryNodesForType = (leaves, targetType, opt = {}) => {
|
|
2545
457
|
const { complex, precede } = opt;
|
|
2546
|
-
let nodes = [];
|
|
2547
|
-
let filtered = false;
|
|
2548
458
|
if (targetType === TARGET_SELF) {
|
|
2549
|
-
[nodes, filtered] = this._matchSelf(leaves);
|
|
459
|
+
const [nodes, filtered] = this._matchSelf(leaves);
|
|
460
|
+
return { nodes, filtered, pending: false };
|
|
2550
461
|
} else if (targetType === TARGET_LINEAL) {
|
|
2551
|
-
[nodes, filtered] = this._findLineal(leaves, { complex });
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
462
|
+
const [nodes, filtered] = this._findLineal(leaves, { complex });
|
|
463
|
+
return { nodes, filtered, pending: false };
|
|
464
|
+
}
|
|
465
|
+
const [leaf] = leaves;
|
|
466
|
+
const tagName = unescapeSelector(leaf.name);
|
|
467
|
+
if (
|
|
468
|
+
targetType !== TARGET_FIRST &&
|
|
469
|
+
!precede &&
|
|
470
|
+
this.document.contentType === 'text/html' &&
|
|
471
|
+
typeof this.node.getElementsByTagName === 'function' &&
|
|
472
|
+
tagName.indexOf('|') === -1
|
|
473
|
+
) {
|
|
474
|
+
this.matchLeaves(leaves, this.node, this.matchOpts);
|
|
475
|
+
const collection = this.node.getElementsByTagName(tagName);
|
|
476
|
+
const len = collection.length;
|
|
477
|
+
const filterLeaves = this.getFilterLeaves(leaves);
|
|
478
|
+
const hasFilter = filterLeaves.length > 0;
|
|
479
|
+
const nodeArray = [];
|
|
480
|
+
for (let i = 0; i < len; i++) {
|
|
481
|
+
const currentNode = collection[i];
|
|
482
|
+
if (
|
|
483
|
+
!hasFilter ||
|
|
484
|
+
this.matchLeaves(filterLeaves, currentNode, this.matchOpts)
|
|
485
|
+
) {
|
|
486
|
+
nodeArray.push(currentNode);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return {
|
|
490
|
+
nodes: nodeArray,
|
|
491
|
+
filtered: nodeArray.length > 0,
|
|
492
|
+
pending: false
|
|
493
|
+
};
|
|
2555
494
|
}
|
|
2556
|
-
|
|
495
|
+
const nodes = this._findNodeWalker(leaves, this.node, {
|
|
496
|
+
precede,
|
|
497
|
+
targetType
|
|
498
|
+
});
|
|
499
|
+
return { nodes, filtered: nodes.length > 0, pending: false };
|
|
2557
500
|
};
|
|
2558
501
|
|
|
2559
502
|
/**
|
|
2560
|
-
* Finds entry nodes for other selector types
|
|
503
|
+
* Finds entry nodes for other selector types.
|
|
2561
504
|
* @private
|
|
2562
|
-
* @param {object} twig - The
|
|
2563
|
-
* @param {string} targetType - The
|
|
2564
|
-
* @param {object} [opt] -
|
|
2565
|
-
* @
|
|
2566
|
-
* @param {boolean} [opt.precede] - If true, finds preceding nodes.
|
|
2567
|
-
* @returns {object} The result { nodes, filtered, pending }.
|
|
505
|
+
* @param {object} twig - The twig object containing leaves.
|
|
506
|
+
* @param {string} targetType - The target type.
|
|
507
|
+
* @param {object} [opt] - Strategy options.
|
|
508
|
+
* @returns {object} Result object with nodes and flags.
|
|
2568
509
|
*/
|
|
2569
510
|
_findEntryNodesForOther = (twig, targetType, opt = {}) => {
|
|
2570
511
|
const { leaves } = twig;
|
|
2571
512
|
const [leaf] = leaves;
|
|
2572
|
-
const filterLeaves = this.
|
|
513
|
+
const filterLeaves = this.getFilterLeaves(leaves);
|
|
2573
514
|
const { complex, precede } = opt;
|
|
2574
|
-
let nodes = [];
|
|
2575
|
-
let filtered = false;
|
|
2576
|
-
let pending = false;
|
|
2577
515
|
if (targetType !== TARGET_LINEAL && /host(?:-context)?/.test(leaf.name)) {
|
|
2578
516
|
let shadowRoot = null;
|
|
2579
517
|
if (
|
|
2580
|
-
this
|
|
2581
|
-
this
|
|
2582
|
-
this.
|
|
518
|
+
this.shadow &&
|
|
519
|
+
this.node.nodeType === DOCUMENT_FRAGMENT_NODE &&
|
|
520
|
+
this.evaluateShadowHost(leaf, this.node)
|
|
2583
521
|
) {
|
|
2584
|
-
shadowRoot = this
|
|
522
|
+
shadowRoot = this.node;
|
|
2585
523
|
} else if (
|
|
2586
524
|
filterLeaves.length &&
|
|
2587
|
-
this
|
|
2588
|
-
this.
|
|
525
|
+
this.node.nodeType === ELEMENT_NODE &&
|
|
526
|
+
this.evaluateShadowHost(leaf, this.node.shadowRoot)
|
|
2589
527
|
) {
|
|
2590
|
-
shadowRoot = this
|
|
528
|
+
shadowRoot = this.node.shadowRoot;
|
|
2591
529
|
}
|
|
2592
530
|
if (shadowRoot) {
|
|
2593
531
|
let bool = true;
|
|
@@ -2597,11 +535,11 @@ export class Finder {
|
|
|
2597
535
|
switch (filterLeaf.name) {
|
|
2598
536
|
case 'host':
|
|
2599
537
|
case 'host-context': {
|
|
2600
|
-
bool = this.
|
|
538
|
+
bool = this.evaluateShadowHost(filterLeaf, shadowRoot);
|
|
2601
539
|
break;
|
|
2602
540
|
}
|
|
2603
541
|
case 'has': {
|
|
2604
|
-
bool = this.
|
|
542
|
+
bool = this.matchPseudoClassSelector(filterLeaf, shadowRoot, {});
|
|
2605
543
|
break;
|
|
2606
544
|
}
|
|
2607
545
|
default: {
|
|
@@ -2612,43 +550,45 @@ export class Finder {
|
|
|
2612
550
|
break;
|
|
2613
551
|
}
|
|
2614
552
|
}
|
|
553
|
+
const nodes = [];
|
|
2615
554
|
if (bool) {
|
|
2616
555
|
nodes.push(shadowRoot);
|
|
2617
|
-
filtered = true;
|
|
2618
556
|
}
|
|
557
|
+
return { nodes, filtered: nodes.length > 0, pending: false };
|
|
2619
558
|
}
|
|
2620
559
|
} else if (targetType === TARGET_SELF) {
|
|
2621
|
-
[nodes, filtered] = this._matchSelf(leaves);
|
|
560
|
+
const [nodes, filtered] = this._matchSelf(leaves);
|
|
561
|
+
return { nodes, filtered, pending: false };
|
|
2622
562
|
} else if (targetType === TARGET_LINEAL) {
|
|
2623
|
-
[nodes, filtered] = this._findLineal(leaves, { complex });
|
|
563
|
+
const [nodes, filtered] = this._findLineal(leaves, { complex });
|
|
564
|
+
return { nodes, filtered, pending: false };
|
|
2624
565
|
} else if (targetType === TARGET_FIRST) {
|
|
2625
|
-
nodes = this._findNodeWalker(leaves, this
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
566
|
+
const nodes = this._findNodeWalker(leaves, this.node, {
|
|
567
|
+
precede,
|
|
568
|
+
targetType
|
|
569
|
+
});
|
|
570
|
+
return { nodes, filtered: nodes.length > 0, pending: false };
|
|
2629
571
|
}
|
|
2630
|
-
return { nodes, filtered, pending };
|
|
572
|
+
return { nodes: [], filtered: false, pending: true };
|
|
2631
573
|
};
|
|
2632
574
|
|
|
2633
575
|
/**
|
|
2634
|
-
* Finds entry nodes.
|
|
576
|
+
* Finds entry nodes based on the selector type.
|
|
2635
577
|
* @private
|
|
2636
|
-
* @param {object} twig - The twig object.
|
|
578
|
+
* @param {object} twig - The twig object containing leaves.
|
|
2637
579
|
* @param {string} targetType - The target type.
|
|
2638
|
-
* @param {object} [opt] -
|
|
2639
|
-
* @
|
|
2640
|
-
* @param {string} [opt.dir] - The find direction.
|
|
2641
|
-
* @returns {object} An object with nodes and their state.
|
|
580
|
+
* @param {object} [opt] - Strategy options.
|
|
581
|
+
* @returns {object} Result object with nodes and flags.
|
|
2642
582
|
*/
|
|
2643
583
|
_findEntryNodes = (twig, targetType, opt = {}) => {
|
|
2644
584
|
const { leaves } = twig;
|
|
2645
585
|
const [leaf] = leaves;
|
|
2646
|
-
const filterLeaves = this.
|
|
586
|
+
const filterLeaves = this.getFilterLeaves(leaves);
|
|
2647
587
|
const { complex = false, dir = DIR_PREV } = opt;
|
|
2648
588
|
const precede =
|
|
2649
589
|
dir === DIR_NEXT &&
|
|
2650
|
-
this
|
|
2651
|
-
this
|
|
590
|
+
this.node.nodeType === ELEMENT_NODE &&
|
|
591
|
+
this.node !== this.root;
|
|
2652
592
|
let result;
|
|
2653
593
|
switch (leaf.type) {
|
|
2654
594
|
case PS_ELEMENT_SELECTOR: {
|
|
@@ -2696,11 +636,11 @@ export class Finder {
|
|
|
2696
636
|
};
|
|
2697
637
|
|
|
2698
638
|
/**
|
|
2699
|
-
* Determines the direction and starting twig
|
|
639
|
+
* Determines the traversal direction and starting twig.
|
|
2700
640
|
* @private
|
|
2701
|
-
* @param {Array.<object>} branch - The
|
|
2702
|
-
* @param {string} targetType - The
|
|
2703
|
-
* @returns {object}
|
|
641
|
+
* @param {Array.<object>} branch - The selector branch.
|
|
642
|
+
* @param {string} targetType - The target type.
|
|
643
|
+
* @returns {object} Object containing dir and twig properties.
|
|
2704
644
|
*/
|
|
2705
645
|
_determineTraversalStrategy = (branch, targetType) => {
|
|
2706
646
|
const branchLen = branch.length;
|
|
@@ -2709,7 +649,6 @@ export class Finder {
|
|
|
2709
649
|
if (branchLen === 1) {
|
|
2710
650
|
return { dir: DIR_PREV, twig: firstTwig };
|
|
2711
651
|
}
|
|
2712
|
-
// Complex selector (branchLen > 1).
|
|
2713
652
|
const {
|
|
2714
653
|
leaves: [{ name: firstName, type: firstType }]
|
|
2715
654
|
} = firstTwig;
|
|
@@ -2748,37 +687,34 @@ export class Finder {
|
|
|
2748
687
|
return { dir: DIR_PREV, twig: lastTwig };
|
|
2749
688
|
}
|
|
2750
689
|
}
|
|
2751
|
-
// Default strategy for complex selectors.
|
|
2752
690
|
return { dir: DIR_NEXT, twig: firstTwig };
|
|
2753
691
|
};
|
|
2754
692
|
|
|
2755
693
|
/**
|
|
2756
|
-
* Processes pending items
|
|
694
|
+
* Processes pending items to find matches.
|
|
2757
695
|
* @private
|
|
2758
|
-
* @param {Set.<Map>} pendingItems -
|
|
696
|
+
* @param {Set.<Map>} pendingItems - Set of pending items to process.
|
|
697
|
+
* @returns {void}
|
|
2759
698
|
*/
|
|
2760
699
|
_processPendingItems = pendingItems => {
|
|
2761
700
|
if (!pendingItems.size) {
|
|
2762
701
|
return;
|
|
2763
702
|
}
|
|
2764
703
|
if (!this.#rootWalker) {
|
|
2765
|
-
this.#rootWalker = this.
|
|
704
|
+
this.#rootWalker = this.createTreeWalker(this.root);
|
|
2766
705
|
}
|
|
706
|
+
const node = this.#scoped ? this.node : this.root;
|
|
2767
707
|
const walker = this.#rootWalker;
|
|
2768
|
-
let node = this.#root;
|
|
2769
|
-
if (this.#scoped) {
|
|
2770
|
-
node = this.#node;
|
|
2771
|
-
}
|
|
2772
708
|
let nextNode = traverseNode(node, walker);
|
|
2773
709
|
while (nextNode) {
|
|
2774
710
|
const isWithinScope =
|
|
2775
|
-
this
|
|
2776
|
-
nextNode === this
|
|
2777
|
-
this
|
|
711
|
+
this.node.nodeType !== ELEMENT_NODE ||
|
|
712
|
+
nextNode === this.node ||
|
|
713
|
+
this.node.contains(nextNode);
|
|
2778
714
|
if (isWithinScope) {
|
|
2779
715
|
for (const pendingItem of pendingItems) {
|
|
2780
716
|
const { leaves } = pendingItem.get('twig');
|
|
2781
|
-
if (this.
|
|
717
|
+
if (this.matchLeaves(leaves, nextNode, this.matchOpts)) {
|
|
2782
718
|
const index = pendingItem.get('index');
|
|
2783
719
|
this.#ast[index].filtered = true;
|
|
2784
720
|
this.#ast[index].find = true;
|
|
@@ -2793,10 +729,10 @@ export class Finder {
|
|
|
2793
729
|
};
|
|
2794
730
|
|
|
2795
731
|
/**
|
|
2796
|
-
* Collects nodes.
|
|
732
|
+
* Collects all matching nodes into AST nodes array.
|
|
2797
733
|
* @private
|
|
2798
734
|
* @param {string} targetType - The target type.
|
|
2799
|
-
* @returns {Array
|
|
735
|
+
* @returns {Array} Array containing the AST and nodes arrays.
|
|
2800
736
|
*/
|
|
2801
737
|
_collectNodes = targetType => {
|
|
2802
738
|
[this.#ast, this.#nodes] = this._correspond(this.#selector);
|
|
@@ -2855,35 +791,12 @@ export class Finder {
|
|
|
2855
791
|
};
|
|
2856
792
|
|
|
2857
793
|
/**
|
|
2858
|
-
*
|
|
2859
|
-
* @private
|
|
2860
|
-
* @param {object} twig - The twig object.
|
|
2861
|
-
* @param {object} nodes - A collection of nodes.
|
|
2862
|
-
* @param {string} dir - The direction.
|
|
2863
|
-
* @returns {Array.<object>} A collection of matched nodes.
|
|
2864
|
-
*/
|
|
2865
|
-
_getCombinedNodes = (twig, nodes, dir) => {
|
|
2866
|
-
const arr = [];
|
|
2867
|
-
for (const node of nodes) {
|
|
2868
|
-
this._collectCombinatorMatches(
|
|
2869
|
-
twig,
|
|
2870
|
-
node,
|
|
2871
|
-
{ dir, warn: this.#warn },
|
|
2872
|
-
arr
|
|
2873
|
-
);
|
|
2874
|
-
}
|
|
2875
|
-
return arr;
|
|
2876
|
-
};
|
|
2877
|
-
|
|
2878
|
-
/**
|
|
2879
|
-
* Matches a node in the 'next' direction.
|
|
794
|
+
* Matches a node in the next direction.
|
|
2880
795
|
* @private
|
|
2881
|
-
* @param {Array} branch - The branch.
|
|
2882
|
-
* @param {Set.<object>} nodes -
|
|
2883
|
-
* @param {object} [opt] - Options.
|
|
2884
|
-
* @
|
|
2885
|
-
* @param {number} [opt.index] - The index.
|
|
2886
|
-
* @returns {?object} The matched node.
|
|
796
|
+
* @param {Array.<object>} branch - The selector branch.
|
|
797
|
+
* @param {Array.<object>|Set.<object>} nodes - The starting nodes.
|
|
798
|
+
* @param {object} [opt] - Options containing combo and index.
|
|
799
|
+
* @returns {object|null} The matched node or null.
|
|
2887
800
|
*/
|
|
2888
801
|
_matchNodeNext = (branch, nodes, opt = {}) => {
|
|
2889
802
|
const { combo, index } = opt;
|
|
@@ -2892,63 +805,86 @@ export class Finder {
|
|
|
2892
805
|
combo,
|
|
2893
806
|
leaves
|
|
2894
807
|
};
|
|
2895
|
-
const
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
808
|
+
for (const node of nodes) {
|
|
809
|
+
for (const nextNode of this.yieldCombinatorMatches(twig, node, {
|
|
810
|
+
dir: DIR_NEXT
|
|
811
|
+
})) {
|
|
812
|
+
if (index === branch.length - 1) {
|
|
813
|
+
return nextNode;
|
|
814
|
+
}
|
|
815
|
+
const result = this._matchNodeNext(branch, new Set([nextNode]), {
|
|
816
|
+
combo: nextCombo,
|
|
817
|
+
index: index + 1
|
|
818
|
+
});
|
|
819
|
+
if (result) {
|
|
820
|
+
return result;
|
|
2900
821
|
}
|
|
2901
|
-
const [nextNode] = sortNodes(nextNodes);
|
|
2902
|
-
return nextNode;
|
|
2903
822
|
}
|
|
2904
|
-
return this._matchNodeNext(branch, nextNodes, {
|
|
2905
|
-
combo: nextCombo,
|
|
2906
|
-
index: index + 1
|
|
2907
|
-
});
|
|
2908
823
|
}
|
|
2909
824
|
return null;
|
|
2910
825
|
};
|
|
2911
826
|
|
|
2912
827
|
/**
|
|
2913
|
-
*
|
|
828
|
+
* Recursively checks for a valid backward path.
|
|
2914
829
|
* @private
|
|
2915
|
-
* @param {
|
|
2916
|
-
* @param {object}
|
|
2917
|
-
* @param {
|
|
2918
|
-
* @param {
|
|
2919
|
-
* @returns {
|
|
830
|
+
* @param {object} node - The starting node.
|
|
831
|
+
* @param {Array.<object>} branch - The selector branch.
|
|
832
|
+
* @param {number} index - The current branch index.
|
|
833
|
+
* @param {object} opt - The match options.
|
|
834
|
+
* @returns {boolean} True if a valid path exists, otherwise false.
|
|
2920
835
|
*/
|
|
2921
|
-
|
|
2922
|
-
|
|
836
|
+
_hasValidPathPrev = (node, branch, index, opt) => {
|
|
837
|
+
if (index < 0) {
|
|
838
|
+
return true;
|
|
839
|
+
}
|
|
2923
840
|
const twig = branch[index];
|
|
2924
|
-
const
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
841
|
+
const { combo, leaves } = twig;
|
|
842
|
+
const comboName = combo.name;
|
|
843
|
+
if (comboName === '+') {
|
|
844
|
+
const refNode = node.previousElementSibling;
|
|
845
|
+
if (refNode && this.matchLeaves(leaves, refNode, opt)) {
|
|
846
|
+
if (this._hasValidPathPrev(refNode, branch, index - 1, opt)) {
|
|
847
|
+
return true;
|
|
848
|
+
}
|
|
2928
849
|
}
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
850
|
+
} else if (comboName === '~') {
|
|
851
|
+
let refNode = node.previousElementSibling;
|
|
852
|
+
while (refNode) {
|
|
853
|
+
if (this.matchLeaves(leaves, refNode, opt)) {
|
|
854
|
+
if (this._hasValidPathPrev(refNode, branch, index - 1, opt)) {
|
|
855
|
+
return true;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
refNode = refNode.previousElementSibling;
|
|
859
|
+
}
|
|
860
|
+
} else if (comboName === '>') {
|
|
861
|
+
const parentNode = node.parentNode;
|
|
862
|
+
if (parentNode && this.matchLeaves(leaves, parentNode, opt)) {
|
|
863
|
+
if (this._hasValidPathPrev(parentNode, branch, index - 1, opt)) {
|
|
864
|
+
return true;
|
|
2936
865
|
}
|
|
2937
866
|
}
|
|
2938
|
-
|
|
2939
|
-
|
|
867
|
+
} else {
|
|
868
|
+
let refNode = node.parentNode;
|
|
869
|
+
while (refNode) {
|
|
870
|
+
if (this.matchLeaves(leaves, refNode, opt)) {
|
|
871
|
+
if (this._hasValidPathPrev(refNode, branch, index - 1, opt)) {
|
|
872
|
+
return true;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
refNode = refNode.parentNode;
|
|
2940
876
|
}
|
|
2941
877
|
}
|
|
2942
|
-
return
|
|
878
|
+
return false;
|
|
2943
879
|
};
|
|
2944
880
|
|
|
2945
881
|
/**
|
|
2946
|
-
* Processes
|
|
882
|
+
* Processes complex branch for all matches.
|
|
2947
883
|
* @private
|
|
2948
|
-
* @param {Array} branch - The selector branch
|
|
2949
|
-
* @param {Array} entryNodes - The
|
|
2950
|
-
* @param {string} dir - The
|
|
2951
|
-
* @returns {Set.<object>}
|
|
884
|
+
* @param {Array.<object>} branch - The selector branch.
|
|
885
|
+
* @param {Array.<object>} entryNodes - The entry nodes.
|
|
886
|
+
* @param {string} dir - The traversal direction.
|
|
887
|
+
* @returns {Set.<object>} Set of matched nodes.
|
|
2952
888
|
*/
|
|
2953
889
|
_processComplexBranchAll = (branch, entryNodes, dir) => {
|
|
2954
890
|
const matchedNodes = new Set();
|
|
@@ -2956,42 +892,29 @@ export class Finder {
|
|
|
2956
892
|
const lastIndex = branchLen - 1;
|
|
2957
893
|
if (dir === DIR_NEXT) {
|
|
2958
894
|
const { combo: firstCombo } = branch[0];
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
for (
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
if (j === lastIndex) {
|
|
2968
|
-
for (const nextNode of nodesArr) {
|
|
2969
|
-
matchedNodes.add(nextNode);
|
|
2970
|
-
}
|
|
2971
|
-
}
|
|
2972
|
-
combo = nextCombo;
|
|
2973
|
-
nextNodes = nodesArr;
|
|
895
|
+
const dfs = (node, index, currentCombo) => {
|
|
896
|
+
const { combo: nextCombo, leaves } = branch[index];
|
|
897
|
+
const twig = { combo: currentCombo, leaves };
|
|
898
|
+
for (const nextNode of this.yieldCombinatorMatches(twig, node, {
|
|
899
|
+
dir
|
|
900
|
+
})) {
|
|
901
|
+
if (index === lastIndex) {
|
|
902
|
+
matchedNodes.add(nextNode);
|
|
2974
903
|
} else {
|
|
2975
|
-
|
|
904
|
+
dfs(nextNode, index + 1, nextCombo);
|
|
2976
905
|
}
|
|
2977
906
|
}
|
|
907
|
+
};
|
|
908
|
+
for (const node of entryNodes) {
|
|
909
|
+
dfs(node, 1, firstCombo);
|
|
2978
910
|
}
|
|
2979
|
-
// DIR_PREV
|
|
2980
911
|
} else {
|
|
2981
|
-
for (
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
// The entry node is the final match
|
|
2988
|
-
if (j === 0) {
|
|
2989
|
-
matchedNodes.add(node);
|
|
2990
|
-
}
|
|
2991
|
-
nextNodes = nodesArr;
|
|
2992
|
-
} else {
|
|
2993
|
-
break;
|
|
2994
|
-
}
|
|
912
|
+
for (let i = 0, len = entryNodes.length; i < len; i++) {
|
|
913
|
+
const node = entryNodes[i];
|
|
914
|
+
if (
|
|
915
|
+
this._hasValidPathPrev(node, branch, lastIndex - 1, this.matchOpts)
|
|
916
|
+
) {
|
|
917
|
+
matchedNodes.add(node);
|
|
2995
918
|
}
|
|
2996
919
|
}
|
|
2997
920
|
}
|
|
@@ -2999,18 +922,17 @@ export class Finder {
|
|
|
2999
922
|
};
|
|
3000
923
|
|
|
3001
924
|
/**
|
|
3002
|
-
* Processes
|
|
925
|
+
* Processes complex branch for the first match.
|
|
3003
926
|
* @private
|
|
3004
|
-
* @param {Array} branch - The selector branch
|
|
3005
|
-
* @param {Array} entryNodes - The
|
|
3006
|
-
* @param {string} dir - The
|
|
3007
|
-
* @param {string} targetType - The type
|
|
3008
|
-
* @returns {
|
|
927
|
+
* @param {Array.<object>} branch - The selector branch.
|
|
928
|
+
* @param {Array.<object>} entryNodes - The entry nodes.
|
|
929
|
+
* @param {string} dir - The traversal direction.
|
|
930
|
+
* @param {string} targetType - The target type.
|
|
931
|
+
* @returns {object|null} The matched node or null.
|
|
3009
932
|
*/
|
|
3010
933
|
_processComplexBranchFirst = (branch, entryNodes, dir, targetType) => {
|
|
3011
934
|
const branchLen = branch.length;
|
|
3012
935
|
const lastIndex = branchLen - 1;
|
|
3013
|
-
// DIR_NEXT logic for finding the first match.
|
|
3014
936
|
if (dir === DIR_NEXT) {
|
|
3015
937
|
const { combo: entryCombo } = branch[0];
|
|
3016
938
|
for (const node of entryNodes) {
|
|
@@ -3019,11 +941,8 @@ export class Finder {
|
|
|
3019
941
|
index: 1
|
|
3020
942
|
});
|
|
3021
943
|
if (matchedNode) {
|
|
3022
|
-
if (this
|
|
3023
|
-
if (
|
|
3024
|
-
matchedNode !== this.#node &&
|
|
3025
|
-
this.#node.contains(matchedNode)
|
|
3026
|
-
) {
|
|
944
|
+
if (this.node.nodeType === ELEMENT_NODE) {
|
|
945
|
+
if (matchedNode !== this.node && this.node.contains(matchedNode)) {
|
|
3027
946
|
return matchedNode;
|
|
3028
947
|
}
|
|
3029
948
|
} else {
|
|
@@ -3031,10 +950,9 @@ export class Finder {
|
|
|
3031
950
|
}
|
|
3032
951
|
}
|
|
3033
952
|
}
|
|
3034
|
-
// Fallback logic if no direct match found.
|
|
3035
953
|
const { leaves: entryLeaves } = branch[0];
|
|
3036
954
|
const [entryNode] = entryNodes;
|
|
3037
|
-
if (this
|
|
955
|
+
if (this.node.contains(entryNode)) {
|
|
3038
956
|
let [refNode] = this._findNodeWalker(entryLeaves, entryNode, {
|
|
3039
957
|
targetType
|
|
3040
958
|
});
|
|
@@ -3044,10 +962,10 @@ export class Finder {
|
|
|
3044
962
|
index: 1
|
|
3045
963
|
});
|
|
3046
964
|
if (matchedNode) {
|
|
3047
|
-
if (this
|
|
965
|
+
if (this.node.nodeType === ELEMENT_NODE) {
|
|
3048
966
|
if (
|
|
3049
|
-
matchedNode !== this
|
|
3050
|
-
this
|
|
967
|
+
matchedNode !== this.node &&
|
|
968
|
+
this.node.contains(matchedNode)
|
|
3051
969
|
) {
|
|
3052
970
|
return matchedNode;
|
|
3053
971
|
}
|
|
@@ -3061,28 +979,30 @@ export class Finder {
|
|
|
3061
979
|
});
|
|
3062
980
|
}
|
|
3063
981
|
}
|
|
3064
|
-
// DIR_PREV logic for finding the first match.
|
|
3065
982
|
} else {
|
|
3066
|
-
for (
|
|
3067
|
-
const
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
return
|
|
983
|
+
for (let i = 0, len = entryNodes.length; i < len; i++) {
|
|
984
|
+
const node = entryNodes[i];
|
|
985
|
+
if (
|
|
986
|
+
this._hasValidPathPrev(node, branch, lastIndex - 1, this.matchOpts)
|
|
987
|
+
) {
|
|
988
|
+
return node;
|
|
3072
989
|
}
|
|
3073
990
|
}
|
|
3074
|
-
// Fallback for TARGET_FIRST.
|
|
3075
991
|
if (targetType === TARGET_FIRST) {
|
|
3076
992
|
const { leaves: entryLeaves } = branch[lastIndex];
|
|
3077
|
-
const
|
|
993
|
+
const entryNode = entryNodes[0];
|
|
3078
994
|
let [refNode] = this._findNodeWalker(entryLeaves, entryNode, {
|
|
3079
995
|
targetType
|
|
3080
996
|
});
|
|
3081
997
|
while (refNode) {
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
998
|
+
if (
|
|
999
|
+
this._hasValidPathPrev(
|
|
1000
|
+
refNode,
|
|
1001
|
+
branch,
|
|
1002
|
+
lastIndex - 1,
|
|
1003
|
+
this.matchOpts
|
|
1004
|
+
)
|
|
1005
|
+
) {
|
|
3086
1006
|
return refNode;
|
|
3087
1007
|
}
|
|
3088
1008
|
[refNode] = this._findNodeWalker(entryLeaves, refNode, {
|
|
@@ -3098,24 +1018,20 @@ export class Finder {
|
|
|
3098
1018
|
/**
|
|
3099
1019
|
* Finds matched nodes.
|
|
3100
1020
|
* @param {string} targetType - The target type.
|
|
3101
|
-
* @returns {Set.<object
|
|
1021
|
+
* @returns {Set.<object>|object} A collection of matched nodes.
|
|
3102
1022
|
*/
|
|
3103
1023
|
find = targetType => {
|
|
3104
1024
|
let collection;
|
|
3105
1025
|
try {
|
|
3106
1026
|
collection = this._collectNodes(targetType);
|
|
3107
1027
|
} catch (e) {
|
|
3108
|
-
if (this
|
|
3109
|
-
let pseudoElement;
|
|
3110
|
-
if (this.#pseudoElement.length) {
|
|
3111
|
-
pseudoElement = this.#pseudoElement.join('');
|
|
3112
|
-
} else {
|
|
3113
|
-
pseudoElement = null;
|
|
3114
|
-
}
|
|
1028
|
+
if (this.check) {
|
|
3115
1029
|
return {
|
|
3116
|
-
|
|
1030
|
+
ast: this.#selectorAST,
|
|
3117
1031
|
match: false,
|
|
3118
|
-
|
|
1032
|
+
pseudoElement: this.pseudoElements.length
|
|
1033
|
+
? this.pseudoElements.join('')
|
|
1034
|
+
: null
|
|
3119
1035
|
};
|
|
3120
1036
|
} else {
|
|
3121
1037
|
throw e;
|
|
@@ -3132,17 +1048,18 @@ export class Finder {
|
|
|
3132
1048
|
}
|
|
3133
1049
|
const entryNodes = collectedNodes[i];
|
|
3134
1050
|
const lastIndex = branch.length - 1;
|
|
3135
|
-
// Handle simple selectors (no combinators).
|
|
3136
1051
|
if (lastIndex === 0) {
|
|
3137
1052
|
if (
|
|
3138
1053
|
(targetType === TARGET_ALL || targetType === TARGET_FIRST) &&
|
|
3139
|
-
this
|
|
1054
|
+
this.node.nodeType === ELEMENT_NODE
|
|
3140
1055
|
) {
|
|
3141
1056
|
for (const node of entryNodes) {
|
|
3142
|
-
if (node !== this
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
1057
|
+
if (node !== this.node) {
|
|
1058
|
+
if (targetType === TARGET_ALL || this.node.contains(node)) {
|
|
1059
|
+
nodes.add(node);
|
|
1060
|
+
if (targetType === TARGET_FIRST) {
|
|
1061
|
+
break;
|
|
1062
|
+
}
|
|
3146
1063
|
}
|
|
3147
1064
|
}
|
|
3148
1065
|
}
|
|
@@ -3160,51 +1077,39 @@ export class Finder {
|
|
|
3160
1077
|
nodes.add(entryNodes[0]);
|
|
3161
1078
|
}
|
|
3162
1079
|
}
|
|
3163
|
-
} else {
|
|
3164
|
-
|
|
3165
|
-
if (
|
|
3166
|
-
const newNodes
|
|
3167
|
-
|
|
3168
|
-
entryNodes,
|
|
3169
|
-
dir
|
|
3170
|
-
);
|
|
3171
|
-
if (nodes.size) {
|
|
3172
|
-
for (const newNode of newNodes) {
|
|
3173
|
-
nodes.add(newNode);
|
|
3174
|
-
}
|
|
3175
|
-
sort = true;
|
|
3176
|
-
} else {
|
|
3177
|
-
nodes = newNodes;
|
|
1080
|
+
} else if (targetType === TARGET_ALL) {
|
|
1081
|
+
const newNodes = this._processComplexBranchAll(branch, entryNodes, dir);
|
|
1082
|
+
if (nodes.size) {
|
|
1083
|
+
for (const newNode of newNodes) {
|
|
1084
|
+
nodes.add(newNode);
|
|
3178
1085
|
}
|
|
1086
|
+
sort = true;
|
|
3179
1087
|
} else {
|
|
3180
|
-
|
|
3181
|
-
branch,
|
|
3182
|
-
entryNodes,
|
|
3183
|
-
dir,
|
|
3184
|
-
targetType
|
|
3185
|
-
);
|
|
3186
|
-
if (matchedNode) {
|
|
3187
|
-
nodes.add(matchedNode);
|
|
3188
|
-
}
|
|
1088
|
+
nodes = newNodes;
|
|
3189
1089
|
}
|
|
3190
|
-
}
|
|
3191
|
-
}
|
|
3192
|
-
if (this.#check) {
|
|
3193
|
-
const match = !!nodes.size;
|
|
3194
|
-
let pseudoElement;
|
|
3195
|
-
if (this.#pseudoElement.length) {
|
|
3196
|
-
pseudoElement = this.#pseudoElement.join('');
|
|
3197
1090
|
} else {
|
|
3198
|
-
|
|
1091
|
+
const matchedNode = this._processComplexBranchFirst(
|
|
1092
|
+
branch,
|
|
1093
|
+
entryNodes,
|
|
1094
|
+
dir,
|
|
1095
|
+
targetType
|
|
1096
|
+
);
|
|
1097
|
+
if (matchedNode) {
|
|
1098
|
+
nodes.add(matchedNode);
|
|
1099
|
+
}
|
|
3199
1100
|
}
|
|
1101
|
+
}
|
|
1102
|
+
if (this.check) {
|
|
3200
1103
|
return {
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
1104
|
+
ast: this.#selectorAST,
|
|
1105
|
+
match: nodes.size > 0,
|
|
1106
|
+
pseudoElement: this.pseudoElements.length
|
|
1107
|
+
? this.pseudoElements.join('')
|
|
1108
|
+
: null
|
|
3204
1109
|
};
|
|
3205
1110
|
}
|
|
3206
1111
|
if (targetType === TARGET_FIRST || targetType === TARGET_ALL) {
|
|
3207
|
-
nodes.delete(this
|
|
1112
|
+
nodes.delete(this.node);
|
|
3208
1113
|
}
|
|
3209
1114
|
if ((sort || targetType === TARGET_FIRST) && nodes.size > 1) {
|
|
3210
1115
|
return new Set(sortNodes(nodes));
|