diff --git a/src/browser/tests/performance.html b/src/browser/tests/performance.html index a2647792..5928bba9 100644 --- a/src/browser/tests/performance.html +++ b/src/browser/tests/performance.html @@ -131,3 +131,152 @@ testing.expectEqual(0, marks.length); } + + + + + + + + + + + + + + + + diff --git a/src/browser/webapi/Performance.zig b/src/browser/webapi/Performance.zig index e272f71a..ce070741 100644 --- a/src/browser/webapi/Performance.zig +++ b/src/browser/webapi/Performance.zig @@ -3,7 +3,7 @@ const Page = @import("../Page.zig"); const datetime = @import("../../datetime.zig"); pub fn registerTypes() []const type { - return &.{ Performance, Entry, Mark }; + return &.{ Performance, Entry, Mark, Measure }; } const std = @import("std"); @@ -36,28 +36,32 @@ pub fn mark(self: *Performance, name: []const u8, _options: ?Mark.Options, page: return m; } +pub fn measure(self: *Performance, name: []const u8, _options: ?Measure.Options, page: *Page) !*Measure { + const m = try Measure.init(name, _options, page); + try self._entries.append(page.arena, m._proto); + return m; +} + pub fn clearMarks(self: *Performance, mark_name: ?[]const u8) void { - if (mark_name) |name| { - // Remove specific mark by name - var i: usize = 0; - while (i < self._entries.items.len) { - const entry = self._entries.items[i]; - if (entry._type == .mark and std.mem.eql(u8, entry._name, name)) { - _ = self._entries.orderedRemove(i); - } else { - i += 1; - } + var i: usize = 0; + while (i < self._entries.items.len) { + const entry = self._entries.items[i]; + if (entry._type == .mark and (mark_name == null or std.mem.eql(u8, entry._name, mark_name.?))) { + _ = self._entries.orderedRemove(i); + } else { + i += 1; } - } else { - // Remove all marks - var i: usize = 0; - while (i < self._entries.items.len) { - const entry = self._entries.items[i]; - if (entry._type == .mark) { - _ = self._entries.orderedRemove(i); - } else { - i += 1; - } + } +} + +pub fn clearMeasures(self: *Performance, measure_name: ?[]const u8) void { + var i: usize = 0; + while (i < self._entries.items.len) { + const entry = self._entries.items[i]; + if (entry._type == .measure and (measure_name == null or std.mem.eql(u8, entry._name, measure_name.?))) { + _ = self._entries.orderedRemove(i); + } else { + i += 1; } } } @@ -99,6 +103,15 @@ pub fn getEntriesByName(self: *const Performance, name: []const u8, entry_type: return result.items; } +fn getMarkTime(self: *const Performance, mark_name: []const u8) !f64 { + for (self._entries.items) |entry| { + if (entry._type == .mark and std.mem.eql(u8, entry._name, mark_name)) { + return entry._start_time; + } + } + return error.SyntaxError; // Mark not found +} + pub const JsApi = struct { pub const bridge = js.Bridge(Performance); @@ -110,7 +123,9 @@ pub const JsApi = struct { pub const now = bridge.function(Performance.now, .{}); pub const mark = bridge.function(Performance.mark, .{}); + pub const measure = bridge.function(Performance.measure, .{}); pub const clearMarks = bridge.function(Performance.clearMarks, .{}); + pub const clearMeasures = bridge.function(Performance.clearMeasures, .{}); pub const getEntries = bridge.function(Performance.getEntries, .{}); pub const getEntriesByType = bridge.function(Performance.getEntriesByType, .{}); pub const getEntriesByName = bridge.function(Performance.getEntriesByName, .{}); @@ -131,7 +146,7 @@ pub const Entry = struct { layout_shift, long_animation_frame, longtask, - measure, + measure: *Measure, navigation, paint, resource, @@ -226,6 +241,69 @@ pub const Mark = struct { }; }; +pub const Measure = struct { + _proto: *Entry, + _detail: ?js.Object, + + const Options = struct { + detail: ?js.Object = null, + start: ?[]const u8 = null, + end: ?[]const u8 = null, + duration: ?f64 = null, + }; + + pub fn init(name: []const u8, _opts: ?Options, page: *Page) !*Measure { + const opts = _opts orelse Options{}; + const perf = &page.window._performance; + + const start_time = if (opts.start) |start_mark| + try perf.getMarkTime(start_mark) + else + 0.0; + + const end_time = if (opts.end) |end_mark| + try perf.getMarkTime(end_mark) + else + perf.now(); + + const duration = opts.duration orelse (end_time - start_time); + + if (duration < 0.0) { + return error.TypeError; + } + + const detail = if (opts.detail) |d| try d.persist() else null; + const m = try page._factory.create(Measure{ + ._proto = undefined, + ._detail = detail, + }); + + const entry = try page._factory.create(Entry{ + ._start_time = start_time, + ._duration = duration, + ._name = try page.dupeString(name), + ._type = .{ .measure = m }, + }); + m._proto = entry; + return m; + } + + pub fn getDetail(self: *const Measure) ?js.Object { + return self._detail; + } + + pub const JsApi = struct { + pub const bridge = js.Bridge(Measure); + + pub const Meta = struct { + pub const name = "PerformanceMeasure"; + pub const prototype_chain = bridge.prototypeChain(); + pub var class_id: bridge.ClassId = undefined; + }; + pub const detail = bridge.accessor(Measure.getDetail, null, .{}); + }; +}; + const testing = @import("../../testing.zig"); test "WebApi: Performance" { try testing.htmlRunner("performance.html", .{});