Skip to content

Commit 072bc51

Browse files
committed
Add zig listener support to netsurf event handler
Add _click handler to HTMLElement Register zig click listener on document. Largely waiting on https://github.com/lightpanda-io/browser/pull/501/files to finalize the placeholders.
1 parent 581a79f commit 072bc51

File tree

6 files changed

+425
-88
lines changed

6 files changed

+425
-88
lines changed

src/browser/browser.zig

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,16 @@ pub const Page = struct {
481481
// save a document's pointer in the page.
482482
self.doc = doc;
483483

484+
const document_element = (try parser.documentGetDocumentElement(doc)) orelse return error.DocumentElementError;
485+
try parser.eventTargetAddZigListener(
486+
parser.toEventTarget(parser.Element, document_element),
487+
arena,
488+
"click",
489+
windowClicked,
490+
self,
491+
false,
492+
);
493+
484494
// TODO set document.readyState to interactive
485495
// https://html.spec.whatwg.org/#reporting-document-loading-status
486496

@@ -728,6 +738,29 @@ pub const Page = struct {
728738
return request;
729739
}
730740

741+
fn windowClicked(ctx: *anyopaque, event: *parser.Event) void {
742+
const self: *Page = @alignCast(@ptrCast(ctx));
743+
self._windowClicked(event) catch |err| {
744+
log.err("window click handler: {}", .{err});
745+
};
746+
}
747+
748+
fn _windowClicked(self: *Page, event: *parser.Event) !void {
749+
_ = self;
750+
751+
const target = (try parser.eventTarget(event)) orelse return;
752+
753+
const node = parser.eventTargetToNode(target);
754+
if (try parser.nodeType(node) != .element) {
755+
return;
756+
}
757+
758+
const element: *parser.ElementHTML = @ptrCast(node);
759+
const tag_name = try parser.elementHTMLGetTagType(element);
760+
// TODO https://github.com/lightpanda-io/browser/pull/501
761+
_ = tag_name;
762+
}
763+
731764
const Script = struct {
732765
element: *parser.Element,
733766
kind: Kind,

src/browser/dom/mutation_observer.zig

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -104,15 +104,15 @@ pub const MutationObserver = struct {
104104
arena,
105105
"DOMNodeInserted",
106106
EventHandler,
107-
.{ .cbk = self.cbk, .data = o, .deinitFunc = deinitFunc },
107+
.{ .cbk = self.cbk, .ctx = o, .deinitFunc = deinitFunc },
108108
false,
109109
);
110110
try parser.eventTargetAddEventListener(
111111
parser.toEventTarget(parser.Node, node),
112112
arena,
113113
"DOMNodeRemoved",
114114
EventHandler,
115-
.{ .cbk = self.cbk, .data = o, .deinitFunc = deinitFunc },
115+
.{ .cbk = self.cbk, .ctx = o, .deinitFunc = deinitFunc },
116116
false,
117117
);
118118
}
@@ -122,7 +122,7 @@ pub const MutationObserver = struct {
122122
arena,
123123
"DOMAttrModified",
124124
EventHandler,
125-
.{ .cbk = self.cbk, .data = o, .deinitFunc = deinitFunc },
125+
.{ .cbk = self.cbk, .ctx = o, .deinitFunc = deinitFunc },
126126
false,
127127
);
128128
}
@@ -132,7 +132,7 @@ pub const MutationObserver = struct {
132132
arena,
133133
"DOMCharacterDataModified",
134134
EventHandler,
135-
.{ .cbk = self.cbk, .data = o, .deinitFunc = deinitFunc },
135+
.{ .cbk = self.cbk, .ctx = o, .deinitFunc = deinitFunc },
136136
false,
137137
);
138138
}
@@ -142,7 +142,7 @@ pub const MutationObserver = struct {
142142
arena,
143143
"DOMSubtreeModified",
144144
EventHandler,
145-
.{ .cbk = self.cbk, .data = o, .deinitFunc = deinitFunc },
145+
.{ .cbk = self.cbk, .ctx = o, .deinitFunc = deinitFunc },
146146
false,
147147
);
148148
}
@@ -261,7 +261,7 @@ const EventHandler = struct {
261261
return false;
262262
}
263263

