happy-dom 14.11.3 → 14.12.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 (48) hide show
  1. package/cjs/async-task-manager/AsyncTaskManager.cjs +52 -25
  2. package/cjs/async-task-manager/AsyncTaskManager.cjs.map +1 -1
  3. package/cjs/async-task-manager/AsyncTaskManager.d.ts.map +1 -1
  4. package/cjs/browser/BrowserSettingsFactory.cjs +4 -0
  5. package/cjs/browser/BrowserSettingsFactory.cjs.map +1 -1
  6. package/cjs/browser/BrowserSettingsFactory.d.ts.map +1 -1
  7. package/cjs/browser/DefaultBrowserSettings.cjs +5 -0
  8. package/cjs/browser/DefaultBrowserSettings.cjs.map +1 -1
  9. package/cjs/browser/DefaultBrowserSettings.d.ts.map +1 -1
  10. package/cjs/browser/types/IBrowserSettings.d.ts +6 -0
  11. package/cjs/browser/types/IBrowserSettings.d.ts.map +1 -1
  12. package/cjs/browser/types/IOptionalBrowserSettings.d.ts +6 -0
  13. package/cjs/browser/types/IOptionalBrowserSettings.d.ts.map +1 -1
  14. package/cjs/browser/utilities/BrowserFrameFactory.cjs +12 -0
  15. package/cjs/browser/utilities/BrowserFrameFactory.cjs.map +1 -1
  16. package/cjs/browser/utilities/BrowserFrameFactory.d.ts.map +1 -1
  17. package/cjs/window/BrowserWindow.cjs +66 -4
  18. package/cjs/window/BrowserWindow.cjs.map +1 -1
  19. package/cjs/window/BrowserWindow.d.ts +1 -1
  20. package/cjs/window/BrowserWindow.d.ts.map +1 -1
  21. package/lib/async-task-manager/AsyncTaskManager.d.ts.map +1 -1
  22. package/lib/async-task-manager/AsyncTaskManager.js +52 -25
  23. package/lib/async-task-manager/AsyncTaskManager.js.map +1 -1
  24. package/lib/browser/BrowserSettingsFactory.d.ts.map +1 -1
  25. package/lib/browser/BrowserSettingsFactory.js +4 -0
  26. package/lib/browser/BrowserSettingsFactory.js.map +1 -1
  27. package/lib/browser/DefaultBrowserSettings.d.ts.map +1 -1
  28. package/lib/browser/DefaultBrowserSettings.js +5 -0
  29. package/lib/browser/DefaultBrowserSettings.js.map +1 -1
  30. package/lib/browser/types/IBrowserSettings.d.ts +6 -0
  31. package/lib/browser/types/IBrowserSettings.d.ts.map +1 -1
  32. package/lib/browser/types/IOptionalBrowserSettings.d.ts +6 -0
  33. package/lib/browser/types/IOptionalBrowserSettings.d.ts.map +1 -1
  34. package/lib/browser/utilities/BrowserFrameFactory.d.ts.map +1 -1
  35. package/lib/browser/utilities/BrowserFrameFactory.js +12 -0
  36. package/lib/browser/utilities/BrowserFrameFactory.js.map +1 -1
  37. package/lib/window/BrowserWindow.d.ts +1 -1
  38. package/lib/window/BrowserWindow.d.ts.map +1 -1
  39. package/lib/window/BrowserWindow.js +66 -4
  40. package/lib/window/BrowserWindow.js.map +1 -1
  41. package/package.json +1 -1
  42. package/src/async-task-manager/AsyncTaskManager.ts +54 -27
  43. package/src/browser/BrowserSettingsFactory.ts +4 -0
  44. package/src/browser/DefaultBrowserSettings.ts +5 -0
  45. package/src/browser/types/IBrowserSettings.ts +7 -0
  46. package/src/browser/types/IOptionalBrowserSettings.ts +7 -0
  47. package/src/browser/utilities/BrowserFrameFactory.ts +12 -0
  48. package/src/window/BrowserWindow.ts +92 -20
