happy-dom 9.9.2 → 9.10.0

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.

Potentially problematic release.


This version of happy-dom might be problematic. Click here for more details.

Files changed (43) hide show
  1. package/lib/css/declaration/utilities/CSSStyleDeclarationElementStyle.js +1 -1
  2. package/lib/css/declaration/utilities/CSSStyleDeclarationElementStyle.js.map +1 -1
  3. package/lib/nodes/element/Element.d.ts.map +1 -1
  4. package/lib/nodes/element/Element.js +2 -15
  5. package/lib/nodes/element/Element.js.map +1 -1
  6. package/lib/nodes/node/Node.d.ts +0 -8
  7. package/lib/nodes/node/Node.d.ts.map +1 -1
  8. package/lib/nodes/node/Node.js +1 -23
  9. package/lib/nodes/node/Node.js.map +1 -1
  10. package/lib/query-selector/ISelectorAttribute.d.ts +6 -0
  11. package/lib/query-selector/ISelectorAttribute.d.ts.map +1 -0
  12. package/lib/query-selector/ISelectorAttribute.js +3 -0
  13. package/lib/query-selector/ISelectorAttribute.js.map +1 -0
  14. package/lib/query-selector/ISelectorMatch.d.ts +4 -0
  15. package/lib/query-selector/ISelectorMatch.d.ts.map +1 -0
  16. package/lib/query-selector/ISelectorMatch.js +3 -0
  17. package/lib/query-selector/ISelectorMatch.js.map +1 -0
  18. package/lib/query-selector/QuerySelector.d.ts +21 -31
  19. package/lib/query-selector/QuerySelector.d.ts.map +1 -1
  20. package/lib/query-selector/QuerySelector.js +123 -131
  21. package/lib/query-selector/QuerySelector.js.map +1 -1
  22. package/lib/query-selector/SelectorCombinatorEnum.d.ts +7 -0
  23. package/lib/query-selector/SelectorCombinatorEnum.d.ts.map +1 -0
  24. package/lib/query-selector/SelectorCombinatorEnum.js +10 -0
  25. package/lib/query-selector/SelectorCombinatorEnum.js.map +1 -0
  26. package/lib/query-selector/SelectorItem.d.ts +41 -56
  27. package/lib/query-selector/SelectorItem.d.ts.map +1 -1
  28. package/lib/query-selector/SelectorItem.js +194 -220
  29. package/lib/query-selector/SelectorItem.js.map +1 -1
  30. package/lib/query-selector/SelectorParser.d.ts +21 -0
  31. package/lib/query-selector/SelectorParser.d.ts.map +1 -0
  32. package/lib/query-selector/SelectorParser.js +154 -0
  33. package/lib/query-selector/SelectorParser.js.map +1 -0
  34. package/package.json +1 -1
  35. package/src/css/declaration/utilities/CSSStyleDeclarationElementStyle.ts +2 -2
  36. package/src/nodes/element/Element.ts +3 -17
  37. package/src/nodes/node/Node.ts +1 -25
  38. package/src/query-selector/ISelectorAttribute.ts +5 -0
  39. package/src/query-selector/ISelectorMatch.ts +3 -0
  40. package/src/query-selector/QuerySelector.ts +187 -170
  41. package/src/query-selector/SelectorCombinatorEnum.ts +7 -0
  42. package/src/query-selector/SelectorItem.ts +238 -264
  43. package/src/query-selector/SelectorParser.ts +148 -0
@@ -1,12 +1,13 @@
1
- import Element from '../nodes/element/Element';
2
1
  import IElement from '../nodes/element/IElement';
3
- import INode from '../nodes/node/INode';
4
- import Node from '../nodes/node/Node';
5
2
  import INodeList from '../nodes/node/INodeList';
6
3
  import SelectorItem from './SelectorItem';
7
4
  import NodeList from '../nodes/node/NodeList';
