@asamuzakjp/dom-selector 8.1.5 → 8.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +9 -9
- package/src/index.js +8 -5
- package/src/js/constant.js +2 -0
- package/src/js/evaluator.js +2142 -0
- package/src/js/finder.js +513 -2608
- package/src/js/matcher.js +2 -0
- package/src/js/nwsapi.js +7 -8
- package/src/js/parser.js +3 -1
- package/src/js/utility.js +2 -37
- package/types/js/constant.d.ts +2 -0
- package/types/js/evaluator.d.ts +59 -0
- package/types/js/finder.d.ts +5 -44
- 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,313 +13,180 @@ 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,
|
|
22
|
+
DIR_NEXT,
|
|
23
|
+
DIR_PREV,
|
|
42
24
|
DOCUMENT_FRAGMENT_NODE,
|
|
43
25
|
ELEMENT_NODE,
|
|
44
|
-
FORM_PARTS,
|
|
45
|
-
HEX,
|
|
46
26
|
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
27
|
PS_ELEMENT_SELECTOR,
|
|
55
|
-
SHOW_ALL,
|
|
56
|
-
SHOW_CONTAINER,
|
|
57
28
|
SYNTAX_ERR,
|
|
58
29
|
TARGET_ALL,
|
|
59
30
|
TARGET_FIRST,
|
|
60
31
|
TARGET_LINEAL,
|
|
61
32
|
TARGET_SELF,
|
|
62
|
-
TEXT_NODE,
|
|
63
33
|
TYPE_SELECTOR
|
|
64
34
|
} from './constant.js';
|
|
65
|
-
const ANB_FIRST = { a: 0, b: 1 };
|
|
66
|
-
const ANB_LAST = { a: 0, b: 1, reverse: true };
|
|
67
|
-
const DIR_NEXT = 'next';
|
|
68
|
-
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
59
|
*/
|
|
221
|
-
setup
|
|
222
|
-
|
|
223
|
-
this.#
|
|
224
|
-
this.#
|
|
225
|
-
this.#warn = !!warn;
|
|
226
|
-
[this.#document, this.#root, this.#shadow] = resolveContent(node);
|
|
227
|
-
this.#documentURL = null;
|
|
228
|
-
this.#node = node;
|
|
60
|
+
setup(selector, node, opt = {}) {
|
|
61
|
+
super.setup(selector, node, opt);
|
|
62
|
+
this.#ast = null;
|
|
63
|
+
this.#nodes = null;
|
|
229
64
|
this.#scoped =
|
|
230
|
-
this
|
|
65
|
+
this.node !== this.root && this.node.nodeType === ELEMENT_NODE;
|
|
231
66
|
this.#selector = selector;
|
|
232
|
-
this.#
|
|
233
|
-
this.#walkers = null;
|
|
67
|
+
this.#selectorAST = null;
|
|
234
68
|
this.#nodeWalker = null;
|
|
235
69
|
this.#rootWalker = null;
|
|
236
|
-
this.#verifyShadowHost = null;
|
|
237
|
-
this.clearResults();
|
|
238
70
|
return this;
|
|
239
|
-
}
|
|
71
|
+
}
|
|
240
72
|
|
|
241
73
|
/**
|
|
242
|
-
*
|
|
243
|
-
* @param {
|
|
244
|
-
* @returns {
|
|
74
|
+
* Finds matched nodes.
|
|
75
|
+
* @param {string} targetType - The target type.
|
|
76
|
+
* @returns {Set.<object>|object} A collection of matched nodes.
|
|
245
77
|
*/
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if (all) {
|
|
263
|
-
this.#filterLeavesCache = null;
|
|
264
|
-
this.#results = new WeakMap();
|
|
78
|
+
find = targetType => {
|
|
79
|
+
let collection;
|
|
80
|
+
try {
|
|
81
|
+
collection = this._collectNodes(targetType);
|
|
82
|
+
} catch (e) {
|
|
83
|
+
if (this.check) {
|
|
84
|
+
return {
|
|
85
|
+
ast: this.#selectorAST,
|
|
86
|
+
match: false,
|
|
87
|
+
pseudoElement: this.pseudoElements.length
|
|
88
|
+
? this.pseudoElements.join('')
|
|
89
|
+
: null
|
|
90
|
+
};
|
|
91
|
+
} else {
|
|
92
|
+
throw e;
|
|
93
|
+
}
|
|
265
94
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
95
|
+
const [[...branches], collectedNodes] = collection;
|
|
96
|
+
const l = branches.length;
|
|
97
|
+
let sort = l > 1 && targetType === TARGET_ALL;
|
|
98
|
+
let nodes = new Set();
|
|
99
|
+
for (let i = 0; i < l; i++) {
|
|
100
|
+
const { branch, dir, find } = branches[i];
|
|
101
|
+
if (!branch.length || !find) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const entryNodes = collectedNodes[i];
|
|
105
|
+
const lastIndex = branch.length - 1;
|
|
106
|
+
if (lastIndex === 0) {
|
|
107
|
+
if (
|
|
108
|
+
(targetType === TARGET_ALL || targetType === TARGET_FIRST) &&
|
|
109
|
+
this.node.nodeType === ELEMENT_NODE
|
|
110
|
+
) {
|
|
111
|
+
for (const node of entryNodes) {
|
|
112
|
+
if (node !== this.node) {
|
|
113
|
+
if (targetType === TARGET_ALL || this.node.contains(node)) {
|
|
114
|
+
nodes.add(node);
|
|
115
|
+
if (targetType === TARGET_FIRST) {
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} else if (targetType === TARGET_ALL) {
|
|
122
|
+
if (nodes.size) {
|
|
123
|
+
for (const node of entryNodes) {
|
|
124
|
+
nodes.add(node);
|
|
125
|
+
}
|
|
126
|
+
sort = true;
|
|
127
|
+
} else {
|
|
128
|
+
nodes = new Set(entryNodes);
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
if (entryNodes.length) {
|
|
132
|
+
nodes.add(entryNodes[0]);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
} else if (targetType === TARGET_ALL) {
|
|
136
|
+
const newNodes = this._processComplexBranchAll(branch, entryNodes, dir);
|
|
137
|
+
if (nodes.size) {
|
|
138
|
+
for (const newNode of newNodes) {
|
|
139
|
+
nodes.add(newNode);
|
|
140
|
+
}
|
|
141
|
+
sort = true;
|
|
142
|
+
} else {
|
|
143
|
+
nodes = newNodes;
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
const matchedNode = this._processComplexBranchFirst(
|
|
147
|
+
branch,
|
|
148
|
+
entryNodes,
|
|
149
|
+
dir,
|
|
150
|
+
targetType
|
|
151
|
+
);
|
|
152
|
+
if (matchedNode) {
|
|
153
|
+
nodes.add(matchedNode);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
288
156
|
}
|
|
157
|
+
if (this.check) {
|
|
158
|
+
return {
|
|
159
|
+
ast: this.#selectorAST,
|
|
160
|
+
match: nodes.size > 0,
|
|
161
|
+
pseudoElement: this.pseudoElements.length
|
|
162
|
+
? this.pseudoElements.join('')
|
|
163
|
+
: null
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (targetType === TARGET_FIRST || targetType === TARGET_ALL) {
|
|
167
|
+
nodes.delete(this.node);
|
|
168
|
+
}
|
|
169
|
+
if ((sort || targetType === TARGET_FIRST) && nodes.size > 1) {
|
|
170
|
+
return new Set(sortNodes(nodes));
|
|
171
|
+
}
|
|
172
|
+
return nodes;
|
|
289
173
|
};
|
|
290
174
|
|
|
291
175
|
/**
|
|
292
|
-
*
|
|
293
|
-
* @
|
|
294
|
-
* @
|
|
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.
|
|
176
|
+
* Gets AST for selector.
|
|
177
|
+
* @param {string} selector - The selector text.
|
|
178
|
+
* @returns {object} The AST for the selector.
|
|
305
179
|
*/
|
|
306
|
-
|
|
307
|
-
|
|
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;
|
|
180
|
+
getAST = selector => {
|
|
181
|
+
return parseSelector(selector);
|
|
322
182
|
};
|
|
323
183
|
|
|
324
184
|
/**
|
|
325
185
|
* Processes selector branches into the internal AST structure.
|
|
326
186
|
* @private
|
|
327
|
-
* @param {Array.<
|
|
328
|
-
* @param {string} selector - The
|
|
329
|
-
* @returns {
|
|
187
|
+
* @param {Array.<object>} branches - The selector branches to process.
|
|
188
|
+
* @param {string} selector - The CSS selector string.
|
|
189
|
+
* @returns {object} Object containing ast and descendant flags.
|
|
330
190
|
*/
|
|
331
191
|
_processSelectorBranches = (branches, selector) => {
|
|
332
192
|
let descendant = false;
|
|
@@ -342,7 +202,7 @@ export class Finder {
|
|
|
342
202
|
const isLast = j === itemsLen - 1;
|
|
343
203
|
if (isInvalidCombinator(item.type, prevType, isLast)) {
|
|
344
204
|
const msg = `Invalid selector ${selector}`;
|
|
345
|
-
this.onError(generateException(msg, SYNTAX_ERR, this
|
|
205
|
+
this.onError(generateException(msg, SYNTAX_ERR, this.window));
|
|
346
206
|
return { ast: [], descendant: false, invalidate: false };
|
|
347
207
|
}
|
|
348
208
|
if (item.type === COMBINATOR) {
|
|
@@ -357,1948 +217,102 @@ export class Finder {
|
|
|
357
217
|
if (unescapedName !== item.name) {
|
|
358
218
|
item.name = unescapedName;
|
|
359
219
|
}
|
|
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
|
-
};
|
|
2144
|
-
|
|
2145
|
-
/**
|
|
2146
|
-
* Finds descendant nodes.
|
|
2147
|
-
* @private
|
|
2148
|
-
* @param {Array.<object>} leaves - The AST leaves.
|
|
2149
|
-
* @param {object} baseNode - The base Element node or Element.shadowRoot.
|
|
2150
|
-
* @param {object} opt - Options.
|
|
2151
|
-
* @returns {Set.<object>} A collection of matched nodes.
|
|
2152
|
-
*/
|
|
2153
|
-
_findDescendantNodes = (leaves, baseNode, opt) => {
|
|
2154
|
-
const [leaf] = leaves;
|
|
2155
|
-
const filterLeaves = this._getFilterLeaves(leaves);
|
|
2156
|
-
const { type: leafType } = leaf;
|
|
2157
|
-
switch (leafType) {
|
|
2158
|
-
case ID_SELECTOR: {
|
|
2159
|
-
const canUseGetElementById =
|
|
2160
|
-
!this.#shadow &&
|
|
2161
|
-
baseNode.nodeType === ELEMENT_NODE &&
|
|
2162
|
-
this.#root.nodeType !== ELEMENT_NODE;
|
|
2163
|
-
if (canUseGetElementById) {
|
|
2164
|
-
const leafName = unescapeSelector(leaf.name);
|
|
2165
|
-
const nodes = new Set();
|
|
2166
|
-
const foundNode = this.#root.getElementById(leafName);
|
|
2167
|
-
if (
|
|
2168
|
-
foundNode &&
|
|
2169
|
-
foundNode !== baseNode &&
|
|
2170
|
-
baseNode.contains(foundNode)
|
|
2171
|
-
) {
|
|
2172
|
-
const isCompoundSelector = filterLeaves.length > 0;
|
|
2173
|
-
if (
|
|
2174
|
-
!isCompoundSelector ||
|
|
2175
|
-
this._matchLeaves(filterLeaves, foundNode, opt)
|
|
2176
|
-
) {
|
|
2177
|
-
nodes.add(foundNode);
|
|
2178
|
-
}
|
|
2179
|
-
}
|
|
2180
|
-
return nodes;
|
|
2181
|
-
}
|
|
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
|
-
}
|
|
2193
|
-
}
|
|
2194
|
-
};
|
|
2195
|
-
|
|
2196
|
-
/**
|
|
2197
|
-
* Collects combinator matches into an array.
|
|
2198
|
-
* @private
|
|
2199
|
-
* @param {object} twig - The twig object.
|
|
2200
|
-
* @param {object} node - The Element node.
|
|
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.
|
|
2205
|
-
*/
|
|
2206
|
-
_collectCombinatorMatches = (twig, node, opt = {}, matched = []) => {
|
|
2207
|
-
const {
|
|
2208
|
-
combo: { name: comboName },
|
|
2209
|
-
leaves
|
|
2210
|
-
} = twig;
|
|
2211
|
-
const { dir } = opt;
|
|
2212
|
-
switch (comboName) {
|
|
2213
|
-
case '+': {
|
|
2214
|
-
const refNode =
|
|
2215
|
-
dir === DIR_NEXT
|
|
2216
|
-
? node.nextElementSibling
|
|
2217
|
-
: node.previousElementSibling;
|
|
2218
|
-
if (refNode && this._matchLeaves(leaves, refNode, opt)) {
|
|
2219
|
-
matched.push(refNode);
|
|
2220
|
-
}
|
|
2221
|
-
break;
|
|
2222
|
-
}
|
|
2223
|
-
case '~': {
|
|
2224
|
-
let refNode =
|
|
2225
|
-
dir === DIR_NEXT
|
|
2226
|
-
? node.nextElementSibling
|
|
2227
|
-
: node.previousElementSibling;
|
|
2228
|
-
while (refNode) {
|
|
2229
|
-
if (this._matchLeaves(leaves, refNode, opt)) {
|
|
2230
|
-
matched.push(refNode);
|
|
2231
|
-
}
|
|
2232
|
-
refNode =
|
|
2233
|
-
dir === DIR_NEXT
|
|
2234
|
-
? refNode.nextElementSibling
|
|
2235
|
-
: refNode.previousElementSibling;
|
|
2236
|
-
}
|
|
2237
|
-
break;
|
|
2238
|
-
}
|
|
2239
|
-
case '>': {
|
|
2240
|
-
if (dir === DIR_NEXT) {
|
|
2241
|
-
let refNode = node.firstElementChild;
|
|
2242
|
-
while (refNode) {
|
|
2243
|
-
if (this._matchLeaves(leaves, refNode, opt)) {
|
|
2244
|
-
matched.push(refNode);
|
|
2245
|
-
}
|
|
2246
|
-
refNode = refNode.nextElementSibling;
|
|
2247
|
-
}
|
|
2248
|
-
} else {
|
|
2249
|
-
const { parentNode } = node;
|
|
2250
|
-
if (parentNode && this._matchLeaves(leaves, parentNode, opt)) {
|
|
2251
|
-
matched.push(parentNode);
|
|
2252
|
-
}
|
|
2253
|
-
}
|
|
2254
|
-
break;
|
|
2255
|
-
}
|
|
2256
|
-
case ' ':
|
|
2257
|
-
default: {
|
|
2258
|
-
if (dir === DIR_NEXT) {
|
|
2259
|
-
for (const refNode of this._findDescendantNodes(leaves, node, opt)) {
|
|
2260
|
-
matched.push(refNode);
|
|
2261
|
-
}
|
|
2262
|
-
} else {
|
|
2263
|
-
const ancestors = [];
|
|
2264
|
-
let refNode = node.parentNode;
|
|
2265
|
-
while (refNode) {
|
|
2266
|
-
if (this._matchLeaves(leaves, refNode, opt)) {
|
|
2267
|
-
ancestors.push(refNode);
|
|
220
|
+
if (/[|:]/.test(unescapedName)) {
|
|
221
|
+
item.namespace = true;
|
|
222
|
+
}
|
|
2268
223
|
}
|
|
2269
|
-
|
|
224
|
+
leaves.add(item);
|
|
2270
225
|
}
|
|
2271
|
-
|
|
2272
|
-
|
|
226
|
+
prevType = item.type;
|
|
227
|
+
if (isLast) {
|
|
228
|
+
branch.push({ combo: null, leaves: sortAST(leaves) });
|
|
229
|
+
leaves.clear();
|
|
2273
230
|
}
|
|
2274
231
|
}
|
|
2275
232
|
}
|
|
233
|
+
ast.push({ branch, dir: null, filtered: false, find: false });
|
|
2276
234
|
}
|
|
2277
|
-
return
|
|
235
|
+
return { ast, descendant };
|
|
2278
236
|
};
|
|
2279
237
|
|
|
2280
238
|
/**
|
|
2281
|
-
*
|
|
239
|
+
* Corresponds AST and DOM nodes for the given selector.
|
|
2282
240
|
* @private
|
|
2283
|
-
* @param {
|
|
2284
|
-
* @
|
|
2285
|
-
* @param {object} opt - Options.
|
|
2286
|
-
* @returns {Set.<object>} A collection of matched nodes.
|
|
241
|
+
* @param {string} selector - The CSS selector string.
|
|
242
|
+
* @returns {Array} An array containing the AST and empty nodes array.
|
|
2287
243
|
*/
|
|
2288
|
-
|
|
2289
|
-
|
|
244
|
+
_correspond = selector => {
|
|
245
|
+
const nodes = [];
|
|
246
|
+
let descendant = false;
|
|
247
|
+
this.invalidate = false;
|
|
248
|
+
let ast;
|
|
249
|
+
if (this.documentCache.has(this.document)) {
|
|
250
|
+
const cachedItem = this.documentCache.get(this.document);
|
|
251
|
+
if (cachedItem && cachedItem.has(`${selector}`)) {
|
|
252
|
+
const item = cachedItem.get(`${selector}`);
|
|
253
|
+
ast = item.ast;
|
|
254
|
+
descendant = item.descendant;
|
|
255
|
+
this.invalidate = item.invalidate;
|
|
256
|
+
this.#selectorAST = item.selectorAST;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (ast) {
|
|
260
|
+
const l = ast.length;
|
|
261
|
+
for (let i = 0; i < l; i++) {
|
|
262
|
+
ast[i].dir = null;
|
|
263
|
+
ast[i].filtered = false;
|
|
264
|
+
ast[i].find = false;
|
|
265
|
+
nodes[i] = [];
|
|
266
|
+
}
|
|
267
|
+
} else {
|
|
268
|
+
this.#selectorAST = parseSelector(selector);
|
|
269
|
+
const { branches, info } = walkAST(
|
|
270
|
+
this.#selectorAST,
|
|
271
|
+
true,
|
|
272
|
+
createHasValidator(this.window)
|
|
273
|
+
);
|
|
274
|
+
const {
|
|
275
|
+
hasHasPseudoFunc,
|
|
276
|
+
hasLogicalPseudoFunc,
|
|
277
|
+
hasNthChildOfSelector,
|
|
278
|
+
hasStatePseudoClass,
|
|
279
|
+
hasUnsupportedPseudoClass
|
|
280
|
+
} = info;
|
|
281
|
+
this.invalidate =
|
|
282
|
+
hasHasPseudoFunc ||
|
|
283
|
+
hasStatePseudoClass ||
|
|
284
|
+
hasUnsupportedPseudoClass ||
|
|
285
|
+
!!(hasLogicalPseudoFunc && hasNthChildOfSelector);
|
|
286
|
+
const processed = this._processSelectorBranches(branches, selector);
|
|
287
|
+
ast = processed.ast;
|
|
288
|
+
descendant = processed.descendant;
|
|
289
|
+
let cachedItem;
|
|
290
|
+
if (this.documentCache.has(this.document)) {
|
|
291
|
+
cachedItem = this.documentCache.get(this.document);
|
|
292
|
+
} else {
|
|
293
|
+
cachedItem = new Map();
|
|
294
|
+
}
|
|
295
|
+
cachedItem.set(`${selector}`, {
|
|
296
|
+
ast,
|
|
297
|
+
descendant,
|
|
298
|
+
invalidate: this.invalidate,
|
|
299
|
+
selectorAST: this.#selectorAST
|
|
300
|
+
});
|
|
301
|
+
this.documentCache.set(this.document, cachedItem);
|
|
302
|
+
for (let i = 0; i < ast.length; i++) {
|
|
303
|
+
nodes[i] = [];
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return [ast, nodes];
|
|
307
|
+
};
|
|
2290
308
|
|
|
2291
309
|
/**
|
|
2292
|
-
* Traverses
|
|
310
|
+
* Traverses and collects nodes matching leaves.
|
|
2293
311
|
* @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.
|
|
312
|
+
* @param {object} walker - The TreeWalker instance.
|
|
313
|
+
* @param {Array.<object>} leaves - The AST leaves to match.
|
|
314
|
+
* @param {object} [opt] - Options for traversal.
|
|
315
|
+
* @returns {Array.<object>} An array of collected nodes.
|
|
2302
316
|
*/
|
|
2303
317
|
_traverseAndCollectNodes = (walker, leaves, opt = {}) => {
|
|
2304
318
|
const { boundaryNode, force, startNode, targetType } = opt;
|
|
@@ -2310,14 +324,10 @@ export class Finder {
|
|
|
2310
324
|
// Adjust starting node.
|
|
2311
325
|
if (currentNode.nodeType !== ELEMENT_NODE) {
|
|
2312
326
|
currentNode = walker.nextNode();
|
|
2313
|
-
} else if (currentNode === startNode && currentNode !== this
|
|
327
|
+
} else if (currentNode === startNode && currentNode !== this.root) {
|
|
2314
328
|
currentNode = walker.nextNode();
|
|
2315
329
|
}
|
|
2316
|
-
const matchOpt = {
|
|
2317
|
-
warn: this.#warn
|
|
2318
|
-
};
|
|
2319
330
|
while (currentNode) {
|
|
2320
|
-
// Stop when we reach the boundary.
|
|
2321
331
|
if (boundaryNode) {
|
|
2322
332
|
if (currentNode === boundaryNode) {
|
|
2323
333
|
break;
|
|
@@ -2329,11 +339,10 @@ export class Finder {
|
|
|
2329
339
|
}
|
|
2330
340
|
}
|
|
2331
341
|
if (
|
|
2332
|
-
this.
|
|
2333
|
-
currentNode !== this
|
|
342
|
+
this.matchLeaves(leaves, currentNode, this.matchOpts) &&
|
|
343
|
+
currentNode !== this.node
|
|
2334
344
|
) {
|
|
2335
345
|
collectedNodes.push(currentNode);
|
|
2336
|
-
// Stop after the first match if not collecting all.
|
|
2337
346
|
if (targetType !== TARGET_ALL) {
|
|
2338
347
|
break;
|
|
2339
348
|
}
|
|
@@ -2344,47 +353,44 @@ export class Finder {
|
|
|
2344
353
|
};
|
|
2345
354
|
|
|
2346
355
|
/**
|
|
2347
|
-
* Finds
|
|
356
|
+
* Finds matching nodes preceding the current node.
|
|
2348
357
|
* @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.
|
|
358
|
+
* @param {Array.<object>} leaves - The AST leaves to match.
|
|
359
|
+
* @param {object} node - The starting node.
|
|
360
|
+
* @param {object} [opt] - Options for finding.
|
|
361
|
+
* @returns {Array.<object>} An array of matched nodes.
|
|
2355
362
|
*/
|
|
2356
363
|
_findPrecede = (leaves, node, opt = {}) => {
|
|
2357
364
|
const { force, targetType } = opt;
|
|
2358
365
|
if (!this.#rootWalker) {
|
|
2359
|
-
this.#rootWalker = this.
|
|
366
|
+
this.#rootWalker = this.createTreeWalker(this.root);
|
|
2360
367
|
}
|
|
2361
368
|
return this._traverseAndCollectNodes(this.#rootWalker, leaves, {
|
|
2362
369
|
force,
|
|
2363
370
|
targetType,
|
|
2364
|
-
boundaryNode: this
|
|
371
|
+
boundaryNode: this.node,
|
|
2365
372
|
startNode: node
|
|
2366
373
|
});
|
|
2367
374
|
};
|
|
2368
375
|
|
|
2369
376
|
/**
|
|
2370
|
-
* Finds
|
|
377
|
+
* Finds matching nodes using TreeWalker.
|
|
2371
378
|
* @private
|
|
2372
379
|
* @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.
|
|
380
|
+
* @param {object} node - The starting node.
|
|
381
|
+
* @param {object} [opt] - Traversal options.
|
|
382
|
+
* @returns {Array.<object>} An array of matched nodes.
|
|
2377
383
|
*/
|
|
2378
384
|
_findNodeWalker = (leaves, node, opt = {}) => {
|
|
2379
385
|
const { precede, ...traversalOpts } = opt;
|
|
2380
386
|
if (precede) {
|
|
2381
|
-
const precedeNodes = this._findPrecede(leaves, this
|
|
387
|
+
const precedeNodes = this._findPrecede(leaves, this.root, opt);
|
|
2382
388
|
if (precedeNodes.length) {
|
|
2383
389
|
return precedeNodes;
|
|
2384
390
|
}
|
|
2385
391
|
}
|
|
2386
392
|
if (!this.#nodeWalker) {
|
|
2387
|
-
this.#nodeWalker = this.
|
|
393
|
+
this.#nodeWalker = this.createTreeWalker(this.node);
|
|
2388
394
|
}
|
|
2389
395
|
return this._traverseAndCollectNodes(this.#nodeWalker, leaves, {
|
|
2390
396
|
...traversalOpts,
|
|
@@ -2393,40 +399,38 @@ export class Finder {
|
|
|
2393
399
|
};
|
|
2394
400
|
|
|
2395
401
|
/**
|
|
2396
|
-
* Matches the node itself.
|
|
402
|
+
* Matches the current node itself against leaves.
|
|
2397
403
|
* @private
|
|
2398
|
-
* @param {Array} leaves - The AST leaves.
|
|
2399
|
-
* @returns {Array}
|
|
404
|
+
* @param {Array.<object>} leaves - The AST leaves.
|
|
405
|
+
* @returns {Array} Array with nodes, match boolean, and pseudo-elements.
|
|
2400
406
|
*/
|
|
2401
407
|
_matchSelf = leaves => {
|
|
2402
|
-
const matched = this.
|
|
2403
|
-
check: this
|
|
2404
|
-
warn: this
|
|
408
|
+
const matched = this.matchLeaves(leaves, this.node, {
|
|
409
|
+
check: this.check,
|
|
410
|
+
warn: this.warn
|
|
2405
411
|
});
|
|
2406
|
-
const nodes = matched ? [this
|
|
2407
|
-
return [nodes, matched, this
|
|
412
|
+
const nodes = matched ? [this.node] : [];
|
|
413
|
+
return [nodes, matched, this.pseudoElements];
|
|
2408
414
|
};
|
|
2409
415
|
|
|
2410
416
|
/**
|
|
2411
|
-
* Finds lineal nodes (self and ancestors).
|
|
417
|
+
* Finds lineal matching nodes (self and ancestors).
|
|
2412
418
|
* @private
|
|
2413
|
-
* @param {Array} leaves - The AST leaves.
|
|
2414
|
-
* @param {object} [opt] - Options.
|
|
2415
|
-
* @
|
|
2416
|
-
* @returns {Array} An array containing [nodes, filtered].
|
|
419
|
+
* @param {Array.<object>} leaves - The AST leaves.
|
|
420
|
+
* @param {object} [opt] - Options like complex flag.
|
|
421
|
+
* @returns {Array} Array containing nodes and filtered boolean.
|
|
2417
422
|
*/
|
|
2418
423
|
_findLineal = (leaves, opt = {}) => {
|
|
2419
424
|
const { complex } = opt;
|
|
2420
425
|
const nodes = [];
|
|
2421
|
-
const
|
|
2422
|
-
const selfMatched = this._matchLeaves(leaves, this.#node, matchOpts);
|
|
426
|
+
const selfMatched = this.matchLeaves(leaves, this.node, this.matchOpts);
|
|
2423
427
|
if (selfMatched) {
|
|
2424
|
-
nodes.push(this
|
|
428
|
+
nodes.push(this.node);
|
|
2425
429
|
}
|
|
2426
430
|
if (!selfMatched || complex) {
|
|
2427
|
-
let currentNode = this
|
|
431
|
+
let currentNode = this.node.parentNode;
|
|
2428
432
|
while (currentNode) {
|
|
2429
|
-
if (this.
|
|
433
|
+
if (this.matchLeaves(leaves, currentNode, this.matchOpts)) {
|
|
2430
434
|
nodes.push(currentNode);
|
|
2431
435
|
}
|
|
2432
436
|
currentNode = currentNode.parentNode;
|
|
@@ -2437,157 +441,202 @@ export class Finder {
|
|
|
2437
441
|
};
|
|
2438
442
|
|
|
2439
443
|
/**
|
|
2440
|
-
* Finds entry nodes for pseudo-
|
|
444
|
+
* Finds entry nodes for pseudo-elements.
|
|
2441
445
|
* @private
|
|
2442
|
-
* @param {object} leaf - The
|
|
2443
|
-
* @param {Array.<object>} filterLeaves - Leaves for
|
|
2444
|
-
* @param {string} targetType - The
|
|
2445
|
-
* @returns {object}
|
|
446
|
+
* @param {object} leaf - The AST leaf.
|
|
447
|
+
* @param {Array.<object>} filterLeaves - Leaves for filtering.
|
|
448
|
+
* @param {string} targetType - The target type.
|
|
449
|
+
* @returns {object} Object with nodes, filtered, and pending flags.
|
|
2446
450
|
*/
|
|
2447
451
|
_findEntryNodesForPseudoElement = (leaf, filterLeaves, targetType) => {
|
|
2448
|
-
|
|
2449
|
-
let filtered = false;
|
|
2450
|
-
if (targetType === TARGET_SELF && this.#check) {
|
|
452
|
+
if (targetType === TARGET_SELF && this.check) {
|
|
2451
453
|
const css = generateCSS(leaf);
|
|
2452
|
-
this
|
|
454
|
+
this.pseudoElements.push(css);
|
|
2453
455
|
if (filterLeaves.length) {
|
|
2454
|
-
[nodes, filtered] = this._matchSelf(filterLeaves);
|
|
2455
|
-
|
|
2456
|
-
nodes.push(this.#node);
|
|
2457
|
-
filtered = true;
|
|
456
|
+
const [nodes, filtered] = this._matchSelf(filterLeaves);
|
|
457
|
+
return { nodes, filtered, pending: false };
|
|
2458
458
|
}
|
|
2459
|
-
|
|
2460
|
-
matchPseudoElementSelector(leaf.name, leaf.type, { warn: this.#warn });
|
|
459
|
+
return { nodes: [this.node], filtered: true, pending: false };
|
|
2461
460
|
}
|
|
2462
|
-
|
|
461
|
+
matchPseudoElementSelector(leaf.name, leaf.type, this.matchOpts);
|
|
462
|
+
return { nodes: [], filtered: false, pending: false };
|
|
2463
463
|
};
|
|
2464
464
|
|
|
2465
465
|
/**
|
|
2466
|
-
* Finds entry nodes
|
|
466
|
+
* Finds entry nodes using ID selector strategy.
|
|
2467
467
|
* @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 }.
|
|
468
|
+
* @param {object} twig - The twig object containing leaves.
|
|
469
|
+
* @param {string} targetType - The target type.
|
|
470
|
+
* @param {object} [opt] - Strategy options.
|
|
471
|
+
* @returns {object} Result object with nodes and flags.
|
|
2474
472
|
*/
|
|
2475
473
|
_findEntryNodesForId = (twig, targetType, opt = {}) => {
|
|
2476
474
|
const { leaves } = twig;
|
|
2477
|
-
const
|
|
2478
|
-
const filterLeaves = this._getFilterLeaves(leaves);
|
|
475
|
+
const filterLeaves = this.getFilterLeaves(leaves);
|
|
2479
476
|
const { complex, precede } = opt;
|
|
2480
|
-
let nodes = [];
|
|
2481
|
-
let filtered = false;
|
|
2482
477
|
if (targetType === TARGET_SELF) {
|
|
2483
|
-
[nodes, filtered] = this._matchSelf(leaves);
|
|
478
|
+
const [nodes, filtered] = this._matchSelf(leaves);
|
|
479
|
+
return { nodes, filtered, pending: false };
|
|
2484
480
|
} else if (targetType === TARGET_LINEAL) {
|
|
2485
|
-
[nodes, filtered] = this._findLineal(leaves, { complex });
|
|
481
|
+
const [nodes, filtered] = this._findLineal(leaves, { complex });
|
|
482
|
+
return { nodes, filtered, pending: false };
|
|
2486
483
|
} else if (
|
|
2487
484
|
targetType === TARGET_FIRST &&
|
|
2488
|
-
this
|
|
485
|
+
this.root.nodeType !== ELEMENT_NODE
|
|
2489
486
|
) {
|
|
2490
|
-
const
|
|
487
|
+
const [leaf] = leaves;
|
|
488
|
+
const node = this.root.getElementById(leaf.name);
|
|
489
|
+
const nodes = [];
|
|
2491
490
|
if (node) {
|
|
2492
491
|
if (filterLeaves.length) {
|
|
2493
|
-
if (this.
|
|
492
|
+
if (this.matchLeaves(filterLeaves, node, this.matchOpts)) {
|
|
2494
493
|
nodes.push(node);
|
|
2495
|
-
filtered = true;
|
|
2496
494
|
}
|
|
2497
495
|
} else {
|
|
2498
496
|
nodes.push(node);
|
|
2499
|
-
filtered = true;
|
|
2500
497
|
}
|
|
2501
498
|
}
|
|
2502
|
-
|
|
2503
|
-
nodes = this._findNodeWalker(leaves, this.#node, { precede, targetType });
|
|
2504
|
-
filtered = nodes.length > 0;
|
|
499
|
+
return { nodes, filtered: nodes.length > 0, pending: false };
|
|
2505
500
|
}
|
|
2506
|
-
|
|
501
|
+
const nodes = this._findNodeWalker(leaves, this.node, {
|
|
502
|
+
precede,
|
|
503
|
+
targetType
|
|
504
|
+
});
|
|
505
|
+
return { nodes, filtered: nodes.length > 0, pending: false };
|
|
2507
506
|
};
|
|
2508
507
|
|
|
2509
508
|
/**
|
|
2510
|
-
* Finds entry nodes
|
|
509
|
+
* Finds entry nodes using class selector strategy.
|
|
2511
510
|
* @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 }.
|
|
511
|
+
* @param {Array.<object>} leaves - The AST leaves.
|
|
512
|
+
* @param {string} targetType - The target type.
|
|
513
|
+
* @param {object} [opt] - Strategy options.
|
|
514
|
+
* @returns {object} Result object with nodes and flags.
|
|
2518
515
|
*/
|
|
2519
516
|
_findEntryNodesForClass = (leaves, targetType, opt = {}) => {
|
|
2520
517
|
const { complex, precede } = opt;
|
|
2521
|
-
let nodes = [];
|
|
2522
|
-
let filtered = false;
|
|
2523
518
|
if (targetType === TARGET_SELF) {
|
|
2524
|
-
[nodes, filtered] = this._matchSelf(leaves);
|
|
519
|
+
const [nodes, filtered] = this._matchSelf(leaves);
|
|
520
|
+
return { nodes, filtered, pending: false };
|
|
2525
521
|
} else if (targetType === TARGET_LINEAL) {
|
|
2526
|
-
[nodes, filtered] = this._findLineal(leaves, { complex });
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
522
|
+
const [nodes, filtered] = this._findLineal(leaves, { complex });
|
|
523
|
+
return { nodes, filtered, pending: false };
|
|
524
|
+
} else if (
|
|
525
|
+
targetType !== TARGET_FIRST &&
|
|
526
|
+
!precede &&
|
|
527
|
+
typeof this.node.getElementsByClassName === 'function'
|
|
528
|
+
) {
|
|
529
|
+
this.matchLeaves(leaves, this.node, this.matchOpts);
|
|
530
|
+
const [leaf] = leaves;
|
|
531
|
+
const className = unescapeSelector(leaf.name);
|
|
532
|
+
const collection = this.node.getElementsByClassName(className);
|
|
533
|
+
const len = collection.length;
|
|
534
|
+
const filterLeaves = this.getFilterLeaves(leaves);
|
|
535
|
+
const hasFilter = filterLeaves.length > 0;
|
|
536
|
+
const nodeArray = [];
|
|
537
|
+
for (let i = 0; i < len; i++) {
|
|
538
|
+
const currentNode = collection[i];
|
|
539
|
+
if (
|
|
540
|
+
!hasFilter ||
|
|
541
|
+
this.matchLeaves(filterLeaves, currentNode, this.matchOpts)
|
|
542
|
+
) {
|
|
543
|
+
nodeArray.push(currentNode);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return {
|
|
547
|
+
nodes: nodeArray,
|
|
548
|
+
filtered: nodeArray.length > 0,
|
|
549
|
+
pending: false
|
|
550
|
+
};
|
|
2530
551
|
}
|
|
2531
|
-
|
|
552
|
+
const nodes = this._findNodeWalker(leaves, this.node, {
|
|
553
|
+
precede,
|
|
554
|
+
targetType
|
|
555
|
+
});
|
|
556
|
+
return { nodes, filtered: nodes.length > 0, pending: false };
|
|
2532
557
|
};
|
|
2533
558
|
|
|
2534
559
|
/**
|
|
2535
|
-
* Finds entry nodes
|
|
560
|
+
* Finds entry nodes using type selector strategy.
|
|
2536
561
|
* @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 }.
|
|
562
|
+
* @param {Array.<object>} leaves - The AST leaves.
|
|
563
|
+
* @param {string} targetType - The target type.
|
|
564
|
+
* @param {object} [opt] - Strategy options.
|
|
565
|
+
* @returns {object} Result object with nodes and flags.
|
|
2543
566
|
*/
|
|
2544
567
|
_findEntryNodesForType = (leaves, targetType, opt = {}) => {
|
|
2545
568
|
const { complex, precede } = opt;
|
|
2546
|
-
let nodes = [];
|
|
2547
|
-
let filtered = false;
|
|
2548
569
|
if (targetType === TARGET_SELF) {
|
|
2549
|
-
[nodes, filtered] = this._matchSelf(leaves);
|
|
570
|
+
const [nodes, filtered] = this._matchSelf(leaves);
|
|
571
|
+
return { nodes, filtered, pending: false };
|
|
2550
572
|
} else if (targetType === TARGET_LINEAL) {
|
|
2551
|
-
[nodes, filtered] = this._findLineal(leaves, { complex });
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
573
|
+
const [nodes, filtered] = this._findLineal(leaves, { complex });
|
|
574
|
+
return { nodes, filtered, pending: false };
|
|
575
|
+
}
|
|
576
|
+
const [leaf] = leaves;
|
|
577
|
+
const tagName = unescapeSelector(leaf.name);
|
|
578
|
+
if (
|
|
579
|
+
targetType !== TARGET_FIRST &&
|
|
580
|
+
!precede &&
|
|
581
|
+
this.document.contentType === 'text/html' &&
|
|
582
|
+
typeof this.node.getElementsByTagName === 'function' &&
|
|
583
|
+
tagName.indexOf('|') === -1
|
|
584
|
+
) {
|
|
585
|
+
this.matchLeaves(leaves, this.node, this.matchOpts);
|
|
586
|
+
const collection = this.node.getElementsByTagName(tagName);
|
|
587
|
+
const len = collection.length;
|
|
588
|
+
const filterLeaves = this.getFilterLeaves(leaves);
|
|
589
|
+
const hasFilter = filterLeaves.length > 0;
|
|
590
|
+
const nodeArray = [];
|
|
591
|
+
for (let i = 0; i < len; i++) {
|
|
592
|
+
const currentNode = collection[i];
|
|
593
|
+
if (
|
|
594
|
+
!hasFilter ||
|
|
595
|
+
this.matchLeaves(filterLeaves, currentNode, this.matchOpts)
|
|
596
|
+
) {
|
|
597
|
+
nodeArray.push(currentNode);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return {
|
|
601
|
+
nodes: nodeArray,
|
|
602
|
+
filtered: nodeArray.length > 0,
|
|
603
|
+
pending: false
|
|
604
|
+
};
|
|
2555
605
|
}
|
|
2556
|
-
|
|
606
|
+
const nodes = this._findNodeWalker(leaves, this.node, {
|
|
607
|
+
precede,
|
|
608
|
+
targetType
|
|
609
|
+
});
|
|
610
|
+
return { nodes, filtered: nodes.length > 0, pending: false };
|
|
2557
611
|
};
|
|
2558
612
|
|
|
2559
613
|
/**
|
|
2560
|
-
* Finds entry nodes for other selector types
|
|
614
|
+
* Finds entry nodes for other selector types.
|
|
2561
615
|
* @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 }.
|
|
616
|
+
* @param {object} twig - The twig object containing leaves.
|
|
617
|
+
* @param {string} targetType - The target type.
|
|
618
|
+
* @param {object} [opt] - Strategy options.
|
|
619
|
+
* @returns {object} Result object with nodes and flags.
|
|
2568
620
|
*/
|
|
2569
621
|
_findEntryNodesForOther = (twig, targetType, opt = {}) => {
|
|
2570
622
|
const { leaves } = twig;
|
|
2571
623
|
const [leaf] = leaves;
|
|
2572
|
-
const filterLeaves = this.
|
|
624
|
+
const filterLeaves = this.getFilterLeaves(leaves);
|
|
2573
625
|
const { complex, precede } = opt;
|
|
2574
|
-
let nodes = [];
|
|
2575
|
-
let filtered = false;
|
|
2576
|
-
let pending = false;
|
|
2577
626
|
if (targetType !== TARGET_LINEAL && /host(?:-context)?/.test(leaf.name)) {
|
|
2578
627
|
let shadowRoot = null;
|
|
2579
628
|
if (
|
|
2580
|
-
this
|
|
2581
|
-
this
|
|
2582
|
-
this.
|
|
629
|
+
this.shadow &&
|
|
630
|
+
this.node.nodeType === DOCUMENT_FRAGMENT_NODE &&
|
|
631
|
+
this.evaluateShadowHost(leaf, this.node)
|
|
2583
632
|
) {
|
|
2584
|
-
shadowRoot = this
|
|
633
|
+
shadowRoot = this.node;
|
|
2585
634
|
} else if (
|
|
2586
635
|
filterLeaves.length &&
|
|
2587
|
-
this
|
|
2588
|
-
this.
|
|
636
|
+
this.node.nodeType === ELEMENT_NODE &&
|
|
637
|
+
this.evaluateShadowHost(leaf, this.node.shadowRoot)
|
|
2589
638
|
) {
|
|
2590
|
-
shadowRoot = this
|
|
639
|
+
shadowRoot = this.node.shadowRoot;
|
|
2591
640
|
}
|
|
2592
641
|
if (shadowRoot) {
|
|
2593
642
|
let bool = true;
|
|
@@ -2597,11 +646,11 @@ export class Finder {
|
|
|
2597
646
|
switch (filterLeaf.name) {
|
|
2598
647
|
case 'host':
|
|
2599
648
|
case 'host-context': {
|
|
2600
|
-
bool = this.
|
|
649
|
+
bool = this.evaluateShadowHost(filterLeaf, shadowRoot);
|
|
2601
650
|
break;
|
|
2602
651
|
}
|
|
2603
652
|
case 'has': {
|
|
2604
|
-
bool = this.
|
|
653
|
+
bool = this.matchPseudoClassSelector(filterLeaf, shadowRoot, {});
|
|
2605
654
|
break;
|
|
2606
655
|
}
|
|
2607
656
|
default: {
|
|
@@ -2612,43 +661,45 @@ export class Finder {
|
|
|
2612
661
|
break;
|
|
2613
662
|
}
|
|
2614
663
|
}
|
|
664
|
+
const nodes = [];
|
|
2615
665
|
if (bool) {
|
|
2616
666
|
nodes.push(shadowRoot);
|
|
2617
|
-
filtered = true;
|
|
2618
667
|
}
|
|
668
|
+
return { nodes, filtered: nodes.length > 0, pending: false };
|
|
2619
669
|
}
|
|
2620
670
|
} else if (targetType === TARGET_SELF) {
|
|
2621
|
-
[nodes, filtered] = this._matchSelf(leaves);
|
|
671
|
+
const [nodes, filtered] = this._matchSelf(leaves);
|
|
672
|
+
return { nodes, filtered, pending: false };
|
|
2622
673
|
} else if (targetType === TARGET_LINEAL) {
|
|
2623
|
-
[nodes, filtered] = this._findLineal(leaves, { complex });
|
|
674
|
+
const [nodes, filtered] = this._findLineal(leaves, { complex });
|
|
675
|
+
return { nodes, filtered, pending: false };
|
|
2624
676
|
} else if (targetType === TARGET_FIRST) {
|
|
2625
|
-
nodes = this._findNodeWalker(leaves, this
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
677
|
+
const nodes = this._findNodeWalker(leaves, this.node, {
|
|
678
|
+
precede,
|
|
679
|
+
targetType
|
|
680
|
+
});
|
|
681
|
+
return { nodes, filtered: nodes.length > 0, pending: false };
|
|
2629
682
|
}
|
|
2630
|
-
return { nodes, filtered, pending };
|
|
683
|
+
return { nodes: [], filtered: false, pending: true };
|
|
2631
684
|
};
|
|
2632
685
|
|
|
2633
686
|
/**
|
|
2634
|
-
* Finds entry nodes.
|
|
687
|
+
* Finds entry nodes based on the selector type.
|
|
2635
688
|
* @private
|
|
2636
|
-
* @param {object} twig - The twig object.
|
|
689
|
+
* @param {object} twig - The twig object containing leaves.
|
|
2637
690
|
* @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.
|
|
691
|
+
* @param {object} [opt] - Strategy options.
|
|
692
|
+
* @returns {object} Result object with nodes and flags.
|
|
2642
693
|
*/
|
|
2643
694
|
_findEntryNodes = (twig, targetType, opt = {}) => {
|
|
2644
695
|
const { leaves } = twig;
|
|
2645
696
|
const [leaf] = leaves;
|
|
2646
|
-
const filterLeaves = this.
|
|
697
|
+
const filterLeaves = this.getFilterLeaves(leaves);
|
|
2647
698
|
const { complex = false, dir = DIR_PREV } = opt;
|
|
2648
699
|
const precede =
|
|
2649
700
|
dir === DIR_NEXT &&
|
|
2650
|
-
this
|
|
2651
|
-
this
|
|
701
|
+
this.node.nodeType === ELEMENT_NODE &&
|
|
702
|
+
this.node !== this.root;
|
|
2652
703
|
let result;
|
|
2653
704
|
switch (leaf.type) {
|
|
2654
705
|
case PS_ELEMENT_SELECTOR: {
|
|
@@ -2696,11 +747,11 @@ export class Finder {
|
|
|
2696
747
|
};
|
|
2697
748
|
|
|
2698
749
|
/**
|
|
2699
|
-
* Determines the direction and starting twig
|
|
750
|
+
* Determines the traversal direction and starting twig.
|
|
2700
751
|
* @private
|
|
2701
|
-
* @param {Array.<object>} branch - The
|
|
2702
|
-
* @param {string} targetType - The
|
|
2703
|
-
* @returns {object}
|
|
752
|
+
* @param {Array.<object>} branch - The selector branch.
|
|
753
|
+
* @param {string} targetType - The target type.
|
|
754
|
+
* @returns {object} Object containing dir and twig properties.
|
|
2704
755
|
*/
|
|
2705
756
|
_determineTraversalStrategy = (branch, targetType) => {
|
|
2706
757
|
const branchLen = branch.length;
|
|
@@ -2709,7 +760,6 @@ export class Finder {
|
|
|
2709
760
|
if (branchLen === 1) {
|
|
2710
761
|
return { dir: DIR_PREV, twig: firstTwig };
|
|
2711
762
|
}
|
|
2712
|
-
// Complex selector (branchLen > 1).
|
|
2713
763
|
const {
|
|
2714
764
|
leaves: [{ name: firstName, type: firstType }]
|
|
2715
765
|
} = firstTwig;
|
|
@@ -2748,37 +798,34 @@ export class Finder {
|
|
|
2748
798
|
return { dir: DIR_PREV, twig: lastTwig };
|
|
2749
799
|
}
|
|
2750
800
|
}
|
|
2751
|
-
// Default strategy for complex selectors.
|
|
2752
801
|
return { dir: DIR_NEXT, twig: firstTwig };
|
|
2753
802
|
};
|
|
2754
803
|
|
|
2755
804
|
/**
|
|
2756
|
-
* Processes pending items
|
|
805
|
+
* Processes pending items to find matches.
|
|
2757
806
|
* @private
|
|
2758
|
-
* @param {Set.<Map>} pendingItems -
|
|
807
|
+
* @param {Set.<Map>} pendingItems - Set of pending items to process.
|
|
808
|
+
* @returns {void}
|
|
2759
809
|
*/
|
|
2760
810
|
_processPendingItems = pendingItems => {
|
|
2761
811
|
if (!pendingItems.size) {
|
|
2762
812
|
return;
|
|
2763
813
|
}
|
|
2764
814
|
if (!this.#rootWalker) {
|
|
2765
|
-
this.#rootWalker = this.
|
|
815
|
+
this.#rootWalker = this.createTreeWalker(this.root);
|
|
2766
816
|
}
|
|
817
|
+
const node = this.#scoped ? this.node : this.root;
|
|
2767
818
|
const walker = this.#rootWalker;
|
|
2768
|
-
let node = this.#root;
|
|
2769
|
-
if (this.#scoped) {
|
|
2770
|
-
node = this.#node;
|
|
2771
|
-
}
|
|
2772
819
|
let nextNode = traverseNode(node, walker);
|
|
2773
820
|
while (nextNode) {
|
|
2774
821
|
const isWithinScope =
|
|
2775
|
-
this
|
|
2776
|
-
nextNode === this
|
|
2777
|
-
this
|
|
822
|
+
this.node.nodeType !== ELEMENT_NODE ||
|
|
823
|
+
nextNode === this.node ||
|
|
824
|
+
this.node.contains(nextNode);
|
|
2778
825
|
if (isWithinScope) {
|
|
2779
826
|
for (const pendingItem of pendingItems) {
|
|
2780
827
|
const { leaves } = pendingItem.get('twig');
|
|
2781
|
-
if (this.
|
|
828
|
+
if (this.matchLeaves(leaves, nextNode, this.matchOpts)) {
|
|
2782
829
|
const index = pendingItem.get('index');
|
|
2783
830
|
this.#ast[index].filtered = true;
|
|
2784
831
|
this.#ast[index].find = true;
|
|
@@ -2793,10 +840,10 @@ export class Finder {
|
|
|
2793
840
|
};
|
|
2794
841
|
|
|
2795
842
|
/**
|
|
2796
|
-
* Collects nodes.
|
|
843
|
+
* Collects all matching nodes into AST nodes array.
|
|
2797
844
|
* @private
|
|
2798
845
|
* @param {string} targetType - The target type.
|
|
2799
|
-
* @returns {Array
|
|
846
|
+
* @returns {Array} Array containing the AST and nodes arrays.
|
|
2800
847
|
*/
|
|
2801
848
|
_collectNodes = targetType => {
|
|
2802
849
|
[this.#ast, this.#nodes] = this._correspond(this.#selector);
|
|
@@ -2855,35 +902,12 @@ export class Finder {
|
|
|
2855
902
|
};
|
|
2856
903
|
|
|
2857
904
|
/**
|
|
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.
|
|
905
|
+
* Matches a node in the next direction.
|
|
2880
906
|
* @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.
|
|
907
|
+
* @param {Array.<object>} branch - The selector branch.
|
|
908
|
+
* @param {Array.<object>|Set.<object>} nodes - The starting nodes.
|
|
909
|
+
* @param {object} [opt] - Options containing combo and index.
|
|
910
|
+
* @returns {object|null} The matched node or null.
|
|
2887
911
|
*/
|
|
2888
912
|
_matchNodeNext = (branch, nodes, opt = {}) => {
|
|
2889
913
|
const { combo, index } = opt;
|
|
@@ -2892,63 +916,86 @@ export class Finder {
|
|
|
2892
916
|
combo,
|
|
2893
917
|
leaves
|
|
2894
918
|
};
|
|
2895
|
-
const
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
919
|
+
for (const node of nodes) {
|
|
920
|
+
for (const nextNode of this.yieldCombinatorMatches(twig, node, {
|
|
921
|
+
dir: DIR_NEXT
|
|
922
|
+
})) {
|
|
923
|
+
if (index === branch.length - 1) {
|
|
924
|
+
return nextNode;
|
|
925
|
+
}
|
|
926
|
+
const result = this._matchNodeNext(branch, new Set([nextNode]), {
|
|
927
|
+
combo: nextCombo,
|
|
928
|
+
index: index + 1
|
|
929
|
+
});
|
|
930
|
+
if (result) {
|
|
931
|
+
return result;
|
|
2900
932
|
}
|
|
2901
|
-
const [nextNode] = sortNodes(nextNodes);
|
|
2902
|
-
return nextNode;
|
|
2903
933
|
}
|
|
2904
|
-
return this._matchNodeNext(branch, nextNodes, {
|
|
2905
|
-
combo: nextCombo,
|
|
2906
|
-
index: index + 1
|
|
2907
|
-
});
|
|
2908
934
|
}
|
|
2909
935
|
return null;
|
|
2910
936
|
};
|
|
2911
937
|
|
|
2912
938
|
/**
|
|
2913
|
-
*
|
|
939
|
+
* Recursively checks for a valid backward path.
|
|
2914
940
|
* @private
|
|
2915
|
-
* @param {
|
|
2916
|
-
* @param {object}
|
|
2917
|
-
* @param {
|
|
2918
|
-
* @param {
|
|
2919
|
-
* @returns {
|
|
941
|
+
* @param {object} node - The starting node.
|
|
942
|
+
* @param {Array.<object>} branch - The selector branch.
|
|
943
|
+
* @param {number} index - The current branch index.
|
|
944
|
+
* @param {object} opt - The match options.
|
|
945
|
+
* @returns {boolean} True if a valid path exists, otherwise false.
|
|
2920
946
|
*/
|
|
2921
|
-
|
|
2922
|
-
|
|
947
|
+
_hasValidPathPrev = (node, branch, index, opt) => {
|
|
948
|
+
if (index < 0) {
|
|
949
|
+
return true;
|
|
950
|
+
}
|
|
2923
951
|
const twig = branch[index];
|
|
2924
|
-
const
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
952
|
+
const { combo, leaves } = twig;
|
|
953
|
+
const comboName = combo.name;
|
|
954
|
+
if (comboName === '+') {
|
|
955
|
+
const refNode = node.previousElementSibling;
|
|
956
|
+
if (refNode && this.matchLeaves(leaves, refNode, opt)) {
|
|
957
|
+
if (this._hasValidPathPrev(refNode, branch, index - 1, opt)) {
|
|
958
|
+
return true;
|
|
959
|
+
}
|
|
2928
960
|
}
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
961
|
+
} else if (comboName === '~') {
|
|
962
|
+
let refNode = node.previousElementSibling;
|
|
963
|
+
while (refNode) {
|
|
964
|
+
if (this.matchLeaves(leaves, refNode, opt)) {
|
|
965
|
+
if (this._hasValidPathPrev(refNode, branch, index - 1, opt)) {
|
|
966
|
+
return true;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
refNode = refNode.previousElementSibling;
|
|
970
|
+
}
|
|
971
|
+
} else if (comboName === '>') {
|
|
972
|
+
const parentNode = node.parentNode;
|
|
973
|
+
if (parentNode && this.matchLeaves(leaves, parentNode, opt)) {
|
|
974
|
+
if (this._hasValidPathPrev(parentNode, branch, index - 1, opt)) {
|
|
975
|
+
return true;
|
|
2936
976
|
}
|
|
2937
977
|
}
|
|
2938
|
-
|
|
2939
|
-
|
|
978
|
+
} else {
|
|
979
|
+
let refNode = node.parentNode;
|
|
980
|
+
while (refNode) {
|
|
981
|
+
if (this.matchLeaves(leaves, refNode, opt)) {
|
|
982
|
+
if (this._hasValidPathPrev(refNode, branch, index - 1, opt)) {
|
|
983
|
+
return true;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
refNode = refNode.parentNode;
|
|
2940
987
|
}
|
|
2941
988
|
}
|
|
2942
|
-
return
|
|
989
|
+
return false;
|
|
2943
990
|
};
|
|
2944
991
|
|
|
2945
992
|
/**
|
|
2946
|
-
* Processes
|
|
993
|
+
* Processes complex branch for all matches.
|
|
2947
994
|
* @private
|
|
2948
|
-
* @param {Array} branch - The selector branch
|
|
2949
|
-
* @param {Array} entryNodes - The
|
|
2950
|
-
* @param {string} dir - The
|
|
2951
|
-
* @returns {Set.<object>}
|
|
995
|
+
* @param {Array.<object>} branch - The selector branch.
|
|
996
|
+
* @param {Array.<object>} entryNodes - The entry nodes.
|
|
997
|
+
* @param {string} dir - The traversal direction.
|
|
998
|
+
* @returns {Set.<object>} Set of matched nodes.
|
|
2952
999
|
*/
|
|
2953
1000
|
_processComplexBranchAll = (branch, entryNodes, dir) => {
|
|
2954
1001
|
const matchedNodes = new Set();
|
|
@@ -2956,42 +1003,29 @@ export class Finder {
|
|
|
2956
1003
|
const lastIndex = branchLen - 1;
|
|
2957
1004
|
if (dir === DIR_NEXT) {
|
|
2958
1005
|
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;
|
|
1006
|
+
const dfs = (node, index, currentCombo) => {
|
|
1007
|
+
const { combo: nextCombo, leaves } = branch[index];
|
|
1008
|
+
const twig = { combo: currentCombo, leaves };
|
|
1009
|
+
for (const nextNode of this.yieldCombinatorMatches(twig, node, {
|
|
1010
|
+
dir
|
|
1011
|
+
})) {
|
|
1012
|
+
if (index === lastIndex) {
|
|
1013
|
+
matchedNodes.add(nextNode);
|
|
2974
1014
|
} else {
|
|
2975
|
-
|
|
1015
|
+
dfs(nextNode, index + 1, nextCombo);
|
|
2976
1016
|
}
|
|
2977
1017
|
}
|
|
1018
|
+
};
|
|
1019
|
+
for (const node of entryNodes) {
|
|
1020
|
+
dfs(node, 1, firstCombo);
|
|
2978
1021
|
}
|
|
2979
|
-
// DIR_PREV
|
|
2980
1022
|
} 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
|
-
}
|
|
1023
|
+
for (let i = 0, len = entryNodes.length; i < len; i++) {
|
|
1024
|
+
const node = entryNodes[i];
|
|
1025
|
+
if (
|
|
1026
|
+
this._hasValidPathPrev(node, branch, lastIndex - 1, this.matchOpts)
|
|
1027
|
+
) {
|
|
1028
|
+
matchedNodes.add(node);
|
|
2995
1029
|
}
|
|
2996
1030
|
}
|
|
2997
1031
|
}
|
|
@@ -2999,18 +1033,17 @@ export class Finder {
|
|
|
2999
1033
|
};
|
|
3000
1034
|
|
|
3001
1035
|
/**
|
|
3002
|
-
* Processes
|
|
1036
|
+
* Processes complex branch for the first match.
|
|
3003
1037
|
* @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 {
|
|
1038
|
+
* @param {Array.<object>} branch - The selector branch.
|
|
1039
|
+
* @param {Array.<object>} entryNodes - The entry nodes.
|
|
1040
|
+
* @param {string} dir - The traversal direction.
|
|
1041
|
+
* @param {string} targetType - The target type.
|
|
1042
|
+
* @returns {object|null} The matched node or null.
|
|
3009
1043
|
*/
|
|
3010
1044
|
_processComplexBranchFirst = (branch, entryNodes, dir, targetType) => {
|
|
3011
1045
|
const branchLen = branch.length;
|
|
3012
1046
|
const lastIndex = branchLen - 1;
|
|
3013
|
-
// DIR_NEXT logic for finding the first match.
|
|
3014
1047
|
if (dir === DIR_NEXT) {
|
|
3015
1048
|
const { combo: entryCombo } = branch[0];
|
|
3016
1049
|
for (const node of entryNodes) {
|
|
@@ -3019,11 +1052,8 @@ export class Finder {
|
|
|
3019
1052
|
index: 1
|
|
3020
1053
|
});
|
|
3021
1054
|
if (matchedNode) {
|
|
3022
|
-
if (this
|
|
3023
|
-
if (
|
|
3024
|
-
matchedNode !== this.#node &&
|
|
3025
|
-
this.#node.contains(matchedNode)
|
|
3026
|
-
) {
|
|
1055
|
+
if (this.node.nodeType === ELEMENT_NODE) {
|
|
1056
|
+
if (matchedNode !== this.node && this.node.contains(matchedNode)) {
|
|
3027
1057
|
return matchedNode;
|
|
3028
1058
|
}
|
|
3029
1059
|
} else {
|
|
@@ -3031,10 +1061,9 @@ export class Finder {
|
|
|
3031
1061
|
}
|
|
3032
1062
|
}
|
|
3033
1063
|
}
|
|
3034
|
-
// Fallback logic if no direct match found.
|
|
3035
1064
|
const { leaves: entryLeaves } = branch[0];
|
|
3036
1065
|
const [entryNode] = entryNodes;
|
|
3037
|
-
if (this
|
|
1066
|
+
if (this.node.contains(entryNode)) {
|
|
3038
1067
|
let [refNode] = this._findNodeWalker(entryLeaves, entryNode, {
|
|
3039
1068
|
targetType
|
|
3040
1069
|
});
|
|
@@ -3044,10 +1073,10 @@ export class Finder {
|
|
|
3044
1073
|
index: 1
|
|
3045
1074
|
});
|
|
3046
1075
|
if (matchedNode) {
|
|
3047
|
-
if (this
|
|
1076
|
+
if (this.node.nodeType === ELEMENT_NODE) {
|
|
3048
1077
|
if (
|
|
3049
|
-
matchedNode !== this
|
|
3050
|
-
this
|
|
1078
|
+
matchedNode !== this.node &&
|
|
1079
|
+
this.node.contains(matchedNode)
|
|
3051
1080
|
) {
|
|
3052
1081
|
return matchedNode;
|
|
3053
1082
|
}
|
|
@@ -3061,28 +1090,30 @@ export class Finder {
|
|
|
3061
1090
|
});
|
|
3062
1091
|
}
|
|
3063
1092
|
}
|
|
3064
|
-
// DIR_PREV logic for finding the first match.
|
|
3065
1093
|
} else {
|
|
3066
|
-
for (
|
|
3067
|
-
const
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
return
|
|
1094
|
+
for (let i = 0, len = entryNodes.length; i < len; i++) {
|
|
1095
|
+
const node = entryNodes[i];
|
|
1096
|
+
if (
|
|
1097
|
+
this._hasValidPathPrev(node, branch, lastIndex - 1, this.matchOpts)
|
|
1098
|
+
) {
|
|
1099
|
+
return node;
|
|
3072
1100
|
}
|
|
3073
1101
|
}
|
|
3074
|
-
// Fallback for TARGET_FIRST.
|
|
3075
1102
|
if (targetType === TARGET_FIRST) {
|
|
3076
1103
|
const { leaves: entryLeaves } = branch[lastIndex];
|
|
3077
|
-
const
|
|
1104
|
+
const entryNode = entryNodes[0];
|
|
3078
1105
|
let [refNode] = this._findNodeWalker(entryLeaves, entryNode, {
|
|
3079
1106
|
targetType
|
|
3080
1107
|
});
|
|
3081
1108
|
while (refNode) {
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
1109
|
+
if (
|
|
1110
|
+
this._hasValidPathPrev(
|
|
1111
|
+
refNode,
|
|
1112
|
+
branch,
|
|
1113
|
+
lastIndex - 1,
|
|
1114
|
+
this.matchOpts
|
|
1115
|
+
)
|
|
1116
|
+
) {
|
|
3086
1117
|
return refNode;
|
|
3087
1118
|
}
|
|
3088
1119
|
[refNode] = this._findNodeWalker(entryLeaves, refNode, {
|
|
@@ -3094,130 +1125,4 @@ export class Finder {
|
|
|
3094
1125
|
}
|
|
3095
1126
|
return null;
|
|
3096
1127
|
};
|
|
3097
|
-
|
|
3098
|
-
/**
|
|
3099
|
-
* Finds matched nodes.
|
|
3100
|
-
* @param {string} targetType - The target type.
|
|
3101
|
-
* @returns {Set.<object>} A collection of matched nodes.
|
|
3102
|
-
*/
|
|
3103
|
-
find = targetType => {
|
|
3104
|
-
let collection;
|
|
3105
|
-
try {
|
|
3106
|
-
collection = this._collectNodes(targetType);
|
|
3107
|
-
} catch (e) {
|
|
3108
|
-
if (this.#check) {
|
|
3109
|
-
let pseudoElement;
|
|
3110
|
-
if (this.#pseudoElement.length) {
|
|
3111
|
-
pseudoElement = this.#pseudoElement.join('');
|
|
3112
|
-
} else {
|
|
3113
|
-
pseudoElement = null;
|
|
3114
|
-
}
|
|
3115
|
-
return {
|
|
3116
|
-
pseudoElement,
|
|
3117
|
-
match: false,
|
|
3118
|
-
ast: this.#selectorAST ?? null
|
|
3119
|
-
};
|
|
3120
|
-
} else {
|
|
3121
|
-
throw e;
|
|
3122
|
-
}
|
|
3123
|
-
}
|
|
3124
|
-
const [[...branches], collectedNodes] = collection;
|
|
3125
|
-
const l = branches.length;
|
|
3126
|
-
let sort = l > 1 && targetType === TARGET_ALL;
|
|
3127
|
-
let nodes = new Set();
|
|
3128
|
-
for (let i = 0; i < l; i++) {
|
|
3129
|
-
const { branch, dir, find } = branches[i];
|
|
3130
|
-
if (!branch.length || !find) {
|
|
3131
|
-
continue;
|
|
3132
|
-
}
|
|
3133
|
-
const entryNodes = collectedNodes[i];
|
|
3134
|
-
const lastIndex = branch.length - 1;
|
|
3135
|
-
// Handle simple selectors (no combinators).
|
|
3136
|
-
if (lastIndex === 0) {
|
|
3137
|
-
if (
|
|
3138
|
-
(targetType === TARGET_ALL || targetType === TARGET_FIRST) &&
|
|
3139
|
-
this.#node.nodeType === ELEMENT_NODE
|
|
3140
|
-
) {
|
|
3141
|
-
for (const node of entryNodes) {
|
|
3142
|
-
if (node !== this.#node && this.#node.contains(node)) {
|
|
3143
|
-
nodes.add(node);
|
|
3144
|
-
if (targetType === TARGET_FIRST) {
|
|
3145
|
-
break;
|
|
3146
|
-
}
|
|
3147
|
-
}
|
|
3148
|
-
}
|
|
3149
|
-
} else if (targetType === TARGET_ALL) {
|
|
3150
|
-
if (nodes.size) {
|
|
3151
|
-
for (const node of entryNodes) {
|
|
3152
|
-
nodes.add(node);
|
|
3153
|
-
}
|
|
3154
|
-
sort = true;
|
|
3155
|
-
} else {
|
|
3156
|
-
nodes = new Set(entryNodes);
|
|
3157
|
-
}
|
|
3158
|
-
} else {
|
|
3159
|
-
if (entryNodes.length) {
|
|
3160
|
-
nodes.add(entryNodes[0]);
|
|
3161
|
-
}
|
|
3162
|
-
}
|
|
3163
|
-
} else {
|
|
3164
|
-
// Handle complex selectors.
|
|
3165
|
-
if (targetType === TARGET_ALL) {
|
|
3166
|
-
const newNodes = this._processComplexBranchAll(
|
|
3167
|
-
branch,
|
|
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;
|
|
3178
|
-
}
|
|
3179
|
-
} else {
|
|
3180
|
-
const matchedNode = this._processComplexBranchFirst(
|
|
3181
|
-
branch,
|
|
3182
|
-
entryNodes,
|
|
3183
|
-
dir,
|
|
3184
|
-
targetType
|
|
3185
|
-
);
|
|
3186
|
-
if (matchedNode) {
|
|
3187
|
-
nodes.add(matchedNode);
|
|
3188
|
-
}
|
|
3189
|
-
}
|
|
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
|
-
} else {
|
|
3198
|
-
pseudoElement = null;
|
|
3199
|
-
}
|
|
3200
|
-
return {
|
|
3201
|
-
match,
|
|
3202
|
-
pseudoElement,
|
|
3203
|
-
ast: this.#selectorAST
|
|
3204
|
-
};
|
|
3205
|
-
}
|
|
3206
|
-
if (targetType === TARGET_FIRST || targetType === TARGET_ALL) {
|
|
3207
|
-
nodes.delete(this.#node);
|
|
3208
|
-
}
|
|
3209
|
-
if ((sort || targetType === TARGET_FIRST) && nodes.size > 1) {
|
|
3210
|
-
return new Set(sortNodes(nodes));
|
|
3211
|
-
}
|
|
3212
|
-
return nodes;
|
|
3213
|
-
};
|
|
3214
|
-
|
|
3215
|
-
/**
|
|
3216
|
-
* Gets AST for selector.
|
|
3217
|
-
* @param {string} selector - The selector text.
|
|
3218
|
-
* @returns {object} The AST for the selector.
|
|
3219
|
-
*/
|
|
3220
|
-
getAST = selector => {
|
|
3221
|
-
return parseSelector(selector);
|
|
3222
|
-
};
|
|
3223
1128
|
}
|