@asamuzakjp/dom-selector 8.1.1 → 8.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -58,24 +58,26 @@
58
58
  "yargs": "^18.0.0"
59
59
  },
60
60
  "eslint": {
61
- "brace-expansion": "^1.1.13"
61
+ "brace-expansion": "^1.1.13",
62
+ "js-yaml": "^4.1.2"
62
63
  },
63
64
  "jsdom": "$jsdom",
64
65
  "mocha": {
65
- "diff": "^8.0.3"
66
+ "diff": "^8.0.3",
67
+ "js-yaml": "^4.1.2"
66
68
  },
67
69
  "serialize-javascript": "^7.0.4"
68
70
  },
69
71
  "scripts": {
70
- "bench": "node benchmark/bench.js",
71
- "bench:cache": "node benchmark/bench-cache.js",
72
- "bench:nwsapi": "node benchmark/bench-nwsapi.js",
73
- "bench:ps-form": "node benchmark/bench-default.js && node benchmark/bench-indeterminate.js && node benchmark/bench-valid-invalid.js",
74
- "bench:ps-has": "node benchmark/bench-has.js",
75
- "bench:ps-nth": "node benchmark/bench-nth-child.js && node benchmark/bench-nth-of-type.js",
76
- "bench:ps-traverse": "node benchmark/bench-dir.js && node benchmark/bench-lang.js",
77
- "bench:ts": "node benchmark/bench-testing-library.js",
78
- "bench:universal": "node benchmark/bench-universal.js",
72
+ "bench": "node --expose-gc benchmark/bench.js",
73
+ "bench:cache": "node --expose-gc benchmark/bench-cache.js",
74
+ "bench:nwsapi": "node --expose-gc benchmark/bench-nwsapi.js",
75
+ "bench:ps-form": "node --expose-gc benchmark/bench-default.js && node --expose-gc benchmark/bench-indeterminate.js && node --expose-gc benchmark/bench-valid-invalid.js",
76
+ "bench:ps-has": "node --expose-gc benchmark/bench-has.js",
77
+ "bench:ps-nth": "node --expose-gc benchmark/bench-nth-child.js && node --expose-gc benchmark/bench-nth-of-type.js",
78
+ "bench:ps-traverse": "node --expose-gc benchmark/bench-dir.js && node --expose-gc benchmark/bench-lang.js",
79
+ "bench:ts": "node --expose-gc benchmark/bench-testing-library.js",
80
+ "bench:universal": "node --expose-gc benchmark/bench-universal.js",
79
81
  "build": "npm run tsc && npm run lint && npm test",
80
82
  "lint": "eslint --fix .",
81
83
  "test": "c8 --reporter=text mocha --exit test/**/*.test.js",
@@ -86,5 +88,5 @@
86
88
  "engines": {
87
89
  "node": "^22.13.0 || >=24.0.0"
88
90
  },
89
- "version": "8.1.1"
91
+ "version": "8.1.3"
90
92
  }
