Skip to content

Commit 9ce3fc9

Browse files
committed
Refactor events
Removes some duplication between xhr/event_target and dom/event_target. Implement 'once' option of addEventListener.
1 parent f0017c3 commit 9ce3fc9

File tree

6 files changed

+100
-74
lines changed

6 files changed

+100
-74
lines changed

src/browser/dom/event_target.zig

Lines changed: 2 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -41,68 +41,14 @@ pub const EventTarget = struct {
4141

4242
// JS funcs
4343
// --------
44-
45-
const AddEventListenerOpts = union(enum) {
46-
opts: Opts,
47-
capture: bool,
48-
49-
const Opts = struct {
50-
capture: ?bool,
51-
// We ignore this property. It seems to be largely used to help the
52-
// browser make certain performance tweaks (i.e. the browser knows
53-
// that the listener won't call preventDefault() and thus can safely
54-
// run the default as needed).
55-
passive: ?bool,
56-
once: ?bool, // currently does nothing
57-
signal: ?bool, // currently does nothing
58-
};
59-
};
60-
6144
pub fn _addEventListener(
6245
self: *parser.EventTarget,
6346
typ: []const u8,
6447
listener: EventHandler.Listener,
65-
opts_: ?AddEventListenerOpts,
48+
opts: ?EventHandler.Opts,
6649
page: *Page,
6750
) !void {
68-
var capture = false;
69-
if (opts_) |opts| {
70-
switch (opts) {
71-
.capture => |c| capture = c,
72-
.opts => |o| {
73-
// Done this way so that, for common cases that _only_ set
74-
// capture, i.e. {captrue: true}, it works.
75-
// But for any case that sets any of the other flags, we
76-
// error. If we don't error, this function call would succeed
77-
// but the behavior might be wrong. At this point, it's
78-
// better to be explicit and error.
79-
if (o.once orelse false) return error.NotImplemented;
80-
if (o.signal orelse false) return error.NotImplemented;
81-
capture = o.capture orelse false;
82-
},
83-
}
84-
}
85-
86-
const cbk = (try listener.callback(self)) orelse return;
87-
88-
// check if event target has already this listener
89-
const lst = try parser.eventTargetHasListener(
90-
self,
91-
typ,
92-
capture,
93-
cbk.id,
94-
);
95-
if (lst != null) {
96-
return;
97-
}
98-
const eh = try EventHandler.init(page.arena, cbk);
99-
100-
try parser.eventTargetAddEventListener(
101-
self,
102-
typ,
103-
&eh.node,
104-
capture,
105-
);
51+
_ = try EventHandler.register(page.arena, self, typ, listener, opts);
10652
}
10753

10854
const RemoveEventListenerOpts = union(enum) {

src/browser/dom/mutation_observer.zig

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,37 +63,37 @@ pub const MutationObserver = struct {
6363

6464
// register node's events
6565
if (options.childList or options.subtree) {
66-
try parser.eventTargetAddEventListener(
66+
_ = try parser.eventTargetAddEventListener(
6767
parser.toEventTarget(parser.Node, node),
6868
"DOMNodeInserted",
6969
&observer.event_node,
7070
false,
7171
);
72-
try parser.eventTargetAddEventListener(
72+
_ = try parser.eventTargetAddEventListener(
7373
parser.toEventTarget(parser.Node, node),
7474
"DOMNodeRemoved",
7575
&observer.event_node,
7676
false,
7777
);
7878
}
7979
if (options.attr()) {
80-
try parser.eventTargetAddEventListener(
80+
_ = try parser.eventTargetAddEventListener(
8181
parser.toEventTarget(parser.Node, node),
8282
"DOMAttrModified",
8383
&observer.event_node,
8484
false,
8585
);
8686
}
8787
if (options.cdata()) {
88-
try parser.eventTargetAddEventListener(
88+
_ = try parser.eventTargetAddEventListener(
8989
parser.toEventTarget(parser.Node, node),
9090
"DOMCharacterDataModified",
9191
&observer.event_node,
9292
false,
9393
);
9494
}
9595
if (options.subtree) {
96-
try parser.eventTargetAddEventListener(
96+
_ = try parser.eventTargetAddEventListener(
9797
parser.toEventTarget(parser.Node, node),
9898
"DOMSubtreeModified",
9999
&observer.event_node,

src/browser/events/event.zig

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,11 @@ pub const Event = struct {
138138
};
139139

140140
pub const EventHandler = struct {
141+
once: bool,
142+
capture: bool,
141143
callback: Function,
142144
node: parser.EventNode,
145+
listener: *parser.EventListener,
143146

144147
const Env = @import("../env.zig").Env;
145148
const Function = Env.Function;
@@ -159,15 +162,73 @@ pub const EventHandler = struct {
159162
}
160163
};
161164

162-
pub fn init(allocator: Allocator, callback: Function) !*EventHandler {
165+
pub const Opts = union(enum) {
166+
flags: Flags,
167+
capture: bool,
168+
169+
const Flags = struct {
170+
once: ?bool,
171+
capture: ?bool,
172+
// We ignore this property. It seems to be largely used to help the
173+
// browser make certain performance tweaks (i.e. the browser knows
174+
// that the listener won't call preventDefault() and thus can safely
175+
// run the default as needed).
176+
passive: ?bool,
177+
signal: ?bool, // currently does nothing
178+
};
179+
};
180+
181+
pub fn register(
182+
allocator: Allocator,
183+
target: *parser.EventTarget,
184+
typ: []const u8,
185+
listener: Listener,
186+
opts_: ?Opts,
187+
) !?*EventHandler {
188+
var once = false;
189+
var capture = false;
190+
if (opts_) |opts| {
191+
switch (opts) {
192+
.capture => |c| capture = c,
193+
.flags => |f| {
194+
// Done this way so that, for common cases that _only_ set
195+
// capture, i.e. {captrue: true}, it works.
196+
// But for any case that sets any of the other flags, we
197+
// error. If we don't error, this function call would succeed
198+
// but the behavior might be wrong. At this point, it's
199+
// better to be explicit and error.
200+
if (f.signal orelse false) return error.NotImplemented;
201+
once = f.once orelse false;
202+
capture = f.capture orelse false;
203+
},
204+
}
205+
}
206+
207+
const callback = (try listener.callback(target)) orelse return null;
208+
209+
// check if event target has already this listener
210+
if (try parser.eventTargetHasListener(target, typ, capture, callback.id) != null) {
211+
return null;
212+
}
213+
163214
const eh = try allocator.create(EventHandler);
164215
eh.* = .{
216+
.once = once,
217+
.capture = capture,
165218
.callback = callback,
166219
.node = .{
167220
.id = callback.id,
168221
.func = handle,
169222
},
223+
.listener = undefined,
170224
};
225+
226+
eh.listener = try parser.eventTargetAddEventListener(
227+
target,
228+
typ,
229+
&eh.node,
230+
capture,
231+
);
171232
return eh;
172233
}
173234

@@ -182,6 +243,17 @@ pub const EventHandler = struct {
182243
self.callback.tryCall(void, .{ievent}, &result) catch {
183244
log.debug(.event, "handle callback error", .{ .err = result.exception, .stack = result.stack });
184245
};
246+
247+
if (self.once) {
248+
const target = (parser.eventTarget(event) catch return).?;
249+
const typ = parser.eventType(event) catch return;
250+
parser.eventTargetRemoveEventListener(
251+
target,
252+
typ,
253+
self.listener,
254+
self.capture,
255+
) catch {};
256+
}
185257
}
186258
};
187259

@@ -282,4 +354,13 @@ test "Browser.Event" {
282354
.{ "document.dispatchEvent(new Event('count'))", "true" },
283355
.{ "nb", "0" },
284356
}, .{});
357+
358+
try runner.testCases(&.{
359+
.{ "nb = 0; function cbk(event) { nb ++; }", null },
360+
.{ "document.addEventListener('count', cbk, {once: true})", null },
361+
.{ "document.dispatchEvent(new Event('count'))", "true" },
362+
.{ "document.dispatchEvent(new Event('count'))", "true" },
363+
.{ "document.dispatchEvent(new Event('count'))", "true" },
364+
.{ "nb", "1" },
365+
}, .{});
285366
}

src/browser/netsurf.zig

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,7 @@ pub fn eventTargetAddEventListener(
615615
typ: []const u8,
616616
node: *EventNode,
617617
capture: bool,
618-
) !void {
618+
) !*EventListener {
619619
const event_handler = struct {
620620
fn handle(event_: ?*Event, ptr_: ?*anyopaque) callconv(.C) void {
621621
const ptr = ptr_ orelse return;
@@ -634,6 +634,8 @@ pub fn eventTargetAddEventListener(
634634
const s = try strFromData(typ);
635635
const err = eventTargetVtable(et).add_event_listener.?(et, s, listener, capture);
636636
try DOMErr(err);
637+
638+
return listener.?;
637639
}
638640

639641
pub fn eventTargetHasListener(

src/browser/page.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ pub const Page = struct {
273273
const doc = parser.documentHTMLToDocument(html_doc);
274274

275275
const document_element = (try parser.documentGetDocumentElement(doc)) orelse return error.DocumentElementError;
276-
try parser.eventTargetAddEventListener(
276+
_ = try parser.eventTargetAddEventListener(
277277
parser.toEventTarget(parser.Element, document_element),
278278
"click",
279279
&self.window_clicked_event_node,

src/browser/xhr/event_target.zig

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,14 @@ pub const XMLHttpRequestEventTarget = struct {
4848
) !?Function {
4949
const target = @as(*parser.EventTarget, @ptrCast(self));
5050

51-
const callback = (try listener.callback(target)) orelse return null;
52-
const eh = try EventHandler.init(alloc, callback);
53-
try parser.eventTargetAddEventListener(
54-
target,
55-
typ,
56-
&eh.node,
57-
false,
58-
);
59-
60-
return callback;
51+
// The only time this can return null if the listener is already
52+
// registered. But before calling `register`, all of our functions
53+
// remove any existing listener, so it should be impossible to get null
54+
// from this function call.
55+
const eh = (try EventHandler.register(alloc, target, typ, listener, null)) orelse unreachable;
56+
return eh.callback;
6157
}
58+
6259
fn unregister(self: *XMLHttpRequestEventTarget, typ: []const u8, cbk_id: usize) !void {
6360
const et = @as(*parser.EventTarget, @ptrCast(self));
6461
// check if event target has already this listener

0 commit comments

Comments
 (0)