happy-dom 17.1.1 → 17.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/cjs/PropertySymbol.cjs +6 -5
  2. package/cjs/PropertySymbol.cjs.map +1 -1
  3. package/cjs/PropertySymbol.d.ts +3 -2
  4. package/cjs/PropertySymbol.d.ts.map +1 -1
  5. package/cjs/browser/utilities/BrowserFrameNavigator.cjs +2 -1
  6. package/cjs/browser/utilities/BrowserFrameNavigator.cjs.map +1 -1
  7. package/cjs/browser/utilities/BrowserFrameNavigator.d.ts.map +1 -1
  8. package/cjs/dom/DOMStringMap.cjs +1 -1
  9. package/cjs/dom/DOMStringMap.cjs.map +1 -1
  10. package/cjs/exception/DOMExceptionNameEnum.cjs +1 -0
  11. package/cjs/exception/DOMExceptionNameEnum.cjs.map +1 -1
  12. package/cjs/exception/DOMExceptionNameEnum.d.ts +2 -1
  13. package/cjs/exception/DOMExceptionNameEnum.d.ts.map +1 -1
  14. package/cjs/fetch/AbortController.cjs.map +1 -1
  15. package/cjs/fetch/AbortController.d.ts +1 -1
  16. package/cjs/fetch/AbortController.d.ts.map +1 -1
  17. package/cjs/fetch/AbortSignal.cjs +49 -15
  18. package/cjs/fetch/AbortSignal.cjs.map +1 -1
  19. package/cjs/fetch/AbortSignal.d.ts +28 -4
  20. package/cjs/fetch/AbortSignal.d.ts.map +1 -1
  21. package/cjs/fetch/Fetch.cjs +8 -5
  22. package/cjs/fetch/Fetch.cjs.map +1 -1
  23. package/cjs/fetch/Fetch.d.ts.map +1 -1
  24. package/cjs/fetch/SyncFetch.cjs +5 -2
  25. package/cjs/fetch/SyncFetch.cjs.map +1 -1
  26. package/cjs/fetch/SyncFetch.d.ts.map +1 -1
  27. package/cjs/html-serializer/HTMLSerializer.cjs +5 -5
  28. package/cjs/html-serializer/HTMLSerializer.cjs.map +1 -1
  29. package/cjs/nodes/document/Document.cjs +12 -1
  30. package/cjs/nodes/document/Document.cjs.map +1 -1
  31. package/cjs/nodes/document/Document.d.ts.map +1 -1
  32. package/cjs/nodes/element/Element.cjs +9 -5
  33. package/cjs/nodes/element/Element.cjs.map +1 -1
  34. package/cjs/nodes/element/Element.d.ts.map +1 -1
  35. package/cjs/nodes/element/NamedNodeMap.cjs +34 -26
  36. package/cjs/nodes/element/NamedNodeMap.cjs.map +1 -1
  37. package/cjs/nodes/element/NamedNodeMap.d.ts +3 -2
  38. package/cjs/nodes/element/NamedNodeMap.d.ts.map +1 -1
  39. package/cjs/nodes/element/NamedNodeMapProxyFactory.cjs +22 -16
  40. package/cjs/nodes/element/NamedNodeMapProxyFactory.cjs.map +1 -1
  41. package/cjs/nodes/element/NamedNodeMapProxyFactory.d.ts.map +1 -1
  42. package/cjs/nodes/html-element/HTMLElement.cjs +10 -7
  43. package/cjs/nodes/html-element/HTMLElement.cjs.map +1 -1
  44. package/cjs/nodes/html-element/HTMLElement.d.ts.map +1 -1
  45. package/cjs/nodes/node/Node.cjs +3 -3
  46. package/cjs/nodes/node/Node.cjs.map +1 -1
  47. package/cjs/nodes/node/NodeUtility.cjs +7 -8
  48. package/cjs/nodes/node/NodeUtility.cjs.map +1 -1
  49. package/cjs/nodes/node/NodeUtility.d.ts.map +1 -1
  50. package/cjs/xml-parser/XMLParser.cjs +2 -2
  51. package/cjs/xml-parser/XMLParser.cjs.map +1 -1
  52. package/cjs/xml-serializer/XMLSerializer.cjs +5 -8
  53. package/cjs/xml-serializer/XMLSerializer.cjs.map +1 -1
  54. package/cjs/xml-serializer/XMLSerializer.d.ts.map +1 -1
  55. package/lib/PropertySymbol.d.ts +3 -2
  56. package/lib/PropertySymbol.d.ts.map +1 -1
  57. package/lib/PropertySymbol.js +3 -2
  58. package/lib/PropertySymbol.js.map +1 -1
  59. package/lib/browser/utilities/BrowserFrameNavigator.d.ts.map +1 -1
  60. package/lib/browser/utilities/BrowserFrameNavigator.js +2 -1
  61. package/lib/browser/utilities/BrowserFrameNavigator.js.map +1 -1
  62. package/lib/dom/DOMStringMap.js +1 -1
  63. package/lib/dom/DOMStringMap.js.map +1 -1
  64. package/lib/exception/DOMExceptionNameEnum.d.ts +2 -1
  65. package/lib/exception/DOMExceptionNameEnum.d.ts.map +1 -1
  66. package/lib/exception/DOMExceptionNameEnum.js +1 -0
  67. package/lib/exception/DOMExceptionNameEnum.js.map +1 -1
  68. package/lib/fetch/AbortController.d.ts +1 -1
  69. package/lib/fetch/AbortController.d.ts.map +1 -1
  70. package/lib/fetch/AbortController.js.map +1 -1
  71. package/lib/fetch/AbortSignal.d.ts +28 -4
  72. package/lib/fetch/AbortSignal.d.ts.map +1 -1
  73. package/lib/fetch/AbortSignal.js +49 -15
  74. package/lib/fetch/AbortSignal.js.map +1 -1
  75. package/lib/fetch/Fetch.d.ts.map +1 -1
  76. package/lib/fetch/Fetch.js +8 -5
  77. package/lib/fetch/Fetch.js.map +1 -1
  78. package/lib/fetch/SyncFetch.d.ts.map +1 -1
  79. package/lib/fetch/SyncFetch.js +5 -2
  80. package/lib/fetch/SyncFetch.js.map +1 -1
  81. package/lib/html-serializer/HTMLSerializer.js +5 -5
  82. package/lib/html-serializer/HTMLSerializer.js.map +1 -1
  83. package/lib/nodes/document/Document.d.ts.map +1 -1
  84. package/lib/nodes/document/Document.js +12 -1
  85. package/lib/nodes/document/Document.js.map +1 -1
  86. package/lib/nodes/element/Element.d.ts.map +1 -1
  87. package/lib/nodes/element/Element.js +9 -5
  88. package/lib/nodes/element/Element.js.map +1 -1
  89. package/lib/nodes/element/NamedNodeMap.d.ts +3 -2
  90. package/lib/nodes/element/NamedNodeMap.d.ts.map +1 -1
  91. package/lib/nodes/element/NamedNodeMap.js +34 -26
  92. package/lib/nodes/element/NamedNodeMap.js.map +1 -1
  93. package/lib/nodes/element/NamedNodeMapProxyFactory.d.ts.map +1 -1
  94. package/lib/nodes/element/NamedNodeMapProxyFactory.js +22 -16
  95. package/lib/nodes/element/NamedNodeMapProxyFactory.js.map +1 -1
  96. package/lib/nodes/html-element/HTMLElement.d.ts.map +1 -1
  97. package/lib/nodes/html-element/HTMLElement.js +10 -7
  98. package/lib/nodes/html-element/HTMLElement.js.map +1 -1
  99. package/lib/nodes/node/Node.js +3 -3
  100. package/lib/nodes/node/Node.js.map +1 -1
  101. package/lib/nodes/node/NodeUtility.d.ts.map +1 -1
  102. package/lib/nodes/node/NodeUtility.js +7 -8
  103. package/lib/nodes/node/NodeUtility.js.map +1 -1
  104. package/lib/xml-parser/XMLParser.js +2 -2
  105. package/lib/xml-parser/XMLParser.js.map +1 -1
  106. package/lib/xml-serializer/XMLSerializer.d.ts.map +1 -1
  107. package/lib/xml-serializer/XMLSerializer.js +5 -8
  108. package/lib/xml-serializer/XMLSerializer.js.map +1 -1
  109. package/package.json +1 -1
  110. package/src/PropertySymbol.ts +3 -2
  111. package/src/browser/utilities/BrowserFrameNavigator.ts +8 -1
  112. package/src/dom/DOMStringMap.ts +1 -1
  113. package/src/exception/DOMExceptionNameEnum.ts +2 -1
  114. package/src/fetch/AbortController.ts +1 -1
  115. package/src/fetch/AbortSignal.ts +61 -25
  116. package/src/fetch/Fetch.ts +10 -7
  117. package/src/fetch/SyncFetch.ts +6 -3
  118. package/src/html-serializer/HTMLSerializer.ts +5 -5
  119. package/src/nodes/document/Document.ts +22 -1
  120. package/src/nodes/element/Element.ts +20 -5
  121. package/src/nodes/element/NamedNodeMap.ts +42 -27
  122. package/src/nodes/element/NamedNodeMapProxyFactory.ts +22 -17
  123. package/src/nodes/html-element/HTMLElement.ts +11 -10
  124. package/src/nodes/node/Node.ts +5 -5
  125. package/src/nodes/node/NodeUtility.ts +7 -8
  126. package/src/xml-parser/XMLParser.ts +2 -2
  127. package/src/xml-serializer/XMLSerializer.ts +9 -12