package/src/index.js CHANGED
@@ -101,6 +101,15 @@ export class DOMSelector {
101
101
  doc.contentType === 'text/html' &&
102
102
  doc.documentElement;
103
103
 
104
+ /**
105
+ * Wraps the node for IDL internal implementation if idlUtils is present.
106
+ * @private
107
+ * @param {Document|DocumentFragment|Element} node - The raw node.
108
+ * @returns {object} The wrapped or raw node.
109
+ */
110
+ #wrapNode = node =>
111
+ this.#idlUtils ? this.#idlUtils.wrapperForImpl(node) : node;
112
+
104
113
  /**
105
114
  * Clears the internal caches.
106
115
  * @param {boolean} [clearAll] - Whether to clear all caches. If false,
@@ -205,8 +214,7 @@ export class DOMSelector {
205
214
  }
206
215
  if (filterMatches) {
207
216
  try {
208
- const n = this.#idlUtils ? this.#idlUtils.wrapperForImpl(node) : node;
209
- const match = this.#nwsapi.match(selector, n);
217
+ const match = this.#nwsapi.match(selector, this.#wrapNode(node));
210
218
  let ast = null;
211
219
  if (match) {
212
220
  const astCacheKey = `check_ast_${selector}`;
@@ -226,16 +234,15 @@ export class DOMSelector {
226
234
  }
227
235
  }
228
236
  }
229
- if (this.#idlUtils) {
230
- node = this.#idlUtils.wrapperForImpl(node);
231
- }
232
237
  const options = {
233
238
  ...opt,
234
239
  check: true,
235
240
  noexcept: true,
236
241
  warn: false
237
242
  };
238
- return this.#finder.setup(selector, node, options).find(TARGET_SELF);
243
+ return this.#finder
244
+ .setup(selector, this.#wrapNode(node), options)
245
+ .find(TARGET_SELF);
239
246
  };
240
247
 
241
248
  /**
@@ -263,18 +270,16 @@ export class DOMSelector {
263
270
  }
264
271
  if (filterMatches) {
265
272
  try {
266
- const n = this.#idlUtils ? this.#idlUtils.wrapperForImpl(node) : node;
267
- return this.#nwsapi.match(selector, n);
273
+ return this.#nwsapi.match(selector, this.#wrapNode(node));
268
274
  } catch (e) {
269
275
  // fall through
270
276
  }
271
277
  }
272
278
  }
273
279
  try {
274
- if (this.#idlUtils) {
275
- node = this.#idlUtils.wrapperForImpl(node);
276
- }
277
- const nodes = this.#finder.setup(selector, node, opt).find(TARGET_SELF);
280
+ const nodes = this.#finder
281
+ .setup(selector, this.#wrapNode(node), opt)
282
+ .find(TARGET_SELF);
278
283
  return nodes.size > 0;
279
284
  } catch (e) {
280
285
  this.#finder.onError(e, opt);
@@ -307,25 +312,20 @@ export class DOMSelector {
307
312
  }
308
313
  if (filterMatches) {
309
314
  try {
310
- const n = this.#idlUtils ? this.#idlUtils.wrapperForImpl(node) : node;
311
- return this.#nwsapi.closest(selector, n);
315
+ return this.#nwsapi.closest(selector, this.#wrapNode(node));
312
316
  } catch (e) {
313
317
  // fall through
314
318
  }
315
319
  }
316
320
  }
317
- let res;
318
321
  try {
319
- if (this.#idlUtils) {
320
- node = this.#idlUtils.wrapperForImpl(node);
321
- }
322
+ node = this.#wrapNode(node);
322
323
  const nodes = this.#finder.setup(selector, node, opt).find(TARGET_LINEAL);
323
324
  if (nodes.size) {
324
325
  let refNode = node;
325
326
  while (refNode) {
326
327
  if (nodes.has(refNode)) {
327
- res = refNode;
328
- break;
328
+ return refNode;
329
329
  }
330
330
  refNode = refNode.parentNode;
331
331
  }
@@ -333,7 +333,7 @@ export class DOMSelector {
333
333
  } catch (e) {
334
334
  this.#finder.onError(e, opt);
335
335
  }
336
- return res ?? null;
336
+ return null;
337
337
  };
338
338
 
339
339
  /**
@@ -374,19 +374,16 @@ export class DOMSelector {
374
374
  }
375
375
  }
376
376
  */
377
- let res;
378
377
  try {
379
- if (this.#idlUtils) {
380
- node = this.#idlUtils.wrapperForImpl(node);
381
- }
378
+ node = this.#wrapNode(node);
382
379
  const nodes = this.#finder.setup(selector, node, opt).find(TARGET_FIRST);
383
380
  if (nodes.size) {
384
- res = nodes.values().next().value;
381
+ return nodes.values().next().value;
385
382
  }
386
383
  } catch (e) {
387
384
  this.#finder.onError(e, opt);
388
385
  }
389
- return res ?? null;
386
+ return null;
390
387
  };
