Skip to content

Commit f1fe4c0

Browse files
Merge pull request #600 from lightpanda-io/timeouts_and_intervals
Make intervals easier and faster, add window.setInterval and clearInt…
2 parents 921ac18 + 01aa826 commit f1fe4c0

File tree

6 files changed

+182
-200
lines changed

6 files changed

+182
-200
lines changed

src/browser/browser.zig

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const Walker = @import("dom/walker.zig").WalkerDepthFirst;
3232

3333
const Env = @import("env.zig").Env;
3434
const App = @import("../app.zig").App;
35+
const Loop = @import("../runtime/loop.zig").Loop;
3536

3637
const URL = @import("../url.zig").URL;
3738

@@ -171,8 +172,7 @@ pub const Session = struct {
171172

172173
std.debug.assert(self.page != null);
173174
// Reset all existing callbacks.
174-
self.browser.app.loop.resetJS();
175-
self.browser.app.loop.resetZig();
175+
self.browser.app.loop.reset();
176176
self.executor.endScope();
177177
self.page = null;
178178

@@ -230,6 +230,8 @@ pub const Page = struct {
230230

231231
renderer: FlatRenderer,
232232

233+
microtask_node: Loop.CallbackNode,
234+
233235
window_clicked_event_node: parser.EventNode,
234236

235237
scope: *Env.Scope,
@@ -248,6 +250,7 @@ pub const Page = struct {
248250
.url = URL.empty,
249251
.session = session,
250252
.renderer = FlatRenderer.init(arena),
253+
.microtask_node = .{ .func = microtaskCallback },
251254
.window_clicked_event_node = .{ .func = windowClicked },
252255
.state = .{
253256
.arena = arena,
@@ -264,13 +267,13 @@ pub const Page = struct {
264267
// load polyfills
265268
try polyfill.load(self.arena, self.scope);
266269

267-
self.microtaskLoop();
270+
// _ = try session.browser.app.loop.timeout(1 * std.time.ns_per_ms, &self.microtask_node);
268271
}
269272

270-
fn microtaskLoop(self: *Page) void {
271-
const browser = self.session.browser;
272-
browser.runMicrotasks();
273-
browser.app.loop.zigTimeout(1 * std.time.ns_per_ms, *Page, self, microtaskLoop);
273+
fn microtaskCallback(node: *Loop.CallbackNode, repeat_delay: *?u63) void {
274+
const self: *Page = @fieldParentPtr("microtask_node", node);
275+
self.session.browser.runMicrotasks();
276+
repeat_delay.* = 1 * std.time.ns_per_ms;
274277
}
275278

276279
// dump writes the page content into the given file.
@@ -297,20 +300,19 @@ pub const Page = struct {
297300
}
298301

299302
pub fn wait(self: *Page) !void {
300-
// try catch
301303
var try_catch: Env.TryCatch = undefined;
302304
try_catch.init(self.scope);
303305
defer try_catch.deinit();
304306

305-
self.session.browser.app.loop.run() catch |err| {
306-
if (try try_catch.err(self.arena)) |msg| {
307-
log.info("wait error: {s}", .{msg});
308-
return;
309-
} else {
310-
log.info("wait error: {any}", .{err});
311-
}
312-
};
313-
log.debug("wait: OK", .{});
307+
try self.session.browser.app.loop.run();
308+
309+
if (try_catch.hasCaught() == false) {
310+
log.debug("wait: OK", .{});
311+
return;
312+
}
313+
314+
const msg = (try try_catch.err(self.arena)) orelse "unknown";
315+
log.info("wait error: {s}", .{msg});
314316
}
315317

316318
pub fn origin(self: *const Page, arena: Allocator) ![]const u8 {

src/browser/html/window.zig

Lines changed: 88 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const std = @import("std");
2121
const parser = @import("../netsurf.zig");
2222
const Callback = @import("../env.zig").Callback;
2323
const SessionState = @import("../env.zig").SessionState;
24+
const Loop = @import("../../runtime/loop.zig").Loop;
2425

2526
const Navigator = @import("navigator.zig").Navigator;
2627
const History = @import("history.zig").History;
@@ -31,6 +32,8 @@ const EventTarget = @import("../dom/event_target.zig").EventTarget;
3132

3233
const storage = @import("../storage/storage.zig");
3334

35+
const log = std.log.scoped(.window);
36+
3437
// https://dom.spec.whatwg.org/#interface-window-extensions
3538
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#window
3639
pub const Window = struct {
@@ -45,10 +48,9 @@ pub const Window = struct {
4548
location: Location = .{},
4649
storage_shelf: ?*storage.Shelf = null,
4750

48-
// store a map between internal timeouts ids and pointers to uint.
49-
// the maximum number of possible timeouts is fixed.
50-
timeoutid: u32 = 0,
51-
timeoutids: [512]u64 = undefined,
51+
// counter for having unique timer ids
52+
timer_id: u31 = 0,
53+
timers: std.AutoHashMapUnmanaged(u32, *TimerCallback) = .{},
5254

5355
crypto: Crypto = .{},
5456
console: Console = .{},
@@ -129,23 +131,93 @@ pub const Window = struct {
129131

130132
// TODO handle callback arguments.
131133
pub fn _setTimeout(self: *Window, cbk: Callback, delay: ?u32, state: *SessionState) !u32 {
132-
if (self.timeoutid >= self.timeoutids.len) return error.TooMuchTimeout;
134+
return self.createTimeout(cbk, delay, state, false);
135+
}
133136

134-
const ddelay: u63 = delay orelse 0;
135-
const id = try state.loop.timeout(ddelay * std.time.ns_per_ms, cbk);
137+
// TODO handle callback arguments.
138+
pub fn _setInterval(self: *Window, cbk: Callback, delay: ?u32, state: *SessionState) !u32 {
139+
return self.createTimeout(cbk, delay, state, true);
140+
}
136141

137-
self.timeoutids[self.timeoutid] = id;
138-
defer self.timeoutid += 1;
142+
pub fn _clearTimeout(self: *Window, id: u32, state: *SessionState) !void {
143+
const kv = self.timers.fetchRemove(id) orelse return;
144+
try state.loop.cancel(kv.value.loop_id);
145+
}
139146

140-
return self.timeoutid;
147+
pub fn _clearInterval(self: *Window, id: u32, state: *SessionState) !void {
148+
const kv = self.timers.fetchRemove(id) orelse return;
149+
try state.loop.cancel(kv.value.loop_id);
141150
}
142151

143-
pub fn _clearTimeout(self: *Window, id: u32, state: *SessionState) !void {
144-
// I do would prefer return an error in this case, but it seems some JS
145-
// uses invalid id, in particular id 0.
146-
// So we silently ignore invalid id for now.
147-
if (id >= self.timeoutid) return;
152+
pub fn createTimeout(self: *Window, cbk: Callback, delay_: ?u32, state: *SessionState, comptime repeat: bool) !u32 {
153+
if (self.timers.count() > 512) {
154+
return error.TooManyTimeout;
155+
}
156+
const timer_id = self.timer_id +% 1;
157+
self.timer_id = timer_id;
158+
159+
const arena = state.arena;
160+
161+
const gop = try self.timers.getOrPut(arena, timer_id);
162+
if (gop.found_existing) {
163+
// this can only happen if we've created 2^31 timeouts.
164+
return error.TooManyTimeout;
165+
}
166+
errdefer _ = self.timers.remove(timer_id);
167+
168+
const delay: u63 = (delay_ orelse 0) * std.time.ns_per_ms;
169+
const callback = try arena.create(TimerCallback);
170+
171+
callback.* = .{
172+
.cbk = cbk,
173+
.loop_id = 0, // we're going to set this to a real value shortly
174+
.window = self,
175+
.timer_id = timer_id,
176+
.node = .{ .func = TimerCallback.run },
177+
.repeat = if (repeat) delay else null,
178+
};
179+
callback.loop_id = try state.loop.timeout(delay, &callback.node);
180+
181+
gop.value_ptr.* = callback;
182+
return timer_id;
183+
}
184+
};
185+
186+
const TimerCallback = struct {
187+
// the internal loop id, need it when cancelling
188+
loop_id: usize,
189+
190+
// the id of our timer (windows.timers key)
191+
timer_id: u31,
192+
193+
// The JavaScript callback to execute
194+
cbk: Callback,
195+
196+
// This is the internal data that the event loop tracks. We'll get this
197+
// back in run and, from it, can get our TimerCallback instance
198+
node: Loop.CallbackNode = undefined,
199+
200+
// if the event should be repeated
201+
repeat: ?u63 = null,
202+
203+
window: *Window,
204+
205+
fn run(node: *Loop.CallbackNode, repeat_delay: *?u63) void {
206+
const self: *TimerCallback = @fieldParentPtr("node", node);
207+
208+
var result: Callback.Result = undefined;
209+
self.cbk.tryCall(.{}, &result) catch {
210+
log.err("timeout callback error: {s}", .{result.exception});
211+
log.debug("stack:\n{s}", .{result.stack orelse "???"});
212+
};
213+
214+
if (self.repeat) |r| {
215+
// setInterval
216+
repeat_delay.* = r;
217+
return;
218+
}
148219

149-
try state.loop.cancel(self.timeoutids[id], null);
220+
// setTimeout
221+
_ = self.window.timers.remove(self.timer_id);
150222
}
151223
};

src/main_wpt.zig

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,11 @@ fn run(arena: Allocator, test_file: []const u8, loader: *FileLoader, err_out: *?
156156
var try_catch: Env.TryCatch = undefined;
157157
try_catch.init(runner.scope);
158158
defer try_catch.deinit();
159-
runner.loop.run() catch |err| {
160-
if (try try_catch.err(arena)) |msg| {
161-
err_out.* = msg;
162-
}
163-
return err;
164-
};
159+
try runner.loop.run();
160+
161+
if (try_catch.hasCaught()) {
162+
err_out.* = (try try_catch.err(arena)) orelse "unknwon error";
163+
}
165164
}
166165

167166
// Check the final test status.

0 commit comments

Comments
 (0)