happy-dom 12.8.1 → 12.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (40) hide show
  1. package/cjs/async-task-manager/AsyncTaskManager.cjs +75 -69
  2. package/cjs/async-task-manager/AsyncTaskManager.cjs.map +1 -1
  3. package/cjs/async-task-manager/AsyncTaskManager.d.ts +20 -11
  4. package/cjs/async-task-manager/AsyncTaskManager.d.ts.map +1 -1
  5. package/cjs/fetch/Fetch.cjs +2 -2
  6. package/cjs/fetch/Fetch.cjs.map +1 -1
  7. package/cjs/nodes/document/DocumentReadyStateManager.cjs +9 -9
  8. package/cjs/nodes/document/DocumentReadyStateManager.cjs.map +1 -1
  9. package/cjs/nodes/document/DocumentReadyStateManager.d.ts +1 -1
  10. package/cjs/nodes/document/DocumentReadyStateManager.d.ts.map +1 -1
  11. package/cjs/version.cjs +1 -1
  12. package/cjs/window/IWindow.d.ts +4 -4
  13. package/cjs/window/IWindow.d.ts.map +1 -1
  14. package/cjs/window/Window.cjs +17 -6
  15. package/cjs/window/Window.cjs.map +1 -1
  16. package/cjs/window/Window.d.ts +4 -4
  17. package/cjs/window/Window.d.ts.map +1 -1
  18. package/lib/async-task-manager/AsyncTaskManager.d.ts +20 -11
  19. package/lib/async-task-manager/AsyncTaskManager.d.ts.map +1 -1
  20. package/lib/async-task-manager/AsyncTaskManager.js +75 -69
  21. package/lib/async-task-manager/AsyncTaskManager.js.map +1 -1
  22. package/lib/fetch/Fetch.js +2 -2
  23. package/lib/fetch/Fetch.js.map +1 -1
  24. package/lib/nodes/document/DocumentReadyStateManager.d.ts +1 -1
  25. package/lib/nodes/document/DocumentReadyStateManager.d.ts.map +1 -1
  26. package/lib/nodes/document/DocumentReadyStateManager.js +9 -9
  27. package/lib/nodes/document/DocumentReadyStateManager.js.map +1 -1
  28. package/lib/version.js +1 -1
  29. package/lib/window/IWindow.d.ts +4 -4
  30. package/lib/window/IWindow.d.ts.map +1 -1
  31. package/lib/window/Window.d.ts +4 -4
  32. package/lib/window/Window.d.ts.map +1 -1
  33. package/lib/window/Window.js +17 -6
  34. package/lib/window/Window.js.map +1 -1
  35. package/package.json +1 -1
  36. package/src/async-task-manager/AsyncTaskManager.ts +85 -72
  37. package/src/fetch/Fetch.ts +2 -2
  38. package/src/nodes/document/DocumentReadyStateManager.ts +9 -9
  39. package/src/window/IWindow.ts +4 -4
  40. package/src/window/Window.ts +18 -9
