@asamuzakjp/dom-selector 1.2.2 → 1.2.4

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
@@ -18,7 +18,7 @@ import {
18
18
  DOCUMENT_FRAGMENT_NODE, DOCUMENT_NODE, ELEMENT_NODE, NOT_SUPPORTED_ERR,
19
19
  REG_LOGICAL_PSEUDO, REG_SHADOW_HOST, SELECTOR_ATTR, SELECTOR_CLASS,
20
20
  SELECTOR_ID, SELECTOR_PSEUDO_CLASS, SELECTOR_PSEUDO_ELEMENT, SELECTOR_TYPE,
21
- SHOW_ELEMENT, SYNTAX_ERR, TEXT_NODE
21
+ SHOW_DOCUMENT, SHOW_DOCUMENT_FRAGMENT, SHOW_ELEMENT, SYNTAX_ERR, TEXT_NODE
22
22
  } from './constant.js';
23
23
  const FIND_NEXT = 'next';
24
24
  const FIND_PREV = 'prev';
@@ -62,10 +62,13 @@ export class Matcher {
62
62
  #ast;
63
63
  #bit;
64
64
  #cache;
65
+ #document;
65
66
  #node;
66
67
  #nodes;
67
68
  #root;
68
- #selector;
69
+ #shadow;
70
+ #subtree;
71
+ #tree;
69
72
  #warn;
70
73
 
71
74
  /**
@@ -86,11 +89,11 @@ export class Matcher {
86
89
  [SELECTOR_PSEUDO_CLASS, BIT_32]
87
90
  ]);
88
91
  this.#cache = new WeakMap();
89
- this.#selector = selector;
92
+ [this.#ast, this.#nodes] = this._prepare(selector);
90
93
  this.#node = node;
94
+ [this.#document, this.#root, this.#tree] = this._setup(node);
95
+ this.#shadow = isInShadowTree(node);
91
96
  this.#warn = !!warn;
92
- [this.#ast, this.#nodes] = this._prepare(selector);
93
- this.#root = this._getRoot(node);
94
97
  }
95
98
 
96
99
  /**
@@ -110,11 +113,11 @@ export class Matcher {
110
113
  }
111
114
 
112
115
  /**
113
- * get root
116
+ * set up document, root, walker
114
117
  * @param {object} node - Document, DocumentFragment, Element node
115
- * @returns {object} - root object
118
+ * @returns {Array.<object>} - document, root, walker
116
119
  */
117
- _getRoot(node = this.#node) {
120
+ _setup(node) {
118
121
  let document;
119
122
  let root;
120
123
  switch (node.nodeType) {
@@ -150,12 +153,13 @@ export class Matcher {
150
153
  throw new TypeError(`Unexpected node ${node.nodeName}`);
151
154
  }
152
155
  }
153
- const shadow = isInShadowTree(node);
154
- return {
156
+ const filter = SHOW_DOCUMENT | SHOW_DOCUMENT_FRAGMENT | SHOW_ELEMENT;
157
+ const walker = document.createTreeWalker(root, filter);
158
+ return [
155
159
  document,
156
160
  root,
157
- shadow
158
- };
161
+ walker
162
+ ];
159
163
  }
160
164
 
161
165
  /**
@@ -190,7 +194,7 @@ export class Matcher {
190
194
  * @param {string} selector - CSS selector
191
195
  * @returns {Array.<Array.<object|undefined>>} - array of ast and nodes
192
196
  */
193
- _prepare(selector = this.#selector) {
197
+ _prepare(selector) {
194
198
  const ast = parseSelector(selector);
195
199
  const branches = walkAST(ast);
196
200
  const tree = [];
@@ -243,6 +247,42 @@ export class Matcher {
243
247
  ];
244
248
  }
245
249
 
250
+ /**
251
+ * traverse tree walker
252
+ * @param {object} [node] - Element node
253
+ * @param {object} [tree] - tree walker
254
+ * @returns {?object} - node
255
+ */
256
+ _traverse(node = {}, tree = this.#tree) {
257
+ let current;
258
+ let refNode = tree.currentNode;
259
+ if (node.nodeType === ELEMENT_NODE && refNode === node) {
260
+ current = refNode;
261
+ } else {
262
+ if (refNode !== tree.root) {
263
+ while (refNode) {
264
+ if (refNode === tree.root ||
265
+ (node.nodeType === ELEMENT_NODE && refNode === node)) {
266
+ break;
267
+ }
268
+ refNode = tree.parentNode();
269
+ }
270
+ }
271
+ if (node.nodeType === ELEMENT_NODE) {
272
+ while (refNode) {
273
+ if (refNode === node) {
274
+ current = refNode;
275
+ break;
276
+ }
277
+ refNode = tree.nextNode();
278
+ }
279
+ } else {
280
+ current = refNode;
281
+ }
282
+ }
283
+ return current ?? null;
284
+ }
285
+
246
286
  /**
247
287
  * collect nth child
248
288
  * @param {object} anb - An+B options
@@ -267,16 +307,15 @@ export class Matcher {
267
307
  }
268
308
  }
269
309
  if (parentNode) {
270
- const arr = [].slice.call(parentNode.children);
310
+ const arr = [].slice.call(parentNode.childNodes)
311
+ .filter(n => n.nodeType === ELEMENT_NODE);
271
312
  const l = arr.length;
272
313
  if (l) {
273
314
  const selectorNodes = new Set();
274
315
  if (selectorBranches) {
275
- const branchesLen = selectorBranches.length;
276
316
  for (const refNode of arr) {
277
317
  let bool;
278
- for (let i = 0; i < branchesLen; i++) {
279
- const leaves = selectorBranches[i];
318
+ for (const leaves of selectorBranches) {
280
319
  bool = this._matchLeaves(leaves, refNode);
281
320
  if (!bool) {
282
321
  break;
@@ -294,8 +333,7 @@ export class Matcher {
294
333
  if (a === 0) {
295
334
  if (b > 0 && b <= l) {
296
335
  if (selectorNodes.size) {
297
- for (let i = 0; i < l; i++) {
298
- const current = arr[i];
336
+ for (const current of arr) {
299
337
  if (selectorNodes.has(current)) {
300
338
  matched.add(current);
301
339
  break;
@@ -341,13 +379,11 @@ export class Matcher {
341
379
  }
342
380
  }
343
381
  } else {
344
- const { root } = this.#root;
345
- if (node === root && root.nodeType === ELEMENT_NODE && (a + b) === 1) {
382
+ if (node === this.#root && this.#root.nodeType === ELEMENT_NODE &&
383
+ (a + b) === 1) {
346
384
  if (selectorBranches) {
347
- const branchesLen = selectorBranches.length;
348
385
  let bool;
349
- for (let i = 0; i < branchesLen; i++) {
350
- const leaves = selectorBranches[i];
386
+ for (const leaves of selectorBranches) {
351
387
  bool = this._matchLeaves(leaves, node);
352
388
  if (bool) {
353
389
  break;
@@ -378,7 +414,8 @@ export class Matcher {
378
414
  const { localName, parentNode, prefix } = node;
379
415
  const matched = new Set();
380
416
  if (parentNode) {
381
- const arr = [].slice.call(parentNode.children);
417
+ const arr = [].slice.call(parentNode.childNodes)
418
+ .filter(n => n.nodeType === ELEMENT_NODE);
382
419
  const l = arr.length;
383
420
  if (l) {
384
421
  if (reverse) {
@@ -388,8 +425,7 @@ export class Matcher {
388
425
  if (a === 0) {
389
426
  if (b > 0 && b <= l) {
390
427
  let j = 0;
391
- for (let i = 0; i < l; i++) {
392
- const current = arr[i];
428
+ for (const current of arr) {
393
429
  const { localName: itemLocalName, prefix: itemPrefix } = current;
394
430
  if (itemLocalName === localName && itemPrefix === prefix) {
395
431
  if (j === b - 1) {
@@ -410,8 +446,7 @@ export class Matcher {
410
446
  }
411
447
  if (nth >= 0 && nth < l) {
412
448
  let j = a > 0 ? 0 : b - 1;
413
- for (let i = 0; i < l; i++) {
414
- const current = arr[i];
449
+ for (const current of arr) {
415
450
  const { localName: itemLocalName, prefix: itemPrefix } = current;
416
451
  if (itemLocalName === localName && itemPrefix === prefix) {
417
452
  if (j === nth) {
@@ -431,8 +466,8 @@ export class Matcher {
431
466
  }
432
467
  }
433
468
  } else {
434
- const { root } = this.#root;
435
- if (node === root && root.nodeType === ELEMENT_NODE && (a + b) === 1) {
469
+ if (node === this.#root && this.#root.nodeType === ELEMENT_NODE &&
470
+ (a + b) === 1) {
436
471
  matched.add(node);
437
472
  }
438
473
  }
@@ -900,7 +935,6 @@ export class Matcher {
900
935
  }
901
936
  }
902
937
  } else {
903
- const { document, root } = this.#root;
904
938
  const regAnchor = /^a(?:rea)?$/;
905
939
  const regFormCtrl =
906
940
  /^(?:(?:fieldse|inpu|selec)t|button|opt(?:group|ion)|textarea)$/;
@@ -921,7 +955,7 @@ export class Matcher {
921
955
  }
922
956
  case 'local-link': {
923
957
  if (regAnchor.test(localName) && node.hasAttribute('href')) {
924
- const { href, origin, pathname } = new URL(document.URL);
958
+ const { href, origin, pathname } = new URL(this.#document.URL);
925
959
  const attrURL = new URL(node.getAttribute('href'), href);
926
960
  if (attrURL.origin === origin && attrURL.pathname === pathname) {
927
961
  matched.add(node);
@@ -934,17 +968,18 @@ export class Matcher {
934
968
  break;
935
969
  }
936
970
  case 'target': {
937
- const { hash } = new URL(document.URL);
938
- if (node.id && hash === `#${node.id}` && document.contains(node)) {
971
+ const { hash } = new URL(this.#document.URL);
972
+ if (node.id && hash === `#${node.id}` &&
973
+ this.#document.contains(node)) {
939
974
  matched.add(node);
940
975
  }
941
976
  break;
942
977
  }
943
978
  case 'target-within': {
944
- const { hash } = new URL(document.URL);
979
+ const { hash } = new URL(this.#document.URL);
945
980
  if (hash) {
946
981
  const id = hash.replace(/^#/, '');
947
- let current = document.getElementById(id);
982
+ let current = this.#document.getElementById(id);
948
983
  while (current) {
949
984
  if (current === node) {
950
985
  matched.add(node);
@@ -960,19 +995,19 @@ export class Matcher {
960
995
  if (node === this.#node) {
961
996
  matched.add(node);
962
997
  }
963
- } else if (node === document.documentElement) {
998
+ } else if (node === this.#document.documentElement) {
964
999
  matched.add(node);
965
1000
  }
966
1001
  break;
967
1002
  }
968
1003
  case 'focus': {
969
- if (node === document.activeElement) {
1004
+ if (node === this.#document.activeElement) {
970
1005
  matched.add(node);
971
1006
  }
972
1007
  break;
973
1008
  }
974
1009
  case 'focus-within': {
975
- let current = document.activeElement;
1010
+ let current = this.#document.activeElement;
976
1011
  while (current) {
977
1012
  if (current === node) {
978
1013
  matched.add(node);
@@ -1118,7 +1153,7 @@ export class Matcher {
1118
1153
  parent = parent.parentNode;
1119
1154
  }
1120
1155
  if (!parent) {
1121
- parent = document.documentElement;
1156
+ parent = this.#document.documentElement;
1122
1157
  }
1123
1158
  const nodes = [].slice.call(parent.getElementsByTagName('input'));
1124
1159
  let checked;
@@ -1159,7 +1194,8 @@ export class Matcher {
1159
1194
  form = form.parentNode;
1160
1195
  }
1161
1196
  if (form) {
1162
- const iterator = document.createNodeIterator(form, SHOW_ELEMENT);
1197
+ const iterator =
1198
+ this.#document.createNodeIterator(form, SHOW_ELEMENT);
1163
1199
  let nextNode = iterator.nextNode();
1164
1200
  while (nextNode) {
1165
1201
  const nodeName = nextNode.localName;
@@ -1231,10 +1267,9 @@ export class Matcher {
1231
1267
  matched.add(node);
1232
1268
  }
1233
1269
  } else if (localName === 'fieldset') {
1234
- const iterator = document.createNodeIterator(node, SHOW_ELEMENT);
1235
- let refNode = iterator.nextNode();
1270
+ let refNode = this._traverse(node);
1236
1271
  if (refNode === node) {
1237
- refNode = iterator.nextNode();
1272
+ refNode = this.#tree.nextNode();
1238
1273
  }
1239
1274
  let bool;
1240
1275
  while (refNode) {
@@ -1244,7 +1279,7 @@ export class Matcher {
1244
1279
  break;
1245
1280
  }
1246
1281
  }
1247
- refNode = iterator.nextNode();
1282
+ refNode = this.#tree.nextNode();
1248
1283
  }
1249
1284
  if (bool) {
1250
1285
  matched.add(node);
@@ -1258,10 +1293,9 @@ export class Matcher {
1258
1293
  matched.add(node);
1259
1294
  }
1260
1295
  } else if (localName === 'fieldset') {
1261
- const iterator = document.createNodeIterator(node, SHOW_ELEMENT);
1262
- let refNode = iterator.nextNode();
1296
+ let refNode = this._traverse(node);
1263
1297
  if (refNode === node) {
1264
- refNode = iterator.nextNode();
1298
+ refNode = this.#tree.nextNode();
1265
1299
  }
1266
1300
  let bool;
1267
1301
  while (refNode) {
@@ -1271,7 +1305,7 @@ export class Matcher {
1271
1305
  break;
1272
1306
  }
1273
1307
  }
1274
- refNode = iterator.nextNode();
1308
+ refNode = this.#tree.nextNode();
1275
1309
  }
1276
1310
  if (!bool) {
1277
1311
  matched.add(node);
@@ -1347,7 +1381,7 @@ export class Matcher {
1347
1381
  break;
1348
1382
  }
1349
1383
  case 'root': {
1350
- if (node === document.documentElement) {
1384
+ if (node === this.#document.documentElement) {
1351
1385
  matched.add(node);
1352
1386
  }
1353
1387
  break;
@@ -1373,14 +1407,14 @@ export class Matcher {
1373
1407
  }
1374
1408
  case 'first-child': {
1375
1409
  if ((parentNode && node === parentNode.firstElementChild) ||
1376
- (node === root && root.nodeType === ELEMENT_NODE)) {
1410
+ (node === this.#root && this.#root.nodeType === ELEMENT_NODE)) {
1377
1411
  matched.add(node);
1378
1412
  }
1379
1413
  break;
1380
1414
  }
1381
1415
  case 'last-child': {
1382
1416
  if ((parentNode && node === parentNode.lastElementChild) ||
1383
- (node === root && root.nodeType === ELEMENT_NODE)) {
1417
+ (node === this.#root && this.#root.nodeType === ELEMENT_NODE)) {
1384
1418
  matched.add(node);
1385
1419
  }
1386
1420
  break;
@@ -1389,7 +1423,7 @@ export class Matcher {
1389
1423
  if ((parentNode &&
1390
1424
  node === parentNode.firstElementChild &&
1391
1425
  node === parentNode.lastElementChild) ||
1392
- (node === root && root.nodeType === ELEMENT_NODE)) {
1426
+ (node === this.#root && this.#root.nodeType === ELEMENT_NODE)) {
1393
1427
  matched.add(node);
1394
1428
  }
1395
1429
  break;
@@ -1403,7 +1437,8 @@ export class Matcher {
1403
1437
  if (node1) {
1404
1438
  matched.add(node1);
1405
1439
  }
1406
- } else if (node === root && root.nodeType === ELEMENT_NODE) {
1440
+ } else if (node === this.#root &&
1441
+ this.#root.nodeType === ELEMENT_NODE) {
1407
1442
  matched.add(node);
1408
1443
  }
1409
1444
  break;
@@ -1418,7 +1453,8 @@ export class Matcher {
1418
1453
  if (node1) {
1419
1454
  matched.add(node1);
1420
1455
  }
1421
- } else if (node === root && root.nodeType === ELEMENT_NODE) {
1456
+ } else if (node === this.#root &&
1457
+ this.#root.nodeType === ELEMENT_NODE) {
1422
1458
  matched.add(node);
1423
1459
  }
1424
1460
  break;
@@ -1439,7 +1475,8 @@ export class Matcher {
1439
1475
  matched.add(node);
1440
1476
  }
1441
1477
  }
1442
- } else if (node === root && root.nodeType === ELEMENT_NODE) {
1478
+ } else if (node === this.#root &&
1479
+ this.#root.nodeType === ELEMENT_NODE) {
1443
1480
  matched.add(node);
1444
1481
  }
1445
1482
  break;
@@ -1520,9 +1557,8 @@ export class Matcher {
1520
1557
  const { attributes } = node;
1521
1558
  let res;
1522
1559
  if (attributes && attributes.length) {
1523
- const { document } = this.#root;
1524
1560
  let caseInsensitive;
1525
- if (document.contentType === 'text/html') {
1561
+ if (this.#document.contentType === 'text/html') {
1526
1562
  if (typeof astFlags === 'string' && /^s$/i.test(astFlags)) {
1527
1563
  caseInsensitive = false;
1528
1564
  } else {
@@ -1748,11 +1784,10 @@ export class Matcher {
1748
1784
  const astName = unescapeSelector(ast.name);
1749
1785
  const { localName, prefix } = node;
1750
1786
  const { forgive } = opt;
1751
- const { document } = this.#root;
1752
1787
  let {
1753
1788
  prefix: astPrefix, tagName: astNodeName
1754
1789
  } = selectorToNodeProps(astName, node);
1755
- if (document.contentType === 'text/html') {
1790
+ if (this.#document.contentType === 'text/html') {
1756
1791
  astPrefix = astPrefix.toLowerCase();
1757
1792
  astNodeName = astNodeName.toLowerCase();
1758
1793
  }
@@ -1862,7 +1897,6 @@ export class Matcher {
1862
1897
  _matchSelector(ast, node, opt) {
1863
1898
  const { type: astType } = ast;
1864
1899
  const astName = unescapeSelector(ast.name);
1865
- const { shadow } = this.#root;
1866
1900
  let matched = new Set();
1867
1901
  if (node.nodeType === ELEMENT_NODE) {
1868
1902
  switch (astType) {
@@ -1906,7 +1940,7 @@ export class Matcher {
1906
1940
  }
1907
1941
  }
1908
1942
  }
1909
- } else if (shadow && astType === SELECTOR_PSEUDO_CLASS &&
1943
+ } else if (this.#shadow && astType === SELECTOR_PSEUDO_CLASS &&
1910
1944
  node.nodeType === DOCUMENT_FRAGMENT_NODE) {
1911
1945
  if (astName !== 'has' && REG_LOGICAL_PSEUDO.test(astName)) {
1912
1946
  const nodes = this._matchPseudoClassSelector(ast, node, opt);
@@ -1952,18 +1986,17 @@ export class Matcher {
1952
1986
  const { type: leafType } = leaf;
1953
1987
  const leafName = unescapeSelector(leaf.name);
1954
1988
  const matchItems = items.length > 0;
1955
- const { document, root, shadow } = this.#root;
1956
1989
  let nodes = new Set();
1957
1990
  let pending = false;
1958
- if (shadow) {
1991
+ if (this.#shadow) {
1959
1992
  pending = true;
1960
1993
  } else {
1961
1994
  switch (leafType) {
1962
1995
  case SELECTOR_ID: {
1963
- if (root.nodeType === ELEMENT_NODE) {
1996
+ if (this.#root.nodeType === ELEMENT_NODE) {
1964
1997
  pending = true;
1965
1998
  } else {
1966
- const node = root.getElementById(leafName);
1999
+ const node = this.#root.getElementById(leafName);
1967
2000
  if (node && node !== baseNode && baseNode.contains(node)) {
1968
2001
  if (matchItems) {
1969
2002
  const bool = this._matchLeaves(items, node);
@@ -1994,7 +2027,8 @@ export class Matcher {
1994
2027
  break;
1995
2028
  }
1996
2029
  case SELECTOR_TYPE: {
1997
- if (document.contentType === 'text/html' && !/[*|]/.test(leafName)) {
2030
+ if (this.#document.contentType === 'text/html' &&
2031
+ !/[*|]/.test(leafName)) {
1998
2032
  const arr = [].slice.call(baseNode.getElementsByTagName(leafName));
1999
2033
  if (arr.length) {
2000
2034
  if (matchItems) {
@@ -2066,11 +2100,13 @@ export class Matcher {
2066
2100
  break;
2067
2101
  }
2068
2102
  case '>': {
2069
- const childNodes = [].slice.call(node.children);
2103
+ const childNodes = node.childNodes.values();
2070
2104
  for (const refNode of childNodes) {
2071
- const bool = this._matchLeaves(leaves, refNode, { forgive });
2072
- if (bool) {
2073
- matched.add(refNode);
2105
+ if (refNode.nodeType === ELEMENT_NODE) {
2106
+ const bool = this._matchLeaves(leaves, refNode, { forgive });
2107
+ if (bool) {
2108
+ matched.add(refNode);
2109
+ }
2074
2110
  }
2075
2111
  }
2076
2112
  break;
@@ -2081,18 +2117,16 @@ export class Matcher {
2081
2117
  if (nodes.size) {
2082
2118
  matched = nodes;
2083
2119
  } else if (pending) {
2084
- const { document } = this.#root;
2085
- const iterator = document.createNodeIterator(node, SHOW_ELEMENT);
2086
- let refNode = iterator.nextNode();
2120
+ let refNode = this._traverse(node);
2087
2121
  if (refNode === node) {
2088
- refNode = iterator.nextNode();
2122
+ refNode = this.#tree.nextNode();
2089
2123
  }
2090
2124
  while (refNode) {
2091
2125
  const bool = this._matchLeaves(leaves, refNode, { forgive });
2092
2126
  if (bool) {
2093
2127
  matched.add(refNode);
2094
2128
  }
2095
- refNode = iterator.nextNode();
2129
+ refNode = this.#tree.nextNode();
2096
2130
  }
2097
2131
  }
2098
2132
  }
@@ -2155,42 +2189,92 @@ export class Matcher {
2155
2189
  }
2156
2190
 
2157
2191
  /**
2158
- * find nodes
2192
+ * find matched node from tree walker
2193
+ * @param {Array.<object>} leaves - AST leaves
2194
+ * @param {object} [opt] - options
2195
+ * @param {object} [opt.node] - node to start from
2196
+ * @param {string} [opt.targetType] - target type
2197
+ * @param {object} [opt.tree] - tree walker
2198
+ * @returns {?object} - matched node
2199
+ */
2200
+ _findNode(leaves, opt = {}) {
2201
+ let { node, targetType, tree } = opt;
2202
+ if (!tree) {
2203
+ tree = this.#tree;
2204
+ }
2205
+ let refNode = this._traverse(node);
2206
+ if (refNode.nodeType !== ELEMENT_NODE || refNode === node) {
2207
+ refNode = tree.nextNode();
2208
+ }
2209
+ let matchedNode;
2210
+ while (refNode) {
2211
+ let bool;
2212
+ if (this.#node.nodeType === ELEMENT_NODE) {
2213
+ if (refNode === this.#node) {
2214
+ bool = true;
2215
+ } else {
2216
+ bool = this.#node.contains(refNode);
2217
+ }
2218
+ } else {
2219
+ bool = true;
2220
+ }
2221
+ if (bool) {
2222
+ const matched = this._matchLeaves(leaves, refNode);
2223
+ if (matched) {
2224
+ matchedNode = refNode;
2225
+ break;
2226
+ }
2227
+ }
2228
+ if (targetType === TARGET_LINEAL) {
2229
+ refNode = tree.parentNode();
2230
+ } else {
2231
+ refNode = tree.nextNode();
2232
+ }
2233
+ }
2234
+ return matchedNode ?? null;
2235
+ }
2236
+
2237
+ /**
2238
+ * find entry nodes
2159
2239
  * @param {object} twig - twig
2160
2240
  * @param {string} targetType - target type
2161
2241
  * @returns {object} - collection of nodes etc.
2162
2242
  */
2163
- _findNodes(twig, targetType) {
2164
- const { leaves: [leaf, ...items] } = twig;
2243
+ _findEntryNodes(twig, targetType) {
2244
+ const { leaves } = twig;
2245
+ const [leaf, ...filterLeaves] = leaves;
2165
2246
  const { type: leafType } = leaf;
2166
2247
  const leafName = unescapeSelector(leaf.name);
2167
- const { document, root, shadow } = this.#root;
2248
+ const compound = filterLeaves.length > 0;
2168
2249
  let nodes = new Set();
2250
+ let filtered = false;
2169
2251
  let pending = false;
2170
2252
  switch (leafType) {
2171
2253
  case SELECTOR_ID: {
2172
2254
  if (targetType === TARGET_SELF) {
2173
- const bool = this._matchLeaves([leaf], this.#node);
2255
+ const bool = this._matchLeaves(leaves, this.#node);
2174
2256
  if (bool) {
2175
2257
  nodes.add(this.#node);
2258
+ filtered = true;
2176
2259
  }
2177
2260
  } else if (targetType === TARGET_LINEAL) {
2178
2261
  let refNode = this.#node;
2179
2262
  while (refNode) {
2180
- const bool = this._matchLeaves([leaf], refNode);
2263
+ const bool = this._matchLeaves(leaves, refNode);
2181
2264
  if (bool) {
2182
2265
  nodes.add(refNode);
2183
- break;
2266
+ filtered = true;
2184
2267
  }
2185
2268
  refNode = refNode.parentNode;
2186
2269
  }
2187
2270
  } else if (targetType === TARGET_ALL ||
2188
- root.nodeType === ELEMENT_NODE) {
2271
+ this.#root.nodeType === ELEMENT_NODE) {
2189
2272
  pending = true;
2190
2273
  } else {
2191
- const node = root.getElementById(leafName);
2274
+ const node = this.#root.getElementById(leafName);
2192
2275
  if (node) {
2193
2276
  nodes.add(node);
2277
+ filtered = true;
2194
2278
  }
2195
2279
  }
2196
2280
  break;
@@ -2213,21 +2297,35 @@ export class Matcher {
2213
2297
  break;
2214
2298
  }
2215
2299
  }
2216
- } else if (root.nodeType === DOCUMENT_FRAGMENT_NODE) {
2217
- const childNodes = [].slice.call(root.children);
2300
+ } else if (targetType === TARGET_FIRST) {
2301
+ const node = this._findNode(leaves, {
2302
+ node: this.#node,
2303
+ targetType,
2304
+ tree: this.#subtree
2305
+ });
2306
+ if (node) {
2307
+ nodes.add(node);
2308
+ filtered = true;
2309
+ break;
2310
+ }
2311
+ } else if (this.#root.nodeType === DOCUMENT_FRAGMENT_NODE) {
2312
+ const childNodes = this.#root.childNodes.values();
2218
2313
  const arr = [];
2219
2314
  for (const node of childNodes) {
2220
- if (node.classList.contains(leafName)) {
2221
- arr.push(node);
2315
+ if (node.nodeType === ELEMENT_NODE) {
2316
+ if (node.classList.contains(leafName)) {
2317
+ arr.push(node);
2318
+ }
2319
+ const a = [].slice.call(node.getElementsByClassName(leafName));
2320
+ arr.push(...a);
2222
2321
  }
2223
- const a = [].slice.call(node.getElementsByClassName(leafName));
2224
- arr.push(...a);
2225
2322
  }
2226
2323
  if (arr.length) {
2227
2324
  nodes = new Set(arr);
2228
2325
  }
2229
2326
  } else {
2230
- const arr = [].slice.call(root.getElementsByClassName(leafName));
2327
+ const arr =
2328
+ [].slice.call(this.#root.getElementsByClassName(leafName));
2231
2329
  if (this.#node.nodeType === ELEMENT_NODE) {
2232
2330
  for (const node of arr) {
2233
2331
  if (node === this.#node || isInclusive(node, this.#node)) {
@@ -2243,43 +2341,58 @@ export class Matcher {
2243
2341
  case SELECTOR_TYPE: {
2244
2342
  if (targetType === TARGET_SELF) {
2245
2343
  if (this.#node.nodeType === ELEMENT_NODE) {
2246
- const bool = this._matchLeaves([leaf], this.#node);
2344
+ const bool = this._matchLeaves(leaves, this.#node);
2247
2345
  if (bool) {
2248
2346
  nodes.add(this.#node);
2347
+ filtered = true;
2249
2348
  }
2250
2349
  }
2251
2350
  } else if (targetType === TARGET_LINEAL) {
2252
2351
  let refNode = this.#node;
2253
2352
  while (refNode) {
2254
2353
  if (refNode.nodeType === ELEMENT_NODE) {
2255
- const bool = this._matchLeaves([leaf], refNode);
2354
+ const bool = this._matchLeaves(leaves, refNode);
2256
2355
  if (bool) {
2257
2356
  nodes.add(refNode);
2357
+ filtered = true;
2258
2358
  }
2259
2359
  refNode = refNode.parentNode;
2260
2360
  } else {
2261
2361
  break;
2262
2362
  }
2263
2363
  }
2264
- } else if (document.contentType !== 'text/html' ||
2364
+ } else if (targetType === TARGET_FIRST) {
2365
+ const node = this._findNode(leaves, {
2366
+ node: this.#node,
2367
+ targetType,
2368
+ tree: this.#subtree
2369
+ });
2370
+ if (node) {
2371
+ nodes.add(node);
2372
+ filtered = true;
2373
+ break;
2374
+ }
2375
+ } else if (this.#document.contentType !== 'text/html' ||
2265
2376
  /[*|]/.test(leafName)) {
2266
2377
  pending = true;
2267
- } else if (root.nodeType === DOCUMENT_FRAGMENT_NODE) {
2378
+ } else if (this.#root.nodeType === DOCUMENT_FRAGMENT_NODE) {
2268
2379
  const tagName = leafName.toLowerCase();
2269
- const childNodes = [].slice.call(root.children);
2380
+ const childNodes = this.#root.childNodes.values();
2270
2381
  const arr = [];
2271
2382
  for (const node of childNodes) {
2272
- if (node.localName === tagName) {
2273
- arr.push(node);
2383
+ if (node.nodeType === ELEMENT_NODE) {
2384
+ if (node.localName === tagName) {
2385
+ arr.push(node);
2386
+ }
2387
+ const a = [].slice.call(node.getElementsByTagName(leafName));
2388
+ arr.push(...a);
2274
2389
  }
2275
- const a = [].slice.call(node.getElementsByTagName(leafName));
2276
- arr.push(...a);
2277
2390
  }
2278
2391
  if (arr.length) {
2279
2392
  nodes = new Set(arr);
2280
2393
  }
2281
2394
  } else {
2282
- const arr = [].slice.call(root.getElementsByTagName(leafName));
2395
+ const arr = [].slice.call(this.#root.getElementsByTagName(leafName));
2283
2396
  if (this.#node.nodeType === ELEMENT_NODE) {
2284
2397
  for (const node of arr) {
2285
2398
  if (node === this.#node || isInclusive(node, this.#node)) {
@@ -2299,49 +2412,62 @@ export class Matcher {
2299
2412
  }
2300
2413
  default: {
2301
2414
  if (targetType !== TARGET_LINEAL && REG_SHADOW_HOST.test(leafName)) {
2302
- if (shadow && this.#node.nodeType === DOCUMENT_FRAGMENT_NODE) {
2415
+ if (this.#shadow && this.#node.nodeType === DOCUMENT_FRAGMENT_NODE) {
2303
2416
  const node = this._matchShadowHostPseudoClass(leaf, this.#node);
2304
2417
  if (node) {
2305
2418
  nodes.add(node);
2306
2419
  }
2307
2420
  }
2308
2421
  } else if (targetType === TARGET_SELF) {
2309
- const bool = this._matchLeaves([leaf], this.#node);
2422
+ const bool = this._matchLeaves(leaves, this.#node);
2310
2423
  if (bool) {
2311
2424
  nodes.add(this.#node);
2425
+ filtered = true;
2312
2426
  }
2313
2427
  } else if (targetType === TARGET_LINEAL) {
2314
2428
  let refNode = this.#node;
2315
2429
  while (refNode) {
2316
- const bool = this._matchLeaves([leaf], refNode);
2430
+ const bool = this._matchLeaves(leaves, refNode);
2317
2431
  if (bool) {
2318
2432
  nodes.add(refNode);
2433
+ filtered = true;
2319
2434
  }
2320
2435
  refNode = refNode.parentNode;
2321
2436
  }
2437
+ } else if (targetType === TARGET_FIRST) {
2438
+ const node = this._findNode(leaves, {
2439
+ node: this.#node,
2440
+ targetType,
2441
+ tree: this.#subtree
2442
+ });
2443
+ if (node) {
2444
+ nodes.add(node);
2445
+ filtered = true;
2446
+ break;
2447
+ }
2322
2448
  } else {
2323
2449
  pending = true;
2324
2450
  }
2325
2451
  }
2326
2452
  }
2327
- const itemsLen = items.length;
2328
2453
  // check last leaf if node not found, not pending and leaves left
2329
- if (!nodes.size && !pending && itemsLen) {
2330
- const lastLeaf = items[itemsLen - 1];
2454
+ if (!nodes.size && !pending && compound) {
2455
+ const lastLeaf = filterLeaves[filterLeaves.length - 1];
2331
2456
  const { type: lastLeafType } = lastLeaf;
2332
2457
  if (lastLeafType === SELECTOR_PSEUDO_CLASS) {
2333
2458
  let node;
2334
- if (root.nodeType === ELEMENT_NODE) {
2335
- node = root;
2459
+ if (this.#root.nodeType === ELEMENT_NODE) {
2460
+ node = this.#root;
2336
2461
  } else {
2337
- node = root.firstElementChild;
2462
+ node = this.#root.firstElementChild;
2338
2463
  }
2339
2464
  // throws if unknown pseudo-class
2340
2465
  this._matchPseudoClassSelector(lastLeaf, node);
2341
2466
  }
2342
2467
  }
2343
2468
  return {
2344
- compound: itemsLen > 0,
2469
+ compound,
2470
+ filtered,
2345
2471
  nodes,
2346
2472
  pending
2347
2473
  };
@@ -2355,32 +2481,54 @@ export class Matcher {
2355
2481
  */
2356
2482
  _getEntryTwig(branch, targetType) {
2357
2483
  const branchLen = branch.length;
2484
+ const complex = branchLen > 1;
2358
2485
  const firstTwig = branch[0];
2359
2486
  let find;
2360
2487
  let twig;
2361
- if (branchLen > 1) {
2488
+ if (complex) {
2362
2489
  const { leaves: [{ type: firstType }] } = firstTwig;
2363
2490
  const lastTwig = branch[branchLen - 1];
2364
2491
  const { leaves: [{ type: lastType }] } = lastTwig;
2365
- if (lastType === SELECTOR_PSEUDO_ELEMENT || lastType === SELECTOR_ID) {
2492
+ if (lastType === SELECTOR_PSEUDO_ELEMENT ||
2493
+ lastType === SELECTOR_ID) {
2366
2494
  find = FIND_PREV;
2367
2495
  twig = lastTwig;
2368
2496
  } else if (firstType === SELECTOR_PSEUDO_ELEMENT ||
2369
- firstType === SELECTOR_ID) {
2497
+ firstType === SELECTOR_ID ||
2498
+ targetType === TARGET_ALL) {
2370
2499
  find = FIND_NEXT;
2371
2500
  twig = firstTwig;
2372
- } else if (targetType === TARGET_FIRST && branchLen < BIT_04) {
2373
- find = FIND_PREV;
2374
- twig = lastTwig;
2375
2501
  } else {
2376
- find = FIND_NEXT;
2377
- twig = firstTwig;
2502
+ let bool;
2503
+ for (const { combo, leaves: [leaf] } of branch) {
2504
+ const { type: leafType } = leaf;
2505
+ const leafName = unescapeSelector(leaf.name);
2506
+ if (leafType === SELECTOR_PSEUDO_CLASS && leafName === 'dir') {
2507
+ bool = false;
2508
+ break;
2509
+ }
2510
+ if (combo) {
2511
+ const { name: comboName } = combo;
2512
+ if (/^[+~]$/.test(comboName)) {
2513
+ bool = true;
2514
+ break;
2515
+ }
2516
+ }
2517
+ }
2518
+ if (bool) {
2519
+ find = FIND_NEXT;
2520
+ twig = firstTwig;
2521
+ } else {
2522
+ find = FIND_PREV;
2523
+ twig = lastTwig;
2524
+ }
2378
2525
  }
2379
2526
  } else {
2380
2527
  find = FIND_PREV;
2381
2528
  twig = firstTwig;
2382
2529
  }
2383
2530
  return {
2531
+ complex,
2384
2532
  find,
2385
2533
  twig
2386
2534
  };
@@ -2394,11 +2542,21 @@ export class Matcher {
2394
2542
  _collectNodes(targetType) {
2395
2543
  const ast = this.#ast.values();
2396
2544
  if (targetType === TARGET_ALL || targetType === TARGET_FIRST) {
2545
+ if (targetType === TARGET_FIRST) {
2546
+ if (this.#node.nodeType === ELEMENT_NODE) {
2547
+ this.#subtree =
2548
+ this.#document.createTreeWalker(this.#node, SHOW_ELEMENT);
2549
+ } else {
2550
+ this.#subtree = this.#tree;
2551
+ }
2552
+ }
2397
2553
  const pendingItems = new Set();
2398
2554
  let i = 0;
2399
2555
  for (const { branch } of ast) {
2400
2556
  const { find, twig } = this._getEntryTwig(branch, targetType);
2401
- const { compound, nodes, pending } = this._findNodes(twig, targetType);
2557
+ const {
2558
+ compound, filtered, nodes, pending
2559
+ } = this._findEntryNodes(twig, targetType);
2402
2560
  if (nodes.size) {
2403
2561
  this.#nodes[i] = nodes;
2404
2562
  } else if (pending) {
@@ -2409,14 +2567,12 @@ export class Matcher {
2409
2567
  } else {
2410
2568
  this.#ast[i].skip = true;
2411
2569
  }
2412
- this.#ast[i].filtered = !compound;
2570
+ this.#ast[i].filtered = filtered || !compound;
2413
2571
  this.#ast[i].find = find;
2414
2572
  i++;
2415
2573
  }
2416
2574
  if (pendingItems.size) {
2417
- const { document, root } = this.#root;
2418
- const iterator = document.createNodeIterator(root, SHOW_ELEMENT);
2419
- let nextNode = iterator.nextNode();
2575
+ let nextNode = this._traverse();
2420
2576
  while (nextNode) {
2421
2577
  let bool = false;
2422
2578
  if (this.#node.nodeType === ELEMENT_NODE) {
@@ -2435,26 +2591,26 @@ export class Matcher {
2435
2591
  if (matched) {
2436
2592
  const index = pendingItem.get('index');
2437
2593
  this.#nodes[index].add(nextNode);
2438
- if (!this.#ast[index].filtered) {
2439
- this.#ast[index].filtered = true;
2440
- }
2594
+ this.#ast[index].filtered = true;
2441
2595
  }
2442
2596
  }
2443
2597
  }
2444
- nextNode = iterator.nextNode();
2598
+ nextNode = this.#tree.nextNode();
2445
2599
  }
2446
2600
  }
2447
2601
  } else {
2448
2602
  let i = 0;
2449
2603
  for (const { branch } of ast) {
2450
2604
  const twig = branch[branch.length - 1];
2451
- const { compound, nodes } = this._findNodes(twig, targetType);
2605
+ const {
2606
+ compound, filtered, nodes
2607
+ } = this._findEntryNodes(twig, targetType);
2452
2608
  if (nodes.size) {
2453
2609
  this.#nodes[i] = nodes;
2454
2610
  } else {
2455
2611
  this.#ast[i].skip = true;
2456
2612
  }
2457
- this.#ast[i].filtered = !compound;
2613
+ this.#ast[i].filtered = filtered || !compound;
2458
2614
  this.#ast[i].find = FIND_PREV;
2459
2615
  i++;
2460
2616
  }
@@ -2501,13 +2657,13 @@ export class Matcher {
2501
2657
  if (skip) {
2502
2658
  continue;
2503
2659
  } else if (branchLen) {
2504
- const collectedNodes = this.#nodes[i];
2660
+ const entryNodes = this.#nodes[i];
2505
2661
  const lastIndex = branchLen - 1;
2506
2662
  if (lastIndex === 0) {
2507
2663
  const { leaves: [, ...filterLeaves] } = branch[0];
2508
2664
  if ((targetType === TARGET_ALL || targetType === TARGET_FIRST) &&
2509
2665
  this.#node.nodeType === ELEMENT_NODE) {
2510
- for (const node of collectedNodes) {
2666
+ for (const node of entryNodes) {
2511
2667
  const bool = filtered || this._matchLeaves(filterLeaves, node);
2512
2668
  if (bool && node !== this.#node && this.#node.contains(node)) {
2513
2669
  nodes.add(node);
@@ -2519,13 +2675,13 @@ export class Matcher {
2519
2675
  } else if (!filterLeaves.length) {
2520
2676
  if (targetType === TARGET_ALL) {
2521
2677
  const n = [...nodes];
2522
- nodes = new Set([...n, ...collectedNodes]);
2678
+ nodes = new Set([...n, ...entryNodes]);
2523
2679
  } else {
2524
- const [node] = [...collectedNodes];
2680
+ const [node] = [...entryNodes];
2525
2681
  nodes.add(node);
2526
2682
  }
2527
2683
  } else {
2528
- for (const node of collectedNodes) {
2684
+ for (const node of entryNodes) {
2529
2685
  const bool = filtered || this._matchLeaves(filterLeaves, node);
2530
2686
  if (bool) {
2531
2687
  nodes.add(node);
@@ -2536,10 +2692,11 @@ export class Matcher {
2536
2692
  }
2537
2693
  }
2538
2694
  } else if (find === FIND_NEXT) {
2539
- let { combo, leaves: [, ...filterLeaves] } = branch[0];
2540
- for (const node of collectedNodes) {
2695
+ let { combo, leaves: entryLeaves } = branch[0];
2696
+ const [, ...filterLeaves] = entryLeaves;
2697
+ let matched;
2698
+ for (const node of entryNodes) {
2541
2699
  const bool = filtered || this._matchLeaves(filterLeaves, node);
2542
- let matched;
2543
2700
  if (bool) {
2544
2701
  let nextNodes = new Set([node]);
2545
2702
  for (let j = 1; j < branchLen; j++) {
@@ -2583,11 +2740,61 @@ export class Matcher {
2583
2740
  break;
2584
2741
  }
2585
2742
  }
2743
+ if (!matched && targetType === TARGET_FIRST) {
2744
+ const [entryNode] = [...entryNodes];
2745
+ let refNode = this._findNode(entryLeaves, {
2746
+ targetType,
2747
+ node: entryNode,
2748
+ tree: this.#subtree
2749
+ });
2750
+ while (refNode) {
2751
+ let nextNodes = new Set([refNode]);
2752
+ for (let j = 1; j < branchLen; j++) {
2753
+ const { combo: nextCombo, leaves } = branch[j];
2754
+ const arr = [];
2755
+ for (const nextNode of nextNodes) {
2756
+ const twig = {
2757
+ combo,
2758
+ leaves
2759
+ };
2760
+ const m = this._matchCombinator(twig, nextNode, { find });
2761
+ if (m.size) {
2762
+ arr.push(...m);
2763
+ }
2764
+ }
2765
+ if (arr.length) {
2766
+ if (j === lastIndex) {
2767
+ const [node] = this._sortNodes(arr);
2768
+ nodes.add(node);
2769
+ matched = true;
2770
+ break;
2771
+ } else {
2772
+ matched = false;
2773
+ combo = nextCombo;
2774
+ nextNodes = new Set(arr);
2775
+ }
2776
+ } else {
2777
+ matched = false;
2778
+ break;
2779
+ }
2780
+ }
2781
+ if (matched) {
2782
+ break;
2783
+ }
2784
+ refNode = this._findNode(entryLeaves, {
2785
+ targetType,
2786
+ node: refNode,
2787
+ tree: this.#subtree
2788
+ });
2789
+ nextNodes = new Set([refNode]);
2790
+ }
2791
+ }
2586
2792
  } else {
2587
- const { leaves: [, ...filterLeaves] } = branch[lastIndex];
2588
- for (const node of collectedNodes) {
2793
+ const { leaves: entryLeaves } = branch[lastIndex];
2794
+ const [, ...filterLeaves] = entryLeaves;
2795
+ let matched;
2796
+ for (const node of entryNodes) {
2589
2797
  const bool = filtered || this._matchLeaves(filterLeaves, node);
2590
- let matched;
2591
2798
  if (bool) {
2592
2799
  let nextNodes = new Set([node]);
2593
2800
  for (let j = lastIndex - 1; j >= 0; j--) {
@@ -2613,13 +2820,54 @@ export class Matcher {
2613
2820
  break;
2614
2821
  }
2615
2822
  }
2616
- } else {
2617
- matched = false;
2618
2823
  }
2619
2824
  if (matched && targetType !== TARGET_ALL) {
2620
2825
  break;
2621
2826
  }
2622
2827
  }
2828
+ if (!matched && targetType === TARGET_FIRST) {
2829
+ const [entryNode] = [...entryNodes];
2830
+ let refNode = this._findNode(entryLeaves, {
2831
+ targetType,
2832
+ node: entryNode,
2833
+ tree: this.#subtree
2834
+ });
2835
+ while (refNode) {
2836
+ let nextNodes = new Set([refNode]);
2837
+ for (let j = lastIndex - 1; j >= 0; j--) {
2838
+ const twig = branch[j];
2839
+ const arr = [];
2840
+ for (const nextNode of nextNodes) {
2841
+ const m = this._matchCombinator(twig, nextNode, { find });
2842
+ if (m.size) {
2843
+ arr.push(...m);
2844
+ }
2845
+ }
2846
+ if (arr.length) {
2847
+ if (j === 0) {
2848
+ nodes.add(refNode);
2849
+ matched = true;
2850
+ break;
2851
+ } else {
2852
+ matched = false;
2853
+ nextNodes = new Set(arr);
2854
+ }
2855
+ } else {
2856
+ matched = false;
2857
+ break;
2858
+ }
2859
+ }
2860
+ if (matched) {
2861
+ break;
2862
+ }
2863
+ refNode = this._findNode(entryLeaves, {
2864
+ targetType,
2865
+ node: refNode,
2866
+ tree: this.#subtree
2867
+ });
2868
+ nextNodes = new Set([refNode]);
2869
+ }
2870
+ }
2623
2871
  }
2624
2872
  }
2625
2873
  }