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