diff --git a/src/browser/dom/event_target.zig b/src/browser/dom/event_target.zig index 7d1b18c36..bf59910a5 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; @@ -28,6 +29,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 @@ -35,34 +37,43 @@ pub const EventTarget = struct { pub const Self = parser.EventTarget; pub const Exception = DOMException; - pub fn toInterface(e: *parser.Event, et: *parser.EventTarget, page: *Page) !Union { - // libdom assumes that all event targets are libdom nodes. They are not. + // Extend libdom event target for pure zig struct. + base: parser.EventTargetTBase = parser.EventTargetTBase{ .internal_target_type = .plain }, - // 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 } }; - } + pub fn toInterface(et: *parser.EventTarget, page: *Page) !Union { + // libdom assumes that all event targets are libdom nodes. They are not. - // 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, } } // 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 +139,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/browser/dom/performance.zig b/src/browser/dom/performance.zig index 7346ddfc5..145206f9c 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 417f5c692..b9a5c2977 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 bf7214a42..37f6eb5bd 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 32ffc1d62..28216db35 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 78f57fd17..c1be58c64 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 3be9481a2..56c423446 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,22 @@ 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_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{ .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 +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, 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 +840,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/src/browser/xhr/event_target.zig b/src/browser/xhr/event_target.zig index f664a3a68..c74a1bf35 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, diff --git a/src/runtime/js.zig b/src/runtime/js.zig index 5c97a1247..455aeebd7 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 }); } } } diff --git a/vendor/netsurf/libdom b/vendor/netsurf/libdom index 614187b0a..84723c5be 160000 --- a/vendor/netsurf/libdom +++ b/vendor/netsurf/libdom @@ -1 +1 @@ -Subproject commit 614187b0aa6305ff992b4e0cb24af2e3ca0c4bfc +Subproject commit 84723c5be588db1d8a7777eba528d1976f394012