@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/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 isCustomElementName from 'is-potential-custom-element-name';
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
- ALPHA_NUM, BIT_01, BIT_02, BIT_04, BIT_08, BIT_16, BIT_32, COMBINATOR,
18
- DOCUMENT_FRAGMENT_NODE, DOCUMENT_NODE, ELEMENT_NODE, NOT_SUPPORTED_ERR,
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
- * Matcher
34
- * NOTE: #ast[i] corresponds to #nodes[i]
35
- * #ast: [
36
- * {
37
- * branch: branch[],
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 class Matcher {
63
- /* private fields */
64
- #ast;
65
- #bit;
66
- #cache;
67
- #document;
68
- #finder;
69
- #node;
70
- #nodes;
71
- #root;
72
- #selector;
73
- #shadow;
74
- #sort;
75
- #tree;
76
- #warn;
77
- #window;
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
- throw e;
43
+ caseInsensitive = true;
111
44
  }
45
+ } else if (typeof astFlags === 'string' && /^i$/i.test(astFlags)) {
46
+ caseInsensitive = true;
112
47
  } else {
113
- throw e;
48
+ caseInsensitive = false;
114
49
  }
115
- }
116
-
117
- /**
118
- * set up #window, #document, #root
119
- * @param {object} node - Document, DocumentFragment, Element node
120
- * @returns {Array.<object>} - array of #window, #document, #root
121
- */
122
- _setup(node) {
123
- let document;
124
- let root;
125
- switch (node?.nodeType) {
126
- case DOCUMENT_NODE: {
127
- document = node;
128
- root = node;
129
- break;
130
- }
131
- case DOCUMENT_FRAGMENT_NODE: {
132
- document = node.ownerDocument;
133
- root = node;
134
- break;
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
- if (parent.nodeType === DOCUMENT_NODE) {
150
- document = parent;
151
- } else {
152
- document = parent.ownerDocument;
153
- }
154
- root = parent;
155
- }
156
- break;
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
- cachedItem.set(`${selector}`, e.message);
238
- this.#cache.set(this.#document, cachedItem);
239
- }
240
- this._onError(e);
241
- }
242
- const branches = walkAST(cssAst);
243
- ast = [];
244
- let i = 0;
245
- for (const [...items] of branches) {
246
- const branch = [];
247
- let item = items.shift();
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
- if (refNode !== walker.root) {
320
- while (refNode) {
321
- if (refNode === walker.root ||
322
- (node.nodeType === ELEMENT_NODE && refNode === node)) {
323
- break;
324
- }
325
- refNode = walker.parentNode();
326
- }
327
- }
328
- if (node.nodeType === ELEMENT_NODE) {
329
- while (refNode) {
330
- if (refNode === node) {
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
- refNode = walker.nextNode();
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 (parentNode) {
367
- const walker = this.#document.createTreeWalker(parentNode, WALKER_FILTER);
368
- let l = 0;
369
- let refNode = walker.firstChild();
370
- while (refNode) {
371
- l++;
372
- refNode = walker.nextSibling();
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
- // :first-child, :last-child, :nth-child(b of S), :nth-last-child(b of S)
394
- if (a === 0) {
395
- if (b > 0 && b <= l) {
396
- if (selectorNodes.size) {
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
- // :nth-child()
441
- } else {
442
- let nth = b - 1;
443
- if (a > 0) {
444
- while (nth < 0) {
445
- nth += a;
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
- if (nth >= 0 && nth < l) {
449
- let i = 0;
450
- let j = a > 0 ? 0 : b - 1;
451
- refNode = this._traverse(parentNode, walker);
452
- if (reverse) {
453
- refNode = walker.lastChild();
454
- } else {
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
- if (reverse && matched.size > 1) {
490
- const m = [...matched];
491
- matched = new Set(m.reverse());
492
- }
493
- } else if (node === this.#root && this.#root.nodeType === ELEMENT_NODE &&
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 (reverse) {
554
- refNode = walker.previousSibling();
555
- } else {
556
- refNode = walker.nextSibling();
165
+ if (item) {
166
+ res = node;
557
167
  }
558
168
  }
169
+ break;
559
170
  }
560
- // :nth-of-type()
561
- } else {
562
- let nth = b - 1;
563
- if (a > 0) {
564
- while (nth < 0) {
565
- nth += a;
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 (reverse) {
592
- refNode = walker.previousSibling();
593
- } else {
594
- refNode = walker.nextSibling();
180
+ if (item) {
181
+ res = node;
595
182
  }
596
183
  }
184
+ break;
597
185
  }
598
- }
599
- if (reverse && matched.size > 1) {
600
- const m = [...matched];
601
- matched = new Set(m.reverse());
602
- }
603
- } else if (node === this.#root && this.#root.nodeType === ELEMENT_NODE &&
604
- (a + b) === 1) {
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
- parent = parent.parentNode;
769
- } else {
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
- regExtendedLang =
796
- new RegExp(`^${extendedMain}${extendedSub}${extendedRest}$`, 'i');
797
- } else {
798
- regExtendedLang = new RegExp(`^${astName}${langPart}$`, 'i');
199
+ break;
799
200
  }
800
- if (node.hasAttribute('lang')) {
801
- if (regExtendedLang.test(node.getAttribute('lang'))) {
802
- res = node;
803
- }
804
- } else {
805
- let parent = node.parentNode;
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
- parent = parent.parentNode;
816
- } else {
817
- break;
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
- const twig = {
857
- combo,
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
- * match logical pseudo-class functions - :has(), :is(), :not(), :where()
882
- * @param {object} astData - AST data
883
- * @param {object} node - Element node
884
- * @returns {?object} - matched node
885
- */
886
- _matchLogicalPseudoFunc(astData, node) {
887
- const {
888
- astName = '', branches = [], selector = '', twigBranches = []
889
- } = astData;
890
- let res;
891
- if (astName === 'has') {
892
- if (selector.includes(':has(')) {
893
- res = null;
894
- } else {
895
- let bool;
896
- for (const leaves of branches) {
897
- bool = this._matchHasPseudoFunc(Object.assign([], leaves), node);
898
- if (bool) {
899
- break;
900
- }
901
- }
902
- if (bool) {
903
- res = node;
904
- }
905
- }
906
- } else {
907
- const forgive = /^(?:is|where)$/.test(astName);
908
- const l = twigBranches.length;
909
- let bool;
910
- for (let i = 0; i < l; i++) {
911
- const branch = twigBranches[i];
912
- const lastIndex = branch.length - 1;
913
- const { leaves } = branch[lastIndex];
914
- bool = this._matchLeaves(leaves, node, { forgive });
915
- if (bool && lastIndex > 0) {
916
- let nextNodes = new Set([node]);
917
- for (let j = lastIndex - 1; j >= 0; j--) {
918
- const twig = branch[j];
919
- const arr = [];
920
- for (const nextNode of nextNodes) {
921
- const m = this._matchCombinator(twig, nextNode, {
922
- forgive,
923
- dir: DIR_PREV
924
- });
925
- if (m.size) {
926
- arr.push(...m);
927
- }
928
- }
929
- if (arr.length) {
930
- if (j === 0) {
931
- bool = true;
932
- } else {
933
- nextNodes = new Set(arr);
934
- }
935
- } else {
936
- bool = false;
937
- break;
938
- }
939
- }
940
- }
941
- if (bool) {
942
- break;
943
- }
944
- }
945
- if (astName === 'not') {
946
- if (!bool) {
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 (bool) {
950
- res = node;
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
- * match pseudo-class selector
958
- * @see https://html.spec.whatwg.org/#pseudo-classes
959
- * @param {object} ast - AST
960
- * @param {object} node - Element node
961
- * @param {object} [opt] - options
962
- * @param {boolean} [opt.forgive] - is forgiving selector list
963
- * @returns {Set.<object>} - collection of matched nodes
964
- */
965
- _matchPseudoClassSelector(ast, node, opt = {}) {
966
- const { children: astChildren } = ast;
967
- const { localName, parentNode } = node;
968
- const { forgive } = opt;
969
- const astName = unescapeSelector(ast.name);
970
- let matched = new Set();
971
- // :has(), :is(), :not(), :where()
972
- if (REG_LOGICAL_PSEUDO.test(astName)) {
973
- let astData;
974
- if (this.#cache.has(ast)) {
975
- astData = this.#cache.get(ast);
976
- } else {
977
- const branches = walkAST(ast);
978
- const selectors = [];
979
- const twigBranches = [];
980
- for (const [...leaves] of branches) {
981
- for (const leaf of leaves) {
982
- const css = generateCSS(leaf);
983
- selectors.push(css);
984
- }
985
- const branch = [];
986
- const leavesSet = new Set();
987
- let item = leaves.shift();
988
- while (item) {
989
- if (item.type === COMBINATOR) {
990
- branch.push({
991
- combo: item,
992
- leaves: [...leavesSet]
993
- });
994
- leavesSet.clear();
995
- } else if (item) {
996
- leavesSet.add(item);
997
- }
998
- if (leaves.length) {
999
- item = leaves.shift();
1000
- } else {
1001
- branch.push({
1002
- combo: null,
1003
- leaves: [...leavesSet]
1004
- });
1005
- leavesSet.clear();
1006
- break;
1007
- }
1008
- }
1009
- twigBranches.push(branch);
1010
- }
1011
- astData = {
1012
- astName,
1013
- branches,
1014
- twigBranches,
1015
- selector: selectors.join(',')
1016
- };
1017
- this.#cache.set(ast, astData);
1018
- }
1019
- const res = this._matchLogicalPseudoFunc(astData, node);
1020
- if (res) {
1021
- matched.add(res);
1022
- }
1023
- } else if (Array.isArray(astChildren)) {
1024
- const [branch] = astChildren;
1025
- // :nth-child(), :nth-last-child(), nth-of-type(), :nth-last-of-type()
1026
- if (/^nth-(?:last-)?(?:child|of-type)$/.test(astName)) {
1027
- const nodes = this._matchAnPlusB(branch, node, astName);
1028
- if (nodes.size) {
1029
- matched = nodes;
1030
- }
1031
- // :dir()
1032
- } else if (astName === 'dir') {
1033
- const res = this._matchDirectionPseudoClass(branch, node);
1034
- if (res) {
1035
- matched.add(res);
1036
- }
1037
- // :lang()
1038
- } else if (astName === 'lang') {
1039
- const res = this._matchLanguagePseudoClass(branch, node);
1040
- if (res) {
1041
- matched.add(res);
1042
- }
1043
- } else {
1044
- switch (astName) {
1045
- case 'current':
1046
- case 'nth-col':
1047
- case 'nth-last-col': {
1048
- if (this.#warn) {
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
  };