@@ -165,6 +165,19 @@ const TIMER = {
165
165
  clearImmediate: globalThis.clearImmediate.bind(globalThis)
166
166
  };
167
167
  const IS_NODE_JS_TIMEOUT_ENVIRONMENT = setTimeout.toString().includes('new Timeout');
168
+ /**
169
+ * Zero Timeout.
170
+ */
171
+ class Timeout {
172
+ public callback: () => void;
173
+ /**
174
+ * Constructor.
175
+ * @param callback Callback.
176
+ */
177
+ constructor(callback: () => void) {
178
+ this.callback = callback;
179
+ }
180
+ }
168
181
 
169
182
  /**
170
183
  * Browser window.
@@ -494,7 +507,7 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal
494
507
  // Used for tracking capture event listeners to improve performance when they are not used.
495
508
  // See EventTarget class.
496
509
  public [PropertySymbol.captureEventListenerCount]: { [eventType: string]: number } = {};
497
- public readonly [PropertySymbol.mutationObservers]: MutationObserver[] = [];
510
+ public [PropertySymbol.mutationObservers]: MutationObserver[] = [];
498
511
  public readonly [PropertySymbol.readyStateManager] = new DocumentReadyStateManager(this);
499
512
  public [PropertySymbol.asyncTaskManager]: AsyncTaskManager | null = null;
500
513
  public [PropertySymbol.location]: Location;
@@ -511,6 +524,7 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal
511
524
  #outerWidth: number | null = null;
512
525
  #outerHeight: number | null = null;
513
526
  #devicePixelRatio: number | null = null;
527
+ #zeroTimeouts: Array<Timeout> | null = null;
514
528
 
515
529
  /**
516
530
  * Constructor.
@@ -1018,19 +1032,55 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal
1018
1032
  * @returns Timeout ID.
1019
1033
  */
1020
1034
  public setTimeout(callback: Function, delay = 0, ...args: unknown[]): NodeJS.Timeout {
1035
+ // We can group timeouts with a delay of 0 into one timeout to improve performance.
1036
+ // Grouping timeouts will also improve the performance of the async task manager.
1037
+ // It may also make the async task manager to stable as many timeouts may cause waitUntilComplete() to be resolved to early.
1038
+ if (!delay) {
1039
+ if (!this.#zeroTimeouts) {
1040
+ const settings = this.#browserFrame.page?.context?.browser?.settings;
1041
+ const useTryCatch =
1042
+ !settings ||
1043
+ !settings.disableErrorCapturing ||
1044
+ settings.errorCapture === BrowserErrorCaptureEnum.tryAndCatch;
1045
+ const id = TIMER.setTimeout(() => {
1046
+ const zeroTimeouts = this.#zeroTimeouts;
1047
+ this.#zeroTimeouts = null;
1048
+ for (const zeroTimeout of zeroTimeouts) {
1049
+ if (useTryCatch) {
1050
+ WindowErrorUtility.captureError(this, () => zeroTimeout.callback());
1051
+ } else {
1052
+ zeroTimeout.callback();
1053
+ }
1054
+ }
1055
+ this.#browserFrame[PropertySymbol.asyncTaskManager].endTimer(id);
1056
+ });
1057
+ this.#zeroTimeouts = [];
1058
+ this.#browserFrame[PropertySymbol.asyncTaskManager].startTimer(id);
1059
+ }
1060
+ const zeroTimeout = new Timeout(() => callback(...args));
1061
+ this.#zeroTimeouts.push(zeroTimeout);
1062
+ return <NodeJS.Timeout>(<unknown>zeroTimeout);
1063
+ }
1064
+
1021
1065
  const settings = this.#browserFrame.page?.context?.browser?.settings;
1022
1066
  const useTryCatch =
1023
1067
  !settings ||
1024
1068
  !settings.disableErrorCapturing ||
1025
1069
  settings.errorCapture === BrowserErrorCaptureEnum.tryAndCatch;
1026
- const id = TIMER.setTimeout(() => {
1027
- if (useTryCatch) {
1028
- WindowErrorUtility.captureError(this, () => callback(...args));
1029
- } else {
1030
- callback(...args);
1031
- }
1032
- this.#browserFrame[PropertySymbol.asyncTaskManager].endTimer(id);
1033
- }, delay);
1070
+
1071
+ const id = TIMER.setTimeout(
1072
+ () => {
1073
+ if (useTryCatch) {
1074
+ WindowErrorUtility.captureError(this, () => callback(...args));
1075
+ } else {
1076
+ callback(...args);
1077
+ }
1078
+ this.#browserFrame[PropertySymbol.asyncTaskManager].endTimer(id);
1079
+ },
1080
+ settings?.timer.maxTimeout !== -1 && delay && delay > settings?.timer.maxTimeout
1081
+ ? settings?.timer.maxTimeout
1082
+ : delay
1083
+ );
1034
1084
  this.#browserFrame[PropertySymbol.asyncTaskManager].startTimer(id);
1035
1085
  return id;
1036
1086
  }
