diff --git a/src/browser/tests/performance.html b/src/browser/tests/performance.html
index 5aed2cc1..a2647792 100644
--- a/src/browser/tests/performance.html
+++ b/src/browser/tests/performance.html
@@ -43,3 +43,91 @@
}
}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/browser/webapi/Performance.zig b/src/browser/webapi/Performance.zig
index c659a7f8..e272f71a 100644
--- a/src/browser/webapi/Performance.zig
+++ b/src/browser/webapi/Performance.zig
@@ -1,20 +1,22 @@
const js = @import("../js/js.zig");
+const Page = @import("../Page.zig");
const datetime = @import("../../datetime.zig");
pub fn registerTypes() []const type {
- return &.{
- Performance,
- Entry,
- };
+ return &.{ Performance, Entry, Mark };
}
+const std = @import("std");
+
const Performance = @This();
_time_origin: u64,
+_entries: std.ArrayListUnmanaged(*Entry) = .{},
pub fn init() Performance {
return .{
._time_origin = datetime.milliTimestamp(.monotonic),
+ ._entries = .{},
};
}
@@ -28,6 +30,75 @@ pub fn getTimeOrigin(self: *const Performance) f64 {
return @floatFromInt(self._time_origin);
}
+pub fn mark(self: *Performance, name: []const u8, _options: ?Mark.Options, page: *Page) !*Mark {
+ const m = try Mark.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;
+ }
+ }
+ } 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 getEntries(self: *const Performance) []*Entry {
+ return self._entries.items;
+}
+
+pub fn getEntriesByType(self: *const Performance, entry_type: []const u8, page: *Page) ![]const *Entry {
+ var result: std.ArrayList(*Entry) = .empty;
+
+ for (self._entries.items) |entry| {
+ if (std.mem.eql(u8, entry.getEntryType(), entry_type)) {
+ try result.append(page.call_arena, entry);
+ }
+ }
+
+ return result.items;
+}
+
+pub fn getEntriesByName(self: *const Performance, name: []const u8, entry_type: ?[]const u8, page: *Page) ![]const *Entry {
+ var result: std.ArrayList(*Entry) = .empty;
+
+ for (self._entries.items) |entry| {
+ if (!std.mem.eql(u8, entry._name, name)) {
+ continue;
+ }
+
+ const et = entry_type orelse {
+ try result.append(page.call_arena, entry);
+ continue;
+ };
+
+ if (std.mem.eql(u8, entry.getEntryType(), et)) {
+ try result.append(page.call_arena, entry);
+ }
+ }
+
+ return result.items;
+}
+
pub const JsApi = struct {
pub const bridge = js.Bridge(Performance);
@@ -38,16 +109,21 @@ pub const JsApi = struct {
};
pub const now = bridge.function(Performance.now, .{});
+ pub const mark = bridge.function(Performance.mark, .{});
+ pub const clearMarks = bridge.function(Performance.clearMarks, .{});
+ pub const getEntries = bridge.function(Performance.getEntries, .{});
+ pub const getEntriesByType = bridge.function(Performance.getEntriesByType, .{});
+ pub const getEntriesByName = bridge.function(Performance.getEntriesByName, .{});
pub const timeOrigin = bridge.accessor(Performance.getTimeOrigin, null, .{});
};
pub const Entry = struct {
_duration: f64 = 0.0,
- _entry_type: Type,
+ _type: Type,
_name: []const u8,
_start_time: f64 = 0.0,
- const Type = enum {
+ const Type = union(enum) {
element,
event,
first_input,
@@ -55,13 +131,13 @@ pub const Entry = struct {
layout_shift,
long_animation_frame,
longtask,
- mark,
measure,
navigation,
paint,
resource,
taskattribution,
visibility_state,
+ mark: *Mark,
};
pub fn getDuration(self: *const Entry) f64 {
@@ -69,7 +145,7 @@ pub const Entry = struct {
}
pub fn getEntryType(self: *const Entry) []const u8 {
- return switch (self._entry_type) {
+ return switch (self._type) {
.first_input => "first-input",
.largest_contentful_paint => "largest-contentful-paint",
.layout_shift => "layout-shift",
@@ -95,8 +171,58 @@ pub const Entry = struct {
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
+ pub const name = bridge.accessor(Entry.getName, null, .{});
pub const duration = bridge.accessor(Entry.getDuration, null, .{});
pub const entryType = bridge.accessor(Entry.getEntryType, null, .{});
+ pub const startTime = bridge.accessor(Entry.getStartTime, null, .{});
+ };
+};
+
+pub const Mark = struct {
+ _proto: *Entry,
+ _detail: ?js.Object,
+
+ const Options = struct {
+ detail: ?js.Object = null,
+ startTime: ?f64 = null,
+ };
+
+ pub fn init(name: []const u8, _opts: ?Options, page: *Page) !*Mark {
+ const opts = _opts orelse Options{};
+ const start_time = opts.startTime orelse page.window._performance.now();
+
+ if (start_time < 0.0) {
+ return error.TypeError;
+ }
+
+ const detail = if (opts.detail) |d| try d.persist() else null;
+ const m = try page._factory.create(Mark{
+ ._proto = undefined,
+ ._detail = detail,
+ });
+
+ const entry = try page._factory.create(Entry{
+ ._start_time = start_time,
+ ._name = try page.dupeString(name),
+ ._type = .{ .mark = m },
+ });
+ m._proto = entry;
+ return m;
+ }
+
+ pub fn getDetail(self: *const Mark) ?js.Object {
+ return self._detail;
+ }
+
+ pub const JsApi = struct {
+ pub const bridge = js.Bridge(Mark);
+
+ pub const Meta = struct {
+ pub const name = "PerformanceMark";
+ pub const prototype_chain = bridge.prototypeChain();
+ pub var class_id: bridge.ClassId = undefined;
+ };
+ pub const detail = bridge.accessor(Mark.getDetail, null, .{});
};
};