diff --git a/src/app.zig b/src/app.zig index 88599598..ad66f73a 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 = null, 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..23f2bdd6 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,7 +97,14 @@ pub const Browser = struct { } pub fn runMicrotasks(self: *const Browser) void { - return self.env.runMicrotasks(); + self.env.runMicrotasks(); + } + + pub fn runMessageLoop(self: *const Browser) void { + while (self.env.pumpMessageLoop()) { + log.debug(.browser, "pumpMessageLoop", .{}); + } + self.env.runIdleTasks(); } }; 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| { diff --git a/src/main.zig b/src/main.zig index f54c90fb..95cdcead 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(), @@ -602,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); @@ -618,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(); } @@ -800,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/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/runtime/js.zig b/src/runtime/js.zig index 3e471f44..80585970 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,16 @@ pub fn Env(comptime State: type, comptime WebApis: type) type { self.isolate.performMicrotasksCheckpoint(); } + pub fn pumpMessageLoop(self: *const Self) bool { + // assume it's not-null. + return self.platform.?.inner.pumpMessageLoop(self.isolate, false); + } + + pub fn runIdleTasks(self: *const Self) void { + // assume it's not-null. + return self.platform.?.inner.runIdleTasks(self.isolate, 1); + } + pub fn newExecutionWorld(self: *Self) !ExecutionWorld { return .{ .env = self, 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(); 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 = \\