happy-dom 9.8.2 → 9.8.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.

Potentially problematic release.


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

Files changed (35) hide show
  1. package/lib/event/EventTarget.d.ts.map +1 -1
  2. package/lib/event/EventTarget.js +9 -0
  3. package/lib/event/EventTarget.js.map +1 -1
  4. package/lib/nodes/document/Document.d.ts +1 -1
  5. package/lib/nodes/document/Document.d.ts.map +1 -1
  6. package/lib/nodes/document/Document.js +6 -6
  7. package/lib/nodes/document/Document.js.map +1 -1
  8. package/lib/nodes/document-fragment/DocumentFragment.d.ts +1 -1
  9. package/lib/nodes/document-fragment/DocumentFragment.d.ts.map +1 -1
  10. package/lib/nodes/document-fragment/DocumentFragment.js +6 -6
  11. package/lib/nodes/document-fragment/DocumentFragment.js.map +1 -1
  12. package/lib/nodes/element/Element.js +6 -6
  13. package/lib/nodes/element/Element.js.map +1 -1
  14. package/lib/nodes/element/ElementUtility.d.ts +20 -9
  15. package/lib/nodes/element/ElementUtility.d.ts.map +1 -1
  16. package/lib/nodes/element/ElementUtility.js +54 -22
  17. package/lib/nodes/element/ElementUtility.js.map +1 -1
  18. package/lib/nodes/html-select-element/HTMLOptionsCollection.js.map +1 -1
  19. package/lib/nodes/node/Node.d.ts +1 -1
  20. package/lib/nodes/node/Node.d.ts.map +1 -1
  21. package/lib/nodes/node/Node.js +4 -111
  22. package/lib/nodes/node/Node.js.map +1 -1
  23. package/lib/nodes/node/NodeUtility.d.ts +37 -12
  24. package/lib/nodes/node/NodeUtility.d.ts.map +1 -1
  25. package/lib/nodes/node/NodeUtility.js +174 -30
  26. package/lib/nodes/node/NodeUtility.js.map +1 -1
  27. package/package.json +1 -1
  28. package/src/event/EventTarget.ts +9 -1
  29. package/src/nodes/document/Document.ts +9 -9
  30. package/src/nodes/document-fragment/DocumentFragment.ts +9 -9
  31. package/src/nodes/element/Element.ts +6 -6
  32. package/src/nodes/element/ElementUtility.ts +77 -27
  33. package/src/nodes/html-select-element/HTMLOptionsCollection.ts +1 -1
  34. package/src/nodes/node/Node.ts +5 -142
  35. package/src/nodes/node/NodeUtility.ts +239 -37
@@ -1,9 +1,6 @@
1
1
  import EventTarget from '../../event/EventTarget';
2
- import MutationRecord from '../../mutation-observer/MutationRecord';
3
- import MutationTypeEnum from '../../mutation-observer/MutationTypeEnum';
4
2
  import MutationListener from '../../mutation-observer/MutationListener';
5
3
  import INode from './INode';
6
- import DOMException from '../../exception/DOMException';
7
4
  import IDocument from '../document/IDocument';
8
5
  import IElement from '../element/IElement';
9
6
  import IHTMLBaseElement from '../html-base-element/IHTMLBaseElement';
