From 96f24a2662a1373f1074d252e5bc8b5bf19f5d9c Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Mon, 16 Mar 2026 20:42:45 -0700 Subject: [PATCH] Implement window.event property Add the deprecated-but-widely-used window.event property that returns the Event currently being handled. Returns undefined when no event is being dispatched. Implementation saves and restores window._current_event around handler invocation in both dispatchDirect and dispatchNode, supporting nested event dispatch correctly. Fixes #1770 Co-Authored-By: Claude Opus 4.6 --- src/browser/EventManager.zig | 13 +++++++++ src/browser/tests/window/window_event.html | 34 ++++++++++++++++++++++ src/browser/webapi/Window.zig | 6 ++++ 3 files changed, 53 insertions(+) create mode 100644 src/browser/tests/window/window_event.html diff --git a/src/browser/EventManager.zig b/src/browser/EventManager.zig index 5588b704..247a298e 100644 --- a/src/browser/EventManager.zig +++ b/src/browser/EventManager.zig @@ -233,6 +233,12 @@ const DispatchDirectOptions = struct { pub fn dispatchDirect(self: *EventManager, target: *EventTarget, event: *Event, handler: anytype, comptime opts: DispatchDirectOptions) !void { const page = self.page; + // Set window.event to the currently dispatching event (WHATWG spec) + const window = page.window; + const prev_event = window._current_event; + window._current_event = event; + defer window._current_event = prev_event; + event.acquireRef(); defer event.deinit(false, page._session); @@ -398,6 +404,13 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event, comptime opts } const page = self.page; + + // Set window.event to the currently dispatching event (WHATWG spec) + const window = page.window; + const prev_event = window._current_event; + window._current_event = event; + defer window._current_event = prev_event; + var was_handled = false; // Create a single scope for all event handlers in this dispatch. diff --git a/src/browser/tests/window/window_event.html b/src/browser/tests/window/window_event.html new file mode 100644 index 00000000..971651f8 --- /dev/null +++ b/src/browser/tests/window/window_event.html @@ -0,0 +1,34 @@ + + + + + + + + diff --git a/src/browser/webapi/Window.zig b/src/browser/webapi/Window.zig index 099cad65..a3c64f65 100644 --- a/src/browser/webapi/Window.zig +++ b/src/browser/webapi/Window.zig @@ -68,6 +68,7 @@ _on_popstate: ?js.Function.Global = null, _on_error: ?js.Function.Global = null, _on_message: ?js.Function.Global = null, _on_unhandled_rejection: ?js.Function.Global = null, // TODO: invoke on error +_current_event: ?*Event = null, _location: *Location, _timer_id: u30 = 0, _timers: std.AutoHashMapUnmanaged(u32, *ScheduleCallback) = .{}, @@ -90,6 +91,10 @@ pub fn asEventTarget(self: *Window) *EventTarget { return self._proto; } +pub fn getEvent(self: *const Window) ?*Event { + return self._current_event; +} + pub fn getSelf(self: *Window) *Window { return self; } @@ -805,6 +810,7 @@ pub const JsApi = struct { pub const onerror = bridge.accessor(Window.getOnError, Window.setOnError, .{}); pub const onmessage = bridge.accessor(Window.getOnMessage, Window.setOnMessage, .{}); pub const onunhandledrejection = bridge.accessor(Window.getOnUnhandledRejection, Window.setOnUnhandledRejection, .{}); + pub const event = bridge.accessor(Window.getEvent, null, .{ .null_as_undefined = true }); pub const fetch = bridge.function(Window.fetch, .{}); pub const queueMicrotask = bridge.function(Window.queueMicrotask, .{}); pub const setTimeout = bridge.function(Window.setTimeout, .{});