@asamuzakjp/dom-selector 2.0.2-a.1 → 2.0.2-a.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
@@ -59,5 +59,5 @@
59
59
  "tsc": "npx tsc",
60
60
  "update-wpt": "git submodule update --init --recursive --remote"
61
61
  },
62
- "version": "2.0.2-a.1"
62
+ "version": "2.0.2-a.3"
63
63
  }
package/src/index.js CHANGED
@@ -8,8 +8,11 @@
8
8
  /* import */
9
9
  import { Matcher } from './js/matcher.js';
10
10
 
11
- /* instance */
12
- let matcher = new Matcher();
11
+ /* export for test */
12
+ export { Matcher };
13
+
14
+ /* instance, export for test */
15
+ export let matcher = new Matcher();
13
16
 
14
17
  /**
15
18
  * matches
@@ -27,7 +30,9 @@ export const matches = (selector, node, opt) => {
27
30
  }
28
31
  res = matcher.matches(node, selector, opt);
29
32
  } catch (e) {
30
- matcher = null;
33
+ if (e instanceof Error) {
34
+ matcher = null;
35
+ }
31
36
  throw e;
32
37
  }
33
38
  return res;
@@ -49,7 +54,9 @@ export const closest = (selector, node, opt) => {
49
54
  }
50
55
  res = matcher.closest(node, selector, opt);
51
56
  } catch (e) {
52
- matcher = null;
57
+ if (e instanceof globalThis[e.name]) {
58
+ matcher = null;
59
+ }
53
60
  throw e;
54
61
  }
55
62
  return res;
@@ -71,7 +78,9 @@ export const querySelector = (selector, node, opt) => {
71
78
  }
72
79
  res = matcher.querySelector(node, selector, opt);
73
80
  } catch (e) {
74
- matcher = null;
81
+ if (e instanceof globalThis[e.name]) {
82
+ matcher = null;
83
+ }
75
84
  throw e;
76
85
  }
77
86
  return res;
@@ -94,7 +103,9 @@ export const querySelectorAll = (selector, node, opt) => {
94
103
  }
95
104
  res = matcher.querySelectorAll(node, selector, opt);
96
105
  } catch (e) {
97
- matcher = null;
106
+ if (e instanceof globalThis[e.name]) {
107
+ matcher = null;
108
+ }
98
109
  throw e;
99
110
  }
100
111
  return res;
package/src/js/matcher.js CHANGED
@@ -107,7 +107,7 @@ export class Matcher {
107
107
  } else if (this.#window) {
108
108
  throw new this.#window.DOMException(e.message, e.name);
109
109
  } else {
110
- throw new DOMException(e.message, e.name);
110
+ throw e;
111
111
  }
112
112
  } else {
113
113
  throw e;
@@ -115,9 +115,9 @@ export class Matcher {
115
115
  }
116
116
 
117
117
  /**
118
- * set up #window, #document, #root, #walker
118
+ * set up #window, #document, #root
119
119
  * @param {object} node - Document, DocumentFragment, Element node
120
- * @returns {Array.<object>} - array of #window, #document, #root, #walker
120
+ * @returns {Array.<object>} - array of #window, #document, #root
121
121
  */
122
122
  _setup(node) {
123
123
  let document;
@@ -146,7 +146,11 @@ export class Matcher {
146
146
  break;
147
147
  }
148
148
  }
149
- document = parent.ownerDocument;
149
+ if (parent.nodeType === DOCUMENT_NODE) {
150
+ document = parent;
151
+ } else {
152
+ document = parent.ownerDocument;
153
+ }
150
154
  root = parent;
151
155
  }
152
156
  break;
@@ -163,13 +167,11 @@ export class Matcher {
163
167
  throw new TypeError(msg);
164
168
  }
165
169
  }
166
- const walker = document.createTreeWalker(root, WALKER_FILTER);
167
170
  const window = document.defaultView;
168
171
  return [
169
172
  window,
170
173
  document,
171
- root,
172
- walker
174
+ root
173
175
  ];
174
176
  }
175
177
 
