Skip to content

Commit

Permalink
Merge pull request #1074 from capricorn86/task/1073-make-it-possible-…
Browse files Browse the repository at this point in the history
…to-disable-error-catching

#1073@minor: Adds support for a new setting called disableErrorCaptur…
  • Loading branch information
capricorn86 authored Sep 18, 2023
2 parents 89e4f76 + aec6034 commit bbec9cf
Show file tree
Hide file tree
Showing 14 changed files with 201 additions and 111 deletions.
18 changes: 12 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 13 additions & 5 deletions packages/happy-dom/src/event/EventTarget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,12 @@ export default abstract class EventTarget implements IEventTarget {

if (typeof this[onEventName] === 'function') {
// We can end up in a never ending loop if the listener for the error event on Window also throws an error.
if (window && (this !== <IEventTarget>window || event.type !== 'error')) {
WindowErrorUtility.captureErrorSync(window, this[onEventName].bind(this, event));
if (
window &&
(this !== <IEventTarget>window || event.type !== 'error') &&
!window.happyDOM.settings.disableErrorCapturing
) {
WindowErrorUtility.captureError(window, this[onEventName].bind(this, event));
} else {
this[onEventName].call(this, event);
}
Expand Down Expand Up @@ -169,14 +173,18 @@ export default abstract class EventTarget implements IEventTarget {
}

// We can end up in a never ending loop if the listener for the error event on Window also throws an error.
if (window && (this !== <IEventTarget>window || event.type !== 'error')) {
if (
window &&
(this !== <IEventTarget>window || event.type !== 'error') &&
!window.happyDOM.settings.disableErrorCapturing
) {
if ((<IEventListener>listener).handleEvent) {
WindowErrorUtility.captureErrorSync(
WindowErrorUtility.captureError(
window,
(<IEventListener>listener).handleEvent.bind(this, event)
);
} else {
WindowErrorUtility.captureErrorSync(
WindowErrorUtility.captureError(
window,
(<(event: Event) => void>listener).bind(this, event)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,32 +24,41 @@ export default class HTMLLinkElementUtility {

if (href !== null && rel && rel.toLowerCase() === 'stylesheet' && element.isConnected) {
if (element.ownerDocument.defaultView.happyDOM.settings.disableCSSFileLoading) {
WindowErrorUtility.dispatchError(
element,
new DOMException(
`Failed to load external stylesheet "${href}". CSS file loading is disabled.`,
DOMExceptionNameEnum.notSupportedError
)
const error = new DOMException(
`Failed to load external stylesheet "${href}". CSS file loading is disabled.`,
DOMExceptionNameEnum.notSupportedError
);

WindowErrorUtility.dispatchError(element, error);
if (element.ownerDocument.defaultView.happyDOM.settings.disableErrorCapturing) {
throw error;
}
return;
}

(<Document>element.ownerDocument)._readyStateManager.startTask();

const code: string | null = await WindowErrorUtility.captureErrorAsync<string>(
element,
async () => await ResourceFetch.fetch(element.ownerDocument, href)
);
let code: string | null = null;
let error: Error | null = null;

try {
code = await ResourceFetch.fetch(element.ownerDocument, href);
} catch (e) {
error = e;
}

if (code) {
(<Document>element.ownerDocument)._readyStateManager.endTask();

if (error) {
WindowErrorUtility.dispatchError(element, error);
if (element.ownerDocument.defaultView.happyDOM.settings.disableErrorCapturing) {
throw error;
}
} else {
const styleSheet = new CSSStyleSheet();
styleSheet.replaceSync(code);
(<CSSStyleSheet>element.sheet) = styleSheet;
element.dispatchEvent(new Event('load'));
}

(<Document>element.ownerDocument)._readyStateManager.endTask();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -189,9 +189,13 @@ export default class HTMLScriptElement extends HTMLElement implements IHTMLScrip
type === 'application/x-javascript' ||
type.startsWith('text/javascript'))
) {
WindowErrorUtility.captureErrorSync(this.ownerDocument.defaultView, () =>
this.ownerDocument.defaultView.eval(textContent)
);
if (this.ownerDocument.defaultView.happyDOM.settings.disableErrorCapturing) {
this.ownerDocument.defaultView.eval(textContent);
} else {
WindowErrorUtility.captureError(this.ownerDocument.defaultView, () =>
this.ownerDocument.defaultView.eval(textContent)
);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,40 +25,69 @@ export default class HTMLScriptElementUtility {
element.ownerDocument.defaultView.happyDOM.settings.disableJavaScriptFileLoading ||
element.ownerDocument.defaultView.happyDOM.settings.disableJavaScriptEvaluation
) {
WindowErrorUtility.dispatchError(
element,
new DOMException(
`Failed to load external script "${src}". JavaScript file loading is disabled.`,
DOMExceptionNameEnum.notSupportedError
)
const error = new DOMException(
`Failed to load external script "${src}". JavaScript file loading is disabled.`,
DOMExceptionNameEnum.notSupportedError
);
WindowErrorUtility.dispatchError(element, error);
if (element.ownerDocument.defaultView.happyDOM.settings.disableErrorCapturing) {
throw error;
}
return;
}

if (async) {
(<Document>element.ownerDocument)._readyStateManager.startTask();

const code = await WindowErrorUtility.captureErrorAsync<string>(
element,
async () => await ResourceFetch.fetch(element.ownerDocument, src)
);
let code: string | null = null;
let error: Error | null = null;

if (code) {
WindowErrorUtility.captureErrorSync(element.ownerDocument.defaultView, () =>
element.ownerDocument.defaultView.eval(code)
);
element.dispatchEvent(new Event('load'));
try {
code = await ResourceFetch.fetch(element.ownerDocument, src);
} catch (e) {
error = e;
}

(<Document>element.ownerDocument)._readyStateManager.endTask();

if (error) {
WindowErrorUtility.dispatchError(element, error);
if (element.ownerDocument.defaultView.happyDOM.settings.disableErrorCapturing) {
throw error;
}
} else {
if (element.ownerDocument.defaultView.happyDOM.settings.disableErrorCapturing) {
element.ownerDocument.defaultView.eval(code);
} else {
WindowErrorUtility.captureError(element.ownerDocument.defaultView, () =>
element.ownerDocument.defaultView.eval(code)
);
}
element.dispatchEvent(new Event('load'));
}
} else {
const code = WindowErrorUtility.captureErrorSync<string>(element, () =>
ResourceFetch.fetchSync(element.ownerDocument, src)
);
let code: string | null = null;
let error: Error | null = null;

try {
code = ResourceFetch.fetchSync(element.ownerDocument, src);
} catch (e) {
error = e;
}

if (code) {
WindowErrorUtility.captureErrorSync(element.ownerDocument.defaultView, () =>
element.ownerDocument.defaultView.eval(code)
);
if (error) {
WindowErrorUtility.dispatchError(element, error);
if (element.ownerDocument.defaultView.happyDOM.settings.disableErrorCapturing) {
throw error;
}
} else {
if (element.ownerDocument.defaultView.happyDOM.settings.disableErrorCapturing) {
element.ownerDocument.defaultView.eval(code);
} else {
WindowErrorUtility.captureError(element.ownerDocument.defaultView, () =>
element.ownerDocument.defaultView.eval(code)
);
}
element.dispatchEvent(new Event('load'));
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/happy-dom/src/window/IHappyDOMOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default interface IHappyDOMOptions {
disableCSSFileLoading?: boolean;
disableIframePageLoading?: boolean;
disableComputedStyleRendering?: boolean;
disableErrorCapturing?: boolean;
enableFileSystemHttpRequests?: boolean;
navigator?: {
userAgent?: string;
Expand Down
1 change: 1 addition & 0 deletions packages/happy-dom/src/window/IHappyDOMSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export default interface IHappyDOMSettings {
disableCSSFileLoading: boolean;
disableIframePageLoading: boolean;
disableComputedStyleRendering: boolean;
disableErrorCapturing: boolean;
enableFileSystemHttpRequests: boolean;
navigator: {
userAgent: string;
Expand Down
28 changes: 21 additions & 7 deletions packages/happy-dom/src/window/Window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ export default class Window extends EventTarget implements IWindow {
disableCSSFileLoading: false,
disableIframePageLoading: false,
disableComputedStyleRendering: false,
disableErrorCapturing: false,
enableFileSystemHttpRequests: false,
navigator: {
userAgent: `Mozilla/5.0 (X11; ${
Expand Down Expand Up @@ -736,7 +737,11 @@ export default class Window extends EventTarget implements IWindow {
public setTimeout(callback: Function, delay = 0, ...args: unknown[]): NodeJS.Timeout {
const id = this._setTimeout(() => {
this.happyDOM.asyncTaskManager.endTimer(id);
WindowErrorUtility.captureErrorSync(this, () => callback(...args));
if (this.happyDOM.settings.disableErrorCapturing) {
callback(...args);
} else {
WindowErrorUtility.captureError(this, () => callback(...args));
}
}, delay);
this.happyDOM.asyncTaskManager.startTimer(id);
return id;
Expand All @@ -762,11 +767,15 @@ export default class Window extends EventTarget implements IWindow {
*/
public setInterval(callback: Function, delay = 0, ...args: unknown[]): NodeJS.Timeout {
const id = this._setInterval(() => {
WindowErrorUtility.captureErrorSync(
this,
() => callback(...args),
() => this.clearInterval(id)
);
if (this.happyDOM.settings.disableErrorCapturing) {
callback(...args);
} else {
WindowErrorUtility.captureError(
this,
() => callback(...args),
() => this.clearInterval(id)
);
}
}, delay);
this.happyDOM.asyncTaskManager.startTimer(id);
return id;
Expand Down Expand Up @@ -811,8 +820,13 @@ export default class Window extends EventTarget implements IWindow {
const taskId = this.happyDOM.asyncTaskManager.startTask(() => (isAborted = true));
this._queueMicrotask(() => {
if (!isAborted) {
WindowErrorUtility.captureErrorSync(this, <() => unknown>callback);
this.happyDOM.asyncTaskManager.endTask(taskId);

if (this.happyDOM.settings.disableErrorCapturing) {
callback();
} else {
WindowErrorUtility.captureError(this, <() => unknown>callback);
}
}
});
}
Expand Down
28 changes: 1 addition & 27 deletions packages/happy-dom/src/window/WindowErrorUtility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,6 @@ import { IElement } from '../index.js';
* Error utility.
*/
export default class WindowErrorUtility {
/**
* Calls a function asynchronously wrapped in a try/catch block to capture errors and dispatch error events.
*
* It will also output the errors to the console.
*
* @param elementOrWindow Element or Window.
* @param callback Callback.
* @param [cleanup] Cleanup callback on error.
* @returns Promise.
*/
public static async captureErrorAsync<T>(
elementOrWindow: IWindow | IElement,
callback: () => Promise<T>,
cleanup?: () => void
): Promise<T | null> {
try {
return await callback();
} catch (error) {
this.dispatchError(elementOrWindow, error);
if (cleanup) {
cleanup();
}
}
return null;
}

/**
* Calls a function synchronously wrapped in a try/catch block to capture errors and dispatch error events.
* If the callback returns a Promise, it will catch errors from the promise.
Expand All @@ -43,7 +17,7 @@ export default class WindowErrorUtility {
* @param [cleanup] Cleanup callback on error.
* @returns Result.
*/
public static captureErrorSync<T>(
public static captureError<T>(
elementOrWindow: IWindow | IElement,
callback: () => T,
cleanup?: () => void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -485,5 +485,17 @@ describe('HTMLScriptElement', () => {
true
);
});

it('Throws an exception when appending an element that contains invalid Javascript and Window.happyDOM.settings.disableErrorCapturing is set to true.', () => {
const element = <IHTMLScriptElement>document.createElement('script');

window.happyDOM.settings.disableErrorCapturing = true;

element.text = 'globalThis.test = /;';

expect(() => {
document.body.appendChild(element);
}).toThrow(new TypeError('Invalid regular expression: missing /'));
});
});
});
Loading

0 comments on commit bbec9cf

Please sign in to comment.