events: create an EventHandlerData struct

It simplifies the EventHandlerFunc creation and allows to insert user's
data.
This commit is contained in:
Pierre Tachoire
2024-06-19 17:03:40 +02:00
parent 522b293149
commit 1924f136c6
4 changed files with 107 additions and 59 deletions

View File

@@ -73,9 +73,9 @@ pub const EventTarget = struct {
self, self,
alloc, alloc,
eventType, eventType,
cbk,
capture orelse false,
EventHandler, EventHandler,
.{ .cbk = cbk },
capture orelse false,
); );
} }

View File

@@ -241,31 +241,23 @@ pub fn testExecFn(
} }
pub const EventHandler = struct { pub const EventHandler = struct {
fn handle(event: ?*parser.Event, data: ?*anyopaque) callconv(.C) void { fn handle(event: ?*parser.Event, data: parser.EventHandlerData) void {
if (data) |d| { // TODO get the allocator by another way?
const func = parser.event_handler_cbk(d); var res = CallbackResult.init(data.cbk.nat_ctx.alloc);
defer res.deinit();
// TODO get the allocator by another way? if (event) |evt| {
var res = CallbackResult.init(func.nat_ctx.alloc); data.cbk.trycall(.{
defer res.deinit(); Event.toInterface(evt) catch unreachable,
}, &res) catch |e| log.err("event handler error: {any}", .{e});
} else {
data.cbk.trycall(.{event}, &res) catch |e| log.err("event handler error: {any}", .{e});
}
if (event) |evt| { // in case of function error, we log the result and the trace.
func.trycall(.{ if (!res.success) {
Event.toInterface(evt) catch unreachable, log.info("event handler error: {s}", .{res.result orelse "unknown"});
}, &res) catch |e| log.err("event handler error: {any}", .{e}); log.debug("{s}", .{res.stack orelse "no stack trace"});
} else {
func.trycall(.{event}, &res) catch |e| log.err("event handler error: {any}", .{e});
}
// in case of function error, we log the result and the trace.
if (!res.success) {
log.info("event handler error: {s}", .{res.result orelse "unknown"});
log.debug("{s}", .{res.stack orelse "no stack trace"});
}
// NOTE: we can not call func.deinit here
// b/c the handler can be called several times
// either on this dispatch event or in anoter one
} }
} }
}.handle; }.handle;

View File

