From 8904afaa7462c34b07a1892d827ff1a8ac1c6230 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Wed, 4 Jun 2025 11:15:36 +0800 Subject: [PATCH] Fix crash when event target is the window. On page load, emitted by the page, the target is the window, but it's improperly cast since the pointer is actually `window.base`. This is going to be a problem in general for any Zig type dispatched as a target, but the Window one is the most obvious and the easiest to fix. If this issue comes up with other types, we'll need to come up with a more robust solution. --- src/browser/dom/event_target.zig | 12 +++++++++--- src/browser/events/custom_event.zig | 1 + src/browser/events/event.zig | 9 +++++---- src/browser/xhr/progress_event.zig | 1 + src/runtime/generate.zig | 12 +++++++----- 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/browser/dom/event_target.zig b/src/browser/dom/event_target.zig index 34836fba..d2a7aae0 100644 --- a/src/browser/dom/event_target.zig +++ b/src/browser/dom/event_target.zig @@ -33,9 +33,15 @@ pub const EventTarget = struct { pub const Self = parser.EventTarget; pub const Exception = DOMException; - pub fn toInterface(et: *parser.EventTarget) !Union { - // NOTE: for now we state that all EventTarget are Nodes - // TODO: handle other types (eg. Window) + pub fn toInterface(et: *parser.EventTarget, page: *Page) !Union { + // Not all targets are *parser.Nodes. page.zig emits a "load" event + // where the target is a Window, which cannot be cast directly to a node. + // 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. + // For now, for the Window, which is a singleton, we can do this: + if (@intFromPtr(et) == @intFromPtr(&page.window.base)) { + return .{ .Window = &page.window }; + } return Nod.Node.toInterface(@as(*parser.Node, @ptrCast(et))); } diff --git a/src/browser/events/custom_event.zig b/src/browser/events/custom_event.zig index a855beac..d5ce0bd3 100644 --- a/src/browser/events/custom_event.zig +++ b/src/browser/events/custom_event.zig @@ -23,6 +23,7 @@ const JsObject = @import("../env.zig").JsObject; // https://dom.spec.whatwg.org/#interface-customevent pub const CustomEvent = struct { pub const prototype = *Event; + pub const union_make_copy = true; proto: parser.Event, detail: ?JsObject, diff --git a/src/browser/events/event.zig b/src/browser/events/event.zig index 1a75f116..af8a75ae 100644 --- a/src/browser/events/event.zig +++ b/src/browser/events/event.zig @@ -23,6 +23,7 @@ const log = @import("../../log.zig"); const parser = @import("../netsurf.zig"); const generate = @import("../../runtime/generate.zig"); +const Page = @import("../page.zig").Page; const DOMException = @import("../dom/exceptions.zig").DOMException; const EventTarget = @import("../dom/event_target.zig").EventTarget; const EventTargetUnion = @import("../dom/event_target.zig").Union; @@ -76,16 +77,16 @@ pub const Event = struct { return try parser.eventType(self); } - pub fn get_target(self: *parser.Event) !?EventTargetUnion { + pub fn get_target(self: *parser.Event, page: *Page) !?EventTargetUnion { const et = try parser.eventTarget(self); if (et == null) return null; - return try EventTarget.toInterface(et.?); + return try EventTarget.toInterface(et.?, page); } - pub fn get_currentTarget(self: *parser.Event) !?EventTargetUnion { + pub fn get_currentTarget(self: *parser.Event, page: *Page) !?EventTargetUnion { const et = try parser.eventCurrentTarget(self); if (et == null) return null; - return try EventTarget.toInterface(et.?); + return try EventTarget.toInterface(et.?, page); } pub fn get_eventPhase(self: *parser.Event) !u8 { diff --git a/src/browser/xhr/progress_event.zig b/src/browser/xhr/progress_event.zig index 5cdd8dd5..a64a8b23 100644 --- a/src/browser/xhr/progress_event.zig +++ b/src/browser/xhr/progress_event.zig @@ -24,6 +24,7 @@ const DOMException = @import("../dom/exceptions.zig").DOMException; pub const ProgressEvent = struct { pub const prototype = *Event; pub const Exception = DOMException; + pub const union_make_copy = true; pub const EventInit = struct { lengthComputable: bool = false, diff --git a/src/runtime/generate.zig b/src/runtime/generate.zig index 1e1c5193..c5000cba 100644 --- a/src/runtime/generate.zig +++ b/src/runtime/generate.zig @@ -71,6 +71,8 @@ pub fn Union(comptime interfaces: anytype) type { var FT = @field(tuple, field.name); if (@hasDecl(FT, "Self")) { FT = *(@field(FT, "Self")); + } else if (!@hasDecl(FT, "union_make_copy")) { + FT = *FT; } union_fields[index] = .{ .type = FT, @@ -171,7 +173,7 @@ fn filterMap(comptime count: usize, interfaces: [count]type) struct { usize, [co return .{ unfiltered_count, map }; } -test "generate.Union" { +test "generate: Union" { const Astruct = struct { pub const Self = Other; const Other = struct {}; @@ -188,15 +190,15 @@ test "generate.Union" { const value = Union(.{ Astruct, Bstruct, .{Cstruct} }); const ti = @typeInfo(value).@"union"; try std.testing.expectEqual(3, ti.fields.len); - try std.testing.expectEqualStrings("*runtime.generate.test.generate.Union.Astruct.Other", @typeName(ti.fields[0].type)); + try std.testing.expectEqualStrings("*runtime.generate.test.generate: Union.Astruct.Other", @typeName(ti.fields[0].type)); try std.testing.expectEqualStrings(ti.fields[0].name, "Astruct"); - try std.testing.expectEqual(Bstruct, ti.fields[1].type); + try std.testing.expectEqual(*Bstruct, ti.fields[1].type); try std.testing.expectEqualStrings(ti.fields[1].name, "Bstruct"); - try std.testing.expectEqual(Cstruct, ti.fields[2].type); + try std.testing.expectEqual(*Cstruct, ti.fields[2].type); try std.testing.expectEqualStrings(ti.fields[2].name, "Cstruct"); } -test "generate.Tuple" { +test "generate: Tuple" { const Astruct = struct {}; const Bstruct = struct {