@@ -206,56 +208,95 @@ export class Matcher {
206
208
  * @returns {Array.<Array.<object|undefined>>} - array of #ast and #nodes
207
209
  */
208
210
  _correspond(selector) {
209
- let cssAst;
210
- try {
211
- cssAst = parseSelector(selector);
212
- } catch (e) {
213
- this._onError(e);
214
- }
215
- const branches = walkAST(cssAst);
216
- const ast = [];
217
211
  const nodes = [];
218
- let i = 0;
219
- for (const [...items] of branches) {
220
- const branch = [];
221
- let item = items.shift();
222
- if (item && item.type !== COMBINATOR) {
223
- const leaves = new Set();
224
- while (item) {
225
- if (item.type === COMBINATOR) {
226
- const [nextItem] = items;
227
- if (nextItem.type === COMBINATOR) {
228
- const msg = `Invalid combinator ${item.name}${nextItem.name}`;
229
- throw new this.#window.DOMException(msg, SYNTAX_ERR);
230
- }
231
- branch.push({
232
- combo: item,
233
- leaves: this._sortLeaves(leaves)
234
- });
235
- leaves.clear();
236
- } else if (item) {
237
- leaves.add(item);
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();
238
236
  }
239
- if (items.length) {
240
- item = items.shift();
241
- } else {
242
- branch.push({
243
- combo: null,
244
- leaves: this._sortLeaves(leaves)
245
- });
246
- leaves.clear();
247
- break;
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);
261
+ }
262
+ throw new DOMException(msg, SYNTAX_ERR);
263
+ }
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
+ }
248
282
  }
249
283
  }
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);
250
299
  }
251
- ast.push({
252
- branch,
253
- dir: null,
254
- filtered: false,
255
- find: false
256
- });
257
- nodes[i] = new Set();
258
- i++;
259
300
  }
260
301
  return [
261
302
  ast,
@@ -2291,26 +2332,22 @@ export class Matcher {
2291
2332
  }
2292
2333
 
2293
2334
  /**
2294
- * find matched node from tree walker
2335
+ * find matched node from finder
2295
2336
  * @param {Array.<object>} leaves - AST leaves
2296
2337
  * @param {object} [opt] - options
2297
2338
  * @param {object} [opt.node] - node to start from
2298
- * @param {object} [opt.tree] - tree walker
2299
2339
  * @returns {?object} - matched node
2300
2340
  */
2301
2341
  _findNode(leaves, opt = {}) {
2302
- let { node, walker } = opt;
2303
- if (!walker) {
2304
- walker = this.#tree;
2305
- }
2342
+ const { node } = opt;
2306
2343
  let matchedNode;
2307
- let refNode = this._traverse(node, walker);
2344
+ let refNode = this._traverse(node, this.#finder);
2308
2345
  if (refNode) {
2309
2346
  if (refNode.nodeType !== ELEMENT_NODE) {
2310
- refNode = walker.nextNode();
2347
+ refNode = this.#finder.nextNode();
2311
2348
  } else if (refNode === node) {
2312
2349
  if (refNode !== this.#root) {
2313
- refNode = walker.nextNode();
2350
+ refNode = this.#finder.nextNode();
2314
2351
  }
2315
2352
  }
2316
2353
  while (refNode) {
@@ -2331,7 +2368,7 @@ export class Matcher {
2331
2368
  break;
2332
2369
  }
2333
2370
  }
2334
- refNode = walker.nextNode();
2371
+ refNode = this.#finder.nextNode();
2335
2372
  }
2336
2373
  }
2337
2374
  return matchedNode ?? null;
@@ -2370,15 +2407,14 @@ export class Matcher {
2370
2407
  }
2371
2408
  refNode = refNode.parentNode;
2372
2409
  }
2373
- } else if (targetType === TARGET_ALL ||
2374
- this.#root.nodeType === ELEMENT_NODE) {
2375
- pending = true;
2376
- } else {
2410
+ } else if (targetType === TARGET_FIRST &&
2411
+ this.#root.nodeType !== ELEMENT_NODE) {
2377
2412
  const node = this.#root.getElementById(leafName);
2378
2413
  if (node) {
2379
2414
  nodes.add(node);
2380
- filtered = true;
2381
2415
  }
2416
+ } else {
2417
+ pending = true;
2382
2418
  }
2383
2419
  break;
2384
2420
  }
@@ -2402,17 +2438,13 @@ export class Matcher {
2402
2438
  }
