diff --git a/src/browser/browser.zig b/src/browser/browser.zig index e7e98dd9..90102c6b 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -251,7 +251,7 @@ pub const Page = struct { fn init(self: *Page, arena: Allocator, session: *Session) !void { const browser = session.browser; self.* = .{ - .window = .{}, + .window = try Window.create(null, null), // TODO why do we not call Window.create()? .arena = arena, .doc = null, .raw_data = null, diff --git a/src/browser/html/html.zig b/src/browser/html/html.zig index 85eff4ec..ef75dde0 100644 --- a/src/browser/html/html.zig +++ b/src/browser/html/html.zig @@ -24,6 +24,7 @@ const Navigator = @import("navigator.zig").Navigator; const History = @import("history.zig").History; const Location = @import("location.zig").Location; const MediaQueryList = @import("media_query_list.zig").MediaQueryList; +const Performance = @import("performance.zig").Performance; pub const Interfaces = .{ HTMLDocument, @@ -36,4 +37,5 @@ pub const Interfaces = .{ History, Location, MediaQueryList, + Performance, }; diff --git a/src/browser/html/performance.zig b/src/browser/html/performance.zig new file mode 100644 index 00000000..b36a9d78 --- /dev/null +++ b/src/browser/html/performance.zig @@ -0,0 +1,87 @@ +// Copyright (C) 2023-2025 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +const std = @import("std"); + +const parser = @import("../netsurf.zig"); +const EventTarget = @import("../dom/event_target.zig").EventTarget; + +// https://developer.mozilla.org/en-US/docs/Web/API/Performance +pub const Performance = struct { + pub const prototype = *EventTarget; + + // Extend libdom event target for pure zig struct. + base: parser.EventTargetTBase = parser.EventTargetTBase{}, + + time_origin: std.time.Timer, + // if (Window.crossOriginIsolated) -> Resolution in isolated contexts: 5 microseconds + // else -> Resolution in non-isolated contexts: 100 microseconds + const ms_resolution = 100; + + fn limited_resolution_ms(nanoseconds: u64) f64 { + const elapsed_at_resolution = ((nanoseconds / std.time.ns_per_ms) + ms_resolution / 2) / ms_resolution * ms_resolution; + const elapsed = @as(f64, @floatFromInt(elapsed_at_resolution)); + return elapsed / @as(f64, std.time.us_per_ms); + } + + pub fn get_timeOrigin(self: *const Performance) f64 { + const is_posix = switch (@import("builtin").os.tag) { // From std.time.zig L125 + .windows, .uefi, .wasi => false, + else => true, + }; + const zero = std.time.Instant{ .timestamp = if (!is_posix) 0 else .{ .sec = 0, .nsec = 0 } }; + const started = self.time_origin.started.since(zero); + return limited_resolution_ms(started); + } + + pub fn _now(self: *Performance) f64 { + return limited_resolution_ms(self.time_origin.read()); + } +}; + +const testing = @import("./../../testing.zig"); + +test "Performance: get_timeOrigin" { + var perf = Performance{ .time_origin = try std.time.Timer.start() }; + const time_origin = perf.get_timeOrigin(); + try testing.expect(time_origin >= 0); + + // Check resolution + try testing.expectDelta(@rem(time_origin * std.time.us_per_ms, 100.0), 0.0, 0.1); +} + +test "Performance: now" { + var perf = Performance{ .time_origin = try std.time.Timer.start() }; + + // Monotonically increasing + var now = perf._now(); + while (now <= 0) { // Loop for now to not be 0 + try testing.expect(now == 0); + now = perf._now(); + } + // Check resolution + try testing.expectDelta(@rem(now * std.time.us_per_ms, 100.0), 0.0, 0.1); + + var after = perf._now(); + while (after <= now) { // Loop untill after > now + try testing.expect(after == now); + after = perf._now(); + } + // Check resolution + try testing.expectDelta(@rem(after * std.time.us_per_ms, 100.0), 0.0, 0.1); +} diff --git a/src/browser/html/window.zig b/src/browser/html/window.zig index 7064be29..0b714093 100644 --- a/src/browser/html/window.zig +++ b/src/browser/html/window.zig @@ -30,6 +30,7 @@ const Crypto = @import("../crypto/crypto.zig").Crypto; const Console = @import("../console/console.zig").Console; const EventTarget = @import("../dom/event_target.zig").EventTarget; const MediaQueryList = @import("media_query_list.zig").MediaQueryList; +const Performance = @import("performance.zig").Performance; const storage = @import("../storage/storage.zig"); @@ -56,11 +57,13 @@ pub const Window = struct { crypto: Crypto = .{}, console: Console = .{}, navigator: Navigator = .{}, + performance: Performance, - pub fn create(target: ?[]const u8, navigator: ?Navigator) Window { + pub fn create(target: ?[]const u8, navigator: ?Navigator) !Window { return .{ .target = target orelse "", .navigator = navigator orelse .{}, + .performance = .{ .time_origin = try std.time.Timer.start() }, }; } @@ -72,6 +75,7 @@ pub const Window = struct { } pub fn replaceDocument(self: *Window, doc: *parser.DocumentHTML) !void { + self.performance.time_origin.reset(); // When to reset see: https://developer.mozilla.org/en-US/docs/Web/API/Performance/timeOrigin self.document = doc; try parser.documentHTMLSetLocation(Location, doc, &self.location); } @@ -130,6 +134,10 @@ pub const Window = struct { return &self.storage_shelf.?.bucket.session; } + pub fn get_performance(self: *Window) *Performance { + return &self.performance; + } + // TODO handle callback arguments. pub fn _setTimeout(self: *Window, cbk: Callback, delay: ?u32, state: *SessionState) !u32 { return self.createTimeout(cbk, delay, state, false); diff --git a/src/cdp/testing.zig b/src/cdp/testing.zig index 7ae4212f..7a6a936c 100644 --- a/src/cdp/testing.zig +++ b/src/cdp/testing.zig @@ -30,6 +30,7 @@ const parser = @import("../browser/netsurf.zig"); const base = @import("../testing.zig"); pub const allocator = base.allocator; pub const expectJson = base.expectJson; +pub const expect = std.testing.expect; pub const expectEqual = base.expectEqual; pub const expectError = base.expectError; pub const expectEqualSlices = base.expectEqualSlices; diff --git a/src/testing.zig b/src/testing.zig index dfb9818b..ea491847 100644 --- a/src/testing.zig +++ b/src/testing.zig @@ -21,6 +21,7 @@ const Allocator = std.mem.Allocator; pub const allocator = std.testing.allocator; pub const expectError = std.testing.expectError; +pub const expect = std.testing.expect; pub const expectString = std.testing.expectEqualStrings; pub const expectEqualSlices = std.testing.expectEqualSlices; @@ -421,7 +422,7 @@ pub const JsRunner = struct { .http_client = &self.http_client, }; - self.window = .{}; + self.window = try Window.create(null, null); try self.window.replaceDocument(document); try self.window.replaceLocation(.{ .url = try self.url.toWebApi(arena),