diff --git a/crates/obscura-js/js/bootstrap.js b/crates/obscura-js/js/bootstrap.js index 4cd6739..46637cc 100644 --- a/crates/obscura-js/js/bootstrap.js +++ b/crates/obscura-js/js/bootstrap.js @@ -5094,7 +5094,7 @@ globalThis.__obscura_setInputFiles = function(el, specs) { try { el.dispatchEvent(globalThis.__obscura_markTrusted(new Event("change", { bubbles: true }))); } catch (_e) {} }; globalThis.Event = class Event { - constructor(t,o={}) { this.type=t;this.bubbles=!!o.bubbles;this.cancelable=!!o.cancelable;this.composed=!!o.composed;this.defaultPrevented=false;this.target=null;this.currentTarget=null;this.eventPhase=0;this.timeStamp=Date.now();this._propagationStopped=false;this._immediatePropagationStopped=false; } + constructor(t,o={}) { if (arguments.length < 1) throw new TypeError("Failed to construct 'Event': 1 argument required, but only 0 present."); this.type=String(t);this.bubbles=!!o.bubbles;this.cancelable=!!o.cancelable;this.composed=!!o.composed;this.defaultPrevented=false;this.target=null;this.currentTarget=null;this.eventPhase=0;this.timeStamp=Date.now();this._propagationStopped=false;this._immediatePropagationStopped=false; } get isTrusted() { return _trustedEvents.has(this); } preventDefault() { if (this.cancelable) this.defaultPrevented=true; } stopPropagation(){ this._propagationStopped=true; } stopImmediatePropagation(){ this._propagationStopped=true; this._immediatePropagationStopped=true; } initEvent(type,bubbles,cancelable) { if (arguments.length < 1) throw new TypeError("Failed to execute 'initEvent' on 'Event': 1 argument required, but only 0 present."); this.type=String(type);this.bubbles=!!bubbles;this.cancelable=!!cancelable;this.defaultPrevented=false;this._propagationStopped=false;this._immediatePropagationStopped=false; } @@ -5109,7 +5109,7 @@ globalThis.Event = class Event { }; _markNative(Event); globalThis.CustomEvent = class extends Event { - constructor(t,o={}) { super(t,o);this.detail=o.detail; } + constructor(t,o={}) { if (arguments.length < 1) throw new TypeError("Failed to construct 'CustomEvent': 1 argument required, but only 0 present."); super(t,o);this.detail=o.detail!==undefined?o.detail:null; } // 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. diff --git a/crates/obscura-js/src/runtime.rs b/crates/obscura-js/src/runtime.rs index d2ded4d..7800d30 100644 --- a/crates/obscura-js/src/runtime.rs +++ b/crates/obscura-js/src/runtime.rs @@ -3545,6 +3545,34 @@ mod tests { assert_eq!(kind, serde_json::json!(true)); } + #[test] + fn event_constructor_matches_webidl_conformance() { + // new Event()/new CustomEvent() must throw (type is a required arg), + // the type argument must be coerced to a string, CustomEvent.detail must + // default to null (not undefined), createEvent must still build a + // type-"" event, and an explicit detail must be preserved. + let mut rt = setup_runtime(""); + let v = rt + .evaluate( + "(function(){\ + var out=[];\ + try{new Event();out.push('no-throw')}catch(e){out.push(e.name)}\ + try{new CustomEvent();out.push('no-throw')}catch(e){out.push(e.name)}\ + out.push(new Event(123).type+':'+typeof new Event(123).type);\ + out.push(String(new CustomEvent('x').detail));\ + out.push(String(new CustomEvent('x',{detail:7}).detail));\ + out.push(new Event('click').type);\ + out.push(JSON.stringify(document.createEvent('Event').type));\ + return out.join('|');\ + })()", + ) + .unwrap(); + assert_eq!( + v, + serde_json::json!("TypeError|TypeError|123:string|null|7|click|\"\"") + ); + } + #[test] fn test_promise_rejection_event_requires_promise() { let mut rt = setup_runtime("");