Merge pull request #979 from lightpanda-io/app_owns_platform

App owns platform
This commit is contained in:
Karl Seguin
2025-08-29 10:33:55 +08:00
committed by GitHub
10 changed files with 71 additions and 77 deletions

View File

@@ -14,7 +14,7 @@ const Notification = @import("notification.zig").Notification;
pub const App = struct { pub const App = struct {
http: Http, http: Http,
config: Config, config: Config,
platform: ?*const Platform, platform: Platform,
allocator: Allocator, allocator: Allocator,
telemetry: Telemetry, telemetry: Telemetry,
app_dir_path: ?[]const u8, app_dir_path: ?[]const u8,
@@ -29,7 +29,6 @@ pub const App = struct {
pub const Config = struct { pub const Config = struct {
run_mode: RunMode, run_mode: RunMode,
platform: ?*const Platform = null,
tls_verify_host: bool = true, tls_verify_host: bool = true,
http_proxy: ?[:0]const u8 = null, http_proxy: ?[:0]const u8 = null,
proxy_bearer_token: ?[:0]const u8 = null, proxy_bearer_token: ?[:0]const u8 = null,
@@ -57,13 +56,16 @@ pub const App = struct {
}); });
errdefer http.deinit(); errdefer http.deinit();
const platform = try Platform.init();
errdefer platform.deinit();
const app_dir_path = getAndMakeAppDir(allocator); const app_dir_path = getAndMakeAppDir(allocator);
app.* = .{ app.* = .{
.http = http, .http = http,
.allocator = allocator, .allocator = allocator,
.telemetry = undefined, .telemetry = undefined,
.platform = config.platform, .platform = platform,
.app_dir_path = app_dir_path, .app_dir_path = app_dir_path,
.notification = notification, .notification = notification,
.config = config, .config = config,
@@ -85,6 +87,7 @@ pub const App = struct {
self.telemetry.deinit(); self.telemetry.deinit();
self.notification.deinit(); self.notification.deinit();
self.http.deinit(); self.http.deinit();
self.platform.deinit();
allocator.destroy(self); allocator.destroy(self);
} }
}; };

View File

@@ -48,7 +48,7 @@ pub const Browser = struct {
pub fn init(app: *App) !Browser { pub fn init(app: *App) !Browser {
const allocator = app.allocator; const allocator = app.allocator;
const env = try Env.init(allocator, app.platform, .{}); const env = try Env.init(allocator, &app.platform, .{});
errdefer env.deinit(); errdefer env.deinit();
const notification = try Notification.init(allocator, app.notification); const notification = try Notification.init(allocator, app.notification);

View File

@@ -1347,7 +1347,7 @@ test "Browser.HTML.Element.DataSet" {
test "Browser.HTML.HtmlInputElement.properties" { test "Browser.HTML.HtmlInputElement.properties" {
var runner = try testing.jsRunner(testing.tracking_allocator, .{ .url = "https://lightpanda.io/noslashattheend" }); var runner = try testing.jsRunner(testing.tracking_allocator, .{ .url = "https://lightpanda.io/noslashattheend" });
defer runner.deinit(); defer runner.deinit();
var alloc = std.heap.ArenaAllocator.init(runner.app.allocator); var alloc = std.heap.ArenaAllocator.init(runner.allocator);
defer alloc.deinit(); defer alloc.deinit();
const arena = alloc.allocator(); const arena = alloc.allocator();

View File

@@ -71,7 +71,6 @@ const TestCDP = main.CDPT(struct {
}); });
const TestContext = struct { const TestContext = struct {
app: *App,
client: ?Client = null, client: ?Client = null,
cdp_: ?TestCDP = null, cdp_: ?TestCDP = null,
arena: ArenaAllocator, arena: ArenaAllocator,
@@ -80,7 +79,6 @@ const TestContext = struct {
if (self.cdp_) |*c| { if (self.cdp_) |*c| {
c.deinit(); c.deinit();
} }
self.app.deinit();
self.arena.deinit(); self.arena.deinit();
} }
@@ -89,7 +87,7 @@ const TestContext = struct {
self.client = Client.init(self.arena.allocator()); self.client = Client.init(self.arena.allocator());
// Don't use the arena here. We want to detect leaks in CDP. // Don't use the arena here. We want to detect leaks in CDP.
// The arena is only for test-specific stuff // The arena is only for test-specific stuff
self.cdp_ = TestCDP.init(self.app, &self.client.?) catch unreachable; self.cdp_ = TestCDP.init(base.test_app, &self.client.?) catch unreachable;
} }
return &self.cdp_.?; return &self.cdp_.?;
} }
@@ -221,7 +219,6 @@ const TestContext = struct {
pub fn context() TestContext { pub fn context() TestContext {
return .{ return .{
.app = App.init(std.testing.allocator, .{ .run_mode = .serve }) catch unreachable,
.arena = ArenaAllocator.init(std.testing.allocator), .arena = ArenaAllocator.init(std.testing.allocator),
}; };
} }

View File

@@ -22,9 +22,8 @@ const Allocator = std.mem.Allocator;
const log = @import("log.zig"); const log = @import("log.zig");
const App = @import("app.zig").App; const App = @import("app.zig").App;
const Server = @import("server.zig").Server;
const Http = @import("http/Http.zig"); const Http = @import("http/Http.zig");
const Platform = @import("runtime/js.zig").Platform; const Server = @import("server.zig").Server;
const Browser = @import("browser/browser.zig").Browser; const Browser = @import("browser/browser.zig").Browser;
const build_config = @import("build_config"); const build_config = @import("build_config");
@@ -109,13 +108,9 @@ fn run(alloc: Allocator) !void {
log.opts.filter_scopes = lfs; log.opts.filter_scopes = lfs;
} }
const platform = try Platform.init();
defer platform.deinit();
// _app is global to handle graceful shutdown. // _app is global to handle graceful shutdown.
_app = try App.init(alloc, .{ _app = try App.init(alloc, .{
.run_mode = args.mode, .run_mode = args.mode,
.platform = &platform,
.http_proxy = args.httpProxy(), .http_proxy = args.httpProxy(),
.proxy_bearer_token = args.proxyBearerToken(), .proxy_bearer_token = args.proxyBearerToken(),
.tls_verify_host = args.tlsVerifyHost(), .tls_verify_host = args.tlsVerifyHost(),
@@ -124,6 +119,7 @@ fn run(alloc: Allocator) !void {
.http_max_host_open = args.httpMaxHostOpen(), .http_max_host_open = args.httpMaxHostOpen(),
.http_max_concurrent = args.httpMaxConcurrent(), .http_max_concurrent = args.httpMaxConcurrent(),
}); });
const app = _app.?; const app = _app.?;
defer app.deinit(); defer app.deinit();
app.telemetry.record(.{ .run = {} }); app.telemetry.record(.{ .run = {} });
@@ -715,48 +711,50 @@ fn parseCommonArg(
return false; return false;
} }
const testing = @import("testing.zig");
test { test {
std.testing.refAllDecls(@This()); std.testing.refAllDecls(@This());
} }
var test_wg: std.Thread.WaitGroup = .{}; var test_cdp_server: ?Server = null;
test "tests:beforeAll" { test "tests:beforeAll" {
try parser.init();
log.opts.level = .err; log.opts.level = .err;
log.opts.format = .logfmt; log.opts.format = .logfmt;
test_wg.startMany(2); try testing.setup();
const platform = try Platform.init();
var wg: std.Thread.WaitGroup = .{};
wg.startMany(2);
{ {
const address = try std.net.Address.parseIp("127.0.0.1", 9582); const thread = try std.Thread.spawn(.{}, serveHTTP, .{&wg});
const thread = try std.Thread.spawn(.{}, serveHTTP, .{address});
thread.detach(); thread.detach();
} }
{ {
const address = try std.net.Address.parseIp("127.0.0.1", 9583); const thread = try std.Thread.spawn(.{}, serveCDP, .{&wg});
const thread = try std.Thread.spawn(.{}, serveCDP, .{ address, &platform });
thread.detach(); thread.detach();
} }
// need to wait for the servers to be listening, else tests will fail because // need to wait for the servers to be listening, else tests will fail because
// they aren't able to connect. // they aren't able to connect.
test_wg.wait(); wg.wait();
} }
test "tests:afterAll" { test "tests:afterAll" {
parser.deinit(); if (test_cdp_server) |*server| {
server.deinit();
}
testing.shutdown();
} }
fn serveHTTP(address: std.net.Address) !void { fn serveHTTP(wg: *std.Thread.WaitGroup) !void {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator); const address = try std.net.Address.parseIp("127.0.0.1", 9582);
defer arena.deinit();
var listener = try address.listen(.{ .reuse_address = true }); var listener = try address.listen(.{ .reuse_address = true });
defer listener.deinit(); defer listener.deinit();
test_wg.finish(); wg.finish();
var read_buffer: [1024]u8 = undefined; var read_buffer: [1024]u8 = undefined;
while (true) { while (true) {
@@ -799,20 +797,15 @@ fn serveHTTP(address: std.net.Address) !void {
} }
} }
fn serveCDP(address: std.net.Address, platform: *const Platform) !void { fn serveCDP(wg: *std.Thread.WaitGroup) !void {
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; const address = try std.net.Address.parseIp("127.0.0.1", 9583);
var app = try App.init(gpa.allocator(), .{ test_cdp_server = try Server.init(testing.test_app, address);
.run_mode = .serve,
.tls_verify_host = false,
.platform = platform,
.http_max_concurrent = 2,
});
defer app.deinit();
test_wg.finish(); var server = try Server.init(testing.test_app, address);
var server = try Server.init(app, address);
defer server.deinit(); defer server.deinit();
server.run(address, 5) catch |err| { wg.finish();
test_cdp_server.?.run(address, 5) catch |err| {
std.debug.print("CDP server error: {}", .{err}); std.debug.print("CDP server error: {}", .{err});
return err; return err;
}; };

View File

@@ -48,8 +48,8 @@ pub fn main() !void {
const cmd = try parseArgs(runner_arena); const cmd = try parseArgs(runner_arena);
const platform = try Platform.init(); try @import("testing.zig").setup();
defer platform.deinit(); defer @import("testing.zig").shutdown();
// prepare libraries to load on each test case. // prepare libraries to load on each test case.
var loader = FileLoader.init(runner_arena, WPT_DIR); var loader = FileLoader.init(runner_arena, WPT_DIR);
@@ -69,7 +69,6 @@ pub fn main() !void {
var err_out: ?[]const u8 = null; var err_out: ?[]const u8 = null;
const result = run( const result = run(
test_arena.allocator(), test_arena.allocator(),
&platform,
test_file, test_file,
&loader, &loader,
&err_out, &err_out,
@@ -81,12 +80,11 @@ pub fn main() !void {
}; };
if (result == null and err_out == null) { if (result == null and err_out == null) {
// We somtimes pass a non-test to `run` (we don't know it's a non // We sometimes pass a non-test to `run` (we don't know it's a non
// test, we need to open the contents of the test file to find out // test, we need to open the contents of the test file to find out
// and that's in run). // and that's in run).
continue; continue;
} }
try writer.process(test_file, result, err_out); try writer.process(test_file, result, err_out);
} }
try writer.finalize(); try writer.finalize();
@@ -94,7 +92,6 @@ pub fn main() !void {
fn run( fn run(
arena: Allocator, arena: Allocator,
platform: *const Platform,
test_file: []const u8, test_file: []const u8,
loader: *FileLoader, loader: *FileLoader,
err_out: *?[]const u8, err_out: *?[]const u8,
@@ -119,10 +116,15 @@ fn run(
var runner = try @import("testing.zig").jsRunner(arena, .{ var runner = try @import("testing.zig").jsRunner(arena, .{
.url = "http://127.0.0.1", .url = "http://127.0.0.1",
.html = html, .html = html,
.platform = platform,
}); });
defer runner.deinit(); defer runner.deinit();
defer if (err_out.*) |eo| {
// the error might be owned by the runner, we'll dupe it with our
// own arena so that it can be returned out of this function.
err_out.* = arena.dupe(u8, eo) catch "failed to dupe error";
};
try polyfill.preload(arena, runner.page.main_context); try polyfill.preload(arena, runner.page.main_context);
// loop over the scripts. // loop over the scripts.
@@ -179,6 +181,7 @@ fn run(
// return the detailed result. // return the detailed result.
const res = try runner.eval("report.log", "report", err_out); const res = try runner.eval("report.log", "report", err_out);
return try res.toString(arena); return try res.toString(arena);
} }

View File

@@ -153,7 +153,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
return struct { return struct {
allocator: Allocator, allocator: Allocator,
platform: ?*const Platform, platform: *const Platform,
// the global isolate // the global isolate
isolate: v8.Isolate, isolate: v8.Isolate,
@@ -182,7 +182,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
const Opts = struct {}; 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 = v8.initCreateParams();
var params = try allocator.create(v8.CreateParams); var params = try allocator.create(v8.CreateParams);
errdefer allocator.destroy(params); errdefer allocator.destroy(params);
@@ -301,13 +301,11 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
} }
pub fn pumpMessageLoop(self: *const Self) bool { pub fn pumpMessageLoop(self: *const Self) bool {
// assume it's not-null. return self.platform.inner.pumpMessageLoop(self.isolate, false);
return self.platform.?.inner.pumpMessageLoop(self.isolate, false);
} }
pub fn runIdleTasks(self: *const Self) void { pub fn runIdleTasks(self: *const Self) void {
// assume it's not-null. return self.platform.inner.runIdleTasks(self.isolate, 1);
return self.platform.?.inner.runIdleTasks(self.isolate, 1);
} }
pub fn newExecutionWorld(self: *Self) !ExecutionWorld { pub fn newExecutionWorld(self: *Self) !ExecutionWorld {

View File

@@ -18,6 +18,7 @@
const std = @import("std"); const std = @import("std");
const js = @import("js.zig"); const js = @import("js.zig");
const base = @import("../testing.zig");
const generate = @import("generate.zig"); const generate = @import("generate.zig");
pub const allocator = std.testing.allocator; pub const allocator = std.testing.allocator;
@@ -42,7 +43,7 @@ pub fn Runner(comptime State: type, comptime Global: type, comptime types: anyty
const self = try allocator.create(Self); const self = try allocator.create(Self);
errdefer allocator.destroy(self); errdefer allocator.destroy(self);
self.env = try Env.init(allocator, null, .{}); self.env = try Env.init(allocator, &base.test_app.platform, .{});
errdefer self.env.deinit(); errdefer self.env.deinit();
self.executor = try self.env.newExecutionWorld(); self.executor = try self.env.newExecutionWorld();

View File

@@ -186,10 +186,7 @@ test "telemetry: getOrCreateId" {
} }
test "telemetry: sends event to provider" { test "telemetry: sends event to provider" {
var app = testing.createApp(.{}); var telemetry = try TelemetryT(MockProvider).init(testing.test_app, .serve);
defer app.deinit();
var telemetry = try TelemetryT(MockProvider).init(app, .serve);
defer telemetry.deinit(); defer telemetry.deinit();
const mock = &telemetry.provider; const mock = &telemetry.provider;

View File

@@ -175,11 +175,6 @@ pub fn print(comptime fmt: []const u8, args: anytype) void {
} }
} }
// dummy opts incase we want to add something, and not have to break all the callers
pub fn createApp(_: anytype) *App {
return App.init(allocator, .{ .run_mode = .serve }) catch unreachable;
}
pub const Random = struct { pub const Random = struct {
var instance: ?std.Random.DefaultPrng = null; var instance: ?std.Random.DefaultPrng = null;
@@ -375,24 +370,17 @@ pub const JsRunner = struct {
const Page = @import("browser/page.zig").Page; const Page = @import("browser/page.zig").Page;
const Browser = @import("browser/browser.zig").Browser; const Browser = @import("browser/browser.zig").Browser;
app: *App,
page: *Page, page: *Page,
browser: *Browser, browser: *Browser,
allocator: Allocator,
fn init(alloc: Allocator, opts: RunnerOpts) !JsRunner { fn init(alloc: Allocator, opts: RunnerOpts) !JsRunner {
parser.deinit(); parser.deinit();
var app = try App.init(alloc, .{
.run_mode = .serve,
.tls_verify_host = false,
.platform = opts.platform,
});
errdefer app.deinit();
const browser = try alloc.create(Browser); const browser = try alloc.create(Browser);
errdefer alloc.destroy(browser); errdefer alloc.destroy(browser);
browser.* = try Browser.init(app); browser.* = try Browser.init(test_app);
errdefer browser.deinit(); errdefer browser.deinit();
var session = try browser.newSession(); var session = try browser.newSession();
@@ -411,16 +399,15 @@ pub const JsRunner = struct {
page.mode = .{ .parsed = {} }; page.mode = .{ .parsed = {} };
return .{ return .{
.app = app,
.page = page, .page = page,
.browser = browser, .browser = browser,
.allocator = alloc,
}; };
} }
pub fn deinit(self: *JsRunner) void { pub fn deinit(self: *JsRunner) void {
self.browser.deinit(); self.browser.deinit();
self.app.allocator.destroy(self.browser); self.allocator.destroy(self.browser);
self.app.deinit();
} }
const RunOpts = struct {}; const RunOpts = struct {};
@@ -484,7 +471,6 @@ pub const JsRunner = struct {
}; };
const RunnerOpts = struct { const RunnerOpts = struct {
platform: ?*const Platform = null,
url: []const u8 = "https://lightpanda.io/opensource-browser/", url: []const u8 = "https://lightpanda.io/opensource-browser/",
html: []const u8 = html: []const u8 =
\\ <div id="content"> \\ <div id="content">
@@ -502,3 +488,19 @@ const RunnerOpts = struct {
pub fn jsRunner(alloc: Allocator, opts: RunnerOpts) !JsRunner { pub fn jsRunner(alloc: Allocator, opts: RunnerOpts) !JsRunner {
return JsRunner.init(alloc, opts); return JsRunner.init(alloc, opts);
} }
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
pub var test_app: *App = undefined;
pub fn setup() !void {
try parser.init();
test_app = try App.init(gpa.allocator(), .{
.run_mode = .serve,
.tls_verify_host = false,
});
}
pub fn shutdown() void {
parser.deinit();
test_app.deinit();
}