@@ -4,30 +4,55 @@
4
4
  export default class AsyncTaskManager {
5
5
  private static taskID = 0;
6
6
  private runningTasks: { [k: string]: () => void } = {};
7
+ private runningTaskCount = 0;
7
8
  private runningTimers: NodeJS.Timeout[] = [];
8
- private callbacks: Array<() => void> = [];
9
- private callbackTimeout: NodeJS.Timeout | null = null;
9
+ private runningImmediates: NodeJS.Immediate[] = [];
10
+ private whenCompleteImmediate: NodeJS.Immediate | null = null;
11
+ private whenCompleteResolvers: Array<() => void> = [];
10
12
 
11
13
  /**
12
- * Returns a promise that is fulfilled when async tasks are complete.
13
- * This method is not part of the HTML standard.
14
+ * Returns a promise that is resolved when async tasks are complete.
14
15
  *
15
16
  * @returns Promise.
16
17
  */
17
18
  public whenComplete(): Promise<void> {
18
19
  return new Promise((resolve) => {
19
- this.callbacks.push(resolve);
20
- this.endTask(null);
20
+ this.whenCompleteResolvers.push(resolve);
21
+ this.endTask(this.startTask());
21
22
  });
22
23
  }
23
24
 
24
25
  /**
25
- * Ends all tasks.
26
- *
27
- * @param [error] Error.
26
+ * Cancels all tasks.
28
27
  */
29
28
  public cancelAll(): void {
30
- this.endAll(true);
29
+ const runningTimers = this.runningTimers;
30
+ const runningImmediates = this.runningImmediates;
31
+ const runningTasks = this.runningTasks;
32
+
33
+ this.runningTasks = {};
34
+ this.runningTaskCount = 0;
35
+ this.runningImmediates = [];
36
+ this.runningTimers = [];
37
+
38
+ if (this.whenCompleteImmediate) {
39
+ global.clearImmediate(this.whenCompleteImmediate);
40
+ this.whenCompleteImmediate = null;
41
+ }
42
+
43
+ for (const immediate of runningImmediates) {
44
+ global.clearImmediate(immediate);
45
+ }
46
+
47
+ for (const timer of runningTimers) {
48
+ global.clearTimeout(timer);
49
+ }
50
+
51
+ for (const key of Object.keys(runningTasks)) {
52
+ runningTasks[key]();
53
+ }
54
+
55
+ this.resolveWhenComplete();
31
56
  }
32
57
 
33
58
  /**
@@ -37,10 +62,6 @@ export default class AsyncTaskManager {
37
62
  */
38
63
  public startTimer(timerID: NodeJS.Timeout): void {
39
64
  this.runningTimers.push(timerID);
40
- if (this.callbackTimeout) {
41
- global.clearTimeout(this.callbackTimeout);
42
- this.callbackTimeout = null;
43
- }
44
65
  }
45
66
 
46
67
  /**
@@ -52,13 +73,33 @@ export default class AsyncTaskManager {
52
73
  const index = this.runningTimers.indexOf(timerID);
53
74
  if (index !== -1) {
54
75
  this.runningTimers.splice(index, 1);
76
+ if (!this.runningTaskCount && !this.runningTimers.length && !this.runningImmediates.length) {
77
+ this.resolveWhenComplete();
78
+ }
55
79
  }
56
- if (this.callbackTimeout) {
57
- global.clearTimeout(this.callbackTimeout);
58
- this.callbackTimeout = null;
59
- }
60
- if (!Object.keys(this.runningTasks).length && !this.runningTimers.length) {
61
- this.endAll();
80
+ }
81
+
82
+ /**
83
+ * Starts an immediate.
84
+ *
85
+ * @param immediateID Immediate ID.
86
+ */
87
+ public startImmediate(immediateID: NodeJS.Immediate): void {
88
+ this.runningImmediates.push(immediateID);
89
+ }
90
+
91
+ /**
92
+ * Ends an immediate.
93
+ *
94
+ * @param immediateID Immediate ID.
95
+ */
96
+ public endImmediate(immediateID: NodeJS.Immediate): void {
97
+ const index = this.runningImmediates.indexOf(immediateID);
98
+ if (index !== -1) {
99
+ this.runningImmediates.splice(index, 1);
100
+ if (!this.runningTaskCount && !this.runningTimers.length && !this.runningImmediates.length) {
101
+ this.resolveWhenComplete();
102
+ }
62
103
  }
63
104
  }
64
105
 
@@ -71,10 +112,7 @@ export default class AsyncTaskManager {
71
112
  public startTask(abortHandler?: () => void): number {
72
113
  const taskID = this.newTaskID();
73
114
  this.runningTasks[taskID] = abortHandler ? abortHandler : () => {};
74
- if (this.callbackTimeout) {
75
- global.clearTimeout(this.callbackTimeout);
76
- this.callbackTimeout = null;
77
- }
115
+ this.runningTaskCount++;
78
116
  return taskID;
79
117
  }
80
118
 
@@ -86,13 +124,22 @@ export default class AsyncTaskManager {
86
124
  public endTask(taskID: number): void {
87
125
  if (this.runningTasks[taskID]) {
88
126
  delete this.runningTasks[taskID];
89
- }
90
- if (this.callbackTimeout) {
91
- global.clearTimeout(this.callbackTimeout);
92
- this.callbackTimeout = null;
93
- }
94
- if (!Object.keys(this.runningTasks).length && !this.runningTimers.length) {
95
- this.endAll();
127
+ this.runningTaskCount--;
128
+ if (this.whenCompleteImmediate) {
129
+ global.clearImmediate(this.whenCompleteImmediate);
130
+ }
131
+ if (!this.runningTaskCount && !this.runningTimers.length && !this.runningImmediates.length) {
132
+ this.whenCompleteImmediate = global.setImmediate(() => {
133
+ this.whenCompleteImmediate = null;
134
+ if (
135
+ !this.runningTaskCount &&
136
+ !this.runningTimers.length &&
137
+ !this.runningImmediates.length
138
+ ) {
139
+ this.resolveWhenComplete();
140
+ }
141
+ });
142
+ }
96
143
  }
97
144
  }
98
145
 
@@ -102,7 +149,7 @@ export default class AsyncTaskManager {
102
149
  * @returns Count.
103
150
  */
104
151
  public getTaskCount(): number {
105
- return Object.keys(this.runningTasks).length;
152
+ return this.runningTaskCount;
106
153
  }
107
154
 
108
155
  /**
@@ -116,47 +163,13 @@ export default class AsyncTaskManager {
116
163
  }
117
164
 
118
165
  /**
119
- * Ends all tasks.
120
- *
121
- * @param [canceled] Canceled.
166
+ * Resolves when complete.
122
167
  */
123
- private endAll(canceled?: boolean): void {
124
- const runningTimers = this.runningTimers;
125
- const runningTasks = this.runningTasks;
126
-
127
- this.runningTasks = {};
128
- this.runningTimers = [];
129
-
130
- for (const timer of runningTimers) {
131
- global.clearTimeout(timer);
132
- }
133
-
134
- for (const key of Object.keys(runningTasks)) {
135
- runningTasks[key]();
136
- }
137
-
138
- if (this.callbackTimeout) {
139
- global.clearTimeout(this.callbackTimeout);
140
- this.callbackTimeout = null;
141
- }
142
- if (this.callbacks.length) {
143
- if (canceled) {
144
- const callbacks = this.callbacks;
145
- this.callbacks = [];
146
- for (const callback of callbacks) {
147
- callback();
148
- }
149
- } else {
150
- this.callbackTimeout = global.setTimeout(() => {
151
- const callbacks = this.callbacks;
152
- this.callbackTimeout = null;
153
- this.callbacks = [];
154
- this.runningTimers = [];
155
- for (const callback of callbacks) {
156
- callback();
157
- }
158
- }, 10);
159
- }
168
+ private resolveWhenComplete(): void {
169
+ const resolvers = this.whenCompleteResolvers;
170
+ this.whenCompleteResolvers = [];
171
+ for (const resolver of resolvers) {
172
+ resolver();
160
173
  }
161
174
  }
162
175
  }
@@ -92,12 +92,12 @@ export default class Fetch {
92
92
  }
93
93
 
94
94
  this.resolve = (response: IResponse | Promise<IResponse>): void => {
95
- resolve(response);
96
95
  taskManager.endTask(taskID);
96
+ resolve(response);
97
97
  };
98
98
  this.reject = (error: Error): void => {
99
- reject(error);
100
99
  taskManager.endTask(taskID);
100
+ reject(error);
101
101
  };
102
102
 
103
103
  this.prepareRequest();
@@ -7,7 +7,7 @@ export default class DocumentReadyStateManager {
7
7
  private totalTasks = 0;
8
8
  private readyStateCallbacks: (() => void)[] = [];
9
9
  private window: IWindow = null;
10
- private timer: NodeJS.Timeout = null;
10
+ private immediate: NodeJS.Immediate | null = null;
11
11
  private isComplete = false;
12
12
 
13
13
  /**
@@ -30,8 +30,8 @@ export default class DocumentReadyStateManager {
30
30
  resolve();
31
31
  } else {
32
32
  this.readyStateCallbacks.push(resolve);
33
- if (this.totalTasks === 0 && !this.timer) {
34
- this.timer = this.window.setTimeout(this.endTask.bind(this));
33
+ if (this.totalTasks === 0 && !this.immediate) {
34
+ this.immediate = this.window.requestAnimationFrame(this.endTask.bind(this));
35
35
  }
36
36
  }
37
37
  });
@@ -45,9 +45,9 @@ export default class DocumentReadyStateManager {
45
45
  return;
46
46
  }
47
47
 
48
- if (this.timer) {
49
- this.window.clearTimeout(this.timer);
50
- this.timer = null;
48
+ if (this.immediate) {
49
+ this.window.cancelAnimationFrame(this.immediate);
50
+ this.immediate = null;
51
51
  }
52
52
 
53
53
  this.totalTasks++;
@@ -61,9 +61,9 @@ export default class DocumentReadyStateManager {
61
61
  return;
62
62
  }
63
63
 
64
- if (this.timer) {
65
- this.window.clearTimeout(this.timer);
66
- this.timer = null;
64
+ if (this.immediate) {
65
+ this.window.cancelAnimationFrame(this.immediate);
66
+ this.immediate = null;
67
67
  }
68
68
 
69
69
  this.totalTasks--;
@@ -492,16 +492,16 @@ export default interface IWindow extends IEventTarget, INodeJSGlobal {
492
492
  * Mock animation frames with timeouts.
493
493
  *
494
494
  * @param {Function} callback Callback.
495
- * @returns {NodeJS.Timeout} Timeout ID.
495
+ * @returns {NodeJS.Timeout} ID.
496
496
  */
497
- requestAnimationFrame(callback: (timestamp: number) => void): NodeJS.Timeout;
497
+ requestAnimationFrame(callback: (timestamp: number) => void): NodeJS.Immediate;
498
498
 
499
499
  /**
500
500
  * Mock animation frames with timeouts.
501
501
  *
502
- * @param {NodeJS.Timeout} id Timeout ID.
502
+ * @param {NodeJS.Timeout} id ID.
503
503
  */
504
- cancelAnimationFrame(id: NodeJS.Timeout): void;
504
+ cancelAnimationFrame(id: NodeJS.Immediate): void;
505
505
 
506
506
  /**
507
507
  * This method provides an easy, logical way to fetch resources asynchronously across the network.
@@ -837,12 +837,12 @@ export default class Window extends EventTarget implements IWindow {
837
837
  */
838
838
  public setTimeout(callback: Function, delay = 0, ...args: unknown[]): NodeJS.Timeout {
839
839
  const id = this._setTimeout(() => {
840
- this.happyDOM.asyncTaskManager.endTimer(id);
841
840
  if (this.happyDOM.settings.disableErrorCapturing) {
842
841
  callback(...args);
843
842
  } else {
844
843
  WindowErrorUtility.captureError(this, () => callback(...args));
845
844
  }
845
+ this.happyDOM.asyncTaskManager.endTimer(id);
846
846
  }, delay);
847
847
  this.happyDOM.asyncTaskManager.startTimer(id);
848
848
  return id;
@@ -896,19 +896,29 @@ export default class Window extends EventTarget implements IWindow {
896
896
  * Mock animation frames with timeouts.
897
897
  *
898
898
  * @param callback Callback.
899
- * @returns Timeout ID.
899
+ * @returns ID.
900
900
  */
901
- public requestAnimationFrame(callback: (timestamp: number) => void): NodeJS.Timeout {
902
- return this.setTimeout(() => callback(this.performance.now()));
901
+ public requestAnimationFrame(callback: (timestamp: number) => void): NodeJS.Immediate {
902
+ const id = global.setImmediate(() => {
903
+ if (this.happyDOM.settings.disableErrorCapturing) {
904
+ callback(this.performance.now());
905
+ } else {
906
+ WindowErrorUtility.captureError(this, () => callback(this.performance.now()));
907
+ }
908
+ this.happyDOM.asyncTaskManager.endImmediate(id);
909
+ });
910
+ this.happyDOM.asyncTaskManager.startImmediate(id);
911
+ return id;
903
912
  }
904
913
 
905
914
  /**
906
915
  * Mock animation frames with timeouts.
907
916
  *
908
- * @param id Timeout ID.
917
+ * @param id ID.
909
918
  */
910
- public cancelAnimationFrame(id: NodeJS.Timeout): void {
911
- this.clearTimeout(id);
919
+ public cancelAnimationFrame(id: NodeJS.Immediate): void {
920
+ global.clearImmediate(id);
921
+ this.happyDOM.asyncTaskManager.endImmediate(id);
912
922
  }
913
923
 
914
924
  /**
@@ -921,13 +931,12 @@ export default class Window extends EventTarget implements IWindow {
921
931
  const taskId = this.happyDOM.asyncTaskManager.startTask(() => (isAborted = true));
922
932
  this._queueMicrotask(() => {
923
933
  if (!isAborted) {
924
- this.happyDOM.asyncTaskManager.endTask(taskId);
925
-
926
934
  if (this.happyDOM.settings.disableErrorCapturing) {
927
935
  callback();
928
936
  } else {
929
937
  WindowErrorUtility.captureError(this, <() => unknown>callback);
930
938
  }
939
+ this.happyDOM.asyncTaskManager.endTask(taskId);
931
940
  }
932
941
  });
933
942
  }