Skip to content

Commit b12eef2

Browse files
Merge pull request #422 from karlseguin/cdp_struct
Refactor CDP
2 parents be12b72 + bc45608 commit b12eef2

20 files changed

+1387
-2360
lines changed

src/browser/browser.zig

Lines changed: 106 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
const std = @import("std");
2020
const builtin = @import("builtin");
2121

22+
const Allocator = std.mem.Allocator;
23+
2224
const Types = @import("root").Types;
2325

2426
const parser = @import("netsurf");
@@ -57,30 +59,44 @@ pub const user_agent = "Lightpanda/1.0";
5759
// A browser contains only one session.
5860
// TODO allow multiple sessions per browser.
5961
pub const Browser = struct {
60-
session: Session = undefined,
61-
agent: []const u8 = user_agent,
62+
loop: *Loop,
63+
session: ?*Session,
64+
allocator: Allocator,
65+
session_pool: SessionPool,
6266

63-
const uri = "about:blank";
67+
const SessionPool = std.heap.MemoryPool(Session);
6468

65-
pub fn init(self: *Browser, alloc: std.mem.Allocator, loop: *Loop, vm: jsruntime.VM) !void {
66-
// We want to ensure the caller initialised a VM, but the browser
67-
// doesn't use it directly...
68-
_ = vm;
69+
const uri = "about:blank";
6970

70-
try Session.init(&self.session, alloc, loop, uri);
71+
pub fn init(allocator: Allocator, loop: *Loop) Browser {
72+
return .{
73+
.loop = loop,
74+
.session = null,
75+
.allocator = allocator,
76+
.session_pool = SessionPool.init(allocator),
77+
};
7178
}
7279

7380
pub fn deinit(self: *Browser) void {
74-
self.session.deinit();
81+
self.closeSession();
82+
self.session_pool.deinit();
7583
}
7684

77-
pub fn newSession(
78-
self: *Browser,
79-
alloc: std.mem.Allocator,
80-
loop: *jsruntime.Loop,
81-
) !void {
82-
self.session.deinit();
83-
try Session.init(&self.session, alloc, loop, uri);
85+
pub fn newSession(self: *Browser, ctx: anytype) !*Session {
86+
self.closeSession();
87+
88+
const session = try self.session_pool.create();
89+
try Session.init(session, self.allocator, ctx, self.loop, uri);
90+
self.session = session;
91+
return session;
92+
}
93+
94+
fn closeSession(self: *Browser) void {
95+
if (self.session) |session| {
96+
session.deinit();
97+
self.session_pool.destroy(session);
98+
self.session = null;
99+
}
84100
}
85101

86102
pub fn currentPage(self: *Browser) ?*Page {
@@ -96,7 +112,7 @@ pub const Browser = struct {
96112
// deinit a page before running another one.
97113
pub const Session = struct {
98114
// allocator used to init the arena.
99-
alloc: std.mem.Allocator,
115+
allocator: Allocator,
100116

101117
// The arena is used only to bound the js env init b/c it leaks memory.
102118
// see https://github.com/lightpanda-io/jsruntime-lib/issues/181
@@ -109,8 +125,9 @@ pub const Session = struct {
109125

110126
// TODO handle proxy
111127
loader: Loader,
112-
env: Env = undefined,
113-
inspector: ?jsruntime.Inspector = null,
128+
129+
env: Env,
130+
inspector: jsruntime.Inspector,
114131

115132
window: Window,
116133

@@ -121,20 +138,54 @@ pub const Session = struct {
121138

122139
jstypes: [Types.len]usize = undefined,
123140

124-
fn init(self: *Session, alloc: std.mem.Allocator, loop: *Loop, uri: []const u8) !void {
125-
self.* = Session{
141+
fn init(self: *Session, allocator: Allocator, ctx: anytype, loop: *Loop, uri: []const u8) !void {
142+
self.* = .{
126143
.uri = uri,
127-
.alloc = alloc,
128-
.arena = std.heap.ArenaAllocator.init(alloc),
144+
.env = undefined,
145+
.inspector = undefined,
146+
.allocator = allocator,
147+
.loader = Loader.init(allocator),
148+
.httpClient = .{ .allocator = allocator },
149+
.storageShed = storage.Shed.init(allocator),
150+
.arena = std.heap.ArenaAllocator.init(allocator),
129151
.window = Window.create(null, .{ .agent = user_agent }),
130-
.loader = Loader.init(alloc),
131-
.storageShed = storage.Shed.init(alloc),
132-
.httpClient = undefined,
133152
};
134153

135-
Env.init(&self.env, self.arena.allocator(), loop, null);
136-
self.httpClient = .{ .allocator = alloc };
154+
const arena = self.arena.allocator();
155+
156+
Env.init(&self.env, arena, loop, null);
157+
errdefer self.env.deinit();
137158
try self.env.load(&self.jstypes);
159+
160+
const ContextT = @TypeOf(ctx);
161+
const InspectorContainer = switch (@typeInfo(ContextT)) {
162+
.Struct => ContextT,
163+
.Pointer => |ptr| ptr.child,
164+
.Void => NoopInspector,
165+
else => @compileError("invalid context type"),
166+
};
167+
168+
// const ctx_opaque = @as(*anyopaque, @ptrCast(ctx));
169+
self.inspector = try jsruntime.Inspector.init(
170+
arena,
171+
self.env,
172+
if (@TypeOf(ctx) == void) @constCast(@ptrCast(&{})) else ctx,
173+
InspectorContainer.onInspectorResponse,
174+
InspectorContainer.onInspectorEvent,
175+
);
176+
self.env.setInspector(self.inspector);
177+
}
178+
179+
fn deinit(self: *Session) void {
180+
if (self.page) |*p| {
181+
p.deinit();
182+
}
183+
184+
self.env.deinit();
185+
self.arena.deinit();
186+
self.httpClient.deinit();
187+
self.loader.deinit();
188+
self.storageShed.deinit();
138189
}
139190

140191
fn fetchModule(ctx: *anyopaque, referrer: ?jsruntime.Module, specifier: []const u8) !jsruntime.Module {
@@ -152,49 +203,21 @@ pub const Session = struct {
152203
return self.env.compileModule(body, specifier);
153204
}
154205

155-
fn deinit(self: *Session) void {
156-
if (self.page) |*p| p.deinit();
157-
158-
if (self.inspector) |inspector| {
159-
inspector.deinit(self.alloc);
160-
}
161-
162-
self.env.deinit();
163-
self.arena.deinit();
164-
165-
self.httpClient.deinit();
166-
self.loader.deinit();
167-
self.storageShed.deinit();
168-
}
169-
170-
pub fn initInspector(
171-
self: *Session,
172-
ctx: anytype,
173-
onResp: jsruntime.InspectorOnResponseFn,
174-
onEvent: jsruntime.InspectorOnEventFn,
175-
) !void {
176-
const ctx_opaque = @as(*anyopaque, @ptrCast(ctx));
177-
self.inspector = try jsruntime.Inspector.init(self.alloc, self.env, ctx_opaque, onResp, onEvent);
178-
self.env.setInspector(self.inspector.?);
179-
}
180-
181206
pub fn callInspector(self: *Session, msg: []const u8) void {
182-
if (self.inspector) |inspector| {
183-
inspector.send(msg, self.env);
184-
} else {
185-
@panic("No Inspector");
186-
}
207+
self.inspector.send(self.env, msg);
187208
}
188209

189210
// NOTE: the caller is not the owner of the returned value,
190211
// the pointer on Page is just returned as a convenience
191212
pub fn createPage(self: *Session) !*Page {
192213
if (self.page != null) return error.SessionPageExists;
193-
const p: Page = undefined;
194-
self.page = p;
195-
Page.init(&self.page.?, self.alloc, self);
214+
self.page = Page.init(self.allocator, self);
196215
return &self.page.?;
197216
}
217+
218+
pub fn currentPage(self: *Session) ?*Page {
219+
return &(self.page orelse return null);
220+
}
198221
};
199222

200223
// Page navigates to an url.
@@ -203,8 +226,8 @@ pub const Session = struct {
203226
// The page handle all its memory in an arena allocator. The arena is reseted
204227
// when end() is called.
205228
pub const Page = struct {
206-
arena: std.heap.ArenaAllocator,
207229
session: *Session,
230+
arena: std.heap.ArenaAllocator,
208231
doc: ?*parser.Document = null,
209232

210233
// handle url
@@ -218,17 +241,19 @@ pub const Page = struct {
218241

219242
raw_data: ?[]const u8 = null,
220243

221-
fn init(
222-
self: *Page,
223-
alloc: std.mem.Allocator,
224-
session: *Session,
225-
) void {
226-
self.* = .{
227-
.arena = std.heap.ArenaAllocator.init(alloc),
244+
fn init(allocator: Allocator, session: *Session) Page {
245+
return .{
228246
.session = session,
247+
.arena = std.heap.ArenaAllocator.init(allocator),
229248
};
230249
}
231250

251+
pub fn deinit(self: *Page) void {
252+
self.end();
253+
self.arena.deinit();
254+
self.session.page = null;
255+
}
256+
232257
// start js env.
233258
// - auxData: extra data forwarded to the Inspector
234259
// see Inspector.contextCreated
@@ -253,18 +278,15 @@ pub const Page = struct {
253278
try polyfill.load(self.arena.allocator(), self.session.env);
254279

255280
// inspector
256-
if (self.session.inspector) |inspector| {
257-
log.debug("inspector context created", .{});
258-
inspector.contextCreated(self.session.env, "", self.origin orelse "://", auxData);
259-
}
281+
log.debug("inspector context created", .{});
282+
self.session.inspector.contextCreated(self.session.env, "", self.origin orelse "://", auxData);
260283
}
261284

262285
// reset js env and mem arena.
263286
pub fn end(self: *Page) void {
264287
self.session.env.stop();
265288
// TODO unload document: https://html.spec.whatwg.org/#unloading-documents
266289

267-
if (self.url) |*u| u.deinit(self.arena.allocator());
268290
self.url = null;
269291
self.location.url = null;
270292
self.session.window.replaceLocation(&self.location) catch |e| {
@@ -278,14 +300,8 @@ pub const Page = struct {
278300
_ = self.arena.reset(.free_all);
279301
}
280302

281-
pub fn deinit(self: *Page) void {
282-
self.end();
283-
self.arena.deinit();
284-
self.session.page = null;
285-
}
286-
287303
// dump writes the page content into the given file.
288-
pub fn dump(self: *Page, out: std.fs.File) !void {
304+
pub fn dump(self: *const Page, out: std.fs.File) !void {
289305

290306
// if no HTML document pointer available, dump the data content only.
291307
if (self.doc == null) {
@@ -333,11 +349,9 @@ pub const Page = struct {
333349
}
334350

335351
// own the url
336-
if (self.rawuri) |prev| alloc.free(prev);
337352
self.rawuri = try alloc.dupe(u8, uri);
338353
self.uri = std.Uri.parse(self.rawuri.?) catch try std.Uri.parseAfterScheme("", self.rawuri.?);
339354

340-
if (self.url) |*prev| prev.deinit(alloc);
341355
self.url = try URL.constructor(alloc, self.rawuri.?, null);
342356
self.location.url = &self.url.?;
343357
try self.session.window.replaceLocation(&self.location);
@@ -435,9 +449,7 @@ pub const Page = struct {
435449
// https://html.spec.whatwg.org/#read-html
436450

437451
// inspector
438-
if (self.session.inspector) |inspector| {
439-
inspector.contextCreated(self.session.env, "", self.origin.?, auxData);
440-
}
452+
self.session.inspector.contextCreated(self.session.env, "", self.origin.?, auxData);
441453

442454
// replace the user context document with the new one.
443455
try self.session.env.setUserContext(.{
@@ -596,7 +608,7 @@ pub const Page = struct {
596608
};
597609

598610
// the caller owns the returned string
599-
fn fetchData(self: *Page, alloc: std.mem.Allocator, src: []const u8) ![]const u8 {
611+
fn fetchData(self: *Page, alloc: Allocator, src: []const u8) ![]const u8 {
600612
log.debug("starting fetch {s}", .{src});
601613

602614
var buffer: [1024]u8 = undefined;
@@ -671,7 +683,7 @@ pub const Page = struct {
671683
return .unknown;
672684
}
673685

674-
fn eval(self: Script, alloc: std.mem.Allocator, env: Env, body: []const u8) !void {
686+
fn eval(self: Script, alloc: Allocator, env: Env, body: []const u8) !void {
675687
var try_catch: jsruntime.TryCatch = undefined;
676688
try_catch.init(env);
677689
defer try_catch.deinit();
@@ -696,3 +708,8 @@ pub const Page = struct {
696708
}
697709
};
698710
};
711+
712+
const NoopInspector = struct {
713+
pub fn onInspectorResponse(_: *anyopaque, _: u32, _: []const u8) void {}
714+
pub fn onInspectorEvent(_: *anyopaque, _: []const u8) void {}
715+
};

0 commit comments

Comments
 (0)