Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/browser/browser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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),
.arena = arena,
.doc = null,
.raw_data = null,
Expand Down
2 changes: 2 additions & 0 deletions src/browser/html/html.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -36,4 +37,5 @@ pub const Interfaces = .{
History,
Location,
MediaQueryList,
Performance,
};
87 changes: 87 additions & 0 deletions src/browser/html/performance.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
//
// Francis Bouvier <[email protected]>
// Pierre Tachoire <[email protected]>
//
// 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 <https://www.gnu.org/licenses/>.

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 limitedResolutionMs(nanoseconds: u64) f64 {
const elapsed_at_resolution = ((nanoseconds / std.time.ns_per_us) + 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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's fine, but just FYI, build.zig blocks any builds that aren't on linux or macos. There are a few places, like browser.zig timestamp function, which treat anything else as unreachable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, this is ugly. I wanted to just use the is_posix constant in time.zig, but that is not pub for some reason.
Then wanted to just check if @TypeOf(std.time.Instant.timestamp) == i64 but also:

        const ts_field = @typeInfo(std.time.Instant).@"struct".fields[0];
        if (!std.mem.eql(u8, ts_field.name, "timestamp")) @compileError(ts_field.name);
        const is_i64 = ts_field.type == i64;

Does not seem to work ..

.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 limitedResolutionMs(started);
}

pub fn _now(self: *Performance) f64 {
return limitedResolutionMs(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.expectEqual(now, 0);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parameters are swapped, it's expected, actual /shrug.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll take this change into the next PR

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.expectEqual(after, now);
after = perf._now();
}
// Check resolution
try testing.expectDelta(@rem(after * std.time.us_per_ms, 100.0), 0.0, 0.1);
}
10 changes: 9 additions & 1 deletion src/browser/html/window.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand All @@ -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() },
};
}

Expand All @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/cdp/testing.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion src/testing.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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),
Expand Down