@@ -522,12 +522,6 @@ pub const EventType = enum(u8) {
progress_event = 1, progress_event = 1,
}; };
// EventHandler
pub fn event_handler_cbk(data: *anyopaque) *Callback {
const ptr: *align(@alignOf(*Callback)) anyopaque = @alignCast(data);
return @as(*Callback, @ptrCast(ptr));
}
// EventListener // EventListener
pub const EventListener = c.dom_event_listener; pub const EventListener = c.dom_event_listener;
const EventListenerEntry = c.listener_entry; const EventListenerEntry = c.listener_entry;
@@ -587,10 +581,9 @@ pub fn eventTargetHasListener(
// and capture property, // and capture property,
// let's check if the callback handler is the same // let's check if the callback handler is the same
defer c.dom_event_listener_unref(listener); defer c.dom_event_listener_unref(listener);
const data = eventListenerGetData(listener); const ehd = EventHandlerDataInternal.fromListener(listener);
if (data) |d| { if (ehd) |d| {
const cbk = event_handler_cbk(d); if (cbk_id == d.data.cbk.id()) {
if (cbk_id == cbk.id()) {
return lst; return lst;
} }
} }
@@ -608,29 +601,99 @@ pub fn eventTargetHasListener(
return null; return null;
} }
const EventHandler = fn (event: ?*Event, data: ?*anyopaque) callconv(.C) void; // EventHandlerFunc is a zig function called when the event is dispatched to a
// listener.
// The EventHandlerFunc is responsible to call the callback included into the
// EventHandlerData.
pub const EventHandlerFunc = *const fn (event: ?*Event, data: EventHandlerData) void;
// EventHandler implements the function exposed in C and called by libdom.
// It retrieves the EventHandlerInternalData and call the EventHandlerFunc with
// the EventHandlerData in parameter.
const EventHandler = struct {
fn handle(event: ?*Event, data: ?*anyopaque) callconv(.C) void {
if (data) |d| {
const ehd = EventHandlerDataInternal.get(d);
ehd.handler(event, ehd.data);
// NOTE: we can not call func.deinit here
// b/c the handler can be called several times
// either on this dispatch event or in anoter one
}
}
}.handle;
// EventHandlerData contains a JS callback and the data associated to the
// handler.
// If given, deinitFunc is called with the data pointer to allow the creator to
// clean memory.
// The callback is deinit by EventHandlerDataInternal. It must NOT be deinit
// into deinitFunc.
pub const EventHandlerData = struct {
cbk: Callback,
data: ?*anyopaque = null,
// deinitFunc implements the data deinitialization.
deinitFunc: ?DeinitFunc = null,
pub const DeinitFunc = *const fn (data: ?*anyopaque, alloc: std.mem.Allocator) void;
};
// EventHandlerDataInternal groups the EventHandlerFunc and the EventHandlerData.
const EventHandlerDataInternal = struct {
data: EventHandlerData,
handler: EventHandlerFunc,
fn init(alloc: std.mem.Allocator, handler: EventHandlerFunc, data: EventHandlerData) !*EventHandlerDataInternal {
const ptr = try alloc.create(EventHandlerDataInternal);
ptr.* = .{
.data = data,
.handler = handler,
};
return ptr;
}
fn deinit(self: *EventHandlerDataInternal, alloc: std.mem.Allocator) void {
if (self.data.deinitFunc) |d| d(self.data.data, alloc);
self.data.cbk.deinit(alloc);
alloc.destroy(self);
}
fn get(data: *anyopaque) *EventHandlerDataInternal {
const ptr: *align(@alignOf(*EventHandlerDataInternal)) anyopaque = @alignCast(data);
return @as(*EventHandlerDataInternal, @ptrCast(ptr));
}
// retrieve a EventHandlerDataInternal from a listener.
fn fromListener(lst: *EventListener) ?*EventHandlerDataInternal {
const data = eventListenerGetData(lst);
// free cbk allocation made on eventTargetAddEventListener
if (data == null) return null;
return get(data.?);
}
};
pub fn eventTargetAddEventListener( pub fn eventTargetAddEventListener(
et: *EventTarget, et: *EventTarget,
alloc: std.mem.Allocator, alloc: std.mem.Allocator,
typ: []const u8, typ: []const u8,
cbk: Callback, handlerFunc: EventHandlerFunc,
data: EventHandlerData,
capture: bool, capture: bool,
handler: EventHandler,
) !void { ) !void {
// this allocation will be removed either on // this allocation will be removed either on
// eventTargetRemoveEventListener or eventTargetRemoveAllEventListeners // eventTargetRemoveEventListener or eventTargetRemoveAllEventListeners
const cbk_ptr = try alloc.create(Callback); const ehd = try EventHandlerDataInternal.init(alloc, handlerFunc, data);
cbk_ptr.* = cbk; errdefer ehd.deinit(alloc);
// When a function is used as an event handler, its this parameter is bound // When a function is used as an event handler, its this parameter is bound
// to the DOM element on which the listener is placed. // to the DOM element on which the listener is placed.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#this_in_dom_event_handlers // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#this_in_dom_event_handlers
try cbk_ptr.setThisArg(et); try ehd.data.cbk.setThisArg(et);
const ctx = @as(*anyopaque, @ptrCast(cbk_ptr)); const ctx = @as(*anyopaque, @ptrCast(ehd));
var listener: ?*EventListener = undefined; var listener: ?*EventListener = undefined;
const errLst = c.dom_event_listener_create(handler, ctx, &listener); const errLst = c.dom_event_listener_create(EventHandler, ctx, &listener);
try DOMErr(errLst); try DOMErr(errLst);
defer c.dom_event_listener_unref(listener); defer c.dom_event_listener_unref(listener);
@@ -646,13 +709,9 @@ pub fn eventTargetRemoveEventListener(
lst: *EventListener, lst: *EventListener,
capture: bool, capture: bool,
) !void { ) !void {
const data = eventListenerGetData(lst); // free data allocation made on eventTargetAddEventListener
// free cbk allocation made on eventTargetAddEventListener const ehd = EventHandlerDataInternal.fromListener(lst);
if (data) |d| { if (ehd) |d| d.deinit(alloc);
const cbk_ptr = event_handler_cbk(d);
cbk_ptr.deinit(alloc);
alloc.destroy(cbk_ptr);
}
const s = try strFromData(typ); const s = try strFromData(typ);
const err = eventTargetVtable(et).remove_event_listener.?(et, s, lst, capture); const err = eventTargetVtable(et).remove_event_listener.?(et, s, lst, capture);
@@ -680,13 +739,10 @@ pub fn eventTargetRemoveAllEventListeners(
if (lst) |listener| { if (lst) |listener| {
defer c.dom_event_listener_unref(listener); defer c.dom_event_listener_unref(listener);
const data = eventListenerGetData(listener);
if (data) |d| { const ehd = EventHandlerDataInternal.fromListener(listener);
// free cbk allocation made on eventTargetAddEventListener if (ehd) |d| d.deinit(alloc);
const cbk = event_handler_cbk(d);
cbk.deinit(alloc);
alloc.destroy(cbk);
}
const err = eventTargetVtable(et).remove_event_listener.?( const err = eventTargetVtable(et).remove_event_listener.?(
et, et,
null, null,

View File

@@ -52,9 +52,9 @@ pub const XMLHttpRequestEventTarget = struct {
@as(*parser.EventTarget, @ptrCast(self)), @as(*parser.EventTarget, @ptrCast(self)),
alloc, alloc,
typ, typ,
cbk,
false,
EventHandler, EventHandler,
.{ .cbk = cbk },
false,
); );
} }
fn unregister(self: *XMLHttpRequestEventTarget, alloc: std.mem.Allocator, typ: []const u8, cbk: Callback) !void { fn unregister(self: *XMLHttpRequestEventTarget, alloc: std.mem.Allocator, typ: []const u8, cbk: Callback) !void {