@dooboostore/dom-parser 1.0.1 → 1.0.2

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 (48) hide show
  1. package/dist/cjs/DomParser.js +33 -12
  2. package/dist/cjs/DomParser.js.map +2 -2
  3. package/dist/cjs/node/DocumentBase.js +4 -0
  4. package/dist/cjs/node/DocumentBase.js.map +2 -2
  5. package/dist/cjs/node/elements/Element.js.map +1 -1
  6. package/dist/cjs/node/elements/ElementBase.js +12 -2
  7. package/dist/cjs/node/elements/ElementBase.js.map +2 -2
  8. package/dist/cjs/node/elements/HTMLElement.js.map +1 -1
  9. package/dist/cjs/node/elements/HTMLElementBase.js +154 -2
  10. package/dist/cjs/node/elements/HTMLElementBase.js.map +2 -2
  11. package/dist/cjs/window/WindowBase.js +128 -7
  12. package/dist/cjs/window/WindowBase.js.map +2 -2
  13. package/dist/esm/DomParser.js +33 -12
  14. package/dist/esm/DomParser.js.map +2 -2
  15. package/dist/esm/node/DocumentBase.js +4 -0
  16. package/dist/esm/node/DocumentBase.js.map +2 -2
  17. package/dist/esm/node/elements/ElementBase.js +12 -2
  18. package/dist/esm/node/elements/ElementBase.js.map +2 -2
  19. package/dist/esm/node/elements/HTMLElementBase.js +154 -2
  20. package/dist/esm/node/elements/HTMLElementBase.js.map +2 -2
  21. package/dist/esm/window/WindowBase.js +128 -7
  22. package/dist/esm/window/WindowBase.js.map +2 -2
  23. package/dist/esm-bundle/dooboostore-dom-parser.esm.js +504 -195
  24. package/dist/esm-bundle/dooboostore-dom-parser.esm.js.map +3 -3
  25. package/dist/types/DomParser.d.ts +4 -0
  26. package/dist/types/DomParser.d.ts.map +1 -1
  27. package/dist/types/node/DocumentBase.d.ts +2 -0
  28. package/dist/types/node/DocumentBase.d.ts.map +1 -1
  29. package/dist/types/node/elements/Element.d.ts +1 -0
  30. package/dist/types/node/elements/Element.d.ts.map +1 -1
  31. package/dist/types/node/elements/ElementBase.d.ts +2 -2
  32. package/dist/types/node/elements/ElementBase.d.ts.map +1 -1
  33. package/dist/types/node/elements/HTMLElement.d.ts +32 -1
  34. package/dist/types/node/elements/HTMLElement.d.ts.map +1 -1
  35. package/dist/types/node/elements/HTMLElementBase.d.ts +11 -2
  36. package/dist/types/node/elements/HTMLElementBase.d.ts.map +1 -1
  37. package/dist/types/window/WindowBase.d.ts +12 -2
  38. package/dist/types/window/WindowBase.d.ts.map +1 -1
  39. package/dist/umd-bundle/dooboostore-dom-parser.umd.js +504 -195
  40. package/dist/umd-bundle/dooboostore-dom-parser.umd.js.map +3 -3
  41. package/package.json +1 -1
  42. package/src/DomParser.ts +457 -436
  43. package/src/node/DocumentBase.ts +7 -2
  44. package/src/node/elements/Element.ts +24 -23
  45. package/src/node/elements/ElementBase.ts +50 -41
  46. package/src/node/elements/HTMLElement.ts +36 -1
  47. package/src/node/elements/HTMLElementBase.ts +191 -5
  48. package/src/window/WindowBase.ts +1128 -919
@@ -1,1018 +1,1227 @@
1
- import { Window, Location, History, Navigator } from './Window';
2
- import { Document } from '../node/Document';
3
- import { DocumentBase } from '../node/DocumentBase';
4
- import { NodeBase } from '../node/NodeBase';
5
- import { ElementBase } from '../node/elements/ElementBase';
1
+ import {Window, Location, History, Navigator} from './Window';
2
+ import {Document} from '../node/Document';
3
+ import {DocumentBase} from '../node/DocumentBase';
4
+ import {NodeBase} from '../node/NodeBase';
5
+ import {ElementBase} from '../node/elements/ElementBase';
6
6
 
7
7
  // Import all element classes
8
- import { HTMLAnchorElement } from '../node/elements/HTMLAnchorElement';
9
- import { HTMLBodyElement } from '../node/elements/HTMLBodyElement';
10
- import { HTMLButtonElement } from '../node/elements/HTMLButtonElement';
11
- import { HTMLCanvasElement } from '../node/elements/HTMLCanvasElement';
12
- import { HTMLDivElement } from '../node/elements/HTMLDivElement';
13
- import { HTMLH1Element } from '../node/elements/HTMLH1Element';
14
- import { HTMLHeadElement } from '../node/elements/HTMLHeadElement';
15
- import { HTMLHtmlElement } from '../node/elements/HTMLHtmlElement';
16
- import { HTMLImgElement } from '../node/elements/HTMLImgElement';
17
- import { HTMLInputElement } from '../node/elements/HTMLInputElement';
18
- import { HTMLPElement } from '../node/elements/HTMLPElement';
19
- import { HTMLSpanElement } from '../node/elements/HTMLSpanElement';
20
- import { HTMLTitleElement } from '../node/elements/HTMLTitleElement';
21
- import { HTMLLinkElement } from '../node/elements/HTMLLinkElement';
22
- import { HTMLScriptElement } from '../node/elements/HTMLScriptElement';
23
- import { HTMLStyleElement } from '../node/elements/HTMLStyleElement';
24
- import { HTMLFormElement } from '../node/elements/HTMLFormElement';
25
- import { HTMLTableElement } from '../node/elements/HTMLTableElement';
26
- import { HTMLUListElement } from '../node/elements/HTMLUListElement';
27
- import { HTMLOListElement } from '../node/elements/HTMLOListElement';
28
- import { HTMLLIElement } from '../node/elements/HTMLLIElement';
29
- import { HTMLMetaElement } from '../node/elements/HTMLMetaElement';
30
- import { HTMLTemplateElement } from '../node/elements/HTMLTemplateElement';
31
- import { HTMLTheadElement } from '../node/elements/HTMLTheadElement';
32
- import { HTMLTfootElement } from '../node/elements/HTMLTfootElement';
33
- import { HTMLTrElement } from '../node/elements/HTMLTrElement';
34
- import { HTMLTdElement } from '../node/elements/HTMLTdElement';
35
- import { HTMLThElement } from '../node/elements/HTMLThElement';
36
- import { HTMLCaptionElement } from '../node/elements/HTMLCaptionElement';
37
- import { HTMLTbodyElement } from '../node/elements/HTMLTbodyElement';
8
+ import {HTMLAnchorElement} from '../node/elements/HTMLAnchorElement';
9
+ import {HTMLBodyElement} from '../node/elements/HTMLBodyElement';
10
+ import {HTMLButtonElement} from '../node/elements/HTMLButtonElement';
11
+ import {HTMLCanvasElement} from '../node/elements/HTMLCanvasElement';
12
+ import {HTMLDivElement} from '../node/elements/HTMLDivElement';
13
+ import {HTMLH1Element} from '../node/elements/HTMLH1Element';
14
+ import {HTMLHeadElement} from '../node/elements/HTMLHeadElement';
15
+ import {HTMLHtmlElement} from '../node/elements/HTMLHtmlElement';
16
+ import {HTMLImgElement} from '../node/elements/HTMLImgElement';
17
+ import {HTMLInputElement} from '../node/elements/HTMLInputElement';
18
+ import {HTMLPElement} from '../node/elements/HTMLPElement';
19
+ import {HTMLSpanElement} from '../node/elements/HTMLSpanElement';
20
+ import {HTMLTitleElement} from '../node/elements/HTMLTitleElement';
21
+ import {HTMLLinkElement} from '../node/elements/HTMLLinkElement';
22
+ import {HTMLScriptElement} from '../node/elements/HTMLScriptElement';
23
+ import {HTMLStyleElement} from '../node/elements/HTMLStyleElement';
24
+ import {HTMLFormElement} from '../node/elements/HTMLFormElement';
25
+ import {HTMLTableElement} from '../node/elements/HTMLTableElement';
26
+ import {HTMLUListElement} from '../node/elements/HTMLUListElement';
27
+ import {HTMLOListElement} from '../node/elements/HTMLOListElement';
28
+ import {HTMLLIElement} from '../node/elements/HTMLLIElement';
29
+ import {HTMLMetaElement} from '../node/elements/HTMLMetaElement';
30
+ import {HTMLTemplateElement} from '../node/elements/HTMLTemplateElement';
31
+ import {HTMLTheadElement} from '../node/elements/HTMLTheadElement';
32
+ import {HTMLTfootElement} from '../node/elements/HTMLTfootElement';
33
+ import {HTMLTrElement} from '../node/elements/HTMLTrElement';
34
+ import {HTMLTdElement} from '../node/elements/HTMLTdElement';
35
+ import {HTMLThElement} from '../node/elements/HTMLThElement';
36
+ import {HTMLCaptionElement} from '../node/elements/HTMLCaptionElement';
37
+ import {HTMLTbodyElement} from '../node/elements/HTMLTbodyElement';
38
38
 
