Skip to content

Commit 0721104

Browse files
committed
Unify the Zig and JS events using an intrusive node.
The approach borrows heavily from Zig's new LinkedList API. The main benefit is that it unifies how event callbacks are done. When the Page.windowClick event was added, the Event structure was changed to a union, supporting a distinct Zig and JS event. This new approach more or less treats everything like a Zig event. A JS event is just a Zig struct that has a Env.Callback which it can invoke in its handle method. The intrusive nature of the EventNode means that what used to be 1 or 2 allocations is now 0 or 1. It also has the benefit of making netsurf completely unaware of Env.Callbacks.
1 parent 0fb0532 commit 0721104

File tree

7 files changed

+142
-220
lines changed

7 files changed

+142
-220
lines changed

src/browser/browser.zig

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -341,12 +341,15 @@ pub const Page = struct {
341341

342342
renderer: FlatRenderer,
343343

344+
window_clicked_event_node: parser.EventNode,
345+
344346
fn init(session: *Session) Page {
345347
const arena = session.browser.page_arena.allocator();
346348
return .{
347349
.arena = arena,
348350
.session = session,
349351
.renderer = FlatRenderer.init(arena),
352+
.window_clicked_event_node = .{ .func = windowClicked },
350353
};
351354
}
352355

@@ -481,12 +484,10 @@ pub const Page = struct {
481484
self.doc = doc;
482485

483486
const document_element = (try parser.documentGetDocumentElement(doc)) orelse return error.DocumentElementError;
484-
try parser.eventTargetAddZigListener(
487+
try parser.eventTargetAddEventListener(
485488
parser.toEventTarget(parser.Element, document_element),
486-
arena,
487489
"click",
488-
windowClicked,
489-
self,
490+
&self.window_clicked_event_node,
490491
false,
491492
);
492493

@@ -766,8 +767,8 @@ pub const Page = struct {
766767
_ = try parser.elementDispatchEvent(element, @ptrCast(event));
767768
}
768769

769-
fn windowClicked(ctx: *anyopaque, event: *parser.Event) void {
770-
const self: *Page = @alignCast(@ptrCast(ctx));
770+
fn windowClicked(node: *parser.EventNode, event: *parser.Event) void {
771+
const self: *Page = @fieldParentPtr("window_clicked_event_node", node);
771772
self._windowClicked(event) catch |err| {
772773
log.err("window click handler: {}", .{err});
773774
};

src/browser/dom/event_target.zig

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ pub const EventTarget = struct {
4646

4747
pub fn _addEventListener(
4848
self: *parser.EventTarget,
49-
eventType: []const u8,
49+
typ: []const u8,
5050
cbk: Env.Callback,
5151
capture: ?bool,
5252
state: *SessionState,
@@ -56,37 +56,36 @@ pub const EventTarget = struct {
5656
// check if event target has already this listener
5757
const lst = try parser.eventTargetHasListener(
5858
self,
59-
eventType,
59+
typ,
6060
capture orelse false,
6161
cbk.id,
6262
);
6363
if (lst != null) {
6464
return;
6565
}
6666

67+
const eh = try EventHandler.init(state.arena, try cbk.withThis(self));
68+
6769
try parser.eventTargetAddEventListener(
6870
self,
69-
state.arena,
70-
eventType,
71-
EventHandler,
72-
.{ .cbk = cbk },
71+
typ,
72+
&eh.node,
7373
capture orelse false,
7474
);
7575
}
7676

7777
pub fn _removeEventListener(
7878
self: *parser.EventTarget,
79-
eventType: []const u8,
79+
typ: []const u8,
8080
cbk: Env.Callback,
8181
capture: ?bool,
82-
state: *SessionState,
8382
// TODO: hanle EventListenerOptions
8483
// see #https://github.com/lightpanda-io/jsruntime-lib/issues/114
8584
) !void {
8685
// check if event target has already this listener
8786
const lst = try parser.eventTargetHasListener(
8887
self,
89-
eventType,
88+
typ,
9089
capture orelse false,
9190
cbk.id,
9291
);
@@ -97,8 +96,7 @@ pub const EventTarget = struct {
9796
// remove listener
9897
try parser.eventTargetRemoveEventListener(
9998
self,
100-
state.arena,
101-
eventType,
99+
typ,
102100
lst.?,
103101
capture orelse false,
104102
);

src/browser/dom/mutation_observer.zig

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -59,56 +59,45 @@ pub const MutationObserver = struct {
5959
.node = node,
6060
.options = options,
6161
.mutation_observer = self,
62+
.event_node = .{ .id = self.cbk.id, .func = Observer.handle },
6263
};
6364

64-
const arena = self.arena;
65-
6665
// register node's events
6766
if (options.childList or options.subtree) {
68-
try parser.eventTargetAddZigListener(
67+
try parser.eventTargetAddEventListener(
6968
parser.toEventTarget(parser.Node, node),
70-
arena,
7169
"DOMNodeInserted",
72-
Observer.handle,
73-
observer,
70+
&observer.event_node,
7471
false,
7572
);
76-
try parser.eventTargetAddZigListener(
73+
try parser.eventTargetAddEventListener(
7774
parser.toEventTarget(parser.Node, node),
78-
arena,
7975
"DOMNodeRemoved",
80-
Observer.handle,
81-
observer,
76+
&observer.event_node,
8277
false,
8378
);
8479
}
8580
if (options.attr()) {
86-
try parser.eventTargetAddZigListener(
81+
try parser.eventTargetAddEventListener(
8782
parser.toEventTarget(parser.Node, node),
88-
arena,
8983
"DOMAttrModified",
90-
Observer.handle,
91-
observer,
84+
&observer.event_node,
9285
false,
9386
);
9487
}
9588
if (options.cdata()) {
96-
try parser.eventTargetAddZigListener(
89+
try parser.eventTargetAddEventListener(
9790
parser.toEventTarget(parser.Node, node),
98-
arena,
9991
"DOMCharacterDataModified",
100-
Observer.handle,
101-
observer,
92+
&observer.event_node,
10293
false,
10394
);
10495
}
10596
if (options.subtree) {
106-
try parser.eventTargetAddZigListener(
97+
try parser.eventTargetAddEventListener(
10798
parser.toEventTarget(parser.Node, node),
108-
arena,
10999
"DOMSubtreeModified",
110-
Observer.handle,
111-
observer,
100+
&observer.event_node,
112101
false,
113102
);
114103
}
@@ -221,6 +210,8 @@ const Observer = struct {
221210
// and batch the mutation records.
222211
mutation_observer: *MutationObserver,
223212

213+
event_node: parser.EventNode,
214+
224215
fn appliesTo(o: *const Observer, target: *parser.Node) bool {
225216
// mutation on any target is always ok.
226217
if (o.options.subtree) {
@@ -250,9 +241,9 @@ const Observer = struct {
250241
return false;
251242
}
252243

253-
fn handle(ctx: *anyopaque, event: *parser.Event) void {
254-
// retrieve the observer from the data.
255-
var self: *Observer = @alignCast(@ptrCast(ctx));
244+
fn handle(en: *parser.EventNode, event: *parser.Event) void {
245+
const self: *Observer = @fieldParentPtr("event_node", en);
246+
256247
var mutation_observer = self.mutation_observer;
257248

258249
const node = blk: {

src/browser/events/event.zig

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
// along with this program. If not, see <https://www.gnu.org/licenses/>.
1818

1919
const std = @import("std");
20+
const Allocator = std.mem.Allocator;
2021

2122
const parser = @import("../netsurf.zig");
2223
const Callback = @import("../env.zig").Callback;
@@ -136,14 +137,35 @@ pub const Event = struct {
136137
};
137138

138139
pub const EventHandler = struct {
139-
fn handle(event: ?*parser.Event, data: *const parser.JSEventHandlerData) void {
140+
callback: Callback,
141+
node: parser.EventNode,
142+
143+
pub fn init(allocator: Allocator, callback: Callback) !*EventHandler {
144+
const eh = try allocator.create(EventHandler);
145+
eh.* = .{
146+
.callback = callback,
147+
.node = .{
148+
.id = callback.id,
149+
.func = handle,
150+
},
151+
};
152+
return eh;
153+
}
154+
155+
fn handle(node: *parser.EventNode, event: *parser.Event) void {
156+
const ievent = Event.toInterface(event) catch |err| {
157+
log.err("Event.toInterface: {}", .{err});
158+
return;
159+
};
160+
161+
const self: *EventHandler = @fieldParentPtr("node", node);
140162
var result: Callback.Result = undefined;
141-
data.cbk.tryCall(.{if (event) |evt| Event.toInterface(evt) catch unreachable else null}, &result) catch {
163+
self.callback.tryCall(.{ievent}, &result) catch {
142164
log.err("event handler error: {s}", .{result.exception});
143165
log.debug("stack:\n{s}", .{result.stack orelse "???"});
144166
};
145167
}
146-
}.handle;
168+
};
147169

148170
const testing = @import("../../testing.zig");
149171
test "Browser.Event" {

0 commit comments

Comments
 (0)