Skip to content

Commit 9ba4c69

Browse files
Add removeEventListener
And check if callback has been already added in addEventListener Signed-off-by: Francis Bouvier <[email protected]>
1 parent 3483dfa commit 9ba4c69

File tree

2 files changed

+129
-22
lines changed

2 files changed

+129
-22
lines changed

src/dom/event_target.zig

Lines changed: 84 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const std = @import("std");
22

33
const jsruntime = @import("jsruntime");
44
const Callback = jsruntime.Callback;
5+
const JSObjectID = jsruntime.JSObjectID;
56
const Case = jsruntime.test_utils.Case;
67
const checkCases = jsruntime.test_utils.checkCases;
78

@@ -39,22 +40,71 @@ pub const EventTarget = struct {
3940
) !void {
4041

4142
// check if event target has already this listener
42-
const lst = try parser.eventTargetHasListener(self, eventType, cbk.id());
43+
const lst = try parser.eventTargetHasListener(
44+
self,
45+
eventType,
46+
capture orelse false,
47+
cbk.id(),
48+
);
4349
if (lst != null) {
4450
return;
4551
}
4652

47-
// TODO: when can we free this allocation?
53+
// NOTE: this allocation will be removed either if removeEventListener
54+
// or at EventTarget deinit
4855
const cbk_ptr = try alloc.create(Callback);
4956
cbk_ptr.* = cbk;
50-
try parser.eventTargetAddEventListener(self, eventType, cbk_ptr, capture orelse false);
57+
try parser.eventTargetAddEventListener(
58+
self,
59+
eventType,
60+
cbk_ptr,
61+
capture orelse false,
62+
);
63+
}
64+
65+
pub fn _removeEventListener(
66+
self: *parser.EventTarget,
67+
alloc: std.mem.Allocator,
68+
eventType: []const u8,
69+
cbk_id: JSObjectID,
70+
capture: ?bool,
71+
// TODO: hanle EventListenerOptions
72+
// see #https://github.com/lightpanda-io/jsruntime-lib/issues/114
73+
) !void {
74+
75+
// check if event target has already this listener
76+
const lst = try parser.eventTargetHasListener(
77+
self,
78+
eventType,
79+
capture orelse false,
80+
cbk_id.get(),
81+
);
82+
if (lst == null) {
83+
return;
84+
}
85+
86+
// remove listener
87+
const cbk_handler = try parser.eventTargetRemoveEventListener(
88+
self,
89+
eventType,
90+
lst.?,
91+
capture orelse false,
92+
);
93+
if (cbk_handler) |cbk_ptr| {
94+
cbk_ptr.deinit(alloc);
95+
alloc.destroy(cbk_ptr);
96+
}
5197
}
5298

5399
pub fn _dispatchEvent(self: *parser.EventTarget, event: *parser.Event) !bool {
54100
return try parser.eventTargetDispatchEvent(self, event);
55101
}
56102

57-
pub fn deinit(_: *parser.EventTarget, _: std.mem.Allocator) void {}
103+
pub fn deinit(_: *parser.EventTarget, _: std.mem.Allocator) void {
104+
// TODO:
105+
// - deinit and destroy all cbk_handler
106+
// - remove all listeners
107+
}
58108
};
59109

60110
// Tests
@@ -92,6 +142,14 @@ pub fn testExecFn(
92142
};
93143
try checkCases(js_env, &basic);
94144

