From ae6a7145733afbdac4fc050d08c2645161d95785 Mon Sep 17 00:00:00 2001 From: Francis Bouvier Date: Wed, 17 Jan 2024 00:12:57 +0100 Subject: [PATCH] First implementation of Event system Signed-off-by: Francis Bouvier --- src/apiweb.zig | 2 ++ src/dom/event_target.zig | 58 ++++++++++++++++++++++++++++++++++++++++ src/events/event.zig | 29 ++++++++++++++++++++ src/netsurf.zig | 42 +++++++++++++++++++++++++++++ src/run_tests.zig | 2 ++ 5 files changed, 133 insertions(+) create mode 100644 src/events/event.zig diff --git a/src/apiweb.zig b/src/apiweb.zig index 08f4c3f5..40cc8779 100644 --- a/src/apiweb.zig +++ b/src/apiweb.zig @@ -4,6 +4,7 @@ const Console = @import("jsruntime").Console; const DOM = @import("dom/dom.zig"); const HTML = @import("html/html.zig"); +const Events = @import("events/event.zig"); pub const HTMLDocument = @import("html/document.zig").HTMLDocument; @@ -11,5 +12,6 @@ pub const HTMLDocument = @import("html/document.zig").HTMLDocument; pub const Interfaces = generate.Tuple(.{ Console, DOM.Interfaces, + Events.Interfaces, HTML.Interfaces, }); diff --git a/src/dom/event_target.zig b/src/dom/event_target.zig index 9058f9c0..03ae8975 100644 --- a/src/dom/event_target.zig +++ b/src/dom/event_target.zig @@ -1,3 +1,10 @@ +const std = @import("std"); + +const jsruntime = @import("jsruntime"); +const Callback = jsruntime.Callback; +const Case = jsruntime.test_utils.Case; +const checkCases = jsruntime.test_utils.checkCases; + const parser = @import("../netsurf.zig"); const DOMException = @import("exceptions.zig").DOMException; @@ -6,4 +13,55 @@ pub const EventTarget = struct { pub const Self = parser.EventTarget; pub const Exception = DOMException; pub const mem_guarantied = true; + + // JS funcs + // -------- + + const js_handler = struct { + fn handle(event: ?*parser.Event, data: ?*anyopaque) callconv(.C) void { + const ptr: *align(@alignOf(*Callback)) anyopaque = @alignCast(data.?); + const func = @as(*Callback, @ptrCast(ptr)); + func.call(.{event}) catch unreachable; + // NOTE: we can not call func.deinit here + // b/c the handler can be called several times + // as the event goes through the ancestors + // TODO: check the event phase to call func.deinit and free func + } + }.handle; + + pub fn _addEventListener( + self: *parser.EventTarget, + alloc: std.mem.Allocator, + eventType: []const u8, + cbk: Callback, + ) !void { + // TODO: when can we free this allocation? + const cbk_ptr = try alloc.create(Callback); + cbk_ptr.* = cbk; + const func = @as(*anyopaque, @ptrCast(cbk_ptr)); + try parser.eventTargetAddEventListener(self, eventType, func, js_handler); + } + + pub fn _dispatchEvent(self: *parser.EventTarget, event: *parser.Event) !bool { + return try parser.eventTargetDispatchEvent(self, event); + } + + pub fn deinit(_: *parser.EventTarget, _: std.mem.Allocator) void {} }; + +// Tests +// ----- + +pub fn testExecFn( + _: std.mem.Allocator, + js_env: *jsruntime.Env, +) anyerror!void { + var basic = [_]Case{ + .{ .src = "let event = new Event('myEvent')", .ex = "undefined" }, + .{ .src = "let content = document.getElementById('content')", .ex = "undefined" }, + .{ .src = "var nb = 0; content.addEventListener('myEvent', function(event) {nb ++;})", .ex = "undefined" }, + .{ .src = "content.dispatchEvent(event)", .ex = "true" }, + .{ .src = "nb", .ex = "2" }, // 2 because the callback is called twice + }; + try checkCases(js_env, &basic); +} diff --git a/src/events/event.zig b/src/events/event.zig new file mode 100644 index 00000000..a336a700 --- /dev/null +++ b/src/events/event.zig @@ -0,0 +1,29 @@ +const std = @import("std"); + +const generate = @import("../generate.zig"); + +const parser = @import("../netsurf.zig"); + +const DOMException = @import("../dom/exceptions.zig").DOMException; + +pub const Event = struct { + pub const Self = parser.Event; + pub const Exception = DOMException; + pub const mem_guarantied = true; + + // JS funcs + // -------- + + pub fn constructor(eventType: []const u8) !*parser.Event { + const event = try parser.eventCreate(); + try parser.eventInit(event, eventType); + return event; + } +}; + +// Event interfaces +pub const Interfaces = generate.Tuple(.{ + Event, +}); +const Generated = generate.Union.compile(Interfaces); +pub const Union = Generated._union; diff --git a/src/netsurf.zig b/src/netsurf.zig index 7d5802b1..b8d4d7e2 100644 --- a/src/netsurf.zig +++ b/src/netsurf.zig @@ -341,9 +341,51 @@ fn DOMErr(except: DOMException) DOMError!void { }; } +// Event +pub const Event = c.dom_event; +pub const EventHandler = fn (?*Event, ?*anyopaque) callconv(.C) void; + +pub fn eventCreate() !*Event { + var evt: ?*Event = undefined; + const err = c._dom_event_create(&evt); + try DOMErr(err); + return evt.?; +} + +pub fn eventInit(evt: *Event, eventType: []const u8) !void { + const s = try strFromData(eventType); + const err = c._dom_event_init(evt, s, false, false); + try DOMErr(err); +} + // EventTarget pub const EventTarget = c.dom_event_target; +fn eventTargetVtable(et: *EventTarget) c.dom_event_target_vtable { + return getVtable(c.dom_event_target_vtable, EventTarget, et); +} + +pub fn eventTargetAddEventListener( + et: *EventTarget, + eventType: []const u8, + data: ?*anyopaque, + comptime handler: EventHandler, +) !void { + const s = try strFromData(eventType); + var listener: ?*c.dom_event_listener = undefined; + const errLst = c.dom_event_listener_create(handler, data, &listener); + try DOMErr(errLst); + const err = eventTargetVtable(et).add_event_listener.?(et, s, listener, true); + try DOMErr(err); +} + +pub fn eventTargetDispatchEvent(et: *EventTarget, event: *Event) !bool { + var res: bool = undefined; + const err = eventTargetVtable(et).dispatch_event.?(et, event, &res); + try DOMErr(err); + return res; +} + // NodeType pub const NodeType = enum(u4) { diff --git a/src/run_tests.zig b/src/run_tests.zig index 3a8fc499..f54f5186 100644 --- a/src/run_tests.zig +++ b/src/run_tests.zig @@ -20,6 +20,7 @@ const NamedNodeMapExecFn = @import("dom/namednodemap.zig").testExecFn; const DOMTokenListExecFn = @import("dom/token_list.zig").testExecFn; const NodeListTestExecFn = @import("dom/nodelist.zig").testExecFn; const AttrTestExecFn = @import("dom/attribute.zig").testExecFn; +const EventTargetTestExecFn = @import("dom/event_target.zig").testExecFn; pub const Types = jsruntime.reflect(apiweb.Interfaces); @@ -73,6 +74,7 @@ fn testsAllExecFn( DOMTokenListExecFn, NodeListTestExecFn, AttrTestExecFn, + EventTargetTestExecFn, }; inline for (testFns) |testFn| {