2403
2439
  } else if (targetType === TARGET_FIRST) {
2404
2440
  const node = this._findNode(leaves, {
2405
- node: this.#node,
2406
- walker: this.#finder
2441
+ node: this.#node
2407
2442
  });
2408
2443
  if (node) {
2409
2444
  nodes.add(node);
2410
2445
  filtered = true;
2411
2446
  }
2412
- } else if (this.#root.nodeType === DOCUMENT_FRAGMENT_NODE ||
2413
- this.#root.nodeType === ELEMENT_NODE) {
2414
- pending = true;
2415
- } else {
2447
+ } else if (this.#root.nodeType === DOCUMENT_NODE) {
2416
2448
  const arr =
2417
2449
  [].slice.call(this.#root.getElementsByClassName(leafName));
2418
2450
  if (this.#node.nodeType === ELEMENT_NODE) {
@@ -2424,6 +2456,8 @@ export class Matcher {
2424
2456
  } else if (arr.length) {
2425
2457
  nodes = new Set(arr);
2426
2458
  }
2459
+ } else {
2460
+ pending = true;
2427
2461
  }
2428
2462
  break;
2429
2463
  }
@@ -2452,19 +2486,15 @@ export class Matcher {
2452
2486
  }
2453
2487
  } else if (targetType === TARGET_FIRST) {
2454
2488
  const node = this._findNode(leaves, {
2455
- node: this.#node,
2456
- walker: this.#finder
2489
+ node: this.#node
2457
2490
  });
2458
2491
  if (node) {
2459
2492
  nodes.add(node);
2460
2493
  filtered = true;
2461
2494
  }
2462
- } else if (this.#document.contentType !== 'text/html' ||
2463
- /[*|]/.test(leafName) ||
2464
- this.#root.nodeType === DOCUMENT_FRAGMENT_NODE ||
2465
- this.#root.nodeType === ELEMENT_NODE) {
2466
- pending = true;
2467
- } else {
2495
+ } else if (this.#document.contentType === 'text/html' &&
2496
+ this.#root.nodeType === DOCUMENT_NODE &&
2497
+ !/[*|]/.test(leafName)) {
2468
2498
  const arr = [].slice.call(this.#root.getElementsByTagName(leafName));
2469
2499
  if (this.#node.nodeType === ELEMENT_NODE) {
2470
2500
  for (const node of arr) {
@@ -2475,6 +2505,8 @@ export class Matcher {
2475
2505
  } else if (arr.length) {
2476
2506
  nodes = new Set(arr);
2477
2507
  }
2508
+ } else {
2509
+ pending = true;
2478
2510
  }
2479
2511
  break;
2480
2512
  }
@@ -2509,8 +2541,7 @@ export class Matcher {
2509
2541
  }
2510
2542
  } else if (targetType === TARGET_FIRST) {
2511
2543
  const node = this._findNode(leaves, {
2512
- node: this.#node,
2513
- walker: this.#finder
2544
+ node: this.#node
2514
2545
  });
2515
2546
  if (node) {
2516
2547
  nodes.add(node);
@@ -2542,9 +2573,20 @@ export class Matcher {
2542
2573
  let dir;
2543
2574
  let twig;
2544
2575
  if (complex) {
2545
- const { combo: firstCombo, leaves: [{ type: firstType }] } = firstTwig;
2576
+ const {
2577
+ combo: firstCombo,
2578
+ leaves: [{
2579
+ name: firstName,
2580
+ type: firstType
2581
+ }]
2582
+ } = firstTwig;
2546
2583
  const lastTwig = branch[branchLen - 1];
2547
- const { leaves: [{ type: lastType }] } = lastTwig;
2584
+ const {
2585
+ leaves: [{
2586
+ name: lastName,
2587
+ type: lastType
2588
+ }]
2589
+ } = lastTwig;
2548
2590
  if (lastType === SELECTOR_PSEUDO_ELEMENT || lastType === SELECTOR_ID) {
2549
2591
  dir = DIR_PREV;
2550
2592
  twig = lastTwig;
@@ -2553,7 +2595,13 @@ export class Matcher {
2553
2595
  dir = DIR_NEXT;
2554
2596
  twig = firstTwig;
2555
2597
  } else if (targetType === TARGET_ALL) {
2556
- if (branchLen === 2) {
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) {
2557
2605
  const { name: comboName } = firstCombo;
2558
2606
  if (/^[+~]$/.test(comboName)) {
2559
2607
  dir = DIR_PREV;
@@ -2566,6 +2614,12 @@ export class Matcher {
2566
2614
  dir = DIR_NEXT;
2567
2615
  twig = firstTwig;
2568
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;
2569
2623
  } else {
2570
2624
  let bool;
2571
2625
  let sibling;
@@ -2790,10 +2844,10 @@ export class Matcher {
2790
2844
  if (nodes.size) {
2791
2845
  const n = [...nodes];
2792
2846
  nodes = new Set([...n, ...arr]);
2793
- this.#sort = true;
2794
2847
  } else {
2795
2848
  nodes = new Set([...arr]);
2796
2849
  }
2850
+ this.#sort = true;
2797
2851
  } else {
2798
2852
  const [node] = this._sortNodes(arr);
2799
2853
  nodes.add(node);
@@ -2819,8 +2873,7 @@ export class Matcher {
2819
2873
  if (!matched && targetType === TARGET_FIRST) {
2820
2874
  const [entryNode] = [...entryNodes];
2821
2875
  let refNode = this._findNode(entryLeaves, {
2822
- node: entryNode,
2823
- walker: this.#finder
2876
+ node: entryNode
2824
2877
  });
2825
2878
  while (refNode) {
2826
2879
  let nextNodes = new Set([refNode]);
@@ -2856,8 +2909,7 @@ export class Matcher {
2856
2909
  break;
2857
2910
  }
2858
2911
  refNode = this._findNode(entryLeaves, {
2859
- node: refNode,
2860
- walker: this.#finder
2912
+ node: refNode
2861
2913
  });
2862
2914
  nextNodes = new Set([refNode]);
2863
2915
  }
@@ -2883,6 +2935,10 @@ export class Matcher {
2883
2935
  if (j === 0) {
2884
2936
  nodes.add(node);
2885
2937
  matched = true;
2938
+ if (targetType === TARGET_ALL &&
2939
+ branchLen > 1 && nodes.size > 1) {
2940
+ this.#sort = true;
2941
+ }
2886
2942
  } else {
2887
2943
  nextNodes = new Set(arr);
2888
2944
  matched = false;
@@ -2900,8 +2956,7 @@ export class Matcher {
2900
2956
  if (!matched && targetType === TARGET_FIRST) {
2901
2957
  const [entryNode] = [...entryNodes];
2902
2958
  let refNode = this._findNode(entryLeaves, {
2903
- node: entryNode,
2904
- walker: this.#finder
2959
+ node: entryNode
2905
2960
  });
2906
2961
  while (refNode) {
2907
2962
  let nextNodes = new Set([refNode]);
@@ -2931,8 +2986,7 @@ export class Matcher {
2931
2986
  break;
2932
2987
  }
2933
2988
  refNode = this._findNode(entryLeaves, {
2934
- node: refNode,
2935
- walker: this.#finder
2989
+ node: refNode
2936
2990
  });
2937
2991
  nextNodes = new Set([refNode]);
2938
2992
  }
@@ -2969,20 +3023,13 @@ export class Matcher {
2969
3023
  const msg = `Unexpected node ${node.nodeName}`;
2970
3024
  throw new TypeError(msg);
2971
3025
  }
2972
- this.#cache = new WeakMap();
2973
3026
  this.#node = node;
2974
- [this.#window, this.#document, this.#root, this.#tree] = this._setup(node);
3027
+ [this.#window, this.#document, this.#root] = this._setup(node);
2975
3028
  this.#shadow = isInShadowTree(node);
2976
- if (selector && selector === this.#selector) {
2977
- for (const i of this.#nodes) {
2978
- i.clear();
2979
- }
2980
- } else {
2981
- this.#selector = selector;
2982
- [this.#ast, this.#nodes] = this._correspond(selector);
2983
- }
2984
- // prepare #finder
3029
+ this.#selector = selector;
3030
+ [this.#ast, this.#nodes] = this._correspond(selector);
2985
3031
  if (targetType === TARGET_ALL || targetType === TARGET_FIRST) {
3032
+ this.#tree = this.#document.createTreeWalker(this.#root, WALKER_FILTER);
2986
3033
  this.#finder = this.#document.createTreeWalker(this.#node, SHOW_ELEMENT);
2987
3034
  this.#sort = false;
2988
3035
  }
package/types/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ export { Matcher };
2
+ export let matcher: Matcher;
1
3
  export function matches(selector: string, node: object, opt?: {
2
4
  warn?: boolean;
3
5
  }): boolean;
@@ -10,3 +12,4 @@ export function querySelector(selector: string, node: object, opt?: {
10
12
  export function querySelectorAll(selector: string, node: object, opt?: {
11
13
  warn?: boolean;
12
14
  }): Array<object | undefined>;
15
+ import { Matcher } from './js/matcher.js';
@@ -42,7 +42,6 @@ export class Matcher {
42
42
  }): Set<object>;
43
43
  _findNode(leaves: Array<object>, opt?: {
44
44
  node?: object;
45
- tree?: object;
46
45
  }): object | null;
47
46
  _findEntryNodes(twig: object, targetType: string): object;
48
47
  _getEntryTwig(branch: Array<object>, targetType: string): object;