Fix crash when event.currentTarget is used with EventTargetTBase

When EventTargetTBase is used, we pass the container as the target to libdom.
This is not safe, as libdom is expecting an event_target. We see, for example
that when _dom_event_get_current_target is called, the refcnt is increased.
This works if the current_target is a valid event_target, but if it's a
Zig instance (like the Window) ... we're just altering some bits of the
window instance.

This attempts to add a dummy target to EventTargetTBase which can acts as a
real event_targt in place of the Zig instance.
This commit is contained in:
Karl Seguin
2025-06-18 14:49:15 +08:00
parent 9b35736be3
commit 223611d89e
3 changed files with 33 additions and 3 deletions

View File

@@ -39,7 +39,7 @@ pub const EventTarget = struct {
// Ideally, we'd remove this duality. Failing that, we'll need to embed // Ideally, we'd remove this duality. Failing that, we'll need to embed
// data into the *parser.EventTarget should we need this for other types. // data into the *parser.EventTarget should we need this for other types.
// For now, for the Window, which is a singleton, we can do this: // For now, for the Window, which is a singleton, we can do this:
if (@intFromPtr(et) == @intFromPtr(&page.window.base)) { if (@intFromPtr(et) == @intFromPtr(&page.window.base.target)) {
return .{ .Window = &page.window }; return .{ .Window = &page.window };
} }
return Nod.Node.toInterface(@as(*parser.Node, @ptrCast(et))); return Nod.Node.toInterface(@as(*parser.Node, @ptrCast(et)));

View File

@@ -387,4 +387,19 @@ test "Browser.HTML.Window" {
.{ "window.setTimeout(() => {longCall = true}, 5001);", null }, .{ "window.setTimeout(() => {longCall = true}, 5001);", null },
.{ "longCall;", "false" }, .{ "longCall;", "false" },
}, .{}); }, .{});
// window event target
try runner.testCases(&.{
.{
\\ let called = false;
\\ window.addEventListener("ready", (e) => {
\\ called = (e.currentTarget == window);
\\ }, {capture: false, once: false});
\\ const evt = new Event("ready", { bubbles: true, cancelable: false });
\\ window.dispatchEvent(evt);
\\ called;
,
"true",
},
}, .{});
} }

View File

@@ -777,6 +777,21 @@ pub const EventTargetTBase = extern struct {
}, },
eti: c.dom_event_target_internal = c.dom_event_target_internal{ .listeners = null }, eti: c.dom_event_target_internal = c.dom_event_target_internal{ .listeners = null },
// When we dispatch the event, we need to provide a target. In reality, the
// target is the container of this EventTargetTBase. But we can't pass that
// to _dom_event_target_dispatch, because it expects a dom_event_target.
// If you try to pass an non-event_target, you'll get weird behavior. For
// example, libdom might dom_node_ref that memory. Say we passed a *Window
// as the target, what happens if libdom calls dom_node_ref(window)? If
// you're lucky, you'll crash. If you're unlucky, you'll increment a random
// part of the window structure.
target: DummyTarget = .{},
const DummyTarget = extern struct {
vtable: usize = undefined,
refcnt: u32 = 0,
};
pub fn add_event_listener(et: [*c]c.dom_event_target, t: [*c]c.dom_string, l: ?*c.struct_dom_event_listener, capture: bool) callconv(.C) c.dom_exception { pub fn add_event_listener(et: [*c]c.dom_event_target, t: [*c]c.dom_string, l: ?*c.struct_dom_event_listener, capture: bool) callconv(.C) c.dom_exception {
const self = @as(*Self, @ptrCast(et)); const self = @as(*Self, @ptrCast(et));
return c._dom_event_target_add_event_listener(&self.eti, t, l, capture); return c._dom_event_target_add_event_listener(&self.eti, t, l, capture);
@@ -785,11 +800,11 @@ pub const EventTargetTBase = extern struct {
pub fn dispatch_event(et: [*c]c.dom_event_target, evt: ?*c.struct_dom_event, res: [*c]bool) callconv(.C) c.dom_exception { pub fn dispatch_event(et: [*c]c.dom_event_target, evt: ?*c.struct_dom_event, res: [*c]bool) callconv(.C) c.dom_exception {
const self = @as(*Self, @ptrCast(et)); const self = @as(*Self, @ptrCast(et));
// Set the event target to the target dispatched. // Set the event target to the target dispatched.
const e = c._dom_event_set_target(evt, et); const e = c._dom_event_set_target(evt, @ptrCast(&self.target));
if (e != c.DOM_NO_ERR) { if (e != c.DOM_NO_ERR) {
return e; return e;
} }
return c._dom_event_target_dispatch(et, &self.eti, evt, c.DOM_AT_TARGET, res); return c._dom_event_target_dispatch(@ptrCast(&self.target), &self.eti, evt, c.DOM_AT_TARGET, res);
} }
pub fn remove_event_listener(et: [*c]c.dom_event_target, t: [*c]c.dom_string, l: ?*c.struct_dom_event_listener, capture: bool) callconv(.C) c.dom_exception { pub fn remove_event_listener(et: [*c]c.dom_event_target, t: [*c]c.dom_string, l: ?*c.struct_dom_event_listener, capture: bool) callconv(.C) c.dom_exception {