Merge pull request #523 from stack3mpty/fix/missing-event-constructors-521-522

fix(js): add global PromiseRejectionEvent and StorageEvent constructors
This commit is contained in:
SGavrl authored and GitHub committed 2026-07-31 17:43:25 +00:00
commit 0def838088
2 files changed
+138 -1

No files matched your search

+43 -1
View File
@@ -2613,6 +2613,13 @@ class Document extends Node {
// returned a generic Event for every type, which broke libraries that call // returned a generic Event for every type, which broke libraries that call
// createEvent('CustomEvent').initCustomEvent(...) — see issue #41. // createEvent('CustomEvent').initCustomEvent(...) — see issue #41.
createEvent(type) { createEvent(type) {
const normalized = String(type || '').toLowerCase();
if (normalized === 'promiserejectionevent') {
throw new DOMException(
"The provided event type ('PromiseRejectionEvent') is invalid",
'NotSupportedError'
);
}
const map = { const map = {
'customevent': CustomEvent, 'customevents': CustomEvent, 'customevent': CustomEvent, 'customevents': CustomEvent,
'mouseevent': MouseEvent, 'mouseevents': MouseEvent, 'mouseevent': MouseEvent, 'mouseevents': MouseEvent,
@@ -2627,8 +2634,9 @@ class Document extends Node {
'popstateevent': PopStateEvent, 'popstateevent': PopStateEvent,
'animationevent': AnimationEvent, 'animationevent': AnimationEvent,
'transitionevent': TransitionEvent, 'transitionevent': TransitionEvent,
'storageevent': StorageEvent,
}; };
const Cls = map[String(type || '').toLowerCase()] || Event; const Cls = map[normalized] || Event;
return new Cls(''); return new Cls('');
} }
createRange() { return new Range(); } createRange() { return new Range(); }
@@ -5007,6 +5015,40 @@ globalThis.ToggleEvent = class ToggleEvent extends Event {
}; };
_markNative(globalThis.ToggleEvent); _markNative(globalThis.ToggleEvent);
globalThis.PromiseRejectionEvent = class PromiseRejectionEvent extends Event {
constructor(type, init) {
if (arguments.length < 2 || init == null || !('promise' in Object(init))) {
throw new TypeError(
"Failed to construct 'PromiseRejectionEvent': required member promise is undefined."
);
}
super(type, init);
this.promise = init.promise;
this.reason = init.reason;
}
};
_markNative(globalThis.PromiseRejectionEvent);
globalThis.StorageEvent = class StorageEvent extends Event {
constructor(type, init = {}) {
super(type, init);
this.key = init.key !== undefined ? init.key : null;
this.oldValue = init.oldValue !== undefined ? init.oldValue : null;
this.newValue = init.newValue !== undefined ? init.newValue : null;
this.url = init.url || "";
this.storageArea = init.storageArea || null;
}
initStorageEvent(type, bubbles, cancelable, key, oldValue, newValue, url, storageArea) {
this.initEvent(type, bubbles, cancelable);
this.key = key !== undefined ? key : null;
this.oldValue = oldValue !== undefined ? oldValue : null;
this.newValue = newValue !== undefined ? newValue : null;
this.url = url || "";
this.storageArea = storageArea || null;
}
};
_markNative(globalThis.StorageEvent);
// AbortController / AbortSignal. AbortSignal is a real constructor with a // AbortController / AbortSignal. AbortSignal is a real constructor with a
// prototype, so feature-detection and `AbortSignal.prototype` access work. It // prototype, so feature-detection and `AbortSignal.prototype` access work. It
// carries aborted/reason, supports throwIfAborted(), and fires "abort" to // carries aborted/reason, supports throwIfAborted(), and fires "abort" to
+95
View File
@@ -3085,6 +3085,101 @@ mod tests {
assert_eq!(kind, serde_json::json!(true)); assert_eq!(kind, serde_json::json!(true));
} }
#[test]
fn test_promise_rejection_event_requires_promise() {
let mut rt = setup_runtime("<html><body></body></html>");
let result = rt
.evaluate(
r#"(() => {
const promise = Promise.resolve(1);
const event = new PromiseRejectionEvent('unhandledrejection', {
promise,
reason: 'failed'
});
let missingPromiseThrows = false;
try {
new PromiseRejectionEvent('unhandledrejection');
} catch (error) {
missingPromiseThrows = error instanceof TypeError;
}
return [
event instanceof Event,
event.promise === promise,
event.reason === 'failed',
missingPromiseThrows
];
})()"#,
)
.unwrap();
assert_eq!(result, serde_json::json!([true, true, true, true]));
}
#[test]
fn test_create_event_rejects_promise_rejection_event() {
let mut rt = setup_runtime("<html><body></body></html>");
let result = rt
.evaluate(
r#"(() => {
try {
document.createEvent('PromiseRejectionEvent');
return null;
} catch (error) {
return [error.name, error instanceof DOMException];
}
})()"#,
)
.unwrap();
assert_eq!(
result,
serde_json::json!(["NotSupportedError", true])
);
}
#[test]
fn test_storage_event_constructor_and_legacy_factory() {
let mut rt = setup_runtime("<html><body></body></html>");
let result = rt
.evaluate(
r#"(() => {
const event = new StorageEvent('storage', {
key: 'theme',
oldValue: 'light',
newValue: 'dark',
url: 'https://example.test/'
});
const legacy = document.createEvent('StorageEvent');
legacy.initStorageEvent(
'storage', false, false, 'count', '1', '2',
'https://example.test/', null
);
return [
event instanceof Event,
event.key,
event.oldValue,
event.newValue,
event.url,
legacy instanceof StorageEvent,
legacy.key,
legacy.newValue
];
})()"#,
)
.unwrap();
assert_eq!(
result,
serde_json::json!([
true,
"theme",
"light",
"dark",
"https://example.test/",
true,
"count",
"2"
])
);
}
#[test] #[test]
fn test_html_to_markdown_headings() { fn test_html_to_markdown_headings() {
let mut rt = setup_runtime("<html><body><h1>Title</h1><h2>Sub</h2><p>Body</p></body></html>"); let mut rt = setup_runtime("<html><body><h1>Title</h1><h2>Sub</h2><p>Body</p></body></html>");