@@ -1041,6 +1091,14 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal
1041
1091
  * @param id ID of the timeout.
1042
1092
  */
1043
1093
  public clearTimeout(id: NodeJS.Timeout): void {
1094
+ if (id && id instanceof Timeout) {
1095
+ const zeroTimeouts = this.#zeroTimeouts || [];
1096
+ const index = zeroTimeouts.indexOf(<Timeout>(<unknown>id));
1097
+ if (index !== -1) {
1098
+ zeroTimeouts.splice(index, 1);
1099
+ }
1100
+ return;
1101
+ }
1044
1102
  // We need to make sure that the ID is a Timeout object, otherwise Node.js might throw an error.
1045
1103
  // This is only necessary if we are in a Node.js environment.
1046
1104
  if (IS_NODE_JS_TIMEOUT_ENVIRONMENT && (!id || id.constructor.name !== 'Timeout')) {
@@ -1064,17 +1122,29 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal
1064
1122
  !settings ||
1065
1123
  !settings.disableErrorCapturing ||
1066
1124
  settings.errorCapture === BrowserErrorCaptureEnum.tryAndCatch;
1067
- const id = TIMER.setInterval(() => {
1068
- if (useTryCatch) {
1069
- WindowErrorUtility.captureError(
1070
- this,
1071
- () => callback(...args),
1072
- () => this.clearInterval(id)
1073
- );
1074
- } else {
1075
- callback(...args);
1076
- }
1077
- }, delay);
1125
+ let iterations = 0;
1126
+ const id = TIMER.setInterval(
1127
+ () => {
1128
+ if (useTryCatch) {
1129
+ WindowErrorUtility.captureError(
1130
+ this,
1131
+ () => callback(...args),
1132
+ () => this.clearInterval(id)
1133
+ );
1134
+ } else {
1135
+ callback(...args);
1136
+ }
1137
+ if (settings?.timer.maxIntervalIterations !== -1) {
1138
+ if (iterations >= settings?.timer.maxIntervalIterations) {
1139
+ this.clearInterval(id);
1140
+ }
1141
+ iterations++;
1142
+ }
1143
+ },
1144
+ settings?.timer.maxIntervalTime !== -1 && delay && delay > settings?.timer.maxIntervalTime
1145
+ ? settings?.timer.maxIntervalTime
1146
+ : delay
1147
+ );
1078
1148
  this.#browserFrame[PropertySymbol.asyncTaskManager].startTimer(id);
1079
1149
  return id;
1080
1150
  }
@@ -1319,6 +1389,8 @@ export default class BrowserWindow extends EventTarget implements INodeJSGlobal
1319
1389
  mutationObserver.disconnect();
1320
1390
  }
1321
1391
 
1392
+ this[PropertySymbol.mutationObservers] = [];
1393
+
1322
1394
  // Disconnects nodes from the document, so that they can be garbage collected.
1323
1395
  for (const node of this.document[PropertySymbol.childNodes].slice()) {
1324
1396
  // Makes sure that something won't be triggered by the disconnect.