Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/browser/dom/dom.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -44,4 +46,6 @@ pub const Interfaces = .{
DOMParser,
TreeWalker,
NodeFilter,
Performance,
PerformanceObserver,
};
File renamed without changes.
34 changes: 34 additions & 0 deletions src/browser/dom/performance_observer.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// 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 <https://www.gnu.org/licenses/>.

const std = @import("std");

// https://developer.mozilla.org/en-US/docs/Web/API/PerformanceObserver
pub const PerformanceObserver = struct {
pub const _supportedEntryTypes = [0][]const u8{};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would the need to register the Array as a type be resolved if we handle static properties the same as instance properties, like get_x()?
It would just return statis data instead of instance data.
I think we can do static_supportedEntryTypes() can we not?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arrays are already supported. This code goes through the existing zigValueToJs. The issue is that they're only supported when we have a context. You're effectively doing new Array(); - you need a handlescope + context to be able to do that.

We have simpleZigValueToJs (which could be renamed to "primitive" now that I know that's what V8 calls them) which specifically exists for cases where we don't have a context, but need a JS value.

static_ creates a function - it needs to be invoked with ()in JS, else you get a function reference, not the value. We could do static_get_, but I think I might still implement it the same way (directly against the JSObject) because I think it'll perform better than invoking a function callback.

};

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" },
}, .{});
}
2 changes: 0 additions & 2 deletions src/browser/html/html.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -37,6 +36,5 @@ pub const Interfaces = .{
History,
Location,
MediaQueryList,
Performance,
@import("screen.zig").Interfaces,
};
2 changes: 1 addition & 1 deletion src/browser/html/window.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
54 changes: 51 additions & 3 deletions src/runtime/js.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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));
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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
Expand Down