From 22a93a9c39d3603b3763d7a8d5f85d417ff7f887 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Mon, 30 Jun 2025 15:03:52 -0700 Subject: [PATCH 1/8] add pump message loop calls --- src/app.zig | 4 ++++ src/browser/browser.zig | 7 ++++++- src/main.zig | 1 + src/runtime/js.zig | 9 ++++++++- 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/app.zig b/src/app.zig index 88599598..f78fa8c8 100644 --- a/src/app.zig +++ b/src/app.zig @@ -4,6 +4,7 @@ const Allocator = std.mem.Allocator; const log = @import("log.zig"); const Loop = @import("runtime/loop.zig").Loop; const http = @import("http/client.zig"); +const Platform = @import("runtime/js.zig").Platform; const Telemetry = @import("telemetry/telemetry.zig").Telemetry; const Notification = @import("notification.zig").Notification; @@ -13,6 +14,7 @@ const Notification = @import("notification.zig").Notification; pub const App = struct { loop: *Loop, config: Config, + platform: *const Platform, allocator: Allocator, telemetry: Telemetry, http_client: http.Client, @@ -28,6 +30,7 @@ pub const App = struct { pub const Config = struct { run_mode: RunMode, + platform: *const Platform, tls_verify_host: bool = true, http_proxy: ?std.Uri = null, proxy_type: ?http.ProxyType = null, @@ -53,6 +56,7 @@ pub const App = struct { .loop = loop, .allocator = allocator, .telemetry = undefined, + .platform = config.platform, .app_dir_path = app_dir_path, .notification = notification, .http_client = try http.Client.init(allocator, loop, .{ diff --git a/src/browser/browser.zig b/src/browser/browser.zig index 8bf24e36..7e9699aa 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -27,6 +27,8 @@ const App = @import("../app.zig").App; const Session = @import("session.zig").Session; const Notification = @import("../notification.zig").Notification; +const log = @import("../log.zig"); + const http = @import("../http/client.zig"); // Browser is an instance of the browser. @@ -47,7 +49,7 @@ pub const Browser = struct { pub fn init(app: *App) !Browser { const allocator = app.allocator; - const env = try Env.init(allocator, .{}); + const env = try Env.init(allocator, app.platform, .{}); errdefer env.deinit(); const notification = try Notification.init(allocator, app.notification); @@ -95,6 +97,9 @@ pub const Browser = struct { } pub fn runMicrotasks(self: *const Browser) void { + while (self.env.pumpMessageLoop()) { + log.debug(.browser, "pumpMessageLoop", .{}); + } return self.env.runMicrotasks(); } }; diff --git a/src/main.zig b/src/main.zig index f54c90fb..a7f3a050 100644 --- a/src/main.zig +++ b/src/main.zig @@ -83,6 +83,7 @@ fn run(alloc: Allocator) !void { var app = try App.init(alloc, .{ .run_mode = args.mode, + .platform = &platform, .http_proxy = args.httpProxy(), .proxy_type = args.proxyType(), .proxy_auth = args.proxyAuth(), diff --git a/src/runtime/js.zig b/src/runtime/js.zig index 3e471f44..b7fa12b3 100644 --- a/src/runtime/js.zig +++ b/src/runtime/js.zig @@ -156,6 +156,8 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { return struct { allocator: Allocator, + platform: *const Platform, + // the global isolate isolate: v8.Isolate, @@ -181,7 +183,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { const Opts = struct {}; - pub fn init(allocator: Allocator, _: Opts) !*Self { + pub fn init(allocator: Allocator, platform: *const Platform, _: Opts) !*Self { // var params = v8.initCreateParams(); var params = try allocator.create(v8.CreateParams); errdefer allocator.destroy(params); @@ -215,6 +217,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { errdefer allocator.destroy(env); env.* = .{ + .platform = platform, .isolate = isolate, .templates = undefined, .allocator = allocator, @@ -270,6 +273,10 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { self.isolate.performMicrotasksCheckpoint(); } + pub fn pumpMessageLoop(self: *const Self) bool { + return self.platform.inner.pumpMessageLoop(self.isolate, false); + } + pub fn newExecutionWorld(self: *Self) !ExecutionWorld { return .{ .env = self, From 3c0143af92b1801cd0ed7bc056a9befea048f710 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Mon, 30 Jun 2025 18:50:04 -0700 Subject: [PATCH 2/8] add runIdleTasks --- src/app.zig | 4 ++-- src/browser/browser.zig | 3 ++- src/runtime/js.zig | 12 +++++++++--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/app.zig b/src/app.zig index f78fa8c8..ad66f73a 100644 --- a/src/app.zig +++ b/src/app.zig @@ -14,7 +14,7 @@ const Notification = @import("notification.zig").Notification; pub const App = struct { loop: *Loop, config: Config, - platform: *const Platform, + platform: ?*const Platform, allocator: Allocator, telemetry: Telemetry, http_client: http.Client, @@ -30,7 +30,7 @@ pub const App = struct { pub const Config = struct { run_mode: RunMode, - platform: *const Platform, + platform: ?*const Platform = null, tls_verify_host: bool = true, http_proxy: ?std.Uri = null, proxy_type: ?http.ProxyType = null, diff --git a/src/browser/browser.zig b/src/browser/browser.zig index 7e9699aa..623df886 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -97,10 +97,11 @@ pub const Browser = struct { } pub fn runMicrotasks(self: *const Browser) void { + self.env.runMicrotasks(); while (self.env.pumpMessageLoop()) { log.debug(.browser, "pumpMessageLoop", .{}); } - return self.env.runMicrotasks(); + self.env.runIdleTasks(); } }; diff --git a/src/runtime/js.zig b/src/runtime/js.zig index b7fa12b3..2eb5e1bc 100644 --- a/src/runtime/js.zig +++ b/src/runtime/js.zig @@ -156,7 +156,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { return struct { allocator: Allocator, - platform: *const Platform, + platform: ?*const Platform, // the global isolate isolate: v8.Isolate, @@ -183,7 +183,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { const Opts = struct {}; - pub fn init(allocator: Allocator, platform: *const Platform, _: Opts) !*Self { + pub fn init(allocator: Allocator, platform: ?*const Platform, _: Opts) !*Self { // var params = v8.initCreateParams(); var params = try allocator.create(v8.CreateParams); errdefer allocator.destroy(params); @@ -274,7 +274,13 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { } pub fn pumpMessageLoop(self: *const Self) bool { - return self.platform.inner.pumpMessageLoop(self.isolate, false); + if (self.platform == null) return false; + return self.platform.?.inner.pumpMessageLoop(self.isolate, false); + } + + pub fn runIdleTasks(self: *const Self) void { + if (self.platform == null) return; + return self.platform.?.inner.runIdleTasks(self.isolate, 1); } pub fn newExecutionWorld(self: *Self) !ExecutionWorld { From a3c14748d3d9bcca4d23dfb9373db93488a2b37b Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Tue, 1 Jul 2025 16:21:47 -0700 Subject: [PATCH 3/8] fix unit testing with platform deps requirement --- src/runtime/js.zig | 12 ++++++++++-- src/runtime/testing.zig | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/runtime/js.zig b/src/runtime/js.zig index 2eb5e1bc..357ce34c 100644 --- a/src/runtime/js.zig +++ b/src/runtime/js.zig @@ -274,12 +274,20 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { } pub fn pumpMessageLoop(self: *const Self) bool { - if (self.platform == null) return false; + if (self.platform == null) { + // In test mode only, platform can be null. + if (builtin.is_test) return false; + @panic("platform is null"); + } return self.platform.?.inner.pumpMessageLoop(self.isolate, false); } pub fn runIdleTasks(self: *const Self) void { - if (self.platform == null) return; + if (self.platform == null) { + // In test mode only, platform can be null. + if (builtin.is_test) return; + @panic("platform is null"); + } return self.platform.?.inner.runIdleTasks(self.isolate, 1); } diff --git a/src/runtime/testing.zig b/src/runtime/testing.zig index 4ecd194f..25695232 100644 --- a/src/runtime/testing.zig +++ b/src/runtime/testing.zig @@ -42,7 +42,7 @@ pub fn Runner(comptime State: type, comptime Global: type, comptime types: anyty const self = try allocator.create(Self); errdefer allocator.destroy(self); - self.env = try Env.init(allocator, .{}); + self.env = try Env.init(allocator, null, .{}); errdefer self.env.deinit(); self.executor = try self.env.newExecutionWorld(); From 80348ef190c5079949d3f2256d4fca731c8e9be0 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Tue, 1 Jul 2025 16:45:24 -0700 Subject: [PATCH 4/8] fix wpt tests with platform requirement --- src/main_wpt.zig | 17 +++++++++++++++-- src/testing.zig | 4 ++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/main_wpt.zig b/src/main_wpt.zig index cc3e013d..3f2396ee 100644 --- a/src/main_wpt.zig +++ b/src/main_wpt.zig @@ -70,7 +70,13 @@ pub fn main() !void { defer _ = test_arena.reset(.{ .retain_capacity = {} }); var err_out: ?[]const u8 = null; - const result = run(test_arena.allocator(), test_file, &loader, &err_out) catch |err| blk: { + const result = run( + test_arena.allocator(), + &platform, + test_file, + &loader, + &err_out, + ) catch |err| blk: { if (err_out == null) { err_out = @errorName(err); } @@ -89,7 +95,13 @@ pub fn main() !void { try writer.finalize(); } -fn run(arena: Allocator, test_file: []const u8, loader: *FileLoader, err_out: *?[]const u8) !?[]const u8 { +fn run( + arena: Allocator, + platform: *const Platform, + test_file: []const u8, + loader: *FileLoader, + err_out: *?[]const u8, +) !?[]const u8 { // document const html = blk: { const full_path = try std.fs.path.join(arena, &.{ WPT_DIR, test_file }); @@ -110,6 +122,7 @@ fn run(arena: Allocator, test_file: []const u8, loader: *FileLoader, err_out: *? var runner = try @import("testing.zig").jsRunner(arena, .{ .url = "http://127.0.0.1", .html = html, + .platform = platform, }); defer runner.deinit(); diff --git a/src/testing.zig b/src/testing.zig index f5e9c20b..f05f9023 100644 --- a/src/testing.zig +++ b/src/testing.zig @@ -19,6 +19,8 @@ const std = @import("std"); const Allocator = std.mem.Allocator; +const Platform = @import("runtime/js.zig").Platform; + pub const allocator = std.testing.allocator; pub const expectError = std.testing.expectError; pub const expect = std.testing.expect; @@ -383,6 +385,7 @@ pub const JsRunner = struct { var app = try App.init(alloc, .{ .run_mode = .serve, .tls_verify_host = false, + .platform = opts.platform, }); errdefer app.deinit(); @@ -474,6 +477,7 @@ pub const JsRunner = struct { }; const RunnerOpts = struct { + platform: ?*const Platform = null, url: []const u8 = "https://lightpanda.io/opensource-browser/", html: []const u8 = \\
From 1504e36a68a8d148921784b2fb577bdd45178658 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Wed, 2 Jul 2025 12:26:33 -0700 Subject: [PATCH 5/8] use comptime test for platform existence --- src/runtime/js.zig | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/runtime/js.zig b/src/runtime/js.zig index 357ce34c..bd35bdd8 100644 --- a/src/runtime/js.zig +++ b/src/runtime/js.zig @@ -274,20 +274,18 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { } pub fn pumpMessageLoop(self: *const Self) bool { - if (self.platform == null) { - // In test mode only, platform can be null. - if (builtin.is_test) return false; - @panic("platform is null"); + if (comptime builtin.is_test) { + if (self.platform == null) return false; } + // assume it's not-null in non-test. return self.platform.?.inner.pumpMessageLoop(self.isolate, false); } pub fn runIdleTasks(self: *const Self) void { - if (self.platform == null) { - // In test mode only, platform can be null. - if (builtin.is_test) return; - @panic("platform is null"); + if (comptime builtin.is_test) { + if (self.platform == null) return; } + // assume it's not-null in non-test. return self.platform.?.inner.runIdleTasks(self.isolate, 1); } From 44a76e59f98690cb3224a6c36e51ef718203390a Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Wed, 2 Jul 2025 13:16:20 -0700 Subject: [PATCH 6/8] run pumpmessageloop in its own loop --- src/browser/browser.zig | 4 ++++ src/browser/page.zig | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/browser/browser.zig b/src/browser/browser.zig index 623df886..d132a860 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -98,6 +98,10 @@ pub const Browser = struct { pub fn runMicrotasks(self: *const Browser) void { self.env.runMicrotasks(); + } + + pub fn runMessageLoop(self: *const Browser) void { + log.debug(.browser, "pumpMessageLoop", .{}); while (self.env.pumpMessageLoop()) { log.debug(.browser, "pumpMessageLoop", .{}); } diff --git a/src/browser/page.zig b/src/browser/page.zig index e8f92216..fd750b3d 100644 --- a/src/browser/page.zig +++ b/src/browser/page.zig @@ -78,7 +78,10 @@ pub const Page = struct { renderer: Renderer, + // run v8 micro tasks microtask_node: Loop.CallbackNode, + // run v8 pump message loop and idle tasks + messageloop_node: Loop.CallbackNode, keydown_event_node: parser.EventNode, window_clicked_event_node: parser.EventNode, @@ -106,6 +109,7 @@ pub const Page = struct { .state_pool = &browser.state_pool, .cookie_jar = &session.cookie_jar, .microtask_node = .{ .func = microtaskCallback }, + .messageloop_node = .{ .func = messageLoopCallback }, .keydown_event_node = .{ .func = keydownCallback }, .window_clicked_event_node = .{ .func = windowClicked }, .request_factory = browser.http_client.requestFactory(.{ @@ -119,6 +123,10 @@ pub const Page = struct { try polyfill.load(self.arena, self.main_context); _ = try session.browser.app.loop.timeout(1 * std.time.ns_per_ms, &self.microtask_node); + // message loop must run only non-test env + if (comptime !builtin.is_test) { + _ = try session.browser.app.loop.timeout(1 * std.time.ns_per_ms, &self.messageloop_node); + } } fn microtaskCallback(node: *Loop.CallbackNode, repeat_delay: *?u63) void { @@ -127,6 +135,12 @@ pub const Page = struct { repeat_delay.* = 1 * std.time.ns_per_ms; } + fn messageLoopCallback(node: *Loop.CallbackNode, repeat_delay: *?u63) void { + const self: *Page = @fieldParentPtr("messageloop_node", node); + self.session.browser.runMessageLoop(); + repeat_delay.* = 100 * std.time.ns_per_ms; + } + // dump writes the page content into the given file. pub fn dump(self: *const Page, out: std.fs.File) !void { if (self.raw_data) |raw_data| { From b78729f685a6d3766ebdde3aecc127c8506df0d9 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Wed, 2 Jul 2025 13:16:49 -0700 Subject: [PATCH 7/8] test: inject platform to the serveCDP app --- src/main.zig | 7 ++++--- src/runtime/js.zig | 10 ++-------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/main.zig b/src/main.zig index a7f3a050..95cdcead 100644 --- a/src/main.zig +++ b/src/main.zig @@ -603,7 +603,7 @@ test "tests:beforeAll" { log.opts.format = .logfmt; test_wg.startMany(3); - _ = try Platform.init(); + const platform = try Platform.init(); { const address = try std.net.Address.parseIp("127.0.0.1", 9582); @@ -619,7 +619,7 @@ test "tests:beforeAll" { { const address = try std.net.Address.parseIp("127.0.0.1", 9583); - const thread = try std.Thread.spawn(.{}, serveCDP, .{address}); + const thread = try std.Thread.spawn(.{}, serveCDP, .{ address, &platform }); thread.detach(); } @@ -801,11 +801,12 @@ fn serveHTTPS(address: std.net.Address) !void { } } -fn serveCDP(address: std.net.Address) !void { +fn serveCDP(address: std.net.Address, platform: *const Platform) !void { var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; var app = try App.init(gpa.allocator(), .{ .run_mode = .serve, .tls_verify_host = false, + .platform = platform, }); defer app.deinit(); diff --git a/src/runtime/js.zig b/src/runtime/js.zig index bd35bdd8..80585970 100644 --- a/src/runtime/js.zig +++ b/src/runtime/js.zig @@ -274,18 +274,12 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { } pub fn pumpMessageLoop(self: *const Self) bool { - if (comptime builtin.is_test) { - if (self.platform == null) return false; - } - // assume it's not-null in non-test. + // assume it's not-null. return self.platform.?.inner.pumpMessageLoop(self.isolate, false); } pub fn runIdleTasks(self: *const Self) void { - if (comptime builtin.is_test) { - if (self.platform == null) return; - } - // assume it's not-null in non-test. + // assume it's not-null. return self.platform.?.inner.runIdleTasks(self.isolate, 1); } From 9d1dc977662da6fe2d960c0d826ca3f5c016f4d8 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Wed, 2 Jul 2025 16:17:17 -0700 Subject: [PATCH 8/8] remove useless debug log --- src/browser/browser.zig | 1 - 1 file changed, 1 deletion(-) diff --git a/src/browser/browser.zig b/src/browser/browser.zig index d132a860..23f2bdd6 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -101,7 +101,6 @@ pub const Browser = struct { } pub fn runMessageLoop(self: *const Browser) void { - log.debug(.browser, "pumpMessageLoop", .{}); while (self.env.pumpMessageLoop()) { log.debug(.browser, "pumpMessageLoop", .{}); }