145+
var basic_child = [_]Case{
146+
.{ .src = "nb = 0; evt = undefined; phase = undefined; cur = undefined", .ex = "undefined" },
147+
.{ .src = "para.dispatchEvent(new Event('basic'))", .ex = "true" },
148+
.{ .src = "nb", .ex = "0" }, // handler is not called, no capture, not the target, no bubbling
149+
.{ .src = "evt === undefined", .ex = "true" },
150+
};
151+
try checkCases(js_env, &basic_child);
152+
95153
var basic_twice = [_]Case{
96154
.{ .src = "nb = 0", .ex = "0" },
97155
.{ .src = "content.addEventListener('basic', cbk)", .ex = "undefined" },
@@ -100,13 +158,29 @@ pub fn testExecFn(
100158
};
101159
try checkCases(js_env, &basic_twice);
102160

103-
var basic_child = [_]Case{
104-
.{ .src = "nb = 0; evt = undefined; phase = undefined; cur = undefined", .ex = "undefined" },
105-
.{ .src = "para.dispatchEvent(new Event('basic'))", .ex = "true" },
106-
.{ .src = "nb", .ex = "0" }, // handler is not called, no capture, not the target, no bubbling
107-
.{ .src = "evt === undefined", .ex = "true" },
161+
var basic_twice_capture = [_]Case{
162+
.{ .src = "nb = 0", .ex = "0" },
163+
.{ .src = "content.addEventListener('basic', cbk, true)", .ex = "undefined" },
164+
.{ .src = "content.dispatchEvent(new Event('basic'))", .ex = "true" },
165+
.{ .src = "nb", .ex = "2" },
108166
};
109-
try checkCases(js_env, &basic_child);
167+
try checkCases(js_env, &basic_twice_capture);
168+
169+
var basic_remove = [_]Case{
170+
.{ .src = "nb = 0", .ex = "0" },
171+
.{ .src = "content.removeEventListener('basic', cbk)", .ex = "undefined" },
172+
.{ .src = "content.dispatchEvent(new Event('basic'))", .ex = "true" },
173+
.{ .src = "nb", .ex = "1" },
174+
};
175+
try checkCases(js_env, &basic_remove);
176+
177+
var basic_capture_remove = [_]Case{
178+
.{ .src = "nb = 0", .ex = "0" },
179+
.{ .src = "content.removeEventListener('basic', cbk, true)", .ex = "undefined" },
180+
.{ .src = "content.dispatchEvent(new Event('basic'))", .ex = "true" },
181+
.{ .src = "nb", .ex = "0" },
182+
};
183+
try checkCases(js_env, &basic_capture_remove);
110184

111185
var capture = [_]Case{
112186
.{ .src = "nb = 0; evt = undefined; phase = undefined; cur = undefined", .ex = "undefined" },

src/netsurf.zig

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -446,15 +446,21 @@ pub fn eventPreventDefault(evt: *Event) !void {
446446
// EventHandler
447447
pub const EventHandler = fn (?*Event, ?*anyopaque) callconv(.C) void;
448448

449+
fn event_handler_cbk(data: *anyopaque) *Callback {
450+
const ptr: *align(@alignOf(*Callback)) anyopaque = @alignCast(data);
451+
return @as(*Callback, @ptrCast(ptr));
452+
}
453+
449454
const event_handler = struct {
450455
fn handle(event: ?*Event, data: ?*anyopaque) callconv(.C) void {
451-
const ptr: *align(@alignOf(*Callback)) anyopaque = @alignCast(data.?);
452-
const func = @as(*Callback, @ptrCast(ptr));
453-
func.call(.{event}) catch unreachable;
454-
// NOTE: we can not call func.deinit here
455-
// b/c the handler can be called several times
456-
// as the event goes through the ancestors
457-
// TODO: check the event phase to call func.deinit and free func
456+
if (data) |d| {
457+
const func = event_handler_cbk(d);
458+
func.call(.{event}) catch unreachable;
459+
// NOTE: we can not call func.deinit here
460+
// b/c the handler can be called several times
461+
// as the event goes through the ancestors
462+
// TODO: check the event phase to call func.deinit and free func
463+
}
458464
}
459465
}.handle;
460466

@@ -472,7 +478,12 @@ fn eventTargetVtable(et: *EventTarget) c.dom_event_target_vtable {
472478
return getVtable(c.dom_event_target_vtable, EventTarget, et);
473479
}
474480

475-
pub fn eventTargetHasListener(et: *EventTarget, typ: []const u8, cbk_id: usize) !?*EventListener {
481+
pub fn eventTargetHasListener(
482+
et: *EventTarget,
483+
typ: []const u8,
484+
capture: bool,
485+
cbk_id: usize,
486+
) !?*EventListener {
476487
const str = try strFromData(typ);
477488

478489
const EventListenerEntry = c.listener_entry;
@@ -482,7 +493,14 @@ pub fn eventTargetHasListener(et: *EventTarget, typ: []const u8, cbk_id: usize)
482493

483494
// iterate over the EventTarget's listeners
484495
while (true) {
485-
const err = eventTargetVtable(et).iter_event_listener.?(et, str, current, &next, &lst);
496+
const err = eventTargetVtable(et).iter_event_listener.?(
497+
et,
498+
str,
499+
capture,
500+
current,
501+
&next,
502+
&lst,
503+
);
486504
try DOMErr(err);
487505

488506
if (lst) |listener| {
@@ -491,8 +509,7 @@ pub fn eventTargetHasListener(et: *EventTarget, typ: []const u8, cbk_id: usize)
491509
defer c.dom_event_listener_unref(listener);
492510
const data = eventListenerGetData(listener);
493511
if (data) |d| {
494-
const ptr: *align(@alignOf(*Callback)) anyopaque = @alignCast(d);
495-
const cbk = @as(*Callback, @ptrCast(ptr));
512+
const cbk = event_handler_cbk(d);
496513
if (cbk_id == cbk.id())
497514
return lst;
498515
}
@@ -525,9 +542,25 @@ pub fn eventTargetAddEventListener(
525542
try DOMErr(err);
526543
}
527544

545+
pub fn eventTargetRemoveEventListener(
546+
et: *EventTarget,
547+
typ: []const u8,
548+
lst: *EventListener,
549+
capture: bool,
550+
) !?*Callback {
551+
const data = eventListenerGetData(lst);
552+
var cbk_ptr: ?*Callback = null;
553+
if (data) |d| {
554+
cbk_ptr = event_handler_cbk(d);
555+
}
556+
const s = try strFromData(typ);
557+
const err = eventTargetVtable(et).remove_event_listener.?(et, s, lst, capture);
558+
try DOMErr(err);
559+
return cbk_ptr;
560+
}
561+
528562
pub fn eventTargetDispatchEvent(et: *EventTarget, event: *Event) !bool {
529563
var res: bool = undefined;
530-
// const err = c.dom_event_target_dispatch_event(et, event, &res);
531564
const err = eventTargetVtable(et).dispatch_event.?(et, event, &res);
532565
try DOMErr(err);
533566
return res;

0 commit comments

Comments
 (0)