Skip to content

Commit 365e6c1

Browse files
committed
performance now
1 parent 35bff8c commit 365e6c1

File tree

6 files changed

+102
-3
lines changed

6 files changed

+102
-3
lines changed

src/browser/browser.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ pub const Page = struct {
248248
fn init(self: *Page, arena: Allocator, session: *Session) !void {
249249
const browser = session.browser;
250250
self.* = .{
251-
.window = .{},
251+
.window = try Window.create(null, null), // TODO why do we not call Window.create()?
252252
.arena = arena,
253253
.doc = null,
254254
.raw_data = null,

src/browser/html/html.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const Navigator = @import("navigator.zig").Navigator;
2424
const History = @import("history.zig").History;
2525
const Location = @import("location.zig").Location;
2626
const MediaQueryList = @import("media_query_list.zig").MediaQueryList;
27+
const Performance = @import("performance.zig").Performance;
2728

2829
pub const Interfaces = .{
2930
HTMLDocument,
@@ -36,4 +37,5 @@ pub const Interfaces = .{
3637
History,
3738
Location,
3839
MediaQueryList,
40+
Performance,
3941
};

src/browser/html/performance.zig

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
2+
//
3+
// Francis Bouvier <[email protected]>
4+
// Pierre Tachoire <[email protected]>
5+
//
6+
// This program is free software: you can redistribute it and/or modify
7+
// it under the terms of the GNU Affero General Public License as
8+
// published by the Free Software Foundation, either version 3 of the
9+
// License, or (at your option) any later version.
10+
//
11+
// This program is distributed in the hope that it will be useful,
12+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
// GNU Affero General Public License for more details.
15+
//
16+
// You should have received a copy of the GNU Affero General Public License
17+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
19+
const std = @import("std");
20+
21+
const parser = @import("../netsurf.zig");
22+
const EventTarget = @import("../dom/event_target.zig").EventTarget;
23+
24+
// https://developer.mozilla.org/en-US/docs/Web/API/Performance
25+
pub const Performance = struct {
26+
pub const prototype = *EventTarget;
27+
28+
// Extend libdom event target for pure zig struct.
29+
base: parser.EventTargetTBase = parser.EventTargetTBase{},
30+
31+
time_origin: std.time.Timer,
32+
// if (Window.crossOriginIsolated) -> Resolution in isolated contexts: 5 microseconds
33+
// else -> Resolution in non-isolated contexts: 100 microseconds
34+
const ms_resolution = 100;
35+
36+
fn limited_resolution_ms(nanoseconds: u64) f64 {
37+
const elapsed_at_resolution = ((nanoseconds / std.time.ns_per_ms) + ms_resolution / 2) / ms_resolution * ms_resolution;
38+
const elapsed = @as(f64, @floatFromInt(elapsed_at_resolution));
39+
return elapsed / @as(f64, std.time.us_per_ms);
40+
}
41+
42+
pub fn get_timeOrigin(self: *const Performance) f64 {
43+
const is_posix = switch (@import("builtin").os.tag) { // From std.time.zig L125
44+
.windows, .uefi, .wasi => false,
45+
else => true,
46+
};
47+
const zero = std.time.Instant{ .timestamp = if (!is_posix) 0 else .{ .sec = 0, .nsec = 0 } };
48+
const started = self.time_origin.started.since(zero);
49+
return limited_resolution_ms(started);
50+
}
51+
52+
pub fn _now(self: *Performance) f64 {
53+
return limited_resolution_ms(self.time_origin.read());
54+
}
55+
};
56+
57+
const testing = @import("./../../testing.zig");
58+
59+
test "Performance: get_timeOrigin" {
60+
var perf = Performance{ .time_origin = try std.time.Timer.start() };
61+
const time_origin = perf.get_timeOrigin();
62+
try testing.expect(time_origin >= 0);
63+
64+
// Check resolution
65+
try testing.expectDelta(@rem(time_origin * std.time.us_per_ms, 100.0), 0.0, 0.1);
66+
}
67+
68+
test "Performance: now" {
69+
var perf = Performance{ .time_origin = try std.time.Timer.start() };
70+
71+
// Monotonically increasing
72+
var now = perf._now();
73+
while (now <= 0) { // Loop for now to not be 0
74+
try testing.expect(now == 0);
75+
now = perf._now();
76+
}
77+
// Check resolution
78+
try testing.expectDelta(@rem(now * std.time.us_per_ms, 100.0), 0.0, 0.1);
79+
80+
var after = perf._now();
81+
while (after <= now) { // Loop untill after > now
82+
try testing.expect(after == now);
83+
after = perf._now();
84+
}
85+
// Check resolution
86+
try testing.expectDelta(@rem(after * std.time.us_per_ms, 100.0), 0.0, 0.1);
87+
}

src/browser/html/window.zig

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const Crypto = @import("../crypto/crypto.zig").Crypto;
3030
const Console = @import("../console/console.zig").Console;
3131
const EventTarget = @import("../dom/event_target.zig").EventTarget;
3232
const MediaQueryList = @import("media_query_list.zig").MediaQueryList;
33+
const Performance = @import("performance.zig").Performance;
3334

3435
const storage = @import("../storage/storage.zig");
3536

@@ -56,11 +57,13 @@ pub const Window = struct {
5657
crypto: Crypto = .{},
5758
console: Console = .{},
5859
navigator: Navigator = .{},
60+
performance: Performance,
5961

60-
pub fn create(target: ?[]const u8, navigator: ?Navigator) Window {
62+
pub fn create(target: ?[]const u8, navigator: ?Navigator) !Window {
6163
return .{
6264
.target = target orelse "",
6365
.navigator = navigator orelse .{},
66+
.performance = .{ .time_origin = try std.time.Timer.start() },
6467
};
6568
}
6669

@@ -72,6 +75,7 @@ pub const Window = struct {
7275
}
7376

7477
pub fn replaceDocument(self: *Window, doc: *parser.DocumentHTML) !void {
78+
self.performance.time_origin.reset(); // When to reset see: https://developer.mozilla.org/en-US/docs/Web/API/Performance/timeOrigin
7579
self.document = doc;
7680
try parser.documentHTMLSetLocation(Location, doc, &self.location);
7781
}
@@ -130,6 +134,10 @@ pub const Window = struct {
130134
return &self.storage_shelf.?.bucket.session;
131135
}
132136

137+
pub fn get_performance(self: *Window) *Performance {
138+
return &self.performance;
139+
}
140+
133141
// TODO handle callback arguments.
134142
pub fn _setTimeout(self: *Window, cbk: Callback, delay: ?u32, state: *SessionState) !u32 {
135143
return self.createTimeout(cbk, delay, state, false);

src/cdp/testing.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const parser = @import("../browser/netsurf.zig");
3030
const base = @import("../testing.zig");
3131
pub const allocator = base.allocator;
3232
pub const expectJson = base.expectJson;
33+
pub const expect = std.testing.expect;
3334
pub const expectEqual = base.expectEqual;
3435
pub const expectError = base.expectError;
3536
pub const expectEqualSlices = base.expectEqualSlices;

src/testing.zig

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const Allocator = std.mem.Allocator;
2121

2222
pub const allocator = std.testing.allocator;
2323
pub const expectError = std.testing.expectError;
24+
pub const expect = std.testing.expect;
2425
pub const expectString = std.testing.expectEqualStrings;
2526
pub const expectEqualSlices = std.testing.expectEqualSlices;
2627

@@ -421,7 +422,7 @@ pub const JsRunner = struct {
421422
.http_client = &self.http_client,
422423
};
423424

424-
self.window = .{};
425+
self.window = try Window.create(null, null);
425426
try self.window.replaceDocument(document);
426427
try self.window.replaceLocation(.{
427428
.url = try self.url.toWebApi(arena),

0 commit comments

Comments
 (0)