@asamuzakjp/dom-selector 2.0.3-a.1 → 2.0.3-a.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +2 -2
- package/dist/cjs/index.js.map +3 -3
- package/dist/cjs/js/constant.js +1 -1
- package/dist/cjs/js/constant.js.map +3 -3
- package/dist/cjs/js/dom-util.js +1 -1
- package/dist/cjs/js/dom-util.js.map +3 -3
- package/dist/cjs/js/finder.js +2 -0
- package/dist/cjs/js/finder.js.map +7 -0
- package/dist/cjs/js/matcher.js +1 -1
- package/dist/cjs/js/matcher.js.map +3 -3
- package/dist/cjs/js/parser.js +2 -2
- package/dist/cjs/js/parser.js.map +3 -3
- package/package.json +1 -1
- package/src/index.js +19 -19
- package/src/js/constant.js +1 -1
- package/src/js/dom-util.js +71 -21
- package/src/js/finder.js +2722 -0
- package/src/js/matcher.js +339 -3055
- package/src/js/parser.js +65 -4
- package/types/index.d.ts +3 -3
- package/types/js/constant.d.ts +1 -1
- package/types/js/dom-util.d.ts +2 -1
- package/types/js/finder.d.ts +30 -0
- package/types/js/matcher.d.ts +11 -57
- package/types/js/parser.d.ts +2 -0
package/src/js/matcher.js
CHANGED
|
@@ -1,3132 +1,416 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* matcher.js
|
|
3
|
+
* NOTE: Pseudo-class selector matching is done within `./finder.js`.
|
|
4
|
+
* Functions that begin with `_` are not intended for general use,
|
|
5
|
+
* and are exported for testing purposes only.
|
|
3
6
|
*/
|
|
4
7
|
|
|
5
8
|
/* import */
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
getDirectionality, isContentEditable, isInclusive, isInShadowTree,
|
|
9
|
-
isNamespaceDeclared, isPreceding, selectorToNodeProps
|
|
10
|
-
} from './dom-util.js';
|
|
11
|
-
import {
|
|
12
|
-
generateCSS, parseSelector, unescapeSelector, walkAST
|
|
13
|
-
} from './parser.js';
|
|
9
|
+
import { isNamespaceDeclared } from './dom-util.js';
|
|
10
|
+
import { generateCSS, parseAstName, unescapeSelector } from './parser.js';
|
|
14
11
|
|
|
15
12
|
/* constants */
|
|
16
13
|
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
REG_LOGICAL_PSEUDO, REG_SHADOW_HOST, SELECTOR_ATTR, SELECTOR_CLASS,
|
|
20
|
-
SELECTOR_ID, SELECTOR_PSEUDO_CLASS, SELECTOR_PSEUDO_ELEMENT, SELECTOR_TYPE,
|
|
21
|
-
SHOW_ALL, SHOW_DOCUMENT, SHOW_DOCUMENT_FRAGMENT, SHOW_ELEMENT, SYNTAX_ERR,
|
|
22
|
-
TEXT_NODE, TYPE_FROM, TYPE_TO
|
|
14
|
+
ELEMENT_NODE, NOT_SUPPORTED_ERR, SELECTOR_ATTR, SELECTOR_CLASS, SELECTOR_ID,
|
|
15
|
+
SELECTOR_TYPE, SYNTAX_ERR, TYPE_FROM, TYPE_TO
|
|
23
16
|
} from './constant.js';
|
|
24
|
-
const DIR_NEXT = 'next';
|
|
25
|
-
const DIR_PREV = 'prev';
|
|
26
|
-
const TARGET_ALL = 'all';
|
|
27
|
-
const TARGET_FIRST = 'first';
|
|
28
|
-
const TARGET_LINEAL = 'lineal';
|
|
29
|
-
const TARGET_SELF = 'self';
|
|
30
|
-
const WALKER_FILTER = SHOW_DOCUMENT | SHOW_DOCUMENT_FRAGMENT | SHOW_ELEMENT;
|
|
31
17
|
|
|
32
18
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* dir: string|null,
|
|
39
|
-
* filtered: boolean,
|
|
40
|
-
* find: boolean
|
|
41
|
-
* },
|
|
42
|
-
* {
|
|
43
|
-
* branch: branch[],
|
|
44
|
-
* dir: string|null,
|
|
45
|
-
* filtered: boolean,
|
|
46
|
-
* find: boolean
|
|
47
|
-
* }
|
|
48
|
-
* ]
|
|
49
|
-
* #nodes: [
|
|
50
|
-
* Set([node{}, node{}]),
|
|
51
|
-
* Set([node{}, node{}, node{}])
|
|
52
|
-
* ]
|
|
53
|
-
* branch[]: [twig{}, twig{}]
|
|
54
|
-
* twig{}: {
|
|
55
|
-
* combo: leaf{}|null,
|
|
56
|
-
* leaves: leaves[]
|
|
57
|
-
* }
|
|
58
|
-
* leaves[]: [leaf{}, leaf{}, leaf{}]
|
|
59
|
-
* leaf{}: CSSTree AST object
|
|
60
|
-
* node{}: Element node
|
|
19
|
+
* match attribute selector
|
|
20
|
+
* @private
|
|
21
|
+
* @param {object} ast - AST
|
|
22
|
+
* @param {object} node - Element node or structured clone
|
|
23
|
+
* @returns {?object} - matched node
|
|
61
24
|
*/
|
|
62
|
-
export
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* construct
|
|
81
|
-
*/
|
|
82
|
-
constructor() {
|
|
83
|
-
this.#bit = new Map([
|
|
84
|
-
[SELECTOR_PSEUDO_ELEMENT, BIT_01],
|
|
85
|
-
[SELECTOR_ID, BIT_02],
|
|
86
|
-
[SELECTOR_CLASS, BIT_04],
|
|
87
|
-
[SELECTOR_TYPE, BIT_08],
|
|
88
|
-
[SELECTOR_ATTR, BIT_16],
|
|
89
|
-
[SELECTOR_PSEUDO_CLASS, BIT_32]
|
|
90
|
-
]);
|
|
91
|
-
this.#cache = new WeakMap();
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* handle error
|
|
96
|
-
* @param {Error} e - Error
|
|
97
|
-
* @throws Error
|
|
98
|
-
* @returns {void}
|
|
99
|
-
*/
|
|
100
|
-
_onError(e) {
|
|
101
|
-
if (e instanceof DOMException ||
|
|
102
|
-
(this.#window && e instanceof this.#window.DOMException)) {
|
|
103
|
-
if (e.name === NOT_SUPPORTED_ERR) {
|
|
104
|
-
if (this.#warn) {
|
|
105
|
-
console.warn(e.message);
|
|
106
|
-
}
|
|
107
|
-
} else if (this.#window) {
|
|
108
|
-
throw new this.#window.DOMException(e.message, e.name);
|
|
25
|
+
export const _matchAttributeSelector = (ast, node) => {
|
|
26
|
+
const {
|
|
27
|
+
flags: astFlags, matcher: astMatcher, name: astName, value: astValue
|
|
28
|
+
} = ast;
|
|
29
|
+
if (typeof astFlags === 'string' && !/^[is]$/i.test(astFlags)) {
|
|
30
|
+
const css = generateCSS(ast);
|
|
31
|
+
const msg = `Invalid selector ${css}`;
|
|
32
|
+
throw new DOMException(msg, SYNTAX_ERR);
|
|
33
|
+
}
|
|
34
|
+
const { attributes } = node;
|
|
35
|
+
let res;
|
|
36
|
+
if (attributes && attributes.length) {
|
|
37
|
+
const contentType = node.ownerDocument.contentType;
|
|
38
|
+
let caseInsensitive;
|
|
39
|
+
if (contentType === 'text/html') {
|
|
40
|
+
if (typeof astFlags === 'string' && /^s$/i.test(astFlags)) {
|
|
41
|
+
caseInsensitive = false;
|
|
109
42
|
} else {
|
|
110
|
-
|
|
43
|
+
caseInsensitive = true;
|
|
111
44
|
}
|
|
45
|
+
} else if (typeof astFlags === 'string' && /^i$/i.test(astFlags)) {
|
|
46
|
+
caseInsensitive = true;
|
|
112
47
|
} else {
|
|
113
|
-
|
|
48
|
+
caseInsensitive = false;
|
|
114
49
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
case ELEMENT_NODE: {
|
|
137
|
-
if (node.ownerDocument.contains(node)) {
|
|
138
|
-
document = node.ownerDocument;
|
|
139
|
-
root = node.ownerDocument;
|
|
140
|
-
} else {
|
|
141
|
-
let parent = node;
|
|
142
|
-
while (parent) {
|
|
143
|
-
if (parent.parentNode) {
|
|
144
|
-
parent = parent.parentNode;
|
|
145
|
-
} else {
|
|
146
|
-
break;
|
|
50
|
+
let astAttrName = unescapeSelector(astName.name);
|
|
51
|
+
if (caseInsensitive) {
|
|
52
|
+
astAttrName = astAttrName.toLowerCase();
|
|
53
|
+
}
|
|
54
|
+
const attrValues = new Set();
|
|
55
|
+
// namespaced
|
|
56
|
+
if (astAttrName.indexOf('|') > -1) {
|
|
57
|
+
const {
|
|
58
|
+
prefix: astPrefix, localName: astLocalName
|
|
59
|
+
} = parseAstName(astAttrName);
|
|
60
|
+
for (const item of attributes) {
|
|
61
|
+
let { name: itemName, value: itemValue } = item;
|
|
62
|
+
if (caseInsensitive) {
|
|
63
|
+
itemName = itemName.toLowerCase();
|
|
64
|
+
itemValue = itemValue.toLowerCase();
|
|
65
|
+
}
|
|
66
|
+
switch (astPrefix) {
|
|
67
|
+
case '': {
|
|
68
|
+
if (astLocalName === itemName) {
|
|
69
|
+
attrValues.add(itemValue);
|
|
147
70
|
}
|
|
71
|
+
break;
|
|
148
72
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
default: {
|
|
159
|
-
let msg;
|
|
160
|
-
if (node?.nodeName) {
|
|
161
|
-
msg = `Unexpected node ${node.nodeName}`;
|
|
162
|
-
} else {
|
|
163
|
-
const nodeType =
|
|
164
|
-
Object.prototype.toString.call(node).slice(TYPE_FROM, TYPE_TO);
|
|
165
|
-
msg = `Unexpected node ${nodeType}`;
|
|
166
|
-
}
|
|
167
|
-
throw new TypeError(msg);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
const window = document.defaultView;
|
|
171
|
-
return [
|
|
172
|
-
window,
|
|
173
|
-
document,
|
|
174
|
-
root
|
|
175
|
-
];
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* sort AST leaves
|
|
180
|
-
* @param {Array.<object>} leaves - collection of AST leaves
|
|
181
|
-
* @returns {Array.<object>} - sorted leaves
|
|
182
|
-
*/
|
|
183
|
-
_sortLeaves(leaves) {
|
|
184
|
-
const arr = [...leaves];
|
|
185
|
-
if (arr.length > 1) {
|
|
186
|
-
arr.sort((a, b) => {
|
|
187
|
-
const { type: typeA } = a;
|
|
188
|
-
const { type: typeB } = b;
|
|
189
|
-
const bitA = this.#bit.get(typeA);
|
|
190
|
-
const bitB = this.#bit.get(typeB);
|
|
191
|
-
let res;
|
|
192
|
-
if (bitA === bitB) {
|
|
193
|
-
res = 0;
|
|
194
|
-
} else if (bitA > bitB) {
|
|
195
|
-
res = 1;
|
|
196
|
-
} else {
|
|
197
|
-
res = -1;
|
|
198
|
-
}
|
|
199
|
-
return res;
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
return arr;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* correspond #ast and #nodes
|
|
207
|
-
* @param {string} selector - CSS selector
|
|
208
|
-
* @returns {Array.<Array.<object|undefined>>} - array of #ast and #nodes
|
|
209
|
-
*/
|
|
210
|
-
_correspond(selector) {
|
|
211
|
-
const nodes = [];
|
|
212
|
-
let ast;
|
|
213
|
-
let cachedItem = this.#document && this.#cache.get(this.#document);
|
|
214
|
-
if (cachedItem && cachedItem.has(`${selector}`)) {
|
|
215
|
-
ast = cachedItem.get(selector);
|
|
216
|
-
if (typeof ast === 'string') {
|
|
217
|
-
throw new DOMException(ast, SYNTAX_ERR);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
if (ast) {
|
|
221
|
-
const l = ast.length;
|
|
222
|
-
for (let i = 0; i < l; i++) {
|
|
223
|
-
ast[i].dir = null;
|
|
224
|
-
ast[i].filtered = false;
|
|
225
|
-
ast[i].find = false;
|
|
226
|
-
nodes[i] = new Set();
|
|
227
|
-
}
|
|
228
|
-
} else {
|
|
229
|
-
let cssAst;
|
|
230
|
-
try {
|
|
231
|
-
cssAst = parseSelector(selector);
|
|
232
|
-
} catch (e) {
|
|
233
|
-
if (this.#document) {
|
|
234
|
-
if (!cachedItem) {
|
|
235
|
-
cachedItem = new Map();
|
|
73
|
+
case '*': {
|
|
74
|
+
if (itemName.indexOf(':') > -1) {
|
|
75
|
+
if (itemName.endsWith(`:${astLocalName}`)) {
|
|
76
|
+
attrValues.add(itemValue);
|
|
77
|
+
}
|
|
78
|
+
} else if (astLocalName === itemName) {
|
|
79
|
+
attrValues.add(itemValue);
|
|
80
|
+
}
|
|
81
|
+
break;
|
|
236
82
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
if (item && item.type !== COMBINATOR) {
|
|
249
|
-
const leaves = new Set();
|
|
250
|
-
while (item) {
|
|
251
|
-
if (item.type === COMBINATOR) {
|
|
252
|
-
const [nextItem] = items;
|
|
253
|
-
if (nextItem.type === COMBINATOR) {
|
|
254
|
-
const msg = `Invalid selector ${selector}`;
|
|
255
|
-
if (this.#document) {
|
|
256
|
-
if (!cachedItem) {
|
|
257
|
-
cachedItem = new Map();
|
|
258
|
-
}
|
|
259
|
-
cachedItem.set(`${selector}`, msg);
|
|
260
|
-
this.#cache.set(this.#document, cachedItem);
|
|
83
|
+
default: {
|
|
84
|
+
if (itemName.indexOf(':') > -1) {
|
|
85
|
+
const [itemPrefix, itemLocalName] = itemName.split(':');
|
|
86
|
+
// ignore xml:lang
|
|
87
|
+
if (itemPrefix === 'xml' && itemLocalName === 'lang') {
|
|
88
|
+
continue;
|
|
89
|
+
} else if (astPrefix === itemPrefix &&
|
|
90
|
+
astLocalName === itemLocalName) {
|
|
91
|
+
const namespaceDeclared = isNamespaceDeclared(itemPrefix, node);
|
|
92
|
+
if (namespaceDeclared) {
|
|
93
|
+
attrValues.add(itemValue);
|
|
261
94
|
}
|
|
262
|
-
throw new DOMException(msg, SYNTAX_ERR);
|
|
263
95
|
}
|
|
264
|
-
branch.push({
|
|
265
|
-
combo: item,
|
|
266
|
-
leaves: this._sortLeaves(leaves)
|
|
267
|
-
});
|
|
268
|
-
leaves.clear();
|
|
269
|
-
} else if (item) {
|
|
270
|
-
leaves.add(item);
|
|
271
|
-
}
|
|
272
|
-
if (items.length) {
|
|
273
|
-
item = items.shift();
|
|
274
|
-
} else {
|
|
275
|
-
branch.push({
|
|
276
|
-
combo: null,
|
|
277
|
-
leaves: this._sortLeaves(leaves)
|
|
278
|
-
});
|
|
279
|
-
leaves.clear();
|
|
280
|
-
break;
|
|
281
96
|
}
|
|
282
97
|
}
|
|
283
98
|
}
|
|
284
|
-
ast.push({
|
|
285
|
-
branch,
|
|
286
|
-
dir: null,
|
|
287
|
-
filtered: false,
|
|
288
|
-
find: false
|
|
289
|
-
});
|
|
290
|
-
nodes[i] = new Set();
|
|
291
|
-
i++;
|
|
292
|
-
}
|
|
293
|
-
if (this.#document) {
|
|
294
|
-
if (!cachedItem) {
|
|
295
|
-
cachedItem = new Map();
|
|
296
|
-
}
|
|
297
|
-
cachedItem.set(`${selector}`, ast);
|
|
298
|
-
this.#cache.set(this.#document, cachedItem);
|
|
299
99
|
}
|
|
300
|
-
}
|
|
301
|
-
return [
|
|
302
|
-
ast,
|
|
303
|
-
nodes
|
|
304
|
-
];
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* traverse tree walker
|
|
309
|
-
* @param {object} [node] - Element node
|
|
310
|
-
* @param {object} [walker] - tree walker
|
|
311
|
-
* @returns {?object} - current node
|
|
312
|
-
*/
|
|
313
|
-
_traverse(node = {}, walker = this.#tree) {
|
|
314
|
-
let current;
|
|
315
|
-
let refNode = walker.currentNode;
|
|
316
|
-
if (node.nodeType === ELEMENT_NODE && refNode === node) {
|
|
317
|
-
current = refNode;
|
|
318
100
|
} else {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
current = refNode;
|
|
332
|
-
break;
|
|
101
|
+
for (let { name: itemName, value: itemValue } of attributes) {
|
|
102
|
+
if (caseInsensitive) {
|
|
103
|
+
itemName = itemName.toLowerCase();
|
|
104
|
+
itemValue = itemValue.toLowerCase();
|
|
105
|
+
}
|
|
106
|
+
if (itemName.indexOf(':') > -1) {
|
|
107
|
+
const [itemPrefix, itemLocalName] = itemName.split(':');
|
|
108
|
+
// ignore xml:lang
|
|
109
|
+
if (itemPrefix === 'xml' && itemLocalName === 'lang') {
|
|
110
|
+
continue;
|
|
111
|
+
} else if (astAttrName === itemLocalName) {
|
|
112
|
+
attrValues.add(itemValue);
|
|
333
113
|
}
|
|
334
|
-
|
|
114
|
+
} else if (astAttrName === itemName) {
|
|
115
|
+
attrValues.add(itemValue);
|
|
335
116
|
}
|
|
336
|
-
} else {
|
|
337
|
-
current = refNode;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
return current ?? null;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* collect nth child
|
|
345
|
-
* @param {object} anb - An+B options
|
|
346
|
-
* @param {number} anb.a - a
|
|
347
|
-
* @param {number} anb.b - b
|
|
348
|
-
* @param {boolean} [anb.reverse] - reverse order
|
|
349
|
-
* @param {object} [anb.selector] - AST
|
|
350
|
-
* @param {object} node - Element node
|
|
351
|
-
* @returns {Set.<object>} - collection of matched nodes
|
|
352
|
-
*/
|
|
353
|
-
_collectNthChild(anb, node) {
|
|
354
|
-
const { a, b, reverse, selector } = anb;
|
|
355
|
-
const { parentNode } = node;
|
|
356
|
-
let matched = new Set();
|
|
357
|
-
let selectorBranches;
|
|
358
|
-
if (selector) {
|
|
359
|
-
if (this.#cache.has(selector)) {
|
|
360
|
-
selectorBranches = this.#cache.get(selector);
|
|
361
|
-
} else {
|
|
362
|
-
selectorBranches = walkAST(selector);
|
|
363
|
-
this.#cache.set(selector, selectorBranches);
|
|
364
117
|
}
|
|
365
118
|
}
|
|
366
|
-
if (
|
|
367
|
-
const
|
|
368
|
-
let
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
refNode = this._traverse(parentNode, walker);
|
|
375
|
-
const selectorNodes = new Set();
|
|
376
|
-
if (selectorBranches) {
|
|
377
|
-
refNode = this._traverse(parentNode, walker);
|
|
378
|
-
refNode = walker.firstChild();
|
|
379
|
-
while (refNode) {
|
|
380
|
-
let bool;
|
|
381
|
-
for (const leaves of selectorBranches) {
|
|
382
|
-
bool = this._matchLeaves(leaves, refNode);
|
|
383
|
-
if (!bool) {
|
|
384
|
-
break;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
if (bool) {
|
|
388
|
-
selectorNodes.add(refNode);
|
|
389
|
-
}
|
|
390
|
-
refNode = walker.nextSibling();
|
|
119
|
+
if (attrValues.size) {
|
|
120
|
+
const { name: astIdentValue, value: astStringValue } = astValue ?? {};
|
|
121
|
+
let attrValue;
|
|
122
|
+
if (astIdentValue) {
|
|
123
|
+
if (caseInsensitive) {
|
|
124
|
+
attrValue = astIdentValue.toLowerCase();
|
|
125
|
+
} else {
|
|
126
|
+
attrValue = astIdentValue;
|
|
391
127
|
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
let i = 0;
|
|
398
|
-
refNode = this._traverse(parentNode, walker);
|
|
399
|
-
if (reverse) {
|
|
400
|
-
refNode = walker.lastChild();
|
|
401
|
-
} else {
|
|
402
|
-
refNode = walker.firstChild();
|
|
403
|
-
}
|
|
404
|
-
while (refNode) {
|
|
405
|
-
if (selectorNodes.has(refNode)) {
|
|
406
|
-
if (i === b - 1) {
|
|
407
|
-
matched.add(refNode);
|
|
408
|
-
break;
|
|
409
|
-
}
|
|
410
|
-
i++;
|
|
411
|
-
}
|
|
412
|
-
if (reverse) {
|
|
413
|
-
refNode = walker.previousSibling();
|
|
414
|
-
} else {
|
|
415
|
-
refNode = walker.nextSibling();
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
} else if (!selector) {
|
|
419
|
-
let i = 0;
|
|
420
|
-
refNode = this._traverse(parentNode, walker);
|
|
421
|
-
if (reverse) {
|
|
422
|
-
refNode = walker.lastChild();
|
|
423
|
-
} else {
|
|
424
|
-
refNode = walker.firstChild();
|
|
425
|
-
}
|
|
426
|
-
while (refNode) {
|
|
427
|
-
if (i === b - 1) {
|
|
428
|
-
matched.add(refNode);
|
|
429
|
-
break;
|
|
430
|
-
}
|
|
431
|
-
if (reverse) {
|
|
432
|
-
refNode = walker.previousSibling();
|
|
433
|
-
} else {
|
|
434
|
-
refNode = walker.nextSibling();
|
|
435
|
-
}
|
|
436
|
-
i++;
|
|
437
|
-
}
|
|
438
|
-
}
|
|
128
|
+
} else if (astStringValue) {
|
|
129
|
+
if (caseInsensitive) {
|
|
130
|
+
attrValue = astStringValue.toLowerCase();
|
|
131
|
+
} else {
|
|
132
|
+
attrValue = astStringValue;
|
|
439
133
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
134
|
+
} else if (astStringValue === '') {
|
|
135
|
+
attrValue = astStringValue;
|
|
136
|
+
}
|
|
137
|
+
switch (astMatcher) {
|
|
138
|
+
case '=': {
|
|
139
|
+
if (typeof attrValue === 'string' && attrValues.has(attrValue)) {
|
|
140
|
+
res = node;
|
|
446
141
|
}
|
|
142
|
+
break;
|
|
447
143
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
refNode = walker.firstChild();
|
|
456
|
-
}
|
|
457
|
-
while (refNode) {
|
|
458
|
-
if (refNode && nth >= 0 && nth < l) {
|
|
459
|
-
if (selectorNodes.size) {
|
|
460
|
-
if (selectorNodes.has(refNode)) {
|
|
461
|
-
if (j === nth) {
|
|
462
|
-
matched.add(refNode);
|
|
463
|
-
nth += a;
|
|
464
|
-
}
|
|
465
|
-
if (a > 0) {
|
|
466
|
-
j++;
|
|
467
|
-
} else {
|
|
468
|
-
j--;
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
} else if (i === nth) {
|
|
472
|
-
if (!selector) {
|
|
473
|
-
matched.add(refNode);
|
|
474
|
-
}
|
|
475
|
-
nth += a;
|
|
476
|
-
}
|
|
477
|
-
if (reverse) {
|
|
478
|
-
refNode = walker.previousSibling();
|
|
479
|
-
} else {
|
|
480
|
-
refNode = walker.nextSibling();
|
|
144
|
+
case '~=': {
|
|
145
|
+
if (attrValue && typeof attrValue === 'string') {
|
|
146
|
+
for (const value of attrValues) {
|
|
147
|
+
const item = new Set(value.split(/\s+/));
|
|
148
|
+
if (item.has(attrValue)) {
|
|
149
|
+
res = node;
|
|
150
|
+
break;
|
|
481
151
|
}
|
|
482
|
-
i++;
|
|
483
|
-
} else {
|
|
484
|
-
break;
|
|
485
152
|
}
|
|
486
153
|
}
|
|
154
|
+
break;
|
|
487
155
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
(a + b) === 1) {
|
|
495
|
-
if (selectorBranches) {
|
|
496
|
-
let bool;
|
|
497
|
-
for (const leaves of selectorBranches) {
|
|
498
|
-
bool = this._matchLeaves(leaves, node);
|
|
499
|
-
if (bool) {
|
|
500
|
-
break;
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
if (bool) {
|
|
504
|
-
matched.add(node);
|
|
505
|
-
}
|
|
506
|
-
} else {
|
|
507
|
-
matched.add(node);
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
return matched;
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
/**
|
|
514
|
-
* collect nth of type
|
|
515
|
-
* @param {object} anb - An+B options
|
|
516
|
-
* @param {number} anb.a - a
|
|
517
|
-
* @param {number} anb.b - b
|
|
518
|
-
* @param {boolean} [anb.reverse] - reverse order
|
|
519
|
-
* @param {object} node - Element node
|
|
520
|
-
* @returns {Set.<object>} - collection of matched nodes
|
|
521
|
-
*/
|
|
522
|
-
_collectNthOfType(anb, node) {
|
|
523
|
-
const { a, b, reverse } = anb;
|
|
524
|
-
const { localName, parentNode, prefix } = node;
|
|
525
|
-
let matched = new Set();
|
|
526
|
-
if (parentNode) {
|
|
527
|
-
const walker = this.#document.createTreeWalker(parentNode, WALKER_FILTER);
|
|
528
|
-
let l = 0;
|
|
529
|
-
let refNode = walker.firstChild();
|
|
530
|
-
while (refNode) {
|
|
531
|
-
l++;
|
|
532
|
-
refNode = walker.nextSibling();
|
|
533
|
-
}
|
|
534
|
-
// :first-of-type, :last-of-type
|
|
535
|
-
if (a === 0) {
|
|
536
|
-
if (b > 0 && b <= l) {
|
|
537
|
-
let j = 0;
|
|
538
|
-
refNode = this._traverse(parentNode, walker);
|
|
539
|
-
if (reverse) {
|
|
540
|
-
refNode = walker.lastChild();
|
|
541
|
-
} else {
|
|
542
|
-
refNode = walker.firstChild();
|
|
543
|
-
}
|
|
544
|
-
while (refNode) {
|
|
545
|
-
const { localName: itemLocalName, prefix: itemPrefix } = refNode;
|
|
546
|
-
if (itemLocalName === localName && itemPrefix === prefix) {
|
|
547
|
-
if (j === b - 1) {
|
|
548
|
-
matched.add(refNode);
|
|
156
|
+
case '|=': {
|
|
157
|
+
if (attrValue && typeof attrValue === 'string') {
|
|
158
|
+
let item;
|
|
159
|
+
for (const value of attrValues) {
|
|
160
|
+
if (value === attrValue || value.startsWith(`${attrValue}-`)) {
|
|
161
|
+
item = value;
|
|
549
162
|
break;
|
|
550
163
|
}
|
|
551
|
-
j++;
|
|
552
164
|
}
|
|
553
|
-
if (
|
|
554
|
-
|
|
555
|
-
} else {
|
|
556
|
-
refNode = walker.nextSibling();
|
|
165
|
+
if (item) {
|
|
166
|
+
res = node;
|
|
557
167
|
}
|
|
558
168
|
}
|
|
169
|
+
break;
|
|
559
170
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
if (nth >= 0 && nth < l) {
|
|
569
|
-
let j = a > 0 ? 0 : b - 1;
|
|
570
|
-
refNode = this._traverse(parentNode, walker);
|
|
571
|
-
if (reverse) {
|
|
572
|
-
refNode = walker.lastChild();
|
|
573
|
-
} else {
|
|
574
|
-
refNode = walker.firstChild();
|
|
575
|
-
}
|
|
576
|
-
while (refNode) {
|
|
577
|
-
const { localName: itemLocalName, prefix: itemPrefix } = refNode;
|
|
578
|
-
if (itemLocalName === localName && itemPrefix === prefix) {
|
|
579
|
-
if (j === nth) {
|
|
580
|
-
matched.add(refNode);
|
|
581
|
-
nth += a;
|
|
582
|
-
}
|
|
583
|
-
if (nth < 0 || nth >= l) {
|
|
171
|
+
case '^=': {
|
|
172
|
+
if (attrValue && typeof attrValue === 'string') {
|
|
173
|
+
let item;
|
|
174
|
+
for (const value of attrValues) {
|
|
175
|
+
if (value.startsWith(`${attrValue}`)) {
|
|
176
|
+
item = value;
|
|
584
177
|
break;
|
|
585
|
-
} else if (a > 0) {
|
|
586
|
-
j++;
|
|
587
|
-
} else {
|
|
588
|
-
j--;
|
|
589
178
|
}
|
|
590
179
|
}
|
|
591
|
-
if (
|
|
592
|
-
|
|
593
|
-
} else {
|
|
594
|
-
refNode = walker.nextSibling();
|
|
180
|
+
if (item) {
|
|
181
|
+
res = node;
|
|
595
182
|
}
|
|
596
183
|
}
|
|
184
|
+
break;
|
|
597
185
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
matched.add(node);
|
|
606
|
-
}
|
|
607
|
-
return matched;
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
/**
|
|
611
|
-
* match An+B
|
|
612
|
-
* @param {object} ast - AST
|
|
613
|
-
* @param {object} node - Element node
|
|
614
|
-
* @param {string} nthName - nth pseudo-class name
|
|
615
|
-
* @returns {Set.<object>} - collection of matched nodes
|
|
616
|
-
*/
|
|
617
|
-
_matchAnPlusB(ast, node, nthName) {
|
|
618
|
-
const {
|
|
619
|
-
nth: {
|
|
620
|
-
a,
|
|
621
|
-
b,
|
|
622
|
-
name: nthIdentName
|
|
623
|
-
},
|
|
624
|
-
selector
|
|
625
|
-
} = ast;
|
|
626
|
-
const identName = unescapeSelector(nthIdentName);
|
|
627
|
-
const anbMap = new Map();
|
|
628
|
-
if (identName) {
|
|
629
|
-
if (identName === 'even') {
|
|
630
|
-
anbMap.set('a', 2);
|
|
631
|
-
anbMap.set('b', 0);
|
|
632
|
-
} else if (identName === 'odd') {
|
|
633
|
-
anbMap.set('a', 2);
|
|
634
|
-
anbMap.set('b', 1);
|
|
635
|
-
}
|
|
636
|
-
if (nthName.indexOf('last') > -1) {
|
|
637
|
-
anbMap.set('reverse', true);
|
|
638
|
-
}
|
|
639
|
-
} else {
|
|
640
|
-
if (typeof a === 'string' && /-?\d+/.test(a)) {
|
|
641
|
-
anbMap.set('a', a * 1);
|
|
642
|
-
} else {
|
|
643
|
-
anbMap.set('a', 0);
|
|
644
|
-
}
|
|
645
|
-
if (typeof b === 'string' && /-?\d+/.test(b)) {
|
|
646
|
-
anbMap.set('b', b * 1);
|
|
647
|
-
} else {
|
|
648
|
-
anbMap.set('b', 0);
|
|
649
|
-
}
|
|
650
|
-
if (nthName.indexOf('last') > -1) {
|
|
651
|
-
anbMap.set('reverse', true);
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
let matched = new Set();
|
|
655
|
-
if (anbMap.has('a') && anbMap.has('b')) {
|
|
656
|
-
if (/^nth-(?:last-)?child$/.test(nthName)) {
|
|
657
|
-
if (selector) {
|
|
658
|
-
anbMap.set('selector', selector);
|
|
659
|
-
}
|
|
660
|
-
const anb = Object.fromEntries(anbMap);
|
|
661
|
-
const nodes = this._collectNthChild(anb, node);
|
|
662
|
-
if (nodes.size) {
|
|
663
|
-
matched = nodes;
|
|
664
|
-
}
|
|
665
|
-
} else if (/^nth-(?:last-)?of-type$/.test(nthName)) {
|
|
666
|
-
const anb = Object.fromEntries(anbMap);
|
|
667
|
-
const nodes = this._collectNthOfType(anb, node);
|
|
668
|
-
if (nodes.size) {
|
|
669
|
-
matched = nodes;
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
return matched;
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
/**
|
|
677
|
-
* match pseudo element selector
|
|
678
|
-
* @param {string} astName - AST name
|
|
679
|
-
* @param {object} [opt] - options
|
|
680
|
-
* @param {boolean} [opt.forgive] - is forgiving selector list
|
|
681
|
-
* @throws {DOMException}
|
|
682
|
-
* @returns {void}
|
|
683
|
-
*/
|
|
684
|
-
_matchPseudoElementSelector(astName, opt = {}) {
|
|
685
|
-
const { forgive } = opt;
|
|
686
|
-
switch (astName) {
|
|
687
|
-
case 'after':
|
|
688
|
-
case 'backdrop':
|
|
689
|
-
case 'before':
|
|
690
|
-
case 'cue':
|
|
691
|
-
case 'cue-region':
|
|
692
|
-
case 'first-letter':
|
|
693
|
-
case 'first-line':
|
|
694
|
-
case 'file-selector-button':
|
|
695
|
-
case 'marker':
|
|
696
|
-
case 'placeholder':
|
|
697
|
-
case 'selection':
|
|
698
|
-
case 'target-text': {
|
|
699
|
-
if (this.#warn) {
|
|
700
|
-
const msg = `Unsupported pseudo-element ::${astName}`;
|
|
701
|
-
throw new DOMException(msg, NOT_SUPPORTED_ERR);
|
|
702
|
-
}
|
|
703
|
-
break;
|
|
704
|
-
}
|
|
705
|
-
case 'part':
|
|
706
|
-
case 'slotted': {
|
|
707
|
-
if (this.#warn) {
|
|
708
|
-
const msg = `Unsupported pseudo-element ::${astName}()`;
|
|
709
|
-
throw new DOMException(msg, NOT_SUPPORTED_ERR);
|
|
710
|
-
}
|
|
711
|
-
break;
|
|
712
|
-
}
|
|
713
|
-
default: {
|
|
714
|
-
if (astName.startsWith('-webkit-')) {
|
|
715
|
-
if (this.#warn) {
|
|
716
|
-
const msg = `Unsupported pseudo-element ::${astName}`;
|
|
717
|
-
throw new DOMException(msg, NOT_SUPPORTED_ERR);
|
|
718
|
-
}
|
|
719
|
-
} else if (!forgive) {
|
|
720
|
-
const msg = `Unknown pseudo-element ::${astName}`;
|
|
721
|
-
throw new DOMException(msg, SYNTAX_ERR);
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
/**
|
|
728
|
-
* match directionality pseudo-class - :dir()
|
|
729
|
-
* @param {object} ast - AST
|
|
730
|
-
* @param {object} node - Element node
|
|
731
|
-
* @returns {?object} - matched node
|
|
732
|
-
*/
|
|
733
|
-
_matchDirectionPseudoClass(ast, node) {
|
|
734
|
-
const astName = unescapeSelector(ast.name);
|
|
735
|
-
const dir = getDirectionality(node);
|
|
736
|
-
let res;
|
|
737
|
-
if (astName === dir) {
|
|
738
|
-
res = node;
|
|
739
|
-
}
|
|
740
|
-
return res ?? null;
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
/**
|
|
744
|
-
* match language pseudo-class - :lang()
|
|
745
|
-
* @see https://datatracker.ietf.org/doc/html/rfc4647#section-3.3.1
|
|
746
|
-
* @param {object} ast - AST
|
|
747
|
-
* @param {object} node - Element node
|
|
748
|
-
* @returns {?object} - matched node
|
|
749
|
-
*/
|
|
750
|
-
_matchLanguagePseudoClass(ast, node) {
|
|
751
|
-
const astName = unescapeSelector(ast.name);
|
|
752
|
-
let res;
|
|
753
|
-
if (astName === '*') {
|
|
754
|
-
if (node.hasAttribute('lang')) {
|
|
755
|
-
if (node.getAttribute('lang')) {
|
|
756
|
-
res = node;
|
|
757
|
-
}
|
|
758
|
-
} else {
|
|
759
|
-
let parent = node.parentNode;
|
|
760
|
-
while (parent) {
|
|
761
|
-
if (parent.nodeType === ELEMENT_NODE) {
|
|
762
|
-
if (parent.hasAttribute('lang')) {
|
|
763
|
-
if (parent.getAttribute('lang')) {
|
|
764
|
-
res = node;
|
|
186
|
+
case '$=': {
|
|
187
|
+
if (attrValue && typeof attrValue === 'string') {
|
|
188
|
+
let item;
|
|
189
|
+
for (const value of attrValues) {
|
|
190
|
+
if (value.endsWith(`${attrValue}`)) {
|
|
191
|
+
item = value;
|
|
192
|
+
break;
|
|
765
193
|
}
|
|
766
|
-
break;
|
|
767
194
|
}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
break;
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
} else if (astName) {
|
|
775
|
-
const langPart = `(?:-${ALPHA_NUM})*`;
|
|
776
|
-
const regLang = new RegExp(`^(?:\\*-)?${ALPHA_NUM}${langPart}$`, 'i');
|
|
777
|
-
if (regLang.test(astName)) {
|
|
778
|
-
let regExtendedLang;
|
|
779
|
-
if (astName.indexOf('-') > -1) {
|
|
780
|
-
const [langMain, langSub, ...langRest] = astName.split('-');
|
|
781
|
-
let extendedMain;
|
|
782
|
-
if (langMain === '*') {
|
|
783
|
-
extendedMain = `${ALPHA_NUM}${langPart}`;
|
|
784
|
-
} else {
|
|
785
|
-
extendedMain = `${langMain}${langPart}`;
|
|
786
|
-
}
|
|
787
|
-
const extendedSub = `-${langSub}${langPart}`;
|
|
788
|
-
const len = langRest.length;
|
|
789
|
-
let extendedRest = '';
|
|
790
|
-
if (len) {
|
|
791
|
-
for (let i = 0; i < len; i++) {
|
|
792
|
-
extendedRest += `-${langRest[i]}${langPart}`;
|
|
195
|
+
if (item) {
|
|
196
|
+
res = node;
|
|
793
197
|
}
|
|
794
198
|
}
|
|
795
|
-
|
|
796
|
-
new RegExp(`^${extendedMain}${extendedSub}${extendedRest}$`, 'i');
|
|
797
|
-
} else {
|
|
798
|
-
regExtendedLang = new RegExp(`^${astName}${langPart}$`, 'i');
|
|
199
|
+
break;
|
|
799
200
|
}
|
|
800
|
-
|
|
801
|
-
if (
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
while (parent) {
|
|
807
|
-
if (parent.nodeType === ELEMENT_NODE) {
|
|
808
|
-
if (parent.hasAttribute('lang')) {
|
|
809
|
-
const value = parent.getAttribute('lang');
|
|
810
|
-
if (regExtendedLang.test(value)) {
|
|
811
|
-
res = node;
|
|
812
|
-
}
|
|
201
|
+
case '*=': {
|
|
202
|
+
if (attrValue && typeof attrValue === 'string') {
|
|
203
|
+
let item;
|
|
204
|
+
for (const value of attrValues) {
|
|
205
|
+
if (value.includes(`${attrValue}`)) {
|
|
206
|
+
item = value;
|
|
813
207
|
break;
|
|
814
208
|
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
209
|
+
}
|
|
210
|
+
if (item) {
|
|
211
|
+
res = node;
|
|
818
212
|
}
|
|
819
213
|
}
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
return res ?? null;
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
/**
|
|
827
|
-
* match :has() pseudo-class function
|
|
828
|
-
* @param {Array.<object>} leaves - AST leaves
|
|
829
|
-
* @param {object} node - Element node
|
|
830
|
-
* @returns {boolean} - result
|
|
831
|
-
*/
|
|
832
|
-
_matchHasPseudoFunc(leaves, node) {
|
|
833
|
-
let bool;
|
|
834
|
-
if (Array.isArray(leaves) && leaves.length) {
|
|
835
|
-
const [leaf] = leaves;
|
|
836
|
-
const { type: leafType } = leaf;
|
|
837
|
-
let combo;
|
|
838
|
-
if (leafType === COMBINATOR) {
|
|
839
|
-
combo = leaves.shift();
|
|
840
|
-
} else {
|
|
841
|
-
combo = {
|
|
842
|
-
name: ' ',
|
|
843
|
-
type: COMBINATOR
|
|
844
|
-
};
|
|
845
|
-
}
|
|
846
|
-
const twigLeaves = [];
|
|
847
|
-
while (leaves.length) {
|
|
848
|
-
const [item] = leaves;
|
|
849
|
-
const { type: itemType } = item;
|
|
850
|
-
if (itemType === COMBINATOR) {
|
|
851
214
|
break;
|
|
852
|
-
} else {
|
|
853
|
-
twigLeaves.push(leaves.shift());
|
|
854
215
|
}
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
leaves: twigLeaves
|
|
859
|
-
};
|
|
860
|
-
const nodes = this._matchCombinator(twig, node, {
|
|
861
|
-
dir: DIR_NEXT
|
|
862
|
-
});
|
|
863
|
-
if (nodes.size) {
|
|
864
|
-
if (leaves.length) {
|
|
865
|
-
for (const nextNode of nodes) {
|
|
866
|
-
bool =
|
|
867
|
-
this._matchHasPseudoFunc(Object.assign([], leaves), nextNode);
|
|
868
|
-
if (bool) {
|
|
869
|
-
break;
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
} else {
|
|
873
|
-
bool = true;
|
|
216
|
+
case null:
|
|
217
|
+
default: {
|
|
218
|
+
res = node;
|
|
874
219
|
}
|
|
875
220
|
}
|
|
876
221
|
}
|
|
877
|
-
return !!bool;
|
|
878
222
|
}
|
|
223
|
+
return res ?? null;
|
|
224
|
+
};
|
|
879
225
|
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
226
|
+
/**
|
|
227
|
+
* match class selector
|
|
228
|
+
* @private
|
|
229
|
+
* @param {object} ast - AST
|
|
230
|
+
* @param {object} node - Element node or structured clone
|
|
231
|
+
* @returns {?object} - matched node
|
|
232
|
+
*/
|
|
233
|
+
export const _matchClassSelector = (ast, node) => {
|
|
234
|
+
const astName = unescapeSelector(ast.name);
|
|
235
|
+
const { classList } = node;
|
|
236
|
+
let res;
|
|
237
|
+
if (classList.contains(astName)) {
|
|
238
|
+
res = node;
|
|
239
|
+
}
|
|
240
|
+
return res ?? null;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* match ID selector
|
|
245
|
+
* @private
|
|
246
|
+
* @param {object} ast - AST
|
|
247
|
+
* @param {object} node - Element node or structured clone
|
|
248
|
+
* @returns {?object} - matched node
|
|
249
|
+
*/
|
|
250
|
+
export const _matchIDSelector = (ast, node) => {
|
|
251
|
+
const astName = unescapeSelector(ast.name);
|
|
252
|
+
const { id } = node;
|
|
253
|
+
let res;
|
|
254
|
+
if (astName === id) {
|
|
255
|
+
res = node;
|
|
256
|
+
}
|
|
257
|
+
return res ?? null;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* match type selector
|
|
262
|
+
* @private
|
|
263
|
+
* @param {object} ast - AST
|
|
264
|
+
* @param {object} node - Element node or structured clone
|
|
265
|
+
* @param {object} opt - options
|
|
266
|
+
* @param {boolean} [opt.forgive] - forgive undeclared namespace
|
|
267
|
+
* @returns {?object} - matched node
|
|
268
|
+
*/
|
|
269
|
+
export const _matchTypeSelector = (ast, node, opt = {}) => {
|
|
270
|
+
const astName = unescapeSelector(ast.name);
|
|
271
|
+
const { localName, namespaceURI, prefix } = node;
|
|
272
|
+
const { forgive } = opt;
|
|
273
|
+
let {
|
|
274
|
+
prefix: astPrefix, localName: astLocalName
|
|
275
|
+
} = parseAstName(astName, node);
|
|
276
|
+
const contentType = node.ownerDocument.contentType;
|
|
277
|
+
if (contentType === 'text/html') {
|
|
278
|
+
astPrefix = astPrefix.toLowerCase();
|
|
279
|
+
astLocalName = astLocalName.toLowerCase();
|
|
280
|
+
}
|
|
281
|
+
let nodePrefix;
|
|
282
|
+
let nodeLocalName;
|
|
283
|
+
// just in case that the namespaced content is parsed as text/html
|
|
284
|
+
if (localName.indexOf(':') > -1) {
|
|
285
|
+
[nodePrefix, nodeLocalName] = localName.split(':');
|
|
286
|
+
} else {
|
|
287
|
+
nodePrefix = prefix || '';
|
|
288
|
+
nodeLocalName = localName;
|
|
289
|
+
}
|
|
290
|
+
let res;
|
|
291
|
+
if (astPrefix === '' && nodePrefix === '') {
|
|
292
|
+
if (namespaceURI === null &&
|
|
293
|
+
(astLocalName === '*' || astLocalName === nodeLocalName)) {
|
|
294
|
+
res = node;
|
|
295
|
+
}
|
|
296
|
+
} else if (astPrefix === '*') {
|
|
297
|
+
if (astLocalName === '*' || astLocalName === nodeLocalName) {
|
|
298
|
+
res = node;
|
|
299
|
+
}
|
|
300
|
+
} else if (astPrefix && nodePrefix) {
|
|
301
|
+
const namespaceDeclared = isNamespaceDeclared(nodePrefix, node);
|
|
302
|
+
if (astPrefix === nodePrefix) {
|
|
303
|
+
if (namespaceDeclared) {
|
|
304
|
+
if (astLocalName === '*' || astLocalName === nodeLocalName) {
|
|
947
305
|
res = node;
|
|
948
306
|
}
|
|
949
|
-
} else if (
|
|
950
|
-
|
|
307
|
+
} else if (!forgive) {
|
|
308
|
+
const msg = `Undeclared namespace ${astPrefix}`;
|
|
309
|
+
throw new DOMException(msg, SYNTAX_ERR);
|
|
951
310
|
}
|
|
952
311
|
}
|
|
953
|
-
return res ?? null;
|
|
954
312
|
}
|
|
313
|
+
return res ?? null;
|
|
314
|
+
};
|
|
955
315
|
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
const
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
const
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
const msg = `Unsupported pseudo-class :${astName}()`;
|
|
1050
|
-
throw new DOMException(msg, NOT_SUPPORTED_ERR);
|
|
1051
|
-
}
|
|
1052
|
-
break;
|
|
1053
|
-
}
|
|
1054
|
-
case 'host':
|
|
1055
|
-
case 'host-context': {
|
|
1056
|
-
// ignore
|
|
1057
|
-
break;
|
|
1058
|
-
}
|
|
1059
|
-
default: {
|
|
1060
|
-
if (!forgive) {
|
|
1061
|
-
const msg = `Unknown pseudo-class :${astName}()`;
|
|
1062
|
-
throw new DOMException(msg, SYNTAX_ERR);
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
} else {
|
|
1068
|
-
const regAnchor = /^a(?:rea)?$/;
|
|
1069
|
-
const regFormCtrl =
|
|
1070
|
-
/^(?:(?:fieldse|inpu|selec)t|button|opt(?:group|ion)|textarea)$/;
|
|
1071
|
-
const regFormValidity = /^(?:(?:inpu|selec)t|button|form|textarea)$/;
|
|
1072
|
-
const regInteract = /^d(?:etails|ialog)$/;
|
|
1073
|
-
const regTypeCheck = /^(?:checkbox|radio)$/;
|
|
1074
|
-
const regTypeDate = /^(?:date(?:time-local)?|month|time|week)$/;
|
|
1075
|
-
const regTypeRange =
|
|
1076
|
-
/(?:(?:rang|tim)e|date(?:time-local)?|month|number|week)$/;
|
|
1077
|
-
const regTypeText = /^(?:(?:emai|te|ur)l|number|password|search|text)$/;
|
|
1078
|
-
switch (astName) {
|
|
1079
|
-
case 'any-link':
|
|
1080
|
-
case 'link': {
|
|
1081
|
-
if (regAnchor.test(localName) && node.hasAttribute('href')) {
|
|
1082
|
-
matched.add(node);
|
|
1083
|
-
}
|
|
1084
|
-
break;
|
|
1085
|
-
}
|
|
1086
|
-
case 'local-link': {
|
|
1087
|
-
if (regAnchor.test(localName) && node.hasAttribute('href')) {
|
|
1088
|
-
const { href, origin, pathname } = new URL(this.#document.URL);
|
|
1089
|
-
const attrURL = new URL(node.getAttribute('href'), href);
|
|
1090
|
-
if (attrURL.origin === origin && attrURL.pathname === pathname) {
|
|
1091
|
-
matched.add(node);
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
break;
|
|
1095
|
-
}
|
|
1096
|
-
case 'visited': {
|
|
1097
|
-
// prevent fingerprinting
|
|
1098
|
-
break;
|
|
1099
|
-
}
|
|
1100
|
-
case 'target': {
|
|
1101
|
-
const { hash } = new URL(this.#document.URL);
|
|
1102
|
-
if (node.id && hash === `#${node.id}` &&
|
|
1103
|
-
this.#document.contains(node)) {
|
|
1104
|
-
matched.add(node);
|
|
1105
|
-
}
|
|
1106
|
-
break;
|
|
1107
|
-
}
|
|
1108
|
-
case 'target-within': {
|
|
1109
|
-
const { hash } = new URL(this.#document.URL);
|
|
1110
|
-
if (hash) {
|
|
1111
|
-
const id = hash.replace(/^#/, '');
|
|
1112
|
-
let current = this.#document.getElementById(id);
|
|
1113
|
-
while (current) {
|
|
1114
|
-
if (current === node) {
|
|
1115
|
-
matched.add(node);
|
|
1116
|
-
break;
|
|
1117
|
-
}
|
|
1118
|
-
current = current.parentNode;
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
break;
|
|
1122
|
-
}
|
|
1123
|
-
case 'scope': {
|
|
1124
|
-
if (this.#node.nodeType === ELEMENT_NODE) {
|
|
1125
|
-
if (node === this.#node) {
|
|
1126
|
-
matched.add(node);
|
|
1127
|
-
}
|
|
1128
|
-
} else if (node === this.#document.documentElement) {
|
|
1129
|
-
matched.add(node);
|
|
1130
|
-
}
|
|
1131
|
-
break;
|
|
1132
|
-
}
|
|
1133
|
-
case 'focus': {
|
|
1134
|
-
if (node === this.#document.activeElement) {
|
|
1135
|
-
matched.add(node);
|
|
1136
|
-
}
|
|
1137
|
-
break;
|
|
1138
|
-
}
|
|
1139
|
-
case 'focus-within': {
|
|
1140
|
-
let current = this.#document.activeElement;
|
|
1141
|
-
while (current) {
|
|
1142
|
-
if (current === node) {
|
|
1143
|
-
matched.add(node);
|
|
1144
|
-
break;
|
|
1145
|
-
}
|
|
1146
|
-
current = current.parentNode;
|
|
1147
|
-
}
|
|
1148
|
-
break;
|
|
1149
|
-
}
|
|
1150
|
-
case 'open': {
|
|
1151
|
-
if (regInteract.test(localName) && node.hasAttribute('open')) {
|
|
1152
|
-
matched.add(node);
|
|
1153
|
-
}
|
|
1154
|
-
break;
|
|
1155
|
-
}
|
|
1156
|
-
case 'closed': {
|
|
1157
|
-
if (regInteract.test(localName) && !node.hasAttribute('open')) {
|
|
1158
|
-
matched.add(node);
|
|
1159
|
-
}
|
|
1160
|
-
break;
|
|
1161
|
-
}
|
|
1162
|
-
case 'disabled': {
|
|
1163
|
-
if (regFormCtrl.test(localName) || isCustomElementName(localName)) {
|
|
1164
|
-
if (node.disabled || node.hasAttribute('disabled')) {
|
|
1165
|
-
matched.add(node);
|
|
1166
|
-
} else {
|
|
1167
|
-
let parent = parentNode;
|
|
1168
|
-
while (parent) {
|
|
1169
|
-
if (parent.localName === 'fieldset') {
|
|
1170
|
-
break;
|
|
1171
|
-
}
|
|
1172
|
-
parent = parent.parentNode;
|
|
1173
|
-
}
|
|
1174
|
-
if (parent && parentNode.localName !== 'legend' &&
|
|
1175
|
-
parent.hasAttribute('disabled')) {
|
|
1176
|
-
matched.add(node);
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
break;
|
|
1181
|
-
}
|
|
1182
|
-
case 'enabled': {
|
|
1183
|
-
if ((regFormCtrl.test(localName) || isCustomElementName(localName)) &&
|
|
1184
|
-
!(node.disabled && node.hasAttribute('disabled'))) {
|
|
1185
|
-
matched.add(node);
|
|
1186
|
-
}
|
|
1187
|
-
break;
|
|
1188
|
-
}
|
|
1189
|
-
case 'read-only': {
|
|
1190
|
-
switch (localName) {
|
|
1191
|
-
case 'textarea': {
|
|
1192
|
-
if (node.readonly || node.hasAttribute('readonly') ||
|
|
1193
|
-
node.disabled || node.hasAttribute('disabled')) {
|
|
1194
|
-
matched.add(node);
|
|
1195
|
-
}
|
|
1196
|
-
break;
|
|
1197
|
-
}
|
|
1198
|
-
case 'input': {
|
|
1199
|
-
if ((!node.type || regTypeDate.test(node.type) ||
|
|
1200
|
-
regTypeText.test(node.type)) &&
|
|
1201
|
-
(node.readonly || node.hasAttribute('readonly') ||
|
|
1202
|
-
node.disabled || node.hasAttribute('disabled'))) {
|
|
1203
|
-
matched.add(node);
|
|
1204
|
-
}
|
|
1205
|
-
break;
|
|
1206
|
-
}
|
|
1207
|
-
default: {
|
|
1208
|
-
if (!isContentEditable(node)) {
|
|
1209
|
-
matched.add(node);
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
break;
|
|
1214
|
-
}
|
|
1215
|
-
case 'read-write': {
|
|
1216
|
-
switch (localName) {
|
|
1217
|
-
case 'textarea': {
|
|
1218
|
-
if (!(node.readonly || node.hasAttribute('readonly') ||
|
|
1219
|
-
node.disabled || node.hasAttribute('disabled'))) {
|
|
1220
|
-
matched.add(node);
|
|
1221
|
-
}
|
|
1222
|
-
break;
|
|
1223
|
-
}
|
|
1224
|
-
case 'input': {
|
|
1225
|
-
if ((!node.type || regTypeDate.test(node.type) ||
|
|
1226
|
-
regTypeText.test(node.type)) &&
|
|
1227
|
-
!(node.readonly || node.hasAttribute('readonly') ||
|
|
1228
|
-
node.disabled || node.hasAttribute('disabled'))) {
|
|
1229
|
-
matched.add(node);
|
|
1230
|
-
}
|
|
1231
|
-
break;
|
|
1232
|
-
}
|
|
1233
|
-
default: {
|
|
1234
|
-
if (isContentEditable(node)) {
|
|
1235
|
-
matched.add(node);
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
break;
|
|
1240
|
-
}
|
|
1241
|
-
case 'placeholder-shown': {
|
|
1242
|
-
let targetNode;
|
|
1243
|
-
if (localName === 'textarea') {
|
|
1244
|
-
targetNode = node;
|
|
1245
|
-
} else if (localName === 'input') {
|
|
1246
|
-
if (node.hasAttribute('type')) {
|
|
1247
|
-
if (regTypeText.test(node.getAttribute('type'))) {
|
|
1248
|
-
targetNode = node;
|
|
1249
|
-
}
|
|
1250
|
-
} else {
|
|
1251
|
-
targetNode = node;
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
if (targetNode && node.value === '' &&
|
|
1255
|
-
node.hasAttribute('placeholder') &&
|
|
1256
|
-
node.getAttribute('placeholder').trim().length) {
|
|
1257
|
-
matched.add(node);
|
|
1258
|
-
}
|
|
1259
|
-
break;
|
|
1260
|
-
}
|
|
1261
|
-
case 'checked': {
|
|
1262
|
-
if ((node.checked && localName === 'input' &&
|
|
1263
|
-
node.hasAttribute('type') &&
|
|
1264
|
-
regTypeCheck.test(node.getAttribute('type'))) ||
|
|
1265
|
-
(node.selected && localName === 'option')) {
|
|
1266
|
-
matched.add(node);
|
|
1267
|
-
}
|
|
1268
|
-
break;
|
|
1269
|
-
}
|
|
1270
|
-
case 'indeterminate': {
|
|
1271
|
-
if ((node.indeterminate && localName === 'input' &&
|
|
1272
|
-
node.type === 'checkbox') ||
|
|
1273
|
-
(localName === 'progress' && !node.hasAttribute('value'))) {
|
|
1274
|
-
matched.add(node);
|
|
1275
|
-
} else if (localName === 'input' && node.type === 'radio' &&
|
|
1276
|
-
!node.hasAttribute('checked')) {
|
|
1277
|
-
const nodeName = node.name;
|
|
1278
|
-
let parent = node.parentNode;
|
|
1279
|
-
while (parent) {
|
|
1280
|
-
if (parent.localName === 'form') {
|
|
1281
|
-
break;
|
|
1282
|
-
}
|
|
1283
|
-
parent = parent.parentNode;
|
|
1284
|
-
}
|
|
1285
|
-
if (!parent) {
|
|
1286
|
-
parent = this.#document.documentElement;
|
|
1287
|
-
}
|
|
1288
|
-
let checked;
|
|
1289
|
-
const nodes = [].slice.call(parent.getElementsByTagName('input'));
|
|
1290
|
-
for (const item of nodes) {
|
|
1291
|
-
if (item.getAttribute('type') === 'radio') {
|
|
1292
|
-
if (nodeName) {
|
|
1293
|
-
if (item.getAttribute('name') === nodeName) {
|
|
1294
|
-
checked = !!item.checked;
|
|
1295
|
-
}
|
|
1296
|
-
} else if (!item.hasAttribute('name')) {
|
|
1297
|
-
checked = !!item.checked;
|
|
1298
|
-
}
|
|
1299
|
-
if (checked) {
|
|
1300
|
-
break;
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
if (!checked) {
|
|
1305
|
-
matched.add(node);
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
break;
|
|
1309
|
-
}
|
|
1310
|
-
case 'default': {
|
|
1311
|
-
const regTypeReset = /^(?:button|reset)$/;
|
|
1312
|
-
const regTypeSubmit = /^(?:image|submit)$/;
|
|
1313
|
-
// button[type="submit"], input[type="submit"], input[type="image"]
|
|
1314
|
-
if ((localName === 'button' &&
|
|
1315
|
-
!(node.hasAttribute('type') &&
|
|
1316
|
-
regTypeReset.test(node.getAttribute('type')))) ||
|
|
1317
|
-
(localName === 'input' && node.hasAttribute('type') &&
|
|
1318
|
-
regTypeSubmit.test(node.getAttribute('type')))) {
|
|
1319
|
-
let form = node.parentNode;
|
|
1320
|
-
while (form) {
|
|
1321
|
-
if (form.localName === 'form') {
|
|
1322
|
-
break;
|
|
1323
|
-
}
|
|
1324
|
-
form = form.parentNode;
|
|
1325
|
-
}
|
|
1326
|
-
if (form) {
|
|
1327
|
-
const walker =
|
|
1328
|
-
this.#document.createTreeWalker(form, SHOW_ELEMENT);
|
|
1329
|
-
let nextNode = walker.firstChild();
|
|
1330
|
-
while (nextNode) {
|
|
1331
|
-
const nodeName = nextNode.localName;
|
|
1332
|
-
let m;
|
|
1333
|
-
if (nodeName === 'button') {
|
|
1334
|
-
m = !(nextNode.hasAttribute('type') &&
|
|
1335
|
-
regTypeReset.test(nextNode.getAttribute('type')));
|
|
1336
|
-
} else if (nodeName === 'input') {
|
|
1337
|
-
m = nextNode.hasAttribute('type') &&
|
|
1338
|
-
regTypeSubmit.test(nextNode.getAttribute('type'));
|
|
1339
|
-
}
|
|
1340
|
-
if (m) {
|
|
1341
|
-
if (nextNode === node) {
|
|
1342
|
-
matched.add(node);
|
|
1343
|
-
}
|
|
1344
|
-
break;
|
|
1345
|
-
}
|
|
1346
|
-
nextNode = walker.nextNode();
|
|
1347
|
-
}
|
|
1348
|
-
}
|
|
1349
|
-
// input[type="checkbox"], input[type="radio"]
|
|
1350
|
-
} else if (localName === 'input' && node.hasAttribute('type') &&
|
|
1351
|
-
regTypeCheck.test(node.getAttribute('type')) &&
|
|
1352
|
-
(node.checked || node.hasAttribute('checked'))) {
|
|
1353
|
-
matched.add(node);
|
|
1354
|
-
// option
|
|
1355
|
-
} else if (localName === 'option') {
|
|
1356
|
-
let isMultiple = false;
|
|
1357
|
-
let parent = parentNode;
|
|
1358
|
-
while (parent) {
|
|
1359
|
-
if (parent.localName === 'datalist') {
|
|
1360
|
-
break;
|
|
1361
|
-
} else if (parent.localName === 'select') {
|
|
1362
|
-
if (parent.multiple || parent.hasAttribute('multiple')) {
|
|
1363
|
-
isMultiple = true;
|
|
1364
|
-
}
|
|
1365
|
-
break;
|
|
1366
|
-
}
|
|
1367
|
-
parent = parent.parentNode;
|
|
1368
|
-
}
|
|
1369
|
-
if (isMultiple) {
|
|
1370
|
-
if (node.selected || node.hasAttribute('selected')) {
|
|
1371
|
-
matched.add(node);
|
|
1372
|
-
}
|
|
1373
|
-
} else {
|
|
1374
|
-
const defaultOpt = new Set();
|
|
1375
|
-
const walker =
|
|
1376
|
-
this.#document.createTreeWalker(parentNode, SHOW_ELEMENT);
|
|
1377
|
-
let refNode = walker.firstChild();
|
|
1378
|
-
while (refNode) {
|
|
1379
|
-
if (refNode.selected || refNode.hasAttribute('selected')) {
|
|
1380
|
-
defaultOpt.add(refNode);
|
|
1381
|
-
break;
|
|
1382
|
-
}
|
|
1383
|
-
refNode = walker.nextSibling();
|
|
1384
|
-
}
|
|
1385
|
-
if (defaultOpt.size) {
|
|
1386
|
-
if (defaultOpt.has(node)) {
|
|
1387
|
-
matched.add(node);
|
|
1388
|
-
}
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
|
-
}
|
|
1392
|
-
break;
|
|
1393
|
-
}
|
|
1394
|
-
case 'valid': {
|
|
1395
|
-
if (regFormValidity.test(localName)) {
|
|
1396
|
-
if (node.checkValidity()) {
|
|
1397
|
-
matched.add(node);
|
|
1398
|
-
}
|
|
1399
|
-
} else if (localName === 'fieldset') {
|
|
1400
|
-
let bool;
|
|
1401
|
-
const walker = this.#document.createTreeWalker(node, SHOW_ELEMENT);
|
|
1402
|
-
let refNode = walker.firstChild();
|
|
1403
|
-
while (refNode) {
|
|
1404
|
-
if (regFormValidity.test(refNode.localName)) {
|
|
1405
|
-
bool = refNode.checkValidity();
|
|
1406
|
-
if (!bool) {
|
|
1407
|
-
break;
|
|
1408
|
-
}
|
|
1409
|
-
}
|
|
1410
|
-
refNode = walker.nextNode();
|
|
1411
|
-
}
|
|
1412
|
-
if (bool) {
|
|
1413
|
-
matched.add(node);
|
|
1414
|
-
}
|
|
1415
|
-
}
|
|
1416
|
-
break;
|
|
1417
|
-
}
|
|
1418
|
-
case 'invalid': {
|
|
1419
|
-
if (regFormValidity.test(localName)) {
|
|
1420
|
-
if (!node.checkValidity()) {
|
|
1421
|
-
matched.add(node);
|
|
1422
|
-
}
|
|
1423
|
-
} else if (localName === 'fieldset') {
|
|
1424
|
-
let bool;
|
|
1425
|
-
const walker = this.#document.createTreeWalker(node, SHOW_ELEMENT);
|
|
1426
|
-
let refNode = walker.firstChild();
|
|
1427
|
-
while (refNode) {
|
|
1428
|
-
if (regFormValidity.test(refNode.localName)) {
|
|
1429
|
-
bool = refNode.checkValidity();
|
|
1430
|
-
if (!bool) {
|
|
1431
|
-
break;
|
|
1432
|
-
}
|
|
1433
|
-
}
|
|
1434
|
-
refNode = walker.nextNode();
|
|
1435
|
-
}
|
|
1436
|
-
if (!bool) {
|
|
1437
|
-
matched.add(node);
|
|
1438
|
-
}
|
|
1439
|
-
}
|
|
1440
|
-
break;
|
|
1441
|
-
}
|
|
1442
|
-
case 'in-range': {
|
|
1443
|
-
if (localName === 'input' &&
|
|
1444
|
-
!(node.readonly || node.hasAttribute('readonly')) &&
|
|
1445
|
-
!(node.disabled || node.hasAttribute('disabled')) &&
|
|
1446
|
-
node.hasAttribute('type') &&
|
|
1447
|
-
regTypeRange.test(node.getAttribute('type')) &&
|
|
1448
|
-
!(node.validity.rangeUnderflow ||
|
|
1449
|
-
node.validity.rangeOverflow) &&
|
|
1450
|
-
(node.hasAttribute('min') || node.hasAttribute('max') ||
|
|
1451
|
-
node.getAttribute('type') === 'range')) {
|
|
1452
|
-
matched.add(node);
|
|
1453
|
-
}
|
|
1454
|
-
break;
|
|
1455
|
-
}
|
|
1456
|
-
case 'out-of-range': {
|
|
1457
|
-
if (localName === 'input' &&
|
|
1458
|
-
!(node.readonly || node.hasAttribute('readonly')) &&
|
|
1459
|
-
!(node.disabled || node.hasAttribute('disabled')) &&
|
|
1460
|
-
node.hasAttribute('type') &&
|
|
1461
|
-
regTypeRange.test(node.getAttribute('type')) &&
|
|
1462
|
-
(node.validity.rangeUnderflow || node.validity.rangeOverflow)) {
|
|
1463
|
-
matched.add(node);
|
|
1464
|
-
}
|
|
1465
|
-
break;
|
|
1466
|
-
}
|
|
1467
|
-
case 'required': {
|
|
1468
|
-
let targetNode;
|
|
1469
|
-
if (/^(?:select|textarea)$/.test(localName)) {
|
|
1470
|
-
targetNode = node;
|
|
1471
|
-
} else if (localName === 'input') {
|
|
1472
|
-
if (node.hasAttribute('type')) {
|
|
1473
|
-
const inputType = node.getAttribute('type');
|
|
1474
|
-
if (inputType === 'file' || regTypeCheck.test(inputType) ||
|
|
1475
|
-
regTypeDate.test(inputType) || regTypeText.test(inputType)) {
|
|
1476
|
-
targetNode = node;
|
|
1477
|
-
}
|
|
1478
|
-
} else {
|
|
1479
|
-
targetNode = node;
|
|
1480
|
-
}
|
|
1481
|
-
}
|
|
1482
|
-
if (targetNode &&
|
|
1483
|
-
(node.required || node.hasAttribute('required'))) {
|
|
1484
|
-
matched.add(node);
|
|
1485
|
-
}
|
|
1486
|
-
break;
|
|
1487
|
-
}
|
|
1488
|
-
case 'optional': {
|
|
1489
|
-
let targetNode;
|
|
1490
|
-
if (/^(?:select|textarea)$/.test(localName)) {
|
|
1491
|
-
targetNode = node;
|
|
1492
|
-
} else if (localName === 'input') {
|
|
1493
|
-
if (node.hasAttribute('type')) {
|
|
1494
|
-
const inputType = node.getAttribute('type');
|
|
1495
|
-
if (inputType === 'file' || regTypeCheck.test(inputType) ||
|
|
1496
|
-
regTypeDate.test(inputType) || regTypeText.test(inputType)) {
|
|
1497
|
-
targetNode = node;
|
|
1498
|
-
}
|
|
1499
|
-
} else {
|
|
1500
|
-
targetNode = node;
|
|
1501
|
-
}
|
|
1502
|
-
}
|
|
1503
|
-
if (targetNode &&
|
|
1504
|
-
!(node.required || node.hasAttribute('required'))) {
|
|
1505
|
-
matched.add(node);
|
|
1506
|
-
}
|
|
1507
|
-
break;
|
|
1508
|
-
}
|
|
1509
|
-
case 'root': {
|
|
1510
|
-
if (node === this.#document.documentElement) {
|
|
1511
|
-
matched.add(node);
|
|
1512
|
-
}
|
|
1513
|
-
break;
|
|
1514
|
-
}
|
|
1515
|
-
case 'empty': {
|
|
1516
|
-
if (node.hasChildNodes()) {
|
|
1517
|
-
let bool;
|
|
1518
|
-
const walker = this.#document.createTreeWalker(node, SHOW_ALL);
|
|
1519
|
-
let refNode = walker.firstChild();
|
|
1520
|
-
while (refNode) {
|
|
1521
|
-
bool = refNode.nodeType !== ELEMENT_NODE &&
|
|
1522
|
-
refNode.nodeType !== TEXT_NODE;
|
|
1523
|
-
if (!bool) {
|
|
1524
|
-
break;
|
|
1525
|
-
}
|
|
1526
|
-
refNode = walker.nextSibling();
|
|
1527
|
-
}
|
|
1528
|
-
if (bool) {
|
|
1529
|
-
matched.add(node);
|
|
1530
|
-
}
|
|
1531
|
-
} else {
|
|
1532
|
-
matched.add(node);
|
|
1533
|
-
}
|
|
1534
|
-
break;
|
|
1535
|
-
}
|
|
1536
|
-
case 'first-child': {
|
|
1537
|
-
if ((parentNode && node === parentNode.firstElementChild) ||
|
|
1538
|
-
(node === this.#root && this.#root.nodeType === ELEMENT_NODE)) {
|
|
1539
|
-
matched.add(node);
|
|
1540
|
-
}
|
|
1541
|
-
break;
|
|
1542
|
-
}
|
|
1543
|
-
case 'last-child': {
|
|
1544
|
-
if ((parentNode && node === parentNode.lastElementChild) ||
|
|
1545
|
-
(node === this.#root && this.#root.nodeType === ELEMENT_NODE)) {
|
|
1546
|
-
matched.add(node);
|
|
1547
|
-
}
|
|
1548
|
-
break;
|
|
1549
|
-
}
|
|
1550
|
-
case 'only-child': {
|
|
1551
|
-
if ((parentNode &&
|
|
1552
|
-
node === parentNode.firstElementChild &&
|
|
1553
|
-
node === parentNode.lastElementChild) ||
|
|
1554
|
-
(node === this.#root && this.#root.nodeType === ELEMENT_NODE)) {
|
|
1555
|
-
matched.add(node);
|
|
1556
|
-
}
|
|
1557
|
-
break;
|
|
1558
|
-
}
|
|
1559
|
-
case 'first-of-type': {
|
|
1560
|
-
if (parentNode) {
|
|
1561
|
-
const [node1] = this._collectNthOfType({
|
|
1562
|
-
a: 0,
|
|
1563
|
-
b: 1
|
|
1564
|
-
}, node);
|
|
1565
|
-
if (node1) {
|
|
1566
|
-
matched.add(node1);
|
|
1567
|
-
}
|
|
1568
|
-
} else if (node === this.#root &&
|
|
1569
|
-
this.#root.nodeType === ELEMENT_NODE) {
|
|
1570
|
-
matched.add(node);
|
|
1571
|
-
}
|
|
1572
|
-
break;
|
|
1573
|
-
}
|
|
1574
|
-
case 'last-of-type': {
|
|
1575
|
-
if (parentNode) {
|
|
1576
|
-
const [node1] = this._collectNthOfType({
|
|
1577
|
-
a: 0,
|
|
1578
|
-
b: 1,
|
|
1579
|
-
reverse: true
|
|
1580
|
-
}, node);
|
|
1581
|
-
if (node1) {
|
|
1582
|
-
matched.add(node1);
|
|
1583
|
-
}
|
|
1584
|
-
} else if (node === this.#root &&
|
|
1585
|
-
this.#root.nodeType === ELEMENT_NODE) {
|
|
1586
|
-
matched.add(node);
|
|
1587
|
-
}
|
|
1588
|
-
break;
|
|
1589
|
-
}
|
|
1590
|
-
case 'only-of-type': {
|
|
1591
|
-
if (parentNode) {
|
|
1592
|
-
const [node1] = this._collectNthOfType({
|
|
1593
|
-
a: 0,
|
|
1594
|
-
b: 1
|
|
1595
|
-
}, node);
|
|
1596
|
-
if (node1 === node) {
|
|
1597
|
-
const [node2] = this._collectNthOfType({
|
|
1598
|
-
a: 0,
|
|
1599
|
-
b: 1,
|
|
1600
|
-
reverse: true
|
|
1601
|
-
}, node);
|
|
1602
|
-
if (node2 === node) {
|
|
1603
|
-
matched.add(node);
|
|
1604
|
-
}
|
|
1605
|
-
}
|
|
1606
|
-
} else if (node === this.#root &&
|
|
1607
|
-
this.#root.nodeType === ELEMENT_NODE) {
|
|
1608
|
-
matched.add(node);
|
|
1609
|
-
}
|
|
1610
|
-
break;
|
|
1611
|
-
}
|
|
1612
|
-
case 'host':
|
|
1613
|
-
case 'host-context': {
|
|
1614
|
-
// ignore
|
|
1615
|
-
break;
|
|
1616
|
-
}
|
|
1617
|
-
// legacy pseudo-elements
|
|
1618
|
-
case 'after':
|
|
1619
|
-
case 'before':
|
|
1620
|
-
case 'first-letter':
|
|
1621
|
-
case 'first-line': {
|
|
1622
|
-
if (this.#warn) {
|
|
1623
|
-
const msg = `Unsupported pseudo-element ::${astName}`;
|
|
1624
|
-
throw new DOMException(msg, NOT_SUPPORTED_ERR);
|
|
1625
|
-
}
|
|
1626
|
-
break;
|
|
1627
|
-
}
|
|
1628
|
-
case 'active':
|
|
1629
|
-
case 'autofill':
|
|
1630
|
-
case 'blank':
|
|
1631
|
-
case 'buffering':
|
|
1632
|
-
case 'current':
|
|
1633
|
-
case 'defined':
|
|
1634
|
-
case 'focus-visible':
|
|
1635
|
-
case 'fullscreen':
|
|
1636
|
-
case 'future':
|
|
1637
|
-
case 'hover':
|
|
1638
|
-
case 'modal':
|
|
1639
|
-
case 'muted':
|
|
1640
|
-
case 'past':
|
|
1641
|
-
case 'paused':
|
|
1642
|
-
case 'picture-in-picture':
|
|
1643
|
-
case 'playing':
|
|
1644
|
-
case 'seeking':
|
|
1645
|
-
case 'stalled':
|
|
1646
|
-
case 'user-invalid':
|
|
1647
|
-
case 'user-valid':
|
|
1648
|
-
case 'volume-locked':
|
|
1649
|
-
case '-webkit-autofill': {
|
|
1650
|
-
if (this.#warn) {
|
|
1651
|
-
const msg = `Unsupported pseudo-class :${astName}`;
|
|
1652
|
-
throw new DOMException(msg, NOT_SUPPORTED_ERR);
|
|
1653
|
-
}
|
|
1654
|
-
break;
|
|
1655
|
-
}
|
|
1656
|
-
default: {
|
|
1657
|
-
if (astName.startsWith('-webkit-')) {
|
|
1658
|
-
if (this.#warn) {
|
|
1659
|
-
const msg = `Unsupported pseudo-class :${astName}`;
|
|
1660
|
-
throw new DOMException(msg, NOT_SUPPORTED_ERR);
|
|
1661
|
-
}
|
|
1662
|
-
} else if (!forgive) {
|
|
1663
|
-
const msg = `Unknown pseudo-class :${astName}`;
|
|
1664
|
-
throw new DOMException(msg, SYNTAX_ERR);
|
|
1665
|
-
}
|
|
1666
|
-
}
|
|
1667
|
-
}
|
|
1668
|
-
}
|
|
1669
|
-
return matched;
|
|
1670
|
-
}
|
|
1671
|
-
|
|
1672
|
-
/**
|
|
1673
|
-
* match attribute selector
|
|
1674
|
-
* @param {object} ast - AST
|
|
1675
|
-
* @param {object} node - Element node
|
|
1676
|
-
* @returns {?object} - matched node
|
|
1677
|
-
*/
|
|
1678
|
-
_matchAttributeSelector(ast, node) {
|
|
1679
|
-
const {
|
|
1680
|
-
flags: astFlags, matcher: astMatcher, name: astName, value: astValue
|
|
1681
|
-
} = ast;
|
|
1682
|
-
if (typeof astFlags === 'string' && !/^[is]$/i.test(astFlags)) {
|
|
1683
|
-
const css = generateCSS(ast);
|
|
1684
|
-
const msg = `Invalid selector ${css}`;
|
|
1685
|
-
throw new DOMException(msg, SYNTAX_ERR);
|
|
1686
|
-
}
|
|
1687
|
-
const { attributes } = node;
|
|
1688
|
-
let res;
|
|
1689
|
-
if (attributes && attributes.length) {
|
|
1690
|
-
let caseInsensitive;
|
|
1691
|
-
if (this.#document.contentType === 'text/html') {
|
|
1692
|
-
if (typeof astFlags === 'string' && /^s$/i.test(astFlags)) {
|
|
1693
|
-
caseInsensitive = false;
|
|
1694
|
-
} else {
|
|
1695
|
-
caseInsensitive = true;
|
|
1696
|
-
}
|
|
1697
|
-
} else if (typeof astFlags === 'string' && /^i$/i.test(astFlags)) {
|
|
1698
|
-
caseInsensitive = true;
|
|
1699
|
-
} else {
|
|
1700
|
-
caseInsensitive = false;
|
|
1701
|
-
}
|
|
1702
|
-
let astAttrName = unescapeSelector(astName.name);
|
|
1703
|
-
if (caseInsensitive) {
|
|
1704
|
-
astAttrName = astAttrName.toLowerCase();
|
|
1705
|
-
}
|
|
1706
|
-
const attrValues = new Set();
|
|
1707
|
-
// namespaced
|
|
1708
|
-
if (astAttrName.indexOf('|') > -1) {
|
|
1709
|
-
const {
|
|
1710
|
-
prefix: astAttrPrefix, tagName: astAttrLocalName
|
|
1711
|
-
} = selectorToNodeProps(astAttrName);
|
|
1712
|
-
for (let { name: itemName, value: itemValue } of attributes) {
|
|
1713
|
-
if (caseInsensitive) {
|
|
1714
|
-
itemName = itemName.toLowerCase();
|
|
1715
|
-
itemValue = itemValue.toLowerCase();
|
|
1716
|
-
}
|
|
1717
|
-
switch (astAttrPrefix) {
|
|
1718
|
-
case '': {
|
|
1719
|
-
if (astAttrLocalName === itemName) {
|
|
1720
|
-
attrValues.add(itemValue);
|
|
1721
|
-
}
|
|
1722
|
-
break;
|
|
1723
|
-
}
|
|
1724
|
-
case '*': {
|
|
1725
|
-
if (itemName.indexOf(':') > -1) {
|
|
1726
|
-
if (itemName.endsWith(`:${astAttrLocalName}`)) {
|
|
1727
|
-
attrValues.add(itemValue);
|
|
1728
|
-
}
|
|
1729
|
-
} else if (astAttrLocalName === itemName) {
|
|
1730
|
-
attrValues.add(itemValue);
|
|
1731
|
-
}
|
|
1732
|
-
break;
|
|
1733
|
-
}
|
|
1734
|
-
default: {
|
|
1735
|
-
if (itemName.indexOf(':') > -1) {
|
|
1736
|
-
const [itemNamePrefix, itemNameLocalName] = itemName.split(':');
|
|
1737
|
-
if (astAttrPrefix === itemNamePrefix &&
|
|
1738
|
-
astAttrLocalName === itemNameLocalName &&
|
|
1739
|
-
isNamespaceDeclared(astAttrPrefix, node)) {
|
|
1740
|
-
attrValues.add(itemValue);
|
|
1741
|
-
}
|
|
1742
|
-
}
|
|
1743
|
-
}
|
|
1744
|
-
}
|
|
1745
|
-
}
|
|
1746
|
-
} else {
|
|
1747
|
-
for (let { name: itemName, value: itemValue } of attributes) {
|
|
1748
|
-
if (caseInsensitive) {
|
|
1749
|
-
itemName = itemName.toLowerCase();
|
|
1750
|
-
itemValue = itemValue.toLowerCase();
|
|
1751
|
-
}
|
|
1752
|
-
if (itemName.indexOf(':') > -1) {
|
|
1753
|
-
const [itemNamePrefix, itemNameLocalName] = itemName.split(':');
|
|
1754
|
-
// ignore xml:lang
|
|
1755
|
-
if (itemNamePrefix === 'xml' && itemNameLocalName === 'lang') {
|
|
1756
|
-
continue;
|
|
1757
|
-
} else if (astAttrName === itemNameLocalName) {
|
|
1758
|
-
attrValues.add(itemValue);
|
|
1759
|
-
}
|
|
1760
|
-
} else if (astAttrName === itemName) {
|
|
1761
|
-
attrValues.add(itemValue);
|
|
1762
|
-
}
|
|
1763
|
-
}
|
|
1764
|
-
}
|
|
1765
|
-
if (attrValues.size) {
|
|
1766
|
-
const {
|
|
1767
|
-
name: astAttrIdentValue, value: astAttrStringValue
|
|
1768
|
-
} = astValue || {};
|
|
1769
|
-
let attrValue;
|
|
1770
|
-
if (astAttrIdentValue) {
|
|
1771
|
-
if (caseInsensitive) {
|
|
1772
|
-
attrValue = astAttrIdentValue.toLowerCase();
|
|
1773
|
-
} else {
|
|
1774
|
-
attrValue = astAttrIdentValue;
|
|
1775
|
-
}
|
|
1776
|
-
} else if (astAttrStringValue) {
|
|
1777
|
-
if (caseInsensitive) {
|
|
1778
|
-
attrValue = astAttrStringValue.toLowerCase();
|
|
1779
|
-
} else {
|
|
1780
|
-
attrValue = astAttrStringValue;
|
|
1781
|
-
}
|
|
1782
|
-
} else if (astAttrStringValue === '') {
|
|
1783
|
-
attrValue = astAttrStringValue;
|
|
1784
|
-
}
|
|
1785
|
-
switch (astMatcher) {
|
|
1786
|
-
case '=': {
|
|
1787
|
-
if (typeof attrValue === 'string' && attrValues.has(attrValue)) {
|
|
1788
|
-
res = node;
|
|
1789
|
-
}
|
|
1790
|
-
break;
|
|
1791
|
-
}
|
|
1792
|
-
case '~=': {
|
|
1793
|
-
if (attrValue && typeof attrValue === 'string') {
|
|
1794
|
-
for (const value of attrValues) {
|
|
1795
|
-
const item = new Set(value.split(/\s+/));
|
|
1796
|
-
if (item.has(attrValue)) {
|
|
1797
|
-
res = node;
|
|
1798
|
-
break;
|
|
1799
|
-
}
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
1802
|
-
break;
|
|
1803
|
-
}
|
|
1804
|
-
case '|=': {
|
|
1805
|
-
if (attrValue && typeof attrValue === 'string') {
|
|
1806
|
-
let item;
|
|
1807
|
-
for (const value of attrValues) {
|
|
1808
|
-
if (value === attrValue || value.startsWith(`${attrValue}-`)) {
|
|
1809
|
-
item = value;
|
|
1810
|
-
break;
|
|
1811
|
-
}
|
|
1812
|
-
}
|
|
1813
|
-
if (item) {
|
|
1814
|
-
res = node;
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
break;
|
|
1818
|
-
}
|
|
1819
|
-
case '^=': {
|
|
1820
|
-
if (attrValue && typeof attrValue === 'string') {
|
|
1821
|
-
let item;
|
|
1822
|
-
for (const value of attrValues) {
|
|
1823
|
-
if (value.startsWith(`${attrValue}`)) {
|
|
1824
|
-
item = value;
|
|
1825
|
-
break;
|
|
1826
|
-
}
|
|
1827
|
-
}
|
|
1828
|
-
if (item) {
|
|
1829
|
-
res = node;
|
|
1830
|
-
}
|
|
1831
|
-
}
|
|
1832
|
-
break;
|
|
1833
|
-
}
|
|
1834
|
-
case '$=': {
|
|
1835
|
-
if (attrValue && typeof attrValue === 'string') {
|
|
1836
|
-
let item;
|
|
1837
|
-
for (const value of attrValues) {
|
|
1838
|
-
if (value.endsWith(`${attrValue}`)) {
|
|
1839
|
-
item = value;
|
|
1840
|
-
break;
|
|
1841
|
-
}
|
|
1842
|
-
}
|
|
1843
|
-
if (item) {
|
|
1844
|
-
res = node;
|
|
1845
|
-
}
|
|
1846
|
-
}
|
|
1847
|
-
break;
|
|
1848
|
-
}
|
|
1849
|
-
case '*=': {
|
|
1850
|
-
if (attrValue && typeof attrValue === 'string') {
|
|
1851
|
-
let item;
|
|
1852
|
-
for (const value of attrValues) {
|
|
1853
|
-
if (value.includes(`${attrValue}`)) {
|
|
1854
|
-
item = value;
|
|
1855
|
-
break;
|
|
1856
|
-
}
|
|
1857
|
-
}
|
|
1858
|
-
if (item) {
|
|
1859
|
-
res = node;
|
|
1860
|
-
}
|
|
1861
|
-
}
|
|
1862
|
-
break;
|
|
1863
|
-
}
|
|
1864
|
-
case null:
|
|
1865
|
-
default: {
|
|
1866
|
-
res = node;
|
|
1867
|
-
}
|
|
1868
|
-
}
|
|
1869
|
-
}
|
|
1870
|
-
}
|
|
1871
|
-
return res ?? null;
|
|
1872
|
-
}
|
|
1873
|
-
|
|
1874
|
-
/**
|
|
1875
|
-
* match class selector
|
|
1876
|
-
* @param {object} ast - AST
|
|
1877
|
-
* @param {object} node - Element node
|
|
1878
|
-
* @returns {?object} - matched node
|
|
1879
|
-
*/
|
|
1880
|
-
_matchClassSelector(ast, node) {
|
|
1881
|
-
const astName = unescapeSelector(ast.name);
|
|
1882
|
-
let res;
|
|
1883
|
-
if (node.classList.contains(astName)) {
|
|
1884
|
-
res = node;
|
|
1885
|
-
}
|
|
1886
|
-
return res ?? null;
|
|
1887
|
-
}
|
|
1888
|
-
|
|
1889
|
-
/**
|
|
1890
|
-
* match ID selector
|
|
1891
|
-
* @param {object} ast - AST
|
|
1892
|
-
* @param {object} node - Element node
|
|
1893
|
-
* @returns {?object} - matched node
|
|
1894
|
-
*/
|
|
1895
|
-
_matchIDSelector(ast, node) {
|
|
1896
|
-
const astName = unescapeSelector(ast.name);
|
|
1897
|
-
const { id } = node;
|
|
1898
|
-
let res;
|
|
1899
|
-
if (astName === id) {
|
|
1900
|
-
res = node;
|
|
1901
|
-
}
|
|
1902
|
-
return res ?? null;
|
|
1903
|
-
}
|
|
1904
|
-
|
|
1905
|
-
/**
|
|
1906
|
-
* match type selector
|
|
1907
|
-
* @param {object} ast - AST
|
|
1908
|
-
* @param {object} node - Element node
|
|
1909
|
-
* @param {object} [opt] - options
|
|
1910
|
-
* @param {boolean} [opt.forgive] - is forgiving selector list
|
|
1911
|
-
* @returns {?object} - matched node
|
|
1912
|
-
*/
|
|
1913
|
-
_matchTypeSelector(ast, node, opt = {}) {
|
|
1914
|
-
const astName = unescapeSelector(ast.name);
|
|
1915
|
-
const { localName, prefix } = node;
|
|
1916
|
-
const { forgive } = opt;
|
|
1917
|
-
let {
|
|
1918
|
-
prefix: astPrefix, tagName: astNodeName
|
|
1919
|
-
} = selectorToNodeProps(astName, node);
|
|
1920
|
-
if (this.#document.contentType === 'text/html') {
|
|
1921
|
-
astPrefix = astPrefix.toLowerCase();
|
|
1922
|
-
astNodeName = astNodeName.toLowerCase();
|
|
1923
|
-
}
|
|
1924
|
-
let nodePrefix;
|
|
1925
|
-
let nodeName;
|
|
1926
|
-
// just in case that the namespaced content is parsed as text/html
|
|
1927
|
-
if (localName.indexOf(':') > -1) {
|
|
1928
|
-
[nodePrefix, nodeName] = localName.split(':');
|
|
1929
|
-
} else {
|
|
1930
|
-
nodePrefix = prefix || '';
|
|
1931
|
-
nodeName = localName;
|
|
1932
|
-
}
|
|
1933
|
-
let res;
|
|
1934
|
-
if (astPrefix === '' && nodePrefix === '') {
|
|
1935
|
-
if (node.namespaceURI === null &&
|
|
1936
|
-
(astNodeName === '*' || astNodeName === nodeName)) {
|
|
1937
|
-
res = node;
|
|
1938
|
-
}
|
|
1939
|
-
} else if (astPrefix === '*') {
|
|
1940
|
-
if (astNodeName === '*' || astNodeName === nodeName) {
|
|
1941
|
-
res = node;
|
|
1942
|
-
}
|
|
1943
|
-
} else if (astPrefix === nodePrefix) {
|
|
1944
|
-
if (isNamespaceDeclared(astPrefix, node)) {
|
|
1945
|
-
if (astNodeName === '*' || astNodeName === nodeName) {
|
|
1946
|
-
res = node;
|
|
1947
|
-
}
|
|
1948
|
-
} else if (!forgive) {
|
|
1949
|
-
const msg = `Undeclared namespace ${astPrefix}`;
|
|
1950
|
-
throw new DOMException(msg, SYNTAX_ERR);
|
|
1951
|
-
}
|
|
1952
|
-
} else if (astPrefix && !forgive && !isNamespaceDeclared(astPrefix, node)) {
|
|
1953
|
-
const msg = `Undeclared namespace ${astPrefix}`;
|
|
1954
|
-
throw new DOMException(msg, SYNTAX_ERR);
|
|
1955
|
-
}
|
|
1956
|
-
return res ?? null;
|
|
1957
|
-
};
|
|
1958
|
-
|
|
1959
|
-
/**
|
|
1960
|
-
* match shadow host pseudo class
|
|
1961
|
-
* @param {object} ast - AST
|
|
1962
|
-
* @param {object} node - DocumentFragment node
|
|
1963
|
-
* @returns {?object} - matched node
|
|
1964
|
-
*/
|
|
1965
|
-
_matchShadowHostPseudoClass(ast, node) {
|
|
1966
|
-
const { children: astChildren } = ast;
|
|
1967
|
-
const astName = unescapeSelector(ast.name);
|
|
1968
|
-
let res;
|
|
1969
|
-
if (Array.isArray(astChildren)) {
|
|
1970
|
-
const [branch] = walkAST(astChildren[0]);
|
|
1971
|
-
const [...leaves] = branch;
|
|
1972
|
-
const { host } = node;
|
|
1973
|
-
if (astName === 'host') {
|
|
1974
|
-
let bool;
|
|
1975
|
-
for (const leaf of leaves) {
|
|
1976
|
-
const { type: leafType } = leaf;
|
|
1977
|
-
if (leafType === COMBINATOR) {
|
|
1978
|
-
const css = generateCSS(ast);
|
|
1979
|
-
const msg = `Invalid selector ${css}`;
|
|
1980
|
-
throw new DOMException(msg, SYNTAX_ERR);
|
|
1981
|
-
}
|
|
1982
|
-
bool = this._matchSelector(leaf, host).has(host);
|
|
1983
|
-
if (!bool) {
|
|
1984
|
-
break;
|
|
1985
|
-
}
|
|
1986
|
-
}
|
|
1987
|
-
if (bool) {
|
|
1988
|
-
res = node;
|
|
1989
|
-
}
|
|
1990
|
-
} else if (astName === 'host-context') {
|
|
1991
|
-
let bool;
|
|
1992
|
-
let parent = host;
|
|
1993
|
-
while (parent) {
|
|
1994
|
-
for (const leaf of leaves) {
|
|
1995
|
-
const { type: leafType } = leaf;
|
|
1996
|
-
if (leafType === COMBINATOR) {
|
|
1997
|
-
const css = generateCSS(ast);
|
|
1998
|
-
const msg = `Invalid selector ${css}`;
|
|
1999
|
-
throw new DOMException(msg, SYNTAX_ERR);
|
|
2000
|
-
}
|
|
2001
|
-
bool = this._matchSelector(leaf, parent).has(parent);
|
|
2002
|
-
if (!bool) {
|
|
2003
|
-
break;
|
|
2004
|
-
}
|
|
2005
|
-
}
|
|
2006
|
-
if (bool) {
|
|
2007
|
-
break;
|
|
2008
|
-
} else {
|
|
2009
|
-
parent = parent.parentNode;
|
|
2010
|
-
}
|
|
2011
|
-
}
|
|
2012
|
-
if (bool) {
|
|
2013
|
-
res = node;
|
|
2014
|
-
}
|
|
2015
|
-
}
|
|
2016
|
-
} else if (astName === 'host') {
|
|
2017
|
-
res = node;
|
|
2018
|
-
} else {
|
|
2019
|
-
const msg = `Invalid selector :${astName}`;
|
|
2020
|
-
throw new DOMException(msg, SYNTAX_ERR);
|
|
2021
|
-
}
|
|
2022
|
-
return res ?? null;
|
|
2023
|
-
}
|
|
2024
|
-
|
|
2025
|
-
/**
|
|
2026
|
-
* match selector
|
|
2027
|
-
* @param {object} ast - AST
|
|
2028
|
-
* @param {object} node - Document, DocumentFragment, Element node
|
|
2029
|
-
* @param {object} [opt] - options
|
|
2030
|
-
* @returns {Set.<object>} - collection of matched nodes
|
|
2031
|
-
*/
|
|
2032
|
-
_matchSelector(ast, node, opt) {
|
|
2033
|
-
const { type: astType } = ast;
|
|
2034
|
-
const astName = unescapeSelector(ast.name);
|
|
2035
|
-
let matched = new Set();
|
|
2036
|
-
if (node.nodeType === ELEMENT_NODE) {
|
|
2037
|
-
switch (astType) {
|
|
2038
|
-
case SELECTOR_ATTR: {
|
|
2039
|
-
const res = this._matchAttributeSelector(ast, node);
|
|
2040
|
-
if (res) {
|
|
2041
|
-
matched.add(res);
|
|
2042
|
-
}
|
|
2043
|
-
break;
|
|
2044
|
-
}
|
|
2045
|
-
case SELECTOR_CLASS: {
|
|
2046
|
-
const res = this._matchClassSelector(ast, node);
|
|
2047
|
-
if (res) {
|
|
2048
|
-
matched.add(res);
|
|
2049
|
-
}
|
|
2050
|
-
break;
|
|
2051
|
-
}
|
|
2052
|
-
case SELECTOR_ID: {
|
|
2053
|
-
const res = this._matchIDSelector(ast, node);
|
|
2054
|
-
if (res) {
|
|
2055
|
-
matched.add(res);
|
|
2056
|
-
}
|
|
2057
|
-
break;
|
|
2058
|
-
}
|
|
2059
|
-
case SELECTOR_PSEUDO_CLASS: {
|
|
2060
|
-
const nodes = this._matchPseudoClassSelector(ast, node, opt);
|
|
2061
|
-
if (nodes.size) {
|
|
2062
|
-
matched = nodes;
|
|
2063
|
-
}
|
|
2064
|
-
break;
|
|
2065
|
-
}
|
|
2066
|
-
case SELECTOR_PSEUDO_ELEMENT: {
|
|
2067
|
-
this._matchPseudoElementSelector(astName, opt);
|
|
2068
|
-
break;
|
|
2069
|
-
}
|
|
2070
|
-
case SELECTOR_TYPE:
|
|
2071
|
-
default: {
|
|
2072
|
-
const res = this._matchTypeSelector(ast, node, opt);
|
|
2073
|
-
if (res) {
|
|
2074
|
-
matched.add(res);
|
|
2075
|
-
}
|
|
2076
|
-
}
|
|
2077
|
-
}
|
|
2078
|
-
} else if (this.#shadow && astType === SELECTOR_PSEUDO_CLASS &&
|
|
2079
|
-
node.nodeType === DOCUMENT_FRAGMENT_NODE) {
|
|
2080
|
-
if (astName !== 'has' && REG_LOGICAL_PSEUDO.test(astName)) {
|
|
2081
|
-
const nodes = this._matchPseudoClassSelector(ast, node, opt);
|
|
2082
|
-
if (nodes.size) {
|
|
2083
|
-
matched = nodes;
|
|
2084
|
-
}
|
|
2085
|
-
} else if (REG_SHADOW_HOST.test(astName)) {
|
|
2086
|
-
const res = this._matchShadowHostPseudoClass(ast, node);
|
|
2087
|
-
if (res) {
|
|
2088
|
-
matched.add(res);
|
|
2089
|
-
}
|
|
2090
|
-
}
|
|
2091
|
-
}
|
|
2092
|
-
return matched;
|
|
2093
|
-
}
|
|
2094
|
-
|
|
2095
|
-
/**
|
|
2096
|
-
* match leaves
|
|
2097
|
-
* @param {Array.<object>} leaves - AST leaves
|
|
2098
|
-
* @param {object} node - node
|
|
2099
|
-
* @param {object} [opt] - options
|
|
2100
|
-
* @returns {boolean} - result
|
|
2101
|
-
*/
|
|
2102
|
-
_matchLeaves(leaves, node, opt) {
|
|
2103
|
-
let bool;
|
|
2104
|
-
for (const leaf of leaves) {
|
|
2105
|
-
bool = this._matchSelector(leaf, node, opt).has(node);
|
|
2106
|
-
if (!bool) {
|
|
2107
|
-
break;
|
|
2108
|
-
}
|
|
2109
|
-
}
|
|
2110
|
-
return !!bool;
|
|
2111
|
-
}
|
|
2112
|
-
|
|
2113
|
-
/**
|
|
2114
|
-
* find descendant nodes
|
|
2115
|
-
* @param {Array.<object>} leaves - AST leaves
|
|
2116
|
-
* @param {object} baseNode - base Element node
|
|
2117
|
-
* @returns {object} - collection of nodes and pending state
|
|
2118
|
-
*/
|
|
2119
|
-
_findDescendantNodes(leaves, baseNode) {
|
|
2120
|
-
const [leaf, ...filterLeaves] = leaves;
|
|
2121
|
-
const { type: leafType } = leaf;
|
|
2122
|
-
const leafName = unescapeSelector(leaf.name);
|
|
2123
|
-
const compound = filterLeaves.length > 0;
|
|
2124
|
-
let nodes = new Set();
|
|
2125
|
-
let pending = false;
|
|
2126
|
-
if (this.#shadow) {
|
|
2127
|
-
pending = true;
|
|
2128
|
-
} else {
|
|
2129
|
-
switch (leafType) {
|
|
2130
|
-
case SELECTOR_ID: {
|
|
2131
|
-
if (this.#root.nodeType === ELEMENT_NODE) {
|
|
2132
|
-
pending = true;
|
|
2133
|
-
} else {
|
|
2134
|
-
const node = this.#root.getElementById(leafName);
|
|
2135
|
-
if (node && node !== baseNode && baseNode.contains(node)) {
|
|
2136
|
-
if (compound) {
|
|
2137
|
-
const bool = this._matchLeaves(filterLeaves, node);
|
|
2138
|
-
if (bool) {
|
|
2139
|
-
nodes.add(node);
|
|
2140
|
-
}
|
|
2141
|
-
} else {
|
|
2142
|
-
nodes.add(node);
|
|
2143
|
-
}
|
|
2144
|
-
}
|
|
2145
|
-
}
|
|
2146
|
-
break;
|
|
2147
|
-
}
|
|
2148
|
-
case SELECTOR_CLASS: {
|
|
2149
|
-
const arr = [].slice.call(baseNode.getElementsByClassName(leafName));
|
|
2150
|
-
if (arr.length) {
|
|
2151
|
-
if (compound) {
|
|
2152
|
-
for (const node of arr) {
|
|
2153
|
-
const bool = this._matchLeaves(filterLeaves, node);
|
|
2154
|
-
if (bool) {
|
|
2155
|
-
nodes.add(node);
|
|
2156
|
-
}
|
|
2157
|
-
}
|
|
2158
|
-
} else {
|
|
2159
|
-
nodes = new Set(arr);
|
|
2160
|
-
}
|
|
2161
|
-
}
|
|
2162
|
-
break;
|
|
2163
|
-
}
|
|
2164
|
-
case SELECTOR_TYPE: {
|
|
2165
|
-
if (this.#document.contentType === 'text/html' &&
|
|
2166
|
-
!/[*|]/.test(leafName)) {
|
|
2167
|
-
const arr = [].slice.call(baseNode.getElementsByTagName(leafName));
|
|
2168
|
-
if (arr.length) {
|
|
2169
|
-
if (compound) {
|
|
2170
|
-
for (const node of arr) {
|
|
2171
|
-
const bool = this._matchLeaves(filterLeaves, node);
|
|
2172
|
-
if (bool) {
|
|
2173
|
-
nodes.add(node);
|
|
2174
|
-
}
|
|
2175
|
-
}
|
|
2176
|
-
} else {
|
|
2177
|
-
nodes = new Set(arr);
|
|
2178
|
-
}
|
|
2179
|
-
}
|
|
2180
|
-
} else {
|
|
2181
|
-
pending = true;
|
|
2182
|
-
}
|
|
2183
|
-
break;
|
|
2184
|
-
}
|
|
2185
|
-
case SELECTOR_PSEUDO_ELEMENT: {
|
|
2186
|
-
this._matchPseudoElementSelector(leafName);
|
|
2187
|
-
break;
|
|
2188
|
-
}
|
|
2189
|
-
default: {
|
|
2190
|
-
pending = true;
|
|
2191
|
-
}
|
|
2192
|
-
}
|
|
2193
|
-
}
|
|
2194
|
-
return {
|
|
2195
|
-
nodes,
|
|
2196
|
-
pending
|
|
2197
|
-
};
|
|
2198
|
-
}
|
|
2199
|
-
|
|
2200
|
-
/**
|
|
2201
|
-
* match combinator
|
|
2202
|
-
* @param {object} twig - twig
|
|
2203
|
-
* @param {object} node - Element node
|
|
2204
|
-
* @param {object} [opt] - option
|
|
2205
|
-
* @param {string} [opt.dir] - direction to find
|
|
2206
|
-
* @param {boolean} [opt.forgive] - is forgiving selector list
|
|
2207
|
-
* @returns {Set.<object>} - collection of matched nodes
|
|
2208
|
-
*/
|
|
2209
|
-
_matchCombinator(twig, node, opt = {}) {
|
|
2210
|
-
const { combo, leaves } = twig;
|
|
2211
|
-
const { name: comboName } = combo;
|
|
2212
|
-
const { dir, forgive } = opt;
|
|
2213
|
-
let matched = new Set();
|
|
2214
|
-
if (dir === DIR_NEXT) {
|
|
2215
|
-
switch (comboName) {
|
|
2216
|
-
case '+': {
|
|
2217
|
-
const refNode = node.nextElementSibling;
|
|
2218
|
-
if (refNode) {
|
|
2219
|
-
const bool = this._matchLeaves(leaves, refNode, { forgive });
|
|
2220
|
-
if (bool) {
|
|
2221
|
-
matched.add(refNode);
|
|
2222
|
-
}
|
|
2223
|
-
}
|
|
2224
|
-
break;
|
|
2225
|
-
}
|
|
2226
|
-
case '~': {
|
|
2227
|
-
const { parentNode } = node;
|
|
2228
|
-
if (parentNode) {
|
|
2229
|
-
const walker =
|
|
2230
|
-
this.#document.createTreeWalker(parentNode, SHOW_ELEMENT);
|
|
2231
|
-
let refNode = this._traverse(node, walker);
|
|
2232
|
-
if (refNode === node) {
|
|
2233
|
-
refNode = walker.nextSibling();
|
|
2234
|
-
}
|
|
2235
|
-
while (refNode) {
|
|
2236
|
-
const bool = this._matchLeaves(leaves, refNode, { forgive });
|
|
2237
|
-
if (bool) {
|
|
2238
|
-
matched.add(refNode);
|
|
2239
|
-
}
|
|
2240
|
-
refNode = walker.nextSibling();
|
|
2241
|
-
}
|
|
2242
|
-
}
|
|
2243
|
-
break;
|
|
2244
|
-
}
|
|
2245
|
-
case '>': {
|
|
2246
|
-
const walker = this.#document.createTreeWalker(node, SHOW_ELEMENT);
|
|
2247
|
-
let refNode = walker.firstChild();
|
|
2248
|
-
while (refNode) {
|
|
2249
|
-
const bool = this._matchLeaves(leaves, refNode, { forgive });
|
|
2250
|
-
if (bool) {
|
|
2251
|
-
matched.add(refNode);
|
|
2252
|
-
}
|
|
2253
|
-
refNode = walker.nextSibling();
|
|
2254
|
-
}
|
|
2255
|
-
break;
|
|
2256
|
-
}
|
|
2257
|
-
case ' ':
|
|
2258
|
-
default: {
|
|
2259
|
-
const { nodes, pending } = this._findDescendantNodes(leaves, node);
|
|
2260
|
-
if (nodes.size) {
|
|
2261
|
-
matched = nodes;
|
|
2262
|
-
} else if (pending) {
|
|
2263
|
-
const walker = this.#document.createTreeWalker(node, SHOW_ELEMENT);
|
|
2264
|
-
let refNode = walker.nextNode();
|
|
2265
|
-
while (refNode) {
|
|
2266
|
-
const bool = this._matchLeaves(leaves, refNode, { forgive });
|
|
2267
|
-
if (bool) {
|
|
2268
|
-
matched.add(refNode);
|
|
2269
|
-
}
|
|
2270
|
-
refNode = walker.nextNode();
|
|
2271
|
-
}
|
|
2272
|
-
}
|
|
2273
|
-
}
|
|
2274
|
-
}
|
|
2275
|
-
} else {
|
|
2276
|
-
switch (comboName) {
|
|
2277
|
-
case '+': {
|
|
2278
|
-
const refNode = node.previousElementSibling;
|
|
2279
|
-
if (refNode) {
|
|
2280
|
-
const bool = this._matchLeaves(leaves, refNode, { forgive });
|
|
2281
|
-
if (bool) {
|
|
2282
|
-
matched.add(refNode);
|
|
2283
|
-
}
|
|
2284
|
-
}
|
|
2285
|
-
break;
|
|
2286
|
-
}
|
|
2287
|
-
case '~': {
|
|
2288
|
-
const walker =
|
|
2289
|
-
this.#document.createTreeWalker(node.parentNode, SHOW_ELEMENT);
|
|
2290
|
-
let refNode = walker.firstChild();
|
|
2291
|
-
while (refNode) {
|
|
2292
|
-
if (refNode === node) {
|
|
2293
|
-
break;
|
|
2294
|
-
} else {
|
|
2295
|
-
const bool = this._matchLeaves(leaves, refNode, { forgive });
|
|
2296
|
-
if (bool) {
|
|
2297
|
-
matched.add(refNode);
|
|
2298
|
-
}
|
|
2299
|
-
}
|
|
2300
|
-
refNode = walker.nextSibling();
|
|
2301
|
-
}
|
|
2302
|
-
break;
|
|
2303
|
-
}
|
|
2304
|
-
case '>': {
|
|
2305
|
-
const refNode = node.parentNode;
|
|
2306
|
-
if (refNode) {
|
|
2307
|
-
const bool = this._matchLeaves(leaves, refNode, { forgive });
|
|
2308
|
-
if (bool) {
|
|
2309
|
-
matched.add(refNode);
|
|
2310
|
-
}
|
|
2311
|
-
}
|
|
2312
|
-
break;
|
|
2313
|
-
}
|
|
2314
|
-
case ' ':
|
|
2315
|
-
default: {
|
|
2316
|
-
const arr = [];
|
|
2317
|
-
let refNode = node.parentNode;
|
|
2318
|
-
while (refNode) {
|
|
2319
|
-
const bool = this._matchLeaves(leaves, refNode, { forgive });
|
|
2320
|
-
if (bool) {
|
|
2321
|
-
arr.push(refNode);
|
|
2322
|
-
}
|
|
2323
|
-
refNode = refNode.parentNode;
|
|
2324
|
-
}
|
|
2325
|
-
if (arr.length) {
|
|
2326
|
-
matched = new Set(arr.reverse());
|
|
2327
|
-
}
|
|
2328
|
-
}
|
|
2329
|
-
}
|
|
2330
|
-
}
|
|
2331
|
-
return matched;
|
|
2332
|
-
}
|
|
2333
|
-
|
|
2334
|
-
/**
|
|
2335
|
-
* find matched node from finder
|
|
2336
|
-
* @param {Array.<object>} leaves - AST leaves
|
|
2337
|
-
* @param {object} [opt] - options
|
|
2338
|
-
* @param {object} [opt.node] - node to start from
|
|
2339
|
-
* @returns {?object} - matched node
|
|
2340
|
-
*/
|
|
2341
|
-
_findNode(leaves, opt = {}) {
|
|
2342
|
-
const { node } = opt;
|
|
2343
|
-
let matchedNode;
|
|
2344
|
-
let refNode = this._traverse(node, this.#finder);
|
|
2345
|
-
if (refNode) {
|
|
2346
|
-
if (refNode.nodeType !== ELEMENT_NODE) {
|
|
2347
|
-
refNode = this.#finder.nextNode();
|
|
2348
|
-
} else if (refNode === node) {
|
|
2349
|
-
if (refNode !== this.#root) {
|
|
2350
|
-
refNode = this.#finder.nextNode();
|
|
2351
|
-
}
|
|
2352
|
-
}
|
|
2353
|
-
while (refNode) {
|
|
2354
|
-
let bool;
|
|
2355
|
-
if (this.#node.nodeType === ELEMENT_NODE) {
|
|
2356
|
-
if (refNode === this.#node) {
|
|
2357
|
-
bool = true;
|
|
2358
|
-
} else {
|
|
2359
|
-
bool = this.#node.contains(refNode);
|
|
2360
|
-
}
|
|
2361
|
-
} else {
|
|
2362
|
-
bool = true;
|
|
2363
|
-
}
|
|
2364
|
-
if (bool) {
|
|
2365
|
-
const matched = this._matchLeaves(leaves, refNode);
|
|
2366
|
-
if (matched) {
|
|
2367
|
-
matchedNode = refNode;
|
|
2368
|
-
break;
|
|
2369
|
-
}
|
|
2370
|
-
}
|
|
2371
|
-
refNode = this.#finder.nextNode();
|
|
2372
|
-
}
|
|
2373
|
-
}
|
|
2374
|
-
return matchedNode ?? null;
|
|
2375
|
-
}
|
|
2376
|
-
|
|
2377
|
-
/**
|
|
2378
|
-
* find entry nodes
|
|
2379
|
-
* @param {object} twig - twig
|
|
2380
|
-
* @param {string} targetType - target type
|
|
2381
|
-
* @returns {object} - collection of nodes etc.
|
|
2382
|
-
*/
|
|
2383
|
-
_findEntryNodes(twig, targetType) {
|
|
2384
|
-
const { leaves } = twig;
|
|
2385
|
-
const [leaf, ...filterLeaves] = leaves;
|
|
2386
|
-
const { type: leafType } = leaf;
|
|
2387
|
-
const leafName = unescapeSelector(leaf.name);
|
|
2388
|
-
const compound = filterLeaves.length > 0;
|
|
2389
|
-
let nodes = new Set();
|
|
2390
|
-
let filtered = false;
|
|
2391
|
-
let pending = false;
|
|
2392
|
-
switch (leafType) {
|
|
2393
|
-
case SELECTOR_ID: {
|
|
2394
|
-
if (targetType === TARGET_SELF) {
|
|
2395
|
-
const bool = this._matchLeaves(leaves, this.#node);
|
|
2396
|
-
if (bool) {
|
|
2397
|
-
nodes.add(this.#node);
|
|
2398
|
-
filtered = true;
|
|
2399
|
-
}
|
|
2400
|
-
} else if (targetType === TARGET_LINEAL) {
|
|
2401
|
-
let refNode = this.#node;
|
|
2402
|
-
while (refNode) {
|
|
2403
|
-
const bool = this._matchLeaves(leaves, refNode);
|
|
2404
|
-
if (bool) {
|
|
2405
|
-
nodes.add(refNode);
|
|
2406
|
-
filtered = true;
|
|
2407
|
-
}
|
|
2408
|
-
refNode = refNode.parentNode;
|
|
2409
|
-
}
|
|
2410
|
-
} else if (targetType === TARGET_FIRST &&
|
|
2411
|
-
this.#root.nodeType !== ELEMENT_NODE) {
|
|
2412
|
-
const node = this.#root.getElementById(leafName);
|
|
2413
|
-
if (node) {
|
|
2414
|
-
nodes.add(node);
|
|
2415
|
-
}
|
|
2416
|
-
} else {
|
|
2417
|
-
pending = true;
|
|
2418
|
-
}
|
|
2419
|
-
break;
|
|
2420
|
-
}
|
|
2421
|
-
case SELECTOR_CLASS: {
|
|
2422
|
-
if (targetType === TARGET_SELF) {
|
|
2423
|
-
if (this.#node.nodeType === ELEMENT_NODE &&
|
|
2424
|
-
this.#node.classList.contains(leafName)) {
|
|
2425
|
-
nodes.add(this.#node);
|
|
2426
|
-
}
|
|
2427
|
-
} else if (targetType === TARGET_LINEAL) {
|
|
2428
|
-
let refNode = this.#node;
|
|
2429
|
-
while (refNode) {
|
|
2430
|
-
if (refNode.nodeType === ELEMENT_NODE) {
|
|
2431
|
-
if (refNode.classList.contains(leafName)) {
|
|
2432
|
-
nodes.add(refNode);
|
|
2433
|
-
}
|
|
2434
|
-
refNode = refNode.parentNode;
|
|
2435
|
-
} else {
|
|
2436
|
-
break;
|
|
2437
|
-
}
|
|
2438
|
-
}
|
|
2439
|
-
} else if (targetType === TARGET_FIRST) {
|
|
2440
|
-
const node = this._findNode(leaves, {
|
|
2441
|
-
node: this.#node
|
|
2442
|
-
});
|
|
2443
|
-
if (node) {
|
|
2444
|
-
nodes.add(node);
|
|
2445
|
-
filtered = true;
|
|
2446
|
-
}
|
|
2447
|
-
} else if (this.#root.nodeType === DOCUMENT_NODE) {
|
|
2448
|
-
const arr =
|
|
2449
|
-
[].slice.call(this.#root.getElementsByClassName(leafName));
|
|
2450
|
-
if (this.#node.nodeType === ELEMENT_NODE) {
|
|
2451
|
-
for (const node of arr) {
|
|
2452
|
-
if (node === this.#node || isInclusive(node, this.#node)) {
|
|
2453
|
-
nodes.add(node);
|
|
2454
|
-
}
|
|
2455
|
-
}
|
|
2456
|
-
} else if (arr.length) {
|
|
2457
|
-
nodes = new Set(arr);
|
|
2458
|
-
}
|
|
2459
|
-
} else {
|
|
2460
|
-
pending = true;
|
|
2461
|
-
}
|
|
2462
|
-
break;
|
|
2463
|
-
}
|
|
2464
|
-
case SELECTOR_TYPE: {
|
|
2465
|
-
if (targetType === TARGET_SELF) {
|
|
2466
|
-
if (this.#node.nodeType === ELEMENT_NODE) {
|
|
2467
|
-
const bool = this._matchLeaves(leaves, this.#node);
|
|
2468
|
-
if (bool) {
|
|
2469
|
-
nodes.add(this.#node);
|
|
2470
|
-
filtered = true;
|
|
2471
|
-
}
|
|
2472
|
-
}
|
|
2473
|
-
} else if (targetType === TARGET_LINEAL) {
|
|
2474
|
-
let refNode = this.#node;
|
|
2475
|
-
while (refNode) {
|
|
2476
|
-
if (refNode.nodeType === ELEMENT_NODE) {
|
|
2477
|
-
const bool = this._matchLeaves(leaves, refNode);
|
|
2478
|
-
if (bool) {
|
|
2479
|
-
nodes.add(refNode);
|
|
2480
|
-
filtered = true;
|
|
2481
|
-
}
|
|
2482
|
-
refNode = refNode.parentNode;
|
|
2483
|
-
} else {
|
|
2484
|
-
break;
|
|
2485
|
-
}
|
|
2486
|
-
}
|
|
2487
|
-
} else if (targetType === TARGET_FIRST) {
|
|
2488
|
-
const node = this._findNode(leaves, {
|
|
2489
|
-
node: this.#node
|
|
2490
|
-
});
|
|
2491
|
-
if (node) {
|
|
2492
|
-
nodes.add(node);
|
|
2493
|
-
filtered = true;
|
|
2494
|
-
}
|
|
2495
|
-
} else if (this.#document.contentType === 'text/html' &&
|
|
2496
|
-
this.#root.nodeType === DOCUMENT_NODE &&
|
|
2497
|
-
!/[*|]/.test(leafName)) {
|
|
2498
|
-
const arr = [].slice.call(this.#root.getElementsByTagName(leafName));
|
|
2499
|
-
if (this.#node.nodeType === ELEMENT_NODE) {
|
|
2500
|
-
for (const node of arr) {
|
|
2501
|
-
if (node === this.#node || isInclusive(node, this.#node)) {
|
|
2502
|
-
nodes.add(node);
|
|
2503
|
-
}
|
|
2504
|
-
}
|
|
2505
|
-
} else if (arr.length) {
|
|
2506
|
-
nodes = new Set(arr);
|
|
2507
|
-
}
|
|
2508
|
-
} else {
|
|
2509
|
-
pending = true;
|
|
2510
|
-
}
|
|
2511
|
-
break;
|
|
2512
|
-
}
|
|
2513
|
-
case SELECTOR_PSEUDO_ELEMENT: {
|
|
2514
|
-
// throws
|
|
2515
|
-
this._matchPseudoElementSelector(leafName);
|
|
2516
|
-
break;
|
|
2517
|
-
}
|
|
2518
|
-
default: {
|
|
2519
|
-
if (targetType !== TARGET_LINEAL && REG_SHADOW_HOST.test(leafName)) {
|
|
2520
|
-
if (this.#shadow && this.#node.nodeType === DOCUMENT_FRAGMENT_NODE) {
|
|
2521
|
-
const node = this._matchShadowHostPseudoClass(leaf, this.#node);
|
|
2522
|
-
if (node) {
|
|
2523
|
-
nodes.add(node);
|
|
2524
|
-
}
|
|
2525
|
-
}
|
|
2526
|
-
} else if (targetType === TARGET_SELF) {
|
|
2527
|
-
const bool = this._matchLeaves(leaves, this.#node);
|
|
2528
|
-
if (bool) {
|
|
2529
|
-
nodes.add(this.#node);
|
|
2530
|
-
filtered = true;
|
|
2531
|
-
}
|
|
2532
|
-
} else if (targetType === TARGET_LINEAL) {
|
|
2533
|
-
let refNode = this.#node;
|
|
2534
|
-
while (refNode) {
|
|
2535
|
-
const bool = this._matchLeaves(leaves, refNode);
|
|
2536
|
-
if (bool) {
|
|
2537
|
-
nodes.add(refNode);
|
|
2538
|
-
filtered = true;
|
|
2539
|
-
}
|
|
2540
|
-
refNode = refNode.parentNode;
|
|
2541
|
-
}
|
|
2542
|
-
} else if (targetType === TARGET_FIRST) {
|
|
2543
|
-
const node = this._findNode(leaves, {
|
|
2544
|
-
node: this.#node
|
|
2545
|
-
});
|
|
2546
|
-
if (node) {
|
|
2547
|
-
nodes.add(node);
|
|
2548
|
-
filtered = true;
|
|
2549
|
-
}
|
|
2550
|
-
} else {
|
|
2551
|
-
pending = true;
|
|
2552
|
-
}
|
|
2553
|
-
}
|
|
2554
|
-
}
|
|
2555
|
-
return {
|
|
2556
|
-
compound,
|
|
2557
|
-
filtered,
|
|
2558
|
-
nodes,
|
|
2559
|
-
pending
|
|
2560
|
-
};
|
|
2561
|
-
}
|
|
2562
|
-
|
|
2563
|
-
/**
|
|
2564
|
-
* get entry twig
|
|
2565
|
-
* @param {Array.<object>} branch - AST branch
|
|
2566
|
-
* @param {string} targetType - target type
|
|
2567
|
-
* @returns {object} - direction and twig
|
|
2568
|
-
*/
|
|
2569
|
-
_getEntryTwig(branch, targetType) {
|
|
2570
|
-
const branchLen = branch.length;
|
|
2571
|
-
const complex = branchLen > 1;
|
|
2572
|
-
const firstTwig = branch[0];
|
|
2573
|
-
let dir;
|
|
2574
|
-
let twig;
|
|
2575
|
-
if (complex) {
|
|
2576
|
-
const {
|
|
2577
|
-
combo: firstCombo,
|
|
2578
|
-
leaves: [{
|
|
2579
|
-
name: firstName,
|
|
2580
|
-
type: firstType
|
|
2581
|
-
}]
|
|
2582
|
-
} = firstTwig;
|
|
2583
|
-
const lastTwig = branch[branchLen - 1];
|
|
2584
|
-
const {
|
|
2585
|
-
leaves: [{
|
|
2586
|
-
name: lastName,
|
|
2587
|
-
type: lastType
|
|
2588
|
-
}]
|
|
2589
|
-
} = lastTwig;
|
|
2590
|
-
if (lastType === SELECTOR_PSEUDO_ELEMENT || lastType === SELECTOR_ID) {
|
|
2591
|
-
dir = DIR_PREV;
|
|
2592
|
-
twig = lastTwig;
|
|
2593
|
-
} else if (firstType === SELECTOR_PSEUDO_ELEMENT ||
|
|
2594
|
-
firstType === SELECTOR_ID) {
|
|
2595
|
-
dir = DIR_NEXT;
|
|
2596
|
-
twig = firstTwig;
|
|
2597
|
-
} else if (targetType === TARGET_ALL) {
|
|
2598
|
-
if (firstName === '*' && firstType === SELECTOR_TYPE) {
|
|
2599
|
-
dir = DIR_PREV;
|
|
2600
|
-
twig = lastTwig;
|
|
2601
|
-
} else if (lastName === '*' && lastType === SELECTOR_TYPE) {
|
|
2602
|
-
dir = DIR_NEXT;
|
|
2603
|
-
twig = firstTwig;
|
|
2604
|
-
} else if (branchLen === 2) {
|
|
2605
|
-
const { name: comboName } = firstCombo;
|
|
2606
|
-
if (/^[+~]$/.test(comboName)) {
|
|
2607
|
-
dir = DIR_PREV;
|
|
2608
|
-
twig = lastTwig;
|
|
2609
|
-
} else {
|
|
2610
|
-
dir = DIR_NEXT;
|
|
2611
|
-
twig = firstTwig;
|
|
2612
|
-
}
|
|
2613
|
-
} else {
|
|
2614
|
-
dir = DIR_NEXT;
|
|
2615
|
-
twig = firstTwig;
|
|
2616
|
-
}
|
|
2617
|
-
} else if (lastName === '*' && lastType === SELECTOR_TYPE) {
|
|
2618
|
-
dir = DIR_NEXT;
|
|
2619
|
-
twig = firstTwig;
|
|
2620
|
-
} else if (firstName === '*' && firstType === SELECTOR_TYPE) {
|
|
2621
|
-
dir = DIR_PREV;
|
|
2622
|
-
twig = lastTwig;
|
|
2623
|
-
} else {
|
|
2624
|
-
let bool;
|
|
2625
|
-
let sibling;
|
|
2626
|
-
for (const { combo, leaves: [leaf] } of branch) {
|
|
2627
|
-
const { type: leafType } = leaf;
|
|
2628
|
-
const leafName = unescapeSelector(leaf.name);
|
|
2629
|
-
if (leafType === SELECTOR_PSEUDO_CLASS && leafName === 'dir') {
|
|
2630
|
-
bool = false;
|
|
2631
|
-
break;
|
|
2632
|
-
}
|
|
2633
|
-
if (combo && !sibling) {
|
|
2634
|
-
const { name: comboName } = combo;
|
|
2635
|
-
if (/^[+~]$/.test(comboName)) {
|
|
2636
|
-
bool = true;
|
|
2637
|
-
sibling = true;
|
|
2638
|
-
}
|
|
2639
|
-
}
|
|
2640
|
-
}
|
|
2641
|
-
if (bool) {
|
|
2642
|
-
dir = DIR_NEXT;
|
|
2643
|
-
twig = firstTwig;
|
|
2644
|
-
} else {
|
|
2645
|
-
dir = DIR_PREV;
|
|
2646
|
-
twig = lastTwig;
|
|
2647
|
-
}
|
|
2648
|
-
}
|
|
2649
|
-
} else {
|
|
2650
|
-
dir = DIR_PREV;
|
|
2651
|
-
twig = firstTwig;
|
|
2652
|
-
}
|
|
2653
|
-
return {
|
|
2654
|
-
complex,
|
|
2655
|
-
dir,
|
|
2656
|
-
twig
|
|
2657
|
-
};
|
|
2658
|
-
}
|
|
2659
|
-
|
|
2660
|
-
/**
|
|
2661
|
-
* collect nodes
|
|
2662
|
-
* @param {string} targetType - target type
|
|
2663
|
-
* @returns {Array.<Array.<object|undefined>>} - #ast and #nodes
|
|
2664
|
-
*/
|
|
2665
|
-
_collectNodes(targetType) {
|
|
2666
|
-
const ast = this.#ast.values();
|
|
2667
|
-
if (targetType === TARGET_ALL || targetType === TARGET_FIRST) {
|
|
2668
|
-
const pendingItems = new Set();
|
|
2669
|
-
let i = 0;
|
|
2670
|
-
for (const { branch } of ast) {
|
|
2671
|
-
const { dir, twig } = this._getEntryTwig(branch, targetType);
|
|
2672
|
-
const {
|
|
2673
|
-
compound, filtered, nodes, pending
|
|
2674
|
-
} = this._findEntryNodes(twig, targetType);
|
|
2675
|
-
if (nodes.size) {
|
|
2676
|
-
this.#ast[i].find = true;
|
|
2677
|
-
this.#nodes[i] = nodes;
|
|
2678
|
-
} else if (pending) {
|
|
2679
|
-
pendingItems.add(new Map([
|
|
2680
|
-
['index', i],
|
|
2681
|
-
['twig', twig]
|
|
2682
|
-
]));
|
|
2683
|
-
}
|
|
2684
|
-
this.#ast[i].dir = dir;
|
|
2685
|
-
this.#ast[i].filtered = filtered || !compound;
|
|
2686
|
-
i++;
|
|
2687
|
-
}
|
|
2688
|
-
if (pendingItems.size) {
|
|
2689
|
-
let node;
|
|
2690
|
-
let walker;
|
|
2691
|
-
if (this.#node !== this.#root && this.#node.nodeType === ELEMENT_NODE) {
|
|
2692
|
-
node = this.#node;
|
|
2693
|
-
walker = this.#finder;
|
|
2694
|
-
} else {
|
|
2695
|
-
node = this.#root;
|
|
2696
|
-
walker = this.#tree;
|
|
2697
|
-
}
|
|
2698
|
-
let nextNode = this._traverse(node, walker);
|
|
2699
|
-
while (nextNode) {
|
|
2700
|
-
let bool = false;
|
|
2701
|
-
if (this.#node.nodeType === ELEMENT_NODE) {
|
|
2702
|
-
if (nextNode === this.#node) {
|
|
2703
|
-
bool = true;
|
|
2704
|
-
} else {
|
|
2705
|
-
bool = this.#node.contains(nextNode);
|
|
2706
|
-
}
|
|
2707
|
-
} else {
|
|
2708
|
-
bool = true;
|
|
2709
|
-
}
|
|
2710
|
-
if (bool) {
|
|
2711
|
-
for (const pendingItem of pendingItems) {
|
|
2712
|
-
const { leaves } = pendingItem.get('twig');
|
|
2713
|
-
const matched = this._matchLeaves(leaves, nextNode);
|
|
2714
|
-
if (matched) {
|
|
2715
|
-
const index = pendingItem.get('index');
|
|
2716
|
-
this.#ast[index].filtered = true;
|
|
2717
|
-
this.#ast[index].find = true;
|
|
2718
|
-
this.#nodes[index].add(nextNode);
|
|
2719
|
-
}
|
|
2720
|
-
}
|
|
2721
|
-
}
|
|
2722
|
-
nextNode = walker.nextNode();
|
|
2723
|
-
}
|
|
2724
|
-
}
|
|
2725
|
-
} else {
|
|
2726
|
-
let i = 0;
|
|
2727
|
-
for (const { branch } of ast) {
|
|
2728
|
-
const twig = branch[branch.length - 1];
|
|
2729
|
-
const {
|
|
2730
|
-
compound, filtered, nodes
|
|
2731
|
-
} = this._findEntryNodes(twig, targetType);
|
|
2732
|
-
if (nodes.size) {
|
|
2733
|
-
this.#ast[i].find = true;
|
|
2734
|
-
this.#nodes[i] = nodes;
|
|
2735
|
-
}
|
|
2736
|
-
this.#ast[i].dir = DIR_PREV;
|
|
2737
|
-
this.#ast[i].filtered = filtered || !compound;
|
|
2738
|
-
i++;
|
|
2739
|
-
}
|
|
2740
|
-
}
|
|
2741
|
-
return [
|
|
2742
|
-
this.#ast,
|
|
2743
|
-
this.#nodes
|
|
2744
|
-
];
|
|
2745
|
-
}
|
|
2746
|
-
|
|
2747
|
-
/**
|
|
2748
|
-
* sort nodes
|
|
2749
|
-
* @param {Array.<object>|Set.<object>} nodes - collection of nodes
|
|
2750
|
-
* @returns {Array.<object|undefined>} - collection of sorted nodes
|
|
2751
|
-
*/
|
|
2752
|
-
_sortNodes(nodes) {
|
|
2753
|
-
const arr = [...nodes];
|
|
2754
|
-
if (arr.length > 1) {
|
|
2755
|
-
arr.sort((a, b) => {
|
|
2756
|
-
let res;
|
|
2757
|
-
if (isPreceding(b, a)) {
|
|
2758
|
-
res = 1;
|
|
2759
|
-
} else {
|
|
2760
|
-
res = -1;
|
|
2761
|
-
}
|
|
2762
|
-
return res;
|
|
2763
|
-
});
|
|
2764
|
-
}
|
|
2765
|
-
return arr;
|
|
2766
|
-
}
|
|
2767
|
-
|
|
2768
|
-
/**
|
|
2769
|
-
* match nodes
|
|
2770
|
-
* @param {string} targetType - target type
|
|
2771
|
-
* @returns {Set.<object>} - collection of matched nodes
|
|
2772
|
-
*/
|
|
2773
|
-
_matchNodes(targetType) {
|
|
2774
|
-
const [...branches] = this.#ast;
|
|
2775
|
-
const l = branches.length;
|
|
2776
|
-
let nodes = new Set();
|
|
2777
|
-
for (let i = 0; i < l; i++) {
|
|
2778
|
-
const { branch, dir, filtered, find } = branches[i];
|
|
2779
|
-
const branchLen = branch.length;
|
|
2780
|
-
if (branchLen && find) {
|
|
2781
|
-
const entryNodes = this.#nodes[i];
|
|
2782
|
-
const lastIndex = branchLen - 1;
|
|
2783
|
-
if (lastIndex === 0) {
|
|
2784
|
-
const { leaves: [, ...filterLeaves] } = branch[0];
|
|
2785
|
-
if ((targetType === TARGET_ALL || targetType === TARGET_FIRST) &&
|
|
2786
|
-
this.#node.nodeType === ELEMENT_NODE) {
|
|
2787
|
-
for (const node of entryNodes) {
|
|
2788
|
-
const bool = filtered || this._matchLeaves(filterLeaves, node);
|
|
2789
|
-
if (bool && node !== this.#node && this.#node.contains(node)) {
|
|
2790
|
-
nodes.add(node);
|
|
2791
|
-
if (targetType !== TARGET_ALL) {
|
|
2792
|
-
break;
|
|
2793
|
-
}
|
|
2794
|
-
}
|
|
2795
|
-
}
|
|
2796
|
-
} else if (!filterLeaves.length) {
|
|
2797
|
-
if (targetType === TARGET_ALL) {
|
|
2798
|
-
if (nodes.size) {
|
|
2799
|
-
const n = [...nodes];
|
|
2800
|
-
nodes = new Set([...n, ...entryNodes]);
|
|
2801
|
-
this.#sort = true;
|
|
2802
|
-
} else {
|
|
2803
|
-
nodes = new Set([...entryNodes]);
|
|
2804
|
-
}
|
|
2805
|
-
} else {
|
|
2806
|
-
const [node] = [...entryNodes];
|
|
2807
|
-
nodes.add(node);
|
|
2808
|
-
}
|
|
2809
|
-
} else {
|
|
2810
|
-
for (const node of entryNodes) {
|
|
2811
|
-
const bool = filtered || this._matchLeaves(filterLeaves, node);
|
|
2812
|
-
if (bool) {
|
|
2813
|
-
nodes.add(node);
|
|
2814
|
-
if (targetType !== TARGET_ALL) {
|
|
2815
|
-
break;
|
|
2816
|
-
}
|
|
2817
|
-
}
|
|
2818
|
-
}
|
|
2819
|
-
}
|
|
2820
|
-
} else if (dir === DIR_NEXT) {
|
|
2821
|
-
let { combo, leaves: entryLeaves } = branch[0];
|
|
2822
|
-
const [, ...filterLeaves] = entryLeaves;
|
|
2823
|
-
let matched;
|
|
2824
|
-
for (const node of entryNodes) {
|
|
2825
|
-
const bool = filtered || this._matchLeaves(filterLeaves, node);
|
|
2826
|
-
if (bool) {
|
|
2827
|
-
let nextNodes = new Set([node]);
|
|
2828
|
-
for (let j = 1; j < branchLen; j++) {
|
|
2829
|
-
const { combo: nextCombo, leaves } = branch[j];
|
|
2830
|
-
const arr = [];
|
|
2831
|
-
for (const nextNode of nextNodes) {
|
|
2832
|
-
const twig = {
|
|
2833
|
-
combo,
|
|
2834
|
-
leaves
|
|
2835
|
-
};
|
|
2836
|
-
const m = this._matchCombinator(twig, nextNode, { dir });
|
|
2837
|
-
if (m.size) {
|
|
2838
|
-
arr.push(...m);
|
|
2839
|
-
}
|
|
2840
|
-
}
|
|
2841
|
-
if (arr.length) {
|
|
2842
|
-
if (j === lastIndex) {
|
|
2843
|
-
if (targetType === TARGET_ALL) {
|
|
2844
|
-
if (nodes.size) {
|
|
2845
|
-
const n = [...nodes];
|
|
2846
|
-
nodes = new Set([...n, ...arr]);
|
|
2847
|
-
} else {
|
|
2848
|
-
nodes = new Set([...arr]);
|
|
2849
|
-
}
|
|
2850
|
-
this.#sort = true;
|
|
2851
|
-
} else {
|
|
2852
|
-
const [node] = this._sortNodes(arr);
|
|
2853
|
-
nodes.add(node);
|
|
2854
|
-
}
|
|
2855
|
-
matched = true;
|
|
2856
|
-
} else {
|
|
2857
|
-
combo = nextCombo;
|
|
2858
|
-
nextNodes = new Set(arr);
|
|
2859
|
-
matched = false;
|
|
2860
|
-
}
|
|
2861
|
-
} else {
|
|
2862
|
-
matched = false;
|
|
2863
|
-
break;
|
|
2864
|
-
}
|
|
2865
|
-
}
|
|
2866
|
-
} else {
|
|
2867
|
-
matched = false;
|
|
2868
|
-
}
|
|
2869
|
-
if (matched && targetType !== TARGET_ALL) {
|
|
2870
|
-
break;
|
|
2871
|
-
}
|
|
2872
|
-
}
|
|
2873
|
-
if (!matched && targetType === TARGET_FIRST) {
|
|
2874
|
-
const [entryNode] = [...entryNodes];
|
|
2875
|
-
let refNode = this._findNode(entryLeaves, {
|
|
2876
|
-
node: entryNode
|
|
2877
|
-
});
|
|
2878
|
-
while (refNode) {
|
|
2879
|
-
let nextNodes = new Set([refNode]);
|
|
2880
|
-
for (let j = 1; j < branchLen; j++) {
|
|
2881
|
-
const { combo: nextCombo, leaves } = branch[j];
|
|
2882
|
-
const arr = [];
|
|
2883
|
-
for (const nextNode of nextNodes) {
|
|
2884
|
-
const twig = {
|
|
2885
|
-
combo,
|
|
2886
|
-
leaves
|
|
2887
|
-
};
|
|
2888
|
-
const m = this._matchCombinator(twig, nextNode, { dir });
|
|
2889
|
-
if (m.size) {
|
|
2890
|
-
arr.push(...m);
|
|
2891
|
-
}
|
|
2892
|
-
}
|
|
2893
|
-
if (arr.length) {
|
|
2894
|
-
if (j === lastIndex) {
|
|
2895
|
-
const [node] = this._sortNodes(arr);
|
|
2896
|
-
nodes.add(node);
|
|
2897
|
-
matched = true;
|
|
2898
|
-
} else {
|
|
2899
|
-
combo = nextCombo;
|
|
2900
|
-
nextNodes = new Set(arr);
|
|
2901
|
-
matched = false;
|
|
2902
|
-
}
|
|
2903
|
-
} else {
|
|
2904
|
-
matched = false;
|
|
2905
|
-
break;
|
|
2906
|
-
}
|
|
2907
|
-
}
|
|
2908
|
-
if (matched) {
|
|
2909
|
-
break;
|
|
2910
|
-
}
|
|
2911
|
-
refNode = this._findNode(entryLeaves, {
|
|
2912
|
-
node: refNode
|
|
2913
|
-
});
|
|
2914
|
-
nextNodes = new Set([refNode]);
|
|
2915
|
-
}
|
|
2916
|
-
}
|
|
2917
|
-
} else {
|
|
2918
|
-
const { leaves: entryLeaves } = branch[lastIndex];
|
|
2919
|
-
const [, ...filterLeaves] = entryLeaves;
|
|
2920
|
-
let matched;
|
|
2921
|
-
for (const node of entryNodes) {
|
|
2922
|
-
const bool = filtered || this._matchLeaves(filterLeaves, node);
|
|
2923
|
-
if (bool) {
|
|
2924
|
-
let nextNodes = new Set([node]);
|
|
2925
|
-
for (let j = lastIndex - 1; j >= 0; j--) {
|
|
2926
|
-
const twig = branch[j];
|
|
2927
|
-
const arr = [];
|
|
2928
|
-
for (const nextNode of nextNodes) {
|
|
2929
|
-
const m = this._matchCombinator(twig, nextNode, { dir });
|
|
2930
|
-
if (m.size) {
|
|
2931
|
-
arr.push(...m);
|
|
2932
|
-
}
|
|
2933
|
-
}
|
|
2934
|
-
if (arr.length) {
|
|
2935
|
-
if (j === 0) {
|
|
2936
|
-
nodes.add(node);
|
|
2937
|
-
matched = true;
|
|
2938
|
-
if (targetType === TARGET_ALL &&
|
|
2939
|
-
branchLen > 1 && nodes.size > 1) {
|
|
2940
|
-
this.#sort = true;
|
|
2941
|
-
}
|
|
2942
|
-
} else {
|
|
2943
|
-
nextNodes = new Set(arr);
|
|
2944
|
-
matched = false;
|
|
2945
|
-
}
|
|
2946
|
-
} else {
|
|
2947
|
-
matched = false;
|
|
2948
|
-
break;
|
|
2949
|
-
}
|
|
2950
|
-
}
|
|
2951
|
-
}
|
|
2952
|
-
if (matched && targetType !== TARGET_ALL) {
|
|
2953
|
-
break;
|
|
2954
|
-
}
|
|
2955
|
-
}
|
|
2956
|
-
if (!matched && targetType === TARGET_FIRST) {
|
|
2957
|
-
const [entryNode] = [...entryNodes];
|
|
2958
|
-
let refNode = this._findNode(entryLeaves, {
|
|
2959
|
-
node: entryNode
|
|
2960
|
-
});
|
|
2961
|
-
while (refNode) {
|
|
2962
|
-
let nextNodes = new Set([refNode]);
|
|
2963
|
-
for (let j = lastIndex - 1; j >= 0; j--) {
|
|
2964
|
-
const twig = branch[j];
|
|
2965
|
-
const arr = [];
|
|
2966
|
-
for (const nextNode of nextNodes) {
|
|
2967
|
-
const m = this._matchCombinator(twig, nextNode, { dir });
|
|
2968
|
-
if (m.size) {
|
|
2969
|
-
arr.push(...m);
|
|
2970
|
-
}
|
|
2971
|
-
}
|
|
2972
|
-
if (arr.length) {
|
|
2973
|
-
if (j === 0) {
|
|
2974
|
-
nodes.add(refNode);
|
|
2975
|
-
matched = true;
|
|
2976
|
-
} else {
|
|
2977
|
-
nextNodes = new Set(arr);
|
|
2978
|
-
matched = false;
|
|
2979
|
-
}
|
|
2980
|
-
} else {
|
|
2981
|
-
matched = false;
|
|
2982
|
-
break;
|
|
2983
|
-
}
|
|
2984
|
-
}
|
|
2985
|
-
if (matched) {
|
|
2986
|
-
break;
|
|
2987
|
-
}
|
|
2988
|
-
refNode = this._findNode(entryLeaves, {
|
|
2989
|
-
node: refNode
|
|
2990
|
-
});
|
|
2991
|
-
nextNodes = new Set([refNode]);
|
|
2992
|
-
}
|
|
2993
|
-
}
|
|
2994
|
-
}
|
|
2995
|
-
}
|
|
2996
|
-
}
|
|
2997
|
-
return nodes;
|
|
2998
|
-
}
|
|
2999
|
-
|
|
3000
|
-
/**
|
|
3001
|
-
* find matched nodes
|
|
3002
|
-
* @param {string} targetType - target type
|
|
3003
|
-
* @param {object} node - Document, DocumentFragment, Element node
|
|
3004
|
-
* @param {string} selector - CSS selector
|
|
3005
|
-
* @param {object} [opt] - options
|
|
3006
|
-
* @returns {Set.<object>} - collection of matched nodes
|
|
3007
|
-
*/
|
|
3008
|
-
_find(targetType, node, selector, opt = {}) {
|
|
3009
|
-
const { warn } = opt;
|
|
3010
|
-
this.#warn = !!warn;
|
|
3011
|
-
if (!node) {
|
|
3012
|
-
const nodeType =
|
|
3013
|
-
Object.prototype.toString.call(node).slice(TYPE_FROM, TYPE_TO);
|
|
3014
|
-
const msg = `Unexpected node ${nodeType}`;
|
|
3015
|
-
throw new TypeError(msg);
|
|
3016
|
-
} else if (node.nodeType !== DOCUMENT_NODE &&
|
|
3017
|
-
node.nodeType !== DOCUMENT_FRAGMENT_NODE &&
|
|
3018
|
-
node.nodeType !== ELEMENT_NODE) {
|
|
3019
|
-
const msg = `Unexpected node ${node.nodeName}`;
|
|
3020
|
-
throw new TypeError(msg);
|
|
3021
|
-
} else if ((targetType === TARGET_SELF || targetType === TARGET_LINEAL) &&
|
|
3022
|
-
node.nodeType !== ELEMENT_NODE) {
|
|
3023
|
-
const msg = `Unexpected node ${node.nodeName}`;
|
|
3024
|
-
throw new TypeError(msg);
|
|
3025
|
-
}
|
|
3026
|
-
this.#node = node;
|
|
3027
|
-
[this.#window, this.#document, this.#root] = this._setup(node);
|
|
3028
|
-
this.#shadow = isInShadowTree(node);
|
|
3029
|
-
this.#selector = selector;
|
|
3030
|
-
[this.#ast, this.#nodes] = this._correspond(selector);
|
|
3031
|
-
if (targetType === TARGET_ALL || targetType === TARGET_FIRST) {
|
|
3032
|
-
this.#tree = this.#document.createTreeWalker(this.#root, WALKER_FILTER);
|
|
3033
|
-
this.#finder = this.#document.createTreeWalker(this.#node, SHOW_ELEMENT);
|
|
3034
|
-
this.#sort = false;
|
|
3035
|
-
}
|
|
3036
|
-
this._collectNodes(targetType);
|
|
3037
|
-
const nodes = this._matchNodes(targetType);
|
|
3038
|
-
return nodes;
|
|
3039
|
-
}
|
|
3040
|
-
|
|
3041
|
-
/**
|
|
3042
|
-
* matches
|
|
3043
|
-
* @param {object} node - Element node
|
|
3044
|
-
* @param {string} selector - CSS selector
|
|
3045
|
-
* @param {object} opt - options
|
|
3046
|
-
* @returns {boolean} - `true` if matched `false` otherwise
|
|
3047
|
-
*/
|
|
3048
|
-
matches(node, selector, opt) {
|
|
3049
|
-
let res;
|
|
3050
|
-
try {
|
|
3051
|
-
const nodes = this._find(TARGET_SELF, node, selector, opt);
|
|
3052
|
-
if (nodes.size) {
|
|
3053
|
-
res = nodes.has(this.#node);
|
|
3054
|
-
}
|
|
3055
|
-
} catch (e) {
|
|
3056
|
-
this._onError(e);
|
|
3057
|
-
}
|
|
3058
|
-
return !!res;
|
|
3059
|
-
}
|
|
3060
|
-
|
|
3061
|
-
/**
|
|
3062
|
-
* closest
|
|
3063
|
-
* @param {object} node - Element node
|
|
3064
|
-
* @param {string} selector - CSS selector
|
|
3065
|
-
* @param {object} opt - options
|
|
3066
|
-
* @returns {?object} - matched node
|
|
3067
|
-
*/
|
|
3068
|
-
closest(node, selector, opt) {
|
|
3069
|
-
let res;
|
|
3070
|
-
try {
|
|
3071
|
-
const nodes = this._find(TARGET_LINEAL, node, selector, opt);
|
|
3072
|
-
let refNode = this.#node;
|
|
3073
|
-
while (refNode) {
|
|
3074
|
-
if (nodes.has(refNode)) {
|
|
3075
|
-
res = refNode;
|
|
3076
|
-
break;
|
|
3077
|
-
}
|
|
3078
|
-
refNode = refNode.parentNode;
|
|
3079
|
-
}
|
|
3080
|
-
} catch (e) {
|
|
3081
|
-
this._onError(e);
|
|
3082
|
-
}
|
|
3083
|
-
return res ?? null;
|
|
3084
|
-
}
|
|
3085
|
-
|
|
3086
|
-
/**
|
|
3087
|
-
* query selector
|
|
3088
|
-
* @param {object} node - Document, DocumentFragment, Element node
|
|
3089
|
-
* @param {string} selector - CSS selector
|
|
3090
|
-
* @param {object} opt - options
|
|
3091
|
-
* @returns {?object} - matched node
|
|
3092
|
-
*/
|
|
3093
|
-
querySelector(node, selector, opt) {
|
|
3094
|
-
let res;
|
|
3095
|
-
try {
|
|
3096
|
-
const nodes = this._find(TARGET_FIRST, node, selector, opt);
|
|
3097
|
-
nodes.delete(this.#node);
|
|
3098
|
-
if (nodes.size) {
|
|
3099
|
-
[res] = this._sortNodes(nodes);
|
|
3100
|
-
}
|
|
3101
|
-
} catch (e) {
|
|
3102
|
-
this._onError(e);
|
|
3103
|
-
}
|
|
3104
|
-
return res ?? null;
|
|
3105
|
-
}
|
|
3106
|
-
|
|
3107
|
-
/**
|
|
3108
|
-
* query selector all
|
|
3109
|
-
* NOTE: returns Array, not NodeList
|
|
3110
|
-
* @param {object} node - Document, DocumentFragment, Element node
|
|
3111
|
-
* @param {string} selector - CSS selector
|
|
3112
|
-
* @param {object} opt - options
|
|
3113
|
-
* @returns {Array.<object|undefined>} - collection of matched nodes
|
|
3114
|
-
*/
|
|
3115
|
-
querySelectorAll(node, selector, opt) {
|
|
3116
|
-
let res;
|
|
3117
|
-
try {
|
|
3118
|
-
const nodes = this._find(TARGET_ALL, node, selector, opt);
|
|
3119
|
-
nodes.delete(this.#node);
|
|
3120
|
-
if (nodes.size) {
|
|
3121
|
-
if (this.#sort) {
|
|
3122
|
-
res = this._sortNodes(nodes);
|
|
3123
|
-
} else {
|
|
3124
|
-
res = [...nodes];
|
|
316
|
+
/**
|
|
317
|
+
* match selector
|
|
318
|
+
* @param {object} ast - AST
|
|
319
|
+
* @param {object} node - Element node or structured clone
|
|
320
|
+
* @param {object} opt - options
|
|
321
|
+
* @returns {?object} - matched node
|
|
322
|
+
*/
|
|
323
|
+
export const matchSelector = (ast, node, opt) => {
|
|
324
|
+
if (!ast || !ast.type) {
|
|
325
|
+
const nodeType =
|
|
326
|
+
Object.prototype.toString.call(ast).slice(TYPE_FROM, TYPE_TO);
|
|
327
|
+
const msg = `Unexpected node ${nodeType}`;
|
|
328
|
+
throw new TypeError(msg);
|
|
329
|
+
} else if (!node || !node.nodeType) {
|
|
330
|
+
const nodeType =
|
|
331
|
+
Object.prototype.toString.call(node).slice(TYPE_FROM, TYPE_TO);
|
|
332
|
+
const msg = `Unexpected node ${nodeType}`;
|
|
333
|
+
throw new TypeError(msg);
|
|
334
|
+
} else if (node.nodeType !== ELEMENT_NODE) {
|
|
335
|
+
const msg = `Unexpected node ${node.nodeName}`;
|
|
336
|
+
throw new TypeError(msg);
|
|
337
|
+
}
|
|
338
|
+
let matched;
|
|
339
|
+
switch (ast.type) {
|
|
340
|
+
case SELECTOR_ATTR: {
|
|
341
|
+
matched = _matchAttributeSelector(ast, node);
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
case SELECTOR_CLASS: {
|
|
345
|
+
matched = _matchClassSelector(ast, node);
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
case SELECTOR_ID: {
|
|
349
|
+
matched = _matchIDSelector(ast, node);
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
case SELECTOR_TYPE:
|
|
353
|
+
default: {
|
|
354
|
+
matched = _matchTypeSelector(ast, node, opt);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return matched;
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* match pseudo-element selector
|
|
362
|
+
* @param {string} astName - AST name
|
|
363
|
+
* @param {object} opt - options
|
|
364
|
+
* @param {boolean} [opt.forgive] - forgive unknown pseudo-element
|
|
365
|
+
* @param {boolean} [opt.warn] - warn unsupported pseudo-element
|
|
366
|
+
* @throws {DOMException}
|
|
367
|
+
* @returns {void}
|
|
368
|
+
*/
|
|
369
|
+
export const matchPseudoElementSelector = (astName, opt = {}) => {
|
|
370
|
+
if (!astName || typeof astName !== 'string') {
|
|
371
|
+
const nodeType =
|
|
372
|
+
Object.prototype.toString.call(astName).slice(TYPE_FROM, TYPE_TO);
|
|
373
|
+
const msg = `Unexpected type ${nodeType}`;
|
|
374
|
+
throw new TypeError(msg);
|
|
375
|
+
}
|
|
376
|
+
const { forgive, warn } = opt;
|
|
377
|
+
switch (astName) {
|
|
378
|
+
case 'after':
|
|
379
|
+
case 'backdrop':
|
|
380
|
+
case 'before':
|
|
381
|
+
case 'cue':
|
|
382
|
+
case 'cue-region':
|
|
383
|
+
case 'first-letter':
|
|
384
|
+
case 'first-line':
|
|
385
|
+
case 'file-selector-button':
|
|
386
|
+
case 'marker':
|
|
387
|
+
case 'placeholder':
|
|
388
|
+
case 'selection':
|
|
389
|
+
case 'target-text': {
|
|
390
|
+
if (warn) {
|
|
391
|
+
const msg = `Unsupported pseudo-element ::${astName}`;
|
|
392
|
+
throw new DOMException(msg, NOT_SUPPORTED_ERR);
|
|
393
|
+
}
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
case 'part':
|
|
397
|
+
case 'slotted': {
|
|
398
|
+
if (warn) {
|
|
399
|
+
const msg = `Unsupported pseudo-element ::${astName}()`;
|
|
400
|
+
throw new DOMException(msg, NOT_SUPPORTED_ERR);
|
|
401
|
+
}
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
default: {
|
|
405
|
+
if (astName.startsWith('-webkit-')) {
|
|
406
|
+
if (warn) {
|
|
407
|
+
const msg = `Unsupported pseudo-element ::${astName}`;
|
|
408
|
+
throw new DOMException(msg, NOT_SUPPORTED_ERR);
|
|
3125
409
|
}
|
|
410
|
+
} else if (!forgive) {
|
|
411
|
+
const msg = `Unknown pseudo-element ::${astName}`;
|
|
412
|
+
throw new DOMException(msg, SYNTAX_ERR);
|
|
3126
413
|
}
|
|
3127
|
-
} catch (e) {
|
|
3128
|
-
this._onError(e);
|
|
3129
414
|
}
|
|
3130
|
-
return res ?? [];
|
|
3131
415
|
}
|
|
3132
416
|
};
|