@asamuzakjp/dom-selector 8.2.0 → 8.2.1
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 +7 -7
- package/src/index.js +5 -5
- package/src/js/constant.js +2 -0
- package/src/js/evaluator.js +235 -242
- package/src/js/finder.js +113 -113
- package/src/js/matcher.js +2 -0
- package/src/js/nwsapi.js +6 -7
- package/src/js/parser.js +3 -1
- package/src/js/utility.js +2 -0
- package/types/js/constant.d.ts +2 -0
- package/types/js/evaluator.d.ts +7 -6
- package/types/js/finder.d.ts +2 -2
package/package.json
CHANGED
|
@@ -25,20 +25,20 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@asamuzakjp/generational-cache": "^3.0.1",
|
|
29
28
|
"bidi-js": "^1.0.3",
|
|
30
29
|
"css-tree": "^3.2.1",
|
|
31
|
-
"is-potential-custom-element-name": "^1.0.1"
|
|
30
|
+
"is-potential-custom-element-name": "^1.0.1",
|
|
31
|
+
"lru-cache": "^11.5.2"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/css-tree": "^2.3.11",
|
|
35
|
-
"@types/node": "^26.
|
|
35
|
+
"@types/node": "^26.1.1",
|
|
36
36
|
"c8": "^11.0.0",
|
|
37
37
|
"chai": "^6.2.2",
|
|
38
38
|
"commander": "^15.0.0",
|
|
39
|
-
"eslint": "^9.39.
|
|
39
|
+
"eslint": "^9.39.5",
|
|
40
40
|
"eslint-config-prettier": "^10.1.8",
|
|
41
|
-
"eslint-plugin-jsdoc": "^63.0.
|
|
41
|
+
"eslint-plugin-jsdoc": "^63.0.13",
|
|
42
42
|
"eslint-plugin-prettier": "^5.5.6",
|
|
43
43
|
"eslint-plugin-regexp": "^3.1.1",
|
|
44
44
|
"eslint-plugin-unicorn": "^65.0.1",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"mitata": "^1.0.34",
|
|
48
48
|
"mocha": "^11.7.6",
|
|
49
49
|
"neostandard": "^0.13.0",
|
|
50
|
-
"prettier": "^3.9.
|
|
50
|
+
"prettier": "^3.9.5",
|
|
51
51
|
"sinon": "^22.0.0",
|
|
52
52
|
"tinybench": "^6.0.2",
|
|
53
53
|
"typescript": "^6.0.3",
|
|
@@ -88,5 +88,5 @@
|
|
|
88
88
|
"engines": {
|
|
89
89
|
"node": "^22.13.0 || >=24.0.0"
|
|
90
90
|
},
|
|
91
|
-
"version": "8.2.
|
|
91
|
+
"version": "8.2.1"
|
|
92
92
|
}
|
package/src/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
/* import */
|
|
9
|
-
import {
|
|
9
|
+
import { LRUCache } from 'lru-cache';
|
|
10
10
|
import { Finder } from './js/finder.js';
|
|
11
11
|
import { Nwsapi } from './js/nwsapi.js';
|
|
12
12
|
import { extractSubjectsAst } from './js/parser.js';
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
TARGET_LINEAL,
|
|
27
27
|
TARGET_SELF
|
|
28
28
|
} from './js/constant.js';
|
|
29
|
-
const CACHE_SIZE =
|
|
29
|
+
const CACHE_SIZE = 4096;
|
|
30
30
|
|
|
31
31
|
/* regexp */
|
|
32
32
|
const REG_SELECTOR = /[[\]():\\"'`]/;
|
|
@@ -60,11 +60,11 @@ export class DOMSelector {
|
|
|
60
60
|
this.#window = window;
|
|
61
61
|
this.#document = document ?? window.document;
|
|
62
62
|
this.#idlUtils = idlUtils;
|
|
63
|
-
this.#cache = new
|
|
64
|
-
|
|
63
|
+
this.#cache = new LRUCache({
|
|
64
|
+
max: cacheSize ?? CACHE_SIZE
|
|
65
65
|
});
|
|
66
66
|
this.#finder = new Finder(this.#window);
|
|
67
|
-
this.#nwsapi = new Nwsapi(this.#window, this.#document);
|
|
67
|
+
this.#nwsapi = new Nwsapi(this.#window, this.#document, cacheSize);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/**
|
package/src/js/constant.js
CHANGED
|
@@ -7,6 +7,8 @@ export const ATRULE = 'Atrule';
|
|
|
7
7
|
export const ATTR_SELECTOR = 'AttributeSelector';
|
|
8
8
|
export const CLASS_SELECTOR = 'ClassSelector';
|
|
9
9
|
export const COMBINATOR = 'Combinator';
|
|
10
|
+
export const DIR_NEXT = 'next';
|
|
11
|
+
export const DIR_PREV = 'prev';
|
|
10
12
|
export const IDENT = 'Identifier';
|
|
11
13
|
export const ID_SELECTOR = 'IdSelector';
|
|
12
14
|
export const NOT_SUPPORTED_ERR = 'NotSupportedError';
|
package/src/js/evaluator.js
CHANGED
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
isCustomElement,
|
|
20
20
|
isFocusVisible,
|
|
21
21
|
isFocusableArea,
|
|
22
|
-
isVisible,
|
|
23
22
|
populateHasAllowlist,
|
|
24
23
|
resolveContent,
|
|
25
24
|
traverseNode
|
|
@@ -30,6 +29,8 @@ import {
|
|
|
30
29
|
ATTR_SELECTOR,
|
|
31
30
|
CLASS_SELECTOR,
|
|
32
31
|
COMBINATOR,
|
|
32
|
+
DIR_NEXT,
|
|
33
|
+
DIR_PREV,
|
|
33
34
|
DOCUMENT_FRAGMENT_NODE,
|
|
34
35
|
ELEMENT_NODE,
|
|
35
36
|
FORM_PARTS,
|
|
@@ -49,8 +50,6 @@ import {
|
|
|
49
50
|
TEXT_NODE,
|
|
50
51
|
TYPE_SELECTOR
|
|
51
52
|
} from './constant.js';
|
|
52
|
-
const DIR_NEXT = 'next';
|
|
53
|
-
const DIR_PREV = 'prev';
|
|
54
53
|
const KEYS_FORM = new Set([...FORM_PARTS, 'fieldset', 'form']);
|
|
55
54
|
const KEYS_FORM_PS_VALID = new Set([...FORM_PARTS, 'form']);
|
|
56
55
|
const KEYS_INPUT_CHECK = new Set(INPUT_CHECK);
|
|
@@ -141,6 +140,33 @@ export class Evaluator {
|
|
|
141
140
|
this._registerEventListeners();
|
|
142
141
|
}
|
|
143
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Sets up the evaluator.
|
|
145
|
+
* @param {string} selector - The CSS selector.
|
|
146
|
+
* @param {object} node - Document, DocumentFragment, or Element.
|
|
147
|
+
* @param {object} [opt] - Options.
|
|
148
|
+
* @param {boolean} [opt.check] - Indicates if running in internal check().
|
|
149
|
+
* @param {boolean} [opt.noexcept] - If true, exceptions are not thrown.
|
|
150
|
+
* @param {boolean} [opt.warn] - If true, console warnings are enabled.
|
|
151
|
+
* @returns {object} The evaluator instance.
|
|
152
|
+
*/
|
|
153
|
+
setup(selector, node, opt = {}) {
|
|
154
|
+
const { check, noexcept, warn } = opt;
|
|
155
|
+
this.check = !!check;
|
|
156
|
+
this.noexcept = !!noexcept;
|
|
157
|
+
this.warn = !!warn;
|
|
158
|
+
this.matchOpts = { warn: this.warn };
|
|
159
|
+
[this.document, this.root, this.shadow] = resolveContent(node);
|
|
160
|
+
this.node = node;
|
|
161
|
+
this.pseudoElements = [];
|
|
162
|
+
this.invalidate = false;
|
|
163
|
+
this.clearResults();
|
|
164
|
+
this.#documentURL = null;
|
|
165
|
+
this.#verifyShadowHost = false;
|
|
166
|
+
this.#walkers = null;
|
|
167
|
+
return this;
|
|
168
|
+
}
|
|
169
|
+
|
|
144
170
|
/**
|
|
145
171
|
* Handles errors.
|
|
146
172
|
* @param {Error} e - The error object.
|
|
@@ -171,39 +197,12 @@ export class Evaluator {
|
|
|
171
197
|
throw e;
|
|
172
198
|
};
|
|
173
199
|
|
|
174
|
-
/**
|
|
175
|
-
* Sets up the evaluator.
|
|
176
|
-
* @param {string} selector - The CSS selector.
|
|
177
|
-
* @param {object} node - Document, DocumentFragment, or Element.
|
|
178
|
-
* @param {object} [opt] - Options.
|
|
179
|
-
* @param {boolean} [opt.check] - Indicates if running in internal check().
|
|
180
|
-
* @param {boolean} [opt.noexcept] - If true, exceptions are not thrown.
|
|
181
|
-
* @param {boolean} [opt.warn] - If true, console warnings are enabled.
|
|
182
|
-
* @returns {object} The matcher instance.
|
|
183
|
-
*/
|
|
184
|
-
setup(selector, node, opt = {}) {
|
|
185
|
-
const { check, noexcept, warn } = opt;
|
|
186
|
-
this.check = !!check;
|
|
187
|
-
this.noexcept = !!noexcept;
|
|
188
|
-
this.warn = !!warn;
|
|
189
|
-
this.matchOpts = { warn: this.warn };
|
|
190
|
-
[this.document, this.root, this.shadow] = resolveContent(node);
|
|
191
|
-
this.node = node;
|
|
192
|
-
this.pseudoElements = [];
|
|
193
|
-
this.invalidate = false;
|
|
194
|
-
this.clearResults();
|
|
195
|
-
this.#documentURL = null;
|
|
196
|
-
this.#verifyShadowHost = false;
|
|
197
|
-
this.#walkers = null;
|
|
198
|
-
return this;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
200
|
/**
|
|
202
201
|
* Clear cached results.
|
|
203
202
|
* @param {boolean} all - Clear all results.
|
|
204
203
|
* @returns {void}
|
|
205
204
|
*/
|
|
206
|
-
clearResults(all = false) {
|
|
205
|
+
clearResults = (all = false) => {
|
|
207
206
|
this.#anbCache = null;
|
|
208
207
|
this.#focusWithinCache = null;
|
|
209
208
|
this.#invalidateResults = null;
|
|
@@ -217,7 +216,7 @@ export class Evaluator {
|
|
|
217
216
|
this.#filterLeavesCache = null;
|
|
218
217
|
this.#results = new WeakMap();
|
|
219
218
|
}
|
|
220
|
-
}
|
|
219
|
+
};
|
|
221
220
|
|
|
222
221
|
/**
|
|
223
222
|
* Matches a selector.
|
|
@@ -253,9 +252,11 @@ export class Evaluator {
|
|
|
253
252
|
}
|
|
254
253
|
const results = this.invalidate ? this.#invalidateResults : this.#results;
|
|
255
254
|
let result = results.get(leaves);
|
|
256
|
-
if (result
|
|
257
|
-
const
|
|
258
|
-
|
|
255
|
+
if (result) {
|
|
256
|
+
const nodeResult = result.get(node);
|
|
257
|
+
if (nodeResult) {
|
|
258
|
+
return nodeResult.matched;
|
|
259
|
+
}
|
|
259
260
|
}
|
|
260
261
|
let cacheable = true;
|
|
261
262
|
if (node.nodeType === ELEMENT_NODE && KEYS_FORM.has(node.localName)) {
|
|
@@ -307,10 +308,11 @@ export class Evaluator {
|
|
|
307
308
|
if (!this.#filterLeavesCache) {
|
|
308
309
|
this.#filterLeavesCache = new WeakMap();
|
|
309
310
|
}
|
|
310
|
-
|
|
311
|
-
|
|
311
|
+
let filterLeaves = this.#filterLeavesCache.get(leaves);
|
|
312
|
+
if (filterLeaves) {
|
|
313
|
+
return filterLeaves;
|
|
312
314
|
}
|
|
313
|
-
|
|
315
|
+
filterLeaves = leaves.slice(1);
|
|
314
316
|
this.#filterLeavesCache.set(leaves, filterLeaves);
|
|
315
317
|
return filterLeaves;
|
|
316
318
|
};
|
|
@@ -518,7 +520,7 @@ export class Evaluator {
|
|
|
518
520
|
this.#psDefaultCache = new WeakMap();
|
|
519
521
|
}
|
|
520
522
|
let defaultSubmit = this.#psDefaultCache.get(form);
|
|
521
|
-
if (
|
|
523
|
+
if (defaultSubmit === undefined) {
|
|
522
524
|
const walker = this.createTreeWalker(form, { force: true });
|
|
523
525
|
let refNode = traverseNode(form, walker);
|
|
524
526
|
refNode = walker.firstChild();
|
|
@@ -587,7 +589,7 @@ export class Evaluator {
|
|
|
587
589
|
this.#psIndeterminateCache = new WeakMap();
|
|
588
590
|
}
|
|
589
591
|
let parentCache = this.#psIndeterminateCache.get(parent);
|
|
590
|
-
if (
|
|
592
|
+
if (parentCache === undefined) {
|
|
591
593
|
parentCache = new Map();
|
|
592
594
|
this.#psIndeterminateCache.set(parent, parentCache);
|
|
593
595
|
}
|
|
@@ -644,7 +646,7 @@ export class Evaluator {
|
|
|
644
646
|
this.#psValidCache = new WeakMap();
|
|
645
647
|
}
|
|
646
648
|
let valid = this.#psValidCache.get(node);
|
|
647
|
-
if (valid === undefined
|
|
649
|
+
if (valid === undefined) {
|
|
648
650
|
const walker = this.createTreeWalker(node, { force: true });
|
|
649
651
|
let refNode = traverseNode(node, walker);
|
|
650
652
|
refNode = walker.firstChild();
|
|
@@ -919,6 +921,9 @@ export class Evaluator {
|
|
|
919
921
|
if (!this.#focusWithinCache) {
|
|
920
922
|
this.#focusWithinCache = new Set();
|
|
921
923
|
let currentFocus = this.document.activeElement;
|
|
924
|
+
while (currentFocus?.shadowRoot?.activeElement) {
|
|
925
|
+
currentFocus = currentFocus.shadowRoot.activeElement;
|
|
926
|
+
}
|
|
922
927
|
if (currentFocus && isFocusableArea(currentFocus)) {
|
|
923
928
|
while (currentFocus) {
|
|
924
929
|
this.#focusWithinCache.add(currentFocus);
|
|
@@ -933,23 +938,6 @@ export class Evaluator {
|
|
|
933
938
|
break;
|
|
934
939
|
}
|
|
935
940
|
}
|
|
936
|
-
} else if (currentFocus && currentFocus.shadowRoot) {
|
|
937
|
-
let shadowFocus = currentFocus.shadowRoot.activeElement;
|
|
938
|
-
if (shadowFocus) {
|
|
939
|
-
while (shadowFocus) {
|
|
940
|
-
this.#focusWithinCache.add(shadowFocus);
|
|
941
|
-
if (shadowFocus.parentNode) {
|
|
942
|
-
shadowFocus = shadowFocus.parentNode;
|
|
943
|
-
} else if (
|
|
944
|
-
shadowFocus.nodeType === DOCUMENT_FRAGMENT_NODE &&
|
|
945
|
-
shadowFocus.host
|
|
946
|
-
) {
|
|
947
|
-
shadowFocus = shadowFocus.host;
|
|
948
|
-
} else {
|
|
949
|
-
break;
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
941
|
}
|
|
954
942
|
}
|
|
955
943
|
return this.#focusWithinCache.has(node);
|
|
@@ -1047,10 +1035,11 @@ export class Evaluator {
|
|
|
1047
1035
|
if (!this.#walkers) {
|
|
1048
1036
|
this.#walkers = new WeakMap();
|
|
1049
1037
|
}
|
|
1050
|
-
|
|
1051
|
-
|
|
1038
|
+
let walker = this.#walkers.get(node);
|
|
1039
|
+
if (walker) {
|
|
1040
|
+
return walker;
|
|
1052
1041
|
}
|
|
1053
|
-
|
|
1042
|
+
walker = this.document.createTreeWalker(node, whatToShow);
|
|
1054
1043
|
this.#walkers.set(node, walker);
|
|
1055
1044
|
return walker;
|
|
1056
1045
|
};
|
|
@@ -1116,7 +1105,11 @@ export class Evaluator {
|
|
|
1116
1105
|
case ' ':
|
|
1117
1106
|
default: {
|
|
1118
1107
|
if (dir === DIR_NEXT) {
|
|
1119
|
-
for (const refNode of this.
|
|
1108
|
+
for (const refNode of this.yieldFindDescendantNodes(
|
|
1109
|
+
leaves,
|
|
1110
|
+
node,
|
|
1111
|
+
opt
|
|
1112
|
+
)) {
|
|
1120
1113
|
yield refNode;
|
|
1121
1114
|
}
|
|
1122
1115
|
} else {
|
|
@@ -1138,6 +1131,99 @@ export class Evaluator {
|
|
|
1138
1131
|
}
|
|
1139
1132
|
}
|
|
1140
1133
|
|
|
1134
|
+
/**
|
|
1135
|
+
* Traverses all descendant nodes and yields matches.
|
|
1136
|
+
* @param {object} baseNode - The base Element node or Element.shadowRoot.
|
|
1137
|
+
* @param {Array.<object>} leaves - The AST leaves.
|
|
1138
|
+
* @param {object} opt - Options.
|
|
1139
|
+
* @yields {object} The matched node.
|
|
1140
|
+
*/
|
|
1141
|
+
*yieldTraverseAllDescendants(baseNode, leaves, opt) {
|
|
1142
|
+
const walker = this.createTreeWalker(baseNode);
|
|
1143
|
+
traverseNode(baseNode, walker);
|
|
1144
|
+
let currentNode = walker.firstChild();
|
|
1145
|
+
while (currentNode) {
|
|
1146
|
+
if (this.matchLeaves(leaves, currentNode, opt)) {
|
|
1147
|
+
yield currentNode;
|
|
1148
|
+
}
|
|
1149
|
+
currentNode = walker.nextNode();
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
/**
|
|
1154
|
+
* Finds descendant nodes and yields matches.
|
|
1155
|
+
* @param {Array.<object>} leaves - The AST leaves.
|
|
1156
|
+
* @param {object} baseNode - The base Element node or Element.shadowRoot.
|
|
1157
|
+
* @param {object} opt - Options.
|
|
1158
|
+
* @yields {object} The matched node.
|
|
1159
|
+
*/
|
|
1160
|
+
*yieldFindDescendantNodes(leaves, baseNode, opt) {
|
|
1161
|
+
const [{ name, type: leafType }] = leaves;
|
|
1162
|
+
const leafName = unescapeSelector(name);
|
|
1163
|
+
const filterLeaves = this.getFilterLeaves(leaves);
|
|
1164
|
+
const isSimple = filterLeaves.length === 0;
|
|
1165
|
+
|
|
1166
|
+
switch (leafType) {
|
|
1167
|
+
case ID_SELECTOR: {
|
|
1168
|
+
if (
|
|
1169
|
+
!this.shadow &&
|
|
1170
|
+
baseNode.nodeType === ELEMENT_NODE &&
|
|
1171
|
+
this.root.nodeType !== ELEMENT_NODE
|
|
1172
|
+
) {
|
|
1173
|
+
const foundNode = this.root.getElementById(leafName);
|
|
1174
|
+
if (
|
|
1175
|
+
foundNode &&
|
|
1176
|
+
foundNode !== baseNode &&
|
|
1177
|
+
baseNode.contains(foundNode)
|
|
1178
|
+
) {
|
|
1179
|
+
if (isSimple || this.matchLeaves(filterLeaves, foundNode, opt)) {
|
|
1180
|
+
yield foundNode;
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
break;
|
|
1186
|
+
}
|
|
1187
|
+
case CLASS_SELECTOR: {
|
|
1188
|
+
if (typeof baseNode.getElementsByClassName === 'function') {
|
|
1189
|
+
const collection = baseNode.getElementsByClassName(leafName);
|
|
1190
|
+
for (let i = 0, len = collection.length; i < len; i++) {
|
|
1191
|
+
const foundNode = collection[i];
|
|
1192
|
+
if (isSimple || this.matchLeaves(filterLeaves, foundNode, opt)) {
|
|
1193
|
+
yield foundNode;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
break;
|
|
1199
|
+
}
|
|
1200
|
+
case TYPE_SELECTOR: {
|
|
1201
|
+
if (
|
|
1202
|
+
typeof baseNode.getElementsByTagName === 'function' &&
|
|
1203
|
+
!leafName.includes('|')
|
|
1204
|
+
) {
|
|
1205
|
+
const collection = baseNode.getElementsByTagName(leafName);
|
|
1206
|
+
for (let i = 0, len = collection.length; i < len; i++) {
|
|
1207
|
+
const foundNode = collection[i];
|
|
1208
|
+
if (isSimple || this.matchLeaves(filterLeaves, foundNode, opt)) {
|
|
1209
|
+
yield foundNode;
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1214
|
+
break;
|
|
1215
|
+
}
|
|
1216
|
+
case PS_ELEMENT_SELECTOR: {
|
|
1217
|
+
matchPseudoElementSelector(leafName, leafType, opt);
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
default: {
|
|
1221
|
+
// no-op
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
yield* this.yieldTraverseAllDescendants(baseNode, leaves, opt);
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1141
1227
|
/**
|
|
1142
1228
|
* Handles focus events.
|
|
1143
1229
|
* @private
|
|
@@ -1201,16 +1287,37 @@ export class Evaluator {
|
|
|
1201
1287
|
* @returns {Array.<Array.<object>>} The selector branches.
|
|
1202
1288
|
*/
|
|
1203
1289
|
_getSelectorBranches = selector => {
|
|
1204
|
-
|
|
1205
|
-
|
|
1290
|
+
let branches = this.#astCache.get(selector);
|
|
1291
|
+
if (branches) {
|
|
1292
|
+
return branches;
|
|
1206
1293
|
}
|
|
1207
|
-
const
|
|
1294
|
+
const walkedResult = walkAST(selector);
|
|
1295
|
+
branches = walkedResult.branches;
|
|
1208
1296
|
this.#astCache.set(selector, branches);
|
|
1209
1297
|
return branches;
|
|
1210
1298
|
};
|
|
1211
1299
|
|
|
1212
1300
|
/**
|
|
1213
|
-
*
|
|
1301
|
+
* Checks if a node matches any of the given selector branches.
|
|
1302
|
+
* @private
|
|
1303
|
+
* @param {Array.<Array.<object>>} branches - The selector branches to test.
|
|
1304
|
+
* @param {object} node - The element node to match against.
|
|
1305
|
+
* @param {object} [opt] - Optional parameters.
|
|
1306
|
+
* @returns {boolean} True if any branch matches, otherwise false.
|
|
1307
|
+
*/
|
|
1308
|
+
_filterNthChildOfSelectorBranches = (branches, node, opt) => {
|
|
1309
|
+
let filterMatch = false;
|
|
1310
|
+
for (const branch of branches) {
|
|
1311
|
+
if (this.matchLeaves(branch, node, opt)) {
|
|
1312
|
+
filterMatch = true;
|
|
1313
|
+
break;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
return filterMatch;
|
|
1317
|
+
};
|
|
1318
|
+
|
|
1319
|
+
/**
|
|
1320
|
+
* Evaluates An+B mathematically.
|
|
1214
1321
|
* @private
|
|
1215
1322
|
* @param {object} ast - The AST.
|
|
1216
1323
|
* @param {object} node - The Element node.
|
|
@@ -1219,16 +1326,32 @@ export class Evaluator {
|
|
|
1219
1326
|
* @returns {boolean} True if matches, otherwise false.
|
|
1220
1327
|
*/
|
|
1221
1328
|
_matchAnPlusB = (ast, node, nthName, opt) => {
|
|
1329
|
+
const {
|
|
1330
|
+
localName,
|
|
1331
|
+
namespaceURI,
|
|
1332
|
+
nextElementSibling,
|
|
1333
|
+
parentNode,
|
|
1334
|
+
previousElementSibling
|
|
1335
|
+
} = node;
|
|
1336
|
+
if (!parentNode && node !== this.root) {
|
|
1337
|
+
return false;
|
|
1338
|
+
}
|
|
1222
1339
|
if (!this.#anbCache) {
|
|
1223
1340
|
this.#anbCache = new WeakMap();
|
|
1224
1341
|
}
|
|
1225
1342
|
let anb = this.#anbCache.get(ast);
|
|
1226
|
-
if (
|
|
1343
|
+
if (anb === undefined) {
|
|
1227
1344
|
const {
|
|
1228
1345
|
nth: { a, b, name: nthIdentName },
|
|
1229
1346
|
selector
|
|
1230
1347
|
} = ast;
|
|
1231
|
-
anb = {
|
|
1348
|
+
anb = {
|
|
1349
|
+
a: 0,
|
|
1350
|
+
b: 0,
|
|
1351
|
+
isLast: nthName.includes('last'),
|
|
1352
|
+
isOfType: nthName.includes('of-type'),
|
|
1353
|
+
selector: null
|
|
1354
|
+
};
|
|
1232
1355
|
if (nthIdentName) {
|
|
1233
1356
|
if (nthIdentName === 'even') {
|
|
1234
1357
|
anb.a = 2;
|
|
@@ -1238,78 +1361,62 @@ export class Evaluator {
|
|
|
1238
1361
|
anb.b = 1;
|
|
1239
1362
|
}
|
|
1240
1363
|
} else {
|
|
1241
|
-
|
|
1242
|
-
|
|
1364
|
+
const intA = parseInt(a);
|
|
1365
|
+
if (Number.isInteger(intA)) {
|
|
1366
|
+
anb.a = intA;
|
|
1367
|
+
}
|
|
1368
|
+
const intB = parseInt(b);
|
|
1369
|
+
if (Number.isInteger(intB)) {
|
|
1370
|
+
anb.b = intB;
|
|
1371
|
+
}
|
|
1243
1372
|
}
|
|
1244
|
-
if (
|
|
1245
|
-
selector &&
|
|
1246
|
-
(nthName === 'nth-child' || nthName === 'nth-last-child')
|
|
1247
|
-
) {
|
|
1373
|
+
if (selector && /^nth-(?:last-)?child$/.test(nthName)) {
|
|
1248
1374
|
anb.selector = selector;
|
|
1249
1375
|
}
|
|
1250
1376
|
this.#anbCache.set(ast, anb);
|
|
1251
1377
|
}
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
const hasFilter = !!anb.selector;
|
|
1263
|
-
if (hasFilter) {
|
|
1264
|
-
const selectorBranches = this._getSelectorBranches(anb.selector);
|
|
1265
|
-
let filterMatch = false;
|
|
1266
|
-
for (let i = 0; i < selectorBranches.length; i++) {
|
|
1267
|
-
if (this.matchLeaves(selectorBranches[i], node, opt)) {
|
|
1268
|
-
filterMatch = true;
|
|
1269
|
-
break;
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1378
|
+
const { a, b, isLast, isOfType, selector: anbSelector } = anb;
|
|
1379
|
+
const startNode = isLast ? nextElementSibling : previousElementSibling;
|
|
1380
|
+
let pos = 1;
|
|
1381
|
+
if (anbSelector) {
|
|
1382
|
+
const selectorBranches = this._getSelectorBranches(anbSelector);
|
|
1383
|
+
const filterMatch = this._filterNthChildOfSelectorBranches(
|
|
1384
|
+
selectorBranches,
|
|
1385
|
+
node,
|
|
1386
|
+
opt
|
|
1387
|
+
);
|
|
1272
1388
|
if (!filterMatch) {
|
|
1273
1389
|
return false;
|
|
1274
1390
|
}
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
let current = isLast
|
|
1282
|
-
? node.nextElementSibling
|
|
1283
|
-
: node.previousElementSibling;
|
|
1284
|
-
while (current) {
|
|
1285
|
-
let match = true;
|
|
1286
|
-
if (isOfType) {
|
|
1287
|
-
match =
|
|
1288
|
-
current.localName === node.localName &&
|
|
1289
|
-
current.namespaceURI === node.namespaceURI;
|
|
1290
|
-
} else if (hasFilter) {
|
|
1291
|
-
const selectorBranches = this._getSelectorBranches(anb.selector);
|
|
1292
|
-
let filterMatch = false;
|
|
1293
|
-
for (let i = 0; i < selectorBranches.length; i++) {
|
|
1294
|
-
if (this.matchLeaves(selectorBranches[i], current, opt)) {
|
|
1295
|
-
filterMatch = true;
|
|
1296
|
-
break;
|
|
1297
|
-
}
|
|
1391
|
+
let current = startNode;
|
|
1392
|
+
while (current) {
|
|
1393
|
+
if (
|
|
1394
|
+
this._filterNthChildOfSelectorBranches(selectorBranches, current, opt)
|
|
1395
|
+
) {
|
|
1396
|
+
pos++;
|
|
1298
1397
|
}
|
|
1299
|
-
|
|
1300
|
-
|
|
1398
|
+
current = isLast
|
|
1399
|
+
? current.nextElementSibling
|
|
1400
|
+
: current.previousElementSibling;
|
|
1401
|
+
}
|
|
1402
|
+
} else {
|
|
1403
|
+
let current = startNode;
|
|
1404
|
+
while (current) {
|
|
1405
|
+
if (isOfType) {
|
|
1406
|
+
if (
|
|
1407
|
+
current.localName === localName &&
|
|
1408
|
+
current.namespaceURI === namespaceURI
|
|
1409
|
+
) {
|
|
1410
|
+
pos++;
|
|
1411
|
+
}
|
|
1301
1412
|
} else {
|
|
1302
|
-
|
|
1413
|
+
pos++;
|
|
1303
1414
|
}
|
|
1415
|
+
current = isLast
|
|
1416
|
+
? current.nextElementSibling
|
|
1417
|
+
: current.previousElementSibling;
|
|
1304
1418
|
}
|
|
1305
|
-
if (match) {
|
|
1306
|
-
pos++;
|
|
1307
|
-
}
|
|
1308
|
-
current = isLast
|
|
1309
|
-
? current.nextElementSibling
|
|
1310
|
-
: current.previousElementSibling;
|
|
1311
1419
|
}
|
|
1312
|
-
const { a, b } = anb;
|
|
1313
1420
|
if (a === 0) {
|
|
1314
1421
|
return pos === b;
|
|
1315
1422
|
}
|
|
@@ -1576,7 +1683,7 @@ export class Evaluator {
|
|
|
1576
1683
|
this.#psHasFilterCache = new WeakMap();
|
|
1577
1684
|
}
|
|
1578
1685
|
let rootCache = this.#psHasFilterCache.get(this.root);
|
|
1579
|
-
if (
|
|
1686
|
+
if (rootCache === undefined) {
|
|
1580
1687
|
rootCache = new WeakMap();
|
|
1581
1688
|
this.#psHasFilterCache.set(this.root, rootCache);
|
|
1582
1689
|
}
|
|
@@ -2032,118 +2139,4 @@ export class Evaluator {
|
|
|
2032
2139
|
}
|
|
2033
2140
|
return false;
|
|
2034
2141
|
};
|
|
2035
|
-
|
|
2036
|
-
/**
|
|
2037
|
-
* Traverses all descendant nodes and collects matches.
|
|
2038
|
-
* @private
|
|
2039
|
-
* @param {object} baseNode - The base Element node or Element.shadowRoot.
|
|
2040
|
-
* @param {Array.<object>} leaves - The AST leaves.
|
|
2041
|
-
* @param {object} opt - Options.
|
|
2042
|
-
* @returns {Set.<object>} A collection of matched nodes.
|
|
2043
|
-
*/
|
|
2044
|
-
_traverseAllDescendants = (baseNode, leaves, opt) => {
|
|
2045
|
-
const walker = this.createTreeWalker(baseNode);
|
|
2046
|
-
traverseNode(baseNode, walker);
|
|
2047
|
-
let currentNode = walker.firstChild();
|
|
2048
|
-
const nodes = new Set();
|
|
2049
|
-
while (currentNode) {
|
|
2050
|
-
if (this.matchLeaves(leaves, currentNode, opt)) {
|
|
2051
|
-
nodes.add(currentNode);
|
|
2052
|
-
}
|
|
2053
|
-
currentNode = walker.nextNode();
|
|
2054
|
-
}
|
|
2055
|
-
return nodes;
|
|
2056
|
-
};
|
|
2057
|
-
|
|
2058
|
-
/**
|
|
2059
|
-
* Finds descendant nodes.
|
|
2060
|
-
* @private
|
|
2061
|
-
* @param {Array.<object>} leaves - The AST leaves.
|
|
2062
|
-
* @param {object} baseNode - The base Element node or Element.shadowRoot.
|
|
2063
|
-
* @param {object} opt - Options.
|
|
2064
|
-
* @returns {Set.<object>} A collection of matched nodes.
|
|
2065
|
-
*/
|
|
2066
|
-
_findDescendantNodes = (leaves, baseNode, opt) => {
|
|
2067
|
-
const [leaf] = leaves;
|
|
2068
|
-
const filterLeaves = this.getFilterLeaves(leaves);
|
|
2069
|
-
const { type: leafType } = leaf;
|
|
2070
|
-
switch (leafType) {
|
|
2071
|
-
case ID_SELECTOR: {
|
|
2072
|
-
const canUseGetElementById =
|
|
2073
|
-
!this.shadow &&
|
|
2074
|
-
baseNode.nodeType === ELEMENT_NODE &&
|
|
2075
|
-
this.root.nodeType !== ELEMENT_NODE;
|
|
2076
|
-
if (canUseGetElementById) {
|
|
2077
|
-
const leafName = unescapeSelector(leaf.name);
|
|
2078
|
-
const nodes = new Set();
|
|
2079
|
-
const foundNode = this.root.getElementById(leafName);
|
|
2080
|
-
if (
|
|
2081
|
-
foundNode &&
|
|
2082
|
-
foundNode !== baseNode &&
|
|
2083
|
-
baseNode.contains(foundNode)
|
|
2084
|
-
) {
|
|
2085
|
-
const isCompoundSelector = filterLeaves.length > 0;
|
|
2086
|
-
if (
|
|
2087
|
-
!isCompoundSelector ||
|
|
2088
|
-
this.matchLeaves(filterLeaves, foundNode, opt)
|
|
2089
|
-
) {
|
|
2090
|
-
nodes.add(foundNode);
|
|
2091
|
-
}
|
|
2092
|
-
}
|
|
2093
|
-
return nodes;
|
|
2094
|
-
}
|
|
2095
|
-
break;
|
|
2096
|
-
}
|
|
2097
|
-
case CLASS_SELECTOR: {
|
|
2098
|
-
if (typeof baseNode.getElementsByClassName === 'function') {
|
|
2099
|
-
const leafName = unescapeSelector(leaf.name);
|
|
2100
|
-
const collection = baseNode.getElementsByClassName(leafName);
|
|
2101
|
-
const nodes = new Set();
|
|
2102
|
-
const isCompoundSelector = filterLeaves.length > 0;
|
|
2103
|
-
for (let i = 0, len = collection.length; i < len; i++) {
|
|
2104
|
-
const foundNode = collection[i];
|
|
2105
|
-
if (
|
|
2106
|
-
!isCompoundSelector ||
|
|
2107
|
-
this.matchLeaves(filterLeaves, foundNode, opt)
|
|
2108
|
-
) {
|
|
2109
|
-
nodes.add(foundNode);
|
|
2110
|
-
}
|
|
2111
|
-
}
|
|
2112
|
-
return nodes;
|
|
2113
|
-
}
|
|
2114
|
-
break;
|
|
2115
|
-
}
|
|
2116
|
-
case TYPE_SELECTOR: {
|
|
2117
|
-
const leafName = unescapeSelector(leaf.name);
|
|
2118
|
-
if (
|
|
2119
|
-
typeof baseNode.getElementsByTagName === 'function' &&
|
|
2120
|
-
!leafName.includes('|')
|
|
2121
|
-
) {
|
|
2122
|
-
const collection = baseNode.getElementsByTagName(leafName);
|
|
2123
|
-
const nodes = new Set();
|
|
2124
|
-
const isCompoundSelector = filterLeaves.length > 0;
|
|
2125
|
-
for (let i = 0, len = collection.length; i < len; i++) {
|
|
2126
|
-
const foundNode = collection[i];
|
|
2127
|
-
if (
|
|
2128
|
-
!isCompoundSelector ||
|
|
2129
|
-
this.matchLeaves(filterLeaves, foundNode, opt)
|
|
2130
|
-
) {
|
|
2131
|
-
nodes.add(foundNode);
|
|
2132
|
-
}
|
|
2133
|
-
}
|
|
2134
|
-
return nodes;
|
|
2135
|
-
}
|
|
2136
|
-
break;
|
|
2137
|
-
}
|
|
2138
|
-
case PS_ELEMENT_SELECTOR: {
|
|
2139
|
-
const leafName = unescapeSelector(leaf.name);
|
|
2140
|
-
matchPseudoElementSelector(leafName, leafType, opt);
|
|
2141
|
-
return new Set();
|
|
2142
|
-
}
|
|
2143
|
-
default: {
|
|
2144
|
-
// no-op
|
|
2145
|
-
}
|
|
2146
|
-
}
|
|
2147
|
-
return this._traverseAllDescendants(baseNode, leaves, opt);
|
|
2148
|
-
};
|
|
2149
2142
|
}
|
package/src/js/finder.js
CHANGED
|
@@ -19,6 +19,8 @@ import { generateException, sortNodes, traverseNode } from './utility.js';
|
|
|
19
19
|
import {
|
|
20
20
|
CLASS_SELECTOR,
|
|
21
21
|
COMBINATOR,
|
|
22
|
+
DIR_NEXT,
|
|
23
|
+
DIR_PREV,
|
|
22
24
|
DOCUMENT_FRAGMENT_NODE,
|
|
23
25
|
ELEMENT_NODE,
|
|
24
26
|
ID_SELECTOR,
|
|
@@ -30,8 +32,6 @@ import {
|
|
|
30
32
|
TARGET_SELF,
|
|
31
33
|
TYPE_SELECTOR
|
|
32
34
|
} from './constant.js';
|
|
33
|
-
const DIR_NEXT = 'next';
|
|
34
|
-
const DIR_PREV = 'prev';
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* Finder
|
|
@@ -70,6 +70,117 @@ export class Finder extends Evaluator {
|
|
|
70
70
|
return this;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Finds matched nodes.
|
|
75
|
+
* @param {string} targetType - The target type.
|
|
76
|
+
* @returns {Set.<object>|object} A collection of matched nodes.
|
|
77
|
+
*/
|
|
78
|
+
find = targetType => {
|
|
79
|
+
let collection;
|
|
80
|
+
try {
|
|
81
|
+
collection = this._collectNodes(targetType);
|
|
82
|
+
} catch (e) {
|
|
83
|
+
if (this.check) {
|
|
84
|
+
return {
|
|
85
|
+
ast: this.#selectorAST,
|
|
86
|
+
match: false,
|
|
87
|
+
pseudoElement: this.pseudoElements.length
|
|
88
|
+
? this.pseudoElements.join('')
|
|
89
|
+
: null
|
|
90
|
+
};
|
|
91
|
+
} else {
|
|
92
|
+
throw e;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const [[...branches], collectedNodes] = collection;
|
|
96
|
+
const l = branches.length;
|
|
97
|
+
let sort = l > 1 && targetType === TARGET_ALL;
|
|
98
|
+
let nodes = new Set();
|
|
99
|
+
for (let i = 0; i < l; i++) {
|
|
100
|
+
const { branch, dir, find } = branches[i];
|
|
101
|
+
if (!branch.length || !find) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const entryNodes = collectedNodes[i];
|
|
105
|
+
const lastIndex = branch.length - 1;
|
|
106
|
+
if (lastIndex === 0) {
|
|
107
|
+
if (
|
|
108
|
+
(targetType === TARGET_ALL || targetType === TARGET_FIRST) &&
|
|
109
|
+
this.node.nodeType === ELEMENT_NODE
|
|
110
|
+
) {
|
|
111
|
+
for (const node of entryNodes) {
|
|
112
|
+
if (node !== this.node) {
|
|
113
|
+
if (targetType === TARGET_ALL || this.node.contains(node)) {
|
|
114
|
+
nodes.add(node);
|
|
115
|
+
if (targetType === TARGET_FIRST) {
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} else if (targetType === TARGET_ALL) {
|
|
122
|
+
if (nodes.size) {
|
|
123
|
+
for (const node of entryNodes) {
|
|
124
|
+
nodes.add(node);
|
|
125
|
+
}
|
|
126
|
+
sort = true;
|
|
127
|
+
} else {
|
|
128
|
+
nodes = new Set(entryNodes);
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
if (entryNodes.length) {
|
|
132
|
+
nodes.add(entryNodes[0]);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
} else if (targetType === TARGET_ALL) {
|
|
136
|
+
const newNodes = this._processComplexBranchAll(branch, entryNodes, dir);
|
|
137
|
+
if (nodes.size) {
|
|
138
|
+
for (const newNode of newNodes) {
|
|
139
|
+
nodes.add(newNode);
|
|
140
|
+
}
|
|
141
|
+
sort = true;
|
|
142
|
+
} else {
|
|
143
|
+
nodes = newNodes;
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
const matchedNode = this._processComplexBranchFirst(
|
|
147
|
+
branch,
|
|
148
|
+
entryNodes,
|
|
149
|
+
dir,
|
|
150
|
+
targetType
|
|
151
|
+
);
|
|
152
|
+
if (matchedNode) {
|
|
153
|
+
nodes.add(matchedNode);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (this.check) {
|
|
158
|
+
return {
|
|
159
|
+
ast: this.#selectorAST,
|
|
160
|
+
match: nodes.size > 0,
|
|
161
|
+
pseudoElement: this.pseudoElements.length
|
|
162
|
+
? this.pseudoElements.join('')
|
|
163
|
+
: null
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (targetType === TARGET_FIRST || targetType === TARGET_ALL) {
|
|
167
|
+
nodes.delete(this.node);
|
|
168
|
+
}
|
|
169
|
+
if ((sort || targetType === TARGET_FIRST) && nodes.size > 1) {
|
|
170
|
+
return new Set(sortNodes(nodes));
|
|
171
|
+
}
|
|
172
|
+
return nodes;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Gets AST for selector.
|
|
177
|
+
* @param {string} selector - The selector text.
|
|
178
|
+
* @returns {object} The AST for the selector.
|
|
179
|
+
*/
|
|
180
|
+
getAST = selector => {
|
|
181
|
+
return parseSelector(selector);
|
|
182
|
+
};
|
|
183
|
+
|
|
73
184
|
/**
|
|
74
185
|
* Processes selector branches into the internal AST structure.
|
|
75
186
|
* @private
|
|
@@ -1014,115 +1125,4 @@ export class Finder extends Evaluator {
|
|
|
1014
1125
|
}
|
|
1015
1126
|
return null;
|
|
1016
1127
|
};
|
|
1017
|
-
|
|
1018
|
-
/**
|
|
1019
|
-
* Finds matched nodes.
|
|
1020
|
-
* @param {string} targetType - The target type.
|
|
1021
|
-
* @returns {Set.<object>|object} A collection of matched nodes.
|
|
1022
|
-
*/
|
|
1023
|
-
find = targetType => {
|
|
1024
|
-
let collection;
|
|
1025
|
-
try {
|
|
1026
|
-
collection = this._collectNodes(targetType);
|
|
1027
|
-
} catch (e) {
|
|
1028
|
-
if (this.check) {
|
|
1029
|
-
return {
|
|
1030
|
-
ast: this.#selectorAST,
|
|
1031
|
-
match: false,
|
|
1032
|
-
pseudoElement: this.pseudoElements.length
|
|
1033
|
-
? this.pseudoElements.join('')
|
|
1034
|
-
: null
|
|
1035
|
-
};
|
|
1036
|
-
} else {
|
|
1037
|
-
throw e;
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
const [[...branches], collectedNodes] = collection;
|
|
1041
|
-
const l = branches.length;
|
|
1042
|
-
let sort = l > 1 && targetType === TARGET_ALL;
|
|
1043
|
-
let nodes = new Set();
|
|
1044
|
-
for (let i = 0; i < l; i++) {
|
|
1045
|
-
const { branch, dir, find } = branches[i];
|
|
1046
|
-
if (!branch.length || !find) {
|
|
1047
|
-
continue;
|
|
1048
|
-
}
|
|
1049
|
-
const entryNodes = collectedNodes[i];
|
|
1050
|
-
const lastIndex = branch.length - 1;
|
|
1051
|
-
if (lastIndex === 0) {
|
|
1052
|
-
if (
|
|
1053
|
-
(targetType === TARGET_ALL || targetType === TARGET_FIRST) &&
|
|
1054
|
-
this.node.nodeType === ELEMENT_NODE
|
|
1055
|
-
) {
|
|
1056
|
-
for (const node of entryNodes) {
|
|
1057
|
-
if (node !== this.node) {
|
|
1058
|
-
if (targetType === TARGET_ALL || this.node.contains(node)) {
|
|
1059
|
-
nodes.add(node);
|
|
1060
|
-
if (targetType === TARGET_FIRST) {
|
|
1061
|
-
break;
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
} else if (targetType === TARGET_ALL) {
|
|
1067
|
-
if (nodes.size) {
|
|
1068
|
-
for (const node of entryNodes) {
|
|
1069
|
-
nodes.add(node);
|
|
1070
|
-
}
|
|
1071
|
-
sort = true;
|
|
1072
|
-
} else {
|
|
1073
|
-
nodes = new Set(entryNodes);
|
|
1074
|
-
}
|
|
1075
|
-
} else {
|
|
1076
|
-
if (entryNodes.length) {
|
|
1077
|
-
nodes.add(entryNodes[0]);
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
} else if (targetType === TARGET_ALL) {
|
|
1081
|
-
const newNodes = this._processComplexBranchAll(branch, entryNodes, dir);
|
|
1082
|
-
if (nodes.size) {
|
|
1083
|
-
for (const newNode of newNodes) {
|
|
1084
|
-
nodes.add(newNode);
|
|
1085
|
-
}
|
|
1086
|
-
sort = true;
|
|
1087
|
-
} else {
|
|
1088
|
-
nodes = newNodes;
|
|
1089
|
-
}
|
|
1090
|
-
} else {
|
|
1091
|
-
const matchedNode = this._processComplexBranchFirst(
|
|
1092
|
-
branch,
|
|
1093
|
-
entryNodes,
|
|
1094
|
-
dir,
|
|
1095
|
-
targetType
|
|
1096
|
-
);
|
|
1097
|
-
if (matchedNode) {
|
|
1098
|
-
nodes.add(matchedNode);
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
if (this.check) {
|
|
1103
|
-
return {
|
|
1104
|
-
ast: this.#selectorAST,
|
|
1105
|
-
match: nodes.size > 0,
|
|
1106
|
-
pseudoElement: this.pseudoElements.length
|
|
1107
|
-
? this.pseudoElements.join('')
|
|
1108
|
-
: null
|
|
1109
|
-
};
|
|
1110
|
-
}
|
|
1111
|
-
if (targetType === TARGET_FIRST || targetType === TARGET_ALL) {
|
|
1112
|
-
nodes.delete(this.node);
|
|
1113
|
-
}
|
|
1114
|
-
if ((sort || targetType === TARGET_FIRST) && nodes.size > 1) {
|
|
1115
|
-
return new Set(sortNodes(nodes));
|
|
1116
|
-
}
|
|
1117
|
-
return nodes;
|
|
1118
|
-
};
|
|
1119
|
-
|
|
1120
|
-
/**
|
|
1121
|
-
* Gets AST for selector.
|
|
1122
|
-
* @param {string} selector - The selector text.
|
|
1123
|
-
* @returns {object} The AST for the selector.
|
|
1124
|
-
*/
|
|
1125
|
-
getAST = selector => {
|
|
1126
|
-
return parseSelector(selector);
|
|
1127
|
-
};
|
|
1128
1128
|
}
|
package/src/js/matcher.js
CHANGED
package/src/js/nwsapi.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
/* import */
|
|
8
|
-
import {
|
|
8
|
+
import { LRUCache } from 'lru-cache';
|
|
9
9
|
import { isContentEditable } from './utility.js';
|
|
10
10
|
|
|
11
11
|
/* constants */
|
|
@@ -465,13 +465,12 @@ export class Nwsapi {
|
|
|
465
465
|
constructor(window, document, cacheSize = CACHE_SIZE) {
|
|
466
466
|
this.#window = window;
|
|
467
467
|
const cacheOpt = {
|
|
468
|
-
|
|
469
|
-
strictValidate: false
|
|
468
|
+
max: cacheSize
|
|
470
469
|
};
|
|
471
|
-
this.#matchLambdas = new
|
|
472
|
-
this.#selectLambdas = new
|
|
473
|
-
this.#matchResolvers = new
|
|
474
|
-
this.#selectResolvers = new
|
|
470
|
+
this.#matchLambdas = new LRUCache(cacheOpt);
|
|
471
|
+
this.#selectLambdas = new LRUCache(cacheOpt);
|
|
472
|
+
this.#matchResolvers = new LRUCache(cacheOpt);
|
|
473
|
+
this.#selectResolvers = new LRUCache(cacheOpt);
|
|
475
474
|
this.#nthChildState = {
|
|
476
475
|
idx: 0,
|
|
477
476
|
len: 0,
|
package/src/js/parser.js
CHANGED
|
@@ -55,10 +55,12 @@ const KEYS_PS_CLASS_STATE = new Set([
|
|
|
55
55
|
'valid'
|
|
56
56
|
]);
|
|
57
57
|
const KEYS_SHADOW_HOST = new Set(['host', 'host-context']);
|
|
58
|
+
const U_FFFD = '\uFFFD';
|
|
59
|
+
|
|
60
|
+
/* regexp */
|
|
58
61
|
const REG_EMPTY_PS_FUNC =
|
|
59
62
|
/(?<=:(?:dir|has|host(?:-context)?|is|lang|not|nth-(?:last-)?(?:child|of-type)|where))\(\s+\)/g;
|
|
60
63
|
const REG_SHADOW_PS_ELEMENT = /^part|slotted$/;
|
|
61
|
-
const U_FFFD = '\uFFFD';
|
|
62
64
|
|
|
63
65
|
/**
|
|
64
66
|
* Unescapes a CSS selector string.
|
package/src/js/utility.js
CHANGED
package/types/js/constant.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export const ATRULE: "Atrule";
|
|
|
2
2
|
export const ATTR_SELECTOR: "AttributeSelector";
|
|
3
3
|
export const CLASS_SELECTOR: "ClassSelector";
|
|
4
4
|
export const COMBINATOR: "Combinator";
|
|
5
|
+
export const DIR_NEXT: "next";
|
|
6
|
+
export const DIR_PREV: "prev";
|
|
5
7
|
export const IDENT: "Identifier";
|
|
6
8
|
export const ID_SELECTOR: "IdSelector";
|
|
7
9
|
export const NOT_SUPPORTED_ERR: "NotSupportedError";
|
package/types/js/evaluator.d.ts
CHANGED
|
@@ -2,9 +2,6 @@ export class Evaluator {
|
|
|
2
2
|
constructor(window: object);
|
|
3
3
|
window: object;
|
|
4
4
|
documentCache: WeakMap<WeakKey, any>;
|
|
5
|
-
onError: (e: Error, opt?: {
|
|
6
|
-
noexcept?: boolean | undefined;
|
|
7
|
-
}) => void;
|
|
8
5
|
setup(selector: string, node: object, opt?: {
|
|
9
6
|
check?: boolean | undefined;
|
|
10
7
|
noexcept?: boolean | undefined;
|
|
@@ -19,7 +16,10 @@ export class Evaluator {
|
|
|
19
16
|
node: object | undefined;
|
|
20
17
|
pseudoElements: any[] | undefined;
|
|
21
18
|
invalidate: boolean | undefined;
|
|
22
|
-
|
|
19
|
+
onError: (e: Error, opt?: {
|
|
20
|
+
noexcept?: boolean | undefined;
|
|
21
|
+
}) => void;
|
|
22
|
+
clearResults: (all?: boolean) => void;
|
|
23
23
|
matchSelector: (ast: object, node: object, opt: object) => boolean;
|
|
24
24
|
matchLeaves: (leaves: Array<object>, node: object, opt: object) => boolean;
|
|
25
25
|
getFilterLeaves: (leaves: Array<object>) => Array<object>;
|
|
@@ -35,11 +35,14 @@ export class Evaluator {
|
|
|
35
35
|
yieldCombinatorMatches(twig: object, node: object, opt?: {
|
|
36
36
|
dir?: string | undefined;
|
|
37
37
|
}): Generator<any, void, unknown>;
|
|
38
|
+
yieldTraverseAllDescendants(baseNode: object, leaves: Array<object>, opt: object): Generator<any, void, unknown>;
|
|
39
|
+
yieldFindDescendantNodes(leaves: Array<object>, baseNode: object, opt: object): Generator<any, void, unknown>;
|
|
38
40
|
private _handleFocusEvent;
|
|
39
41
|
private _handleKeyboardEvent;
|
|
40
42
|
private _handleMouseEvent;
|
|
41
43
|
private _registerEventListeners;
|
|
42
44
|
private _getSelectorBranches;
|
|
45
|
+
private _filterNthChildOfSelectorBranches;
|
|
43
46
|
private _matchAnPlusB;
|
|
44
47
|
private _hasCombinatorMatch;
|
|
45
48
|
private _matchHasPseudoFunc;
|
|
@@ -52,7 +55,5 @@ export class Evaluator {
|
|
|
52
55
|
private _evaluateHostContextPseudo;
|
|
53
56
|
private _matchSelectorForElement;
|
|
54
57
|
private _matchSelectorForShadowRoot;
|
|
55
|
-
private _traverseAllDescendants;
|
|
56
|
-
private _findDescendantNodes;
|
|
57
58
|
#private;
|
|
58
59
|
}
|
package/types/js/finder.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export class Finder extends Evaluator {
|
|
2
|
+
find: (targetType: string) => Set<object> | object;
|
|
3
|
+
getAST: (selector: string) => object;
|
|
2
4
|
private _processSelectorBranches;
|
|
3
5
|
private _correspond;
|
|
4
6
|
private _traverseAndCollectNodes;
|
|
@@ -19,8 +21,6 @@ export class Finder extends Evaluator {
|
|
|
19
21
|
private _hasValidPathPrev;
|
|
20
22
|
private _processComplexBranchAll;
|
|
21
23
|
private _processComplexBranchFirst;
|
|
22
|
-
find: (targetType: string) => Set<object> | object;
|
|
23
|
-
getAST: (selector: string) => object;
|
|
24
24
|
#private;
|
|
25
25
|
}
|
|
26
26
|
import { Evaluator } from './evaluator.js';
|