mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-12-16 16:28:58 +00:00
Add removeEventListener
And check if callback has been already added in addEventListener Signed-off-by: Francis Bouvier <francis@lightpanda.io>
This commit is contained in:
@@ -2,6 +2,7 @@ const std = @import("std");
|
|||||||
|
|
||||||
const jsruntime = @import("jsruntime");
|
const jsruntime = @import("jsruntime");
|
||||||
const Callback = jsruntime.Callback;
|
const Callback = jsruntime.Callback;
|
||||||
|
const JSObjectID = jsruntime.JSObjectID;
|
||||||
const Case = jsruntime.test_utils.Case;
|
const Case = jsruntime.test_utils.Case;
|
||||||
const checkCases = jsruntime.test_utils.checkCases;
|
const checkCases = jsruntime.test_utils.checkCases;
|
||||||
|
|
||||||
@@ -39,22 +40,71 @@ pub const EventTarget = struct {
|
|||||||
) !void {
|
) !void {
|
||||||
|
|
||||||
// check if event target has already this listener
|
// check if event target has already this listener
|
||||||
const lst = try parser.eventTargetHasListener(self, eventType, cbk.id());
|
const lst = try parser.eventTargetHasListener(
|
||||||
|
self,
|
||||||
|
eventType,
|
||||||
|
capture orelse false,
|
||||||
|
cbk.id(),
|
||||||
|
);
|
||||||
if (lst != null) {
|
if (lst != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: when can we free this allocation?
|
// NOTE: this allocation will be removed either if removeEventListener
|
||||||
|
// or at EventTarget deinit
|
||||||
const cbk_ptr = try alloc.create(Callback);
|
const cbk_ptr = try alloc.create(Callback);
|
||||||
cbk_ptr.* = cbk;
|
cbk_ptr.* = cbk;
|
||||||
try parser.eventTargetAddEventListener(self, eventType, cbk_ptr, capture orelse false);
|
try parser.eventTargetAddEventListener(
|
||||||
|
self,
|
||||||
|
eventType,
|
||||||
|
cbk_ptr,
|
||||||
|
capture orelse false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _removeEventListener(
|
||||||
|
self: *parser.EventTarget,
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
eventType: []const u8,
|
||||||
|
cbk_id: JSObjectID,
|
||||||
|
capture: ?bool,
|
||||||
|
// TODO: hanle EventListenerOptions
|
||||||
|
// see #https://github.com/lightpanda-io/jsruntime-lib/issues/114
|
||||||
|
) !void {
|
||||||
|
|
||||||
|
// check if event target has already this listener
|
||||||
|
const lst = try parser.eventTargetHasListener(
|
||||||
|
self,
|
||||||
|
eventType,
|
||||||
|
capture orelse false,
|
||||||
|
cbk_id.get(),
|
||||||
|
);
|
||||||
|
if (lst == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove listener
|
||||||
|
const cbk_handler = try parser.eventTargetRemoveEventListener(
|
||||||
|
self,
|
||||||
|
eventType,
|
||||||
|
lst.?,
|
||||||
|
capture orelse false,
|
||||||
|
);
|
||||||
|
if (cbk_handler) |cbk_ptr| {
|
||||||
|
cbk_ptr.deinit(alloc);
|
||||||
|
alloc.destroy(cbk_ptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _dispatchEvent(self: *parser.EventTarget, event: *parser.Event) !bool {
|
pub fn _dispatchEvent(self: *parser.EventTarget, event: *parser.Event) !bool {
|
||||||
return try parser.eventTargetDispatchEvent(self, event);
|
return try parser.eventTargetDispatchEvent(self, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(_: *parser.EventTarget, _: std.mem.Allocator) void {}
|
pub fn deinit(_: *parser.EventTarget, _: std.mem.Allocator) void {
|
||||||
|
// TODO:
|
||||||
|
// - deinit and destroy all cbk_handler
|
||||||
|
// - remove all listeners
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tests
|
// Tests
|
||||||
@@ -92,6 +142,14 @@ pub fn testExecFn(
|
|||||||
};
|
};
|
||||||
try checkCases(js_env, &basic);
|
try checkCases(js_env, &basic);
|
||||||
|
|
||||||
|
var basic_child = [_]Case{
|
||||||
|
.{ .src = "nb = 0; evt = undefined; phase = undefined; cur = undefined", .ex = "undefined" },
|
||||||
|
.{ .src = "para.dispatchEvent(new Event('basic'))", .ex = "true" },
|
||||||
|
.{ .src = "nb", .ex = "0" }, // handler is not called, no capture, not the target, no bubbling
|
||||||
|
.{ .src = "evt === undefined", .ex = "true" },
|
||||||
|
};
|
||||||
|
try checkCases(js_env, &basic_child);
|
||||||
|
|
||||||
var basic_twice = [_]Case{
|
var basic_twice = [_]Case{
|
||||||
.{ .src = "nb = 0", .ex = "0" },
|
.{ .src = "nb = 0", .ex = "0" },
|
||||||
.{ .src = "content.addEventListener('basic', cbk)", .ex = "undefined" },
|
.{ .src = "content.addEventListener('basic', cbk)", .ex = "undefined" },
|
||||||
@@ -100,13 +158,29 @@ pub fn testExecFn(
|
|||||||
};
|
};
|
||||||
try checkCases(js_env, &basic_twice);
|
try checkCases(js_env, &basic_twice);
|
||||||
|
|
||||||
var basic_child = [_]Case{
|
var basic_twice_capture = [_]Case{
|
||||||
.{ .src = "nb = 0; evt = undefined; phase = undefined; cur = undefined", .ex = "undefined" },
|
.{ .src = "nb = 0", .ex = "0" },
|
||||||
.{ .src = "para.dispatchEvent(new Event('basic'))", .ex = "true" },
|
.{ .src = "content.addEventListener('basic', cbk, true)", .ex = "undefined" },
|
||||||
.{ .src = "nb", .ex = "0" }, // handler is not called, no capture, not the target, no bubbling
|
.{ .src = "content.dispatchEvent(new Event('basic'))", .ex = "true" },
|
||||||
.{ .src = "evt === undefined", .ex = "true" },
|
.{ .src = "nb", .ex = "2" },
|
||||||
};
|
};
|
||||||
try checkCases(js_env, &basic_child);
|
try checkCases(js_env, &basic_twice_capture);
|
||||||
|
|
||||||
|
var basic_remove = [_]Case{
|
||||||
|
.{ .src = "nb = 0", .ex = "0" },
|
||||||
|
.{ .src = "content.removeEventListener('basic', cbk)", .ex = "undefined" },
|
||||||
|
.{ .src = "content.dispatchEvent(new Event('basic'))", .ex = "true" },
|
||||||
|
.{ .src = "nb", .ex = "1" },
|
||||||
|
};
|
||||||
|
try checkCases(js_env, &basic_remove);
|
||||||
|
|
||||||
|
var basic_capture_remove = [_]Case{
|
||||||
|
.{ .src = "nb = 0", .ex = "0" },
|
||||||
|
.{ .src = "content.removeEventListener('basic', cbk, true)", .ex = "undefined" },
|
||||||
|
.{ .src = "content.dispatchEvent(new Event('basic'))", .ex = "true" },
|
||||||
|
.{ .src = "nb", .ex = "0" },
|
||||||
|
};
|
||||||
|
try checkCases(js_env, &basic_capture_remove);
|
||||||
|
|
||||||
var capture = [_]Case{
|
var capture = [_]Case{
|
||||||
.{ .src = "nb = 0; evt = undefined; phase = undefined; cur = undefined", .ex = "undefined" },
|
.{ .src = "nb = 0; evt = undefined; phase = undefined; cur = undefined", .ex = "undefined" },
|
||||||
|
|||||||
@@ -446,16 +446,22 @@ pub fn eventPreventDefault(evt: *Event) !void {
|
|||||||
// EventHandler
|
// EventHandler
|
||||||
pub const EventHandler = fn (?*Event, ?*anyopaque) callconv(.C) void;
|
pub const EventHandler = fn (?*Event, ?*anyopaque) callconv(.C) void;
|
||||||
|
|
||||||
|
fn event_handler_cbk(data: *anyopaque) *Callback {
|
||||||
|
const ptr: *align(@alignOf(*Callback)) anyopaque = @alignCast(data);
|
||||||
|
return @as(*Callback, @ptrCast(ptr));
|
||||||
|
}
|
||||||
|
|
||||||
const event_handler = struct {
|
const event_handler = struct {
|
||||||
fn handle(event: ?*Event, data: ?*anyopaque) callconv(.C) void {
|
fn handle(event: ?*Event, data: ?*anyopaque) callconv(.C) void {
|
||||||
const ptr: *align(@alignOf(*Callback)) anyopaque = @alignCast(data.?);
|
if (data) |d| {
|
||||||
const func = @as(*Callback, @ptrCast(ptr));
|
const func = event_handler_cbk(d);
|
||||||
func.call(.{event}) catch unreachable;
|
func.call(.{event}) catch unreachable;
|
||||||
// NOTE: we can not call func.deinit here
|
// NOTE: we can not call func.deinit here
|
||||||
// b/c the handler can be called several times
|
// b/c the handler can be called several times
|
||||||
// as the event goes through the ancestors
|
// as the event goes through the ancestors
|
||||||
// TODO: check the event phase to call func.deinit and free func
|
// TODO: check the event phase to call func.deinit and free func
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}.handle;
|
}.handle;
|
||||||
|
|
||||||
// EventListener
|
// EventListener
|
||||||
@@ -472,7 +478,12 @@ fn eventTargetVtable(et: *EventTarget) c.dom_event_target_vtable {
|
|||||||
return getVtable(c.dom_event_target_vtable, EventTarget, et);
|
return getVtable(c.dom_event_target_vtable, EventTarget, et);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eventTargetHasListener(et: *EventTarget, typ: []const u8, cbk_id: usize) !?*EventListener {
|
pub fn eventTargetHasListener(
|
||||||
|
et: *EventTarget,
|
||||||
|
typ: []const u8,
|
||||||
|
capture: bool,
|
||||||
|
cbk_id: usize,
|
||||||
|
) !?*EventListener {
|
||||||
const str = try strFromData(typ);
|
const str = try strFromData(typ);
|
||||||
|
|
||||||
const EventListenerEntry = c.listener_entry;
|
const EventListenerEntry = c.listener_entry;
|
||||||
@@ -482,7 +493,14 @@ pub fn eventTargetHasListener(et: *EventTarget, typ: []const u8, cbk_id: usize)
|
|||||||
|
|
||||||
// iterate over the EventTarget's listeners
|
// iterate over the EventTarget's listeners
|
||||||
while (true) {
|
while (true) {
|
||||||
const err = eventTargetVtable(et).iter_event_listener.?(et, str, current, &next, &lst);
|
const err = eventTargetVtable(et).iter_event_listener.?(
|
||||||
|
et,
|
||||||
|
str,
|
||||||
|
capture,
|
||||||
|
current,
|
||||||
|
&next,
|
||||||
|
&lst,
|
||||||
|
);
|
||||||
try DOMErr(err);
|
try DOMErr(err);
|
||||||
|
|
||||||
if (lst) |listener| {
|
if (lst) |listener| {
|
||||||
@@ -491,8 +509,7 @@ pub fn eventTargetHasListener(et: *EventTarget, typ: []const u8, cbk_id: usize)
|
|||||||
defer c.dom_event_listener_unref(listener);
|
defer c.dom_event_listener_unref(listener);
|
||||||
const data = eventListenerGetData(listener);
|
const data = eventListenerGetData(listener);
|
||||||
if (data) |d| {
|
if (data) |d| {
|
||||||
const ptr: *align(@alignOf(*Callback)) anyopaque = @alignCast(d);
|
const cbk = event_handler_cbk(d);
|
||||||
const cbk = @as(*Callback, @ptrCast(ptr));
|
|
||||||
if (cbk_id == cbk.id())
|
if (cbk_id == cbk.id())
|
||||||
return lst;
|
return lst;
|
||||||
}
|
}
|
||||||
@@ -525,9 +542,25 @@ pub fn eventTargetAddEventListener(
|
|||||||
try DOMErr(err);
|
try DOMErr(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn eventTargetRemoveEventListener(
|
||||||
|
et: *EventTarget,
|
||||||
|
typ: []const u8,
|
||||||
|
lst: *EventListener,
|
||||||
|
capture: bool,
|
||||||
|
) !?*Callback {
|
||||||
|
const data = eventListenerGetData(lst);
|
||||||
|
var cbk_ptr: ?*Callback = null;
|
||||||
|
if (data) |d| {
|
||||||
|
cbk_ptr = event_handler_cbk(d);
|
||||||
|
}
|
||||||
|
const s = try strFromData(typ);
|
||||||
|
const err = eventTargetVtable(et).remove_event_listener.?(et, s, lst, capture);
|
||||||
|
try DOMErr(err);
|
||||||
|
return cbk_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn eventTargetDispatchEvent(et: *EventTarget, event: *Event) !bool {
|
pub fn eventTargetDispatchEvent(et: *EventTarget, event: *Event) !bool {
|
||||||
var res: bool = undefined;
|
var res: bool = undefined;
|
||||||
// const err = c.dom_event_target_dispatch_event(et, event, &res);
|
|
||||||
const err = eventTargetVtable(et).dispatch_event.?(et, event, &res);
|
const err = eventTargetVtable(et).dispatch_event.?(et, event, &res);
|
||||||
try DOMErr(err);
|
try DOMErr(err);
|
||||||
return res;
|
return res;
|
||||||
|
|||||||
Reference in New Issue
Block a user