diff --git a/src/browser/Browser.zig b/src/browser/Browser.zig index 42171d6e..408af7e4 100644 --- a/src/browser/Browser.zig +++ b/src/browser/Browser.zig @@ -112,6 +112,10 @@ pub fn runMicrotasks(self: *const Browser) void { self.env.runMicrotasks(); } +pub fn runMacrotasks(self: *Browser) !?u64 { + return try self.env.runMacrotasks(); +} + pub fn runMessageLoop(self: *const Browser) void { while (self.env.pumpMessageLoop()) { if (comptime IS_DEBUG) { diff --git a/src/browser/Page.zig b/src/browser/Page.zig index c55bf634..37c87605 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -33,7 +33,6 @@ const String = @import("../string.zig").String; const Mime = @import("Mime.zig"); const Factory = @import("Factory.zig"); const Session = @import("Session.zig"); -const Scheduler = @import("Scheduler.zig"); const EventManager = @import("EventManager.zig"); const ScriptManager = @import("ScriptManager.zig"); @@ -202,8 +201,6 @@ document: *Document, // DOM version used to invalidate cached state of "live" collections version: usize, -scheduler: Scheduler, - _req_id: ?usize = null, _navigated_options: ?NavigatedOpts = null, @@ -237,10 +234,6 @@ pub fn deinit(self: *Page) void { // stats.print(&stream) catch unreachable; } - // This can release JS objects, so we need to do this while the js.Context - // is still around. - self.scheduler.deinit(); - { // some MicroTasks might be referencing the page, we need to drain it while // the page still exists @@ -271,8 +264,6 @@ fn reset(self: *Page, comptime initializing: bool) !void { const browser = self._session.browser; if (comptime initializing == false) { - self.scheduler.deinit(); - browser.env.destroyContext(self.js); // We force a garbage collection between page navigations to keep v8 @@ -299,7 +290,6 @@ fn reset(self: *Page, comptime initializing: bool) !void { } self._factory = Factory.init(self); - self.scheduler = Scheduler.init(self.arena); self.version = 0; self.url = "about:blank"; @@ -381,7 +371,7 @@ fn registerBackgroundTasks(self: *Page) !void { const Browser = @import("Browser.zig"); - try self.scheduler.add(self._session.browser, struct { + try self.js.scheduler.add(self._session.browser, struct { fn runMessageLoop(ctx: *anyopaque) !?u32 { const b: *Browser = @ptrCast(@alignCast(ctx)); b.runMessageLoop(); @@ -887,8 +877,8 @@ fn _wait(self: *Page, wait_ms: u32) !Session.WaitResult { var timer = try std.time.Timer.start(); var ms_remaining = wait_ms; - var scheduler = &self.scheduler; - var http_client = self._session.browser.http_client; + const browser = self._session.browser; + var http_client = browser.http_client; // I'd like the page to know NOTHING about cdp_socket / CDP, but the // fact is that the behavior of wait changes depending on whether or @@ -941,7 +931,7 @@ fn _wait(self: *Page, wait_ms: u32) !Session.WaitResult { // scheduler.run could trigger new http transfers, so do not // store http_client.active BEFORE this call and then use // it AFTER. - const ms_to_next_task = try scheduler.run(); + const ms_to_next_task = try browser.runMacrotasks(); const http_active = http_client.active; const total_network_activity = http_active + http_client.intercepted; @@ -1077,16 +1067,16 @@ fn printWaitAnalysis(self: *Page) void { const now = milliTimestamp(.monotonic); { - std.debug.print("\nhigh_priority schedule: {d}\n", .{self.scheduler.high_priority.count()}); - var it = self.scheduler.high_priority.iterator(); + std.debug.print("\nhigh_priority schedule: {d}\n", .{self.js.scheduler.high_priority.count()}); + var it = self.js.scheduler.high_priority.iterator(); while (it.next()) |task| { std.debug.print(" - {s} schedule: {d}ms\n", .{ task.name, task.run_at - now }); } } { - std.debug.print("\nlow_priority schedule: {d}\n", .{self.scheduler.low_priority.count()}); - var it = self.scheduler.low_priority.iterator(); + std.debug.print("\nlow_priority schedule: {d}\n", .{self.js.scheduler.low_priority.count()}); + var it = self.js.scheduler.low_priority.iterator(); while (it.next()) |task| { std.debug.print(" - {s} schedule: {d}ms\n", .{ task.name, task.run_at - now }); } @@ -1258,7 +1248,7 @@ pub fn notifyPerformanceObservers(self: *Page, entry: *Performance.Entry) !void } self._performance_delivery_scheduled = true; - return self.scheduler.add( + return self.js.scheduler.add( self, struct { fn run(_page: *anyopaque) anyerror!?u32 { diff --git a/src/browser/ScriptManager.zig b/src/browser/ScriptManager.zig index be968870..95c6150e 100644 --- a/src/browser/ScriptManager.zig +++ b/src/browser/ScriptManager.zig @@ -846,7 +846,7 @@ pub const Script = struct { defer { // We should run microtasks even if script execution fails. local.runMicrotasks(); - _ = page.scheduler.run() catch |err| { + _ = page.js.scheduler.run() catch |err| { log.err(.page, "scheduler", .{ .err = err }); }; } diff --git a/src/browser/js/Caller.zig b/src/browser/js/Caller.zig index 9f48d345..b999c6e0 100644 --- a/src/browser/js/Caller.zig +++ b/src/browser/js/Caller.zig @@ -35,6 +35,7 @@ const IS_DEBUG = @import("builtin").mode == .Debug; const Caller = @This(); local: js.Local, prev_local: ?*const js.Local, +prev_context: *Context, // Takes the raw v8 isolate and extracts the context from it. pub fn init(self: *Caller, v8_isolate: *v8.Isolate) void { @@ -53,7 +54,9 @@ pub fn init(self: *Caller, v8_isolate: *v8.Isolate) void { .isolate = .{ .handle = v8_isolate }, }, .prev_local = ctx.local, + .prev_context = ctx.page.js, }; + ctx.page.js = ctx; ctx.local = &self.local; } @@ -79,6 +82,7 @@ pub fn deinit(self: *Caller) void { ctx.call_depth = call_depth; ctx.local = self.prev_local; + ctx.page.js = self.prev_context; } pub const CallOpts = struct { diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index 6330ea1f..dbd1a116 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -23,6 +23,7 @@ const log = @import("../../log.zig"); const js = @import("js.zig"); const Env = @import("Env.zig"); const bridge = @import("bridge.zig"); +const Scheduler = @import("Scheduler.zig"); const Page = @import("../Page.zig"); const ScriptManager = @import("../ScriptManager.zig"); @@ -118,6 +119,9 @@ module_identifier: std.AutoHashMapUnmanaged(u32, [:0]const u8) = .empty, // the page's script manager script_manager: ?*ScriptManager, +// Our macrotasks +scheduler: Scheduler, + const ModuleEntry = struct { // Can be null if we're asynchrously loading the module, in // which case resolver_promise cannot be null. @@ -150,6 +154,14 @@ pub fn fromIsolate(isolate: js.Isolate) *Context { } pub fn deinit(self: *Context) void { + var page = self.page; + const prev_context = page.js; + page.js = self; + defer page.js = prev_context; + + // This can release JS objects + self.scheduler.deinit(); + { var it = self.identity_map.valueIterator(); while (it.next()) |global| { diff --git a/src/browser/js/Env.zig b/src/browser/js/Env.zig index b45ba00a..7cc5e3e7 100644 --- a/src/browser/js/Env.zig +++ b/src/browser/js/Env.zig @@ -240,6 +240,7 @@ pub fn createContext(self: *Env, page: *Page, enter: bool) !*Context { .templates = self.templates, .call_arena = page.call_arena, .script_manager = &page._script_manager, + .scheduler = .init(context_arena), }; try context.identity_map.putNoClobber(context_arena, @intFromPtr(page.window), global_global); @@ -271,6 +272,17 @@ pub fn runMicrotasks(self: *const Env) void { self.isolate.performMicrotasksCheckpoint(); } +pub fn runMacrotasks(self: *Env) !?u64 { + var ms_to_next_task: ?u64 = null; + for (self.contexts.items) |ctx| { + const ms = (try ctx.scheduler.run()) orelse continue; + if (ms_to_next_task == null or ms < ms_to_next_task.?) { + ms_to_next_task = ms; + } + } + return ms_to_next_task; +} + pub fn pumpMessageLoop(self: *const Env) bool { var hs: v8.HandleScope = undefined; v8.v8__HandleScope__CONSTRUCT(&hs, self.isolate.handle); diff --git a/src/browser/Scheduler.zig b/src/browser/js/Scheduler.zig similarity index 97% rename from src/browser/Scheduler.zig rename to src/browser/js/Scheduler.zig index 1bd3e60a..cbd83b7b 100644 --- a/src/browser/Scheduler.zig +++ b/src/browser/js/Scheduler.zig @@ -19,8 +19,8 @@ const std = @import("std"); const builtin = @import("builtin"); -const log = @import("../log.zig"); -const milliTimestamp = @import("../datetime.zig").milliTimestamp; +const log = @import("../../log.zig"); +const milliTimestamp = @import("../../datetime.zig").milliTimestamp; const IS_DEBUG = builtin.mode == .Debug; diff --git a/src/browser/webapi/AbortSignal.zig b/src/browser/webapi/AbortSignal.zig index 0bb93886..e5039b28 100644 --- a/src/browser/webapi/AbortSignal.zig +++ b/src/browser/webapi/AbortSignal.zig @@ -99,7 +99,7 @@ pub fn createTimeout(delay: u32, page: *Page) !*AbortSignal { .signal = try init(page), }; - try page.scheduler.add(callback, TimeoutCallback.run, delay, .{ + try page.js.scheduler.add(callback, TimeoutCallback.run, delay, .{ .name = "AbortSignal.timeout", }); diff --git a/src/browser/webapi/MessagePort.zig b/src/browser/webapi/MessagePort.zig index 15fa5091..58dd699c 100644 --- a/src/browser/webapi/MessagePort.zig +++ b/src/browser/webapi/MessagePort.zig @@ -65,7 +65,7 @@ pub fn postMessage(self: *MessagePort, message: js.Value.Global, page: *Page) !v .message = message, }); - try page.scheduler.add(callback, PostMessageCallback.run, 0, .{ + try page.js.scheduler.add(callback, PostMessageCallback.run, 0, .{ .name = "MessagePort.postMessage", .low_priority = false, }); diff --git a/src/browser/webapi/Window.zig b/src/browser/webapi/Window.zig index 9adf87a8..bbcaa3b0 100644 --- a/src/browser/webapi/Window.zig +++ b/src/browser/webapi/Window.zig @@ -366,7 +366,7 @@ pub fn postMessage(self: *Window, message: js.Value.Global, target_origin: ?[]co .message = message, .origin = try arena.dupe(u8, origin), }; - try page.scheduler.add(callback, PostMessageCallback.run, 0, .{ + try page.js.scheduler.add(callback, PostMessageCallback.run, 0, .{ .name = "postMessage", .low_priority = false, .finalizer = PostMessageCallback.cancelled, @@ -447,7 +447,7 @@ pub fn scrollTo(self: *Window, opts: ScrollToOpts, y: ?i32, page: *Page) !void { // We dispatch scroll event asynchronously after 10ms. So we can throttle // them. - try page.scheduler.add( + try page.js.scheduler.add( page, struct { fn dispatch(_page: *anyopaque) anyerror!?u32 { @@ -471,7 +471,7 @@ pub fn scrollTo(self: *Window, opts: ScrollToOpts, y: ?i32, page: *Page) !void { .{ .low_priority = true }, ); // We dispatch scrollend event asynchronously after 20ms. - try page.scheduler.add( + try page.js.scheduler.add( page, struct { fn dispatch(_page: *anyopaque) anyerror!?u32 { @@ -545,7 +545,7 @@ fn scheduleCallback(self: *Window, cb: js.Function.Temp, delay_ms: u32, opts: Sc }; gop.value_ptr.* = callback; - try page.scheduler.add(callback, ScheduleCallback.run, delay_ms, .{ + try page.js.scheduler.add(callback, ScheduleCallback.run, delay_ms, .{ .name = opts.name, .low_priority = opts.low_priority, .finalizer = ScheduleCallback.cancelled,