mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-07-31 01:36:15 -04:00
webapi: Event.timeStamp relative to the performance time origin
Fixes all 4 failing tests in WPT /dom/events/Event-timestamp-high-resolution.html: event.timeStamp returned a raw monotonic-clock value (milliseconds since boot) instead of a DOMHighResTimeStamp sharing performance.now()'s time origin. - Events now record their creation time with the same (already coarsened) clock Performance uses, and the timeStamp getter reports it in milliseconds relative to the time origin. - Per spec the origin is the *event's relevant global*'s: the JS Event constructor captures the creating realm's origin so cross-realm reads (Event-timestamp-cross-realm-getter.html) stay correct; events without a captured origin fall back to the accessing realm's, which is the creating realm in all other paths. Coverage: /dom/events/Event-timestamp-high-resolution.html 0/4 -> 4/4, Event-timestamp-cross-realm-getter.html stays 1/1. No regressions across /dom/events; Event-dispatch-single-activation-behavior.html now runs (0/0 crash -> 3/132). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
committed by
Karl Seguin
parent
ad9e1d8dc6
commit
40fa69ecf6
@@ -237,8 +237,9 @@ fn AutoPrototypeChain(comptime types: []const type) type {
|
||||
|
||||
fn eventInit(arena: Allocator, typ: String, value: anytype) !Event {
|
||||
// Round to 2ms for privacy (browsers do this)
|
||||
const raw_timestamp = @import("../datetime.zig").milliTimestamp(.monotonic);
|
||||
const time_stamp = (raw_timestamp / 2) * 2;
|
||||
// Same (already coarsened) clock as the performance time origin, so the
|
||||
// timeStamp getter can report it relative to that origin.
|
||||
const time_stamp = @import("webapi/Performance.zig").highResTimestamp();
|
||||
|
||||
return .{
|
||||
._rc = .{},
|
||||
|
||||
@@ -54,6 +54,9 @@ _listeners_did_throw: bool = false, // IndexedDB needs to abort on callback thro
|
||||
// until one of the init*Event methods runs; dispatching one throws an
|
||||
// InvalidStateError. Events created any other way start initialized.
|
||||
_initialized: bool = true,
|
||||
// Time origin of the event's relevant global, captured at creation when
|
||||
// known; 0 means "use the accessing realm's origin" (see getTimeStamp).
|
||||
_time_origin: u64 = 0,
|
||||
|
||||
// There's a period of time between creating an event and handing it off to v8
|
||||
// where things can fail. If it does fail, we need to deinit the event. The timing
|
||||
@@ -104,6 +107,15 @@ pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*Event {
|
||||
return initWithTrusted(arena, str, opts_, false);
|
||||
}
|
||||
|
||||
// The JS constructor entry point: also captures the creating realm's time
|
||||
// origin so timeStamp stays relative to the event's relevant global even
|
||||
// when read from another realm.
|
||||
fn initFromJs(typ: []const u8, opts_: ?Options, exec: *js.Execution) !*Event {
|
||||
const event = try init(typ, opts_, exec.page);
|
||||
event._time_origin = exec.performance()._time_origin;
|
||||
return event;
|
||||
}
|
||||
|
||||
pub fn initTrusted(typ: String, opts_: ?Options, page: *Page) !*Event {
|
||||
const arena = try page.getArena(.tiny, "Event.trusted");
|
||||
errdefer page.releaseArena(arena);
|
||||
@@ -113,9 +125,9 @@ pub fn initTrusted(typ: String, opts_: ?Options, page: *Page) !*Event {
|
||||
fn initWithTrusted(arena: Allocator, typ: String, opts_: ?Options, comptime trusted: bool) !*Event {
|
||||
const opts = opts_ orelse Options{};
|
||||
|
||||
// Round to 2ms for privacy (browsers do this)
|
||||
const raw_timestamp = @import("../../datetime.zig").milliTimestamp(.monotonic);
|
||||
const time_stamp = (raw_timestamp / 2) * 2;
|
||||
// Same (already coarsened) clock as the performance time origin, so the
|
||||
// timeStamp getter can report it relative to that origin.
|
||||
const time_stamp = @import("Performance.zig").highResTimestamp();
|
||||
|
||||
const event = try arena.create(Event);
|
||||
event.* = .{
|
||||
@@ -262,8 +274,16 @@ pub fn getEventPhase(self: *const Event) u8 {
|
||||
return @intFromEnum(self._event_phase);
|
||||
}
|
||||
|
||||
pub fn getTimeStamp(self: *const Event) u64 {
|
||||
return self._time_stamp;
|
||||
// A DOMHighResTimeStamp in milliseconds, relative to the relevant global's
|
||||
// time origin (the same clock as performance.now()). When the creating
|
||||
// realm's origin wasn't captured, fall back to the accessing realm's, which
|
||||
// is the same realm in all but cross-realm accesses.
|
||||
pub fn getTimeStamp(self: *const Event, exec: *js.Execution) f64 {
|
||||
const origin = if (self._time_origin != 0) self._time_origin else exec.performance()._time_origin;
|
||||
if (self._time_stamp <= origin) {
|
||||
return 0.0;
|
||||
}
|
||||
return @as(f64, @floatFromInt(self._time_stamp - origin)) / 1000.0;
|
||||
}
|
||||
|
||||
pub fn setTrusted(self: *Event) void {
|
||||
@@ -475,7 +495,7 @@ pub const JsApi = struct {
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
pub const constructor = bridge.constructor(Event.init, .{});
|
||||
pub const constructor = bridge.constructor(Event.initFromJs, .{});
|
||||
pub const @"type" = bridge.accessor(Event.getType, null, .{});
|
||||
pub const bubbles = bridge.accessor(Event.getBubbles, null, .{});
|
||||
pub const cancelable = bridge.accessor(Event.getCancelable, null, .{});
|
||||
|
||||
@@ -47,7 +47,7 @@ _delivery_scheduled: bool = false,
|
||||
|
||||
/// Get high-resolution timestamp in microseconds, rounded to 5μs increments
|
||||
/// to match browser behavior (prevents fingerprinting)
|
||||
fn highResTimestamp() u64 {
|
||||
pub fn highResTimestamp() u64 {
|
||||
const ts = datetime.timespec();
|
||||
const micros = @as(u64, @intCast(ts.sec)) * 1_000_000 + @as(u64, @intCast(@divTrunc(ts.nsec, 1_000)));
|
||||
// Round to nearest 5 microseconds (like Firefox default)
|
||||
|
||||
Reference in New Issue
Block a user