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
39 changes: 30 additions & 9 deletions src/browser/html/window.zig
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const std = @import("std");

const log = @import("../../log.zig");
const parser = @import("../netsurf.zig");
const Function = @import("../env.zig").Function;
const Env = @import("../env.zig").Env;
const Page = @import("../page.zig").Page;
const Loop = @import("../../runtime/loop.zig").Loop;

Expand All @@ -36,6 +36,9 @@ const CSSStyleDeclaration = @import("../cssom/css_style_declaration.zig").CSSSty
const Screen = @import("screen.zig").Screen;
const Css = @import("../css/css.zig").Css;

const Function = Env.Function;
const JsObject = Env.JsObject;

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

// https://dom.spec.whatwg.org/#interface-window-extensions
Expand Down Expand Up @@ -184,13 +187,12 @@ pub const Window = struct {
return page.loop.cancel(kv.value.loop_id);
}

pub fn _setTimeout(self: *Window, cbk: Function, delay: ?u32, page: *Page) !u32 {
return self.createTimeout(cbk, delay, page, .{});
pub fn _setTimeout(self: *Window, cbk: Function, delay: ?u32, params: []Env.JsObject, page: *Page) !u32 {
return self.createTimeout(cbk, delay, page, .{ .args = params });
}

// TODO handle callback arguments.
pub fn _setInterval(self: *Window, cbk: Function, delay: ?u32, page: *Page) !u32 {
return self.createTimeout(cbk, delay, page, .{ .repeat = true });
pub fn _setInterval(self: *Window, cbk: Function, delay: ?u32, params: []Env.JsObject, page: *Page) !u32 {
return self.createTimeout(cbk, delay, page, .{ .repeat = true, .args = params });
}

pub fn _clearTimeout(self: *Window, id: u32, page: *Page) !void {
Expand Down Expand Up @@ -230,10 +232,11 @@ pub const Window = struct {
}

const CreateTimeoutOpts = struct {
args: []Env.JsObject = &.{},
repeat: bool = false,
animation_frame: bool = false,
};
fn createTimeout(self: *Window, cbk: Function, delay_: ?u32, page: *Page, comptime opts: CreateTimeoutOpts) !u32 {
fn createTimeout(self: *Window, cbk: Function, delay_: ?u32, page: *Page, opts: CreateTimeoutOpts) !u32 {
const delay = delay_ orelse 0;
if (delay > 5000) {
log.warn(.user_script, "long timeout ignored", .{ .delay = delay, .interval = opts.repeat });
Expand All @@ -258,6 +261,15 @@ pub const Window = struct {
}
errdefer _ = self.timers.remove(timer_id);

const args = opts.args;
var persisted_args: []Env.JsObject = &.{};
if (args.len > 0) {
persisted_args = try page.arena.alloc(Env.JsObject, args.len);
for (args, persisted_args) |a, *ca| {
ca.* = try a.persist();
}
}

const delay_ms: u63 = @as(u63, delay) * std.time.ns_per_ms;
const callback = try arena.create(TimerCallback);

Expand All @@ -266,6 +278,7 @@ pub const Window = struct {
.loop_id = 0, // we're going to set this to a real value shortly
.window = self,
.timer_id = timer_id,
.args = persisted_args,
.node = .{ .func = TimerCallback.run },
.repeat = if (opts.repeat) delay_ms else null,
.animation_frame = opts.animation_frame,
Expand Down Expand Up @@ -344,6 +357,8 @@ const TimerCallback = struct {

window: *Window,

args: []Env.JsObject = &.{},

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

Expand All @@ -353,7 +368,7 @@ const TimerCallback = struct {
if (self.animation_frame) {
call = self.cbk.tryCall(void, .{self.window.performance._now()}, &result);
} else {
call = self.cbk.tryCall(void, .{}, &result);
call = self.cbk.tryCall(void, self.args, &result);
}

call catch {
Expand Down Expand Up @@ -427,11 +442,17 @@ test "Browser.HTML.Window" {
.{ "innerWidth", "2" },
}, .{});

// cancelAnimationFrame should be able to cancel a request with the given id
try runner.testCases(&.{
.{ "let longCall = false;", null },
.{ "window.setTimeout(() => {longCall = true}, 5001);", null },
.{ "longCall;", "false" },

.{ "let wst = 0;", null },
.{ "window.setTimeout(() => {wst += 1}, 1)", null },
.{ "wst", "1" },

.{ "window.setTimeout((a, b) => {wst = a + b}, 1, 2, 3)", null },
.{ "wst", "5" },
}, .{});

// window event target
Expand Down
27 changes: 21 additions & 6 deletions src/runtime/js.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1610,13 +1610,28 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
const js_this = try js_context.valueToExistingObject(this);

const aargs = if (comptime @typeInfo(@TypeOf(args)) == .null) struct {}{} else args;
const fields = @typeInfo(@TypeOf(aargs)).@"struct".fields;
var js_args: [fields.len]v8.Value = undefined;
inline for (fields, 0..) |f, i| {
js_args[i] = try js_context.zigValueToJs(@field(aargs, f.name));
}

const result = self.func.castToFunction().call(js_context.v8_context, js_this, &js_args);
const js_args: []const v8.Value = switch (@typeInfo(@TypeOf(aargs))) {
.@"struct" => |s| blk: {
const fields = s.fields;
var js_args: [fields.len]v8.Value = undefined;
inline for (fields, 0..) |f, i| {
js_args[i] = try js_context.zigValueToJs(@field(aargs, f.name));
}
const cargs: [fields.len]v8.Value = js_args;
break :blk &cargs;
},
.pointer => blk: {
var values = try js_context.call_arena.alloc(v8.Value, args.len);
for (args, 0..) |a, i| {
values[i] = try js_context.zigValueToJs(a);
}
break :blk values;
},
else => @compileError("JS Function called with invalid paremter type"),
};

const result = self.func.castToFunction().call(js_context.v8_context, js_this, js_args);
if (result == null) {
return error.JSExecCallback;
}
Expand Down