Skip to content

Commit 82a63be

Browse files
committed
fix error events not working
1 parent e16a89c commit 82a63be

File tree

5 files changed

+255
-0
lines changed

5 files changed

+255
-0
lines changed

.changeset/nasty-spoons-wonder.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@remote-dom/polyfill': patch
3+
---
4+
5+
fix error events not working
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import {Event} from './Event.ts';
2+
3+
// https://html.spec.whatwg.org/multipage/webappapis.html#errorevent
4+
export class ErrorEvent extends Event {
5+
readonly message: ErrorEventInit['message'];
6+
readonly filename: ErrorEventInit['filename'];
7+
readonly lineno: ErrorEventInit['lineno'];
8+
readonly colno: ErrorEventInit['colno'];
9+
readonly error: ErrorEventInit['error'];
10+
11+
constructor(type: string, eventInitDict: ErrorEventInit) {
12+
super(type, eventInitDict);
13+
14+
this.message = eventInitDict.message;
15+
this.filename = eventInitDict.filename;
16+
this.lineno = eventInitDict.lineno;
17+
this.colno = eventInitDict.colno;
18+
this.error = eventInitDict.error;
19+
}
20+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import {Event} from './Event.ts';
2+
3+
//https://html.spec.whatwg.org/multipage/webappapis.html#promiserejectionevent
4+
export class PromiseRejectionEvent extends Event {
5+
readonly promise: PromiseRejectionEventInit['promise'];
6+
readonly reason: PromiseRejectionEventInit['reason'];
7+
8+
constructor(type: string, eventInitDict: PromiseRejectionEventInit) {
9+
super(type, eventInitDict);
10+
11+
this.promise = eventInitDict.promise;
12+
this.reason = eventInitDict.reason;
13+
}
14+
}

packages/polyfill/source/Window.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import {Document} from './Document.ts';
22
import {Event} from './Event.ts';
33
import {EventTarget} from './EventTarget.ts';
44
import {CustomEvent} from './CustomEvent.ts';
5+
import {ErrorEvent} from './ErrorEvent.ts';
6+
import {PromiseRejectionEvent} from './PromiseRejectionEvent.ts';
57
import {Node} from './Node.ts';
68
import {ParentNode} from './ParentNode.ts';
79
import {ChildNode} from './ChildNode.ts';
@@ -18,6 +20,16 @@ import {MutationObserver} from './MutationObserver.ts';
1820
import {HOOKS} from './constants.ts';
1921
import type {Hooks} from './hooks.ts';
2022

23+
type OnErrorHandler =
24+
| ((
25+
message: string,
26+
filename?: string,
27+
lineno?: number,
28+
colno?: number,
29+
error?: any,
30+
) => void)
31+
| null;
32+
2133
export class Window extends EventTarget {
2234
[HOOKS]: Partial<Hooks> = {};
2335
name = '';
@@ -30,6 +42,8 @@ export class Window extends EventTarget {
3042
location = globalThis.location;
3143
navigator = globalThis.navigator;
3244
Event = Event;
45+
ErrorEvent = ErrorEvent;
46+
PromiseRejectionEvent = PromiseRejectionEvent;
3347
EventTarget = EventTarget;
3448
CustomEvent = CustomEvent;
3549
Node = Node;
@@ -46,6 +60,59 @@ export class Window extends EventTarget {
4660
HTMLTemplateElement = HTMLTemplateElement;
4761
MutationObserver = MutationObserver;
4862

63+
#currentOnErrorHandler: ((event: any) => void) | null = null;
64+
#currentOriginalOnErrorHandler: OnErrorHandler = null;
65+
#currentOnUnhandledRejectionHandler: WindowEventHandlers['onunhandledrejection'] =
66+
null;
67+
68+
get onerror() {
69+
return this.#currentOriginalOnErrorHandler as OnErrorEventHandler;
70+
}
71+
set onerror(handler: OnErrorHandler) {
72+
if (this.#currentOnErrorHandler) {
73+
this.removeEventListener('error', this.#currentOnErrorHandler);
74+
}
75+
if (handler && typeof handler === 'function') {
76+
this.#currentOriginalOnErrorHandler = handler;
77+
this.#currentOnErrorHandler = (event: ErrorEvent) => {
78+
handler(
79+
event.message ?? 'Error',
80+
event.filename,
81+
event.lineno,
82+
event.colno,
83+
event.error,
84+
);
85+
};
86+
this.addEventListener('error', this.#currentOnErrorHandler);
87+
} else {
88+
this.#currentOnErrorHandler = null;
89+
this.#currentOriginalOnErrorHandler = null;
90+
}
91+
}
92+
93+
get onunhandledrejection() {
94+
return this.#currentOnUnhandledRejectionHandler;
95+
}
96+
set onunhandledrejection(
97+
handler: WindowEventHandlers['onunhandledrejection'],
98+
) {
99+
if (this.#currentOnUnhandledRejectionHandler) {
100+
this.removeEventListener(
101+
'unhandledrejection',
102+
this.#currentOnUnhandledRejectionHandler as any,
103+
);
104+
}
105+
if (handler && typeof handler === 'function') {
106+
this.#currentOnUnhandledRejectionHandler = handler;
107+
this.addEventListener(
108+
'unhandledrejection',
109+
this.#currentOnUnhandledRejectionHandler as any,
110+
);
111+
} else {
112+
this.#currentOnUnhandledRejectionHandler = null;
113+
}
114+
}
115+
49116
static setGlobal(window: Window) {
50117
const properties = Object.getOwnPropertyDescriptors(window);
51118

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import {ErrorEvent} from '../ErrorEvent.ts';
2+
import {PromiseRejectionEvent} from '../PromiseRejectionEvent.ts';
3+
import {Window} from '../Window.ts';
4+
import {describe, it, expect, beforeEach, afterEach, vi} from 'vitest';
5+
6+
describe('global errors', () => {
7+
describe('onerror', () => {
8+
let originalOnerror: any;
9+
10+
beforeEach(() => {
11+
originalOnerror = globalThis.onerror;
12+
});
13+
14+
afterEach(() => {
15+
globalThis.onerror = originalOnerror;
16+
});
17+
18+
it('sets onerror handler on window instance', () => {
19+
const handler = vi.fn();
20+
const window = new Window();
21+
22+
window.onerror = handler;
23+
expect(window.onerror).toBe(handler);
24+
});
25+
26+
it('overrides onerror handler on window instance', () => {
27+
const handler = vi.fn();
28+
const handler2 = vi.fn();
29+
30+
const window = new Window();
31+
window.onerror = handler;
32+
window.onerror = handler2;
33+
34+
expect(window.onerror).toBe(handler2);
35+
});
36+
37+
it('calls handler when error occurs', () => {
38+
const handler = vi.fn();
39+
const window = new Window();
40+
41+
const error = new Error('test');
42+
43+
window.onerror = handler;
44+
window.dispatchEvent(
45+
new ErrorEvent('error', {
46+
error,
47+
filename: 'test.ts',
48+
lineno: 1,
49+
colno: 1,
50+
}),
51+
);
52+
53+
expect(handler).toHaveBeenCalledWith('Error', 'test.ts', 1, 1, error);
54+
});
55+
56+
it('allows null assignment', () => {
57+
const handler = vi.fn();
58+
const window = new Window();
59+
window.onerror = handler;
60+
expect(window.onerror).toBe(handler);
61+
62+
window.onerror = null;
63+
expect(window.onerror).toBe(null);
64+
});
65+
66+
it('works when window is set as global', () => {
67+
const window = new Window();
68+
Window.setGlobalThis(window);
69+
const handler = vi.fn();
70+
window.onerror = handler;
71+
72+
expect(window.onerror).toBe(handler);
73+
});
74+
});
75+
76+
describe('onunhandledrejection', () => {
77+
let originalOnunhandledrejection: any;
78+
79+
beforeEach(() => {
80+
originalOnunhandledrejection = globalThis.onunhandledrejection;
81+
});
82+
83+
afterEach(() => {
84+
globalThis.onunhandledrejection = originalOnunhandledrejection;
85+
});
86+
87+
it('sets onunhandledrejection handler on window instance', () => {
88+
const handler = vi.fn();
89+
const window = new Window();
90+
91+
window.onunhandledrejection = handler;
92+
expect(window.onunhandledrejection).toBe(handler);
93+
});
94+
95+
it('overrides onunhandledrejection handler on window instance', () => {
96+
const handler = vi.fn();
97+
const handler2 = vi.fn();
98+
const window = new Window();
99+
100+
window.onunhandledrejection = handler;
101+
window.onunhandledrejection = handler2;
102+
expect(window.onunhandledrejection).toBe(handler2);
103+
});
104+
105+
it('calls handler when unhandled rejection occurs', () => {
106+
const handler = vi.fn();
107+
const window = new Window();
108+
window.onunhandledrejection = handler;
109+
110+
const error = new Error('test');
111+
const promise = Promise.resolve();
112+
113+
window.dispatchEvent(
114+
new PromiseRejectionEvent('unhandledrejection', {
115+
promise: promise,
116+
reason: error,
117+
}),
118+
);
119+
120+
expect(handler).toHaveBeenCalledWith(
121+
expect.objectContaining({
122+
promise,
123+
reason: error,
124+
}),
125+
);
126+
127+
expect(handler).toHaveBeenCalledWith(expect.any(PromiseRejectionEvent));
128+
});
129+
130+
it('allows null assignment', () => {
131+
const handler = vi.fn();
132+
const window = new Window();
133+
window.onunhandledrejection = handler;
134+
expect(window.onunhandledrejection).toBe(handler);
135+
136+
window.onunhandledrejection = null;
137+
expect(window.onunhandledrejection).toBe(null);
138+
});
139+
140+
it('works when window is set as global', () => {
141+
const window = new Window();
142+
Window.setGlobalThis(window);
143+
const handler = vi.fn();
144+
window.onunhandledrejection = handler;
145+
146+
expect(window.onunhandledrejection).toBe(handler);
147+
});
148+
});
149+
});

0 commit comments

Comments
 (0)