From 1fca035cfe3d5458fe9412723f2e0fefb6aa41cc Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Thu, 24 Apr 2025 18:06:55 +0800 Subject: [PATCH 1/2] Make CDP less generic. It's still generic over the client - we need to assert messages written to and be able to send specific commands, but it's no longer generic over Browser/ Session/Page/etc.. --- src/browser/browser.zig | 2 +- src/cdp/cdp.zig | 11 +- src/cdp/domains/target.zig | 2 +- src/cdp/testing.zig | 211 ++----------------------------------- 4 files changed, 18 insertions(+), 208 deletions(-) diff --git a/src/browser/browser.zig b/src/browser/browser.zig index c5f96b36..74ed2516 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -97,7 +97,7 @@ pub const Browser = struct { return session; } - fn closeSession(self: *Browser) void { + pub fn closeSession(self: *Browser) void { if (self.session) |session| { session.deinit(); self.session_pool.destroy(session); diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index ed297355..a318bdd8 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -22,6 +22,8 @@ const json = std.json; const App = @import("../app.zig").App; const asUint = @import("../str/parser.zig").asUint; +const Browser = @import("../browser/browser.zig").Browser; +const Session = @import("../browser/browser.zig").Session; const Incrementing = @import("../id.zig").Incrementing; const Notification = @import("../notification.zig").Notification; @@ -32,8 +34,6 @@ pub const LOADER_ID = "LOADERID24DD2FD56CF1EF33C965C79C"; pub const CDP = CDPT(struct { const Client = *@import("../server.zig").Client; - const Browser = @import("../browser/browser.zig").Browser; - const Session = @import("../browser/browser.zig").Session; }); const SessionIdGen = Incrementing(u32, "SID"); @@ -69,8 +69,6 @@ pub fn CDPT(comptime TypeProvider: type) type { message_arena: std.heap.ArenaAllocator, const Self = @This(); - pub const Browser = TypeProvider.Browser; - pub const Session = TypeProvider.Session; pub fn init(app: *App, client: TypeProvider.Client) !Self { const allocator = app.allocator; @@ -247,6 +245,7 @@ pub fn CDPT(comptime TypeProvider: type) type { return false; } bc.deinit(); + self.browser.closeSession(); self.browser_context_pool.destroy(bc); self.browser_context = null; return true; @@ -282,7 +281,7 @@ pub fn BrowserContext(comptime CDP_T: type) type { // all intents and purpose, from CDP's point of view our Browser and // our Session more or less maps to a BrowserContext. THIS HAS ZERO // RELATION TO SESSION_ID - session: *CDP_T.Session, + session: *Session, // Points to the session arena arena: Allocator, @@ -309,7 +308,7 @@ pub fn BrowserContext(comptime CDP_T: type) type { node_registry: Node.Registry, node_search_list: Node.Search.List, - isolated_world: ?IsolatedWorld(CDP_T.Browser.EnvType), + isolated_world: ?IsolatedWorld(Browser.EnvType), const Self = @This(); diff --git a/src/cdp/domains/target.zig b/src/cdp/domains/target.zig index 6c4fd442..9380f9b5 100644 --- a/src/cdp/domains/target.zig +++ b/src/cdp/domains/target.zig @@ -522,7 +522,7 @@ test "cdp.target: createTarget" { try testing.expectEqual(true, bc.target_id != null); try testing.expectEqual( \\{"isDefault":true,"type":"default","frameId":"TID-1"} - , bc.session.page.?.aux_data); + , bc.session.aux_data); try ctx.expectSentResult(.{ .targetId = bc.target_id.? }, .{ .id = 10 }); try ctx.expectSentEvent("Target.targetCreated", .{ .targetInfo = .{ .url = "about:blank", .title = "about:blank", .attached = false, .type = "page", .canAccessOpener = false, .browserContextId = "BID-9", .targetId = bc.target_id.? } }, .{}); diff --git a/src/cdp/testing.zig b/src/cdp/testing.zig index 4dd9c4de..9ca7fc20 100644 --- a/src/cdp/testing.zig +++ b/src/cdp/testing.zig @@ -19,6 +19,7 @@ const std = @import("std"); const json = std.json; const Allocator = std.mem.Allocator; +const ArenaAllocator = std.heap.ArenaAllocator; const Testing = @This(); @@ -36,196 +37,6 @@ pub const expectEqualSlices = base.expectEqualSlices; pub const Document = @import("../testing.zig").Document; -const Browser = struct { - session: ?*Session = null, - arena: std.heap.ArenaAllocator, - env: Env, - pub const EnvType = Env; - - pub fn init(app: *App) !Browser { - return .{ - .arena = std.heap.ArenaAllocator.init(app.allocator), - .env = Env{}, - }; - } - - pub fn deinit(self: *Browser) void { - self.arena.deinit(); - } - - pub fn newSession(self: *Browser, ctx: anytype) !*Session { - _ = ctx; - if (self.session != null) { - return error.MockBrowserSessionAlreadyExists; - } - const arena = self.arena.allocator(); - const executor = arena.create(Env.Executor) catch unreachable; - self.session = try arena.create(Session); - self.session.?.* = .{ - .page = null, - .arena = self.arena, - .executor = executor, - .inspector = .{}, - .state = 0, - }; - return self.session.?; - } - - pub fn hasSession(self: *const Browser, session_id: []const u8) bool { - const session = self.session orelse return false; - return std.mem.eql(u8, session.id, session_id); - } - - pub fn runMicrotasks(_: *const Browser) void {} -}; - -const Session = struct { - page: ?Page = null, - arena: std.heap.ArenaAllocator, - executor: *Env.Executor, - inspector: Inspector, - state: i32, - - pub fn currentPage(self: *Session) ?*Page { - return &(self.page orelse return null); - } - - pub fn createPage(self: *Session, aux_data: ?[]const u8) !*Page { - if (self.page != null) { - return error.MockBrowserPageAlreadyExists; - } - self.page = .{ - .session = self, - .url = URL.parse("https://lightpanda.io/", null) catch unreachable, - .aux_data = try self.arena.allocator().dupe(u8, aux_data orelse ""), - }; - return &self.page.?; - } - - pub fn removePage(self: *Session) void { - self.page = null; - } - - pub fn callInspector(self: *Session, msg: []const u8) void { - _ = self; - _ = msg; - } -}; - -const Env = struct { - pub const Executor = MockExecutor; - pub fn startExecutor(self: *Env, comptime Global: type, state: anytype, module_loader: anytype, kind: anytype) !*Executor { - _ = self; - _ = Global; - _ = state; - _ = module_loader; - _ = kind; - return error.MockExecutor; - } - pub fn stopExecutor(self: *Env, executor: *Executor) void { - _ = self; - _ = executor; - } -}; -const MockExecutor = struct { - context: Context, - - pub fn startScope(self: *MockExecutor, global: anytype) !void { - _ = self; - _ = global; - } - pub fn endScope(self: *MockExecutor) void { - _ = self; - } -}; -const Context = struct { - pub fn debugContextId(self: Context) i32 { - _ = self; - return 0; - } -}; - -const Inspector = struct { - pub fn getRemoteObject( - self: *const Inspector, - executor: *Env.Executor, - group: []const u8, - value: anytype, - ) !RemoteObject { - _ = self; - _ = executor; - _ = group; - _ = value; - return RemoteObject{}; - } - pub fn getNodePtr(self: Inspector, alloc: std.mem.Allocator, object_id: []const u8) !?*anyopaque { - _ = self; - _ = object_id; - return try alloc.create(i32); - } - pub fn contextCreated( - self: *const Inspector, - executor: *const Env.Executor, - name: []const u8, - origin: []const u8, - aux_data: ?[]const u8, - is_default_context: bool, - ) void { - _ = self; - _ = executor; - _ = name; - _ = origin; - _ = aux_data; - _ = is_default_context; - } -}; - -const RemoteObject = struct { - pub fn deinit(self: RemoteObject) void { - _ = self; - } - pub fn getType(self: RemoteObject, alloc: std.mem.Allocator) ![:0]const u8 { - _ = self; - _ = alloc; - return "TheType"; - } - pub fn getSubtype(self: RemoteObject, alloc: std.mem.Allocator) ![:0]const u8 { - _ = self; - _ = alloc; - return "TheSubtype"; - } - pub fn getClassName(self: RemoteObject, alloc: std.mem.Allocator) ![:0]const u8 { - _ = self; - _ = alloc; - return "TheClassName"; - } - pub fn getDescription(self: RemoteObject, alloc: std.mem.Allocator) ![:0]const u8 { - _ = self; - _ = alloc; - return "TheDescription"; - } - pub fn getObjectId(self: RemoteObject, alloc: std.mem.Allocator) ![:0]const u8 { - _ = self; - _ = alloc; - return "TheObjectId"; - } -}; - -const Page = struct { - session: *Session, - url: ?URL = null, - aux_data: []const u8 = "", - doc: ?*parser.Document = null, - - pub fn navigate(_: *Page, url: URL, opts: anytype) !void { - _ = url; - _ = opts; - } - - const MouseEvent = @import("../browser/browser.zig").Page.MouseEvent; - pub fn mouseEvent(_: *Page, _: MouseEvent) !void {} -}; - const Client = struct { allocator: Allocator, sent: std.ArrayListUnmanaged(json.Value) = .{}, @@ -246,11 +57,14 @@ const Client = struct { const value = try json.parseFromSliceLeaky(json.Value, self.allocator, serialized, .{}); try self.sent.append(self.allocator, value); } + + pub fn sendJSONRaw(self: *Client, _: ArenaAllocator, buf: std.ArrayListUnmanaged(u8)) !void { + const value = try json.parseFromSliceLeaky(json.Value, self.allocator, buf.items, .{}); + try self.sent.append(self.allocator, value); + } }; const TestCDP = main.CDPT(struct { - pub const Browser = Testing.Browser; - pub const Session = Testing.Session; pub const Client = *Testing.Client; }); @@ -258,7 +72,7 @@ const TestContext = struct { app: *App, client: ?Client = null, cdp_: ?TestCDP = null, - arena: std.heap.ArenaAllocator, + arena: ArenaAllocator, pub fn deinit(self: *TestContext) void { if (self.cdp_) |*c| { @@ -273,7 +87,7 @@ const TestContext = struct { self.client = Client.init(self.arena.allocator()); // Don't use the arena here. We want to detect leaks in CDP. // The arena is only for test-specific stuff - self.cdp_ = try TestCDP.init(self.app, &self.client.?); + self.cdp_ = TestCDP.init(self.app, &self.client.?) catch unreachable; } return &self.cdp_.?; } @@ -286,11 +100,8 @@ const TestContext = struct { }; pub fn loadBrowserContext(self: *TestContext, opts: BrowserContextOpts) !*main.BrowserContext(TestCDP) { var c = self.cdp(); - c.browser.session = null; - if (c.browser_context) |bc| { - bc.deinit(); - c.browser_context = null; + _ = c.disposeBrowserContext(bc.id); } _ = try c.createBrowserContext(); @@ -410,7 +221,7 @@ const TestContext = struct { pub fn context() TestContext { return .{ .app = App.init(std.testing.allocator, .{ .run_mode = .serve }) catch unreachable, - .arena = std.heap.ArenaAllocator.init(std.testing.allocator), + .arena = ArenaAllocator.init(std.testing.allocator), }; } @@ -420,7 +231,7 @@ pub fn context() TestContext { // json and check if the two are equal. // Except serializing to JSON isn't deterministic. // So we serialize the JSON then we deserialize to json.Value. And then we can -// compare our anytype expection with the json.Value that we captured +// compare our anytype expectation with the json.Value that we captured fn compareExpectedToSent(expected: []const u8, actual: json.Value) !bool { const expected_value = try std.json.parseFromSlice(json.Value, std.testing.allocator, expected, .{}); From b0b3e92600e5e377965a25bb2e5d83667deee5b3 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Thu, 24 Apr 2025 19:48:27 +0800 Subject: [PATCH 2/2] remove Browser.EnvType --- src/browser/browser.zig | 1 - src/cdp/cdp.zig | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/browser/browser.zig b/src/browser/browser.zig index 74ed2516..7a39d2ad 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -58,7 +58,6 @@ pub const Browser = struct { http_client: *http.Client, session_pool: SessionPool, page_arena: std.heap.ArenaAllocator, - pub const EnvType = Env; const SessionPool = std.heap.MemoryPool(Session); diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index a318bdd8..dc44e22b 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -21,6 +21,7 @@ const Allocator = std.mem.Allocator; const json = std.json; const App = @import("../app.zig").App; +const Env = @import("../browser/env.zig").Env; const asUint = @import("../str/parser.zig").asUint; const Browser = @import("../browser/browser.zig").Browser; const Session = @import("../browser/browser.zig").Session; @@ -308,7 +309,7 @@ pub fn BrowserContext(comptime CDP_T: type) type { node_registry: Node.Registry, node_search_list: Node.Search.List, - isolated_world: ?IsolatedWorld(Browser.EnvType), + isolated_world: ?IsolatedWorld(Env), const Self = @This();