@@ -245,8 +242,8 @@ export default class Node extends EventTarget implements INode {
245
242
  * @param otherNode Node to test with.
246
243
  * @returns "true" if this node contains the other node.
247
244
  */
248
- public contains(otherNode: INode | null): boolean {
249
- return NodeUtility.contains(this, otherNode);
245
+ public contains(otherNode: INode): boolean {
246
+ return NodeUtility.isInclusiveAncestor(this, otherNode);
250
247
  }
251
248
 
252
249
  /**
@@ -304,53 +301,7 @@ export default class Node extends EventTarget implements INode {
304
301
  * @returns Appended node.
305
302
  */
306
303
  public appendChild(node: INode): INode {
307
- if (node === this) {
308
- throw new DOMException('Not possible to append a node as a child of itself.');
309
- }
310
-
311
- // If the type is DocumentFragment, then the child nodes of if it should be moved instead of the actual node.
312
- // See: https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment
313
- if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
314
- for (const child of node.childNodes.slice()) {
315
- this.appendChild(child);
316
- }
317
- return node;
318
- }
319
-
320
- // Remove the node from its previous parent if it has any.
321
- if (node.parentNode) {
322
- const index = node.parentNode.childNodes.indexOf(node);
323
- if (index !== -1) {
324
- node.parentNode.childNodes.splice(index, 1);
325
- }
326
- }
327
-
328
- if (this.isConnected) {
329
- (this.ownerDocument || this)['_cacheID']++;
330
- }
331
-
332
- this.childNodes.push(node);
333
-
334
- (<Node>node)._connectToNode(this);
335
-
336
- // MutationObserver
337
- if (this._observers.length > 0) {
338
- const record = new MutationRecord();
339
- record.target = this;
340
- record.type = MutationTypeEnum.childList;
341
- record.addedNodes = [node];
342
-
343
- for (const observer of this._observers) {
344
- if (observer.options.subtree) {
345
- (<Node>node)._observe(observer);
346
- }
347
- if (observer.options.childList) {
348
- observer.callback([record]);
349
- }
350
- }
351
- }
352
-
353
- return node;
304
+ return NodeUtility.appendChild(this, node);
354
305
  }
355
306
 
356
307
  /**
@@ -360,36 +311,7 @@ export default class Node extends EventTarget implements INode {
360
311
  * @returns Removed node.
361
312
  */
362
313
  public removeChild(node: INode): INode {
363
- const index = this.childNodes.indexOf(node);
364
-
365
- if (index === -1) {
366
- throw new DOMException('Failed to remove node. Node is not child of parent.');
367
- }
368
-
369
- if (this.isConnected) {
370
- (this.ownerDocument || this)['_cacheID']++;
371
- }
372
-
373
- this.childNodes.splice(index, 1);
374
-
375
- (<Node>node)._connectToNode(null);
376
-
377
- // MutationObserver
378
- if (this._observers.length > 0) {
379
- const record = new MutationRecord();
380
- record.target = this;
381
- record.type = MutationTypeEnum.childList;
382
- record.removedNodes = [node];
383
-
384
- for (const observer of this._observers) {
385
- (<Node>node)._unobserve(observer);
386
- if (observer.options.childList) {
387
- observer.callback([record]);
388
- }
389
- }
390
- }
391
-
392
- return node;
314
+ return NodeUtility.removeChild(this, node);
393
315
  }
394
316
 
395
317
  /**
@@ -400,66 +322,7 @@ export default class Node extends EventTarget implements INode {
400
322
  * @returns Inserted node.
401
323
  */
402
324
  public insertBefore(newNode: INode, referenceNode: INode | null): INode {
403
- // If the type is DocumentFragment, then the child nodes of if it should be moved instead of the actual node.
404
- // See: https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment
405
- if (newNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
406
- for (const child of newNode.childNodes.slice()) {
407
- this.insertBefore(child, referenceNode);
408
- }
409
- return newNode;
410
- }
411
-
412
- if (referenceNode === null) {
413
- this.appendChild(newNode);
414
- return newNode;
415
- }
416
-
417
- if (!referenceNode) {
418
- throw new DOMException(
419
- "Failed to execute 'insertBefore' on 'Node': 2 arguments required, but only 1 present.",
420
- 'TypeError'
421
- );
422
- }
423
-
424
- if (this.childNodes.indexOf(referenceNode) === -1) {
425
- throw new DOMException(
426
- "Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node."
427
- );
428
- }
429
-
430
- if (this.isConnected) {
431
- (this.ownerDocument || this)['_cacheID']++;
432
- }
433
-
434
- if (newNode.parentNode) {
435
- const index = newNode.parentNode.childNodes.indexOf(newNode);
436
- if (index !== -1) {
437
- newNode.parentNode.childNodes.splice(index, 1);
438
- }
439
- }
440
-
441
- this.childNodes.splice(this.childNodes.indexOf(referenceNode), 0, newNode);
442
-
443
- (<Node>newNode)._connectToNode(this);
444
-
445
- // MutationObserver
446
- if (this._observers.length > 0) {
447
- const record = new MutationRecord();
448
- record.target = this;
449
- record.type = MutationTypeEnum.childList;
450
- record.addedNodes = [newNode];
451
-
452
- for (const observer of this._observers) {
453
- if (observer.options.subtree) {
454
- (<Node>newNode)._observe(observer);
455
- }
456
- if (observer.options.childList) {
457
- observer.callback([record]);
458
- }
459
- }
460
- }
461
-
462
- return newNode;
325
+ return NodeUtility.insertBefore(this, newNode, referenceNode);
463
326
  }
464
327
 
465
328
  /**
@@ -6,63 +6,229 @@ import IElement from '../element/IElement';
6
6
  import IDocumentType from '../document-type/IDocumentType';
7
7
  import IAttr from '../attr/IAttr';
8
8
  import IProcessingInstruction from '../processing-instruction/IProcessingInstruction';
9
- import IHTMLElement from '../html-element/IHTMLElement';
9
+ import IShadowRoot from '../shadow-root/IShadowRoot';
10
+ import DOMException from '../../exception/DOMException';
11
+ import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum';
12
+ import Node from './Node';
13
+ import MutationRecord from '../../mutation-observer/MutationRecord';
14
+ import MutationTypeEnum from '../../mutation-observer/MutationTypeEnum';
10
15
 
11
16
  /**
12
17
  * Node utility.
13
18
  */
14
19
  export default class NodeUtility {
15
20
  /**
16
- * Returns whether the passed node is a text node, and narrows its type.
21
+ * Append a child node to childNodes.
17
22
  *
18
- * @param node The node to be tested.
19
- * @returns "true" if the node is a text node.
23
+ * @param ancestorNode Ancestor node.
24
+ * @param node Node to append.
25
+ * @param [options] Options.
26
+ * @param [options.disableAncestorValidation] Disables validation for checking if the node is an ancestor of the ancestorNode.
27
+ * @returns Appended node.
20
28
  */
21
- public static isTextNode(node: INode | null): node is IText {
22
- return node?.nodeType === NodeTypeEnum.textNode;
29
+ public static appendChild(
30
+ ancestorNode: INode,
31
+ node: INode,
32
+ options?: { disableAncestorValidation?: boolean }
33
+ ): INode {
34
+ if (node === ancestorNode) {
35
+ throw new DOMException(
36
+ "Failed to execute 'appendChild' on 'Node': Not possible to append a node as a child of itself."
37
+ );
38
+ }
39
+
40
+ if (!options?.disableAncestorValidation && this.isInclusiveAncestor(node, ancestorNode, true)) {
41
+ throw new DOMException(
42
+ "Failed to execute 'appendChild' on 'Node': The new node is a parent of the node to insert to.",
43
+ DOMExceptionNameEnum.domException
44
+ );
45
+ }
46
+
47
+ // If the type is DocumentFragment, then the child nodes of if it should be moved instead of the actual node.
48
+ // See: https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment
49
+ if (node.nodeType === NodeTypeEnum.documentFragmentNode) {
50
+ for (const child of node.childNodes.slice()) {
51
+ ancestorNode.appendChild(child);
52
+ }
53
+ return node;
54
+ }
55
+
56
+ // Remove the node from its previous parent if it has any.
57
+ if (node.parentNode) {
58
+ const index = node.parentNode.childNodes.indexOf(node);
59
+ if (index !== -1) {
60
+ node.parentNode.childNodes.splice(index, 1);
61
+ }
62
+ }
63
+
64
+ if (ancestorNode.isConnected) {
65
+ (ancestorNode.ownerDocument || this)['_cacheID']++;
66
+ }
67
+
68
+ ancestorNode.childNodes.push(node);
69
+
70
+ (<Node>node)._connectToNode(ancestorNode);
71
+
72
+ // MutationObserver
73
+ if ((<Node>ancestorNode)._observers.length > 0) {
74
+ const record = new MutationRecord();
75
+ record.target = ancestorNode;
76
+ record.type = MutationTypeEnum.childList;
77
+ record.addedNodes = [node];
78
+
79
+ for (const observer of (<Node>ancestorNode)._observers) {
80
+ if (observer.options.subtree) {
81
+ (<Node>node)._observe(observer);
82
+ }
83
+ if (observer.options.childList) {
84
+ observer.callback([record]);
85
+ }
86
+ }
87
+ }
88
+
89
+ return node;
23
90
  }
24
91
 
25
92
  /**
26
- * Returns "true" if this node contains the other node.
93
+ * Remove Child element from childNodes array.
27
94
  *
28
- * @param rootNode Root node.
29
- * @param otherNode Node to test with.
30
- * @param [includeShadowRoots = false] Include shadow roots.
31
- * @returns "true" if this node contains the other node.
95
+ * @param ancestorNode Ancestor node.
96
+ * @param node Node to remove.
97
+ * @returns Removed node.
32
98
  */
33
- public static contains(
34
- rootNode: INode,
35
- otherNode: INode | null,
36
- includeShadowRoots = false
37
- ): boolean {
38
- if (otherNode === null) {
39
- return false;
99
+ public static removeChild(ancestorNode: INode, node: INode): INode {
100
+ const index = ancestorNode.childNodes.indexOf(node);
101
+
102
+ if (index === -1) {
103
+ throw new DOMException('Failed to remove node. Node is not child of parent.');
40
104
  }
41
105
 
42
- if (rootNode === otherNode) {
43
- return true;
106
+ if (ancestorNode.isConnected) {
107
+ (ancestorNode.ownerDocument || this)['_cacheID']++;
44
108
  }
45
109
 
46
- if (includeShadowRoots && rootNode === otherNode.ownerDocument && otherNode.isConnected) {
47
- return true;
110
+ ancestorNode.childNodes.splice(index, 1);
111
+
112
+ (<Node>node)._connectToNode(null);
113
+
114
+ // MutationObserver
115
+ if ((<Node>ancestorNode)._observers.length > 0) {
116
+ const record = new MutationRecord();
117
+ record.target = ancestorNode;
118
+ record.type = MutationTypeEnum.childList;
119
+ record.removedNodes = [node];
120
+
121
+ for (const observer of (<Node>ancestorNode)._observers) {
122
+ (<Node>node)._unobserve(observer);
123
+ if (observer.options.childList) {
124
+ observer.callback([record]);
125
+ }
126
+ }
48
127
  }
49
128
 
50
- for (const childNode of rootNode.childNodes) {
51
- if (
52
- childNode === otherNode ||
53
- this.contains(childNode, otherNode, includeShadowRoots) ||
54
- (includeShadowRoots &&
55
- (<IHTMLElement>childNode).shadowRoot &&
56
- (<IHTMLElement>childNode).shadowRoot.contains(otherNode))
57
- ) {
58
- return true;
129
+ return node;
130
+ }
131
+
132
+ /**
133
+ * Inserts a node before another.
134
+ *
135
+ * @param ancestorNode Ancestor node.
136
+ * @param newNode Node to insert.
137
+ * @param referenceNode Node to insert before.
138
+ * @param [options] Options.
139
+ * @param [options.disableAncestorValidation] Disables validation for checking if the node is an ancestor of the ancestorNode.
140
+ * @returns Inserted node.
141
+ */
142
+ public static insertBefore(
143
+ ancestorNode: INode,
144
+ newNode: INode,
145
+ referenceNode: INode | null,
146
+ options?: { disableAncestorValidation?: boolean }
147
+ ): INode {
148
+ if (
149
+ !options?.disableAncestorValidation &&
150
+ this.isInclusiveAncestor(newNode, ancestorNode, true)
151
+ ) {
152
+ throw new DOMException(
153
+ "Failed to execute 'insertBefore' on 'Node': The new node is a parent of the node to insert to.",
154
+ DOMExceptionNameEnum.domException
155
+ );
156
+ }
157
+
158
+ // If the type is DocumentFragment, then the child nodes of if it should be moved instead of the actual node.
159
+ // See: https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment
160
+ if (newNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
161
+ for (const child of newNode.childNodes.slice()) {
162
+ ancestorNode.insertBefore(child, referenceNode);
59
163
  }
164
+ return newNode;
60
165
  }
61
- return false;
166
+
167
+ if (referenceNode === null) {
168
+ ancestorNode.appendChild(newNode);
169
+ return newNode;
170
+ }
171
+
172
+ if (!referenceNode) {
173
+ throw new DOMException(
174
+ "Failed to execute 'insertBefore' on 'Node': 2 arguments required, but only 1 present.",
175
+ 'TypeError'
176
+ );
177
+ }
178
+
179
+ if (ancestorNode.childNodes.indexOf(referenceNode) === -1) {
180
+ throw new DOMException(
181
+ "Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node."
182
+ );
183
+ }
184
+
185
+ if (ancestorNode.isConnected) {
186
+ (ancestorNode.ownerDocument || this)['_cacheID']++;
187
+ }
188
+
189
+ if (newNode.parentNode) {
190
+ const index = newNode.parentNode.childNodes.indexOf(newNode);
191
+ if (index !== -1) {
192
+ newNode.parentNode.childNodes.splice(index, 1);
193
+ }
194
+ }
195
+
196
+ ancestorNode.childNodes.splice(ancestorNode.childNodes.indexOf(referenceNode), 0, newNode);
197
+
198
+ (<Node>newNode)._connectToNode(ancestorNode);
199
+
200
+ // MutationObserver
201
+ if ((<Node>ancestorNode)._observers.length > 0) {
202
+ const record = new MutationRecord();
203
+ record.target = ancestorNode;
204
+ record.type = MutationTypeEnum.childList;
205
+ record.addedNodes = [newNode];
206
+
207
+ for (const observer of (<Node>ancestorNode)._observers) {
208
+ if (observer.options.subtree) {
209
+ (<Node>newNode)._observe(observer);
210
+ }
211
+ if (observer.options.childList) {
212
+ observer.callback([record]);
213
+ }
214
+ }
215
+ }
216
+
217
+ return newNode;
62
218
  }
63
219
 
64
220
  /**
65
- * Returns boolean indicating if nodeB is an inclusive ancestor of nodeA.
221
+ * Returns whether the passed node is a text node, and narrows its type.
222
+ *
223
+ * @param node The node to be tested.
224
+ * @returns "true" if the node is a text node.
225
+ */
226
+ public static isTextNode(node: INode | null): node is IText {
227
+ return node?.nodeType === NodeTypeEnum.textNode;
228
+ }
229
+
230
+ /**
231
+ * Returns boolean indicating if "ancestorNode" is an inclusive ancestor of "referenceNode".
66
232
  *
67
233
  * Based on:
68
234
  * https://github.com/jsdom/jsdom/blob/master/lib/jsdom/living/helpers/node.js
@@ -70,16 +236,52 @@ export default class NodeUtility {
70
236
  * @see https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor
71
237
  * @param ancestorNode Ancestor node.
72
238
  * @param referenceNode Reference node.
73
- * @returns "true" if following.
239
+ * @param [includeShadowRoots = false] Include shadow roots.
240
+ * @returns "true" if inclusive ancestor.
74
241
  */
75
- public static isInclusiveAncestor(ancestorNode: INode, referenceNode: INode): boolean {
76
- let parent: INode = referenceNode;
242
+ public static isInclusiveAncestor(
243
+ ancestorNode: INode,
244
+ referenceNode: INode,
245
+ includeShadowRoots = false
246
+ ): boolean {
247
+ if (ancestorNode === null || referenceNode === null) {
248
+ return false;
249
+ }
250
+
251
+ if (ancestorNode === referenceNode) {
252
+ return true;
253
+ }
254
+
255
+ if (!ancestorNode.childNodes.length) {
256
+ return false;
257
+ }
258
+
259
+ if (includeShadowRoots && referenceNode.isConnected !== ancestorNode.isConnected) {
260
+ return false;
261
+ }
262
+
263
+ if (
264
+ includeShadowRoots &&
265
+ ancestorNode === referenceNode.ownerDocument &&
266
+ referenceNode.isConnected
267
+ ) {
268
+ return true;
269
+ }
270
+
271
+ let parent: INode = referenceNode.parentNode;
272
+
77
273
  while (parent) {
78
274
  if (ancestorNode === parent) {
79
275
  return true;
80
276
  }
81
- parent = parent.parentNode;
277
+
278
+ parent = parent.parentNode
279
+ ? parent.parentNode
280
+ : includeShadowRoots && (<IShadowRoot>parent).host
281
+ ? (<IShadowRoot>parent).host
282
+ : null;
82
283
  }
284
+
83
285
  return false;
84
286
  }
85
287