mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 07:03:29 +00:00
Refactor events
Removes some duplication between xhr/event_target and dom/event_target. Implement 'once' option of addEventListener.
This commit is contained in:
@@ -41,68 +41,14 @@ pub const EventTarget = struct {
|
|||||||
|
|
||||||
// JS funcs
|
// JS funcs
|
||||||
// --------
|
// --------
|
||||||
|
|
||||||
const AddEventListenerOpts = union(enum) {
|
|
||||||
opts: Opts,
|
|
||||||
capture: bool,
|
|
||||||
|
|
||||||
const Opts = struct {
|
|
||||||
capture: ?bool,
|
|
||||||
// We ignore this property. It seems to be largely used to help the
|
|
||||||
// browser make certain performance tweaks (i.e. the browser knows
|
|
||||||
// that the listener won't call preventDefault() and thus can safely
|
|
||||||
// run the default as needed).
|
|
||||||
passive: ?bool,
|
|
||||||
once: ?bool, // currently does nothing
|
|
||||||
signal: ?bool, // currently does nothing
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn _addEventListener(
|
pub fn _addEventListener(
|
||||||
self: *parser.EventTarget,
|
self: *parser.EventTarget,
|
||||||
typ: []const u8,
|
typ: []const u8,
|
||||||
listener: EventHandler.Listener,
|
listener: EventHandler.Listener,
|
||||||
opts_: ?AddEventListenerOpts,
|
opts: ?EventHandler.Opts,
|
||||||
page: *Page,
|
page: *Page,
|
||||||
) !void {
|
) !void {
|
||||||
var capture = false;
|
_ = try EventHandler.register(page.arena, self, typ, listener, opts);
|
||||||
if (opts_) |opts| {
|
|
||||||
switch (opts) {
|
|
||||||
.capture => |c| capture = c,
|
|
||||||
.opts => |o| {
|
|
||||||
// Done this way so that, for common cases that _only_ set
|
|
||||||
// capture, i.e. {captrue: true}, it works.
|
|
||||||
// But for any case that sets any of the other flags, we
|
|
||||||
// error. If we don't error, this function call would succeed
|
|
||||||
// but the behavior might be wrong. At this point, it's
|
|
||||||
// better to be explicit and error.
|
|
||||||
if (o.once orelse false) return error.NotImplemented;
|
|
||||||
if (o.signal orelse false) return error.NotImplemented;
|
|
||||||
capture = o.capture orelse false;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const cbk = (try listener.callback(self)) orelse return;
|
|
||||||
|
|
||||||
// check if event target has already this listener
|
|
||||||
const lst = try parser.eventTargetHasListener(
|
|
||||||
self,
|
|
||||||
typ,
|
|
||||||
capture,
|
|
||||||
cbk.id,
|
|
||||||
);
|
|
||||||
if (lst != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const eh = try EventHandler.init(page.arena, cbk);
|
|
||||||
|
|
||||||
try parser.eventTargetAddEventListener(
|
|
||||||
self,
|
|
||||||
typ,
|
|
||||||
&eh.node,
|
|
||||||
capture,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const RemoveEventListenerOpts = union(enum) {
|
const RemoveEventListenerOpts = union(enum) {
|
||||||
|
|||||||
@@ -63,13 +63,13 @@ pub const MutationObserver = struct {
|
|||||||
|
|
||||||
// register node's events
|
// register node's events
|
||||||
if (options.childList or options.subtree) {
|
if (options.childList or options.subtree) {
|
||||||
try parser.eventTargetAddEventListener(
|
_ = try parser.eventTargetAddEventListener(
|
||||||
parser.toEventTarget(parser.Node, node),
|
parser.toEventTarget(parser.Node, node),
|
||||||
"DOMNodeInserted",
|
"DOMNodeInserted",
|
||||||
&observer.event_node,
|
&observer.event_node,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
try parser.eventTargetAddEventListener(
|
_ = try parser.eventTargetAddEventListener(
|
||||||
parser.toEventTarget(parser.Node, node),
|
parser.toEventTarget(parser.Node, node),
|
||||||
"DOMNodeRemoved",
|
"DOMNodeRemoved",
|
||||||
&observer.event_node,
|
&observer.event_node,
|
||||||
@@ -77,7 +77,7 @@ pub const MutationObserver = struct {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (options.attr()) {
|
if (options.attr()) {
|
||||||
try parser.eventTargetAddEventListener(
|
_ = try parser.eventTargetAddEventListener(
|
||||||
parser.toEventTarget(parser.Node, node),
|
parser.toEventTarget(parser.Node, node),
|
||||||
"DOMAttrModified",
|
"DOMAttrModified",
|
||||||
&observer.event_node,
|
&observer.event_node,
|
||||||
@@ -85,7 +85,7 @@ pub const MutationObserver = struct {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (options.cdata()) {
|
if (options.cdata()) {
|
||||||
try parser.eventTargetAddEventListener(
|
_ = try parser.eventTargetAddEventListener(
|
||||||
parser.toEventTarget(parser.Node, node),
|
parser.toEventTarget(parser.Node, node),
|
||||||
"DOMCharacterDataModified",
|
"DOMCharacterDataModified",
|
||||||
&observer.event_node,
|
&observer.event_node,
|
||||||
@@ -93,7 +93,7 @@ pub const MutationObserver = struct {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (options.subtree) {
|
if (options.subtree) {
|
||||||
try parser.eventTargetAddEventListener(
|
_ = try parser.eventTargetAddEventListener(
|
||||||
parser.toEventTarget(parser.Node, node),
|
parser.toEventTarget(parser.Node, node),
|
||||||
"DOMSubtreeModified",
|
"DOMSubtreeModified",
|
||||||
&observer.event_node,
|
&observer.event_node,
|
||||||
|
|||||||
@@ -138,8 +138,11 @@ pub const Event = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const EventHandler = struct {
|
pub const EventHandler = struct {
|
||||||
|
once: bool,
|
||||||
|
capture: bool,
|
||||||
callback: Function,
|
callback: Function,
|
||||||
node: parser.EventNode,
|
node: parser.EventNode,
|
||||||
|
listener: *parser.EventListener,
|
||||||
|
|
||||||
const Env = @import("../env.zig").Env;
|
const Env = @import("../env.zig").Env;
|
||||||
const Function = Env.Function;
|
const Function = Env.Function;
|
||||||
@@ -159,15 +162,73 @@ pub const EventHandler = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(allocator: Allocator, callback: Function) !*EventHandler {
|
pub const Opts = union(enum) {
|
||||||
|
flags: Flags,
|
||||||
|
capture: bool,
|
||||||
|
|
||||||
|
const Flags = struct {
|
||||||
|
once: ?bool,
|
||||||
|
capture: ?bool,
|
||||||
|
// We ignore this property. It seems to be largely used to help the
|
||||||
|
// browser make certain performance tweaks (i.e. the browser knows
|
||||||
|
// that the listener won't call preventDefault() and thus can safely
|
||||||
|
// run the default as needed).
|
||||||
|
passive: ?bool,
|
||||||
|
signal: ?bool, // currently does nothing
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn register(
|
||||||
|
allocator: Allocator,
|
||||||
|
target: *parser.EventTarget,
|
||||||
|
typ: []const u8,
|
||||||
|
listener: Listener,
|
||||||
|
opts_: ?Opts,
|
||||||
|
) !?*EventHandler {
|
||||||
|
var once = false;
|
||||||
|
var capture = false;
|
||||||
|
if (opts_) |opts| {
|
||||||
|
switch (opts) {
|
||||||
|
.capture => |c| capture = c,
|
||||||
|
.flags => |f| {
|
||||||
|
// Done this way so that, for common cases that _only_ set
|
||||||
|
// capture, i.e. {captrue: true}, it works.
|
||||||
|
// But for any case that sets any of the other flags, we
|
||||||
|
// error. If we don't error, this function call would succeed
|
||||||
|
// but the behavior might be wrong. At this point, it's
|
||||||
|
// better to be explicit and error.
|
||||||
|
if (f.signal orelse false) return error.NotImplemented;
|
||||||
|
once = f.once orelse false;
|
||||||
|
capture = f.capture orelse false;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const callback = (try listener.callback(target)) orelse return null;
|
||||||
|
|
||||||
|
// check if event target has already this listener
|
||||||
|
if (try parser.eventTargetHasListener(target, typ, capture, callback.id) != null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const eh = try allocator.create(EventHandler);
|
const eh = try allocator.create(EventHandler);
|
||||||
eh.* = .{
|
eh.* = .{
|
||||||
|
.once = once,
|
||||||
|
.capture = capture,
|
||||||
.callback = callback,
|
.callback = callback,
|
||||||
.node = .{
|
.node = .{
|
||||||
.id = callback.id,
|
.id = callback.id,
|
||||||
.func = handle,
|
.func = handle,
|
||||||
},
|
},
|
||||||
|
.listener = undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
eh.listener = try parser.eventTargetAddEventListener(
|
||||||
|
target,
|
||||||
|
typ,
|
||||||
|
&eh.node,
|
||||||
|
capture,
|
||||||
|
);
|
||||||
return eh;
|
return eh;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,6 +243,17 @@ pub const EventHandler = struct {
|
|||||||
self.callback.tryCall(void, .{ievent}, &result) catch {
|
self.callback.tryCall(void, .{ievent}, &result) catch {
|
||||||
log.debug(.event, "handle callback error", .{ .err = result.exception, .stack = result.stack });
|
log.debug(.event, "handle callback error", .{ .err = result.exception, .stack = result.stack });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (self.once) {
|
||||||
|
const target = (parser.eventTarget(event) catch return).?;
|
||||||
|
const typ = parser.eventType(event) catch return;
|
||||||
|
parser.eventTargetRemoveEventListener(
|
||||||
|
target,
|
||||||
|
typ,
|
||||||
|
self.listener,
|
||||||
|
self.capture,
|
||||||
|
) catch {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -282,4 +354,13 @@ test "Browser.Event" {
|
|||||||
.{ "document.dispatchEvent(new Event('count'))", "true" },
|
.{ "document.dispatchEvent(new Event('count'))", "true" },
|
||||||
.{ "nb", "0" },
|
.{ "nb", "0" },
|
||||||
}, .{});
|
}, .{});
|
||||||
|
|
||||||
|
try runner.testCases(&.{
|
||||||
|
.{ "nb = 0; function cbk(event) { nb ++; }", null },
|
||||||
|
.{ "document.addEventListener('count', cbk, {once: true})", null },
|
||||||
|
.{ "document.dispatchEvent(new Event('count'))", "true" },
|
||||||
|
.{ "document.dispatchEvent(new Event('count'))", "true" },
|
||||||
|
.{ "document.dispatchEvent(new Event('count'))", "true" },
|
||||||
|
.{ "nb", "1" },
|
||||||
|
}, .{});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -615,7 +615,7 @@ pub fn eventTargetAddEventListener(
|
|||||||
typ: []const u8,
|
typ: []const u8,
|
||||||
node: *EventNode,
|
node: *EventNode,
|
||||||
capture: bool,
|
capture: bool,
|
||||||
) !void {
|
) !*EventListener {
|
||||||
const event_handler = struct {
|
const event_handler = struct {
|
||||||
fn handle(event_: ?*Event, ptr_: ?*anyopaque) callconv(.C) void {
|
fn handle(event_: ?*Event, ptr_: ?*anyopaque) callconv(.C) void {
|
||||||
const ptr = ptr_ orelse return;
|
const ptr = ptr_ orelse return;
|
||||||
@@ -634,6 +634,8 @@ pub fn eventTargetAddEventListener(
|
|||||||
const s = try strFromData(typ);
|
const s = try strFromData(typ);
|
||||||
const err = eventTargetVtable(et).add_event_listener.?(et, s, listener, capture);
|
const err = eventTargetVtable(et).add_event_listener.?(et, s, listener, capture);
|
||||||
try DOMErr(err);
|
try DOMErr(err);
|
||||||
|
|
||||||
|
return listener.?;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eventTargetHasListener(
|
pub fn eventTargetHasListener(
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ pub const Page = struct {
|
|||||||
const doc = parser.documentHTMLToDocument(html_doc);
|
const doc = parser.documentHTMLToDocument(html_doc);
|
||||||
|
|
||||||
const document_element = (try parser.documentGetDocumentElement(doc)) orelse return error.DocumentElementError;
|
const document_element = (try parser.documentGetDocumentElement(doc)) orelse return error.DocumentElementError;
|
||||||
try parser.eventTargetAddEventListener(
|
_ = try parser.eventTargetAddEventListener(
|
||||||
parser.toEventTarget(parser.Element, document_element),
|
parser.toEventTarget(parser.Element, document_element),
|
||||||
"click",
|
"click",
|
||||||
&self.window_clicked_event_node,
|
&self.window_clicked_event_node,
|
||||||
|
|||||||
@@ -48,17 +48,14 @@ pub const XMLHttpRequestEventTarget = struct {
|
|||||||
) !?Function {
|
) !?Function {
|
||||||
const target = @as(*parser.EventTarget, @ptrCast(self));
|
const target = @as(*parser.EventTarget, @ptrCast(self));
|
||||||
|
|
||||||
const callback = (try listener.callback(target)) orelse return null;
|
// The only time this can return null if the listener is already
|
||||||
const eh = try EventHandler.init(alloc, callback);
|
// registered. But before calling `register`, all of our functions
|
||||||
try parser.eventTargetAddEventListener(
|
// remove any existing listener, so it should be impossible to get null
|
||||||
target,
|
// from this function call.
|
||||||
typ,
|
const eh = (try EventHandler.register(alloc, target, typ, listener, null)) orelse unreachable;
|
||||||
&eh.node,
|
return eh.callback;
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
return callback;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unregister(self: *XMLHttpRequestEventTarget, typ: []const u8, cbk_id: usize) !void {
|
fn unregister(self: *XMLHttpRequestEventTarget, typ: []const u8, cbk_id: usize) !void {
|
||||||
const et = @as(*parser.EventTarget, @ptrCast(self));
|
const et = @as(*parser.EventTarget, @ptrCast(self));
|
||||||
// check if event target has already this listener
|
// check if event target has already this listener
|
||||||
|
|||||||
Reference in New Issue
Block a user