391
388
 
392
389
  /**
@@ -407,37 +404,43 @@ export class DOMSelector {
407
404
  if (document && REG_UNIVERSAL.test(selector)) {
408
405
  return collectAllDescendants(node, document);
409
406
  }
410
- if (
411
- (node === this.#document || REG_TEST_LIB.test(selector)) &&
412
- this.#canUseNwsapi(document)
413
- ) {
414
- const cacheKey = `querySelectorAll_${selector}`;
415
- let filterMatches = this.#cache.get(cacheKey);
416
- if (filterMatches === undefined) {
417
- filterMatches = filterSelector(selector, TARGET_ALL);
418
- this.#cache.set(cacheKey, filterMatches);
407
+ if (this.#canUseNwsapi(document)) {
408
+ let routeToNwsapi = node === this.#document;
409
+ if (!routeToNwsapi) {
410
+ const testCacheKey = `testlib_${selector}`;
411
+ let isTestLib = this.#cache.get(testCacheKey);
412
+ if (isTestLib === undefined) {
413
+ isTestLib = REG_TEST_LIB.test(selector);
414
+ this.#cache.set(testCacheKey, isTestLib);
415
+ }
416
+ routeToNwsapi = isTestLib;
419
417
  }
420
- if (filterMatches) {
421
- try {
422
- const n = this.#idlUtils ? this.#idlUtils.wrapperForImpl(node) : node;
423
- return this.#nwsapi.select(selector, n);
424
- } catch (e) {
425
- // fall through
418
+ if (routeToNwsapi) {
419
+ const cacheKey = `querySelectorAll_${selector}`;
420
+ let filterMatches = this.#cache.get(cacheKey);
421
+ if (filterMatches === undefined) {
422
+ filterMatches = filterSelector(selector, TARGET_ALL);
423
+ this.#cache.set(cacheKey, filterMatches);
424
+ }
425
+ if (filterMatches) {
426
+ try {
427
+ return this.#nwsapi.select(selector, this.#wrapNode(node));
428
+ } catch (e) {
429
+ // fall through
430
+ }
426
431
  }
427
432
  }
428
433
  }
429
- let res;
430
434
  try {
431
- if (this.#idlUtils) {
432
- node = this.#idlUtils.wrapperForImpl(node);
433
- }
434
- const nodes = this.#finder.setup(selector, node, opt).find(TARGET_ALL);
435
+ const nodes = this.#finder
436
+ .setup(selector, this.#wrapNode(node), opt)
437
+ .find(TARGET_ALL);
435
438
  if (nodes.size) {
436
- res = [...nodes];
439
+ return [...nodes];
437
440
  }
438
441
  } catch (e) {
439
442
  this.#finder.onError(e, opt);
440
443
  }
441
- return res ?? [];
444
+ return [];
442
445
  };
443
446
  }
@@ -81,7 +81,6 @@ export const TAG_TYPE = `\\*|${TAG_TYPE_WO_UNIVERSAL}`;
81
81
  export const TAG_TYPE_I = '\\*|[A-Z][\\w-]*';
82
82
  export const COMPOUND = `(?:${TAG_TYPE}|(?:${TAG_TYPE})?(?:${SUB_TYPE})+)`;
83
83
  export const COMPOUND_L = `(?:${TAG_TYPE}|(?:${TAG_TYPE})?(?:${SUB_TYPE}|${LOGIC_IS})+)`;
84
- export const COMPOUND_L_I = `(?:(?:${TAG_TYPE_I})?(?:${SUB_TYPE}|${LOGIC_IS})+)`;
85
84
  export const COMPOUND_I = `(?:${TAG_TYPE_I}|(?:${TAG_TYPE_I})?(?:${SUB_TYPE})+)`;
86
85
  export const COMPOUND_WO_PSEUDO = `(?:${TAG_TYPE}|(?:${TAG_TYPE})?(?:${SUB_TYPE_WO_PSEUDO})+)`;
87
86
  export const COMPLEX = `${COMPOUND}(?:${COMBO}${COMPOUND})*`;
package/src/js/finder.js CHANGED
@@ -113,7 +113,6 @@ export class Finder {
113
113
  #ast;
114
114
  #astCache = new WeakMap();
115
115
  #check;
116
- #combinatorCache;
117
116
  #descendant;
118
117
  #document;
119
118
  #documentCache = new WeakMap();
@@ -246,7 +245,6 @@ export class Finder {
246
245
  */
