diff --git a/src/browser/browser.zig b/src/browser/browser.zig index ffedfb29..57c2900f 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -62,31 +62,35 @@ pub const Browser = struct { loop: *Loop, session: ?*Session, allocator: Allocator, + http_client: HttpClient, session_pool: SessionPool, + page_arena: std.heap.ArenaAllocator, const SessionPool = std.heap.MemoryPool(Session); - const uri = "about:blank"; - pub fn init(allocator: Allocator, loop: *Loop) Browser { return .{ .loop = loop, .session = null, .allocator = allocator, + .http_client = .{ .allocator = allocator }, .session_pool = SessionPool.init(allocator), + .page_arena = std.heap.ArenaAllocator.init(allocator), }; } pub fn deinit(self: *Browser) void { self.closeSession(); + self.http_client.deinit(); self.session_pool.deinit(); + self.page_arena.deinit(); } pub fn newSession(self: *Browser, ctx: anytype) !*Session { self.closeSession(); const session = try self.session_pool.create(); - try Session.init(session, self.allocator, ctx, self.loop, uri); + try Session.init(session, self, ctx); self.session = session; return session; } @@ -105,8 +109,7 @@ pub const Browser = struct { // You can create successively multiple pages for a session, but you must // deinit a page before running another one. pub const Session = struct { - // allocator used to init the arena. - allocator: Allocator, + browser: *Browser, // The arena is used only to bound the js env init b/c it leaks memory. // see https://github.com/lightpanda-io/jsruntime-lib/issues/181 @@ -115,41 +118,34 @@ pub const Session = struct { // all others Session deps use directly self.alloc and not the arena. arena: std.heap.ArenaAllocator, - uri: []const u8, - // TODO handle proxy loader: Loader, env: Env, - loop: *Loop, inspector: jsruntime.Inspector, window: Window, // TODO move the shed to the browser? - storageShed: storage.Shed, + storage_shed: storage.Shed, page: ?Page = null, - httpClient: HttpClient, jstypes: [Types.len]usize = undefined, - fn init(self: *Session, allocator: Allocator, ctx: anytype, loop: *Loop, uri: []const u8) !void { + fn init(self: *Session, browser: *Browser, ctx: anytype) !void { + const allocator = browser.allocator; self.* = .{ - .uri = uri, .env = undefined, + .browser = browser, .inspector = undefined, - .allocator = allocator, .loader = Loader.init(allocator), - .httpClient = .{ .allocator = allocator }, - .storageShed = storage.Shed.init(allocator), + .storage_shed = storage.Shed.init(allocator), .arena = std.heap.ArenaAllocator.init(allocator), .window = Window.create(null, .{ .agent = user_agent }), - .loop = loop, }; const arena = self.arena.allocator(); - - Env.init(&self.env, arena, loop, null); + Env.init(&self.env, arena, browser.loop, null); errdefer self.env.deinit(); try self.env.load(&self.jstypes); @@ -164,24 +160,24 @@ pub const Session = struct { // const ctx_opaque = @as(*anyopaque, @ptrCast(ctx)); self.inspector = try jsruntime.Inspector.init( arena, - self.env, + self.env, // TODO: change to 'env' when https://github.com/lightpanda-io/zig-js-runtime/pull/285 lands if (@TypeOf(ctx) == void) @constCast(@ptrCast(&{})) else ctx, InspectorContainer.onInspectorResponse, InspectorContainer.onInspectorEvent, ); self.env.setInspector(self.inspector); + + try self.env.setModuleLoadFn(self, Session.fetchModule); } fn deinit(self: *Session) void { - if (self.page) |*p| { - p.deinit(); + if (self.page != null) { + self.removePage(); } - self.env.deinit(); self.arena.deinit(); - self.httpClient.deinit(); self.loader.deinit(); - self.storageShed.deinit(); + self.storage_shed.deinit(); } fn fetchModule(ctx: *anyopaque, referrer: ?jsruntime.Module, specifier: []const u8) !jsruntime.Module { @@ -189,13 +185,16 @@ pub const Session = struct { const self: *Session = @ptrCast(@alignCast(ctx)); - if (self.page == null) return error.NoPage; + if (self.page == null) { + return error.NoPage; + } log.debug("fetch module: specifier: {s}", .{specifier}); - const alloc = self.arena.allocator(); - const body = try self.page.?.fetchData(alloc, specifier); - defer alloc.free(body); - + // fetchModule is called within the context of processing a page. + // Use the page_arena for this, which has a more appropriate lifetime + // and which has more retained memory between sessions and pages. + const arena = self.browser.page_arena.allocator(); + const body = try self.page.?.fetchData(arena, specifier); return self.env.compileModule(body, specifier); } @@ -205,15 +204,62 @@ pub const Session = struct { // NOTE: the caller is not the owner of the returned value, // the pointer on Page is just returned as a convenience - pub fn createPage(self: *Session) !*Page { - if (self.page != null) return error.SessionPageExists; - self.page = Page.init(self.allocator, self); - return &self.page.?; + pub fn createPage(self: *Session, aux_data: ?[]const u8) !*Page { + std.debug.assert(self.page == null); + + _ = self.browser.page_arena.reset(.{ .retain_with_limit = 1 * 1024 * 1024 }); + + self.page = Page.init(self); + const page = &self.page.?; + + // start JS env + log.debug("start js env", .{}); + try self.env.start(); + + if (comptime builtin.is_test == false) { + // By not loading this during tests, we aren't required to load + // all of the interfaces into zig-js-runtime. + log.debug("setup global env", .{}); + try self.env.bindGlobal(&self.window); + } + + // load polyfills + // TODO: change to 'env' when https://github.com/lightpanda-io/zig-js-runtime/pull/285 lands + try polyfill.load(self.arena.allocator(), self.env); + + // inspector + self.contextCreated(page, aux_data); + + return page; + } + + pub fn removePage(self: *Session) void { + std.debug.assert(self.page != null); + + // Reset all existing callbacks. + self.browser.loop.reset(); + + self.env.stop(); + // TODO unload document: https://html.spec.whatwg.org/#unloading-documents + + self.window.replaceLocation(null) catch |e| { + log.err("reset window location: {any}", .{e}); + }; + + // clear netsurf memory arena. + parser.deinit(); + + self.page = null; } pub fn currentPage(self: *Session) ?*Page { return &(self.page orelse return null); } + + fn contextCreated(self: *Session, page: *Page, aux_data: ?[]const u8) void { + log.debug("inspector context created", .{}); + self.inspector.contextCreated(self.env, "", page.origin orelse "://", aux_data); + } }; // Page navigates to an url. @@ -222,8 +268,8 @@ pub const Session = struct { // The page handle all its memory in an arena allocator. The arena is reseted // when end() is called. pub const Page = struct { + arena: Allocator, session: *Session, - arena: std.heap.ArenaAllocator, doc: ?*parser.Document = null, // handle url @@ -237,71 +283,15 @@ pub const Page = struct { raw_data: ?[]const u8 = null, - fn init(allocator: Allocator, session: *Session) Page { + fn init(session: *Session) Page { return .{ .session = session, - .arena = std.heap.ArenaAllocator.init(allocator), + .arena = session.browser.page_arena.allocator(), }; } - pub fn deinit(self: *Page) void { - self.end(); - self.arena.deinit(); - self.session.page = null; - } - - // start js env. - // - auxData: extra data forwarded to the Inspector - // see Inspector.contextCreated - pub fn start(self: *Page, auxData: ?[]const u8) !void { - // start JS env - log.debug("start js env", .{}); - try self.session.env.start(); - - // register the module loader - try self.session.env.setModuleLoadFn(self.session, Session.fetchModule); - - // add global objects - log.debug("setup global env", .{}); - - if (comptime builtin.is_test == false) { - // By not loading this during tests, we aren't required to load - // all of the interfaces into zig-js-runtime. - try self.session.env.bindGlobal(&self.session.window); - } - - // load polyfills - try polyfill.load(self.arena.allocator(), self.session.env); - - // inspector - log.debug("inspector context created", .{}); - self.session.inspector.contextCreated(self.session.env, "", self.origin orelse "://", auxData); - } - - // reset js env and mem arena. - pub fn end(self: *Page) void { - // Reset all existing callbacks. - self.session.loop.reset(); - - self.session.env.stop(); - // TODO unload document: https://html.spec.whatwg.org/#unloading-documents - - self.url = null; - self.location.url = null; - self.session.window.replaceLocation(&self.location) catch |e| { - log.err("reset window location: {any}", .{e}); - }; - self.doc = null; - - // clear netsurf memory arena. - parser.deinit(); - - _ = self.arena.reset(.free_all); - } - // dump writes the page content into the given file. pub fn dump(self: *const Page, out: std.fs.File) !void { - // if no HTML document pointer available, dump the data content only. if (self.doc == null) { // no data loaded, nothing to do. @@ -314,7 +304,6 @@ pub const Page = struct { } pub fn wait(self: *Page) !void { - // try catch var try_catch: jsruntime.TryCatch = undefined; try_catch.init(self.session.env); @@ -324,9 +313,9 @@ pub const Page = struct { // the js env could not be started if the document wasn't an HTML. if (err == error.EnvNotStarted) return; - const alloc = self.arena.allocator(); - if (try try_catch.err(alloc, self.session.env)) |msg| { - defer alloc.free(msg); + const arena = self.arena; + if (try try_catch.err(arena, self.session.env)) |msg| { + defer arena.free(msg); log.info("wait error: {s}", .{msg}); return; } @@ -335,10 +324,10 @@ pub const Page = struct { } // spec reference: https://html.spec.whatwg.org/#document-lifecycle - // - auxData: extra data forwarded to the Inspector + // - aux_data: extra data forwarded to the Inspector // see Inspector.contextCreated - pub fn navigate(self: *Page, uri: []const u8, auxData: ?[]const u8) !void { - const alloc = self.arena.allocator(); + pub fn navigate(self: *Page, uri: []const u8, aux_data: ?[]const u8) !void { + const arena = self.arena; log.debug("starting GET {s}", .{uri}); @@ -348,26 +337,25 @@ pub const Page = struct { } // own the url - self.rawuri = try alloc.dupe(u8, uri); + self.rawuri = try arena.dupe(u8, uri); self.uri = std.Uri.parse(self.rawuri.?) catch try std.Uri.parseAfterScheme("", self.rawuri.?); - self.url = try URL.constructor(alloc, self.rawuri.?, null); + self.url = try URL.constructor(arena, self.rawuri.?, null); self.location.url = &self.url.?; try self.session.window.replaceLocation(&self.location); // prepare origin value. - var buf = std.ArrayList(u8).init(alloc); - defer buf.deinit(); + var buf: std.ArrayListUnmanaged(u8) = .{}; try self.uri.writeToStream(.{ .scheme = true, .authority = true, - }, buf.writer()); - self.origin = try buf.toOwnedSlice(); + }, buf.writer(arena)); + self.origin = buf.items; // TODO handle fragment in url. // load the data - var resp = try self.session.loader.get(alloc, self.uri); + var resp = try self.session.loader.get(arena, self.uri); defer resp.deinit(); const req = resp.req; @@ -385,46 +373,43 @@ pub const Page = struct { // TODO handle charset // https://html.spec.whatwg.org/#content-type var it = req.response.iterateHeaders(); - var ct: ?[]const u8 = null; + var ct_: ?[]const u8 = null; while (true) { const h = it.next() orelse break; if (std.ascii.eqlIgnoreCase(h.name, "Content-Type")) { - ct = try alloc.dupe(u8, h.value); + ct_ = try arena.dupe(u8, h.value); } } - if (ct == null) { + const ct = ct_ orelse { // no content type in HTTP headers. // TODO try to sniff mime type from the body. log.info("no content-type HTTP header", .{}); return; - } - defer alloc.free(ct.?); + }; - log.debug("header content-type: {s}", .{ct.?}); - var mime = try Mime.parse(alloc, ct.?); - defer mime.deinit(); + log.debug("header content-type: {s}", .{ct}); + var mime = try Mime.parse(arena, ct); if (mime.isHTML()) { - try self.loadHTMLDoc(req.reader(), mime.charset orelse "utf-8", auxData); + try self.loadHTMLDoc(req.reader(), mime.charset orelse "utf-8", aux_data); } else { - log.info("non-HTML document: {s}", .{ct.?}); + log.info("non-HTML document: {s}", .{ct}); // save the body into the page. - self.raw_data = try req.reader().readAllAlloc(alloc, 16 * 1024 * 1024); + self.raw_data = try req.reader().readAllAlloc(arena, 16 * 1024 * 1024); } } // https://html.spec.whatwg.org/#read-html - fn loadHTMLDoc(self: *Page, reader: anytype, charset: []const u8, auxData: ?[]const u8) !void { - const alloc = self.arena.allocator(); + fn loadHTMLDoc(self: *Page, reader: anytype, charset: []const u8, aux_data: ?[]const u8) !void { + const arena = self.arena; // start netsurf memory arena. try parser.init(); log.debug("parse html with charset {s}", .{charset}); - const ccharset = try alloc.dupeZ(u8, charset); - defer alloc.free(ccharset); + const ccharset = try arena.dupeZ(u8, charset); const html_doc = try parser.documentHTMLParse(reader, ccharset); const doc = parser.documentHTMLToDocument(html_doc); @@ -438,22 +423,22 @@ pub const Page = struct { // inject the URL to the document including the fragment. try parser.documentSetDocumentURI(doc, self.rawuri orelse "about:blank"); + const session = self.session; // TODO set the referrer to the document. - - try self.session.window.replaceDocument(html_doc); - self.session.window.setStorageShelf( - try self.session.storageShed.getOrPut(self.origin orelse "null"), + try session.window.replaceDocument(html_doc); + session.window.setStorageShelf( + try session.storage_shed.getOrPut(self.origin orelse "null"), ); // https://html.spec.whatwg.org/#read-html // inspector - self.session.inspector.contextCreated(self.session.env, "", self.origin.?, auxData); + session.contextCreated(self, aux_data); // replace the user context document with the new one. - try self.session.env.setUserContext(.{ + try session.env.setUserContext(.{ .document = html_doc, - .httpClient = &self.session.httpClient, + .httpClient = &self.session.browser.http_client, }); // browse the DOM tree to retrieve scripts @@ -464,8 +449,7 @@ pub const Page = struct { // sasync stores scripts which can be run asynchronously. // for now they are just run after the non-async one in order to // dispatch DOMContentLoaded the sooner as possible. - var sasync = std.ArrayList(Script).init(alloc); - defer sasync.deinit(); + var sasync: std.ArrayListUnmanaged(Script) = .{}; const root = parser.documentToNode(doc); const walker = Walker{}; @@ -496,8 +480,8 @@ pub const Page = struct { // > then the classic script will be fetched in parallel to // > parsing and evaluated as soon as it is available. // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#async - if (script.isasync) { - try sasync.append(script); + if (script.is_async) { + try sasync.append(arena, script); continue; } @@ -562,8 +546,6 @@ pub const Page = struct { // if no src is present, we evaluate the text source. // https://html.spec.whatwg.org/multipage/scripting.html#script-processing-model fn evalScript(self: *Page, s: Script) !void { - const alloc = self.arena.allocator(); - // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-classic-script const opt_src = try parser.elementGetAttribute(s.element, "src"); if (opt_src) |src| { @@ -591,7 +573,9 @@ pub const Page = struct { // TODO handle charset attribute const opt_text = try parser.nodeTextContent(parser.elementToNode(s.element)); if (opt_text) |text| { - try s.eval(alloc, self.session.env, text); + // TODO: change to &self.session.env when + // https://github.com/lightpanda-io/zig-js-runtime/pull/285 lands + try s.eval(self.arena, self.session.env, text); return; } @@ -607,27 +591,31 @@ pub const Page = struct { }; // the caller owns the returned string - fn fetchData(self: *Page, alloc: Allocator, src: []const u8) ![]const u8 { + fn fetchData(self: *Page, arena: Allocator, src: []const u8) ![]const u8 { log.debug("starting fetch {s}", .{src}); var buffer: [1024]u8 = undefined; var b: []u8 = buffer[0..]; const u = try std.Uri.resolve_inplace(self.uri, src, &b); - var fetchres = try self.session.loader.get(alloc, u); + var fetchres = try self.session.loader.get(arena, u); defer fetchres.deinit(); const resp = fetchres.req.response; log.info("fetch {any}: {d}", .{ u, resp.status }); - if (resp.status != .ok) return FetchError.BadStatusCode; + if (resp.status != .ok) { + return FetchError.BadStatusCode; + } // TODO check content-type - const body = try fetchres.req.reader().readAllAlloc(alloc, 16 * 1024 * 1024); + const body = try fetchres.req.reader().readAllAlloc(arena, 16 * 1024 * 1024); // check no body - if (body.len == 0) return FetchError.NoBody; + if (body.len == 0) { + return FetchError.NoBody; + } return body; } @@ -635,17 +623,17 @@ pub const Page = struct { // fetchScript senf a GET request to the src and execute the script // received. fn fetchScript(self: *Page, s: Script) !void { - const alloc = self.arena.allocator(); - const body = try self.fetchData(alloc, s.src); - defer alloc.free(body); - - try s.eval(alloc, self.session.env, body); + const arena = self.arena; + const body = try self.fetchData(arena, s.src); + // TODO: change to &self.session.env when + // https://github.com/lightpanda-io/zig-js-runtime/pull/285 lands + try s.eval(arena, self.session.env, body); } const Script = struct { element: *parser.Element, kind: Kind, - isasync: bool, + is_async: bool, src: []const u8, @@ -663,7 +651,7 @@ pub const Page = struct { return .{ .element = e, .kind = kind(try parser.elementGetAttribute(e, "type")), - .isasync = try parser.elementGetAttribute(e, "async") != null, + .is_async = try parser.elementGetAttribute(e, "async") != null, .src = try parser.elementGetAttribute(e, "src") orelse "inline", }; @@ -682,7 +670,7 @@ pub const Page = struct { return .unknown; } - fn eval(self: Script, alloc: Allocator, env: Env, body: []const u8) !void { + fn eval(self: Script, arena: Allocator, env: Env, body: []const u8) !void { var try_catch: jsruntime.TryCatch = undefined; try_catch.init(env); defer try_catch.deinit(); @@ -692,16 +680,14 @@ pub const Page = struct { .javascript => env.exec(body, self.src), .module => env.module(body, self.src), } catch { - if (try try_catch.err(alloc, env)) |msg| { - defer alloc.free(msg); + if (try try_catch.err(arena, env)) |msg| { log.info("eval script {s}: {s}", .{ self.src, msg }); } return FetchError.JsErr; }; if (builtin.mode == .Debug) { - const msg = try res.toString(alloc, env); - defer alloc.free(msg); + const msg = try res.toString(arena, env); log.debug("eval script {s}: {s}", .{ self.src, msg }); } } diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index e931253a..1543ba76 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -55,13 +55,15 @@ pub fn CDPT(comptime TypeProvider: type) type { allocator: Allocator, // The active browser - browser: ?Browser = null, + browser: Browser, target_id_gen: TargetIdGen = .{}, session_id_gen: SessionIdGen = .{}, browser_context_id_gen: BrowserContextIdGen = .{}, - browser_context: ?BrowserContext(Self), + browser_context: ?*BrowserContext(Self), + + browser_context_pool: std.heap.MemoryPool(BrowserContext(Self)), // Re-used arena for processing a message. We're assuming that we're getting // 1 message at a time. @@ -77,15 +79,19 @@ pub fn CDPT(comptime TypeProvider: type) type { .client = client, .allocator = allocator, .browser_context = null, + .browser = Browser.init(allocator, loop), .message_arena = std.heap.ArenaAllocator.init(allocator), + .browser_context_pool = std.heap.MemoryPool(BrowserContext(Self)).init(allocator), }; } pub fn deinit(self: *Self) void { - if (self.browser_context) |*bc| { + if (self.browser_context) |bc| { bc.deinit(); } + self.browser.deinit(); self.message_arena.deinit(); + self.browser_context_pool.deinit(); } pub fn handleMessage(self: *Self, msg: []const u8) bool { @@ -119,7 +125,7 @@ pub fn CDPT(comptime TypeProvider: type) type { .cdp = self, .arena = arena, .sender = sender, - .browser_context = if (self.browser_context) |*bc| bc else null, + .browser_context = if (self.browser_context) |bc| bc else null, }; // See dispatchStartupCommand for more info on this. @@ -213,7 +219,7 @@ pub fn CDPT(comptime TypeProvider: type) type { } fn isValidSessionId(self: *const Self, input_session_id: []const u8) bool { - const browser_context = &(self.browser_context orelse return false); + const browser_context = self.browser_context orelse return false; const session_id = browser_context.session_id orelse return false; return std.mem.eql(u8, session_id, input_session_id); } @@ -224,20 +230,21 @@ pub fn CDPT(comptime TypeProvider: type) type { } const browser_context_id = self.browser_context_id_gen.next(); - // is this safe? - self.browser_context = undefined; - errdefer self.browser_context = null; - try BrowserContext(Self).init(&self.browser_context.?, browser_context_id, self); + const browser_context = try self.browser_context_pool.create(); + errdefer self.browser_context_pool.destroy(browser_context); + try BrowserContext(Self).init(browser_context, browser_context_id, self); + self.browser_context = browser_context; return browser_context_id; } pub fn disposeBrowserContext(self: *Self, browser_context_id: []const u8) bool { - const bc = &(self.browser_context orelse return false); + const bc = self.browser_context orelse return false; if (std.mem.eql(u8, bc.id, browser_context_id) == false) { return false; } bc.deinit(); + self.browser_context_pool.destroy(bc); self.browser_context = null; return true; } @@ -257,7 +264,6 @@ pub fn BrowserContext(comptime CDP_T: type) type { id: []const u8, cdp: *CDP_T, - browser: CDP_T.Browser, // Represents the browser session. There is no equivalent in CDP. For // 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 @@ -294,22 +300,17 @@ pub fn BrowserContext(comptime CDP_T: type) type { self.* = .{ .id = id, .cdp = cdp, - .browser = undefined, - .session = undefined, .target_id = null, .session_id = null, .url = URL_BASE, .security_origin = URL_BASE, .secure_context_type = "Secure", // TODO = enum .loader_id = LOADER_ID, + .session = try cdp.browser.newSession(self), .page_life_cycle_events = false, // TODO; Target based value .node_list = dom.NodeList.init(cdp.allocator), .node_search_list = dom.NodeSearchList.init(cdp.allocator), }; - - self.browser = CDP_T.Browser.init(cdp.allocator, cdp.loop); - errdefer self.browser.deinit(); - self.session = try self.browser.newSession(self); } pub fn deinit(self: *Self) void { @@ -318,7 +319,6 @@ pub fn BrowserContext(comptime CDP_T: type) type { s.deinit(); } self.node_search_list.deinit(); - self.browser.deinit(); } pub fn reset(self: *Self) void { @@ -447,7 +447,7 @@ pub fn Command(comptime CDP_T: type, comptime Sender: type) type { pub fn createBrowserContext(self: *Self) !*BrowserContext(CDP_T) { _ = try self.cdp.createBrowserContext(); - self.browser_context = &self.cdp.browser_context.?; + self.browser_context = self.cdp.browser_context.?; return self.browser_context.?; } diff --git a/src/cdp/target.zig b/src/cdp/target.zig index d9de95a4..51250e9a 100644 --- a/src/cdp/target.zig +++ b/src/cdp/target.zig @@ -116,15 +116,8 @@ fn createTarget(cmd: anytype) !void { // if target_id is null, we should never have a session_id std.debug.assert(bc.session_id == null); - const page = try bc.session.createPage(); const target_id = cmd.cdp.target_id_gen.next(); - // change CDP state - bc.url = "about:blank"; - bc.security_origin = "://"; - bc.secure_context_type = "InsecureScheme"; - bc.loader_id = LOADER_ID; - // start the js env const aux_data = try std.fmt.allocPrint( cmd.arena, @@ -132,7 +125,13 @@ fn createTarget(cmd: anytype) !void { "{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\"}}", .{target_id}, ); - try page.start(aux_data); + _ = try bc.session.createPage(aux_data); + + // change CDP state + bc.url = "about:blank"; + bc.security_origin = "://"; + bc.secure_context_type = "InsecureScheme"; + bc.loader_id = LOADER_ID; // send targetCreated event // TODO: should this only be sent when Target.setDiscoverTargets @@ -213,7 +212,7 @@ fn closeTarget(cmd: anytype) !void { bc.session_id = null; } - bc.session.currentPage().?.end(); + bc.session.removePage(); bc.target_id = null; } @@ -508,7 +507,7 @@ test "cdp.target: closeTarget" { } // pretend we createdTarget first - _ = try bc.session.createPage(); + _ = try bc.session.createPage(null); bc.target_id = "TID-A"; { try testing.expectError(error.UnknownTargetId, ctx.processMessage(.{ .id = 10, .method = "Target.closeTarget", .params = .{ .targetId = "TID-8" } })); @@ -539,7 +538,7 @@ test "cdp.target: attachToTarget" { } // pretend we createdTarget first - _ = try bc.session.createPage(); + _ = try bc.session.createPage(null); bc.target_id = "TID-B"; { try testing.expectError(error.UnknownTargetId, ctx.processMessage(.{ .id = 10, .method = "Target.attachToTarget", .params = .{ .targetId = "TID-8" } })); @@ -583,7 +582,7 @@ test "cdp.target: getTargetInfo" { } // pretend we createdTarget first - _ = try bc.session.createPage(); + _ = try bc.session.createPage(null); bc.target_id = "TID-A"; { try testing.expectError(error.UnknownTargetId, ctx.processMessage(.{ .id = 10, .method = "Target.getTargetInfo", .params = .{ .targetId = "TID-8" } })); diff --git a/src/cdp/testing.zig b/src/cdp/testing.zig index 29b67324..d08d2e09 100644 --- a/src/cdp/testing.zig +++ b/src/cdp/testing.zig @@ -56,17 +56,21 @@ const Session = struct { return &(self.page orelse return null); } - pub fn createPage(self: *Session) !*Page { + pub fn createPage(self: *Session, aux_data: ?[]const u8) !*Page { if (self.page != null) { return error.MockBrowserPageAlreadyExists; } self.page = .{ .session = self, - .allocator = self.allocator, + .aux_data = try self.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; @@ -75,7 +79,6 @@ const Session = struct { const Page = struct { session: *Session, - allocator: Allocator, aux_data: []const u8 = "", doc: ?*parser.Document = null, @@ -84,14 +87,6 @@ const Page = struct { _ = url; _ = aux_data; } - - pub fn start(self: *Page, aux_data: []const u8) !void { - self.aux_data = try self.allocator.dupe(u8, aux_data); - } - - pub fn end(self: *Page) void { - self.session.page = null; - } }; const Client = struct { @@ -152,13 +147,15 @@ const TestContext = struct { }; pub fn loadBrowserContext(self: *TestContext, opts: BrowserContextOpts) !*main.BrowserContext(TestCDP) { var c = self.cdp(); - if (c.browser_context) |*bc| { + c.browser.session = null; + + if (c.browser_context) |bc| { bc.deinit(); c.browser_context = null; } _ = try c.createBrowserContext(); - var bc = &c.browser_context.?; + var bc = c.browser_context.?; if (opts.id) |id| { bc.id = id; diff --git a/src/html/window.zig b/src/html/window.zig index 42aef90d..0747c709 100644 --- a/src/html/window.zig +++ b/src/html/window.zig @@ -58,17 +58,17 @@ pub const Window = struct { navigator: Navigator, pub fn create(target: ?[]const u8, navigator: ?Navigator) Window { - return Window{ + return .{ .target = target orelse "", .navigator = navigator orelse .{}, }; } - pub fn replaceLocation(self: *Window, loc: *Location) !void { - self.location = loc; + pub fn replaceLocation(self: *Window, loc: ?*Location) !void { + self.location = loc orelse &emptyLocation; - if (self.document != null) { - try parser.documentHTMLSetLocation(Location, self.document.?, self.location); + if (self.document) |doc| { + try parser.documentHTMLSetLocation(Location, doc, self.location); } } diff --git a/src/main.zig b/src/main.zig index ee6315be..624d0ce1 100644 --- a/src/main.zig +++ b/src/main.zig @@ -96,9 +96,7 @@ pub fn main() !void { var session = try browser.newSession({}); // page - const page = try session.createPage(); - try page.start(null); - defer page.end(); + const page = try session.createPage(null); _ = page.navigate(opts.url, null) catch |err| switch (err) { error.UnsupportedUriScheme, error.UriMissingHost => {