fix(js): make createEvent type-aware and add initCustomEvent (#41) (#77)

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:
Ben YounesandOra Studio authored and GitHub committed 2026-05-03 17:18:30 +02:00
1 parent 86a868bfd6
commit d50dd0f553
2 files changed
+95 -3

No files matched your search

+35 -3
View File
@@ -825,7 +825,28 @@ class Document extends Node {
_cache.set(nid, 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(); } }; }
addEventListener(type, fn, opts) {} removeEventListener() {} dispatchEvent() { return true; }
createTreeWalker(root, whatToShow, filter) {
@@ -1890,7 +1911,18 @@ globalThis.Event = class Event {
preventDefault() { this.defaultPrevented=true; } stopPropagation(){} stopImmediatePropagation(){}
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.KeyboardEvent = class extends Event { constructor(t,o={}) { super(t,o);this.key=o.key||"";this.code=o.code||""; } };
globalThis.FocusEvent = class extends Event {};
@@ -2154,7 +2186,7 @@ class _IframeDocument {
createTextNode(text) { return document.createTextNode(text); }
createComment(text) { return document.createComment(text); }
createDocumentFragment() { return document.createDocumentFragment(); }
createEvent(type) { return new Event(type); }
createEvent(type) { return document.createEvent(type); }
hasFocus() { return false; }
get cookie() { return ''; }