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
36 changes: 19 additions & 17 deletions src/browser/browser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const Walker = @import("dom/walker.zig").WalkerDepthFirst;

const Env = @import("env.zig").Env;
const App = @import("../app.zig").App;
const Loop = @import("../runtime/loop.zig").Loop;

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

Expand Down Expand Up @@ -171,8 +172,7 @@ pub const Session = struct {

std.debug.assert(self.page != null);
// Reset all existing callbacks.
self.browser.app.loop.resetJS();
self.browser.app.loop.resetZig();
self.browser.app.loop.reset();
self.executor.endScope();
self.page = null;

Expand Down Expand Up @@ -230,6 +230,8 @@ pub const Page = struct {

renderer: FlatRenderer,

microtask_node: Loop.CallbackNode,

window_clicked_event_node: parser.EventNode,

scope: *Env.Scope,
Expand All @@ -248,6 +250,7 @@ pub const Page = struct {
.url = URL.empty,
.session = session,
.renderer = FlatRenderer.init(arena),
.microtask_node = .{ .func = microtaskCallback },
.window_clicked_event_node = .{ .func = windowClicked },
.state = .{
.arena = arena,
Expand All @@ -264,13 +267,13 @@ pub const Page = struct {
// load polyfills
try polyfill.load(self.arena, self.scope);

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

fn microtaskLoop(self: *Page) void {
const browser = self.session.browser;
browser.runMicrotasks();
browser.app.loop.zigTimeout(1 * std.time.ns_per_ms, *Page, self, microtaskLoop);
fn microtaskCallback(node: *Loop.CallbackNode, repeat_delay: *?u63) void {
const self: *Page = @fieldParentPtr("microtask_node", node);
self.session.browser.runMicrotasks();
repeat_delay.* = 1 * std.time.ns_per_ms;
}

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

pub fn wait(self: *Page) !void {
// try catch
var try_catch: Env.TryCatch = undefined;
try_catch.init(self.scope);
defer try_catch.deinit();

self.session.browser.app.loop.run() catch |err| {
if (try try_catch.err(self.arena)) |msg| {
log.info("wait error: {s}", .{msg});
return;
} else {
log.info("wait error: {any}", .{err});
}
};
log.debug("wait: OK", .{});
try self.session.browser.app.loop.run();

if (try_catch.hasCaught() == false) {
log.debug("wait: OK", .{});
return;
}

const msg = (try try_catch.err(self.arena)) orelse "unknown";
log.info("wait error: {s}", .{msg});
}

pub fn origin(self: *const Page, arena: Allocator) ![]const u8 {
Expand Down
104 changes: 88 additions & 16 deletions src/browser/html/window.zig
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const std = @import("std");
const parser = @import("../netsurf.zig");
const Callback = @import("../env.zig").Callback;
const SessionState = @import("../env.zig").SessionState;
const Loop = @import("../../runtime/loop.zig").Loop;

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

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

const log = std.log.scoped(.window);

// https://dom.spec.whatwg.org/#interface-window-extensions
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#window
pub const Window = struct {
Expand All @@ -45,10 +48,9 @@ pub const Window = struct {
location: Location = .{},
storage_shelf: ?*storage.Shelf = null,

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

crypto: Crypto = .{},
console: Console = .{},
Expand Down Expand Up @@ -129,23 +131,93 @@ pub const Window = struct {

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

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

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

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

pub fn _clearTimeout(self: *Window, id: u32, state: *SessionState) !void {
// I do would prefer return an error in this case, but it seems some JS
// uses invalid id, in particular id 0.
// So we silently ignore invalid id for now.
if (id >= self.timeoutid) return;
pub fn createTimeout(self: *Window, cbk: Callback, delay_: ?u32, state: *SessionState, comptime repeat: bool) !u32 {
if (self.timers.count() > 512) {
return error.TooManyTimeout;
}
const timer_id = self.timer_id +% 1;
self.timer_id = timer_id;

const arena = state.arena;

const gop = try self.timers.getOrPut(arena, timer_id);
if (gop.found_existing) {
// this can only happen if we've created 2^31 timeouts.
return error.TooManyTimeout;
}
errdefer _ = self.timers.remove(timer_id);

const delay: u63 = (delay_ orelse 0) * std.time.ns_per_ms;
const callback = try arena.create(TimerCallback);

callback.* = .{
.cbk = cbk,
.loop_id = 0, // we're going to set this to a real value shortly
.window = self,
.timer_id = timer_id,
.node = .{ .func = TimerCallback.run },
.repeat = if (repeat) delay else null,
};
callback.loop_id = try state.loop.timeout(delay, &callback.node);

gop.value_ptr.* = callback;
return timer_id;
}
};

const TimerCallback = struct {
// the internal loop id, need it when cancelling
loop_id: usize,

// the id of our timer (windows.timers key)
timer_id: u31,

// The JavaScript callback to execute
cbk: Callback,

// This is the internal data that the event loop tracks. We'll get this
// back in run and, from it, can get our TimerCallback instance
node: Loop.CallbackNode = undefined,

// if the event should be repeated
repeat: ?u63 = null,

window: *Window,

fn run(node: *Loop.CallbackNode, repeat_delay: *?u63) void {
const self: *TimerCallback = @fieldParentPtr("node", node);

var result: Callback.Result = undefined;
self.cbk.tryCall(.{}, &result) catch {
log.err("timeout callback error: {s}", .{result.exception});
log.debug("stack:\n{s}", .{result.stack orelse "???"});
};

if (self.repeat) |r| {
// setInterval
repeat_delay.* = r;
return;
}

try state.loop.cancel(self.timeoutids[id], null);
// setTimeout
_ = self.window.timers.remove(self.timer_id);
}
};
11 changes: 5 additions & 6 deletions src/main_wpt.zig
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,11 @@ fn run(arena: Allocator, test_file: []const u8, loader: *FileLoader, err_out: *?
var try_catch: Env.TryCatch = undefined;
try_catch.init(runner.scope);
defer try_catch.deinit();
runner.loop.run() catch |err| {
if (try try_catch.err(arena)) |msg| {
err_out.* = msg;
}
return err;
};
try runner.loop.run();

if (try_catch.hasCaught()) {
err_out.* = (try try_catch.err(arena)) orelse "unknwon error";
}
}

// Check the final test status.
Expand Down
Loading
Loading