diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig index e3c7813f..704c6975 100644 --- a/src/browser/js/bridge.zig +++ b/src/browser/js/bridge.zig @@ -607,6 +607,7 @@ pub const JsApis = flattenTypes(&.{ @import("../webapi/IntersectionObserver.zig"), @import("../webapi/CustomElementRegistry.zig"), @import("../webapi/ResizeObserver.zig"), + @import("../webapi/IdleDeadline.zig"), @import("../webapi/Blob.zig"), @import("../webapi/File.zig"), @import("../webapi/Screen.zig"), diff --git a/src/browser/webapi/IdleDeadline.zig b/src/browser/webapi/IdleDeadline.zig new file mode 100644 index 00000000..748daa36 --- /dev/null +++ b/src/browser/webapi/IdleDeadline.zig @@ -0,0 +1,51 @@ +// 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 IdleDeadline = @This(); + +pub fn init() IdleDeadline { + return .{}; +} + +pub fn getDidTimeout(_: *const IdleDeadline) bool { + return false; +} + +pub fn timeRemaining(_: *const IdleDeadline) f64 { + // Return a fixed 50ms. + // This allows idle callbacks to perform work without complex + // timing infrastructure. + return 50.0; +} + +pub const JsApi = struct { + const js = @import("../js/js.zig"); + pub const bridge = js.Bridge(IdleDeadline); + + pub const Meta = struct { + pub const name = "IdleDeadline"; + pub const prototype_chain = bridge.prototypeChain(); + pub var class_id: bridge.ClassId = undefined; + pub const empty_with_no_proto = true; + }; + + pub const timeRemaining = bridge.function(IdleDeadline.timeRemaining, .{}); + pub const didTimeout = bridge.accessor(IdleDeadline.getDidTimeout, null, .{}); +}; diff --git a/src/browser/webapi/Window.zig b/src/browser/webapi/Window.zig index 51213ecd..e3b0d0d4 100644 --- a/src/browser/webapi/Window.zig +++ b/src/browser/webapi/Window.zig @@ -223,7 +223,7 @@ pub fn requestAnimationFrame(self: *Window, cb: js.Function, page: *Page) !u32 { .repeat = false, .params = &.{}, .low_priority = false, - .animation_frame = true, + .mode = .animation_frame, .name = "window.requestAnimationFrame", }, page); } @@ -258,6 +258,7 @@ const RequestIdleCallbackOpts = struct { pub fn requestIdleCallback(self: *Window, cb: js.Function, opts_: ?RequestIdleCallbackOpts, page: *Page) !u32 { const opts = opts_ orelse RequestIdleCallbackOpts{}; return self.scheduleCallback(cb, opts.timeout orelse 50, .{ + .mode = .idle, .repeat = false, .params = &.{}, .low_priority = true, @@ -364,6 +365,7 @@ const ScheduleOpts = struct { name: []const u8, low_priority: bool = false, animation_frame: bool = false, + mode: ScheduleCallback.Mode = .normal, }; fn scheduleCallback(self: *Window, cb: js.Function, delay_ms: u32, opts: ScheduleOpts, page: *Page) !u32 { if (self._timers.count() > 512) { @@ -393,10 +395,10 @@ fn scheduleCallback(self: *Window, cb: js.Function, delay_ms: u32, opts: Schedul const callback = try page._factory.create(ScheduleCallback{ .cb = cb, .page = page, + .mode = opts.mode, .name = opts.name, .timer_id = timer_id, .params = persisted_params, - .animation_frame = opts.animation_frame, .repeat_ms = if (opts.repeat) if (delay_ms == 0) 1 else delay_ms else null, }); gop.value_ptr.* = callback; @@ -428,7 +430,13 @@ const ScheduleCallback = struct { removed: bool = false, - animation_frame: bool = false, + mode: Mode, + + const Mode = enum { + idle, + normal, + animation_frame, + }; fn deinit(self: *ScheduleCallback) void { self.page._factory.destroy(self); @@ -443,16 +451,23 @@ const ScheduleCallback = struct { return null; } - if (self.animation_frame) { - self.cb.call(void, .{page.window._performance.now()}) catch |err| { - // a non-JS error - log.warn(.js, "window.RAF", .{ .name = self.name, .err = err }); - }; - } else { - self.cb.call(void, .{self.params}) catch |err| { - // a non-JS error - log.warn(.js, "window.timer", .{ .name = self.name, .err = err }); - }; + switch (self.mode) { + .idle => { + const IdleDeadline = @import("IdleDeadline.zig"); + self.cb.call(void, .{IdleDeadline{}}) catch |err| { + log.warn(.js, "window.idleCallback", .{ .name = self.name, .err = err }); + }; + }, + .animation_frame => { + self.cb.call(void, .{page.window._performance.now()}) catch |err| { + log.warn(.js, "window.RAF", .{ .name = self.name, .err = err }); + }; + }, + .normal => { + self.cb.call(void, .{self.params}) catch |err| { + log.warn(.js, "window.timer", .{ .name = self.name, .err = err }); + }; + }, } if (self.repeat_ms) |ms| {