@@ -14,9 +14,9 @@ export default class AbortSignal extends EventTarget {
14
14
  protected declare static [PropertySymbol.window]: BrowserWindow;
15
15
  protected declare [PropertySymbol.window]: BrowserWindow;
16
16
 
17
- // Public properties
18
- public readonly aborted: boolean = false;
19
- public readonly reason: Error | null = null;
17
+ // Internal properties
18
+ public [PropertySymbol.aborted]: boolean = false;
19
+ public [PropertySymbol.reason]: any = undefined;
20
20
 
21
21
  // Events
22
22
  public onabort: ((this: AbortSignal, event: Event) => void) | null = null;
@@ -28,9 +28,7 @@ export default class AbortSignal extends EventTarget {
28
28
  super();
29
29
 
30
30
  if (!this[PropertySymbol.window]) {
31
- throw new TypeError(
32
- `Failed to construct '${this.constructor.name}': '${this.constructor.name}' was constructed outside a Window context.`
33
- );
31
+ throw new TypeError(`Failed to construct 'AbortSignal': Illegal constructor`);
34
32
  }
35
33
  }
36
34
 
@@ -41,22 +39,59 @@ export default class AbortSignal extends EventTarget {
41
39
  return 'AbortSignal';
42
40
  }
43
41
 
42
+ /**
43
+ * Returns true if the signal has been aborted.
44
+ *
45
+ * @returns True if the signal has been aborted.
46
+ */
47
+ public get aborted(): boolean {
48
+ return this[PropertySymbol.aborted];
49
+ }
50
+
51
+ /**
52
+ * Setter for aborted. Value will be ignored as the property is read-only.
53
+ *
54
+ * @param _value Aborted.
55
+ */
56
+ public set aborted(_value: boolean) {
57
+ // Do nothing
58
+ }
59
+
60
+ /**
61
+ * Returns the reason the signal was aborted.
62
+ *
63
+ * @returns Reason.
64
+ */
65
+ public get reason(): any {
66
+ return this[PropertySymbol.reason];
67
+ }
68
+
69
+ /**
70
+ * Setter for reason. Value will be ignored as the property is read-only.
71
+ *
72
+ * @param _value Reason.
73
+ */
74
+ public set reason(_value: any) {
75
+ // Do nothing
76
+ }
77
+
44
78
  /**
45
79
  * Aborts the signal.
46
80
  *
47
81
  * @param [reason] Reason.
48
82
  */
49
- public [PropertySymbol.abort](reason?: Error): void {
83
+ public [PropertySymbol.abort](reason?: any): void {
50
84
  if (this.aborted) {
51
85
  return;
52
86
  }
53
- (<Error>this.reason) =
54
- reason ||
55
- new this[PropertySymbol.window].DOMException(
56
- 'signal is aborted without reason',
57
- DOMExceptionNameEnum.abortError
58
- );
59
- (<boolean>this.aborted) = true;
87
+ this[PropertySymbol.reason] =
88
+ reason !== undefined
89
+ ? reason
90
+ : new this[PropertySymbol.window].DOMException(
91
+ 'signal is aborted without reason',
92
+ DOMExceptionNameEnum.abortError
93
+ );
94
+ this[PropertySymbol.aborted] = true;
60
95
  this.dispatchEvent(new Event('abort'));
61
96
  }
62
97
 
@@ -75,15 +110,16 @@ export default class AbortSignal extends EventTarget {
75
110
  * @param [reason] Reason.
76
111
  * @returns AbortSignal instance.
77
112
  */
78
- public static abort(reason?: Error): AbortSignal {
113
+ public static abort(reason?: any): AbortSignal {
79
114
  const signal = new this();
80
- (<Error>signal.reason) =
81
- reason ||
82
- new this[PropertySymbol.window].DOMException(
83
- 'signal is aborted without reason',
84
- DOMExceptionNameEnum.abortError
85
- );
86
- (<boolean>signal.aborted) = true;
115
+ signal[PropertySymbol.reason] =
116
+ reason !== undefined
117
+ ? reason
118
+ : new this[PropertySymbol.window].DOMException(
119
+ 'signal is aborted without reason',
120
+ DOMExceptionNameEnum.abortError
121
+ );
122
+ signal[PropertySymbol.aborted] = true;
87
123
  return signal;
88
124
  }
89
125
 
@@ -118,8 +154,8 @@ export default class AbortSignal extends EventTarget {
118
154
  */
119
155
  public static any(signals: AbortSignal[]): AbortSignal {
120
156
  for (const signal of signals) {
121
- if (signal.aborted) {
122
- return this.abort(signal.reason);
157
+ if (signal[PropertySymbol.aborted]) {
158
+ return this.abort(signal[PropertySymbol.reason]);
123
159
  }
124
160
  }
125
161
 
@@ -135,7 +171,7 @@ export default class AbortSignal extends EventTarget {
135
171
  for (const signal of signals) {
136
172
  const handler = (): void => {
137
173
  stopListening();
138
- anySignal[PropertySymbol.abort](signal.reason);
174
+ anySignal[PropertySymbol.abort](signal[PropertySymbol.reason]);
139
175
  };
140
176
  handlers.set(signal, handler);
141
177
  signal.addEventListener('abort', handler);
@@ -134,9 +134,12 @@ export default class Fetch {
134
134
 
135
135
  FetchRequestValidationUtility.validateSchema(this.request);
136
136
 
137
- if (this.request.signal.aborted) {
138
- throw new this.#window.DOMException(
139
- 'The operation was aborted.',
137
+ if (this.request.signal[PropertySymbol.aborted]) {
138
+ if (this.request.signal[PropertySymbol.reason] !== undefined) {
139
+ throw this.request.signal[PropertySymbol.reason];
140
+ }
141
+ throw new this[PropertySymbol.window].DOMException(
142
+ 'signal is aborted without reason',
140
143
  DOMExceptionNameEnum.abortError
141
144
  );
142
145
  }
@@ -947,8 +950,8 @@ export default class Fetch {
947
950
  headers.delete('cookie2');
948
951
  }
949
952
 
950
- if (this.request.signal.aborted) {
951
- this.abort();
953
+ if (this.request.signal[PropertySymbol.aborted]) {
954
+ this.abort(this.request.signal[PropertySymbol.reason]);
952
955
  return true;
953
956
  }
954
957
 
@@ -1006,7 +1009,7 @@ export default class Fetch {
1006
1009
  *
1007
1010
  * @param reason Reason.
1008
1011
  */
1009
- private abort(reason?: Error): void {
1012
+ private abort(reason?: any): void {
1010
1013
  const error = new this.#window.DOMException(
1011
1014
  'The operation was aborted.' + (reason ? ' ' + reason.toString() : ''),
1012
1015
  DOMExceptionNameEnum.abortError
@@ -1034,7 +1037,7 @@ export default class Fetch {
1034
1037
  }
1035
1038
 
1036
1039
  if (this.reject) {
1037
- this.reject(error);
1040
+ this.reject(reason !== undefined ? reason : error);
1038
1041
  }
1039
1042
  }
1040
1043
  }
@@ -113,9 +113,12 @@ export default class SyncFetch {
113
113
 
114
114
  FetchRequestValidationUtility.validateSchema(this.request);
115
115
 
116
- if (this.request.signal.aborted) {
117
- throw new this.#window.DOMException(
118
- 'The operation was aborted.',
116
+ if (this.request.signal[PropertySymbol.aborted]) {
117
+ if (this.request.signal[PropertySymbol.reason] !== undefined) {
118
+ throw this.request.signal[PropertySymbol.reason];
119
+ }
120
+ throw new this[PropertySymbol.window].DOMException(
121
+ 'signal is aborted without reason',
119
122
  DOMExceptionNameEnum.abortError
120
123
  );
121
124
  }
@@ -143,18 +143,18 @@ export default class HTMLSerializer {
143
143
  private getAttributes(element: Element): string {
144
144
  let attributeString = '';
145
145
 
146
- const namedItems = (<Element>element)[PropertySymbol.attributes][PropertySymbol.namedItems];
146
+ const attributes = (<Element>element)[PropertySymbol.attributes][PropertySymbol.items];
147
147
 
148
- if (!namedItems.has('is') && element[PropertySymbol.isValue]) {
148
+ if (!attributes.has(':is') && element[PropertySymbol.isValue]) {
149
149
  attributeString +=
150
150
  ' is="' + XMLEncodeUtility.encodeHTMLAttributeValue(element[PropertySymbol.isValue]) + '"';
151
151
  }
152
152
 
153
- for (const attributes of namedItems.values()) {
153
+ for (const attribute of attributes.values()) {
154
154
  const escapedValue = XMLEncodeUtility.encodeHTMLAttributeValue(
155
- attributes[0][PropertySymbol.value]
155
+ attribute[PropertySymbol.value]
156
156
  );
157
- attributeString += ' ' + attributes[0][PropertySymbol.name] + '="' + escapedValue + '"';
157
+ attributeString += ' ' + attribute[PropertySymbol.name] + '="' + escapedValue + '"';
158
158
  }
159
159
 
160
160
  return attributeString;
@@ -50,6 +50,7 @@ import SVGElementConfig from '../../config/SVGElementConfig.js';
50
50
  import StringUtility from '../../utilities/StringUtility.js';
51
51
  import HTMLParser from '../../html-parser/HTMLParser.js';
52
52
  import PreloadEntry from '../../fetch/preload/PreloadEntry.js';
53
+ import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum.js';
53
54
 
54
55
  const PROCESSING_INSTRUCTION_TARGET_REGEXP = /^[a-z][a-z0-9-]+$/;
55
56
 
@@ -1222,7 +1223,17 @@ export default class Document extends Node {
1222
1223
  * @returns Attribute.
1223
1224
  */
1224
1225
  public createAttribute(qualifiedName: string): Attr {
1225
- return this.createAttributeNS(null, StringUtility.asciiLowerCase(qualifiedName));
1226
+ // We should use the NodeFactory and not the class constructor, so that owner document will be this document
1227
+ const attribute = NodeFactory.createNode(this, this[PropertySymbol.window].Attr);
1228
+
1229
+ const name = StringUtility.asciiLowerCase(qualifiedName);
1230
+ const parts = name.split(':');
1231
+
1232
+ attribute[PropertySymbol.name] = name;
1233
+ attribute[PropertySymbol.localName] = parts[1] ?? name;
1234
+ attribute[PropertySymbol.prefix] = parts[1] ? parts[0] : null;
1235
+
1236
+ return attribute;
1226
1237
  }
1227
1238
 
1228
1239
  /**
@@ -1237,11 +1248,21 @@ export default class Document extends Node {
1237
1248
  const attribute = NodeFactory.createNode(this, this[PropertySymbol.window].Attr);
1238
1249
 
1239
1250
  const parts = qualifiedName.split(':');
1251
+
1240
1252
  attribute[PropertySymbol.namespaceURI] = namespaceURI;
1241
1253
  attribute[PropertySymbol.name] = qualifiedName;
1242
1254
  attribute[PropertySymbol.localName] = parts[1] ?? qualifiedName;
1243
1255
  attribute[PropertySymbol.prefix] = parts[1] ? parts[0] : null;
1244
1256
 
1257
+ if (!namespaceURI && attribute[PropertySymbol.prefix]) {
1258
+ throw new this[PropertySymbol.window].DOMException(
1259
+ `Failed to execute 'createAttributeNS' on 'Document': The namespace URI provided ('${
1260
+ namespaceURI || ''
1261
+ }') is not valid for the qualified name provided ('${qualifiedName}').`,
1262
+ DOMExceptionNameEnum.namespaceError
1263
+ );
1264
+ }
1265
+
1245
1266
  return attribute;
1246
1267
  }
1247
1268
 
@@ -33,6 +33,7 @@ import HTMLSerializer from '../../html-serializer/HTMLSerializer.js';
33
33
  import HTMLParser from '../../html-parser/HTMLParser.js';
34
34
  import IScrollToOptions from '../../window/IScrollToOptions.js';
35
35
  import { AttributeUtility } from '../../utilities/AttributeUtility.js';
36
+ import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum.js';
36
37
 
37
38
  type InsertAdjacentPosition = 'beforebegin' | 'afterbegin' | 'beforeend' | 'afterend';
38
39
 
@@ -545,9 +546,17 @@ export default class Element
545
546
  clone[PropertySymbol.shadowRoot][PropertySymbol.host] = clone;
546
547
  }
547
548
 
548
- for (const item of this[PropertySymbol.attributes][PropertySymbol.namespaceItems].values()) {
549
- clone[PropertySymbol.attributes].setNamedItem(item.cloneNode());
550
- }
549
+ clone[PropertySymbol.attributes][PropertySymbol.itemsByNamespaceURI] = new Map(
550
+ this[PropertySymbol.attributes][PropertySymbol.itemsByNamespaceURI]
551
+ );
552
+
553
+ clone[PropertySymbol.attributes][PropertySymbol.itemsByName] = new Map(
554
+ this[PropertySymbol.attributes][PropertySymbol.itemsByName]
555
+ );
556
+
557
+ clone[PropertySymbol.attributes][PropertySymbol.items] = new Map(
558
+ this[PropertySymbol.attributes][PropertySymbol.items]
559
+ );
551
560
 
552
561
  return <Element>clone;
553
562
  }
@@ -704,6 +713,12 @@ export default class Element
704
713
  */
705
714
  public setAttributeNS(namespaceURI: string, name: string, value: string): void {
706
715
  const attribute = this[PropertySymbol.ownerDocument].createAttributeNS(namespaceURI, name);
716
+ if (!namespaceURI && attribute[PropertySymbol.prefix]) {
717
+ throw new this[PropertySymbol.window].DOMException(
718
+ `Failed to execute 'setAttributeNS' on 'Element': '' is an invalid namespace for attributes.`,
719
+ DOMExceptionNameEnum.namespaceError
720
+ );
721
+ }
707
722
  attribute[PropertySymbol.value] = String(value);
708
723
  this[PropertySymbol.attributes].setNamedItemNS(attribute);
709
724
  }
@@ -715,7 +730,7 @@ export default class Element
715
730
  */
716
731
  public getAttributeNames(): string[] {
717
732
  const names = [];
718
- for (const item of this[PropertySymbol.attributes][PropertySymbol.namespaceItems].values()) {
733
+ for (const item of this[PropertySymbol.attributes][PropertySymbol.items].values()) {
719
734
  names.push(item[PropertySymbol.name]);
720
735
  }
721
736
  return names;
@@ -799,7 +814,7 @@ export default class Element
799
814
  * @returns "true" if the element has attributes.
800
815
  */
801
816
  public hasAttributes(): boolean {
802
- return this[PropertySymbol.attributes][PropertySymbol.namespaceItems].size > 0;
817
+ return this[PropertySymbol.attributes][PropertySymbol.items].size > 0;
803
818
  }
804
819
 
805
820
  /**
@@ -14,11 +14,14 @@ import StringUtility from '../../utilities/StringUtility.js';
14
14
  export default class NamedNodeMap {
15
15
  [index: number]: Attr;
16
16
 
17
- // All items with the namespaceURI as prefix
18
- public [PropertySymbol.namespaceItems]: Map<string, Attr> = new Map();
17
+ // Items by attribute namespaceURI
18
+ public [PropertySymbol.itemsByNamespaceURI]: Map<string, Attr> = new Map();
19
19
 
20
- // Items without namespaceURI as prefix, where the HTML namespace is the default namespace
21
- public [PropertySymbol.namedItems]: Map<string, Attr[]> = new Map();
20
+ // Items by attribute name
21
+ public [PropertySymbol.itemsByName]: Map<string, Attr[]> = new Map();
22
+
23
+ // All items
24
+ public [PropertySymbol.items]: Map<string, Attr> = new Map();
22
25
 
23
26
  public declare [PropertySymbol.ownerElement]: Element;
24
27
 
@@ -37,7 +40,7 @@ export default class NamedNodeMap {
37
40
  * @returns Length.
38
41
  */
39
42
  public get length(): number {
40
- return this[PropertySymbol.namespaceItems].size;
43
+ return this[PropertySymbol.items].size;
41
44
  }
42
45
 
43
46
  /**
@@ -64,7 +67,7 @@ export default class NamedNodeMap {
64
67
  * @returns Iterator.
65
68
  */
66
69
  public [Symbol.iterator](): IterableIterator<Attr> {
67
- return this[PropertySymbol.namespaceItems].values();
70
+ return this[PropertySymbol.items].values();
68
71
  }
69
72
 
70
73
  /**
@@ -73,7 +76,7 @@ export default class NamedNodeMap {
73
76
  * @param index Index.
74
77
  */
75
78
  public item(index: number): Attr | null {
76
- const items = Array.from(this[PropertySymbol.namespaceItems].values());
79
+ const items = Array.from(this[PropertySymbol.items].values());
77
80
  return index >= 0 && items[index] ? items[index] : null;
78
81
  }
79
82
 
@@ -91,9 +94,9 @@ export default class NamedNodeMap {
91
94
  PropertySymbol.contentType
92
95
  ] === 'text/html'
93
96
  ) {
94
- return this[PropertySymbol.namedItems].get(StringUtility.asciiLowerCase(name))?.[0] || null;
97
+ return this[PropertySymbol.itemsByName].get(StringUtility.asciiLowerCase(name))?.[0] || null;
95
98
  }
96
- return this[PropertySymbol.namedItems].get(name)?.[0] || null;
99
+ return this[PropertySymbol.itemsByName].get(name)?.[0] || null;
97
100
  }
98
101
 
99
102
  /**
@@ -104,11 +107,17 @@ export default class NamedNodeMap {
104
107
  * @returns Item.
105
108
  */
106
109
  public getNamedItemNS(namespace: string, localName: string): Attr | null {
107
- if (namespace === '') {
108
- namespace = null;
110
+ const item = this[PropertySymbol.itemsByNamespaceURI].get(`${namespace || ''}:${localName}`);
111
+
112
+ // It seems like an item cant have a prefix without a namespaceURI
113
+ // E.g. element.setAttribute('ns1:key', 'value1');
114
+ // expect(element.attributes.getNamedItemNS(null, 'key')).toBeNull();
115
+
116
+ if (item && (!item[PropertySymbol.prefix] || item[PropertySymbol.namespaceURI])) {
117
+ return item;
109
118
  }
110
119
 
111
- return this[PropertySymbol.namespaceItems].get(`${namespace || ''}:${localName}`) || null;
120
+ return null;
112
121
  }
113
122
 
114
123
  /**
@@ -199,26 +208,29 @@ export default class NamedNodeMap {
199
208
  const replacedItem =
200
209
  this.getNamedItemNS(item[PropertySymbol.namespaceURI], item[PropertySymbol.localName]) ||
201
210
  null;
202
-
203
- const namedItems = this[PropertySymbol.namedItems].get(item[PropertySymbol.name]);
211
+ const itemsByName = this[PropertySymbol.itemsByName].get(item[PropertySymbol.name]);
204
212
 
205
213
  if (replacedItem === item) {
206
214
  return item;
207
215
  }
208
216
 
209
- this[PropertySymbol.namespaceItems].set(
217
+ this[PropertySymbol.itemsByNamespaceURI].set(
210
218
  `${item[PropertySymbol.namespaceURI] || ''}:${item[PropertySymbol.localName]}`,
211
219
  item
212
220
  );
221
+ this[PropertySymbol.items].set(
222
+ `${item[PropertySymbol.namespaceURI] || ''}:${item[PropertySymbol.name]}`,
223
+ item
224
+ );
213
225
 
214
- if (!namedItems?.length) {
215
- this[PropertySymbol.namedItems].set(item[PropertySymbol.name], [item]);
226
+ if (!itemsByName?.length) {
227
+ this[PropertySymbol.itemsByName].set(item[PropertySymbol.name], [item]);
216
228
  } else {
217
- const index = namedItems.indexOf(replacedItem);
229
+ const index = itemsByName.indexOf(replacedItem);
218
230
  if (index !== -1) {
219
- namedItems.splice(index, 1);
231
+ itemsByName.splice(index, 1);
220
232
  }
221
- namedItems.push(item);
233
+ itemsByName.push(item);
222
234
  }
223
235
 
224
236
  if (!ignoreListeners) {
@@ -237,19 +249,22 @@ export default class NamedNodeMap {
237
249
  public [PropertySymbol.removeNamedItem](item: Attr, ignoreListeners = false): void {
238
250
  item[PropertySymbol.ownerElement] = null;
239
251
 
240
- this[PropertySymbol.namespaceItems].delete(
252
+ this[PropertySymbol.itemsByNamespaceURI].delete(
241
253
  `${item[PropertySymbol.namespaceURI] || ''}:${item[PropertySymbol.localName]}`
242
254
  );
255
+ this[PropertySymbol.items].delete(
256
+ `${item[PropertySymbol.namespaceURI] || ''}:${item[PropertySymbol.name]}`
257
+ );
243
258
 
244
- const namedItems = this[PropertySymbol.namedItems].get(item[PropertySymbol.name]);
259
+ const itemsByName = this[PropertySymbol.itemsByName].get(item[PropertySymbol.name]);
245
260
 
246
- if (namedItems?.length) {
247
- const index = namedItems.indexOf(item);
261
+ if (itemsByName?.length) {
262
+ const index = itemsByName.indexOf(item);
248
263
  if (index !== -1) {
249
- namedItems.splice(index, 1);
264
+ itemsByName.splice(index, 1);
250
265
  }
251
- if (!namedItems.length) {
252
- this[PropertySymbol.namedItems].delete(item[PropertySymbol.name]);
266
+ if (!itemsByName.length) {
267
+ this[PropertySymbol.itemsByName].delete(item[PropertySymbol.name]);
253
268
  }
254
269
  }
255
270
 
@@ -21,7 +21,7 @@ export default class NamedNodeMapProxyFactory {
21
21
  return new Proxy<NamedNodeMap>(namedNodeMap, {
22
22
  get: (target, property) => {
23
23
  if (property === 'length') {
24
- return namedNodeMap[PropertySymbol.namedItems].size;
24
+ return namedNodeMap[PropertySymbol.items].size;
25
25
  }
26
26
  if (property in target || typeof property === 'symbol') {
27
27
  methodBinder.bind(property);
@@ -29,7 +29,7 @@ export default class NamedNodeMapProxyFactory {
29
29
  }
30
30
  const index = Number(property);
31
31
  if (!isNaN(index)) {
32
- return Array.from(namedNodeMap[PropertySymbol.namedItems].values())[index]?.[0];
32
+ return target.item(index);
33
33
  }
34
34
  return target.getNamedItem(<string>property) || undefined;
35
35
  },
@@ -57,8 +57,8 @@ export default class NamedNodeMapProxyFactory {
57
57
  return true;
58
58
  },
59
59
  ownKeys(): string[] {
60
- const keys = Array.from(namedNodeMap[PropertySymbol.namedItems].keys());
61
- for (let i = 0, max = namedNodeMap[PropertySymbol.namedItems].size; i < max; i++) {
60
+ const keys = Array.from(namedNodeMap[PropertySymbol.items].keys());
61
+ for (let i = 0, max = namedNodeMap[PropertySymbol.items].size; i < max; i++) {
62
62
  keys.push(String(i));
63
63
  }
64
64
  return keys;
@@ -68,13 +68,13 @@ export default class NamedNodeMapProxyFactory {
68
68
  return false;
69
69
  }
70
70
 
71
- if (property in target || namedNodeMap[PropertySymbol.namedItems].has(property)) {
71
+ if (property in target || namedNodeMap[PropertySymbol.items].has(property)) {
72
72
  return true;
73
73
  }
74
74
 
75
75
  const index = Number(property);
76
76
 
77
- if (!isNaN(index) && index >= 0 && index < namedNodeMap[PropertySymbol.namedItems].size) {
77
+ if (!isNaN(index) && index >= 0 && index < namedNodeMap[PropertySymbol.items].size) {
78
78
  return true;
79
79
  }
80
80
 
@@ -96,21 +96,26 @@ export default class NamedNodeMapProxyFactory {
96
96
  }
97
97
 
98
98
  const index = Number(property);
99
-
100
- if (!isNaN(index) && index >= 0 && index < namedNodeMap[PropertySymbol.namedItems].size) {
101
- return {
102
- value: Array.from(namedNodeMap[PropertySymbol.namedItems].values())[index][0],
103
- writable: false,
104
- enumerable: true,
105
- configurable: true
106
- };
99
+ if (!isNaN(index)) {
100
+ if (index >= 0) {
101
+ const itemByIndex = target.item(index);
102
+ if (itemByIndex) {
103
+ return {
104
+ value: itemByIndex,
105
+ writable: false,
106
+ enumerable: true,
107
+ configurable: true
108
+ };
109
+ }
110
+ }
111
+ return;
107
112
  }
108
113
 
109
- const namedItems = namedNodeMap[PropertySymbol.namedItems].get(<string>property);
114
+ const items = namedNodeMap[PropertySymbol.items].get(<string>property);
110
115
 
111
- if (namedItems) {
116
+ if (items) {
112
117
  return {
113
- value: namedItems[0],
118
+ value: items,
114
119
  writable: false,
115
120
  enumerable: true,
116
121
  configurable: true
@@ -649,14 +649,14 @@ export default class HTMLElement extends Element {
649
649
  newElement[PropertySymbol.isValue] = this[PropertySymbol.isValue];
650
650
  newElement[PropertySymbol.cache] = this[PropertySymbol.cache];
651
651
  newElement[PropertySymbol.affectsCache] = this[PropertySymbol.affectsCache];
652
- newElement[PropertySymbol.attributes][PropertySymbol.namedItems] =
653
- this[PropertySymbol.attributes][PropertySymbol.namedItems];
654
- newElement[PropertySymbol.attributes][PropertySymbol.namespaceItems] =
655
- this[PropertySymbol.attributes][PropertySymbol.namespaceItems];
656
-
657
- for (const attr of newElement[PropertySymbol.attributes][
658
- PropertySymbol.namespaceItems
659
- ].values()) {
652
+ newElement[PropertySymbol.attributes][PropertySymbol.itemsByNamespaceURI] =
653
+ this[PropertySymbol.attributes][PropertySymbol.itemsByNamespaceURI];
654
+ newElement[PropertySymbol.attributes][PropertySymbol.itemsByName] =
655
+ this[PropertySymbol.attributes][PropertySymbol.itemsByName];
656
+ newElement[PropertySymbol.attributes][PropertySymbol.items] =
657
+ this[PropertySymbol.attributes][PropertySymbol.items];
658
+
659
+ for (const attr of newElement[PropertySymbol.attributes][PropertySymbol.items].values()) {
660
660
  attr[PropertySymbol.ownerElement] = newElement;
661
661
  }
662
662
 
@@ -677,8 +677,9 @@ export default class HTMLElement extends Element {
677
677
  this[PropertySymbol.isValue] = null;
678
678
  this[PropertySymbol.cache] = newCache;
679
679
  this[PropertySymbol.affectsCache] = [];
680
- this[PropertySymbol.attributes][PropertySymbol.namedItems] = new Map();
681
- this[PropertySymbol.attributes][PropertySymbol.namespaceItems] = new Map();
680
+ this[PropertySymbol.attributes][PropertySymbol.itemsByNamespaceURI] = new Map();
681
+ this[PropertySymbol.attributes][PropertySymbol.itemsByName] = new Map();
682
+ this[PropertySymbol.attributes][PropertySymbol.items] = new Map();
682
683
 
683
684
  for (const node of newElement[PropertySymbol.nodeArray]) {
684
685
  node[PropertySymbol.parentNode] = newElement;
@@ -1102,13 +1102,13 @@ export default class Node extends EventTarget {
1102
1102
  /**
1103
1103
  * 5.2.1. For each attr in node2’s attribute list:
1104
1104
  */
1105
- for (const attributes of Array.from(
1106
- (<Element>node2)[PropertySymbol.attributes][PropertySymbol.namedItems].values()
1107
- )) {
1105
+ for (const attribute of (<Element>node2)[PropertySymbol.attributes][
1106
+ PropertySymbol.items
1107
+ ].values()) {
1108
1108
  /**
1109
1109
  * 5.2.1.1. If attr equals attr1, then return the result of adding DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC and DOCUMENT_POSITION_PRECEDING.
1110
1110
  */
1111
- if (NodeUtility.isEqualNode(attributes[0], attr1)) {
1111
+ if (NodeUtility.isEqualNode(attribute, attr1)) {
1112
1112
  return (
1113
1113
  Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | Node.DOCUMENT_POSITION_PRECEDING
1114
1114
  );
@@ -1117,7 +1117,7 @@ export default class Node extends EventTarget {
1117
1117
  /**
1118
1118
  * 5.2.1.2. If attr equals attr2, then return the result of adding DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC and DOCUMENT_POSITION_FOLLOWING.
1119
1119
  */
1120
- if (NodeUtility.isEqualNode(attributes[0], attr2)) {
1120
+ if (NodeUtility.isEqualNode(attribute, attr2)) {
1121
1121
  return (
1122
1122
  Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | Node.DOCUMENT_POSITION_FOLLOWING
1123
1123
  );
@@ -200,19 +200,18 @@ export default class NodeUtility {
200
200
  */
201
201
  public static attributeListsEqual(elementA: Element, elementB: Element): boolean {
202
202
  const attributesA = Array.from(
203
- elementA[PropertySymbol.attributes][PropertySymbol.namedItems].values()
203
+ elementA[PropertySymbol.attributes][PropertySymbol.items].values()
204
204
  );
205
205
  const attributesB = Array.from(
206
- elementB[PropertySymbol.attributes][PropertySymbol.namedItems].values()
206
+ elementB[PropertySymbol.attributes][PropertySymbol.items].values()
207
207
  );
208
208
  for (const attributeA of attributesA) {
209
209
  let found = false;
210
210
  for (const attributeB of attributesB) {
211
211
  if (
212
- attributeA[0][PropertySymbol.namespaceURI] ===
213
- attributeB[0][PropertySymbol.namespaceURI] &&
214
- attributeA[0].localName === attributeB[0].localName &&
215
- attributeA[0][PropertySymbol.value] === attributeB[0][PropertySymbol.value]
212
+ attributeA[PropertySymbol.namespaceURI] === attributeB[PropertySymbol.namespaceURI] &&
213
+ attributeA.localName === attributeB.localName &&
214
+ attributeA[PropertySymbol.value] === attributeB[PropertySymbol.value]
216
215
  ) {
217
216
  found = true;
218
217
  break;
@@ -258,8 +257,8 @@ export default class NodeUtility {
258
257
  elementA[PropertySymbol.namespaceURI] !== elementB[PropertySymbol.namespaceURI] ||
259
258
  elementA[PropertySymbol.prefix] !== elementB[PropertySymbol.prefix] ||
260
259
  elementA[PropertySymbol.localName] !== elementB[PropertySymbol.localName] ||
261
- elementA[PropertySymbol.attributes][PropertySymbol.namespaceItems].size !==
262
- elementB[PropertySymbol.attributes][PropertySymbol.namespaceItems].size
260
+ elementA[PropertySymbol.attributes][PropertySymbol.items].size !==
261
+ elementB[PropertySymbol.attributes][PropertySymbol.items].size
263
262
  ) {
264
263
  return false;
265
264
  }
@@ -533,8 +533,8 @@ export default class XMLParser {
533
533
  );
534
534
  this.nextElement[PropertySymbol.attributes] = attributes;
535
535
  attributes[PropertySymbol.ownerElement] = this.nextElement;
536
- for (const attr of attributes[PropertySymbol.namespaceItems].values()) {
537
- attr[PropertySymbol.ownerElement] = this.nextElement;
536
+ for (const item of attributes[PropertySymbol.items].values()) {
537
+ item[PropertySymbol.ownerElement] = this.nextElement;
538
538
  }
539
539
  } else {
540
540
  this.nextElement[PropertySymbol.namespaceURI] = value;