8
-
9
- const SELECTOR_PART_REGEXP = /(\[[^\]]+\]|[a-zA-Z0-9-_.#"*:()\]]+)|([ ,>]+)/g;
5
+ import NodeTypeEnum from '../nodes/node/NodeTypeEnum';
6
+ import SelectorCombinatorEnum from './SelectorCombinatorEnum';
7
+ import IDocument from '../nodes/document/IDocument';
8
+ import IDocumentFragment from '../nodes/document-fragment/IDocumentFragment';
9
+ import SelectorParser from './SelectorParser';
10
+ import ISelectorMatch from './ISelectorMatch';
10
11
 
11
12
  /**
12
13
  * Utility for query selection in an HTML element.
@@ -21,26 +22,36 @@ export default class QuerySelector {
21
22
  * @param selector Selector.
22
23
  * @returns HTML elements.
23
24
  */
24
- public static querySelectorAll(node: INode, selector: string): INodeList<IElement> {
25
- const matches = new NodeList<IElement>();
25
+ public static querySelectorAll(
26
+ node: IElement | IDocument | IDocumentFragment,
27
+ selector: string
28
+ ): INodeList<IElement> {
29
+ const nodeList = new NodeList<IElement>();
30
+ const allMatches = {};
26
31
 
27
32
  if (selector === '') {
28
33
  throw new Error(
29
34
  "Failed to execute 'querySelectorAll' on 'Element': The provided selector is empty."
30
35
  );
31
36
  }
37
+
32
38
  if (selector === null || selector === undefined) {
33
- return matches;
39
+ return nodeList;
34
40
  }
35
- for (const parts of this.getSelectorParts(selector)) {
36
- for (const element of this.findAll(node, [node], parts)) {
37
- if (!matches.includes(element)) {
38
- matches.push(element);
39
- }
40
- }
41
+
42
+ for (const items of SelectorParser.getSelectorGroups(selector)) {
43
+ const matches =
44
+ node.nodeType === NodeTypeEnum.elementNode
45
+ ? this.findAll(<IElement>node, [<IElement>node], items)
46
+ : this.findAll(null, node.children, items);
47
+ Object.assign(allMatches, matches);
48
+ }
49
+
50
+ for (const key of Object.keys(allMatches).sort()) {
51
+ nodeList.push(allMatches[key]);
41
52
  }
42
53
 
43
- return matches;
54
+ return nodeList;
44
55
  }
45
56
 
46
57
  /**
@@ -50,17 +61,25 @@ export default class QuerySelector {
50
61
  * @param selector Selector.
51
62
  * @returns HTML element.
52
63
  */
53
- public static querySelector(node: INode, selector: string): IElement {
64
+ public static querySelector(
65
+ node: IElement | IDocument | IDocumentFragment,
66
+ selector: string
67
+ ): IElement {
54
68
  if (selector === '') {
55
69
  throw new Error(
56
70
  "Failed to execute 'querySelector' on 'Element': The provided selector is empty."
57
71
  );
58
72
  }
73
+
59
74
  if (selector === null || selector === undefined) {
60
75
  return null;
61
76
  }
62
- for (const parts of this.getSelectorParts(selector)) {
63
- const match = this.findFirst(node, [node], parts);
77
+
78
+ for (const items of SelectorParser.getSelectorGroups(selector)) {
79
+ const match =
80
+ node.nodeType === NodeTypeEnum.elementNode
81
+ ? this.findFirst(<IElement>node, [<IElement>node], items)
82
+ : this.findFirst(null, node.children, items);
64
83
 
65
84
  if (match) {
66
85
  return match;
@@ -71,115 +90,153 @@ export default class QuerySelector {
71
90
  }
72
91
 
73
92
  /**
74
- * Checks if a node matches a selector and returns priority weight.
93
+ * Checks if an element matches a selector and returns priority weight.
75
94
  *
76
- * @param node Node to search in.
77
- * @param selector Selector.
95
+ * @param element Element to match.
96
+ * @param selector Selector to match with.
78
97
  * @returns Result.
79
98
  */
80
- public static match(node: INode, selector: string): { priorityWeight: number; matches: boolean } {
81
- for (const parts of this.getSelectorParts(selector)) {
82
- const result = this.matchesSelector(node, node, parts.reverse());
99
+ public static match(element: IElement, selector: string): ISelectorMatch | null {
100
+ for (const items of SelectorParser.getSelectorGroups(selector)) {
101
+ const result = this.matchSelector(element, element, items.reverse());
83
102
 
84
- if (result.matches) {
103
+ if (result) {
85
104
  return result;
86
105
  }
87
106
  }
88
107
 
89
- return { priorityWeight: 0, matches: false };
108
+ return null;
90
109
  }
91
110
 
92
111
  /**
93
112
  * Checks if a node matches a selector.
94
113
  *
95
- * @param targetNode Target node.
96
- * @param currentNode Current node.
97
- * @param selectorParts Selector parts.
114
+ * @param targetElement Target element.
115
+ * @param currentElement Current element.
116
+ * @param selectorItems Selector items.
98
117
  * @param [priorityWeight] Priority weight.
99
118
  * @returns Result.
100
119
  */
101
- private static matchesSelector(
102
- targetNode: INode,
103
- currentNode: INode,
104
- selectorParts: string[],
120
+ private static matchSelector(
121
+ targetElement: IElement,
122
+ currentElement: IElement,
123
+ selectorItems: SelectorItem[],
105
124
  priorityWeight = 0
106
- ): {
107
- priorityWeight: number;
108
- matches: boolean;
109
- } {
110
- const isDirectChild = selectorParts[0] === '>';
111
- if (isDirectChild) {
112
- selectorParts = selectorParts.slice(1);
113
- if (selectorParts.length === 0) {
114
- return { priorityWeight: 0, matches: false };
125
+ ): ISelectorMatch | null {
126
+ const selectorItem = selectorItems[0];
127
+ const result = selectorItem.match(currentElement);
128
+
129
+ if (result) {
130
+ if (selectorItems.length === 1) {
131
+ return {
132
+ priorityWeight: priorityWeight + result.priorityWeight
133
+ };
115
134
  }
116
- }
117
135
 
118
- if (selectorParts.length === 0) {
119
- return { priorityWeight, matches: true };
120
- }
121
-
122
- const selector = new SelectorItem(selectorParts[0]);
123
- const result = selector.match(<IElement>currentNode);
136
+ switch (selectorItem.combinator) {
137
+ case SelectorCombinatorEnum.adjacentSibling:
138
+ if (currentElement.previousElementSibling) {
139
+ const match = this.matchSelector(
140
+ targetElement,
141
+ currentElement.previousElementSibling,
142
+ selectorItems.slice(1),
143
+ priorityWeight + result.priorityWeight
144
+ );
124
145
 
125
- if (result.matches && selectorParts.length === 1) {
126
- return {
127
- priorityWeight: priorityWeight + result.priorityWeight,
128
- matches: true
129
- };
146
+ if (match) {
147
+ return match;
148
+ }
149
+ }
150
+ break;
151
+ case SelectorCombinatorEnum.child:
152
+ case SelectorCombinatorEnum.descendant:
153
+ if (currentElement.parentElement) {
154
+ const match = this.matchSelector(
155
+ targetElement,
156
+ currentElement.parentElement,
157
+ selectorItems.slice(1),
158
+ priorityWeight + result.priorityWeight
159
+ );
160
+ if (match) {
161
+ return match;
162
+ }
163
+ }
164
+ break;
165
+ }
130
166
  }
131
167
 
132
- if (!currentNode.parentElement || (targetNode === currentNode && !result.matches)) {
133
- return { priorityWeight: 0, matches: false };
168
+ if (
169
+ selectorItem.combinator === SelectorCombinatorEnum.descendant &&
170
+ targetElement !== currentElement &&
171
+ currentElement.parentElement
172
+ ) {
173
+ return this.matchSelector(
174
+ targetElement,
175
+ currentElement.parentElement,
176
+ selectorItems,
177
+ priorityWeight
178
+ );
134
179
  }
135
180
 
136
- return this.matchesSelector(
137
- isDirectChild ? currentNode.parentElement : targetNode,
138
- currentNode.parentElement,
139
- result.matches ? selectorParts.slice(1) : selectorParts,
140
- priorityWeight + result.priorityWeight
141
- );
181
+ return null;
142
182
  }
143
183
 
144
184
  /**
145
185
  * Finds elements based on a query selector for a part of a list of selectors separated with comma.
146
186
  *
147
- * @param rootNode Root node.
148
- * @param nodes Nodes.
149
- * @param selectorParts Selector parts.
150
- * @param [selectorItem] Selector item.
151
- * @returns HTML elements.
187
+ * @param rootElement Root element.
188
+ * @param children Child elements.
189
+ * @param selectorItems Selector items.
190
+ * @param [documentPosition] Document position of the element.
191
+ * @returns Document position and element map.
152
192
  */
153
193
  private static findAll(
154
- rootNode: INode,
155
- nodes: INode[],
156
- selectorParts: string[],
157
- selectorItem?: SelectorItem
158
- ): IElement[] {
159
- const isDirectChild = selectorParts[0] === '>';
160
- if (isDirectChild) {
161
- selectorParts = selectorParts.slice(1);
162
- }
163
- const selector = selectorItem || new SelectorItem(selectorParts[0]);
164
- let matched = [];
165
-
166
- for (const node of nodes) {
167
- if (node.nodeType === Node.ELEMENT_NODE) {
168
- if (selector.match(<Element>node).matches) {
169
- if (selectorParts.length === 1) {
170
- if (rootNode !== node) {
171
- matched.push(node);
172
- }
173
- } else {
174
- matched = matched.concat(
175
- this.findAll(rootNode, (<Element>node).children, selectorParts.slice(1), null)
176
- );
194
+ rootElement: IElement,
195
+ children: IElement[],
196
+ selectorItems: SelectorItem[],
197
+ documentPosition?: string
198
+ ): { [documentPosition: string]: IElement } {
199
+ const selectorItem = selectorItems[0];
200
+ const nextSelectorItem = selectorItems[1];
201
+ const matched: { [documentPosition: string]: IElement } = {};
202
+
203
+ for (let i = 0, max = children.length; i < max; i++) {
204
+ const child = children[i];
205
+ const position = (documentPosition ? documentPosition + '>' : '') + i;
206
+
207
+ if (selectorItem.match(child)) {
208
+ if (!nextSelectorItem) {
209
+ if (rootElement !== child) {
210
+ matched[position] = child;
211
+ }
212
+ } else {
213
+ switch (nextSelectorItem.combinator) {
214
+ case SelectorCombinatorEnum.adjacentSibling:
215
+ if (child.nextElementSibling) {
216
+ Object.assign(
217
+ matched,
218
+ this.findAll(
219
+ rootElement,
220
+ [child.nextElementSibling],
221
+ selectorItems.slice(1),
222
+ position
223
+ )
224
+ );
225
+ }
226
+ break;
227
+ case SelectorCombinatorEnum.descendant:
228
+ case SelectorCombinatorEnum.child:
229
+ Object.assign(
230
+ matched,
231
+ this.findAll(rootElement, child.children, selectorItems.slice(1), position)
232
+ );
233
+ break;
177
234
  }
178
235
  }
179
236
  }
180
237
 
181
- if (!isDirectChild && node['children']) {
182
- matched = matched.concat(this.findAll(rootNode, node['children'], selectorParts, selector));
238
+ if (selectorItem.combinator === SelectorCombinatorEnum.descendant && child.children.length) {
239
+ Object.assign(matched, this.findAll(rootElement, child.children, selectorItems, position));
183
240
  }
184
241
  }
185
242
 
@@ -189,99 +246,59 @@ export default class QuerySelector {
189
246
  /**
190
247
  * Finds an element based on a query selector for a part of a list of selectors separated with comma.
191
248
  *
192
- * @param rootNode
193
- * @param nodes Nodes.
194
- * @param selector Selector.
195
- * @param selectorParts
196
- * @param [selectorItem] Selector item.
249
+ * @param rootElement Root element.
250
+ * @param children Child elements.
251
+ * @param selectorItems Selector items.
197
252
  * @returns HTML element.
198
253
  */
199
254
  private static findFirst(
200
- rootNode: INode,
201
- nodes: INode[],
202
- selectorParts: string[],
203
- selectorItem?: SelectorItem
255
+ rootElement: IElement,
256
+ children: IElement[],
257
+ selectorItems: SelectorItem[]
204
258
  ): IElement {
205
- const isDirectChild = selectorParts[0] === '>';
206
- if (isDirectChild) {
207
- selectorParts = selectorParts.slice(1);
208
- }
209
- const selector = selectorItem || new SelectorItem(selectorParts[0]);
210
-
211
- for (const node of nodes) {
212
- if (node.nodeType === Node.ELEMENT_NODE && selector.match(<Element>node).matches) {
213
- if (selectorParts.length === 1) {
214
- if (rootNode !== node) {
215
- return <Element>node;
259
+ const selectorItem = selectorItems[0];
260
+ const nextSelectorItem = selectorItems[1];
261
+
262
+ for (const child of children) {
263
+ if (selectorItem.match(child)) {
264
+ if (!nextSelectorItem) {
265
+ if (rootElement !== child) {
266
+ return child;
216
267
  }
217
268
  } else {
218
- const childSelector = this.findFirst(
219
- rootNode,
220
- (<Element>node).children,
221
- selectorParts.slice(1),
222
- null
223
- );
224
- if (childSelector) {
225
- return childSelector;
269
+ switch (nextSelectorItem.combinator) {
270
+ case SelectorCombinatorEnum.adjacentSibling:
271
+ if (child.nextElementSibling) {
272
+ const match = this.findFirst(
273
+ rootElement,
274
+ [child.nextElementSibling],
275
+ selectorItems.slice(1)
276
+ );
277
+ if (match) {
278
+ return match;
279
+ }
280
+ }
281
+ break;
282
+ case SelectorCombinatorEnum.descendant:
283
+ case SelectorCombinatorEnum.child:
284
+ const match = this.findFirst(rootElement, child.children, selectorItems.slice(1));
285
+ if (match) {
286
+ return match;
287
+ }
288
+ break;
226
289
  }
227
290
  }
228
291
  }
229
292
 
230
- if (!isDirectChild && node['children']) {
231
- const childSelector = this.findFirst(rootNode, node['children'], selectorParts, selector);
293
+ if (selectorItem.combinator === SelectorCombinatorEnum.descendant && child.children.length) {
294
+ const match = this.findFirst(rootElement, child.children, selectorItems);
232
295
 
233
- if (childSelector) {
234
- return childSelector;
296
+ if (match) {
297
+ return match;
235
298
  }
236
299
  }
237
300
  }
238
301
 
239
302
  return null;
240
303
  }
241
-
242
- /**
243
- * Splits a selector string into groups and parts.
244
- *
245
- * @param selector Selector.
246
- * @returns HTML element.
247
- */
248
- private static getSelectorParts(selector: string): string[][] {
249
- if (selector === '*' || (!selector.includes(',') && !selector.includes(' '))) {
250
- return [[selector]];
251
- }
252
-
253
- const regexp = new RegExp(SELECTOR_PART_REGEXP);
254
- const groups = [];
255
- let currentSelector = '';
256
- let parts = [];
257
- let match;
258
-
259
- while ((match = regexp.exec(selector))) {
260
- if (match[2]) {
261
- const trimmed = match[2].trim();
262
-
263
- parts.push(currentSelector);
264
- currentSelector = '';
265
-
266
- if (trimmed === ',') {
267
- groups.push(parts);
268
- parts = [];
269
- } else if (trimmed === '>') {
270
- parts.push('>');
271
- }
272
- } else if (match[1]) {
273
- currentSelector += match[1];
274
- }
275
- }
276
-
277
- if (currentSelector !== '') {
278
- parts.push(currentSelector);
279
- }
280
-
281
- if (parts.length > 0) {
282
- groups.push(parts);
283
- }
284
-
285
- return groups;
286
- }
287
304
  }
@@ -0,0 +1,7 @@
1
+ enum SelectorCombinatorEnum {
2
+ descendant = 'descendant',
3
+ child = 'child',
4
+ adjacentSibling = 'adjacentSibling'
5
+ }
6
+
7
+ export default SelectorCombinatorEnum;