diff --git a/src/browser/dom/dom.zig b/src/browser/dom/dom.zig index f9b7514f..4359b4bb 100644 --- a/src/browser/dom/dom.zig +++ b/src/browser/dom/dom.zig @@ -28,6 +28,8 @@ const IntersectionObserver = @import("intersection_observer.zig"); const DOMParser = @import("dom_parser.zig").DOMParser; const TreeWalker = @import("tree_walker.zig").TreeWalker; const NodeFilter = @import("node_filter.zig").NodeFilter; +const Performance = @import("performance.zig").Performance; +const PerformanceObserver = @import("performance_observer.zig").PerformanceObserver; pub const Interfaces = .{ DOMException, @@ -44,4 +46,6 @@ pub const Interfaces = .{ DOMParser, TreeWalker, NodeFilter, + Performance, + PerformanceObserver, }; diff --git a/src/browser/html/performance.zig b/src/browser/dom/performance.zig similarity index 100% rename from src/browser/html/performance.zig rename to src/browser/dom/performance.zig diff --git a/src/browser/dom/performance_observer.zig b/src/browser/dom/performance_observer.zig new file mode 100644 index 00000000..69ce1f11 --- /dev/null +++ b/src/browser/dom/performance_observer.zig @@ -0,0 +1,34 @@ +// Copyright (C) 2023-2025 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +const std = @import("std"); + +// https://developer.mozilla.org/en-US/docs/Web/API/PerformanceObserver +pub const PerformanceObserver = struct { + pub const _supportedEntryTypes = [0][]const u8{}; +}; + +const testing = @import("../../testing.zig"); +test "Browser.DOM.PerformanceObserver" { + var runner = try testing.jsRunner(testing.tracking_allocator, .{}); + defer runner.deinit(); + + try runner.testCases(&.{ + .{ "PerformanceObserver.supportedEntryTypes.length", "0" }, + }, .{}); +} diff --git a/src/browser/html/html.zig b/src/browser/html/html.zig index 33b6b2ad..01f5f443 100644 --- a/src/browser/html/html.zig +++ b/src/browser/html/html.zig @@ -24,7 +24,6 @@ const Navigator = @import("navigator.zig").Navigator; const History = @import("history.zig").History; const Location = @import("location.zig").Location; const MediaQueryList = @import("media_query_list.zig").MediaQueryList; -const Performance = @import("performance.zig").Performance; pub const Interfaces = .{ HTMLDocument, @@ -37,6 +36,5 @@ pub const Interfaces = .{ History, Location, MediaQueryList, - Performance, @import("screen.zig").Interfaces, }; diff --git a/src/browser/html/window.zig b/src/browser/html/window.zig index c1fbdef2..60ca51b9 100644 --- a/src/browser/html/window.zig +++ b/src/browser/html/window.zig @@ -31,7 +31,7 @@ const Crypto = @import("../crypto/crypto.zig").Crypto; const Console = @import("../console/console.zig").Console; const EventTarget = @import("../dom/event_target.zig").EventTarget; const MediaQueryList = @import("media_query_list.zig").MediaQueryList; -const Performance = @import("performance.zig").Performance; +const Performance = @import("../dom/performance.zig").Performance; const CSSStyleDeclaration = @import("../cssom/css_style_declaration.zig").CSSStyleDeclaration; const CustomElementRegistry = @import("../webcomponents/custom_element_registry.zig").CustomElementRegistry; const Screen = @import("screen.zig").Screen; diff --git a/src/runtime/js.zig b/src/runtime/js.zig index 36aab531..07118ffc 100644 --- a/src/runtime/js.zig +++ b/src/runtime/js.zig @@ -337,6 +337,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { const env = self.env; const isolate = env.isolate; const Global = @TypeOf(global.*); + const templates = &self.env.templates; var v8_context: v8.Context = blk: { var temp_scope: v8.HandleScope = undefined; @@ -351,7 +352,6 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { // All the FunctionTemplates that we created and setup in Env.init // are now going to get associated with our global instance. - const templates = &self.env.templates; inline for (Types, 0..) |s, i| { const Struct = s.defaultValue().?; const class_name = v8.String.initUtf8(isolate, comptime classNameForStruct(Struct)); @@ -463,6 +463,38 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { } } + // Primitive attributes are set directly on the FunctionTemplate + // when we setup the environment. But we cannot set more complex + // types (v8 will crash). + // + // Plus, just to create more complex types, we always need a + // context, i.e. an Array has to have a Context to exist. + // + // As far as I can tell, getting the FunctionTemplate's object + // and setting values directly on it, for each context, is the + // way to do this. + inline for (Types, 0..) |s, i| { + const Struct = s.defaultValue().?; + inline for (@typeInfo(Struct).@"struct".decls) |declaration| { + const name = declaration.name; + if (comptime name[0] == '_') { + const value = @field(Struct, name); + + if (comptime isComplexAttributeType(@typeInfo(@TypeOf(value)))) { + const js_obj = templates[i].getFunction(v8_context).toObject(); + const js_name = v8.String.initUtf8(isolate, name[1..]).toName(); + const js_val = try js_context.zigValueToJs(value); + if (!js_obj.setValue(v8_context, js_name, js_val)) { + log.fatal(.app, "set class attribute", .{ + .@"struct" = @typeName(Struct), + .name = name, + }); + } + } + } + } + } + _ = try js_context._mapZigInstanceToJs(v8_context.getGlobal(), global); return js_context; } @@ -1809,7 +1841,9 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { if (comptime name[0] == '_') { switch (@typeInfo(@TypeOf(@field(Struct, name)))) { .@"fn" => generateMethod(Struct, name, isolate, template_proto), - else => generateAttribute(Struct, name, isolate, template, template_proto), + else => |ti| if (!comptime isComplexAttributeType(ti)) { + generateAttribute(Struct, name, isolate, template, template_proto); + }, } } else if (comptime std.mem.startsWith(u8, name, "get_")) { generateProperty(Struct, name[4..], isolate, template_proto); @@ -1930,7 +1964,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { // apply it both to the type itself template.set(js_name, js_value, v8.PropertyAttribute.ReadOnly + v8.PropertyAttribute.DontDelete); - // andto instances of the type + // and to instances of the type template_proto.set(js_name, js_value, v8.PropertyAttribute.ReadOnly + v8.PropertyAttribute.DontDelete); } @@ -2396,6 +2430,20 @@ fn isEmpty(comptime T: type) bool { return @typeInfo(T) != .@"opaque" and @sizeOf(T) == 0 and @hasDecl(T, "js_legacy_factory") == false; } +// Attributes that return a primitive type are setup directly on the +// FunctionTemplate when the Env is setup. More complex types need a v8.Context +// and cannot be set directly on the FunctionTemplate. +// We default to saying types are primitives because that's mostly what +// we have. If we add a new complex type that isn't explictly handled here, +// we'll get a compiler error in simpleZigValueToJs, and can then explicitly +// add the type here. +fn isComplexAttributeType(ti: std.builtin.Type) bool { + return switch (ti) { + .array => true, + else => false, + }; +} + // Responsible for calling Zig functions from JS invokations. This could // probably just contained in ExecutionWorld, but having this specific logic, which // is somewhat repetitive between constructors, functions, getters, etc contained