diff --git a/src/browser/tests/performance.html b/src/browser/tests/performance.html
index 01d23ab4..d0383d90 100644
--- a/src/browser/tests/performance.html
+++ b/src/browser/tests/performance.html
@@ -274,6 +274,50 @@
}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/browser/webapi/Performance.zig b/src/browser/webapi/Performance.zig
index 78070893..e9ca0ebf 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, Measure };
+ return &.{ Performance, Entry, Mark, Measure, PerformanceTiming, PerformanceNavigation };
}
const std = @import("std");
@@ -12,6 +12,8 @@ const Performance = @This();
_time_origin: u64,
_entries: std.ArrayList(*Entry) = .{},
+_timing: PerformanceTiming = .{},
+_navigation: PerformanceNavigation = .{},
/// Get high-resolution timestamp in microseconds, rounded to 5μs increments
/// to match browser behavior (prevents fingerprinting)
@@ -27,9 +29,15 @@ pub fn init() Performance {
return .{
._time_origin = highResTimestamp(),
._entries = .{},
+ ._timing = .{},
+ ._navigation = .{},
};
}
+pub fn getTiming(self: *Performance) *PerformanceTiming {
+ return &self._timing;
+}
+
pub fn now(self: *const Performance) f64 {
const current = highResTimestamp();
const elapsed = current - self._time_origin;
@@ -42,6 +50,10 @@ pub fn getTimeOrigin(self: *const Performance) f64 {
return @as(f64, @floatFromInt(self._time_origin)) / 1000.0;
}
+pub fn getNavigation(self: *Performance) *PerformanceNavigation {
+ return &self._navigation;
+}
+
pub fn mark(
self: *Performance,
name: []const u8,
@@ -263,6 +275,8 @@ pub const JsApi = struct {
pub const getEntriesByType = bridge.function(Performance.getEntriesByType, .{});
pub const getEntriesByName = bridge.function(Performance.getEntriesByName, .{});
pub const timeOrigin = bridge.accessor(Performance.getTimeOrigin, null, .{});
+ pub const timing = bridge.accessor(Performance.getTiming, null, .{});
+ pub const navigation = bridge.accessor(Performance.getNavigation, null, .{});
};
pub const Entry = struct {
@@ -449,6 +463,70 @@ pub const Measure = struct {
};
};
+/// PerformanceTiming — Navigation Timing Level 1 (legacy, but widely used).
+/// https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming
+/// All properties return 0 as stub values; the object must not be undefined
+/// so that scripts accessing performance.timing.navigationStart don't crash.
+pub const PerformanceTiming = struct {
+ // Padding to avoid zero-size struct, which causes identity_map pointer collisions.
+ _pad: bool = false,
+
+ pub const JsApi = struct {
+ pub const bridge = js.Bridge(PerformanceTiming);
+
+ pub const Meta = struct {
+ pub const name = "PerformanceTiming";
+ pub const prototype_chain = bridge.prototypeChain();
+ pub var class_id: bridge.ClassId = undefined;
+ pub const empty_with_no_proto = true;
+ };
+
+ pub const navigationStart = bridge.property(0.0, .{ .template = false, .readonly = true });
+ pub const unloadEventStart = bridge.property(0.0, .{ .template = false, .readonly = true });
+ pub const unloadEventEnd = bridge.property(0.0, .{ .template = false, .readonly = true });
+ pub const redirectStart = bridge.property(0.0, .{ .template = false, .readonly = true });
+ pub const redirectEnd = bridge.property(0.0, .{ .template = false, .readonly = true });
+ pub const fetchStart = bridge.property(0.0, .{ .template = false, .readonly = true });
+ pub const domainLookupStart = bridge.property(0.0, .{ .template = false, .readonly = true });
+ pub const domainLookupEnd = bridge.property(0.0, .{ .template = false, .readonly = true });
+ pub const connectStart = bridge.property(0.0, .{ .template = false, .readonly = true });
+ pub const connectEnd = bridge.property(0.0, .{ .template = false, .readonly = true });
+ pub const secureConnectionStart = bridge.property(0.0, .{ .template = false, .readonly = true });
+ pub const requestStart = bridge.property(0.0, .{ .template = false, .readonly = true });
+ pub const responseStart = bridge.property(0.0, .{ .template = false, .readonly = true });
+ pub const responseEnd = bridge.property(0.0, .{ .template = false, .readonly = true });
+ pub const domLoading = bridge.property(0.0, .{ .template = false, .readonly = true });
+ pub const domInteractive = bridge.property(0.0, .{ .template = false, .readonly = true });
+ pub const domContentLoadedEventStart = bridge.property(0.0, .{ .template = false, .readonly = true });
+ pub const domContentLoadedEventEnd = bridge.property(0.0, .{ .template = false, .readonly = true });
+ pub const domComplete = bridge.property(0.0, .{ .template = false, .readonly = true });
+ pub const loadEventStart = bridge.property(0.0, .{ .template = false, .readonly = true });
+ pub const loadEventEnd = bridge.property(0.0, .{ .template = false, .readonly = true });
+ };
+};
+
+// PerformanceNavigation implements the Navigation Timing Level 1 API.
+// https://www.w3.org/TR/navigation-timing/#sec-navigation-navigation-timing-interface
+// Stub implementation — returns 0 for type (TYPE_NAVIGATE) and 0 for redirectCount.
+pub const PerformanceNavigation = struct {
+ // Padding to avoid zero-size struct, which causes identity_map pointer collisions.
+ _pad: bool = false,
+
+ pub const JsApi = struct {
+ pub const bridge = js.Bridge(PerformanceNavigation);
+
+ pub const Meta = struct {
+ pub const name = "PerformanceNavigation";
+ pub const prototype_chain = bridge.prototypeChain();
+ pub var class_id: bridge.ClassId = undefined;
+ pub const empty_with_no_proto = true;
+ };
+
+ pub const @"type" = bridge.property(0.0, .{ .template = false, .readonly = true });
+ pub const redirectCount = bridge.property(0.0, .{ .template = false, .readonly = true });
+ };
+};
+
const testing = @import("../../testing.zig");
test "WebApi: Performance" {
try testing.htmlRunner("performance.html", .{});