From 6b651cd5e4423a0e1111de46f97ff64e601f676a Mon Sep 17 00:00:00 2001 From: Muki Kiboigo Date: Mon, 23 Jun 2025 09:25:40 -0700 Subject: [PATCH] add PerformanceEntry and PerformanceMark --- src/browser/dom/dom.zig | 3 +- src/browser/dom/performance.zig | 116 ++++++++++++++++++++++++++++++++ src/runtime/js.zig | 8 ++- 3 files changed, 124 insertions(+), 3 deletions(-) diff --git a/src/browser/dom/dom.zig b/src/browser/dom/dom.zig index 4359b4bb..ecc8ede2 100644 --- a/src/browser/dom/dom.zig +++ b/src/browser/dom/dom.zig @@ -28,7 +28,6 @@ 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 = .{ @@ -46,6 +45,6 @@ pub const Interfaces = .{ DOMParser, TreeWalker, NodeFilter, - Performance, + @import("performance.zig").Interfaces, PerformanceObserver, }; diff --git a/src/browser/dom/performance.zig b/src/browser/dom/performance.zig index 042f881b..7346ddfc 100644 --- a/src/browser/dom/performance.zig +++ b/src/browser/dom/performance.zig @@ -20,6 +20,19 @@ const std = @import("std"); const parser = @import("../netsurf.zig"); const EventTarget = @import("../dom/event_target.zig").EventTarget; +const Env = @import("../env.zig").Env; +const Page = @import("../page.zig").Page; + +pub const Interfaces = .{ + Performance, + PerformanceEntry, + PerformanceMark, +}; + +const MarkOptions = struct { + detail: ?Env.JsObject = null, + start_time: ?f64 = null, +}; // https://developer.mozilla.org/en-US/docs/Web/API/Performance pub const Performance = struct { @@ -52,6 +65,93 @@ pub const Performance = struct { pub fn _now(self: *Performance) f64 { return limitedResolutionMs(self.time_origin.read()); } + + pub fn _mark(_: *Performance, name: []const u8, _options: ?MarkOptions, page: *Page) !PerformanceMark { + const mark: PerformanceMark = try .constructor(name, _options, page); + // TODO: Should store this in an entries list + return mark; + } +}; + +// https://developer.mozilla.org/en-US/docs/Web/API/PerformanceEntry +pub const PerformanceEntry = struct { + const PerformanceEntryType = enum { + element, + event, + first_input, + largest_contentful_paint, + layout_shift, + long_animation_frame, + longtask, + mark, + measure, + navigation, + paint, + resource, + taskattribution, + visibility_state, + + pub fn toString(self: PerformanceEntryType) []const u8 { + return switch (self) { + .first_input => "first-input", + .largest_contentful_paint => "largest-contentful-paint", + .layout_shift => "layout-shift", + .long_animation_frame => "long-animation-frame", + .visibility_state => "visibility-state", + else => @tagName(self), + }; + } + }; + + duration: f64 = 0.0, + entry_type: PerformanceEntryType, + name: []const u8, + start_time: f64 = 0.0, + + pub fn get_duration(self: *const PerformanceEntry) f64 { + return self.duration; + } + + pub fn get_entryType(self: *const PerformanceEntry) PerformanceEntryType { + return self.entry_type; + } + + pub fn get_name(self: *const PerformanceEntry) []const u8 { + return self.name; + } + + pub fn get_startTime(self: *const PerformanceEntry) f64 { + return self.start_time; + } +}; + +// https://developer.mozilla.org/en-US/docs/Web/API/PerformanceMark +pub const PerformanceMark = struct { + pub const prototype = *PerformanceEntry; + + proto: PerformanceEntry, + detail: ?Env.JsObject, + + pub fn constructor(name: []const u8, _options: ?MarkOptions, page: *Page) !PerformanceMark { + const perf = &page.window.performance; + + const options = _options orelse MarkOptions{}; + const start_time = options.start_time orelse perf._now(); + const detail = if (options.detail) |d| try d.persist() else null; + + if (start_time < 0.0) { + return error.TypeError; + } + + const duped_name = try page.arena.dupe(u8, name); + const proto = PerformanceEntry{ .name = duped_name, .entry_type = .mark, .start_time = start_time }; + + return .{ .proto = proto, .detail = detail }; + } + + pub fn get_detail(self: *const PerformanceMark) ?Env.JsObject { + return self.detail; + } }; const testing = @import("./../../testing.zig"); @@ -85,3 +185,19 @@ test "Performance: now" { // Check resolution try testing.expectDelta(@rem(after * std.time.us_per_ms, 100.0), 0.0, 0.1); } + +test "Browser.Performance.Mark" { + var runner = try testing.jsRunner(testing.tracking_allocator, .{}); + defer runner.deinit(); + + try runner.testCases(&.{ + .{ "let performance = window.performance", "undefined" }, + .{ "performance instanceof Performance", "true" }, + .{ "let mark = performance.mark(\"start\")", "undefined" }, + .{ "mark instanceof PerformanceMark", "true" }, + .{ "mark.name", "start" }, + .{ "mark.entryType", "mark" }, + .{ "mark.duration", "0" }, + .{ "mark.detail", "null" }, + }, .{}); +} diff --git a/src/runtime/js.zig b/src/runtime/js.zig index 6b6bef2c..e7535662 100644 --- a/src/runtime/js.zig +++ b/src/runtime/js.zig @@ -2201,7 +2201,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { const T = @TypeOf(value); switch (@typeInfo(T)) { - .void, .bool, .int, .comptime_int, .float, .comptime_float => { + .void, .bool, .int, .comptime_int, .float, .comptime_float, .@"enum" => { // Need to do this to keep the compiler happy // simpleZigValueToJs handles all of these cases. unreachable; @@ -3084,6 +3084,12 @@ fn simpleZigValueToJs(isolate: v8.Isolate, value: anytype, comptime fail: bool) } }, .@"union" => return simpleZigValueToJs(isolate, std.meta.activeTag(value), fail), + .@"enum" => { + const T = @TypeOf(value); + if (@hasDecl(T, "toString")) { + return simpleZigValueToJs(isolate, value.toString(), fail); + } + }, else => {}, } if (fail) {