webapi: fix UIEvent.view default, initCustomEvent guard, KeyboardEvent flags

Fixes 4 failing tests in WPT /dom/events/Event-init-while-dispatching.html
(1/5 -> 5/5), and as a side effect most of
Event-subclasses-constructors.html (16/49 -> 46/49). Three distinct
bugs, all about synthetic (constructor-created) events not following
the spec's dictionary defaults:

- UIEvent.view defaulted to the window: the constructor stored
  `opts.view orelse frame.window` and the getter fell back to the
  window too, so `new UIEvent("x").view` was never null. Per
  UIEventInit, view defaults to null. The window fallback is kept for
  *trusted* (browser-generated) UI events only, which do target the
  window, so internal dispatch call sites stay unchanged.
- initCustomEvent was missing the "short-circuit while dispatching"
  guard that initEvent and the other legacy initializers have.
- KeyboardEvent's constructor unconditionally forced bubbles,
  cancelable and composed to true (correct only for trusted key events
  per UI Events); synthetic KeyboardEvents now follow the EventInit
  defaults.

The unit tests asserting the old view-defaults-to-window behavior are
updated to expect null.

Coverage: /dom/events/Event-init-while-dispatching.html 1/5 -> 5/5,
/dom/events/Event-subclasses-constructors.html 16/49 -> 46/49. No
regressions across /dom/events.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Francis BouvierandClaude Fable 5 authored and Karl Seguin committed 2026-07-15 08:39:51 +08:00
1 parent 0b2cfc35f4
commit b309640ab0
5 files changed
+29 -11

No files matched your search

+2 -1
View File
@@ -11,7 +11,8 @@
testing.expectEqual("test", event.type);
testing.expectEqual("", event.data);
testing.expectEqual(0, event.detail);
testing.expectEqual(window, event.view);
// per UIEventInit, view defaults to null for synthetic events
testing.expectEqual(null, event.view);
}
</script>
+2 -2
View File
@@ -11,7 +11,7 @@
testing.expectEqual(5, evt.detail);
testing.expectEqual(true, evt.bubbles);
testing.expectEqual(true, evt.cancelable);
testing.expectEqual(window, evt.view);
testing.expectEqual(null, evt.view);
</script>
<script id=uiEventWithView>
@@ -30,7 +30,7 @@
testing.expectEqual(0, evt3.detail);
testing.expectEqual(false, evt3.bubbles);
testing.expectEqual(false, evt3.cancelable);
testing.expectEqual(window, evt3.view);
testing.expectEqual(null, evt3.view);
</script>
<script id=uiEventInheritance>
+5
View File
@@ -66,6 +66,11 @@ pub fn initCustomEvent(
cancelable: ?bool,
detail_: ?js.Value.Global,
) !void {
// Like initEvent, a no-op while the event is being dispatched.
if (self._proto._event_phase != .none) {
return;
}
// This function can only be called after the constructor has called.
// So we assume proto is initialized already by constructor.
self._proto._type_string = try String.init(self._proto._arena, event_string, .{});
+9 -5
View File
@@ -321,11 +321,15 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
Event.populatePrototypes(event, opts, trusted);
// https://w3c.github.io/uievents/#event-type-keyup
const rootevt = event._proto._proto;
rootevt._bubbles = true;
rootevt._cancelable = true;
rootevt._composed = true;
if (trusted) {
// Browser-generated key events bubble, are cancelable and composed:
// https://w3c.github.io/uievents/#event-type-keyup
// Synthetic ones follow the EventInit dictionary defaults.
const rootevt = event._proto._proto;
rootevt._bubbles = true;
rootevt._cancelable = true;
rootevt._composed = true;
}
return event;
}
+11 -3
View File
@@ -66,7 +66,7 @@ pub fn init(typ: []const u8, _opts: ?Options, frame: *Frame) !*UIEvent {
._type = .generic,
._proto = undefined,
._detail = opts.detail,
._view = opts.view orelse frame.window,
._view = opts.view,
},
);
@@ -109,8 +109,16 @@ pub fn getDetail(self: *UIEvent) u32 {
// sourceCapabilities not implemented
pub fn getView(self: *UIEvent, frame: *Frame) *Window {
return self._view orelse frame.window;
pub fn getView(self: *UIEvent, frame: *Frame) ?*Window {
if (self._view) |view| {
return view;
}
// Trusted (browser-generated) UI events implicitly target the window;
// synthetic events default to a null view per the UIEventInit dictionary.
if (self._proto._is_trusted) {
return frame.window;
}
return null;
}
// Legacy: see https://w3c.github.io/uievents/#dom-uievent-which