document.createEvent('CustomEvent') was returning a generic Event,
so the legacy DOM Level 2 pattern
var e = document.createEvent('CustomEvent');
e.initCustomEvent('foo', true, false, payload);
threw "TypeError: e.initCustomEvent is not a function" because the
returned object was an Event, not a CustomEvent — and CustomEvent
didn't define initCustomEvent anyway. The Starbucks China bundle
(and a long tail of other CDN bundles using the same shim) depended
on this and aborted bootstrap.
Two changes in crates/obscura-js/js/bootstrap.js:
1. Add CustomEvent.prototype.initCustomEvent(type, bubbles, cancelable,
detail) matching the spec.
2. Rewrite Document.prototype.createEvent so it dispatches to the
right class based on the type string ('CustomEvent', 'MouseEvent',
'MouseEvents', 'KeyboardEvent', 'FocusEvent', 'InputEvent',
'UIEvent', 'WheelEvent', 'PointerEvent', 'ErrorEvent',
'PopStateEvent', 'AnimationEvent', 'TransitionEvent', and
case-insensitive 'Events' aliases). The HTMLDocument variant on
the second Document class delegates to the canonical impl.
Spec note: createEvent returns the event with an empty type until
init*Event() is called — matched exactly.
Four new unit tests cover initCustomEvent presence and field setters,
class dispatch for CustomEvent / MouseEvent / MouseEvents / KeyboardEvent,
and the unknown-type fallback to Event.
Generated by Ora Studio
Vibe coded by ousamabenyounes
Co-authored-by: Ora Studio <[email protected]>
This commit is contained in:
1 parent
86a868bfd6
commit
d50dd0f553
2 files changed
+95
-3
No files matched your search
Vendored
+35
-3
@@ -825,7 +825,28 @@ class Document extends Node {
|
|||||||
_cache.set(nid, frag);
|
_cache.set(nid, frag);
|
||||||
return frag;
|
return frag;
|
||||||
}
|
}
|
||||||
createEvent(type) { return new Event(type); }
|
// Legacy DOM Level 2 event factory. Spec returns an event of the requested
|
||||||
|
// class with an empty type until init*Event() is called. We previously
|
||||||
|
// returned a generic Event for every type, which broke libraries that call
|
||||||
|
// createEvent('CustomEvent').initCustomEvent(...) — see issue #41.
|
||||||
|
createEvent(type) {
|
||||||
|
const map = {
|
||||||
|
'customevent': CustomEvent, 'customevents': CustomEvent,
|
||||||
|
'mouseevent': MouseEvent, 'mouseevents': MouseEvent,
|
||||||
|
'keyboardevent': KeyboardEvent, 'keyboardevents': KeyboardEvent,
|
||||||
|
'focusevent': FocusEvent,
|
||||||
|
'inputevent': InputEvent,
|
||||||
|
'uievent': UIEvent, 'uievents': UIEvent,
|
||||||
|
'wheelevent': WheelEvent,
|
||||||
|
'pointerevent': PointerEvent,
|
||||||
|
'errorevent': ErrorEvent,
|
||||||
|
'popstateevent': PopStateEvent,
|
||||||
|
'animationevent': AnimationEvent,
|
||||||
|
'transitionevent': TransitionEvent,
|
||||||
|
};
|
||||||
|
const Cls = map[String(type || '').toLowerCase()] || Event;
|
||||||
|
return new Cls('');
|
||||||
|
}
|
||||||
createRange() { return { setStart(){}, setEnd(){}, collapse(){}, selectNodeContents(){}, cloneContents(){ return document.createDocumentFragment(); } }; }
|
createRange() { return { setStart(){}, setEnd(){}, collapse(){}, selectNodeContents(){}, cloneContents(){ return document.createDocumentFragment(); } }; }
|
||||||
addEventListener(type, fn, opts) {} removeEventListener() {} dispatchEvent() { return true; }
|
addEventListener(type, fn, opts) {} removeEventListener() {} dispatchEvent() { return true; }
|
||||||
createTreeWalker(root, whatToShow, filter) {
|
createTreeWalker(root, whatToShow, filter) {
|
||||||
@@ -1890,7 +1911,18 @@ globalThis.Event = class Event {
|
|||||||
preventDefault() { this.defaultPrevented=true; } stopPropagation(){} stopImmediatePropagation(){}
|
preventDefault() { this.defaultPrevented=true; } stopPropagation(){} stopImmediatePropagation(){}
|
||||||
initEvent(type,bubbles,cancelable) { this.type=type;this.bubbles=!!bubbles;this.cancelable=!!cancelable; }
|
initEvent(type,bubbles,cancelable) { this.type=type;this.bubbles=!!bubbles;this.cancelable=!!cancelable; }
|
||||||
};
|
};
|
||||||
globalThis.CustomEvent = class extends Event { constructor(t,o={}) { super(t,o);this.detail=o.detail; } };
|
globalThis.CustomEvent = class extends Event {
|
||||||
|
constructor(t,o={}) { super(t,o);this.detail=o.detail; }
|
||||||
|
// Legacy DOM Level 2 init; some libraries (Starbucks China bundle, older
|
||||||
|
// analytics shims) still call createEvent('CustomEvent') + initCustomEvent
|
||||||
|
// instead of new CustomEvent(...). See issue #41.
|
||||||
|
initCustomEvent(type,bubbles,cancelable,detail) {
|
||||||
|
this.type = type;
|
||||||
|
this.bubbles = !!bubbles;
|
||||||
|
this.cancelable = !!cancelable;
|
||||||
|
this.detail = detail;
|
||||||
|
}
|
||||||
|
};
|
||||||
globalThis.MouseEvent = class extends Event { constructor(t,o={}) { super(t,o);this.clientX=o.clientX||0;this.clientY=o.clientY||0; } };
|
globalThis.MouseEvent = class extends Event { constructor(t,o={}) { super(t,o);this.clientX=o.clientX||0;this.clientY=o.clientY||0; } };
|
||||||
globalThis.KeyboardEvent = class extends Event { constructor(t,o={}) { super(t,o);this.key=o.key||"";this.code=o.code||""; } };
|
globalThis.KeyboardEvent = class extends Event { constructor(t,o={}) { super(t,o);this.key=o.key||"";this.code=o.code||""; } };
|
||||||
globalThis.FocusEvent = class extends Event {};
|
globalThis.FocusEvent = class extends Event {};
|
||||||
@@ -2154,7 +2186,7 @@ class _IframeDocument {
|
|||||||
createTextNode(text) { return document.createTextNode(text); }
|
createTextNode(text) { return document.createTextNode(text); }
|
||||||
createComment(text) { return document.createComment(text); }
|
createComment(text) { return document.createComment(text); }
|
||||||
createDocumentFragment() { return document.createDocumentFragment(); }
|
createDocumentFragment() { return document.createDocumentFragment(); }
|
||||||
createEvent(type) { return new Event(type); }
|
createEvent(type) { return document.createEvent(type); }
|
||||||
hasFocus() { return false; }
|
hasFocus() { return false; }
|
||||||
|
|
||||||
get cookie() { return ''; }
|
get cookie() { return ''; }
|
||||||
|
|||||||
@@ -1408,6 +1408,66 @@ mod tests {
|
|||||||
assert!(html.contains("Hello"));
|
assert!(html.contains("Hello"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_event_custom_event_has_init_method() {
|
||||||
|
let mut rt = setup_runtime("<html><body></body></html>");
|
||||||
|
let kind = rt
|
||||||
|
.evaluate("typeof document.createEvent('CustomEvent').initCustomEvent")
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(kind, serde_json::json!("function"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_init_custom_event_sets_fields() {
|
||||||
|
let mut rt = setup_runtime("<html><body></body></html>");
|
||||||
|
rt.execute_script(
|
||||||
|
"test",
|
||||||
|
r#"
|
||||||
|
globalThis.__e = document.createEvent('CustomEvent');
|
||||||
|
globalThis.__e.initCustomEvent('myevent', true, false, {hello: 'world'});
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let t = rt.evaluate("globalThis.__e.type").unwrap();
|
||||||
|
assert_eq!(t, serde_json::json!("myevent"));
|
||||||
|
let b = rt.evaluate("globalThis.__e.bubbles").unwrap();
|
||||||
|
assert_eq!(b, serde_json::json!(true));
|
||||||
|
let c = rt.evaluate("globalThis.__e.cancelable").unwrap();
|
||||||
|
assert_eq!(c, serde_json::json!(false));
|
||||||
|
let d = rt.evaluate("globalThis.__e.detail.hello").unwrap();
|
||||||
|
assert_eq!(d, serde_json::json!("world"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_event_returns_correct_class() {
|
||||||
|
let mut rt = setup_runtime("<html><body></body></html>");
|
||||||
|
let cust = rt
|
||||||
|
.evaluate("document.createEvent('CustomEvent') instanceof CustomEvent")
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(cust, serde_json::json!(true));
|
||||||
|
let mouse = rt
|
||||||
|
.evaluate("document.createEvent('MouseEvent') instanceof MouseEvent")
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(mouse, serde_json::json!(true));
|
||||||
|
let mouses = rt
|
||||||
|
.evaluate("document.createEvent('MouseEvents') instanceof MouseEvent")
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(mouses, serde_json::json!(true));
|
||||||
|
let kb = rt
|
||||||
|
.evaluate("document.createEvent('KeyboardEvent') instanceof KeyboardEvent")
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(kb, serde_json::json!(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_event_unknown_type_returns_event() {
|
||||||
|
let mut rt = setup_runtime("<html><body></body></html>");
|
||||||
|
let kind = rt
|
||||||
|
.evaluate("document.createEvent('NotARealType') instanceof Event")
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(kind, serde_json::json!(true));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_page_content_puppeteer_pattern() {
|
fn test_page_content_puppeteer_pattern() {
|
||||||
let mut rt = setup_runtime("<!DOCTYPE html><html><head></head><body><p>Test</p></body></html>");
|
let mut rt = setup_runtime("<!DOCTYPE html><html><head></head><body><p>Test</p></body></html>");
|
||||||
|
|||||||
Reference in new issue
Block a user