39
39
  class LocationBase implements Location {
40
- private _href: string = 'about:blank';
41
- private _protocol: string = 'about:';
42
- private _host: string = '';
43
- private _hostname: string = '';
44
- private _port: string = '';
45
- private _pathname: string = 'blank';
46
- private _search: string = '';
47
- private _hash: string = '';
48
- private _origin: string = 'null';
49
- private urlChangeCallback?: (url: string) => void;
50
-
51
- constructor(initialUrl: string = 'about:blank', urlChangeCallback?: (url: string) => void) {
52
- this.urlChangeCallback = urlChangeCallback;
53
- this.parseUrl(initialUrl);
54
- }
55
-
56
- get href(): string {
57
- return this._href;
58
- }
59
-
60
- set href(url: string) {
61
- const oldHref = this._href;
62
- this.parseUrl(url);
63
-
64
- // Call URL change callback if URL actually changed
65
- if (this._href !== oldHref && this.urlChangeCallback) {
66
- this.urlChangeCallback(this._href);
67
- }
40
+ private _href: string = 'about:blank';
41
+ private _protocol: string = 'about:';
42
+ private _host: string = '';
43
+ private _hostname: string = '';
44
+ private _port: string = '';
45
+ private _pathname: string = 'blank';
46
+ private _search: string = '';
47
+ private _hash: string = '';
48
+ private _origin: string = 'null';
49
+ private urlChangeCallback?: (url: string) => void;
50
+
51
+ constructor(initialUrl: string = 'about:blank', urlChangeCallback?: (url: string) => void) {
52
+ this.urlChangeCallback = urlChangeCallback;
53
+ this.parseUrl(initialUrl);
54
+ }
55
+
56
+ get href(): string {
57
+ return this._href;
58
+ }
59
+
60
+ set href(url: string) {
61
+ const oldHref = this._href;
62
+ this.parseUrl(url);
63
+
64
+ // Call URL change callback if URL actually changed
65
+ if (this._href !== oldHref && this.urlChangeCallback) {
66
+ this.urlChangeCallback(this._href);
68
67
  }
68
+ }
69
69
 
70
- get protocol(): string {
71
- return this._protocol;
72
- }
73
-
74
- set protocol(value: string) {
75
- if (!value.endsWith(':')) {
76
- value += ':';
77
- }
78
- this._protocol = value;
79
- this.reconstructUrl();
80
- }
70
+ get protocol(): string {
71
+ return this._protocol;
72
+ }
81
73
 
82
- get host(): string {
83
- return this._host;
84
- }
85
-
86
- set host(value: string) {
87
- this._host = value;
88
- // Parse hostname and port from host
89
- const colonIndex = value.lastIndexOf(':');
90
- if (colonIndex !== -1 && colonIndex > value.lastIndexOf(']')) {
91
- // IPv6 addresses are enclosed in brackets, so check if colon is after the closing bracket
92
- this._hostname = value.substring(0, colonIndex);
93
- this._port = value.substring(colonIndex + 1);
94
- } else {
95
- this._hostname = value;
96
- this._port = '';
97
- }
98
- this.reconstructUrl();
74
+ set protocol(value: string) {
75
+ if (!value.endsWith(':')) {
76
+ value += ':';
99
77
  }
100
-
101
- get hostname(): string {
102
- return this._hostname;
78
+ this._protocol = value;
79
+ this.reconstructUrl();
80
+ }
81
+
82
+ get host(): string {
83
+ return this._host;
84
+ }
85
+
86
+ set host(value: string) {
87
+ this._host = value;
88
+ // Parse hostname and port from host
89
+ const colonIndex = value.lastIndexOf(':');
90
+ if (colonIndex !== -1 && colonIndex > value.lastIndexOf(']')) {
91
+ // IPv6 addresses are enclosed in brackets, so check if colon is after the closing bracket
92
+ this._hostname = value.substring(0, colonIndex);
93
+ this._port = value.substring(colonIndex + 1);
94
+ } else {
95
+ this._hostname = value;
96
+ this._port = '';
103
97
  }
104
-
105
- set hostname(value: string) {
106
- this._hostname = value;
107
- this._host = this._port ? `${value}:${this._port}` : value;
108
- this.reconstructUrl();
98
+ this.reconstructUrl();
99
+ }
100
+
101
+ get hostname(): string {
102
+ return this._hostname;
103
+ }
104
+
105
+ set hostname(value: string) {
106
+ this._hostname = value;
107
+ this._host = this._port ? `${value}:${this._port}` : value;
108
+ this.reconstructUrl();
109
+ }
110
+
111
+ get port(): string {
112
+ return this._port;
113
+ }
114
+
115
+ set port(value: string) {
116
+ this._port = value;
117
+ this._host = value ? `${this._hostname}:${value}` : this._hostname;
118
+ this.reconstructUrl();
119
+ }
120
+
121
+ get pathname(): string {
122
+ return this._pathname;
123
+ }
124
+
125
+ set pathname(value: string) {
126
+ if (!value.startsWith('/')) {
127
+ value = '/' + value;
109
128
  }
129
+ this._pathname = value;
130
+ this.reconstructUrl();
131
+ }
110
132
 
111
- get port(): string {
112
- return this._port;
113
- }
133
+ get search(): string {
134
+ return this._search;
135
+ }
114
136
 
115
- set port(value: string) {
116
- this._port = value;
117
- this._host = value ? `${this._hostname}:${value}` : this._hostname;
118
- this.reconstructUrl();
137
+ set search(value: string) {
138
+ if (value && !value.startsWith('?')) {
139
+ value = '?' + value;
119
140
  }
141
+ this._search = value;
142
+ this.reconstructUrl();
143
+ }
120
144
 
121
- get pathname(): string {
122
- return this._pathname;
123
- }
145
+ get hash(): string {
146
+ return this._hash;
147
+ }
124
148
 
125
- set pathname(value: string) {
126
- if (!value.startsWith('/')) {
127
- value = '/' + value;
128
- }
129
- this._pathname = value;
130
- this.reconstructUrl();
149
+ set hash(value: string) {
150
+ if (value && !value.startsWith('#')) {
151
+ value = '#' + value;
131
152
  }
153
+ this._hash = value;
154
+ this.reconstructUrl();
155
+ }
132
156
 
133
- get search(): string {
134
- return this._search;
135
- }
157
+ get origin(): string {
158
+ return this._origin;
159
+ }
136
160
 
137
- set search(value: string) {
138
- if (value && !value.startsWith('?')) {
139
- value = '?' + value;
140
- }
141
- this._search = value;
142
- this.reconstructUrl();
143
- }
161
+ assign(url: string): void {
162
+ const oldHref = this._href;
163
+ this.parseUrl(url);
144
164
 
145
- get hash(): string {
146
- return this._hash;
165
+ // Call URL change callback if URL actually changed
166
+ if (this._href !== oldHref && this.urlChangeCallback) {
167
+ this.urlChangeCallback(this._href);
147
168
  }
169
+ }
148
170
 
149
- set hash(value: string) {
150
- if (value && !value.startsWith('#')) {
151
- value = '#' + value;
152
- }
153
- this._hash = value;
154
- this.reconstructUrl();
155
- }
171
+ replace(url: string): void {
172
+ const oldHref = this._href;
173
+ this.parseUrl(url);
156
174
 
157
- get origin(): string {
158
- return this._origin;
175
+ // Call URL change callback if URL actually changed
176
+ if (this._href !== oldHref && this.urlChangeCallback) {
177
+ this.urlChangeCallback(this._href);
159
178
  }
160
-
161
- assign(url: string): void {
162
- const oldHref = this._href;
163
- this.parseUrl(url);
164
-
165
- // Call URL change callback if URL actually changed
166
- if (this._href !== oldHref && this.urlChangeCallback) {
167
- this.urlChangeCallback(this._href);
168
- }
179
+ }
180
+
181
+ reload(forcedReload?: boolean): void {
182
+ // No-op in server-side environment
183
+ // In a real browser, this would reload the page
184
+ }
185
+
186
+ toString(): string {
187
+ return this._href;
188
+ }
189
+
190
+ private parseUrl(url: string): void {
191
+ try {
192
+ // Handle relative URLs by creating a base URL
193
+ let parsedUrl: URL;
194
+
195
+ if (url.startsWith('//')) {
196
+ // Protocol-relative URL
197
+ parsedUrl = new URL(this._protocol + url);
198
+ } else if (url.startsWith('/')) {
199
+ // Absolute path
200
+ parsedUrl = new URL(url, `${this._protocol}//${this._host}`);
201
+ } else if (url.includes('://')) {
202
+ // Absolute URL
203
+ parsedUrl = new URL(url);
204
+ } else {
205
+ // Relative URL
206
+ const base = `${this._protocol}//${this._host}${this._pathname}`;
207
+ parsedUrl = new URL(url, base);
208
+ }
209
+
210
+ this._href = parsedUrl.href;
211
+ this._protocol = parsedUrl.protocol;
212
+ this._host = parsedUrl.host;
213
+ this._hostname = parsedUrl.hostname;
214
+ this._port = parsedUrl.port;
215
+ this._pathname = parsedUrl.pathname;
216
+ this._search = parsedUrl.search;
217
+ this._hash = parsedUrl.hash;
218
+ this._origin = parsedUrl.origin;
219
+ } catch (e) {
220
+ // Invalid URL, handle special cases
221
+ if (url === 'about:blank') {
222
+ this._href = 'about:blank';
223
+ this._protocol = 'about:';
224
+ this._host = '';
225
+ this._hostname = '';
226
+ this._port = '';
227
+ this._pathname = 'blank';
228
+ this._search = '';
229
+ this._hash = '';
230
+ this._origin = 'null';
231
+ }
232
+ // For other invalid URLs, keep current values
169
233
  }
234
+ }
170
235
 
171
- replace(url: string): void {
172
- const oldHref = this._href;
173
- this.parseUrl(url);
236
+ private reconstructUrl(): void {
237
+ try {
238
+ // Reconstruct the URL from components
239
+ let url = this._protocol;
174
240
 
175
- // Call URL change callback if URL actually changed
176
- if (this._href !== oldHref && this.urlChangeCallback) {
177
- this.urlChangeCallback(this._href);
178
- }
179
- }
180
-
181
- reload(forcedReload?: boolean): void {
182
- // No-op in server-side environment
183
- // In a real browser, this would reload the page
184
- }
185
-
186
- toString(): string {
187
- return this._href;
188
- }
189
-
190
- private parseUrl(url: string): void {
191
- try {
192
- // Handle relative URLs by creating a base URL
193
- let parsedUrl: URL;
194
-
195
- if (url.startsWith('//')) {
196
- // Protocol-relative URL
197
- parsedUrl = new URL(this._protocol + url);
198
- } else if (url.startsWith('/')) {
199
- // Absolute path
200
- parsedUrl = new URL(url, `${this._protocol}//${this._host}`);
201
- } else if (url.includes('://')) {
202
- // Absolute URL
203
- parsedUrl = new URL(url);
204
- } else {
205
- // Relative URL
206
- const base = `${this._protocol}//${this._host}${this._pathname}`;
207
- parsedUrl = new URL(url, base);
208
- }
209
-
210
- this._href = parsedUrl.href;
211
- this._protocol = parsedUrl.protocol;
212
- this._host = parsedUrl.host;
213
- this._hostname = parsedUrl.hostname;
214
- this._port = parsedUrl.port;
215
- this._pathname = parsedUrl.pathname;
216
- this._search = parsedUrl.search;
217
- this._hash = parsedUrl.hash;
218
- this._origin = parsedUrl.origin;
219
- } catch (e) {
220
- // Invalid URL, handle special cases
221
- if (url === 'about:blank') {
222
- this._href = 'about:blank';
223
- this._protocol = 'about:';
224
- this._host = '';
225
- this._hostname = '';
226
- this._port = '';
227
- this._pathname = 'blank';
228
- this._search = '';
229
- this._hash = '';
230
- this._origin = 'null';
231
- }
232
- // For other invalid URLs, keep current values
233
- }
234
- }
235
-
236
- private reconstructUrl(): void {
237
- try {
238
- // Reconstruct the URL from components
239
- let url = this._protocol;
240
-
241
- if (this._protocol !== 'about:' && this._protocol !== 'data:') {
242
- url += '//';
243
- if (this._host) {
244
- url += this._host;
245
- }
246
- }
247
-
248
- url += this._pathname;
249
- url += this._search;
250
- url += this._hash;
251
-
252
- // Validate the reconstructed URL
253
- const testUrl = new URL(url);
254
- this._href = testUrl.href;
255
- this._origin = testUrl.origin;
256
- } catch (e) {
257
- // If reconstruction fails, keep the current href
241
+ if (this._protocol !== 'about:' && this._protocol !== 'data:') {
242
+ url += '//';
243
+ if (this._host) {
244
+ url += this._host;
258
245
  }
246
+ }
247
+
248
+ url += this._pathname;
249
+ url += this._search;
250
+ url += this._hash;
251
+
252
+ // Validate the reconstructed URL
253
+ const testUrl = new URL(url);
254
+ this._href = testUrl.href;
255
+ this._origin = testUrl.origin;
256
+ } catch (e) {
257
+ // If reconstruction fails, keep the current href
259
258
  }
259
+ }
260
260
  }
261
261
 
262
262
  class HistoryBase implements History {
263
- length: number = 1;
264
- state: any = null;
265
- private window: WindowBase;
266
- private historyStack: Array<{ state: any; title: string; url?: string }> = [];
267
- private currentIndex: number = -1;
268
-
269
- constructor(window: WindowBase) {
270
- this.window = window;
263
+ length: number = 1;
264
+ state: any = null;
265
+ private window: WindowBase;
266
+ private historyStack: Array<{ state: any; title: string; url?: string }> = [];
267
+ private currentIndex: number = -1;
268
+
269
+ constructor(window: WindowBase) {
270
+ this.window = window;
271
+ }
272
+
273
+ back(): void {
274
+ this.go(-1);
275
+ }
276
+
277
+ forward(): void {
278
+ this.go(1);
279
+ }
280
+
281
+ go(delta?: number): void {
282
+ if (!delta || this.historyStack.length === 0) return;
283
+
284
+ const newIndex = this.currentIndex + delta;
285
+ if (newIndex >= 0 && newIndex < this.historyStack.length) {
286
+ this.currentIndex = newIndex;
287
+ const entry = this.historyStack[this.currentIndex];
288
+ this.state = entry.state;
289
+
290
+ // Update location if URL is provided
291
+ if (entry.url && this.window.location) {
292
+ (this.window.location as any)._href = entry.url;
293
+ (this.window.location as any).parseUrl(entry.url);
294
+ }
295
+
296
+ // Dispatch popstate event
297
+ (this.window as any).dispatchPopStateEvent(entry.state, entry.url);
271
298
  }
272
-
273
- back(): void {
274
- this.go(-1);
299
+ }
300
+
301
+ pushState(data: any, title: string, url?: string): void {
302
+ // Remove any forward history when pushing new state
303
+ this.historyStack = this.historyStack.slice(0, this.currentIndex + 1);
304
+
305
+ // Add new state
306
+ this.historyStack.push({state: data, title, url});
307
+ this.currentIndex = this.historyStack.length - 1;
308
+ this.state = data;
309
+ this.length = this.historyStack.length;
310
+
311
+ // Update location if URL is provided
312
+ if (url && this.window.location) {
313
+ const oldHref = (this.window.location as any)._href;
314
+ (this.window.location as any)._href = url;
315
+ (this.window.location as any).parseUrl(url);
316
+
317
+ // Trigger URL change callback for dynamic HTML loading
318
+ if (url !== oldHref && (this.window.location as any).urlChangeCallback) {
319
+ (this.window.location as any).urlChangeCallback(url);
320
+ }
275
321
  }
276
322
 
277
- forward(): void {
278
- this.go(1);
323
+ // Note: pushState does NOT dispatch popstate event (per HTML spec)
324
+ }
325
+
326
+ replaceState(data: any, title: string, url?: string): void {
327
+ if (this.currentIndex >= 0 && this.currentIndex < this.historyStack.length) {
328
+ // Replace current state
329
+ this.historyStack[this.currentIndex] = {state: data, title, url};
330
+ } else {
331
+ // No current state, create one
332
+ this.historyStack = [{state: data, title, url}];
333
+ this.currentIndex = 0;
334
+ this.length = 1;
279
335
  }
280
336
 
281
- go(delta?: number): void {
282
- if (!delta || this.historyStack.length === 0) return;
283
-
284
- const newIndex = this.currentIndex + delta;
285
- if (newIndex >= 0 && newIndex < this.historyStack.length) {
286
- this.currentIndex = newIndex;
287
- const entry = this.historyStack[this.currentIndex];
288
- this.state = entry.state;
337
+ this.state = data;
289
338
 
290
- // Update location if URL is provided
291
- if (entry.url && this.window.location) {
292
- (this.window.location as any)._href = entry.url;
293
- (this.window.location as any).parseUrl(entry.url);
294
- }
339
+ // Update location if URL is provided
340
+ if (url && this.window.location) {
341
+ const oldHref = (this.window.location as any)._href;
342
+ (this.window.location as any)._href = url;
343
+ (this.window.location as any).parseUrl(url);
295
344
 
296
- // Dispatch popstate event
297
- (this.window as any).dispatchPopStateEvent(entry.state, entry.url);
298
- }
345
+ // Trigger URL change callback for dynamic HTML loading
346
+ if (url !== oldHref && (this.window.location as any).urlChangeCallback) {
347
+ (this.window.location as any).urlChangeCallback(url);
348
+ }
299
349
  }
300
350
 
301
- pushState(data: any, title: string, url?: string): void {
302
- // Remove any forward history when pushing new state
303
- this.historyStack = this.historyStack.slice(0, this.currentIndex + 1);
304
-
305
- // Add new state
306
- this.historyStack.push({ state: data, title, url });
307
- this.currentIndex = this.historyStack.length - 1;
308
- this.state = data;
309
- this.length = this.historyStack.length;
310
-
311
- // Update location if URL is provided
312
- if (url && this.window.location) {
313
- const oldHref = (this.window.location as any)._href;
314
- (this.window.location as any)._href = url;
315
- (this.window.location as any).parseUrl(url);
316
-
317
- // Trigger URL change callback for dynamic HTML loading
318
- if (url !== oldHref && (this.window.location as any).urlChangeCallback) {
319
- (this.window.location as any).urlChangeCallback(url);
320
- }
321
- }
322
-
323
- // Note: pushState does NOT dispatch popstate event (per HTML spec)
324
- }
325
-
326
- replaceState(data: any, title: string, url?: string): void {
327
- if (this.currentIndex >= 0 && this.currentIndex < this.historyStack.length) {
328
- // Replace current state
329
- this.historyStack[this.currentIndex] = { state: data, title, url };
330
- } else {
331
- // No current state, create one
332
- this.historyStack = [{ state: data, title, url }];
333
- this.currentIndex = 0;
334
- this.length = 1;
335
- }
336
-
337
- this.state = data;
338
-
339
- // Update location if URL is provided
340
- if (url && this.window.location) {
341
- const oldHref = (this.window.location as any)._href;
342
- (this.window.location as any)._href = url;
343
- (this.window.location as any).parseUrl(url);
344
-
345
- // Trigger URL change callback for dynamic HTML loading
346
- if (url !== oldHref && (this.window.location as any).urlChangeCallback) {
347
- (this.window.location as any).urlChangeCallback(url);
348
- }
349
- }
350
-
351
- // Note: replaceState does NOT dispatch popstate event (per HTML spec)
352
- }
351
+ // Note: replaceState does NOT dispatch popstate event (per HTML spec)
352
+ }
353
353
  }
354
354
 
355
355
  class NavigatorBase implements Navigator {
356
- userAgent: string = 'Mozilla/5.0 (Server-Side Rendering)';
357
- language: string = 'en-US';
358
- languages: readonly string[] = ['en-US', 'en'];
359
- platform: string = 'Server';
360
- cookieEnabled: boolean = false;
361
- onLine: boolean = true;
356
+ userAgent: string = 'Mozilla/5.0 (Server-Side Rendering)';
357
+ language: string = 'en-US';
358
+ languages: readonly string[] = ['en-US', 'en'];
359
+ platform: string = 'Server';
360
+ cookieEnabled: boolean = false;
361
+ onLine: boolean = true;
362
362
  }
363
363
 
364
364
  // Simple event system for WindowBase
365
365
  interface EventListener {
366
- type: string;
367
- listener: (event: any) => void;
368
- options?: boolean | AddEventListenerOptions;
366
+ type: string;
367
+ listener: (event: any) => void;
368
+ options?: boolean | AddEventListenerOptions;
369
369
  }
370
370
 
371
371
  interface PopStateEvent {
372
- type: 'popstate';
373
- state: any;
374
- url?: string;
372
+ type: 'popstate';
373
+ state: any;
374
+ url?: string;
375
375
  }
376
376
 
377
377
  export class WindowBase {
378
- // Suppress type errors for complex event handler types
379
- [key: string]: any;
380
-
381
- // Event system
382
- private _eventListeners: EventListener[] = [];
383
- // Properties
384
- readonly clientInformation: Navigator;
385
- readonly closed: boolean = false;
386
- readonly cookieStore: CookieStore = {} as CookieStore;
387
- readonly customElements: CustomElementRegistry = {} as CustomElementRegistry;
388
- readonly devicePixelRatio: number = 1;
389
- readonly document: Document;
390
- readonly event: Event | undefined = undefined;
391
- readonly external: External = {} as External;
392
- readonly frameElement: Element | null = null;
393
- readonly frames: WindowProxy = {} as WindowProxy;
394
- readonly history: History;
395
- readonly innerHeight: number = 768;
396
- readonly innerWidth: number = 1024;
397
- readonly length: number = 0;
398
- readonly locationbar: BarProp = {} as BarProp;
399
- readonly menubar: BarProp = {} as BarProp;
400
- name: string = '';
401
- readonly navigator: Navigator;
402
- ondevicemotion: ((this: Window, ev: DeviceMotionEvent) => any) | null = null;
403
- ondeviceorientation: ((this: Window, ev: DeviceOrientationEvent) => any) | null = null;
404
- ondeviceorientationabsolute: ((this: Window, ev: DeviceOrientationEvent) => any) | null = null;
405
- onorientationchange: ((this: Window, ev: Event) => any) | null = null;
406
- opener: any = null;
407
- readonly orientation: number = 0;
408
- readonly originAgentCluster: boolean = false;
409
- readonly outerHeight: number = 768;
410
- readonly outerWidth: number = 1024;
411
- readonly pageXOffset: number = 0;
412
- readonly pageYOffset: number = 0;
413
- readonly parent: WindowProxy = {} as WindowProxy;
414
- readonly personalbar: BarProp = {} as BarProp;
415
- readonly screen: Screen = {} as Screen;
416
- readonly screenLeft: number = 0;
417
- readonly screenTop: number = 0;
418
- readonly screenX: number = 0;
419
- readonly screenY: number = 0;
420
- readonly scrollX: number = 0;
421
- readonly scrollY: number = 0;
422
- readonly scrollbars: BarProp = {} as BarProp;
423
- readonly self: Window & typeof globalThis = this as any;
424
- readonly speechSynthesis: SpeechSynthesis = {} as SpeechSynthesis;
425
- status: string = '';
426
- readonly statusbar: BarProp = {} as BarProp;
427
- readonly toolbar: BarProp = {} as BarProp;
428
- readonly top: WindowProxy | null = null;
429
- readonly visualViewport: VisualViewport | null = null;
430
- readonly window: Window & typeof globalThis = this as any;
431
-
432
- // Location property with getter/setter
433
- private _location: Location;
434
- get location(): Location {
435
- return this._location;
436
- }
437
- set location(href: string | Location) {
438
- if (typeof href === 'string') {
439
- this._location.href = href;
440
- } else {
441
- this._location.href = href.href;
442
- }
378
+ // Suppress type errors for complex event handler types
379
+ [key: string]: any;
380
+
381
+ // Event system
382
+ private _eventListeners: EventListener[] = [];
383
+ // Timers and intervals tracking
384
+ private _timers: Set<number> = new Set();
385
+ private _intervals: Set<number> = new Set();
386
+ private _animationFrames: Set<number> = new Set();
387
+ // Properties
388
+ readonly clientInformation: Navigator;
389
+ private _closed: boolean = false;
390
+
391
+ get closed(): boolean {
392
+ return this._closed;
393
+ }
394
+
395
+ readonly cookieStore: CookieStore = {} as CookieStore;
396
+ readonly customElements: CustomElementRegistry = {} as CustomElementRegistry;
397
+ readonly devicePixelRatio: number = 1;
398
+ readonly document: Document;
399
+ readonly event: Event | undefined = undefined;
400
+ readonly external: External = {} as External;
401
+ readonly frameElement: Element | null = null;
402
+ readonly frames: WindowProxy = {} as WindowProxy;
403
+ readonly history: History;
404
+ readonly innerHeight: number = 768;
405
+ readonly innerWidth: number = 1024;
406
+ readonly length: number = 0;
407
+ readonly locationbar: BarProp = {} as BarProp;
408
+ readonly menubar: BarProp = {} as BarProp;
409
+ name: string = '';
410
+ readonly navigator: Navigator;
411
+ ondevicemotion: ((this: Window, ev: DeviceMotionEvent) => any) | null = null;
412
+ ondeviceorientation: ((this: Window, ev: DeviceOrientationEvent) => any) | null = null;
413
+ ondeviceorientationabsolute: ((this: Window, ev: DeviceOrientationEvent) => any) | null = null;
414
+ onorientationchange: ((this: Window, ev: Event) => any) | null = null;
415
+ opener: any = null;
416
+ readonly orientation: number = 0;
417
+ readonly originAgentCluster: boolean = false;
418
+ readonly outerHeight: number = 768;
419
+ readonly outerWidth: number = 1024;
420
+ readonly pageXOffset: number = 0;
421
+ readonly pageYOffset: number = 0;
422
+ readonly parent: WindowProxy = {} as WindowProxy;
423
+ readonly personalbar: BarProp = {} as BarProp;
424
+ readonly screen: Screen = {} as Screen;
425
+ readonly screenLeft: number = 0;
426
+ readonly screenTop: number = 0;
427
+ readonly screenX: number = 0;
428
+ readonly screenY: number = 0;
429
+ readonly scrollX: number = 0;
430
+ readonly scrollY: number = 0;
431
+ readonly scrollbars: BarProp = {} as BarProp;
432
+ readonly self: Window & typeof globalThis = this as any;
433
+ readonly speechSynthesis: SpeechSynthesis = {} as SpeechSynthesis;
434
+ status: string = '';
435
+ readonly statusbar: BarProp = {} as BarProp;
436
+ readonly toolbar: BarProp = {} as BarProp;
437
+ readonly top: WindowProxy | null = null;
438
+ readonly visualViewport: VisualViewport | null = null;
439
+ readonly window: Window & typeof globalThis = this as any;
440
+
441
+ // Location property with getter/setter
442
+ private _location: Location;
443
+ get location(): Location {
444
+ return this._location;
445
+ }
446
+
447
+ set location(href: string | Location) {
448
+ if (typeof href === 'string') {
449
+ this._location.href = href;
450
+ } else {
451
+ this._location.href = href.href;
452
+ }
453
+ }
454
+
455
+ // Global constructors
456
+ Node = NodeBase;
457
+ Element = ElementBase;
458
+ HTMLElement = ElementBase;
459
+ Event = class Event {
460
+ };
461
+ PopStateEvent = class PopStateEvent extends this.Event {
462
+ };
463
+ IntersectionObserver = class IntersectionObserver {
464
+ };
465
+ NodeFilter = class NodeFilter {
466
+ };
467
+ DocumentFragment = class DocumentFragment {
468
+ };
469
+ HTMLMetaElement = HTMLMetaElement;
470
+ HTMLCanvasElement = HTMLCanvasElement;
471
+ CanvasRenderingContext2D = class CanvasRenderingContext2D {
472
+ };
473
+ CanvasPattern = class CanvasPattern {
474
+ };
475
+ CanvasGradient = class CanvasGradient {
476
+ };
477
+ Path2D = class Path2D {
478
+ };
479
+ ImageData = class ImageData {
480
+ };
481
+
482
+ // HTML element constructors
483
+ HTMLAnchorElement = HTMLAnchorElement;
484
+ HTMLBodyElement = HTMLBodyElement;
485
+ HTMLButtonElement = HTMLButtonElement;
486
+ HTMLDivElement = HTMLDivElement;
487
+ HTMLH1Element = HTMLH1Element;
488
+ HTMLHeadElement = HTMLHeadElement;
489
+ HTMLHtmlElement = HTMLHtmlElement;
490
+ HTMLImgElement = HTMLImgElement;
491
+ HTMLInputElement = HTMLInputElement;
492
+ HTMLPElement = HTMLPElement;
493
+ HTMLSpanElement = HTMLSpanElement;
494
+ HTMLTitleElement = HTMLTitleElement;
495
+ HTMLLinkElement = HTMLLinkElement;
496
+ HTMLScriptElement = HTMLScriptElement;
497
+ HTMLStyleElement = HTMLStyleElement;
498
+ HTMLFormElement = HTMLFormElement;
499
+ HTMLTableElement = HTMLTableElement;
500
+ HTMLUListElement = HTMLUListElement;
501
+ HTMLOListElement = HTMLOListElement;
502
+ HTMLLIElement = HTMLLIElement;
503
+ HTMLTemplateElement = HTMLTemplateElement;
504
+ HTMLTheadElement = HTMLTheadElement;
505
+ HTMLTfootElement = HTMLTfootElement;
506
+ HTMLTrElement = HTMLTrElement;
507
+ HTMLTdElement = HTMLTdElement;
508
+ HTMLThElement = HTMLThElement;
509
+ HTMLCaptionElement = HTMLCaptionElement;
510
+ HTMLTbodyElement = HTMLTbodyElement;
511
+
512
+ constructor(config?: { initialUrl?: string }) {
513
+ const documentBase = new DocumentBase();
514
+
515
+ // Set window reference in document for load event
516
+ if (this.document && (this.document as any).setWindow) {
517
+ (this.document as any).setWindow(this);
443
518
  }
444
519
 
445
- // Global constructors
446
- Node = NodeBase;
447
- Element = ElementBase;
448
- HTMLElement = ElementBase;
449
- Event = class Event { };
450
- PopStateEvent = class PopStateEvent extends this.Event { };
451
- IntersectionObserver = class IntersectionObserver { };
452
- NodeFilter = class NodeFilter { };
453
- DocumentFragment = class DocumentFragment { };
454
- HTMLMetaElement = HTMLMetaElement;
455
- HTMLCanvasElement = HTMLCanvasElement;
456
- CanvasRenderingContext2D = class CanvasRenderingContext2D { };
457
- CanvasPattern = class CanvasPattern { };
458
- CanvasGradient = class CanvasGradient { };
459
- Path2D = class Path2D { };
460
- ImageData = class ImageData { };
461
-
462
- // HTML element constructors
463
- HTMLAnchorElement = HTMLAnchorElement;
464
- HTMLBodyElement = HTMLBodyElement;
465
- HTMLButtonElement = HTMLButtonElement;
466
- HTMLDivElement = HTMLDivElement;
467
- HTMLH1Element = HTMLH1Element;
468
- HTMLHeadElement = HTMLHeadElement;
469
- HTMLHtmlElement = HTMLHtmlElement;
470
- HTMLImgElement = HTMLImgElement;
471
- HTMLInputElement = HTMLInputElement;
472
- HTMLPElement = HTMLPElement;
473
- HTMLSpanElement = HTMLSpanElement;
474
- HTMLTitleElement = HTMLTitleElement;
475
- HTMLLinkElement = HTMLLinkElement;
476
- HTMLScriptElement = HTMLScriptElement;
477
- HTMLStyleElement = HTMLStyleElement;
478
- HTMLFormElement = HTMLFormElement;
479
- HTMLTableElement = HTMLTableElement;
480
- HTMLUListElement = HTMLUListElement;
481
- HTMLOListElement = HTMLOListElement;
482
- HTMLLIElement = HTMLLIElement;
483
- HTMLTemplateElement = HTMLTemplateElement;
484
- HTMLTheadElement = HTMLTheadElement;
485
- HTMLTfootElement = HTMLTfootElement;
486
- HTMLTrElement = HTMLTrElement;
487
- HTMLTdElement = HTMLTdElement;
488
- HTMLThElement = HTMLThElement;
489
- HTMLCaptionElement = HTMLCaptionElement;
490
- HTMLTbodyElement = HTMLTbodyElement;
491
-
492
- constructor(document?: Document, initialUrl?: string) {
493
- this.document = document || new DocumentBase();
494
-
495
- // Set window reference in document for load event
496
- if (this.document && (this.document as any).setWindow) {
497
- (this.document as any).setWindow(this);
520
+ this._location = new LocationBase(config?.initialUrl);
521
+ documentBase.setLocation(this._location);
522
+ this.document = documentBase;
523
+ this.history = new HistoryBase(this);
524
+ this.navigator = new NavigatorBase();
525
+ this.clientInformation = this.navigator;
526
+
527
+ // Return a Proxy to handle the [index: number]: Window signature
528
+ return new Proxy(this, {
529
+ get(target: WindowBase, prop: string | symbol): any {
530
+ // Handle numeric indices - return the window itself
531
+ if (typeof prop === 'string' && /^\d+$/.test(prop)) {
532
+ return target;
533
+ }
534
+ // Return the actual property
535
+ return (target as any)[prop];
536
+ },
537
+
538
+ set(target: WindowBase, prop: string | symbol, value: any): boolean {
539
+ // Handle numeric indices - ignore setting
540
+ if (typeof prop === 'string' && /^\d+$/.test(prop)) {
541
+ return true;
542
+ }
543
+ // Set the actual property
544
+ (target as any)[prop] = value;
545
+ return true;
546
+ },
547
+
548
+ has(target: WindowBase, prop: string | symbol): boolean {
549
+ // Handle numeric indices
550
+ if (typeof prop === 'string' && /^\d+$/.test(prop)) {
551
+ return true;
552
+ }
553
+ // Check if property exists
554
+ return prop in target;
555
+ },
556
+
557
+ ownKeys(target: WindowBase): ArrayLike<string | symbol> {
558
+ // Get all own keys plus numeric indices for length
559
+ const keys = Object.getOwnPropertyNames(target);
560
+ // Add numeric indices based on length property
561
+ for (let i = 0; i < target.length; i++) {
562
+ keys.push(i.toString());
563
+ }
564
+ return keys;
565
+ },
566
+
567
+ getOwnPropertyDescriptor(target: WindowBase, prop: string | symbol): PropertyDescriptor | undefined {
568
+ // Handle numeric indices
569
+ if (typeof prop === 'string' && /^\d+$/.test(prop)) {
570
+ return {
571
+ enumerable: true,
572
+ configurable: true,
573
+ value: target
574
+ };
498
575
  }
499
-
500
- this._location = new LocationBase(initialUrl);
501
- this.history = new HistoryBase(this);
502
- this.navigator = new NavigatorBase();
503
- this.clientInformation = this.navigator;
504
-
505
- // Return a Proxy to handle the [index: number]: Window signature
506
- return new Proxy(this, {
507
- get(target: WindowBase, prop: string | symbol): any {
508
- // Handle numeric indices - return the window itself
509
- if (typeof prop === 'string' && /^\d+$/.test(prop)) {
510
- return target;
511
- }
512
- // Return the actual property
513
- return (target as any)[prop];
514
- },
515
-
516
- set(target: WindowBase, prop: string | symbol, value: any): boolean {
517
- // Handle numeric indices - ignore setting
518
- if (typeof prop === 'string' && /^\d+$/.test(prop)) {
519
- return true;
520
- }
521
- // Set the actual property
522
- (target as any)[prop] = value;
523
- return true;
524
- },
525
-
526
- has(target: WindowBase, prop: string | symbol): boolean {
527
- // Handle numeric indices
528
- if (typeof prop === 'string' && /^\d+$/.test(prop)) {
529
- return true;
530
- }
531
- // Check if property exists
532
- return prop in target;
533
- },
534
-
535
- ownKeys(target: WindowBase): ArrayLike<string | symbol> {
536
- // Get all own keys plus numeric indices for length
537
- const keys = Object.getOwnPropertyNames(target);
538
- // Add numeric indices based on length property
539
- for (let i = 0; i < target.length; i++) {
540
- keys.push(i.toString());
541
- }
542
- return keys;
543
- },
544
-
545
- getOwnPropertyDescriptor(target: WindowBase, prop: string | symbol): PropertyDescriptor | undefined {
546
- // Handle numeric indices
547
- if (typeof prop === 'string' && /^\d+$/.test(prop)) {
548
- return {
549
- enumerable: true,
550
- configurable: true,
551
- value: target
552
- };
553
- }
554
- // Return actual property descriptor
555
- return Object.getOwnPropertyDescriptor(target, prop);
556
- }
576
+ // Return actual property descriptor
577
+ return Object.getOwnPropertyDescriptor(target, prop);
578
+ }
579
+ });
580
+ }
581
+
582
+ // Window methods - all with empty implementations for server-side
583
+ alert(message?: any): void {
584
+ }
585
+
586
+ blur(): void {
587
+ }
588
+
589
+ cancelIdleCallback(handle: number): void {
590
+ }
591
+
592
+ captureEvents(): void {
593
+ }
594
+
595
+ close(): void {
596
+ if (this._closed) return;
597
+
598
+ this._closed = true;
599
+
600
+ // Clear all timers
601
+ this._timers.forEach(id => clearTimeout(id));
602
+ this._timers.clear();
603
+
604
+ // Clear all intervals
605
+ this._intervals.forEach(id => clearInterval(id));
606
+ this._intervals.clear();
607
+
608
+ // Clear all animation frames
609
+ this._animationFrames.forEach(id => clearTimeout(id));
610
+ this._animationFrames.clear();
611
+
612
+ // Remove all event listeners (clear references to prevent memory leaks)
613
+ this._eventListeners.forEach(listener => {
614
+ // Explicitly null out the listener function reference
615
+ (listener as any).listener = null;
616
+ });
617
+ this._eventListeners.length = 0;
618
+
619
+ // Clear all on* event handler properties
620
+ this.onload = null;
621
+ this.onunload = null;
622
+ this.onbeforeunload = null;
623
+ this.onpopstate = null;
624
+ this.onerror = null;
625
+ this.onmessage = null;
626
+ this.onhashchange = null;
627
+
628
+ // Clear document references
629
+ if (this.document) {
630
+ const doc = this.document as any;
631
+
632
+ // Clear document content recursively
633
+ if (doc.body) {
634
+ this.clearNodeRecursively(doc.body);
635
+ }
636
+
637
+ if (doc.head) {
638
+ this.clearNodeRecursively(doc.head);
639
+ }
640
+
641
+ if (doc.documentElement) {
642
+ this.clearNodeRecursively(doc.documentElement);
643
+ }
644
+
645
+ // Break circular references
646
+ if (doc.setWindow) {
647
+ doc.setWindow(null);
648
+ }
649
+
650
+ // Clear document event listeners
651
+ if (doc._eventListeners) {
652
+ doc._eventListeners.forEach((listener: any) => {
653
+ listener.listener = null;
557
654
  });
655
+ doc._eventListeners.length = 0;
656
+ }
558
657
  }
559
658
 
560
- // Window methods - all with empty implementations for server-side
561
- alert(message?: any): void { }
562
-
563
- blur(): void { }
564
-
565
- cancelIdleCallback(handle: number): void { }
566
-
567
- captureEvents(): void { }
568
-
569
- close(): void { }
570
-
571
- confirm(message?: string): boolean {
572
- return false;
659
+ // Clear history
660
+ if (this.history) {
661
+ const hist = this.history as any;
662
+ if (hist.historyStack) {
663
+ hist.historyStack.forEach((entry: any) => {
664
+ entry.state = null;
665
+ });
666
+ hist.historyStack.length = 0;
667
+ }
668
+ hist.state = null;
669
+ hist.window = null;
573
670
  }
574
671
 
575
- focus(): void { }
576
-
577
- getComputedStyle(elt: Element, pseudoElt?: string | null): CSSStyleDeclaration {
578
- return {} as CSSStyleDeclaration;
672
+ // Clear location callback
673
+ if (this._location) {
674
+ (this._location as any).urlChangeCallback = null;
579
675
  }
580
-
581
- getSelection(): Selection | null {
582
- return null;
676
+ }
677
+
678
+ /**
679
+ * Recursively clear a node and its children to prevent memory leaks
680
+ */
681
+ private clearNodeRecursively(node: any): void {
682
+ if (!node) return;
683
+
684
+ // Clear children first
685
+ while (node.firstChild) {
686
+ const child = node.firstChild;
687
+ node.removeChild(child);
688
+ this.clearNodeRecursively(child);
583
689
  }
584
690
 
585
- matchMedia(query: string): MediaQueryList {
586
- return {} as MediaQueryList;
691
+ // Clear event listeners on the node
692
+ if (node._eventListeners) {
693
+ node._eventListeners.forEach((listener: any) => {
694
+ listener.listener = null;
695
+ });
696
+ node._eventListeners.length = 0;
587
697
  }
588
698
 
589
- moveBy(x: number, y: number): void { }
590
-
591
- moveTo(x: number, y: number): void { }
592
-
593
- open(url?: string | URL, target?: string, features?: string): WindowProxy | null {
594
- return null;
699
+ // Clear node properties that might hold references (safely)
700
+ try {
701
+ if (node.parentNode) {
702
+ node.parentNode = null;
703
+ }
704
+ } catch (e) {
705
+ // parentNode might be read-only
595
706
  }
596
707
 
597
- postMessage(message: any, targetOrigin: string, transfer?: Transferable[]): void;
598
- postMessage(message: any, options?: WindowPostMessageOptions): void;
599
- postMessage(message: any, targetOriginOrOptions?: string | WindowPostMessageOptions, transfer?: Transferable[]): void { }
600
-
601
- print(): void { }
602
-
603
- prompt(message?: string, _default?: string): string | null {
604
- return null;
708
+ try {
709
+ if (node.ownerDocument) {
710
+ node.ownerDocument = null;
711
+ }
712
+ } catch (e) {
713
+ // ownerDocument might be read-only (getter-only)
605
714
  }
606
715
 
607
- releaseEvents(): void { }
608
-
609
- requestIdleCallback(callback: IdleRequestCallback, options?: IdleRequestOptions): number {
610
- return 0;
716
+ // Clear other potential circular references
717
+ if (node._childNodes) {
718
+ if (Array.isArray(node._childNodes)) {
719
+ node._childNodes = [];
720
+ } else if (node._childNodes instanceof Map) {
721
+ node._childNodes.clear();
722
+ }
611
723
  }
724
+ if (node._attributes) {
725
+ if (Array.isArray(node._attributes)) {
726
+ node._attributes = [];
727
+ } else if (node._attributes instanceof Map) {
728
+ node._attributes.clear();
729
+ }
730
+ }
731
+ }
612
732
 
613
- resizeBy(x: number, y: number): void { }
614
-
615
- resizeTo(width: number, height: number): void { }
733
+ confirm(message?: string): boolean {
734
+ return false;
735
+ }
616
736
 
617
- scroll(options?: ScrollToOptions): void;
618
- scroll(x: number, y: number): void;
619
- scroll(optionsOrX?: ScrollToOptions | number, y?: number): void { }
737
+ focus(): void {
738
+ }
620
739
 
621
- scrollBy(options?: ScrollToOptions): void;
622
- scrollBy(x: number, y: number): void;
623
- scrollBy(optionsOrX?: ScrollToOptions | number, y?: number): void { }
740
+ getComputedStyle(elt: Element, pseudoElt?: string | null): CSSStyleDeclaration {
741
+ return {} as CSSStyleDeclaration;
742
+ }
624
743
 
625
- scrollTo(options?: ScrollToOptions): void;
626
- scrollTo(x: number, y: number): void;
627
- scrollTo(optionsOrX?: ScrollToOptions | number, y?: number): void { }
744
+ getSelection(): Selection | null {
745
+ return null;
746
+ }
628
747
 
629
- stop(): void { }
748
+ matchMedia(query: string): MediaQueryList {
749
+ return {} as MediaQueryList;
750
+ }
630
751
 
631
- // Timer methods
632
- setTimeout(callback: Function, delay?: number, ...args: any[]): number {
633
- return setTimeout(callback, delay, ...args) as any;
752
+ moveBy(x: number, y: number): void {
753
+ }
754
+
755
+ moveTo(x: number, y: number): void {
756
+ }
757
+
758
+ open(url?: string | URL, target?: string, features?: string): WindowProxy | null {
759
+ return null;
760
+ }
761
+
762
+ postMessage(message: any, targetOrigin: string, transfer?: Transferable[]): void;
763
+ postMessage(message: any, options?: WindowPostMessageOptions): void;
764
+ postMessage(message: any, targetOriginOrOptions?: string | WindowPostMessageOptions, transfer?: Transferable[]): void {
765
+ }
766
+
767
+ print(): void {
768
+ }
769
+
770
+ prompt(message?: string, _default?: string): string | null {
771
+ return null;
772
+ }
773
+
774
+ releaseEvents(): void {
775
+ }
776
+
777
+ requestIdleCallback(callback: IdleRequestCallback, options?: IdleRequestOptions): number {
778
+ return 0;
779
+ }
780
+
781
+ resizeBy(x: number, y: number): void {
782
+ }
783
+
784
+ resizeTo(width: number, height: number): void {
785
+ }
786
+
787
+ scroll(options?: ScrollToOptions): void;
788
+ scroll(x: number, y: number): void;
789
+ scroll(optionsOrX?: ScrollToOptions | number, y?: number): void {
790
+ }
791
+
792
+ scrollBy(options?: ScrollToOptions): void;
793
+ scrollBy(x: number, y: number): void;
794
+ scrollBy(optionsOrX?: ScrollToOptions | number, y?: number): void {
795
+ }
796
+
797
+ scrollTo(options?: ScrollToOptions): void;
798
+ scrollTo(x: number, y: number): void;
799
+ scrollTo(optionsOrX?: ScrollToOptions | number, y?: number): void {
800
+ }
801
+
802
+ stop(): void {
803
+ }
804
+
805
+ // Timer methods
806
+ setTimeout(callback: Function, delay?: number, ...args: any[]): number {
807
+ if (this._closed) return 0;
808
+
809
+ const id = setTimeout(() => {
810
+ this._timers.delete(id);
811
+ callback(...args);
812
+ }, delay) as any;
813
+
814
+ this._timers.add(id);
815
+ return id;
816
+ }
817
+
818
+ clearTimeout(id: number): void {
819
+ clearTimeout(id);
820
+ this._timers.delete(id);
821
+ }
822
+
823
+ setInterval(callback: Function, delay?: number, ...args: any[]): number {
824
+ if (this._closed) return 0;
825
+
826
+ const id = setInterval(callback, delay, ...args) as any;
827
+ this._intervals.add(id);
828
+ return id;
829
+ }
830
+
831
+ clearInterval(id: number): void {
832
+ clearInterval(id);
833
+ this._intervals.delete(id);
834
+ }
835
+
836
+ // Animation methods
837
+ requestAnimationFrame(callback: FrameRequestCallback): number {
838
+ if (this._closed) return 0;
839
+
840
+ const id = this.setTimeout(callback, 16) as number;
841
+ this._animationFrames.add(id);
842
+ return id;
843
+ }
844
+
845
+ cancelAnimationFrame(id: number): void {
846
+ this.clearTimeout(id);
847
+ this._animationFrames.delete(id);
848
+ }
849
+
850
+ // Event methods
851
+ addEventListener<K extends keyof WindowEventMap>(type: K, listener: (this: Window, ev: WindowEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
852
+ addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
853
+ addEventListener(type: any, listener: any, options?: any): void {
854
+ // Support popstate, load, unload, and beforeunload events
855
+ if (type === 'popstate' || type === 'load' || type === 'unload' || type === 'beforeunload') {
856
+ this._eventListeners.push({
857
+ type,
858
+ listener: typeof listener === 'function' ? listener : listener.handleEvent,
859
+ options
860
+ });
634
861
  }
635
-
636
- clearTimeout(id: number): void {
637
- clearTimeout(id);
862
+ }
863
+
864
+ removeEventListener<K extends keyof WindowEventMap>(type: K, listener: (this: Window, ev: WindowEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
865
+ removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
866
+ removeEventListener(type: any, listener: any, options?: any): void {
867
+ if (type === 'popstate' || type === 'load' || type === 'unload' || type === 'beforeunload') {
868
+ const targetListener = typeof listener === 'function' ? listener : listener.handleEvent;
869
+ this._eventListeners = this._eventListeners.filter(
870
+ eventListener => !(eventListener.type === type && eventListener.listener === targetListener)
871
+ );
638
872
  }
873
+ }
639
874
 
640
- setInterval(callback: Function, delay?: number, ...args: any[]): number {
641
- return setInterval(callback, delay, ...args) as any;
642
- }
875
+ dispatchEvent(event: any): boolean {
876
+ const eventListeners = this._eventListeners.filter(listener => listener.type === event.type);
643
877
 
644
- clearInterval(id: number): void {
645
- clearInterval(id);
646
- }
878
+ for (const eventListener of eventListeners) {
879
+ try {
880
+ eventListener.listener.call(this, event);
647
881
 
648
- // Animation methods
649
- requestAnimationFrame(callback: FrameRequestCallback): number {
650
- return this.setTimeout(callback, 16) as number;
651
- }
652
-
653
- cancelAnimationFrame(id: number): void {
654
- this.clearTimeout(id);
655
- }
656
-
657
- // Event methods
658
- addEventListener<K extends keyof WindowEventMap>(type: K, listener: (this: Window, ev: WindowEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
659
- addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
660
- addEventListener(type: any, listener: any, options?: any): void {
661
- // Support popstate, load, unload, and beforeunload events
662
- if (type === 'popstate' || type === 'load' || type === 'unload' || type === 'beforeunload') {
663
- this._eventListeners.push({
664
- type,
665
- listener: typeof listener === 'function' ? listener : listener.handleEvent,
666
- options
667
- });
882
+ // Handle 'once' option
883
+ if (eventListener.options && typeof eventListener.options === 'object' && eventListener.options.once) {
884
+ this.removeEventListener(event.type, eventListener.listener);
668
885
  }
886
+ } catch (error) {
887
+ console.error('Error in event listener:', error);
888
+ }
669
889
  }
670
890
 
671
- removeEventListener<K extends keyof WindowEventMap>(type: K, listener: (this: Window, ev: WindowEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
672
- removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
673
- removeEventListener(type: any, listener: any, options?: any): void {
674
- if (type === 'popstate' || type === 'load' || type === 'unload' || type === 'beforeunload') {
675
- const targetListener = typeof listener === 'function' ? listener : listener.handleEvent;
676
- this._eventListeners = this._eventListeners.filter(
677
- eventListener => !(eventListener.type === type && eventListener.listener === targetListener)
678
- );
891
+ return true;
892
+ }
893
+
894
+ /**
895
+ * Dispatch a popstate event to registered listeners
896
+ */
897
+ private dispatchPopStateEvent(state: any, url?: string): void {
898
+ const popStateEvent: PopStateEvent = {
899
+ type: 'popstate',
900
+ state,
901
+ url
902
+ };
903
+
904
+ this.dispatchEvent(popStateEvent);
905
+ }
906
+
907
+ // EventTarget methods (inherited)
908
+ // These are already covered by addEventListener/removeEventListener/dispatchEvent
909
+
910
+ // GlobalEventHandlers - empty implementations
911
+ onabort: ((this: GlobalEventHandlers, ev: UIEvent) => any) | null = null;
912
+ onanimationcancel: ((this: GlobalEventHandlers, ev: AnimationEvent) => any) | null = null;
913
+ onanimationend: ((this: GlobalEventHandlers, ev: AnimationEvent) => any) | null = null;
914
+ onanimationiteration: ((this: GlobalEventHandlers, ev: AnimationEvent) => any) | null = null;
915
+ onanimationstart: ((this: GlobalEventHandlers, ev: AnimationEvent) => any) | null = null;
916
+ onauxclick: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null = null;
917
+ onbeforeinput: ((this: GlobalEventHandlers, ev: InputEvent) => any) | null = null;
918
+ onblur: ((this: GlobalEventHandlers, ev: FocusEvent) => any) | null = null;
919
+ oncancel: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
920
+ oncanplay: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
921
+ oncanplaythrough: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
922
+ onchange: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
923
+ onclick: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null = null;
924
+ onclose: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
925
+ oncontextmenu: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null = null;
926
+ oncopy: ((this: GlobalEventHandlers, ev: ClipboardEvent) => any) | null = null;
927
+ oncuechange: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
928
+ oncut: ((this: GlobalEventHandlers, ev: ClipboardEvent) => any) | null = null;
929
+ ondblclick: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null = null;
930
+ ondrag: ((this: GlobalEventHandlers, ev: DragEvent) => any) | null = null;
931
+ ondragend: ((this: GlobalEventHandlers, ev: DragEvent) => any) | null = null;
932
+ ondragenter: ((this: GlobalEventHandlers, ev: DragEvent) => any) | null = null;
933
+ ondragleave: ((this: GlobalEventHandlers, ev: DragEvent) => any) | null = null;
934
+ ondragover: ((this: GlobalEventHandlers, ev: DragEvent) => any) | null = null;
935
+ ondragstart: ((this: GlobalEventHandlers, ev: DragEvent) => any) | null = null;
936
+ ondrop: ((this: GlobalEventHandlers, ev: DragEvent) => any) | null = null;
937
+ ondurationchange: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
938
+ onemptied: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
939
+ onended: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
940
+ onerror: OnErrorEventHandler = null;
941
+ onfocus: ((this: GlobalEventHandlers, ev: FocusEvent) => any) | null = null;
942
+ onformdata: ((this: GlobalEventHandlers, ev: FormDataEvent) => any) | null = null;
943
+ ongotpointercapture: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null = null;
944
+ oninput: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
945
+ oninvalid: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
946
+ onkeydown: ((this: GlobalEventHandlers, ev: KeyboardEvent) => any) | null = null;
947
+ onkeypress: ((this: GlobalEventHandlers, ev: KeyboardEvent) => any) | null = null;
948
+ onkeyup: ((this: GlobalEventHandlers, ev: KeyboardEvent) => any) | null = null;
949
+ onload: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
950
+ onloadeddata: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
951
+ onloadedmetadata: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
952
+ onloadstart: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
953
+ onlostpointercapture: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null = null;
954
+ onmousedown: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null = null;
955
+ onmouseenter: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null = null;
956
+ onmouseleave: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null = null;
957
+ onmousemove: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null = null;
958
+ onmouseout: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null = null;
959
+ onmouseover: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null = null;
960
+ onmouseup: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null = null;
961
+ onpaste: ((this: GlobalEventHandlers, ev: ClipboardEvent) => any) | null = null;
962
+ onpause: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
963
+ onplay: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
964
+ onplaying: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
965
+ onpointercancel: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null = null;
966
+ onpointerdown: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null = null;
967
+ onpointerenter: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null = null;
968
+ onpointerleave: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null = null;
969
+ onpointermove: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null = null;
970
+ onpointerout: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null = null;
971
+ onpointerover: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null = null;
972
+ onpointerup: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null = null;
973
+ onprogress: ((this: GlobalEventHandlers, ev: ProgressEvent<EventTarget>) => any) | null = null;
974
+ onratechange: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
975
+ onreset: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
976
+ onresize: ((this: GlobalEventHandlers, ev: UIEvent) => any) | null = null;
977
+ onscroll: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
978
+ onscrollend: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
979
+ onsecuritypolicyviolation: ((this: GlobalEventHandlers, ev: SecurityPolicyViolationEvent) => any) | null = null;
980
+ onseeked: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
981
+ onseeking: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
982
+ onselect: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
983
+ onselectionchange: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
984
+ onselectstart: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
985
+ onslotchange: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
986
+ onstalled: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
987
+ onsubmit: ((this: GlobalEventHandlers, ev: SubmitEvent) => any) | null = null;
988
+ onsuspend: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
989
+ ontimeupdate: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
990
+ ontoggle: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
991
+ ontouchcancel?: ((this: GlobalEventHandlers, ev: TouchEvent) => any) | null = null;
992
+ ontouchend?: ((this: GlobalEventHandlers, ev: TouchEvent) => any) | null = null;
993
+ ontouchmove?: ((this: GlobalEventHandlers, ev: TouchEvent) => any) | null = null;
994
+ ontouchstart?: ((this: GlobalEventHandlers, ev: TouchEvent) => any) | null = null;
995
+ ontransitioncancel: ((this: GlobalEventHandlers, ev: TransitionEvent) => any) | null = null;
996
+ ontransitionend: ((this: GlobalEventHandlers, ev: TransitionEvent) => any) | null = null;
997
+ ontransitionrun: ((this: GlobalEventHandlers, ev: TransitionEvent) => any) | null = null;
998
+ ontransitionstart: ((this: GlobalEventHandlers, ev: TransitionEvent) => any) | null = null;
999
+ onvolumechange: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
1000
+ onwaiting: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
1001
+ onwebkitanimationend: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
1002
+ onwebkitanimationiteration: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
1003
+ onwebkitanimationstart: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
1004
+ onwebkittransitionend: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
1005
+ onwheel: ((this: GlobalEventHandlers, ev: WheelEvent) => any) | null = null;
1006
+
1007
+ // WindowEventHandlers - empty implementations
1008
+ onafterprint: ((this: WindowEventHandlers, ev: Event) => any) | null = null;
1009
+ onbeforeprint: ((this: WindowEventHandlers, ev: Event) => any) | null = null;
1010
+ onbeforeunload: ((this: WindowEventHandlers, ev: BeforeUnloadEvent) => any) | null = null;
1011
+ onhashchange: ((this: WindowEventHandlers, ev: HashChangeEvent) => any) | null = null;
1012
+ onlanguagechange: ((this: WindowEventHandlers, ev: Event) => any) | null = null;
1013
+ onmessage: ((this: WindowEventHandlers, ev: MessageEvent) => any) | null = null;
1014
+ onmessageerror: ((this: WindowEventHandlers, ev: MessageEvent) => any) | null = null;
1015
+ onoffline: ((this: WindowEventHandlers, ev: Event) => any) | null = null;
1016
+ ononline: ((this: WindowEventHandlers, ev: Event) => any) | null = null;
1017
+ onpagehide: ((this: WindowEventHandlers, ev: PageTransitionEvent) => any) | null = null;
1018
+ onpageshow: ((this: WindowEventHandlers, ev: PageTransitionEvent) => any) | null = null;
1019
+ onpopstate: ((this: WindowEventHandlers, ev: PopStateEvent) => any) | null = null;
1020
+ onrejectionhandled: ((this: WindowEventHandlers, ev: PromiseRejectionEvent) => any) | null = null;
1021
+ onstorage: ((this: WindowEventHandlers, ev: StorageEvent) => any) | null = null;
1022
+ onunhandledrejection: ((this: WindowEventHandlers, ev: PromiseRejectionEvent) => any) | null = null;
1023
+ onunload: ((this: WindowEventHandlers, ev: Event) => any) | null = null;
1024
+
1025
+ // WindowLocalStorage
1026
+ readonly localStorage: Storage = {} as Storage;
1027
+
1028
+ // WindowOrWorkerGlobalScope - empty implementations
1029
+ readonly caches: CacheStorage = {} as CacheStorage;
1030
+ readonly crossOriginIsolated: boolean = false;
1031
+ readonly crypto: Crypto = {} as Crypto;
1032
+ readonly indexedDB: IDBFactory = {} as IDBFactory;
1033
+ readonly isSecureContext: boolean = false;
1034
+ readonly origin: string = 'null';
1035
+ readonly performance: Performance = {} as Performance;
1036
+
1037
+ atob(data: string): string {
1038
+ return '';
1039
+ }
1040
+
1041
+ btoa(data: string): string {
1042
+ return '';
1043
+ }
1044
+
1045
+ createImageBitmap(image: ImageBitmapSource, options?: ImageBitmapOptions): Promise<ImageBitmap>;
1046
+ createImageBitmap(image: ImageBitmapSource, sx: number, sy: number, sw: number, sh: number, options?: ImageBitmapOptions): Promise<ImageBitmap>;
1047
+ createImageBitmap(...args: any[]): Promise<ImageBitmap> {
1048
+ return Promise.resolve({} as ImageBitmap);
1049
+ }
1050
+
1051
+ fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
1052
+ return Promise.resolve({} as Response);
1053
+ }
1054
+
1055
+ queueMicrotask(callback: VoidFunction): void {
1056
+ }
1057
+
1058
+ reportError(e: any): void {
1059
+ }
1060
+
1061
+ structuredClone(value: any, options?: StructuredSerializeOptions): any {
1062
+ return value;
1063
+ }
1064
+
1065
+ // WindowSessionStorage
1066
+ readonly sessionStorage: Storage = {} as Storage;
1067
+
1068
+ // AnimationFrameProvider - already implemented above
1069
+
1070
+ // Index signature - getter implementation
1071
+ [index: number]: Window;
1072
+
1073
+
1074
+ /**
1075
+ * Recursive HTML parsing
1076
+ */
1077
+ private parseHTMLRecursive(html: string, parent: any): void {
1078
+ let position = 0;
1079
+
1080
+ while (position < html.length) {
1081
+ const tagStart = html.indexOf('<', position);
1082
+
1083
+ if (tagStart === -1) {
1084
+ const remainingText = html.substring(position).trim();
1085
+ if (remainingText) {
1086
+ const textNode = this.document.createTextNode(remainingText);
1087
+ parent.appendChild(textNode);
679
1088
  }
680
- }
681
-
682
- dispatchEvent(event: any): boolean {
683
- const eventListeners = this._eventListeners.filter(listener => listener.type === event.type);
684
-
685
- for (const eventListener of eventListeners) {
686
- try {
687
- eventListener.listener.call(this, event);
688
-
689
- // Handle 'once' option
690
- if (eventListener.options && typeof eventListener.options === 'object' && eventListener.options.once) {
691
- this.removeEventListener(event.type, eventListener.listener);
692
- }
693
- } catch (error) {
694
- console.error('Error in event listener:', error);
695
- }
1089
+ break;
1090
+ }
1091
+
1092
+ if (tagStart > position) {
1093
+ const textContent = html.substring(position, tagStart).trim();
1094
+ if (textContent) {
1095
+ const textNode = this.document.createTextNode(textContent);
1096
+ parent.appendChild(textNode);
696
1097
  }
697
-
698
- return true;
699
- }
700
-
701
- /**
702
- * Dispatch a popstate event to registered listeners
703
- */
704
- private dispatchPopStateEvent(state: any, url?: string): void {
705
- const popStateEvent: PopStateEvent = {
706
- type: 'popstate',
707
- state,
708
- url
709
- };
710
-
711
- this.dispatchEvent(popStateEvent);
712
- }
713
-
714
- // EventTarget methods (inherited)
715
- // These are already covered by addEventListener/removeEventListener/dispatchEvent
716
-
717
- // GlobalEventHandlers - empty implementations
718
- onabort: ((this: GlobalEventHandlers, ev: UIEvent) => any) | null = null;
719
- onanimationcancel: ((this: GlobalEventHandlers, ev: AnimationEvent) => any) | null = null;
720
- onanimationend: ((this: GlobalEventHandlers, ev: AnimationEvent) => any) | null = null;
721
- onanimationiteration: ((this: GlobalEventHandlers, ev: AnimationEvent) => any) | null = null;
722
- onanimationstart: ((this: GlobalEventHandlers, ev: AnimationEvent) => any) | null = null;
723
- onauxclick: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null = null;
724
- onbeforeinput: ((this: GlobalEventHandlers, ev: InputEvent) => any) | null = null;
725
- onblur: ((this: GlobalEventHandlers, ev: FocusEvent) => any) | null = null;
726
- oncancel: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
727
- oncanplay: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
728
- oncanplaythrough: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
729
- onchange: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
730
- onclick: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null = null;
731
- onclose: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
732
- oncontextmenu: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null = null;
733
- oncopy: ((this: GlobalEventHandlers, ev: ClipboardEvent) => any) | null = null;
734
- oncuechange: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
735
- oncut: ((this: GlobalEventHandlers, ev: ClipboardEvent) => any) | null = null;
736
- ondblclick: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null = null;
737
- ondrag: ((this: GlobalEventHandlers, ev: DragEvent) => any) | null = null;
738
- ondragend: ((this: GlobalEventHandlers, ev: DragEvent) => any) | null = null;
739
- ondragenter: ((this: GlobalEventHandlers, ev: DragEvent) => any) | null = null;
740
- ondragleave: ((this: GlobalEventHandlers, ev: DragEvent) => any) | null = null;
741
- ondragover: ((this: GlobalEventHandlers, ev: DragEvent) => any) | null = null;
742
- ondragstart: ((this: GlobalEventHandlers, ev: DragEvent) => any) | null = null;
743
- ondrop: ((this: GlobalEventHandlers, ev: DragEvent) => any) | null = null;
744
- ondurationchange: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
745
- onemptied: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
746
- onended: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
747
- onerror: OnErrorEventHandler = null;
748
- onfocus: ((this: GlobalEventHandlers, ev: FocusEvent) => any) | null = null;
749
- onformdata: ((this: GlobalEventHandlers, ev: FormDataEvent) => any) | null = null;
750
- ongotpointercapture: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null = null;
751
- oninput: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
752
- oninvalid: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
753
- onkeydown: ((this: GlobalEventHandlers, ev: KeyboardEvent) => any) | null = null;
754
- onkeypress: ((this: GlobalEventHandlers, ev: KeyboardEvent) => any) | null = null;
755
- onkeyup: ((this: GlobalEventHandlers, ev: KeyboardEvent) => any) | null = null;
756
- onload: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
757
- onloadeddata: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
758
- onloadedmetadata: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
759
- onloadstart: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
760
- onlostpointercapture: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null = null;
761
- onmousedown: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null = null;
762
- onmouseenter: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null = null;
763
- onmouseleave: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null = null;
764
- onmousemove: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null = null;
765
- onmouseout: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null = null;
766
- onmouseover: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null = null;
767
- onmouseup: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null = null;
768
- onpaste: ((this: GlobalEventHandlers, ev: ClipboardEvent) => any) | null = null;
769
- onpause: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
770
- onplay: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
771
- onplaying: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
772
- onpointercancel: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null = null;
773
- onpointerdown: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null = null;
774
- onpointerenter: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null = null;
775
- onpointerleave: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null = null;
776
- onpointermove: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null = null;
777
- onpointerout: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null = null;
778
- onpointerover: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null = null;
779
- onpointerup: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null = null;
780
- onprogress: ((this: GlobalEventHandlers, ev: ProgressEvent<EventTarget>) => any) | null = null;
781
- onratechange: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
782
- onreset: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
783
- onresize: ((this: GlobalEventHandlers, ev: UIEvent) => any) | null = null;
784
- onscroll: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
785
- onscrollend: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
786
- onsecuritypolicyviolation: ((this: GlobalEventHandlers, ev: SecurityPolicyViolationEvent) => any) | null = null;
787
- onseeked: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
788
- onseeking: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
789
- onselect: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
790
- onselectionchange: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
791
- onselectstart: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
792
- onslotchange: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
793
- onstalled: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
794
- onsubmit: ((this: GlobalEventHandlers, ev: SubmitEvent) => any) | null = null;
795
- onsuspend: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
796
- ontimeupdate: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
797
- ontoggle: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
798
- ontouchcancel?: ((this: GlobalEventHandlers, ev: TouchEvent) => any) | null = null;
799
- ontouchend?: ((this: GlobalEventHandlers, ev: TouchEvent) => any) | null = null;
800
- ontouchmove?: ((this: GlobalEventHandlers, ev: TouchEvent) => any) | null = null;
801
- ontouchstart?: ((this: GlobalEventHandlers, ev: TouchEvent) => any) | null = null;
802
- ontransitioncancel: ((this: GlobalEventHandlers, ev: TransitionEvent) => any) | null = null;
803
- ontransitionend: ((this: GlobalEventHandlers, ev: TransitionEvent) => any) | null = null;
804
- ontransitionrun: ((this: GlobalEventHandlers, ev: TransitionEvent) => any) | null = null;
805
- ontransitionstart: ((this: GlobalEventHandlers, ev: TransitionEvent) => any) | null = null;
806
- onvolumechange: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
807
- onwaiting: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
808
- onwebkitanimationend: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
809
- onwebkitanimationiteration: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
810
- onwebkitanimationstart: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
811
- onwebkittransitionend: ((this: GlobalEventHandlers, ev: Event) => any) | null = null;
812
- onwheel: ((this: GlobalEventHandlers, ev: WheelEvent) => any) | null = null;
813
-
814
- // WindowEventHandlers - empty implementations
815
- onafterprint: ((this: WindowEventHandlers, ev: Event) => any) | null = null;
816
- onbeforeprint: ((this: WindowEventHandlers, ev: Event) => any) | null = null;
817
- onbeforeunload: ((this: WindowEventHandlers, ev: BeforeUnloadEvent) => any) | null = null;
818
- onhashchange: ((this: WindowEventHandlers, ev: HashChangeEvent) => any) | null = null;
819
- onlanguagechange: ((this: WindowEventHandlers, ev: Event) => any) | null = null;
820
- onmessage: ((this: WindowEventHandlers, ev: MessageEvent) => any) | null = null;
821
- onmessageerror: ((this: WindowEventHandlers, ev: MessageEvent) => any) | null = null;
822
- onoffline: ((this: WindowEventHandlers, ev: Event) => any) | null = null;
823
- ononline: ((this: WindowEventHandlers, ev: Event) => any) | null = null;
824
- onpagehide: ((this: WindowEventHandlers, ev: PageTransitionEvent) => any) | null = null;
825
- onpageshow: ((this: WindowEventHandlers, ev: PageTransitionEvent) => any) | null = null;
826
- onpopstate: ((this: WindowEventHandlers, ev: PopStateEvent) => any) | null = null;
827
- onrejectionhandled: ((this: WindowEventHandlers, ev: PromiseRejectionEvent) => any) | null = null;
828
- onstorage: ((this: WindowEventHandlers, ev: StorageEvent) => any) | null = null;
829
- onunhandledrejection: ((this: WindowEventHandlers, ev: PromiseRejectionEvent) => any) | null = null;
830
- onunload: ((this: WindowEventHandlers, ev: Event) => any) | null = null;
831
-
832
- // WindowLocalStorage
833
- readonly localStorage: Storage = {} as Storage;
834
-
835
- // WindowOrWorkerGlobalScope - empty implementations
836
- readonly caches: CacheStorage = {} as CacheStorage;
837
- readonly crossOriginIsolated: boolean = false;
838
- readonly crypto: Crypto = {} as Crypto;
839
- readonly indexedDB: IDBFactory = {} as IDBFactory;
840
- readonly isSecureContext: boolean = false;
841
- readonly origin: string = 'null';
842
- readonly performance: Performance = {} as Performance;
843
-
844
- atob(data: string): string { return ''; }
845
- btoa(data: string): string { return ''; }
846
- createImageBitmap(image: ImageBitmapSource, options?: ImageBitmapOptions): Promise<ImageBitmap>;
847
- createImageBitmap(image: ImageBitmapSource, sx: number, sy: number, sw: number, sh: number, options?: ImageBitmapOptions): Promise<ImageBitmap>;
848
- createImageBitmap(...args: any[]): Promise<ImageBitmap> { return Promise.resolve({} as ImageBitmap); }
849
-
850
- fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> { return Promise.resolve({} as Response); }
851
- queueMicrotask(callback: VoidFunction): void { }
852
- reportError(e: any): void { }
853
- structuredClone(value: any, options?: StructuredSerializeOptions): any { return value; }
854
-
855
- // WindowSessionStorage
856
- readonly sessionStorage: Storage = {} as Storage;
857
-
858
- // AnimationFrameProvider - already implemented above
859
-
860
- // Index signature - getter implementation
861
- [index: number]: Window;
862
-
863
-
864
-
865
- /**
866
- * Recursive HTML parsing
867
- */
868
- private parseHTMLRecursive(html: string, parent: any): void {
869
- let position = 0;
870
-
871
- while (position < html.length) {
872
- const tagStart = html.indexOf('<', position);
873
-
874
- if (tagStart === -1) {
875
- const remainingText = html.substring(position).trim();
876
- if (remainingText) {
877
- const textNode = this.document.createTextNode(remainingText);
878
- parent.appendChild(textNode);
879
- }
880
- break;
881
- }
882
-
883
- if (tagStart > position) {
884
- const textContent = html.substring(position, tagStart).trim();
885
- if (textContent) {
886
- const textNode = this.document.createTextNode(textContent);
887
- parent.appendChild(textNode);
888
- }
889
- }
890
-
891
- const tagEnd = html.indexOf('>', tagStart);
892
- if (tagEnd === -1) break;
893
-
894
- const tagContent = html.substring(tagStart + 1, tagEnd);
895
-
896
- if (tagContent.startsWith('/')) {
897
- position = tagEnd + 1;
898
- break;
899
- }
900
-
901
- const isSelfClosing = tagContent.endsWith('/') || this.isSelfClosingTag(tagContent.split(/\s+/)[0]);
902
- const spaceIndex = tagContent.indexOf(' ');
903
- const tagName = spaceIndex === -1 ? tagContent.replace('/', '') : tagContent.substring(0, spaceIndex);
904
- const attributes = spaceIndex === -1 ? '' : tagContent.substring(spaceIndex + 1).replace('/', '');
905
-
906
- const element = this.document.createElement(tagName.toLowerCase());
907
-
908
- if (attributes.trim()) {
909
- this.parseAttributes(attributes, element);
910
- }
911
-
912
- // Handle special HTML structure tags
913
- if (tagName.toLowerCase() === 'html') {
914
- // Replace documentElement
915
- (this.document as any).documentElement = element;
916
- this.document.appendChild(element);
917
- } else if (tagName.toLowerCase() === 'head') {
918
- (this.document as any).head = element;
919
- if (this.document.documentElement) {
920
- this.document.documentElement.appendChild(element);
921
- } else {
922
- parent.appendChild(element);
923
- }
924
- } else if (tagName.toLowerCase() === 'body') {
925
- (this.document as any).body = element;
926
- if (this.document.documentElement) {
927
- this.document.documentElement.appendChild(element);
928
- } else {
929
- parent.appendChild(element);
930
- }
931
- } else {
932
- parent.appendChild(element);
933
- }
934
-
935
- if (isSelfClosing) {
936
- position = tagEnd + 1;
937
- } else {
938
- const closingTag = `</${tagName}>`;
939
- const closingTagIndex = this.findMatchingClosingTag(html, tagEnd + 1, tagName);
940
-
941
- if (closingTagIndex !== -1) {
942
- const innerContent = html.substring(tagEnd + 1, closingTagIndex);
943
- if (innerContent.trim()) {
944
- this.parseHTMLRecursive(innerContent, element);
945
- }
946
- position = closingTagIndex + closingTag.length;
947
- } else {
948
- position = tagEnd + 1;
949
- }
950
- }
1098
+ }
1099
+
1100
+ const tagEnd = html.indexOf('>', tagStart);
1101
+ if (tagEnd === -1) break;
1102
+
1103
+ const tagContent = html.substring(tagStart + 1, tagEnd);
1104
+
1105
+ if (tagContent.startsWith('/')) {
1106
+ position = tagEnd + 1;
1107
+ break;
1108
+ }
1109
+
1110
+ const isSelfClosing = tagContent.endsWith('/') || this.isSelfClosingTag(tagContent.split(/\s+/)[0]);
1111
+ const spaceIndex = tagContent.indexOf(' ');
1112
+ const tagName = spaceIndex === -1 ? tagContent.replace('/', '') : tagContent.substring(0, spaceIndex);
1113
+ const attributes = spaceIndex === -1 ? '' : tagContent.substring(spaceIndex + 1).replace('/', '');
1114
+
1115
+ const element = this.document.createElement(tagName.toLowerCase());
1116
+
1117
+ if (attributes.trim()) {
1118
+ this.parseAttributes(attributes, element);
1119
+ }
1120
+
1121
+ // Handle special HTML structure tags
1122
+ if (tagName.toLowerCase() === 'html') {
1123
+ // Replace documentElement
1124
+ (this.document as any).documentElement = element;
1125
+ this.document.appendChild(element);
1126
+ } else if (tagName.toLowerCase() === 'head') {
1127
+ (this.document as any).head = element;
1128
+ if (this.document.documentElement) {
1129
+ this.document.documentElement.appendChild(element);
1130
+ } else {
1131
+ parent.appendChild(element);
951
1132
  }
952
- }
953
-
954
- private findMatchingClosingTag(html: string, startPos: number, tagName: string): number {
955
- const openTag = `<${tagName}`;
956
- const closeTag = `</${tagName}>`;
957
- let depth = 1;
958
- let pos = startPos;
959
-
960
- while (pos < html.length && depth > 0) {
961
- const nextOpen = html.indexOf(openTag, pos);
962
- const nextClose = html.indexOf(closeTag, pos);
963
-
964
- if (nextClose === -1) return -1;
965
-
966
- if (nextOpen !== -1 && nextOpen < nextClose) {
967
- depth++;
968
- pos = nextOpen + openTag.length;
969
- } else {
970
- depth--;
971
- if (depth === 0) return nextClose;
972
- pos = nextClose + closeTag.length;
973
- }
1133
+ } else if (tagName.toLowerCase() === 'body') {
1134
+ (this.document as any).body = element;
1135
+ if (this.document.documentElement) {
1136
+ this.document.documentElement.appendChild(element);
1137
+ } else {
1138
+ parent.appendChild(element);
974
1139
  }
975
-
976
- return -1;
977
- }
978
-
979
- private parseAttributes(attributeString: string, element: any): void {
980
- // Updated regex to handle hyphenated attribute names like dr-for-of, data-bind, etc.
981
- const attrRegex = /([\w:-]+)(?:\s*=\s*(['"])(.*?)\2)?/g;
982
- let match;
983
-
984
- while ((match = attrRegex.exec(attributeString)) !== null) {
985
- const name = match[1];
986
- let value = match[3] || ''; // match[3] is the content between quotes
987
- element.setAttribute(name, value);
1140
+ } else {
1141
+ parent.appendChild(element);
1142
+ }
1143
+
1144
+ if (isSelfClosing) {
1145
+ position = tagEnd + 1;
1146
+ } else {
1147
+ const closingTag = `</${tagName}>`;
1148
+ const closingTagIndex = this.findMatchingClosingTag(html, tagEnd + 1, tagName);
1149
+
1150
+ if (closingTagIndex !== -1) {
1151
+ const innerContent = html.substring(tagEnd + 1, closingTagIndex);
1152
+ if (innerContent.trim()) {
1153
+ this.parseHTMLRecursive(innerContent, element);
1154
+ }
1155
+ position = closingTagIndex + closingTag.length;
1156
+ } else {
1157
+ position = tagEnd + 1;
988
1158
  }
1159
+ }
989
1160
  }
990
-
991
- private isSelfClosingTag(tagName: string): boolean {
992
- const selfClosingTags = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
993
- return selfClosingTags.includes(tagName.toLowerCase());
1161
+ }
1162
+
1163
+ private findMatchingClosingTag(html: string, startPos: number, tagName: string): number {
1164
+ const openTag = `<${tagName}`;
1165
+ const closeTag = `</${tagName}>`;
1166
+ let depth = 1;
1167
+ let pos = startPos;
1168
+
1169
+ while (pos < html.length && depth > 0) {
1170
+ const nextOpen = html.indexOf(openTag, pos);
1171
+ const nextClose = html.indexOf(closeTag, pos);
1172
+
1173
+ if (nextClose === -1) return -1;
1174
+
1175
+ if (nextOpen !== -1 && nextOpen < nextClose) {
1176
+ depth++;
1177
+ pos = nextOpen + openTag.length;
1178
+ } else {
1179
+ depth--;
1180
+ if (depth === 0) return nextClose;
1181
+ pos = nextClose + closeTag.length;
1182
+ }
994
1183
  }
995
1184
 
996
- /**
997
- * Clear current document content
998
- */
999
- private clearDocumentContent(): void {
1000
- const doc = this.document as any;
1185
+ return -1;
1186
+ }
1001
1187
 
1002
- // Clear head content
1003
- if (doc.head) {
1004
- while (doc.head.firstChild) {
1005
- doc.head.removeChild(doc.head.firstChild);
1006
- }
1007
- }
1188
+ private parseAttributes(attributeString: string, element: any): void {
1189
+ // Updated regex to handle hyphenated attribute names like dr-for-of, data-bind, etc.
1190
+ const attrRegex = /([\w:-]+)(?:\s*=\s*(['"])(.*?)\2)?/g;
1191
+ let match;
1008
1192
 
1009
- // Clear body content
1010
- if (doc.body) {
1011
- while (doc.body.firstChild) {
1012
- doc.body.removeChild(doc.body.firstChild);
1013
- }
1014
- }
1193
+ while ((match = attrRegex.exec(attributeString)) !== null) {
1194
+ const name = match[1];
1195
+ let value = match[3] || ''; // match[3] is the content between quotes
1196
+ element.setAttribute(name, value);
1197
+ }
1198
+ }
1199
+
1200
+ private isSelfClosingTag(tagName: string): boolean {
1201
+ const selfClosingTags = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
1202
+ return selfClosingTags.includes(tagName.toLowerCase());
1203
+ }
1204
+
1205
+ /**
1206
+ * Clear current document content
1207
+ */
1208
+ private clearDocumentContent(): void {
1209
+ const doc = this.document as any;
1210
+
1211
+ // Clear head content
1212
+ if (doc.head) {
1213
+ while (doc.head.firstChild) {
1214
+ doc.head.removeChild(doc.head.firstChild);
1215
+ }
1216
+ }
1217
+
1218
+ // Clear body content
1219
+ if (doc.body) {
1220
+ while (doc.body.firstChild) {
1221
+ doc.body.removeChild(doc.body.firstChild);
1222
+ }
1015
1223
  }
1224
+ }
1016
1225
 
1017
1226
 
1018
1227
  }