From 62dc548fe9f882ba072af59d45e9081cd40e520d Mon Sep 17 00:00:00 2001 From: Muki Kiboigo Date: Thu, 15 May 2025 14:21:44 -0700 Subject: [PATCH 1/4] add MouseEvent --- src/browser/events/event.zig | 3 + src/browser/events/mouse_event.zig | 105 +++++++++++++++++++++++++++++ src/browser/netsurf.zig | 1 + 3 files changed, 109 insertions(+) create mode 100644 src/browser/events/mouse_event.zig diff --git a/src/browser/events/event.zig b/src/browser/events/event.zig index 1d1a504d1..b319b3473 100644 --- a/src/browser/events/event.zig +++ b/src/browser/events/event.zig @@ -29,6 +29,7 @@ const EventTargetUnion = @import("../dom/event_target.zig").Union; const CustomEvent = @import("custom_event.zig").CustomEvent; const ProgressEvent = @import("../xhr/progress_event.zig").ProgressEvent; +const MouseEvent = @import("mouse_event.zig").MouseEvent; const log = std.log.scoped(.events); @@ -37,6 +38,7 @@ pub const Interfaces = .{ Event, CustomEvent, ProgressEvent, + MouseEvent, }; pub const Union = generate.Union(Interfaces); @@ -60,6 +62,7 @@ pub const Event = struct { .event => .{ .Event = evt }, .custom_event => .{ .CustomEvent = @as(*CustomEvent, @ptrCast(evt)).* }, .progress_event => .{ .ProgressEvent = @as(*ProgressEvent, @ptrCast(evt)).* }, + .mouse_event => .{ .MouseEvent = @as(**parser.MouseEvent, @ptrCast(evt)).* }, }; } diff --git a/src/browser/events/mouse_event.zig b/src/browser/events/mouse_event.zig new file mode 100644 index 000000000..c86b99f00 --- /dev/null +++ b/src/browser/events/mouse_event.zig @@ -0,0 +1,105 @@ +// Copyright (C) 2023-2024 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +const parser = @import("../netsurf.zig"); +const Event = @import("event.zig").Event; +const JsObject = @import("../env.zig").JsObject; + +// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent +pub const MouseEvent = struct { + pub const Self = parser.MouseEvent; + pub const prototype = *Event; + + const MouseEventInit = struct { + screenX: i32 = 0, + screenY: i32 = 0, + clientX: i32 = 0, + clientY: i32 = 0, + ctrlKey: bool = false, + shiftKey: bool = false, + altKey: bool = false, + metaKey: bool = false, + button: u16 = 0, + }; + + pub fn constructor(event_type: []const u8, opts_: ?MouseEventInit) !*Self { + const opts = opts_ orelse MouseEventInit{}; + + const mouse_event = try parser.mouseEventCreate(); + + try parser.mouseEventInit(mouse_event, event_type, .{ + .x = opts.clientX, + .y = opts.clientY, + .ctrl = opts.ctrlKey, + .shift = opts.shiftKey, + .alt = opts.altKey, + .meta = opts.metaKey, + .button = opts.button, + }); + + return mouse_event; + } + + // These is just an alias for clientX. + pub fn get_x(self: *Self) !i32 { + return self.cx; + } + + // These is just an alias for clientY. + pub fn get_y(self: *Self) !i32 { + return self.cy; + } + + pub fn get_clientX(self: *Self) !i32 { + return self.cx; + } + + pub fn get_clientY(self: *Self) !i32 { + return self.cy; + } + + pub fn get_screenX(self: *Self) !i32 { + return self.sx; + } + + pub fn get_screenY(self: *Self) !i32 { + return self.sy; + } +}; + +const testing = @import("../../testing.zig"); +test "Browser.MouseEvent" { + var runner = try testing.jsRunner(testing.tracking_allocator, .{}); + defer runner.deinit(); + + try runner.testCases(&.{ + .{ "let event = new MouseEvent('click')", "undefined" }, + .{ "event.type", "click" }, + .{ "event instanceof MouseEvent", "true" }, + .{ "event instanceof Event", "true" }, + .{ "event.clientX", "0" }, + .{ "event.clientY", "0" }, + .{ "event.screenX", "0" }, + .{ "event.screenY", "0" }, + .{ "let new_event = new MouseEvent('click2', { 'clientX': 10, 'clientY': 20 })", "undefined" }, + .{ "new_event.x", "10" }, + .{ "new_event.y", "20" }, + .{ "new_event.screenX", "10" }, + .{ "new_event.screenY", "20" }, + }, .{}); +} diff --git a/src/browser/netsurf.zig b/src/browser/netsurf.zig index 7a4d80569..019d1571f 100644 --- a/src/browser/netsurf.zig +++ b/src/browser/netsurf.zig @@ -521,6 +521,7 @@ pub const EventType = enum(u8) { event = 0, progress_event = 1, custom_event = 2, + mouse_event = 3, }; pub const MutationEvent = c.dom_mutation_event; From 593548bf85eeb784cd1205c5850001091418b27f Mon Sep 17 00:00:00 2001 From: Muki Kiboigo Date: Thu, 22 May 2025 12:49:13 -0700 Subject: [PATCH 2/4] support int enums in jsValueToZig --- src/runtime/js.zig | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/runtime/js.zig b/src/runtime/js.zig index 51960b10e..0ec95f774 100644 --- a/src/runtime/js.zig +++ b/src/runtime/js.zig @@ -938,6 +938,12 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { } unreachable; }, + .@"enum" => |e| { + switch (@typeInfo(e.tag_type)) { + .int => return std.meta.intToEnum(T, try jsIntToZig(e.tag_type, js_value, self.context)), + else => @compileError(named_function.full_name ++ " has an unsupported enum parameter type: " ++ @typeName(T)), + } + }, else => {}, } From 512d79fbeb7efae760eb6cf984e09a0c28adddb6 Mon Sep 17 00:00:00 2001 From: Muki Kiboigo Date: Thu, 22 May 2025 12:51:00 -0700 Subject: [PATCH 3/4] fix style of MouseEvent interface --- src/browser/events/mouse_event.zig | 59 ++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/src/browser/events/mouse_event.zig b/src/browser/events/mouse_event.zig index c86b99f00..c74eef828 100644 --- a/src/browser/events/mouse_event.zig +++ b/src/browser/events/mouse_event.zig @@ -16,14 +16,29 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +const std = @import("std"); +const log = std.log.scoped(.mouse_event); + const parser = @import("../netsurf.zig"); const Event = @import("event.zig").Event; const JsObject = @import("../env.zig").JsObject; +// TODO: We currently don't have a UIEvent interface so we skip it in the prototype chain. +// https://developer.mozilla.org/en-US/docs/Web/API/UIEvent +const UIEvent = Event; + // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent pub const MouseEvent = struct { pub const Self = parser.MouseEvent; - pub const prototype = *Event; + pub const prototype = *UIEvent; + + const MouseButton = enum(u16) { + main_button = 0, + auxillary_button = 1, + secondary_button = 2, + fourth_button = 3, + fifth_button = 4, + }; const MouseEventInit = struct { screenX: i32 = 0, @@ -34,13 +49,14 @@ pub const MouseEvent = struct { shiftKey: bool = false, altKey: bool = false, metaKey: bool = false, - button: u16 = 0, + button: MouseButton = .main_button, }; - pub fn constructor(event_type: []const u8, opts_: ?MouseEventInit) !*Self { + pub fn constructor(event_type: []const u8, opts_: ?MouseEventInit) !*parser.MouseEvent { const opts = opts_ orelse MouseEventInit{}; - const mouse_event = try parser.mouseEventCreate(); + var mouse_event = try parser.mouseEventCreate(); + try parser.eventSetInternalType(@ptrCast(&mouse_event), .mouse_event); try parser.mouseEventInit(mouse_event, event_type, .{ .x = opts.clientX, @@ -49,35 +65,43 @@ pub const MouseEvent = struct { .shift = opts.shiftKey, .alt = opts.altKey, .meta = opts.metaKey, - .button = opts.button, + .button = @intFromEnum(opts.button), }); + if (!std.mem.eql(u8, event_type, "click")) { + log.warn("MouseEvent currently only supports listeners for 'click' events!", .{}); + } + return mouse_event; } + pub fn get_button(self: *parser.MouseEvent) u16 { + return self.button; + } + // These is just an alias for clientX. - pub fn get_x(self: *Self) !i32 { + pub fn get_x(self: *parser.MouseEvent) i32 { return self.cx; } // These is just an alias for clientY. - pub fn get_y(self: *Self) !i32 { + pub fn get_y(self: *parser.MouseEvent) i32 { return self.cy; } - pub fn get_clientX(self: *Self) !i32 { + pub fn get_clientX(self: *parser.MouseEvent) i32 { return self.cx; } - pub fn get_clientY(self: *Self) !i32 { + pub fn get_clientY(self: *parser.MouseEvent) i32 { return self.cy; } - pub fn get_screenX(self: *Self) !i32 { + pub fn get_screenX(self: *parser.MouseEvent) i32 { return self.sx; } - pub fn get_screenY(self: *Self) !i32 { + pub fn get_screenY(self: *parser.MouseEvent) i32 { return self.sy; } }; @@ -88,6 +112,7 @@ test "Browser.MouseEvent" { defer runner.deinit(); try runner.testCases(&.{ + // Default MouseEvent .{ "let event = new MouseEvent('click')", "undefined" }, .{ "event.type", "click" }, .{ "event instanceof MouseEvent", "true" }, @@ -96,10 +121,20 @@ test "Browser.MouseEvent" { .{ "event.clientY", "0" }, .{ "event.screenX", "0" }, .{ "event.screenY", "0" }, - .{ "let new_event = new MouseEvent('click2', { 'clientX': 10, 'clientY': 20 })", "undefined" }, + // MouseEvent with parameters + .{ "let new_event = new MouseEvent('click', { 'button': 0, 'clientX': 10, 'clientY': 20 })", "undefined" }, + .{ "new_event.button", "0" }, .{ "new_event.x", "10" }, .{ "new_event.y", "20" }, .{ "new_event.screenX", "10" }, .{ "new_event.screenY", "20" }, + // MouseEvent Listener + .{ "let me = new MouseEvent('click')", "undefined" }, + .{ "me instanceof Event", "true" }, + .{ "var eevt = null; function ccbk(event) { eevt = event; }", "undefined" }, + .{ "document.addEventListener('click', ccbk)", "undefined" }, + .{ "document.dispatchEvent(me)", "true" }, + .{ "eevt.type", "click" }, + .{ "eevt instanceof MouseEvent", "true" }, }, .{}); } From b7720e60c63f0f763981cf995ac9bf2348fa0ad2 Mon Sep 17 00:00:00 2001 From: Muki Kiboigo Date: Tue, 27 May 2025 06:02:38 -0700 Subject: [PATCH 4/4] use single pointer to parser.MouseEvent --- src/browser/events/event.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/events/event.zig b/src/browser/events/event.zig index b319b3473..ccd0b7df0 100644 --- a/src/browser/events/event.zig +++ b/src/browser/events/event.zig @@ -62,7 +62,7 @@ pub const Event = struct { .event => .{ .Event = evt }, .custom_event => .{ .CustomEvent = @as(*CustomEvent, @ptrCast(evt)).* }, .progress_event => .{ .ProgressEvent = @as(*ProgressEvent, @ptrCast(evt)).* }, - .mouse_event => .{ .MouseEvent = @as(**parser.MouseEvent, @ptrCast(evt)).* }, + .mouse_event => .{ .MouseEvent = @as(*parser.MouseEvent, @ptrCast(evt)) }, }; }