From 99f626a0259f1c09606e22dcecea994cffc9bd18 Mon Sep 17 00:00:00 2001 From: sjorsdonkers <72333389+sjorsdonkers@users.noreply.github.com> Date: Tue, 13 May 2025 16:07:14 +0200 Subject: [PATCH 1/2] requestAnimationFrame --- src/browser/html/window.zig | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/browser/html/window.zig b/src/browser/html/window.zig index 0b714093d..dac4f7a0f 100644 --- a/src/browser/html/window.zig +++ b/src/browser/html/window.zig @@ -138,6 +138,23 @@ pub const Window = struct { return &self.performance; } + // Tells the browser you wish to perform an animation. It requests the browser to call a user-supplied callback function before the next repaint. + // fn callback(timestamp: f64) + // Returns the request ID, that uniquely identifies the entry in the callback list. + pub fn _requestAnimationFrame( + self: *Window, + callback: Callback, + ) !u32 { + // We immediately execute the callback, but this may not be correct TBD. + // Since: When multiple callbacks queued by requestAnimationFrame() begin to fire in a single frame, each receives the same timestamp even though time has passed during the computation of every previous callback's workload. + var result: Callback.Result = undefined; + callback.tryCall(.{self.performance._now()}, &result) catch { + log.err("Window.requestAnimationFrame(): {s}", .{result.exception}); + log.debug("stack:\n{s}", .{result.stack orelse "???"}); + }; + return 99; // not unique, but user cannot make assumptions about it. cancelAnimationFrame will be too late anyway. + } + // TODO handle callback arguments. pub fn _setTimeout(self: *Window, cbk: Callback, delay: ?u32, state: *SessionState) !u32 { return self.createTimeout(cbk, delay, state, false); From bbeec0e7d6366939e5f2c237d51a071ab32a3879 Mon Sep 17 00:00:00 2001 From: sjorsdonkers <72333389+sjorsdonkers@users.noreply.github.com> Date: Tue, 13 May 2025 18:17:01 +0200 Subject: [PATCH 2/2] cancelAnimationFrame and test --- src/browser/html/window.zig | 38 +++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/browser/html/window.zig b/src/browser/html/window.zig index dac4f7a0f..a594da033 100644 --- a/src/browser/html/window.zig +++ b/src/browser/html/window.zig @@ -155,6 +155,12 @@ pub const Window = struct { return 99; // not unique, but user cannot make assumptions about it. cancelAnimationFrame will be too late anyway. } + // Cancels an animation frame request previously scheduled through requestAnimationFrame(). + // This is a no-op since _requestAnimationFrame immediately executes the callback. + pub fn _cancelAnimationFrame(_: *Window, request_id: u32) void { + _ = request_id; + } + // TODO handle callback arguments. pub fn _setTimeout(self: *Window, cbk: Callback, delay: ?u32, state: *SessionState) !u32 { return self.createTimeout(cbk, delay, state, false); @@ -254,3 +260,35 @@ const TimerCallback = struct { _ = self.window.timers.remove(self.timer_id); } }; + +const testing = @import("../../testing.zig"); +test "Browser.HTML.Window" { + var runner = try testing.jsRunner(testing.tracking_allocator, .{}); + defer runner.deinit(); + + // requestAnimationFrame should be able to wait by recursively calling itself + // Note however that we in this test do not wait as the request is just send to the browser + try runner.testCases(&.{ + .{ + \\ let start; + \\ function step(timestamp) { + \\ if (start === undefined) { + \\ start = timestamp; + \\ } + \\ const elapsed = timestamp - start; + \\ if (elapsed < 2000) { + \\ requestAnimationFrame(step); + \\ } + \\ } + , + "undefined", + }, + .{ "let id = requestAnimationFrame(step);", "undefined" }, + }, .{}); + + // cancelAnimationFrame should be able to cancel a request with the given id + try runner.testCases(&.{ + .{ "let request_id = requestAnimationFrame(timestamp => {});", "undefined" }, + .{ "cancelAnimationFrame(request_id);", "undefined" }, + }, .{}); +}