diff --git a/src/browser/EventManager.zig b/src/browser/EventManager.zig index 5f169514..441f32e3 100644 --- a/src/browser/EventManager.zig +++ b/src/browser/EventManager.zig @@ -329,13 +329,36 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event, was_handled: // Phase 2: At target event._event_phase = .at_target; const target_et = target.asEventTarget(); - if (self.lookup.get(.{ - .type_string = event._type_string, - .event_target = @intFromPtr(target_et), - })) |list| { - try self.dispatchPhase(list, target_et, event, was_handled, null); - if (event._stop_propagation) { - return; + + blk: { + // Get inline handler (e.g., onclick property) for this target + if (self.getInlineHandler(target_et, event)) |inline_handler| { + was_handled.* = true; + event._current_target = target_et; + + var ls: js.Local.Scope = undefined; + self.page.js.localScope(&ls); + defer ls.deinit(); + + try ls.toLocal(inline_handler).callWithThis(void, target_et, .{event}); + + if (event._stop_propagation) { + return; + } + + if (event._stop_immediate_propagation) { + break :blk; + } + } + + if (self.lookup.get(.{ + .type_string = event._type_string, + .event_target = @intFromPtr(target_et), + })) |list| { + try self.dispatchPhase(list, target_et, event, was_handled, null); + if (event._stop_propagation) { + return; + } } } @@ -460,6 +483,20 @@ fn dispatchAll(self: *EventManager, list: *std.DoublyLinkedList, current_target: return self.dispatchPhase(list, current_target, event, was_handled, null); } +fn getInlineHandler(self: *EventManager, target: *EventTarget, event: *Event) ?js.Function.Global { + const global_event_handlers = @import("webapi/global_event_handlers.zig"); + const handler_type = global_event_handlers.fromEventType(event._type_string.str()) orelse return null; + + // Look up the inline handler for this target + const Element = @import("webapi/Element.zig"); + const element = switch (target._type) { + .node => |n| n.is(Element) orelse return null, + else => return null, + }; + + return self.page.getAttrListener(element, handler_type); +} + fn removeListener(self: *EventManager, list: *std.DoublyLinkedList, listener: *Listener) void { // If we're in a dispatch, defer removal to avoid invalidating iteration if (self.dispatch_depth > 0) { diff --git a/src/browser/Page.zig b/src/browser/Page.zig index 175cd3a9..e10b76e1 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -710,18 +710,6 @@ fn _documentIsComplete(self: *Page) !void { for (self._to_load.items) |element| { const event = try Event.initTrusted(comptime .wrap("load"), .{}, self); defer if (!event._v8_handoff) event.deinit(false); - - // Dispatch inline event. - blk: { - const html_element = element.is(HtmlElement) orelse break :blk; - - const listener = (try html_element.getOnLoad(self)) orelse break :blk; - ls.toLocal(listener).call(void, .{}) catch |err| { - log.warn(.event, "inline load event", .{ .element = element, .err = err }); - }; - } - - // Dispatch events registered to event manager. try self._event_manager.dispatch(element.asEventTarget(), event); } diff --git a/src/browser/tests/events.html b/src/browser/tests/events.html index 08319569..e463300e 100644 --- a/src/browser/tests/events.html +++ b/src/browser/tests/events.html @@ -635,3 +635,130 @@ // https://github.com/lightpanda-io/browser/pull/1316 testing.expectError('TypeError', () => MessageEvent('')); + +
+ + +
+ + +
+ + +
+ + +
+ diff --git a/src/browser/webapi/element/Html.zig b/src/browser/webapi/element/Html.zig index bbb8a050..ab393abe 100644 --- a/src/browser/webapi/element/Html.zig +++ b/src/browser/webapi/element/Html.zig @@ -329,14 +329,15 @@ pub fn click(self: *HtmlElement, page: *Page) !void { else => {}, } - const event = try @import("../event/MouseEvent.zig").init("click", .{ + const event = (try @import("../event/MouseEvent.zig").init("click", .{ .bubbles = true, .cancelable = true, .composed = true, .clientX = 0, .clientY = 0, - }, page); - try page._event_manager.dispatch(self.asEventTarget(), event.asEvent()); + }, page)).asEvent(); + defer if (!event._v8_handoff) event.deinit(false); + try page._event_manager.dispatch(self.asEventTarget(), event); } fn getAttributeFunction( diff --git a/src/browser/webapi/global_event_handlers.zig b/src/browser/webapi/global_event_handlers.zig index 44123a50..64843c26 100644 --- a/src/browser/webapi/global_event_handlers.zig +++ b/src/browser/webapi/global_event_handlers.zig @@ -164,3 +164,24 @@ pub const Handler = enum(u7) { onwaiting, onwheel, }; + +const typeToHandler = std.StaticStringMap(Handler).initComptime(blk: { + const fields = std.meta.fields(Handler); + var entries: [fields.len]struct { []const u8, Handler } = undefined; + for (fields, 0..) |field, i| { + entries[i] = .{ field.name[2..], @enumFromInt(field.value) }; + } + break :blk entries; +}); + +pub fn fromEventType(typ: []const u8) ?Handler { + return typeToHandler.get(typ); +} + +const testing = @import("../../testing.zig"); +test "GlobalEventHandlers: fromEventType" { + try testing.expectEqual(.onabort, fromEventType("abort")); + try testing.expectEqual(.onselect, fromEventType("select")); + try testing.expectEqual(null, fromEventType("")); + try testing.expectEqual(null, fromEventType("unknown")); +}