browser: start browser API

This commit is contained in:
Pierre Tachoire
2023-12-19 14:29:43 +01:00
parent b53d4a149c
commit 1642741027
3 changed files with 210 additions and 0 deletions

View File

@@ -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(

133
src/browser/browser.zig Normal file
View File

@@ -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();
}

54
src/browser/loader.zig Normal file
View File

@@ -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);
}