From 16427410279386a8c9271eb182c875407010a625 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Tue, 19 Dec 2023 14:29:43 +0100 Subject: [PATCH] browser: start browser API --- build.zig | 23 +++++++ src/browser/browser.zig | 133 ++++++++++++++++++++++++++++++++++++++++ src/browser/loader.zig | 54 ++++++++++++++++ 3 files changed, 210 insertions(+) create mode 100644 src/browser/browser.zig create mode 100644 src/browser/loader.zig diff --git a/build.zig b/build.zig index 3004d839..90d200e9 100644 --- a/build.zig +++ b/build.zig @@ -115,6 +115,29 @@ pub fn build(b: *std.build.Builder) !void { // step const wpt_step = b.step("wpt", "WPT tests"); wpt_step.dependOn(&wpt_cmd.step); + + // get + // ----- + + // compile and install + const get = b.addExecutable(.{ + .name = "browsercore-get", + .root_source_file = .{ .path = "src/main_get.zig" }, + .target = target, + .optimize = mode, + }); + try common(get, options); + b.installArtifact(get); + + // run + const get_cmd = b.addRunArtifact(get); + get_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + get_cmd.addArgs(args); + } + // step + const get_step = b.step("get", "request URL"); + get_step.dependOn(&get_cmd.step); } fn common( diff --git a/src/browser/browser.zig b/src/browser/browser.zig new file mode 100644 index 00000000..6fa291d2 --- /dev/null +++ b/src/browser/browser.zig @@ -0,0 +1,133 @@ +const std = @import("std"); + +const parser = @import("../netsurf.zig"); +const Loader = @import("loader.zig").Loader; + +const jsruntime = @import("jsruntime"); +const Loop = jsruntime.Loop; +const Env = jsruntime.Env; +const TPL = jsruntime.TPL; + +const apiweb = @import("../apiweb.zig"); +const apis = jsruntime.compile(apiweb.Interfaces); +const Window = @import("../nav/window.zig").Window; + +const log = std.log.scoped(.lpd_browser); + +pub const Browser = struct { + allocator: std.mem.Allocator, + session: Session = undefined, + + pub fn init(allocator: std.mem.Allocator) Browser { + var b = Browser{ .allocator = allocator }; + b.session = try b.createSession(null); + + return b; + } + + pub fn deinit(self: *Browser) void { + var session = self.session; + session.deinit(); + } + + pub fn currentSession(self: *Browser) *Session { + return &self.session; + } + + fn createSession(self: *Browser, uri: ?[]const u8) !Session { + return Session.init(self.allocator, uri orelse "about:blank"); + } +}; + +pub const Session = struct { + allocator: std.mem.Allocator, + uri: []const u8, + // TODO handle proxy + loader: Loader, + + fn init(allocator: std.mem.Allocator, uri: []const u8) Session { + return Session{ + .allocator = allocator, + .uri = uri, + .loader = Loader.init(allocator), + }; + } + + fn deinit(self: *Session) void { + self.loader.deinit(); + } + + pub fn createPage(self: *Session) !Page { + return Page.init(self); + } +}; + +pub const Page = struct { + arena: std.heap.ArenaAllocator, + session: *Session, + + fn init(session: *Session) Page { + return Page{ + .session = session, + .arena = std.heap.ArenaAllocator.init(session.allocator), + }; + } + + pub fn deinit(self: *Page) void { + self.arena.deinit(); + } + + pub fn navigate(self: *Page, uri: []const u8) !void { + const allocator = self.arena.allocator(); + + log.debug("starting GET {s}", .{uri}); + + // load the data + var result = try self.session.loader.fetch(allocator, uri); + defer result.deinit(); + + log.info("GET {s} {d}", .{ uri, result.status }); + + // TODO handle redirection + if (result.status != .ok) return error.BadStatusCode; + + if (result.body == null) return error.NoBody; + + // TODO handle charset + + // document + const html_doc = try parser.documentHTMLParseFromStrAlloc(allocator, result.body.?); + const doc = parser.documentHTMLToDocument(html_doc); + + // create JS env + var loop = try Loop.init(allocator); + defer loop.deinit(); + var js_env = try Env.init(allocator, &loop); + defer js_env.deinit(); + + // load APIs in JS env + var tpls: [apis.len]TPL = undefined; + try js_env.load(apis, &tpls); + + // start JS env + try js_env.start(allocator, apis); + defer js_env.stop(); + + // add global objects + const window = Window.create(doc, null); + _ = window; + // TODO should'nt we share the same pointer between instances of window? + // try js_env.addObject(apis, window, "self"); + // try js_env.addObject(apis, window, "window"); + try js_env.addObject(apis, doc, "document"); + } +}; + +test "create page" { + const allocator = std.testing.allocator; + var browser = Browser.init(allocator); + defer browser.deinit(); + + var page = try browser.currentSession().createPage(); + defer page.deinit(); +} diff --git a/src/browser/loader.zig b/src/browser/loader.zig new file mode 100644 index 00000000..8892f607 --- /dev/null +++ b/src/browser/loader.zig @@ -0,0 +1,54 @@ +const std = @import("std"); + +const user_agent = "Lightpanda.io/1.0"; + +pub const Loader = struct { + client: std.http.Client, + + pub const Response = struct { + req: std.http.Request, + + pub fn deinit(self: *Response) void { + self.req.deinit(); + } + }; + + pub fn init(allocator: std.mem.Allocator) Loader { + return Loader{ + .client = std.http.Client{ + .allocator = allocator, + }, + }; + } + + pub fn deinit(self: *Loader) void { + self.client.deinit(); + } + + // the caller must deinit the FetchResult. + pub fn fetch(self: *Loader, allocator: std.mem.Allocator, uri: []const u8) !std.http.Client.FetchResult { + var headers = try std.http.Headers.initList(allocator, &[_]std.http.Field{ + .{ .name = "User-Agent", .value = user_agent }, + .{ .name = "Accept", .value = "*/*" }, + .{ .name = "Accept-Language", .value = "en-US,en;q=0.5" }, + }); + defer headers.deinit(); + + return try self.client.fetch(allocator, .{ + .location = .{ .url = uri }, + .headers = headers, + .payload = .none, + }); + } +}; + +test "basic url fetch" { + const alloc = std.testing.allocator; + var loader = Loader.init(alloc); + defer loader.deinit(); + + var result = try loader.fetch(alloc, "https://en.wikipedia.org/wiki/Main_Page"); + defer result.deinit(); + + try std.testing.expect(result.status == std.http.Status.ok); +}