From 6b41677c1f55e6f436ceac2b0dfc6a292077e276 Mon Sep 17 00:00:00 2001 From: sjorsdonkers <72333389+sjorsdonkers@users.noreply.github.com> Date: Tue, 8 Jul 2025 13:21:52 +0200 Subject: [PATCH 1/3] EventTarget constructor --- src/browser/dom/event_target.zig | 12 ++++++++++++ src/runtime/js.zig | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/browser/dom/event_target.zig b/src/browser/dom/event_target.zig index 7d1b18c3..e3ddaa0f 100644 --- a/src/browser/dom/event_target.zig +++ b/src/browser/dom/event_target.zig @@ -35,6 +35,9 @@ pub const EventTarget = struct { pub const Self = parser.EventTarget; pub const Exception = DOMException; + // Extend libdom event target for pure zig struct. + base: parser.EventTargetTBase = parser.EventTargetTBase{}, + pub fn toInterface(e: *parser.Event, et: *parser.EventTarget, page: *Page) !Union { // libdom assumes that all event targets are libdom nodes. They are not. @@ -63,6 +66,11 @@ pub const EventTarget = struct { // JS funcs // -------- + pub fn constructor(page: *Page) !*parser.EventTarget { + const et = try page.arena.create(EventTarget); + return @ptrCast(&et.base); + } + pub fn _addEventListener( self: *parser.EventTarget, typ: []const u8, @@ -128,6 +136,10 @@ test "Browser.DOM.EventTarget" { var runner = try testing.jsRunner(testing.tracking_allocator, .{}); defer runner.deinit(); + try runner.testCases(&.{ + .{ "new EventTarget()", "[object EventTarget]" }, + }, .{}); + try runner.testCases(&.{ .{ "let content = document.getElementById('content')", "undefined" }, .{ "let para = document.getElementById('para')", "undefined" }, diff --git a/src/runtime/js.zig b/src/runtime/js.zig index 5c97a124..455aeebd 100644 --- a/src/runtime/js.zig +++ b/src/runtime/js.zig @@ -2002,7 +2002,6 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { const info = v8.FunctionCallbackInfo.initFromV8(raw_info); var caller = Caller(Self, State).init(info); defer caller.deinit(); - // See comment above. We generateConstructor on all types // in order to create the FunctionTemplate, but there might // not be an actual "constructor" function. So if someone @@ -2010,6 +2009,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { // a constructor function, we'll return an error. if (@hasDecl(Struct, "constructor") == false) { const iso = caller.isolate; + log.warn(.js, "Illegal constructor call", .{ .name = @typeName(Struct) }); const js_exception = iso.throwException(createException(iso, "Illegal Constructor")); info.getReturnValue().set(js_exception); return; @@ -3420,7 +3420,7 @@ fn valueToDetailString(arena: Allocator, value: v8.Value, isolate: v8.Isolate, v if (debugValueToString(arena, value.castTo(v8.Object), isolate, v8_context)) |ds| { return ds; } else |err| { - log.err(.js, "debug serialize value", .{.err = err}); + log.err(.js, "debug serialize value", .{ .err = err }); } } } From 6b1eb4aab593d5010ca16877ea989aeb4052faa2 Mon Sep 17 00:00:00 2001 From: sjorsdonkers <72333389+sjorsdonkers@users.noreply.github.com> Date: Thu, 10 Jul 2025 14:21:02 +0200 Subject: [PATCH 2/3] EventTarget InternalType --- src/browser/dom/event_target.zig | 7 ++++++- src/browser/netsurf.zig | 19 +++++++++++++++++++ vendor/netsurf/libdom | 2 +- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/browser/dom/event_target.zig b/src/browser/dom/event_target.zig index e3ddaa0f..21d93eda 100644 --- a/src/browser/dom/event_target.zig +++ b/src/browser/dom/event_target.zig @@ -28,6 +28,7 @@ const nod = @import("node.zig"); pub const Union = union(enum) { node: nod.Union, xhr: *@import("../xhr/xhr.zig").XMLHttpRequest, + plain: *parser.EventTarget, }; // EventTarget implementation @@ -36,7 +37,7 @@ pub const EventTarget = struct { pub const Exception = DOMException; // Extend libdom event target for pure zig struct. - base: parser.EventTargetTBase = parser.EventTargetTBase{}, + base: parser.EventTargetTBase = parser.EventTargetTBase{ .internal_target_type = .plain }, pub fn toInterface(e: *parser.Event, et: *parser.EventTarget, page: *Page) !Union { // libdom assumes that all event targets are libdom nodes. They are not. @@ -47,6 +48,10 @@ pub const EventTarget = struct { return .{ .node = .{ .Window = &page.window } }; } + if (try parser.eventTargetInternalType(et) == .plain) { + return .{ .plain = et }; + } + // AbortSignal is another non-node target. It has a distinct usage though // so we hijack the event internal type to identity if. switch (try parser.eventGetInternalType(e)) { diff --git a/src/browser/netsurf.zig b/src/browser/netsurf.zig index 3be9481a..69fc6fdd 100644 --- a/src/browser/netsurf.zig +++ b/src/browser/netsurf.zig @@ -749,6 +749,13 @@ pub fn eventTargetDispatchEvent(et: *EventTarget, event: *Event) !bool { return res; } +pub fn eventTargetInternalType(et: *EventTarget) !EventTargetTBase.InternalType { + var res: u32 = undefined; + const err = eventTargetVtable(et).internal_type.?(et, &res); + try DOMErr(err); + return @enumFromInt(res); +} + pub fn elementDispatchEvent(element: *Element, event: *Event) !bool { const et: *EventTarget = toEventTarget(Element, element); return eventTargetDispatchEvent(et, @ptrCast(event)); @@ -771,12 +778,17 @@ pub fn eventTargetTBaseFieldName(comptime T: type) ?[]const u8 { // EventTargetBase is used to implement EventTarget for pure zig struct. pub const EventTargetTBase = extern struct { const Self = @This(); + const InternalType = enum(u32) { + libdom = 0, + plain = 1, + }; vtable: ?*const c.struct_dom_event_target_vtable = &c.struct_dom_event_target_vtable{ .dispatch_event = dispatch_event, .remove_event_listener = remove_event_listener, .add_event_listener = add_event_listener, .iter_event_listener = iter_event_listener, + .internal_type = internal_type, }, // When we dispatch the event, we need to provide a target. In reality, the @@ -790,6 +802,7 @@ pub const EventTargetTBase = extern struct { refcnt: u32 = 0, eti: c.dom_event_target_internal = c.dom_event_target_internal{ .listeners = null }, + internal_target_type: InternalType = .libdom, 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)); @@ -822,6 +835,12 @@ pub const EventTargetTBase = extern struct { const self = @as(*Self, @ptrCast(et)); return c._dom_event_target_iter_event_listener(self.eti, t, capture, cur, next, l); } + + pub fn internal_type(et: [*c]c.dom_event_target, internal_type_: [*c]u32) callconv(.C) c.dom_exception { + const self = @as(*Self, @ptrCast(et)); + internal_type_.* = @intFromEnum(self.internal_target_type); + return c.DOM_NO_ERR; + } }; // MouseEvent diff --git a/vendor/netsurf/libdom b/vendor/netsurf/libdom index 614187b0..84723c5b 160000 --- a/vendor/netsurf/libdom +++ b/vendor/netsurf/libdom @@ -1 +1 @@ -Subproject commit 614187b0aa6305ff992b4e0cb24af2e3ca0c4bfc +Subproject commit 84723c5be588db1d8a7777eba528d1976f394012 From 14eeffd9f63cbbb01bd3f76a4e676e78816a3fd7 Mon Sep 17 00:00:00 2001 From: sjorsdonkers <72333389+sjorsdonkers@users.noreply.github.com> Date: Fri, 11 Jul 2025 09:49:29 +0200 Subject: [PATCH 3/3] EventTarget internal type for all --- src/browser/dom/event_target.zig | 34 +++++++++++++-------------- src/browser/dom/performance.zig | 2 +- src/browser/events/event.zig | 12 +++++----- src/browser/html/AbortController.zig | 3 +-- src/browser/html/media_query_list.zig | 2 +- src/browser/html/window.zig | 2 +- src/browser/netsurf.zig | 9 +++++-- src/browser/xhr/event_target.zig | 2 +- 8 files changed, 34 insertions(+), 32 deletions(-) diff --git a/src/browser/dom/event_target.zig b/src/browser/dom/event_target.zig index 21d93eda..bf59910a 100644 --- a/src/browser/dom/event_target.zig +++ b/src/browser/dom/event_target.zig @@ -16,6 +16,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +const std = @import("std"); const Env = @import("../env.zig").Env; const parser = @import("../netsurf.zig"); const Page = @import("../page.zig").Page; @@ -39,33 +40,30 @@ pub const EventTarget = struct { // Extend libdom event target for pure zig struct. base: parser.EventTargetTBase = parser.EventTargetTBase{ .internal_target_type = .plain }, - pub fn toInterface(e: *parser.Event, et: *parser.EventTarget, page: *Page) !Union { + pub fn toInterface(et: *parser.EventTarget, page: *Page) !Union { // libdom assumes that all event targets are libdom nodes. They are not. - // The window is a common non-node target, but it's easy to handle as - // its a singleton. - if (@intFromPtr(et) == @intFromPtr(&page.window.base)) { - return .{ .node = .{ .Window = &page.window } }; - } - - if (try parser.eventTargetInternalType(et) == .plain) { - return .{ .plain = et }; - } - - // AbortSignal is another non-node target. It has a distinct usage though - // so we hijack the event internal type to identity if. - switch (try parser.eventGetInternalType(e)) { + switch (try parser.eventTargetInternalType(et)) { + .libdom_node => { + return .{ .node = try nod.Node.toInterface(@as(*parser.Node, @ptrCast(et))) }; + }, + .plain => return .{ .plain = et }, .abort_signal => { + // AbortSignal is a special case, it has its own internal type. + // We return it as a node, but we need to handle it differently. return .{ .node = .{ .AbortSignal = @fieldParentPtr("proto", @as(*parser.EventTargetTBase, @ptrCast(et))) } }; }, - .xhr_event => { + .window => { + // The window is a common non-node target, but it's easy to handle as its a singleton. + std.debug.assert(@intFromPtr(et) == @intFromPtr(&page.window.base)); + return .{ .node = .{ .Window = &page.window } }; + }, + .xhr => { const XMLHttpRequestEventTarget = @import("../xhr/event_target.zig").XMLHttpRequestEventTarget; const base: *XMLHttpRequestEventTarget = @fieldParentPtr("base", @as(*parser.EventTargetTBase, @ptrCast(et))); return .{ .xhr = @fieldParentPtr("proto", base) }; }, - else => { - return .{ .node = try nod.Node.toInterface(@as(*parser.Node, @ptrCast(et))) }; - }, + else => return error.MissingEventTargetType, } } diff --git a/src/browser/dom/performance.zig b/src/browser/dom/performance.zig index 7346ddfc..145206f9 100644 --- a/src/browser/dom/performance.zig +++ b/src/browser/dom/performance.zig @@ -39,7 +39,7 @@ pub const Performance = struct { pub const prototype = *EventTarget; // Extend libdom event target for pure zig struct. - base: parser.EventTargetTBase = parser.EventTargetTBase{}, + base: parser.EventTargetTBase = parser.EventTargetTBase{ .internal_target_type = .performance }, time_origin: std.time.Timer, // if (Window.crossOriginIsolated) -> Resolution in isolated contexts: 5 microseconds diff --git a/src/browser/events/event.zig b/src/browser/events/event.zig index 417f5c69..b9a5c297 100644 --- a/src/browser/events/event.zig +++ b/src/browser/events/event.zig @@ -78,13 +78,13 @@ pub const Event = struct { 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(self, et.?, page); + return try EventTarget.toInterface(et.?, page); } 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(self, et.?, page); + return try EventTarget.toInterface(et.?, page); } pub fn get_eventPhase(self: *parser.Event) !u8 { @@ -215,7 +215,7 @@ pub const EventHandler = struct { .typ = try allocator.dupe(u8, typ), .signal_target = signal_target, .signal_listener = undefined, - .node = .{.func = SignalCallback.handle }, + .node = .{ .func = SignalCallback.handle }, }; scb.signal_listener = try parser.eventTargetAddEventListener( @@ -285,7 +285,7 @@ const SignalCallback = struct { typ: []const u8, capture: bool, callback_id: usize, - node : parser.EventNode, + node: parser.EventNode, target: *parser.EventTarget, signal_target: *parser.EventTarget, signal_listener: *parser.EventListener, @@ -435,11 +435,11 @@ test "Browser.Event" { try runner.testCases(&.{ .{ "nb = 0; function cbk(event) { nb ++; }", null }, - .{ "let ac = new AbortController()", null}, + .{ "let ac = new AbortController()", null }, .{ "document.addEventListener('count', cbk, {signal: ac.signal})", null }, .{ "document.dispatchEvent(new Event('count'))", "true" }, .{ "document.dispatchEvent(new Event('count'))", "true" }, - .{ "ac.abort()", null}, + .{ "ac.abort()", null }, .{ "document.dispatchEvent(new Event('count'))", "true" }, .{ "nb", "2" }, .{ "document.removeEventListener('count', cbk)", "undefined" }, diff --git a/src/browser/html/AbortController.zig b/src/browser/html/AbortController.zig index bf7214a4..37f6eb5b 100644 --- a/src/browser/html/AbortController.zig +++ b/src/browser/html/AbortController.zig @@ -56,13 +56,12 @@ pub const AbortSignal = struct { const DEFAULT_REASON = "AbortError"; pub const prototype = *EventTarget; - proto: parser.EventTargetTBase = .{}, + proto: parser.EventTargetTBase = .{ .internal_target_type = .abort_signal }, aborted: bool, reason: ?[]const u8, pub const init: AbortSignal = .{ - .proto = .{}, .reason = null, .aborted = false, }; diff --git a/src/browser/html/media_query_list.zig b/src/browser/html/media_query_list.zig index 32ffc1d6..28216db3 100644 --- a/src/browser/html/media_query_list.zig +++ b/src/browser/html/media_query_list.zig @@ -26,7 +26,7 @@ pub const MediaQueryList = struct { // Extend libdom event target for pure zig struct. // This is not safe as it relies on a structure layout that isn't guaranteed - base: parser.EventTargetTBase = parser.EventTargetTBase{}, + base: parser.EventTargetTBase = parser.EventTargetTBase{ .internal_target_type = .media_query_list }, matches: bool, media: []const u8, diff --git a/src/browser/html/window.zig b/src/browser/html/window.zig index 78f57fd1..c1be58c6 100644 --- a/src/browser/html/window.zig +++ b/src/browser/html/window.zig @@ -45,7 +45,7 @@ pub const Window = struct { pub const prototype = *EventTarget; // Extend libdom event target for pure zig struct. - base: parser.EventTargetTBase = parser.EventTargetTBase{}, + base: parser.EventTargetTBase = parser.EventTargetTBase{ .internal_target_type = .window }, document: *parser.DocumentHTML, target: []const u8 = "", diff --git a/src/browser/netsurf.zig b/src/browser/netsurf.zig index 69fc6fdd..56c42344 100644 --- a/src/browser/netsurf.zig +++ b/src/browser/netsurf.zig @@ -779,8 +779,13 @@ pub fn eventTargetTBaseFieldName(comptime T: type) ?[]const u8 { pub const EventTargetTBase = extern struct { const Self = @This(); const InternalType = enum(u32) { - libdom = 0, + libdom_node = 0, plain = 1, + abort_signal = 2, + xhr = 3, + window = 4, + performance = 5, + media_query_list = 6, }; vtable: ?*const c.struct_dom_event_target_vtable = &c.struct_dom_event_target_vtable{ @@ -802,7 +807,7 @@ pub const EventTargetTBase = extern struct { refcnt: u32 = 0, eti: c.dom_event_target_internal = c.dom_event_target_internal{ .listeners = null }, - internal_target_type: InternalType = .libdom, + internal_target_type: InternalType, 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)); diff --git a/src/browser/xhr/event_target.zig b/src/browser/xhr/event_target.zig index f664a3a6..c74a1bf3 100644 --- a/src/browser/xhr/event_target.zig +++ b/src/browser/xhr/event_target.zig @@ -31,7 +31,7 @@ pub const XMLHttpRequestEventTarget = struct { pub const prototype = *EventTarget; // Extend libdom event target for pure zig struct. - base: parser.EventTargetTBase = parser.EventTargetTBase{}, + base: parser.EventTargetTBase = parser.EventTargetTBase{ .internal_target_type = .xhr }, onloadstart_cbk: ?Function = null, onprogress_cbk: ?Function = null,