diff --git a/build.zig.zon b/build.zig.zon index 0d115d53..a1ab877c 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -6,8 +6,9 @@ .minimum_zig_version = "0.15.2", .dependencies = .{ .v8 = .{ - .url = "https://github.com/lightpanda-io/zig-v8-fork/archive/refs/tags/v0.3.0.tar.gz", - .hash = "v8-0.0.0-xddH69R6BADRXsnhjA8wNnfKfLQACF1I7CSTZvsMAvp8", + .url = "https://github.com/lightpanda-io/zig-v8-fork/archive/8c7e5df8b93e7cbd42f8f1c4ac24aaa7f05cd098.tar.gz", + .hash = "v8-0.0.0-xddH64J7BAC81mkf6G9RbEJxS-W3TIRl5iFnShwbqCqy", + }, //.v8 = .{ .path = "../zig-v8-fork" }, .@"boringssl-zig" = .{ diff --git a/src/browser/Browser.zig b/src/browser/Browser.zig index 6572b20f..503306d3 100644 --- a/src/browser/Browser.zig +++ b/src/browser/Browser.zig @@ -92,10 +92,24 @@ pub fn runMicrotasks(self: *Browser) void { } pub fn runMacrotasks(self: *Browser) !?u64 { - return try self.env.runMacrotasks(); + const env = &self.env; + + const time_to_next = try self.env.runMacrotasks(); + env.pumpMessageLoop(); + + // either of the above could have queued more microtasks + env.runMicrotasks(); + + return time_to_next; } -pub fn runMessageLoop(self: *const Browser) void { - self.env.pumpMessageLoop(); +pub fn hasBackgroundTasks(self: *Browser) bool { + return self.env.hasBackgroundTasks(); +} +pub fn waitForBackgroundTasks(self: *Browser) void { + self.env.waitForBackgroundTasks(); +} + +pub fn runIdleTasks(self: *const Browser) void { self.env.runIdleTasks(); } diff --git a/src/browser/Page.zig b/src/browser/Page.zig index 11ff9ebc..cf3d2523 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -311,12 +311,12 @@ pub fn init(self: *Page, id: u32, session: *Session, parent: ?*Page) !void { if (comptime builtin.is_test == false) { // HTML test runner manually calls these as necessary try self.js.scheduler.add(session.browser, struct { - fn runMessageLoop(ctx: *anyopaque) !?u32 { + fn runIdleTasks(ctx: *anyopaque) !?u32 { const b: *@import("Browser.zig") = @ptrCast(@alignCast(ctx)); - b.runMessageLoop(); - return 250; + b.runIdleTasks(); + return 200; } - }.runMessageLoop, 250, .{ .name = "page.messageLoop" }); + }.runIdleTasks, 200, .{ .name = "page.runIdleTasks", .low_priority = true }); } } diff --git a/src/browser/ScriptManager.zig b/src/browser/ScriptManager.zig index b536e2db..deed426d 100644 --- a/src/browser/ScriptManager.zig +++ b/src/browser/ScriptManager.zig @@ -862,8 +862,7 @@ pub const Script = struct { } defer { - // We should run microtasks even if script execution fails. - local.runMicrotasks(); + local.runMacrotasks(); // also runs microtasks _ = page.js.scheduler.run() catch |err| { log.err(.page, "scheduler", .{ .err = err }); }; diff --git a/src/browser/Session.zig b/src/browser/Session.zig index c9b4db48..c8d701cc 100644 --- a/src/browser/Session.zig +++ b/src/browser/Session.zig @@ -257,7 +257,7 @@ fn _wait(self: *Session, page: *Page, wait_ms: u32) !WaitResult { std.debug.assert(http_client.intercepted == 0); } - const ms = ms_to_next_task orelse blk: { + const ms: u64 = ms_to_next_task orelse blk: { if (wait_ms - ms_remaining < 100) { if (comptime builtin.is_test) { return .done; @@ -267,6 +267,14 @@ fn _wait(self: *Session, page: *Page, wait_ms: u32) !WaitResult { // background jobs. break :blk 50; } + + if (browser.hasBackgroundTasks()) { + // _we_ have nothing to run, but v8 is working on + // background tasks. We'll wait for them. + browser.waitForBackgroundTasks(); + break :blk 20; + } + // No http transfers, no cdp extra socket, no // scheduled tasks, we're done. return .done; @@ -292,8 +300,14 @@ fn _wait(self: *Session, page: *Page, wait_ms: u32) !WaitResult { // an cdp_socket registered with the http client). // We should continue to run lowPriority tasks, so we // minimize how long we'll poll for network I/O. - const ms_to_wait = @min(200, @min(ms_remaining, ms_to_next_task orelse 200)); - if (try http_client.tick(ms_to_wait) == .cdp_socket) { + var ms_to_wait = @min(200, ms_to_next_task orelse 200); + if (ms_to_wait > 10 and browser.hasBackgroundTasks()) { + // if we have bakcground tasks, we don't want ot wait too + // long for a message from the client. We want to go back + // to the top of the loop and run macrotasks. + ms_to_wait = 10; + } + if (try http_client.tick(@min(ms_remaining, ms_to_wait)) == .cdp_socket) { // data on a socket we aren't handling, return to caller return .cdp_socket; } diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index 1b804ced..ebcc56fc 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -237,7 +237,7 @@ pub fn deinit(self: *Context) void { env.isolate.notifyContextDisposed(); // There can be other tasks associated with this context that we need to // purge while the context is still alive. - env.pumpMessageLoop(); + _ = env.pumpMessageLoop(); v8.v8__MicrotaskQueue__DELETE(self.microtask_queue); } diff --git a/src/browser/js/Env.zig b/src/browser/js/Env.zig index e0e060cb..2f1b6b1e 100644 --- a/src/browser/js/Env.zig +++ b/src/browser/js/Env.zig @@ -397,10 +397,23 @@ pub fn pumpMessageLoop(self: *const Env) void { const isolate = self.isolate.handle; const platform = self.platform.handle; - while (v8.v8__Platform__PumpMessageLoop(platform, isolate, false)) { - if (comptime IS_DEBUG) { - log.debug(.browser, "pumpMessageLoop", .{}); - } + while (v8.v8__Platform__PumpMessageLoop(platform, isolate, false)) {} +} + +pub fn hasBackgroundTasks(self: *const Env) bool { + return v8.v8__Isolate__HasPendingBackgroundTasks(self.isolate.handle); +} + +pub fn waitForBackgroundTasks(self: *Env) void { + var hs: v8.HandleScope = undefined; + v8.v8__HandleScope__CONSTRUCT(&hs, self.isolate.handle); + defer v8.v8__HandleScope__DESTRUCT(&hs); + + const isolate = self.isolate.handle; + const platform = self.platform.handle; + while (v8.v8__Isolate__HasPendingBackgroundTasks(isolate)) { + _ = v8.v8__Platform__PumpMessageLoop(platform, isolate, true); + self.runMicrotasks(); } } diff --git a/src/browser/js/Local.zig b/src/browser/js/Local.zig index af9febf4..1e2d3505 100644 --- a/src/browser/js/Local.zig +++ b/src/browser/js/Local.zig @@ -82,6 +82,12 @@ pub fn createTypedArray(self: *const Local, comptime array_type: js.ArrayType, s return .init(self, size); } +pub fn runMacrotasks(self: *const Local) void { + const env = self.ctx.env; + env.pumpMessageLoop(); + env.runMicrotasks(); // macrotasks can cause microtasks to queue +} + pub fn runMicrotasks(self: *const Local) void { self.ctx.env.runMicrotasks(); } diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index c486e794..8134d3f3 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -436,17 +436,18 @@ pub fn BrowserContext(comptime CDP_T: type) type { pub fn deinit(self: *Self) void { const browser = &self.cdp.browser; + const env = &browser.env; // Drain microtasks makes sure we don't have inspector's callback // in progress before deinit. - browser.env.runMicrotasks(); + env.runMicrotasks(); // resetContextGroup detach the inspector from all contexts. // It append async tasks, so we make sure we run the message loop // before deinit it. - browser.env.inspector.?.resetContextGroup(); - browser.runMessageLoop(); - browser.env.inspector.?.stopSession(); + env.inspector.?.resetContextGroup(); + _ = env.pumpMessageLoop(); + env.inspector.?.stopSession(); // abort all intercepted requests before closing the sesion/page // since some of these might callback into the page/scriptmanager