247
246
  clearResults = (all = false) => {
248
247
  this.#anbCache = null;
249
- this.#combinatorCache = null;
250
248
  this.#focusWithinCache = null;
251
249
  this.#invalidateResults = null;
252
250
  this.#nthChildCache = null;
@@ -2223,36 +2221,12 @@ export class Finder {
2223
2221
  break;
2224
2222
  }
2225
2223
  case '~': {
2226
- const parentNode = node.parentNode;
2227
- if (!parentNode) {
2228
- break;
2229
- }
2230
- if (!this.#combinatorCache) {
2231
- this.#combinatorCache = new WeakMap();
2232
- }
2233
- let cacheMap = this.#combinatorCache.get(parentNode);
2234
- if (!cacheMap) {
2235
- cacheMap = new Map();
2236
- this.#combinatorCache.set(parentNode, cacheMap);
2237
- }
2238
- let matchedSet = cacheMap.get(leaves);
2239
- if (!matchedSet) {
2240
- matchedSet = new Set();
2241
- let child = parentNode.firstElementChild;
2242
- while (child) {
2243
- if (this._matchLeaves(leaves, child, opt)) {
2244
- matchedSet.add(child);
2245
- }
2246
- child = child.nextElementSibling;
2247
- }
2248
- cacheMap.set(leaves, matchedSet);
2249
- }
2250
2224
  let refNode =
2251
2225
  dir === DIR_NEXT
2252
2226
  ? node.nextElementSibling
2253
2227
  : node.previousElementSibling;
2254
2228
  while (refNode) {
2255
- if (matchedSet.has(refNode)) {
2229
+ if (this._matchLeaves(leaves, refNode, opt)) {
2256
2230
  matched.push(refNode);
2257
2231
  }
2258
2232
  refNode =
@@ -11,8 +11,6 @@ import {
11
11
  COMBINATOR,
12
12
  COMBO,
13
13
  COMPOUND_I,
14
- COMPOUND_L_I,
15
- DESCEND,
16
14
  HAS_COMPOUND,
17
15
  KEYS_LOGICAL,
18
16
  KEYS_PS_CLASS_SUPPORTED,
@@ -23,6 +21,7 @@ import {
23
21
  PS_CLASS_SELECTOR,
24
22
  PS_ELEMENT_SELECTOR,
25
23
  SELECTOR,
24
+ SUB_TYPE,
26
25
  SYNTAX_ERR,
27
26
  TARGET_ALL
28
27
  } from './constant.js';
@@ -30,9 +29,11 @@ import {
30
29
  /* regexp */
31
30
  const REG_EXCLUDE_BASIC =
32
31
  /[|\\]|::|[^\u0021-\u007F\s]|\[\s*[\w$*=^|~-]+(?:(?:"[\w$*=^|~\s'-]+"|'[\w$*=^|~\s"-]+')?(?:\s+[\w$*=^|~-]+)+|"[^"\]]{1,255}|'[^'\]]{1,255})\s*\]|:(?:is|where)\(\s*\)/;
32
+ const REG_EXCLUDE_QSA = new RegExp(
33
+ `(?:^(?:[A-Z]|\\.)[\\w-]*$|^(?:${SUB_TYPE}|:${N_TH})+$|${COMPOUND_I}${COMBO}${COMPOUND_I})`,
34
+ 'i'
35
+ );
33
36
  const REG_COMPLEX = new RegExp(`${COMPOUND_I}${COMBO}${COMPOUND_I}`, 'i');
34
- const REG_COMPOUND = new RegExp(`^${COMPOUND_L_I}$`, 'i');
35
- const REG_DESCEND = new RegExp(`${COMPOUND_I}${DESCEND}${COMPOUND_I}`, 'i');
36
37
  const REG_LOGIC_COMPLEX = new RegExp(
37
38
  `:(?!${PSEUDO_CLASS}|${N_TH}|${LOGIC_COMPLEX})`
38
39
  );
@@ -264,7 +265,6 @@ export const extractSubjectsRegExp = (selector, caseSensitive) => {
264
265
  * @returns {boolean} - True if the selector is valid for nwsapi.
265
266
  */
266
267
  export const filterSelector = (selector, target) => {
267
- const isQuerySelectorAll = target === TARGET_ALL;
268
268
  // Basic validation and fast-fail for null/undefined/non-string values.
269
269
  if (
270
270
  !selector ||
@@ -277,6 +277,10 @@ export const filterSelector = (selector, target) => {
277
277
  if (REG_INVALID_SYNTAX.test(selector)) {
278
278
  return false;
279
279
  }
280
+ // Target-specific early exits.
281
+ if (target === TARGET_ALL && REG_EXCLUDE_QSA.test(selector)) {
282
+ return false;
283
+ }
280
284
  // Exclude various complex or unsupported selectors early.
281
285
  // i.e. non-ASCII, escaped selectors, namespaced selectors, pseudo-elements.
282
286
  if (selector.includes('/') || REG_EXCLUDE_BASIC.test(selector)) {
@@ -289,18 +293,11 @@ export const filterSelector = (selector, target) => {
289
293
  return false;
290
294
  }
291
295
  }
292
- // Target-specific early exits.
293
- if (target === TARGET_ALL && !REG_COMPOUND.test(selector)) {
294
- return false;
295
- }
296
296
  // Logic for pseudo-classes.
297
297
  if (selector.includes(':')) {
298
- // Exclude descendant combinators in logical selectors for querySelectorAll.
299
- if (isQuerySelectorAll && REG_DESCEND.test(selector)) {
300
- return false;
301
- }
302
298
  // Determine if the selector has complex logical structures.
303
- const isComplex = isQuerySelectorAll ? false : REG_COMPLEX.test(selector);
299
+ const isComplex =
300
+ target === TARGET_ALL ? false : REG_COMPLEX.test(selector);
304
301
  // Handle :has() specifically.
305
302
  if (selector.includes(':has(')) {
306
303
  if (!isComplex || REG_LOGIC_HAS_COMPOUND.test(selector)) {
@@ -62,7 +62,6 @@ export const TAG_TYPE: "\\*|[A-Za-z][\\w-]*";
62
62
  export const TAG_TYPE_I: "\\*|[A-Z][\\w-]*";
63
63
  export const COMPOUND: "(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)";
64
64
  export const COMPOUND_L: "(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+|:is\\(\\s*[^)]+\\s*\\))+)";
65
- export const COMPOUND_L_I: "(?:(?:\\*|[A-Z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+|:is\\(\\s*[^)]+\\s*\\))+)";
66
65
  export const COMPOUND_I: "(?:\\*|[A-Z][\\w-]*|(?:\\*|[A-Z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)";
67
66
  export const COMPOUND_WO_PSEUDO: "(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.][\\w-]+)+)";
68
67
  export const COMPLEX: "(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+)(?:\\s?[\\s>~+]\\s?(?:\\*|[A-Za-z][\\w-]*|(?:\\*|[A-Za-z][\\w-]*)?(?:\\[[^|\\]]+\\]|[#.:][\\w-]+)+))*";