264-
fn handle(evt: ?*parser.Event, data: parser.EventHandlerData) void {
264+
fn handle(evt: ?*parser.Event, data: *const parser.JSEventHandlerData) void {
265265
if (evt == null) return;
266266

267267
var mrs: MutationRecords = .{};
@@ -277,7 +277,7 @@ const EventHandler = struct {
277277
const node = parser.eventTargetToNode(et);
278278

279279
// retrieve the observer from the data.
280-
const o: *MutationObserver.Observer = @ptrCast(@alignCast(data.data));
280+
const o: *MutationObserver.Observer = @ptrCast(@alignCast(data.ctx));
281281

282282
if (!apply(o, node)) return;
283283

src/browser/events/event.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ pub const Event = struct {
136136
};
137137

138138
pub const EventHandler = struct {
139-
fn handle(event: ?*parser.Event, data: parser.EventHandlerData) void {
139+
fn handle(event: ?*parser.Event, data: *const parser.JSEventHandlerData) void {
140140
var result: Callback.Result = undefined;
141141
data.cbk.tryCall(.{if (event) |evt| Event.toInterface(evt) catch unreachable else null}, &result) catch {
142142
log.err("event handler error: {s}", .{result.exception});

src/browser/html/elements.zig

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,24 @@ pub const HTMLElement = struct {
130130
// attach the text node.
131131
_ = try parser.nodeAppendChild(n, @as(*parser.Node, @ptrCast(t)));
132132
}
133+
134+
pub fn _click(e: *parser.ElementHTML) !void {
135+
_ = e;
136+
// TODO needs: https://github.com/lightpanda-io/browser/pull/501
137+
// TODO: when the above is merged, should we get the element coordinates?
138+
139+
// const event = try parser.mouseEventCreate();
140+
// defer parser.mouseEventDestroy(event);
141+
// try parser.mouseEventInit(event, "click", .{
142+
// .bubbles = true,
143+
// .cancelable = true,
144+
//
145+
// // get the coordinates?
146+
// .x = 0,
147+
// .y = 0,
148+
// });
149+
// _ = try parser.elementDispatchEvent(@ptrCast(e), @ptrCast(event));
150+
}
133151
};
134152

135153
// Deprecated HTMLElements in Chrome (2023/03/15)

src/browser/netsurf.zig

Lines changed: 103 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -616,10 +616,12 @@ pub fn eventTargetHasListener(
616616
// and capture property,
617617
// let's check if the callback handler is the same
618618
defer c.dom_event_listener_unref(listener);
619-
const ehd = EventHandlerDataInternal.fromListener(listener);
620-
if (ehd) |d| {
621-
if (cbk_id == d.data.cbk.id) {
622-
return lst;
619+
if (EventHandlerData.fromListener(listener)) |ehd| {
620+
switch (ehd.*) {
621+
.js => |js| if (cbk_id == js.data.cbk.id) {
622+
return lst;
623+
},
624+
.zig => {},
623625
}
624626
}
625627
}
@@ -636,100 +638,115 @@ pub fn eventTargetHasListener(
636638
return null;
637639
}
638640

639-
// EventHandlerFunc is a zig function called when the event is dispatched to a
640-
// listener.
641-
// The EventHandlerFunc is responsible to call the callback included into the
642-
// EventHandlerData.
643-
pub const EventHandlerFunc = *const fn (event: ?*Event, data: EventHandlerData) void;
641+
// The *anyopque that get stored in the libdom listener, which we'll retrieve
642+
// when then event is dispatched so that we can execute the JS or Zig callback.
643+
const EventHandlerData = union(enum) {
644+
js: JS,
645+
zig: Zig,
644646

645-
// EventHandler implements the function exposed in C and called by libdom.
646-
// It retrieves the EventHandlerInternalData and call the EventHandlerFunc with
647-
// the EventHandlerData in parameter.
648-
const EventHandler = struct {
649-
fn handle(event: ?*Event, data: ?*anyopaque) callconv(.C) void {
650-
if (data) |d| {
651-
const ehd = EventHandlerDataInternal.get(d);
652-
ehd.handler(event, ehd.data);
653-
654-
// NOTE: we can not call func.deinit here
655-
// b/c the handler can be called several times
656-
// either on this dispatch event or in anoter one
657-
}
658-
}
659-
}.handle;
660-
661-
// EventHandlerData contains a JS callback and the data associated to the
662-
// handler.
663-
// If given, deinitFunc is called with the data pointer to allow the creator to
664-
// clean memory.
665-
// The callback is deinit by EventHandlerDataInternal. It must NOT be deinit
666-
// into deinitFunc.
667-
pub const EventHandlerData = struct {
668-
cbk: Callback,
669-
data: ?*anyopaque = null,
670-
// deinitFunc implements the data deinitialization.
671-
deinitFunc: ?DeinitFunc = null,
672-
673-
pub const DeinitFunc = *const fn (data: ?*anyopaque, allocator: std.mem.Allocator) void;
674-
};
647+
const JS = struct {
648+
data: JSEventHandlerData,
649+
func: JSEventHandlerFunc,
650+
};
675651

676-
// EventHandlerDataInternal groups the EventHandlerFunc and the EventHandlerData.
677-
const EventHandlerDataInternal = struct {
678-
data: EventHandlerData,
679-
handler: EventHandlerFunc,
652+
const Zig = struct {
653+
ctx: *anyopaque,
654+
func: ZigEventHandlerFunc,
655+
};
680656

681-
fn init(alloc: std.mem.Allocator, handler: EventHandlerFunc, data: EventHandlerData) !*EventHandlerDataInternal {
682-
const ptr = try alloc.create(EventHandlerDataInternal);
683-
ptr.* = .{
684-
.data = data,
685-
.handler = handler,
686-
};
687-
return ptr;
657+
// retrieve a EventHandlerDataInternal from a listener.
658+
fn fromListener(lst: *EventListener) ?*EventHandlerData {
659+
const ctx = eventListenerGetData(lst) orelse return null;
660+
const ehd: *EventHandlerData = @alignCast(@ptrCast(ctx));
661+
return ehd;
688662
}
689663

690-
fn deinit(self: *EventHandlerDataInternal, alloc: std.mem.Allocator) void {
691-
if (self.data.deinitFunc) |d| {
692-
d(self.data.data, alloc);
664+
pub fn deinit(self: *EventHandlerData, alloc: std.mem.Allocator) void {
665+
switch (self.*) {
666+
.js => |*js| {
667+
const js_data = &js.data;
668+
if (js_data.deinitFunc) |df| {
669+
df(js_data.ctx, alloc);
670+
}
671+
},
672+
.zig => {},
693673
}
694674
alloc.destroy(self);
695675
}
696676

697-
fn get(data: *anyopaque) *EventHandlerDataInternal {
698-
const ptr: *align(@alignOf(*EventHandlerDataInternal)) anyopaque = @alignCast(data);
699-
return @as(*EventHandlerDataInternal, @ptrCast(ptr));
677+
pub fn handle(self: *EventHandlerData, event: ?*Event) void {
678+
switch (self.*) {
679+
.js => |*js| js.func(event, &js.data),
680+
.zig => |zig| zig.func(zig.ctx, event.?),
681+
}
700682
}
683+
};
701684

702-
// retrieve a EventHandlerDataInternal from a listener.
703-
fn fromListener(lst: *EventListener) ?*EventHandlerDataInternal {
704-
const data = eventListenerGetData(lst);
705-
// free cbk allocation made on eventTargetAddEventListener
706-
if (data == null) return null;
685+
pub const JSEventHandlerData = struct {
686+
cbk: Callback,
687+
ctx: ?*anyopaque = null,
688+
// deinitFunc implements the data deinitialization.
689+
deinitFunc: ?DeinitFunc = null,
707690

708-
return get(data.?);
709-
}
691+
pub const DeinitFunc = *const fn (data: ?*anyopaque, alloc: std.mem.Allocator) void;
710692
};
711693

694+
const JSEventHandlerFunc = *const fn (event: ?*Event, data: *JSEventHandlerData) void;
695+
const ZigEventHandlerFunc = *const fn (ctx: *anyopaque, event: *Event) void;
696+
712697
pub fn eventTargetAddEventListener(
713698
et: *EventTarget,
714699
alloc: std.mem.Allocator,
715700
typ: []const u8,
716-
handlerFunc: EventHandlerFunc,
717-
data: EventHandlerData,
701+
func: JSEventHandlerFunc,
702+
data: JSEventHandlerData,
718703
capture: bool,
719704
) !void {
720705
// this allocation will be removed either on
721706
// eventTargetRemoveEventListener or eventTargetRemoveAllEventListeners
722-
const ehd = try EventHandlerDataInternal.init(alloc, handlerFunc, data);
707+
const ehd = try alloc.create(EventHandlerData);
708+
errdefer alloc.destroy(ehd);
709+
ehd.* = .{ .js = .{ .data = data, .func = func } };
723710
errdefer ehd.deinit(alloc);
724711

725712
// When a function is used as an event handler, its this parameter is bound
726713
// to the DOM element on which the listener is placed.
727714
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#this_in_dom_event_handlers
728-
try ehd.data.cbk.setThis(et);
715+
try ehd.js.data.cbk.setThis(et);
716+
717+
return addEventTargetListener(et, typ, ehd, capture);
718+
}
719+
720+
pub fn eventTargetAddZigListener(
721+
et: *EventTarget,
722+
alloc: std.mem.Allocator,
723+
typ: []const u8,
724+
func: ZigEventHandlerFunc,
725+
ctx: *anyopaque,
726+
capture: bool,
727+
) !void {
728+
const ehd = try alloc.create(EventHandlerData);
729+
errdefer alloc.destroy(ehd);
730+
ehd.* = .{ .zig = .{ .ctx = ctx, .func = func } };
731+
return addEventTargetListener(et, typ, ehd, capture);
732+
}
733+
734+
fn addEventTargetListener(et: *EventTarget, typ: []const u8, data: *anyopaque, capture: bool) !void {
735+
// event_handler implements the function exposed in C and called by libdom.
736+
// It retrieves the EventHandler and calls the appropriate (JS or Zig)
737+
// handler function with the corresponding data.
738+
const event_handler = struct {
739+
fn handle(event: ?*Event, ptr_: ?*anyopaque) callconv(.C) void {
740+
const ptr = ptr_ orelse return;
741+
@as(*EventHandlerData, @alignCast(@ptrCast(ptr))).handle(event);
742+
// NOTE: we can not call func.deinit here
743+
// b/c the handler can be called several times
744+
// either on this dispatch event or in anoter one
745+
}
746+
}.handle;
729747

730-
const ctx = @as(*anyopaque, @ptrCast(ehd));
731748
var listener: ?*EventListener = undefined;
732-
const errLst = c.dom_event_listener_create(EventHandler, ctx, &listener);
749+
const errLst = c.dom_event_listener_create(event_handler, data, &listener);
733750
try DOMErr(errLst);
734751
defer c.dom_event_listener_unref(listener);
735752

@@ -746,8 +763,9 @@ pub fn eventTargetRemoveEventListener(
746763
capture: bool,
747764
) !void {
748765
// free data allocation made on eventTargetAddEventListener
749-
const ehd = EventHandlerDataInternal.fromListener(lst);
750-
if (ehd) |d| d.deinit(alloc);
766+
if (EventHandlerData.fromListener(lst)) |ehd| {
767+
ehd.deinit(alloc);
768+
}
751769

752770
const s = try strFromData(typ);
753771
const err = eventTargetVtable(et).remove_event_listener.?(et, s, lst, capture);
@@ -776,16 +794,21 @@ pub fn eventTargetRemoveAllEventListeners(
776794
if (lst) |listener| {
777795
defer c.dom_event_listener_unref(listener);
778796

779-
const ehd = EventHandlerDataInternal.fromListener(listener);
780-
if (ehd) |d| d.deinit(alloc);
797+
if (EventHandlerData.fromListener(listener)) |ehd| {
798+
if (ehd.* == .zig) {
799+
// we don't remove Zig listeners
800+
continue;
801+
}
781802

782-
const err = eventTargetVtable(et).remove_event_listener.?(
783-
et,
784-
null,
785-
lst,
786-
false,
787-
);
788-
try DOMErr(err);
803+
ehd.deinit(alloc);
804+
const err = eventTargetVtable(et).remove_event_listener.?(
805+
et,
806+
null,
807+
lst,
808+
false,
809+
);
810+
try DOMErr(err);
811+
}
789812
}
790813

791814
if (next == null) {

0 commit comments

Comments
 (0)