Merge pull request #203 from lightpanda-io/upgrade-zig

Upgrade zig 0.12
This commit is contained in:
Pierre Tachoire
2024-06-18 16:16:44 +02:00
committed by GitHub
54 changed files with 1206 additions and 909 deletions

View File

@@ -47,7 +47,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: ghcr.io/lightpanda-io/zig-browsercore:0.12.0-dev.1773-8a8fd47d2 image: ghcr.io/lightpanda-io/zig-browsercore:0.12.1
credentials: credentials:
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -27,7 +27,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: ghcr.io/lightpanda-io/zig:0.12.0-dev.1773-8a8fd47d2 image: ghcr.io/lightpanda-io/zig:0.12.1
credentials: credentials:
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -45,7 +45,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: ghcr.io/lightpanda-io/zig-browsercore:0.12.0-dev.1773-8a8fd47d2 image: ghcr.io/lightpanda-io/zig-browsercore:0.12.1
credentials: credentials:
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
@@ -71,7 +71,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: ghcr.io/lightpanda-io/zig-browsercore:0.12.0-dev.1773-8a8fd47d2 image: ghcr.io/lightpanda-io/zig-browsercore:0.12.1
credentials: credentials:
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
@@ -97,7 +97,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: ghcr.io/lightpanda-io/zig-browsercore:0.12.0-dev.1773-8a8fd47d2 image: ghcr.io/lightpanda-io/zig-browsercore:0.12.1
credentials: credentials:
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -76,7 +76,7 @@ We do not provide yet binary versions of Lightpanda, you have to compile it from
### Prerequisites ### Prerequisites
Lightpanda is written with [Zig](https://ziglang.org/) `0.12`. You have to Lightpanda is written with [Zig](https://ziglang.org/) `0.12.1`. You have to
install it with the right version in order to build the project. install it with the right version in order to build the project.
Lightpanda also depends on Lightpanda also depends on

View File

@@ -28,7 +28,7 @@ const jsruntime_pkgs = jsruntime.packages(jsruntime_path);
/// which zig version to install. /// which zig version to install.
const recommended_zig_version = jsruntime.recommended_zig_version; const recommended_zig_version = jsruntime.recommended_zig_version;
pub fn build(b: *std.build.Builder) !void { pub fn build(b: *std.Build) !void {
switch (comptime builtin.zig_version.order(std.SemanticVersion.parse(recommended_zig_version) catch unreachable)) { switch (comptime builtin.zig_version.order(std.SemanticVersion.parse(recommended_zig_version) catch unreachable)) {
.eq => {}, .eq => {},
.lt => { .lt => {
@@ -53,11 +53,11 @@ pub fn build(b: *std.build.Builder) !void {
// compile and install // compile and install
const exe = b.addExecutable(.{ const exe = b.addExecutable(.{
.name = "browsercore", .name = "browsercore",
.root_source_file = .{ .path = "src/main.zig" }, .root_source_file = b.path("src/main.zig"),
.target = target, .target = target,
.optimize = mode, .optimize = mode,
}); });
try common(exe, options); try common(b, exe, options);
b.installArtifact(exe); b.installArtifact(exe);
// run // run
@@ -76,11 +76,11 @@ pub fn build(b: *std.build.Builder) !void {
// compile and install // compile and install
const shell = b.addExecutable(.{ const shell = b.addExecutable(.{
.name = "browsercore-shell", .name = "browsercore-shell",
.root_source_file = .{ .path = "src/main_shell.zig" }, .root_source_file = b.path("src/main_shell.zig"),
.target = target, .target = target,
.optimize = mode, .optimize = mode,
}); });
try common(shell, options); try common(b, shell, options);
try jsruntime_pkgs.add_shell(shell); try jsruntime_pkgs.add_shell(shell);
// run // run
@@ -98,17 +98,17 @@ pub fn build(b: *std.build.Builder) !void {
// compile // compile
const tests = b.addTest(.{ const tests = b.addTest(.{
.root_source_file = .{ .path = "src/run_tests.zig" }, .root_source_file = b.path("src/run_tests.zig"),
.test_runner = "src/test_runner.zig", .test_runner = b.path("src/test_runner.zig"),
.single_threaded = true, .target = target,
.optimize = mode,
}); });
try common(tests, options); try common(b, tests, options);
// add jsruntime pretty deps // add jsruntime pretty deps
const pretty = tests.step.owner.createModule(.{ tests.root_module.addAnonymousImport("pretty", .{
.source_file = .{ .path = "vendor/zig-js-runtime/src/pretty.zig" }, .root_source_file = b.path("vendor/zig-js-runtime/src/pretty.zig"),
}); });
tests.addModule("pretty", pretty);
const run_tests = b.addRunArtifact(tests); const run_tests = b.addRunArtifact(tests);
if (b.args) |args| { if (b.args) |args| {
@@ -125,12 +125,11 @@ pub fn build(b: *std.build.Builder) !void {
// compile and install // compile and install
const wpt = b.addExecutable(.{ const wpt = b.addExecutable(.{
.name = "browsercore-wpt", .name = "browsercore-wpt",
.root_source_file = .{ .path = "src/main_wpt.zig" }, .root_source_file = b.path("src/main_wpt.zig"),
.target = target, .target = target,
.optimize = mode, .optimize = mode,
}); });
try common(wpt, options); try common(b, wpt, options);
b.installArtifact(wpt);
// run // run
const wpt_cmd = b.addRunArtifact(wpt); const wpt_cmd = b.addRunArtifact(wpt);
@@ -147,11 +146,11 @@ pub fn build(b: *std.build.Builder) !void {
// compile and install // compile and install
const get = b.addExecutable(.{ const get = b.addExecutable(.{
.name = "browsercore-get", .name = "browsercore-get",
.root_source_file = .{ .path = "src/main_get.zig" }, .root_source_file = b.path("src/main_get.zig"),
.target = target, .target = target,
.optimize = mode, .optimize = mode,
}); });
try common(get, options); try common(b, get, options);
b.installArtifact(get); b.installArtifact(get);
// run // run
@@ -165,25 +164,38 @@ pub fn build(b: *std.build.Builder) !void {
} }
fn common( fn common(
b: *std.Build,
step: *std.Build.Step.Compile, step: *std.Build.Step.Compile,
options: jsruntime.Options, options: jsruntime.Options,
) !void { ) !void {
try jsruntime_pkgs.add(step, options); const jsruntimemod = try jsruntime_pkgs.module(
linkNetSurf(step); b,
options,
step.root_module.optimize.?,
step.root_module.resolved_target.?,
);
step.root_module.addImport("jsruntime", jsruntimemod);
// link mimalloc const netsurf = moduleNetSurf(b);
step.addObjectFile(.{ .path = "vendor/mimalloc/out/libmimalloc.a" }); netsurf.addImport("jsruntime", jsruntimemod);
step.addIncludePath(.{ .path = "vendor/mimalloc/out/include" }); step.root_module.addImport("netsurf", netsurf);
} }
fn linkNetSurf(step: *std.build.LibExeObjStep) void { fn moduleNetSurf(b: *std.Build) *std.Build.Module {
const mod = b.addModule("netsurf", .{
.root_source_file = b.path("src/netsurf/netsurf.zig"),
});
// iconv // iconv
step.addObjectFile(.{ .path = "vendor/libiconv/lib/libiconv.a" }); mod.addObjectFile(b.path("vendor/libiconv/lib/libiconv.a"));
step.addIncludePath(.{ .path = "vendor/libiconv/include" }); mod.addIncludePath(b.path("vendor/libiconv/include"));
// mimalloc
mod.addImport("mimalloc", moduleMimalloc(b));
// netsurf libs // netsurf libs
const ns = "vendor/netsurf"; const ns = "vendor/netsurf";
mod.addIncludePath(b.path(ns ++ "/include"));
const libs: [4][]const u8 = .{ const libs: [4][]const u8 = .{
"libdom", "libdom",
"libhubbub", "libhubbub",
@@ -191,8 +203,20 @@ fn linkNetSurf(step: *std.build.LibExeObjStep) void {
"libwapcaplet", "libwapcaplet",
}; };
inline for (libs) |lib| { inline for (libs) |lib| {
step.addObjectFile(.{ .path = ns ++ "/lib/" ++ lib ++ ".a" }); mod.addObjectFile(b.path(ns ++ "/lib/" ++ lib ++ ".a"));
step.addIncludePath(.{ .path = ns ++ "/" ++ lib ++ "/src" }); mod.addIncludePath(b.path(ns ++ "/" ++ lib ++ "/src"));
} }
step.addIncludePath(.{ .path = ns ++ "/include" });
return mod;
}
fn moduleMimalloc(b: *std.Build) *std.Build.Module {
const mod = b.addModule("mimalloc", .{
.root_source_file = b.path("src/mimalloc/mimalloc.zig"),
});
mod.addObjectFile(b.path("vendor/mimalloc/out/libmimalloc.a"));
mod.addIncludePath(b.path("vendor/mimalloc/out/include"));
return mod;
} }

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,7 @@
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin"); const builtin = @import("builtin");
const os = std.os; const posix = std.posix;
const io = std.io; const io = std.io;
const assert = std.debug.assert; const assert = std.debug.assert;
@@ -28,15 +28,15 @@ pub const Stream = struct {
alloc: std.mem.Allocator, alloc: std.mem.Allocator,
conn: *tcp.Conn, conn: *tcp.Conn,
handle: std.os.socket_t, handle: posix.socket_t,
pub fn close(self: Stream) void { pub fn close(self: Stream) void {
os.closeSocket(self.handle); posix.close(self.handle);
self.alloc.destroy(self.conn); self.alloc.destroy(self.conn);
} }
pub const ReadError = os.ReadError; pub const ReadError = posix.ReadError;
pub const WriteError = os.WriteError; pub const WriteError = posix.WriteError;
pub const Reader = io.Reader(Stream, ReadError, read); pub const Reader = io.Reader(Stream, ReadError, read);
pub const Writer = io.Writer(Stream, WriteError, write); pub const Writer = io.Writer(Stream, WriteError, write);
@@ -55,8 +55,8 @@ pub const Stream = struct {
}; };
} }
pub fn readv(s: Stream, iovecs: []const os.iovec) ReadError!usize { pub fn readv(s: Stream, iovecs: []const posix.iovec) ReadError!usize {
return os.readv(s.handle, iovecs); return posix.readv(s.handle, iovecs);
} }
/// Returns the number of bytes read. If the number read is smaller than /// Returns the number of bytes read. If the number read is smaller than
@@ -105,7 +105,7 @@ pub const Stream = struct {
/// See https://github.com/ziglang/zig/issues/7699 /// See https://github.com/ziglang/zig/issues/7699
/// See equivalent function: `std.fs.File.writev`. /// See equivalent function: `std.fs.File.writev`.
pub fn writev(self: Stream, iovecs: []const os.iovec_const) WriteError!usize { pub fn writev(self: Stream, iovecs: []const posix.iovec_const) WriteError!usize {
if (iovecs.len == 0) return 0; if (iovecs.len == 0) return 0;
const first_buffer = iovecs[0].iov_base[0..iovecs[0].iov_len]; const first_buffer = iovecs[0].iov_base[0..iovecs[0].iov_len];
return try self.write(first_buffer); return try self.write(first_buffer);
@@ -115,7 +115,7 @@ pub const Stream = struct {
/// order to handle partial writes from the underlying OS layer. /// order to handle partial writes from the underlying OS layer.
/// See https://github.com/ziglang/zig/issues/7699 /// See https://github.com/ziglang/zig/issues/7699
/// See equivalent function: `std.fs.File.writevAll`. /// See equivalent function: `std.fs.File.writevAll`.
pub fn writevAll(self: Stream, iovecs: []os.iovec_const) WriteError!void { pub fn writevAll(self: Stream, iovecs: []posix.iovec_const) WriteError!void {
if (iovecs.len == 0) return; if (iovecs.len == 0) return;
var i: usize = 0; var i: usize = 0;

View File

@@ -59,19 +59,19 @@ pub const Conn = struct {
loop: *Loop, loop: *Loop,
pub fn connect(self: *Conn, socket: std.os.socket_t, address: std.net.Address) !void { pub fn connect(self: *Conn, socket: std.posix.socket_t, address: std.net.Address) !void {
var cmd = Command{ .impl = NetworkImpl.init(self.loop) }; var cmd = Command{ .impl = NetworkImpl.init(self.loop) };
cmd.impl.connect(&cmd, socket, address); cmd.impl.connect(&cmd, socket, address);
_ = try cmd.wait(); _ = try cmd.wait();
} }
pub fn send(self: *Conn, socket: std.os.socket_t, buffer: []const u8) !usize { pub fn send(self: *Conn, socket: std.posix.socket_t, buffer: []const u8) !usize {
var cmd = Command{ .impl = NetworkImpl.init(self.loop) }; var cmd = Command{ .impl = NetworkImpl.init(self.loop) };
cmd.impl.send(&cmd, socket, buffer); cmd.impl.send(&cmd, socket, buffer);
return try cmd.wait(); return try cmd.wait();
} }
pub fn receive(self: *Conn, socket: std.os.socket_t, buffer: []u8) !usize { pub fn receive(self: *Conn, socket: std.posix.socket_t, buffer: []u8) !usize {
var cmd = Command{ .impl = NetworkImpl.init(self.loop) }; var cmd = Command{ .impl = NetworkImpl.init(self.loop) };
cmd.impl.receive(&cmd, socket, buffer); cmd.impl.receive(&cmd, socket, buffer);
return try cmd.wait(); return try cmd.wait();
@@ -93,12 +93,12 @@ pub fn tcpConnectToHost(alloc: std.mem.Allocator, loop: *Loop, name: []const u8,
else => return err, else => return err,
}; };
} }
return std.os.ConnectError.ConnectionRefused; return std.posix.ConnectError.ConnectionRefused;
} }
pub fn tcpConnectToAddress(alloc: std.mem.Allocator, loop: *Loop, addr: net.Address) !Stream { pub fn tcpConnectToAddress(alloc: std.mem.Allocator, loop: *Loop, addr: net.Address) !Stream {
const sockfd = try std.os.socket(addr.any.family, std.os.SOCK.STREAM, std.os.IPPROTO.TCP); const sockfd = try std.posix.socket(addr.any.family, std.posix.SOCK.STREAM, std.posix.IPPROTO.TCP);
errdefer std.os.closeSocket(sockfd); errdefer std.posix.close(sockfd);
var conn = try alloc.create(Conn); var conn = try alloc.create(Conn);
conn.* = Conn{ .loop = loop }; conn.* = Conn{ .loop = loop };

View File

@@ -40,11 +40,9 @@ test "blocking mode fetch API" {
// force client's CA cert scan from system. // force client's CA cert scan from system.
try client.ca_bundle.rescan(client.allocator); try client.ca_bundle.rescan(client.allocator);
var res = try client.fetch(alloc, .{ const res = try client.fetch(.{
.location = .{ .uri = try std.Uri.parse(url) }, .location = .{ .uri = try std.Uri.parse(url) },
.payload = .none,
}); });
defer res.deinit();
try std.testing.expect(res.status == .ok); try std.testing.expect(res.status == .ok);
} }
@@ -64,13 +62,13 @@ test "blocking mode open/send/wait API" {
// force client's CA cert scan from system. // force client's CA cert scan from system.
try client.ca_bundle.rescan(client.allocator); try client.ca_bundle.rescan(client.allocator);
var headers = try std.http.Headers.initList(alloc, &[_]std.http.Field{}); var buf: [2014]u8 = undefined;
defer headers.deinit(); var req = try client.open(.GET, try std.Uri.parse(url), .{
.server_header_buffer = &buf,
var req = try client.open(.GET, try std.Uri.parse(url), headers, .{}); });
defer req.deinit(); defer req.deinit();
try req.send(.{}); try req.send();
try req.finish(); try req.finish();
try req.wait(); try req.wait();
@@ -87,7 +85,6 @@ const AsyncClient = struct {
cli: *Client, cli: *Client,
uri: std.Uri, uri: std.Uri,
headers: std.http.Headers,
req: ?Request = undefined, req: ?Request = undefined,
state: State = .new, state: State = .new,
@@ -95,9 +92,10 @@ const AsyncClient = struct {
impl: YieldImpl, impl: YieldImpl,
err: ?anyerror = null, err: ?anyerror = null,
buf: [2014]u8 = undefined,
pub fn deinit(self: *AsyncRequest) void { pub fn deinit(self: *AsyncRequest) void {
if (self.req) |*r| r.deinit(); if (self.req) |*r| r.deinit();
self.headers.deinit();
} }
pub fn fetch(self: *AsyncRequest) void { pub fn fetch(self: *AsyncRequest) void {
@@ -116,11 +114,13 @@ const AsyncClient = struct {
switch (self.state) { switch (self.state) {
.new => { .new => {
self.state = .open; self.state = .open;
self.req = self.cli.open(.GET, self.uri, self.headers, .{}) catch |e| return self.onerr(e); self.req = self.cli.open(.GET, self.uri, .{
.server_header_buffer = &self.buf,
}) catch |e| return self.onerr(e);
}, },
.open => { .open => {
self.state = .send; self.state = .send;
self.req.?.send(.{}) catch |e| return self.onerr(e); self.req.?.send() catch |e| return self.onerr(e);
}, },
.send => { .send => {
self.state = .finish; self.state = .finish;
@@ -164,7 +164,6 @@ const AsyncClient = struct {
.impl = YieldImpl.init(self.cli.loop), .impl = YieldImpl.init(self.cli.loop),
.cli = &self.cli, .cli = &self.cli,
.uri = uri, .uri = uri,
.headers = .{ .allocator = self.cli.allocator, .owned = false },
}; };
} }
}; };

View File

@@ -21,7 +21,7 @@ const builtin = @import("builtin");
const Types = @import("root").Types; const Types = @import("root").Types;
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const Loader = @import("loader.zig").Loader; const Loader = @import("loader.zig").Loader;
const Dump = @import("dump.zig"); const Dump = @import("dump.zig");
const Mime = @import("mime.zig"); const Mime = @import("mime.zig");
@@ -224,7 +224,7 @@ pub const Page = struct {
// own the url // own the url
if (self.rawuri) |prev| alloc.free(prev); if (self.rawuri) |prev| alloc.free(prev);
self.rawuri = try alloc.dupe(u8, uri); self.rawuri = try alloc.dupe(u8, uri);
self.uri = std.Uri.parse(self.rawuri.?) catch try std.Uri.parseWithoutScheme(self.rawuri.?); self.uri = std.Uri.parse(self.rawuri.?) catch try std.Uri.parseAfterScheme("", self.rawuri.?);
// prepare origin value. // prepare origin value.
var buf = std.ArrayList(u8).init(alloc); var buf = std.ArrayList(u8).init(alloc);
@@ -247,29 +247,39 @@ pub const Page = struct {
// TODO handle redirection // TODO handle redirection
if (req.response.status != .ok) { if (req.response.status != .ok) {
log.debug("{?} {d} {s}\n{any}", .{ log.debug("{?} {d} {s}", .{
req.response.version, req.response.version,
req.response.status, req.response.status,
req.response.reason, req.response.reason,
req.response.headers, // TODO log headers
}); });
return error.BadStatusCode; return error.BadStatusCode;
} }
// TODO handle charset // TODO handle charset
// https://html.spec.whatwg.org/#content-type // https://html.spec.whatwg.org/#content-type
const ct = req.response.headers.getFirstValue("Content-Type") orelse { var it = req.response.iterateHeaders();
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);
}
}
if (ct == null) {
// 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;
}; }
log.debug("header content-type: {s}", .{ct}); defer alloc.free(ct.?);
const mime = try Mime.parse(ct);
log.debug("header content-type: {s}", .{ct.?});
const mime = try Mime.parse(ct.?);
if (mime.eql(Mime.HTML)) { if (mime.eql(Mime.HTML)) {
try self.loadHTMLDoc(req.reader(), mime.charset orelse "utf-8"); try self.loadHTMLDoc(req.reader(), mime.charset orelse "utf-8");
} 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(alloc, 16 * 1024 * 1024);
@@ -500,22 +510,27 @@ pub const Page = struct {
log.debug("starting fetch script {s}", .{src}); log.debug("starting fetch script {s}", .{src});
const u = std.Uri.parse(src) catch try std.Uri.parseWithoutScheme(src); var buffer: [1024]u8 = undefined;
const ru = try std.Uri.resolve(self.uri, u, false, alloc); var b: []u8 = buffer[0..];
const u = try std.Uri.resolve_inplace(self.uri, src, &b);
var fetchres = try self.session.loader.fetch(alloc, ru); var fetchres = try self.session.loader.get(alloc, u);
defer fetchres.deinit(); defer fetchres.deinit();
log.info("fech script {any}: {d}", .{ ru, fetchres.status }); const resp = fetchres.req.response;
if (fetchres.status != .ok) return FetchError.BadStatusCode; log.info("fech script {any}: {d}", .{ u, resp.status });
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);
defer alloc.free(body);
// check no body // check no body
if (fetchres.body == null) return FetchError.NoBody; if (body.len == 0) return FetchError.NoBody;
var res = try self.session.env.execTryCatch(alloc, fetchres.body.?, src); var res = try self.session.env.execTryCatch(alloc, body, src);
defer res.deinit(alloc); defer res.deinit(alloc);
if (res.success) { if (res.success) {

View File

@@ -19,7 +19,7 @@
const std = @import("std"); const std = @import("std");
const File = std.fs.File; const File = std.fs.File;
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const Walker = @import("../dom/walker.zig").WalkerChildren; const Walker = @import("../dom/walker.zig").WalkerChildren;
// writer must be a std.io.Writer // writer must be a std.io.Writer

View File

@@ -22,6 +22,7 @@ const user_agent = "Lightpanda.io/1.0";
pub const Loader = struct { pub const Loader = struct {
client: std.http.Client, client: std.http.Client,
server_header_buffer: [1024]u8 = undefined,
pub const Response = struct { pub const Response = struct {
alloc: std.mem.Allocator, alloc: std.mem.Allocator,
@@ -45,46 +46,30 @@ pub const Loader = struct {
self.client.deinit(); self.client.deinit();
} }
// the caller must deinit the FetchResult.
pub fn fetch(self: *Loader, alloc: std.mem.Allocator, uri: std.Uri) !std.http.Client.FetchResult {
var headers = try std.http.Headers.initList(alloc, &[_]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(alloc, .{
.location = .{ .uri = uri },
.headers = headers,
.payload = .none,
});
}
// see // see
// https://ziglang.org/documentation/master/std/#A;std:http.Client.fetch // https://ziglang.org/documentation/master/std/#A;std:http.Client.fetch
// for reference. // for reference.
// The caller is responsible for calling `deinit()` on the `Response`. // The caller is responsible for calling `deinit()` on the `Response`.
pub fn get(self: *Loader, alloc: std.mem.Allocator, uri: std.Uri) !Response { pub fn get(self: *Loader, alloc: std.mem.Allocator, uri: std.Uri) !Response {
var headers = try std.http.Headers.initList(alloc, &[_]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();
var resp = Response{ var resp = Response{
.alloc = alloc, .alloc = alloc,
.req = try alloc.create(std.http.Client.Request), .req = try alloc.create(std.http.Client.Request),
}; };
errdefer alloc.destroy(resp.req); errdefer alloc.destroy(resp.req);
resp.req.* = try self.client.open(.GET, uri, headers, .{ resp.req.* = try self.client.open(.GET, uri, .{
.handle_redirects = true, // TODO handle redirects manually .headers = .{
.user_agent = .{ .override = user_agent },
},
.extra_headers = &.{
.{ .name = "Accept", .value = "*/*" },
.{ .name = "Accept-Language", .value = "en-US,en;q=0.5" },
},
.server_header_buffer = &self.server_header_buffer,
}); });
errdefer resp.req.deinit(); errdefer resp.req.deinit();
try resp.req.send(.{}); try resp.req.send();
try resp.req.finish(); try resp.req.finish();
try resp.req.wait(); try resp.req.wait();
@@ -92,13 +77,13 @@ pub const Loader = struct {
} }
}; };
test "basic url fetch" { test "basic url get" {
const alloc = std.testing.allocator; const alloc = std.testing.allocator;
var loader = Loader.init(alloc); var loader = Loader.init(alloc);
defer loader.deinit(); defer loader.deinit();
var result = try loader.fetch(alloc, "https://en.wikipedia.org/wiki/Main_Page"); var result = try loader.get(alloc, "https://en.wikipedia.org/wiki/Main_Page");
defer result.deinit(); defer result.deinit();
try std.testing.expect(result.status == std.http.Status.ok); try std.testing.expect(result.req.response.status == std.http.Status.ok);
} }

View File

@@ -18,7 +18,7 @@
const std = @import("std"); const std = @import("std");
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
// Node implementation with Netsurf Libdom C lib. // Node implementation with Netsurf Libdom C lib.
pub const Node = struct { pub const Node = struct {

View File

@@ -19,7 +19,7 @@
const std = @import("std"); const std = @import("std");
const css = @import("css.zig"); const css = @import("css.zig");
const Node = @import("libdom.zig").Node; const Node = @import("libdom.zig").Node;
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const Matcher = struct { const Matcher = struct {
const Nodes = std.ArrayList(Node); const Nodes = std.ArrayList(Node);

View File

@@ -22,7 +22,7 @@ const jsruntime = @import("jsruntime");
const Case = jsruntime.test_utils.Case; const Case = jsruntime.test_utils.Case;
const checkCases = jsruntime.test_utils.checkCases; const checkCases = jsruntime.test_utils.checkCases;
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const Node = @import("node.zig").Node; const Node = @import("node.zig").Node;
const DOMException = @import("exceptions.zig").DOMException; const DOMException = @import("exceptions.zig").DOMException;

View File

@@ -18,7 +18,7 @@
const std = @import("std"); const std = @import("std");
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const Text = @import("text.zig").Text; const Text = @import("text.zig").Text;

View File

@@ -23,7 +23,7 @@ const Case = jsruntime.test_utils.Case;
const checkCases = jsruntime.test_utils.checkCases; const checkCases = jsruntime.test_utils.checkCases;
const generate = @import("../generate.zig"); const generate = @import("../generate.zig");
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const Node = @import("node.zig").Node; const Node = @import("node.zig").Node;
const Comment = @import("comment.zig").Comment; const Comment = @import("comment.zig").Comment;

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const jsruntime = @import("jsruntime"); const jsruntime = @import("jsruntime");
const Case = jsruntime.test_utils.Case; const Case = jsruntime.test_utils.Case;

View File

@@ -18,7 +18,7 @@
const std = @import("std"); const std = @import("std");
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const css = @import("../css/css.zig"); const css = @import("../css/css.zig");
const Node = @import("../css/libdom.zig").Node; const Node = @import("../css/libdom.zig").Node;

View File

@@ -18,7 +18,7 @@
const std = @import("std"); const std = @import("std");
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const jsruntime = @import("jsruntime"); const jsruntime = @import("jsruntime");
const Case = jsruntime.test_utils.Case; const Case = jsruntime.test_utils.Case;
@@ -449,7 +449,7 @@ pub fn testExecFn(
try checkCases(js_env, &adoptNode); try checkCases(js_env, &adoptNode);
const tags = comptime parser.Tag.all(); const tags = comptime parser.Tag.all();
comptime var createElements: [(tags.len) * 2]Case = undefined; var createElements: [(tags.len) * 2]Case = undefined;
inline for (tags, 0..) |tag, i| { inline for (tags, 0..) |tag, i| {
const tag_name = @tagName(tag); const tag_name = @tagName(tag);
createElements[i * 2] = Case{ createElements[i * 2] = Case{

View File

@@ -18,7 +18,7 @@
const std = @import("std"); const std = @import("std");
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const jsruntime = @import("jsruntime"); const jsruntime = @import("jsruntime");
const Case = jsruntime.test_utils.Case; const Case = jsruntime.test_utils.Case;

View File

@@ -18,7 +18,7 @@
const std = @import("std"); const std = @import("std");
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const Node = @import("node.zig").Node; const Node = @import("node.zig").Node;

View File

@@ -18,7 +18,7 @@
const std = @import("std"); const std = @import("std");
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const jsruntime = @import("jsruntime"); const jsruntime = @import("jsruntime");
const Case = jsruntime.test_utils.Case; const Case = jsruntime.test_utils.Case;

View File

@@ -24,7 +24,8 @@ const JSObjectID = jsruntime.JSObjectID;
const Case = jsruntime.test_utils.Case; const Case = jsruntime.test_utils.Case;
const checkCases = jsruntime.test_utils.checkCases; const checkCases = jsruntime.test_utils.checkCases;
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const EventHandler = @import("../events/event.zig").EventHandler;
const DOMException = @import("exceptions.zig").DOMException; const DOMException = @import("exceptions.zig").DOMException;
const Nod = @import("node.zig"); const Nod = @import("node.zig");
@@ -74,6 +75,7 @@ pub const EventTarget = struct {
eventType, eventType,
cbk, cbk,
capture orelse false, capture orelse false,
EventHandler,
); );
} }

View File

@@ -23,7 +23,7 @@ const jsruntime = @import("jsruntime");
const Case = jsruntime.test_utils.Case; const Case = jsruntime.test_utils.Case;
const checkCases = jsruntime.test_utils.checkCases; const checkCases = jsruntime.test_utils.checkCases;
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
// https://webidl.spec.whatwg.org/#idl-DOMException // https://webidl.spec.whatwg.org/#idl-DOMException
pub const DOMException = struct { pub const DOMException = struct {

View File

@@ -18,7 +18,7 @@
const std = @import("std"); const std = @import("std");
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const jsruntime = @import("jsruntime"); const jsruntime = @import("jsruntime");
const Case = jsruntime.test_utils.Case; const Case = jsruntime.test_utils.Case;

View File

@@ -18,7 +18,7 @@
const std = @import("std"); const std = @import("std");
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const jsruntime = @import("jsruntime"); const jsruntime = @import("jsruntime");
const Case = jsruntime.test_utils.Case; const Case = jsruntime.test_utils.Case;

View File

@@ -18,7 +18,7 @@
const std = @import("std"); const std = @import("std");
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const jsruntime = @import("jsruntime"); const jsruntime = @import("jsruntime");
const Case = jsruntime.test_utils.Case; const Case = jsruntime.test_utils.Case;

View File

@@ -26,7 +26,7 @@ const Variadic = jsruntime.Variadic;
const generate = @import("../generate.zig"); const generate = @import("../generate.zig");
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const EventTarget = @import("event_target.zig").EventTarget; const EventTarget = @import("event_target.zig").EventTarget;

View File

@@ -18,7 +18,7 @@
const std = @import("std"); const std = @import("std");
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const jsruntime = @import("jsruntime"); const jsruntime = @import("jsruntime");
const Case = jsruntime.test_utils.Case; const Case = jsruntime.test_utils.Case;

View File

@@ -22,7 +22,7 @@ const jsruntime = @import("jsruntime");
const Case = jsruntime.test_utils.Case; const Case = jsruntime.test_utils.Case;
const checkCases = jsruntime.test_utils.checkCases; const checkCases = jsruntime.test_utils.checkCases;
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const Node = @import("node.zig").Node; const Node = @import("node.zig").Node;
// https://dom.spec.whatwg.org/#processinginstruction // https://dom.spec.whatwg.org/#processinginstruction

View File

@@ -23,7 +23,7 @@ const Case = jsruntime.test_utils.Case;
const checkCases = jsruntime.test_utils.checkCases; const checkCases = jsruntime.test_utils.checkCases;
const generate = @import("../generate.zig"); const generate = @import("../generate.zig");
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const CharacterData = @import("character_data.zig").CharacterData; const CharacterData = @import("character_data.zig").CharacterData;
const CDATASection = @import("cdata_section.zig").CDATASection; const CDATASection = @import("cdata_section.zig").CDATASection;

View File

@@ -18,7 +18,7 @@
const std = @import("std"); const std = @import("std");
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const jsruntime = @import("jsruntime"); const jsruntime = @import("jsruntime");
const Case = jsruntime.test_utils.Case; const Case = jsruntime.test_utils.Case;

View File

@@ -18,7 +18,7 @@
const std = @import("std"); const std = @import("std");
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
pub const Walker = union(enum) { pub const Walker = union(enum) {
walkerDepthFirst: WalkerDepthFirst, walkerDepthFirst: WalkerDepthFirst,

View File

@@ -22,10 +22,11 @@ const generate = @import("../generate.zig");
const jsruntime = @import("jsruntime"); const jsruntime = @import("jsruntime");
const Callback = jsruntime.Callback; const Callback = jsruntime.Callback;
const CallbackResult = jsruntime.CallbackResult;
const Case = jsruntime.test_utils.Case; const Case = jsruntime.test_utils.Case;
const checkCases = jsruntime.test_utils.checkCases; const checkCases = jsruntime.test_utils.checkCases;
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const DOMException = @import("../dom/exceptions.zig").DOMException; const DOMException = @import("../dom/exceptions.zig").DOMException;
const EventTarget = @import("../dom/event_target.zig").EventTarget; const EventTarget = @import("../dom/event_target.zig").EventTarget;
@@ -33,6 +34,8 @@ const EventTargetUnion = @import("../dom/event_target.zig").Union;
const ProgressEvent = @import("../xhr/progress_event.zig").ProgressEvent; const ProgressEvent = @import("../xhr/progress_event.zig").ProgressEvent;
const log = std.log.scoped(.events);
// Event interfaces // Event interfaces
pub const Interfaces = generate.Tuple(.{ pub const Interfaces = generate.Tuple(.{
Event, Event,
@@ -236,3 +239,33 @@ pub fn testExecFn(
}; };
try checkCases(js_env, &remove); try checkCases(js_env, &remove);
} }
pub const EventHandler = struct {
fn handle(event: ?*parser.Event, data: ?*anyopaque) callconv(.C) void {
if (data) |d| {
const func = parser.event_handler_cbk(d);
// TODO get the allocator by another way?
var res = CallbackResult.init(func.nat_ctx.alloc);
defer res.deinit();
if (event) |evt| {
func.trycall(.{
Event.toInterface(evt) catch unreachable,
}, &res) catch |e| log.err("event handler error: {any}", .{e});
} else {
func.trycall(.{event}, &res) catch |e| log.err("event handler error: {any}", .{e});
}
// in case of function error, we log the result and the trace.
if (!res.success) {
log.info("event handler error: {s}", .{res.result orelse "unknown"});
log.debug("{s}", .{res.stack orelse "no stack trace"});
}
// NOTE: we can not call func.deinit here
// b/c the handler can be called several times
// either on this dispatch event or in anoter one
}
}
}.handle;

View File

@@ -35,9 +35,9 @@ fn itoa(comptime i: u8) ![]const u8 {
return try std.fmt.bufPrint(buf[0..], "{d}", .{i}); return try std.fmt.bufPrint(buf[0..], "{d}", .{i});
} }
fn fmtName(comptime T: type) []const u8 { fn fmtName(comptime T: type) [:0]const u8 {
var it = std.mem.splitBackwards(u8, @typeName(T), "."); var it = std.mem.splitBackwards(u8, @typeName(T), ".");
return it.first(); return it.first() ++ "";
} }
// Union // Union
@@ -168,7 +168,11 @@ pub const Union = struct {
T = *T; T = *T;
} }
union_fields[done] = .{ union_fields[done] = .{
.name = fmtName(member_T), // UnionField.name expect a null terminated string.
// concatenate the `[]const u8` string with an empty string
// literal (`name ++ ""`) to explicitly coerce it to `[:0]const
// u8`.
.name = fmtName(member_T) ++ "",
.type = T, .type = T,
.alignment = @alignOf(T), .alignment = @alignOf(T),
}; };
@@ -176,7 +180,7 @@ pub const Union = struct {
} }
} }
const union_info = std.builtin.Type.Union{ const union_info = std.builtin.Type.Union{
.layout = .Auto, .layout = .auto,
.tag_type = enum_T, .tag_type = enum_T,
.fields = &union_fields, .fields = &union_fields,
.decls = &decls, .decls = &decls,
@@ -286,7 +290,11 @@ fn TupleT(comptime tuple: anytype) type {
continue; continue;
} }
fields[done] = .{ fields[done] = .{
.name = try itoa(done), // StructField.name expect a null terminated string.
// concatenate the `[]const u8` string with an empty string
// literal (`name ++ ""`) to explicitly coerce it to `[:0]const
// u8`.
.name = try itoa(done) ++ "",
.type = type, .type = type,
.default_value = null, .default_value = null,
.is_comptime = false, .is_comptime = false,
@@ -296,7 +304,7 @@ fn TupleT(comptime tuple: anytype) type {
} }
const decls: [0]std.builtin.Type.Declaration = undefined; const decls: [0]std.builtin.Type.Declaration = undefined;
const info = std.builtin.Type.Struct{ const info = std.builtin.Type.Struct{
.layout = .Auto, .layout = .auto,
.fields = &fields, .fields = &fields,
.decls = &decls, .decls = &decls,
.is_tuple = true, .is_tuple = true,

View File

@@ -18,7 +18,7 @@
const std = @import("std"); const std = @import("std");
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const jsruntime = @import("jsruntime"); const jsruntime = @import("jsruntime");
const Case = jsruntime.test_utils.Case; const Case = jsruntime.test_utils.Case;

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const generate = @import("../generate.zig"); const generate = @import("../generate.zig");
const jsruntime = @import("jsruntime"); const jsruntime = @import("jsruntime");
@@ -246,10 +246,10 @@ pub const HTMLAnchorElement = struct {
defer u.deinit(alloc); defer u.deinit(alloc);
if (p) |pp| { if (p) |pp| {
u.uri.host = h; u.uri.host = .{ .raw = h };
u.uri.port = pp; u.uri.port = pp;
} else { } else {
u.uri.host = v; u.uri.host = .{ .raw = v };
u.uri.port = null; u.uri.port = null;
} }
@@ -271,7 +271,7 @@ pub const HTMLAnchorElement = struct {
var u = try url(self, alloc); var u = try url(self, alloc);
defer u.deinit(alloc); defer u.deinit(alloc);
u.uri.host = v; u.uri.host = .{ .raw = v };
const href = try u.format(alloc); const href = try u.format(alloc);
try parser.anchorSetHref(self, href); try parser.anchorSetHref(self, href);
} }
@@ -312,7 +312,11 @@ pub const HTMLAnchorElement = struct {
var u = try url(self, alloc); var u = try url(self, alloc);
defer u.deinit(alloc); defer u.deinit(alloc);
u.uri.user = v; if (v) |vv| {
u.uri.user = .{ .raw = vv };
} else {
u.uri.user = null;
}
const href = try u.format(alloc); const href = try u.format(alloc);
defer alloc.free(href); defer alloc.free(href);
@@ -331,7 +335,11 @@ pub const HTMLAnchorElement = struct {
var u = try url(self, alloc); var u = try url(self, alloc);
defer u.deinit(alloc); defer u.deinit(alloc);
u.uri.password = v; if (v) |vv| {
u.uri.password = .{ .raw = vv };
} else {
u.uri.password = null;
}
const href = try u.format(alloc); const href = try u.format(alloc);
defer alloc.free(href); defer alloc.free(href);
@@ -350,7 +358,7 @@ pub const HTMLAnchorElement = struct {
var u = try url(self, alloc); var u = try url(self, alloc);
defer u.deinit(alloc); defer u.deinit(alloc);
u.uri.path = v; u.uri.path = .{ .raw = v };
const href = try u.format(alloc); const href = try u.format(alloc);
defer alloc.free(href); defer alloc.free(href);
@@ -369,7 +377,11 @@ pub const HTMLAnchorElement = struct {
var u = try url(self, alloc); var u = try url(self, alloc);
defer u.deinit(alloc); defer u.deinit(alloc);
u.uri.query = v; if (v) |vv| {
u.uri.query = .{ .raw = vv };
} else {
u.uri.query = null;
}
const href = try u.format(alloc); const href = try u.format(alloc);
defer alloc.free(href); defer alloc.free(href);
@@ -388,7 +400,11 @@ pub const HTMLAnchorElement = struct {
var u = try url(self, alloc); var u = try url(self, alloc);
defer u.deinit(alloc); defer u.deinit(alloc);
u.uri.fragment = v; if (v) |vv| {
u.uri.fragment = .{ .raw = vv };
} else {
u.uri.fragment = null;
}
const href = try u.format(alloc); const href = try u.format(alloc);
defer alloc.free(href); defer alloc.free(href);

View File

@@ -18,7 +18,7 @@
const std = @import("std"); const std = @import("std");
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const EventTarget = @import("../dom/event_target.zig").EventTarget; const EventTarget = @import("../dom/event_target.zig").EventTarget;

View File

@@ -20,7 +20,7 @@ const std = @import("std");
const jsruntime = @import("jsruntime"); const jsruntime = @import("jsruntime");
const parser = @import("netsurf.zig"); const parser = @import("netsurf");
const apiweb = @import("apiweb.zig"); const apiweb = @import("apiweb.zig");
const Window = @import("html/window.zig").Window; const Window = @import("html/window.zig").Window;
@@ -30,7 +30,7 @@ pub const UserContext = apiweb.UserContext;
const socket_path = "/tmp/browsercore-server.sock"; const socket_path = "/tmp/browsercore-server.sock";
var doc: *parser.DocumentHTML = undefined; var doc: *parser.DocumentHTML = undefined;
var server: std.net.StreamServer = undefined; var server: std.net.Server = undefined;
fn execJS( fn execJS(
alloc: std.mem.Allocator, alloc: std.mem.Allocator,
@@ -91,7 +91,7 @@ pub fn main() !void {
// reuse_address (SO_REUSEADDR flag) does not seems to work on unix socket // reuse_address (SO_REUSEADDR flag) does not seems to work on unix socket
// see: https://gavv.net/articles/unix-socket-reuse/ // see: https://gavv.net/articles/unix-socket-reuse/
// TODO: use a lock file instead // TODO: use a lock file instead
std.os.unlink(socket_path) catch |err| { std.posix.unlink(socket_path) catch |err| {
if (err != error.FileNotFound) { if (err != error.FileNotFound) {
return err; return err;
} }
@@ -99,9 +99,8 @@ pub fn main() !void {
// server // server
const addr = try std.net.Address.initUnix(socket_path); const addr = try std.net.Address.initUnix(socket_path);
server = std.net.StreamServer.init(.{}); server = try addr.listen(.{});
defer server.deinit(); defer server.deinit();
try server.listen(addr);
std.debug.print("Listening on: {s}...\n", .{socket_path}); std.debug.print("Listening on: {s}...\n", .{socket_path});
try jsruntime.loadEnv(&arena, null, execJS); try jsruntime.loadEnv(&arena, null, execJS);

View File

@@ -25,8 +25,8 @@ const apiweb = @import("apiweb.zig");
pub const Types = jsruntime.reflect(apiweb.Interfaces); pub const Types = jsruntime.reflect(apiweb.Interfaces);
pub const UserContext = apiweb.UserContext; pub const UserContext = apiweb.UserContext;
pub const std_options = struct { pub const std_options = std.Options{
pub const log_level = .debug; .log_level = .debug,
}; };
const usage = const usage =
@@ -58,7 +58,7 @@ pub fn main() !void {
while (args.next()) |arg| { while (args.next()) |arg| {
if (std.mem.eql(u8, "-h", arg) or std.mem.eql(u8, "--help", arg)) { if (std.mem.eql(u8, "-h", arg) or std.mem.eql(u8, "--help", arg)) {
try std.io.getStdErr().writer().print(usage, .{execname}); try std.io.getStdErr().writer().print(usage, .{execname});
std.os.exit(0); std.posix.exit(0);
} }
if (std.mem.eql(u8, "--dump", arg)) { if (std.mem.eql(u8, "--dump", arg)) {
dump = true; dump = true;
@@ -67,14 +67,14 @@ pub fn main() !void {
// allow only one url // allow only one url
if (url.len != 0) { if (url.len != 0) {
try std.io.getStdErr().writer().print(usage, .{execname}); try std.io.getStdErr().writer().print(usage, .{execname});
std.os.exit(1); std.posix.exit(1);
} }
url = arg; url = arg;
} }
if (url.len == 0) { if (url.len == 0) {
try std.io.getStdErr().writer().print(usage, .{execname}); try std.io.getStdErr().writer().print(usage, .{execname});
std.os.exit(1); std.posix.exit(1);
} }
const vm = jsruntime.VM.init(); const vm = jsruntime.VM.init();

View File

@@ -20,7 +20,7 @@ const std = @import("std");
const jsruntime = @import("jsruntime"); const jsruntime = @import("jsruntime");
const parser = @import("netsurf.zig"); const parser = @import("netsurf");
const apiweb = @import("apiweb.zig"); const apiweb = @import("apiweb.zig");
const Window = @import("html/window.zig").Window; const Window = @import("html/window.zig").Window;
const storage = @import("storage/storage.zig"); const storage = @import("storage/storage.zig");

View File

@@ -76,7 +76,7 @@ pub fn main() !void {
while (args.next()) |arg| { while (args.next()) |arg| {
if (std.mem.eql(u8, "-h", arg) or std.mem.eql(u8, "--help", arg)) { if (std.mem.eql(u8, "-h", arg) or std.mem.eql(u8, "--help", arg)) {
try std.io.getStdErr().writer().print(usage, .{execname}); try std.io.getStdErr().writer().print(usage, .{execname});
std.os.exit(0); std.posix.exit(0);
} }
if (std.mem.eql(u8, "--json", arg)) { if (std.mem.eql(u8, "--json", arg)) {
out = .json; out = .json;
@@ -214,12 +214,12 @@ pub fn main() !void {
} }
try std.json.stringify(output.items, .{ .whitespace = .indent_2 }, std.io.getStdOut().writer()); try std.json.stringify(output.items, .{ .whitespace = .indent_2 }, std.io.getStdOut().writer());
std.os.exit(0); std.posix.exit(0);
} }
if (out == .text and failures > 0) { if (out == .text and failures > 0) {
std.debug.print("{d}/{d} tests suites failures\n", .{ failures, run }); std.debug.print("{d}/{d} tests suites failures\n", .{ failures, run });
std.os.exit(1); std.posix.exit(1);
} }
} }

View File

@@ -26,13 +26,9 @@ const c = @cImport({
@cInclude("events/event.h"); @cInclude("events/event.h");
}); });
const mimalloc = @import("mimalloc.zig"); const mimalloc = @import("mimalloc");
const Callback = @import("jsruntime").Callback; const Callback = @import("jsruntime").Callback;
const CallbackResult = @import("jsruntime").CallbackResult;
const EventToInterface = @import("events/event.zig").Event.toInterface;
const log = std.log.scoped(.netsurf);
// init initializes netsurf lib. // init initializes netsurf lib.
// init starts a mimalloc heap arena for the netsurf session. The caller must // init starts a mimalloc heap arena for the netsurf session. The caller must
@@ -265,8 +261,8 @@ pub const Tag = enum(u8) {
pub fn all() []Tag { pub fn all() []Tag {
comptime { comptime {
const info = @typeInfo(Tag).Enum; const info = @typeInfo(Tag).Enum;
comptime var l: [info.fields.len]Tag = undefined; var l: [info.fields.len]Tag = undefined;
inline for (info.fields, 0..) |field, i| { for (info.fields, 0..) |field, i| {
l[i] = @as(Tag, @enumFromInt(field.value)); l[i] = @as(Tag, @enumFromInt(field.value));
} }
return &l; return &l;
@@ -277,7 +273,7 @@ pub const Tag = enum(u8) {
comptime { comptime {
const tags = all(); const tags = all();
var names: [tags.len][]const u8 = undefined; var names: [tags.len][]const u8 = undefined;
inline for (tags, 0..) |tag, i| { for (tags, 0..) |tag, i| {
names[i] = tag.elementName(); names[i] = tag.elementName();
} }
return &names; return &names;
@@ -527,41 +523,11 @@ pub const EventType = enum(u8) {
}; };
// EventHandler // EventHandler
fn event_handler_cbk(data: *anyopaque) *Callback { pub fn event_handler_cbk(data: *anyopaque) *Callback {
const ptr: *align(@alignOf(*Callback)) anyopaque = @alignCast(data); const ptr: *align(@alignOf(*Callback)) anyopaque = @alignCast(data);
return @as(*Callback, @ptrCast(ptr)); return @as(*Callback, @ptrCast(ptr));
} }
const event_handler = struct {
fn handle(event: ?*Event, data: ?*anyopaque) callconv(.C) void {
if (data) |d| {
const func = event_handler_cbk(d);
// TODO get the allocator by another way?
var res = CallbackResult.init(func.nat_ctx.alloc);
defer res.deinit();
if (event) |evt| {
func.trycall(.{
EventToInterface(evt) catch unreachable,
}, &res) catch {};
} else {
func.trycall(.{event}, &res) catch {};
}
// in case of function error, we log the result and the trace.
if (!res.success) {
log.info("event handler error: {s}", .{res.result orelse "unknown"});
log.debug("{s}", .{res.stack orelse "no stack trace"});
}
// NOTE: we can not call func.deinit here
// b/c the handler can be called several times
// either on this dispatch event or in anoter one
}
}
}.handle;
// EventListener // EventListener
pub const EventListener = c.dom_event_listener; pub const EventListener = c.dom_event_listener;
const EventListenerEntry = c.listener_entry; const EventListenerEntry = c.listener_entry;
@@ -642,12 +608,15 @@ pub fn eventTargetHasListener(
return null; return null;
} }
const EventHandler = fn (event: ?*Event, data: ?*anyopaque) callconv(.C) void;
pub fn eventTargetAddEventListener( pub fn eventTargetAddEventListener(
et: *EventTarget, et: *EventTarget,
alloc: std.mem.Allocator, alloc: std.mem.Allocator,
typ: []const u8, typ: []const u8,
cbk: Callback, cbk: Callback,
capture: bool, capture: bool,
handler: EventHandler,
) !void { ) !void {
// this allocation will be removed either on // this allocation will be removed either on
// eventTargetRemoveEventListener or eventTargetRemoveAllEventListeners // eventTargetRemoveEventListener or eventTargetRemoveAllEventListeners
@@ -661,7 +630,7 @@ pub fn eventTargetAddEventListener(
const ctx = @as(*anyopaque, @ptrCast(cbk_ptr)); const ctx = @as(*anyopaque, @ptrCast(cbk_ptr));
var listener: ?*EventListener = undefined; var listener: ?*EventListener = undefined;
const errLst = c.dom_event_listener_create(event_handler, ctx, &listener); const errLst = c.dom_event_listener_create(handler, ctx, &listener);
try DOMErr(errLst); try DOMErr(errLst);
defer c.dom_event_listener_unref(listener); defer c.dom_event_listener_unref(listener);

View File

@@ -23,7 +23,7 @@ const jsruntime = @import("jsruntime");
const generate = @import("generate.zig"); const generate = @import("generate.zig");
const pretty = @import("pretty"); const pretty = @import("pretty");
const parser = @import("netsurf.zig"); const parser = @import("netsurf");
const apiweb = @import("apiweb.zig"); const apiweb = @import("apiweb.zig");
const Window = @import("html/window.zig").Window; const Window = @import("html/window.zig").Window;
const xhr = @import("xhr/xhr.zig"); const xhr = @import("xhr/xhr.zig");
@@ -182,7 +182,7 @@ pub fn main() !void {
while (args.next()) |arg| { while (args.next()) |arg| {
if (std.mem.eql(u8, "-h", arg) or std.mem.eql(u8, "--help", arg)) { if (std.mem.eql(u8, "-h", arg) or std.mem.eql(u8, "--help", arg)) {
try std.io.getStdErr().writer().print(usage, .{}); try std.io.getStdErr().writer().print(usage, .{});
std.os.exit(0); std.posix.exit(0);
} }
if (std.mem.eql(u8, "--json", arg)) { if (std.mem.eql(u8, "--json", arg)) {
out = .json; out = .json;

View File

@@ -23,7 +23,7 @@ const Case = jsruntime.test_utils.Case;
const checkCases = jsruntime.test_utils.checkCases; const checkCases = jsruntime.test_utils.checkCases;
const generate = @import("../generate.zig"); const generate = @import("../generate.zig");
const DOMError = @import("../netsurf.zig").DOMError; const DOMError = @import("netsurf").DOMError;
const log = std.log.scoped(.storage); const log = std.log.scoped(.storage);

View File

@@ -62,7 +62,10 @@ pub const URL = struct {
return .{ return .{
.rawuri = raw, .rawuri = raw,
.uri = uri, .uri = uri,
.search_params = try URLSearchParams.constructor(alloc, uri.query), .search_params = try URLSearchParams.constructor(
alloc,
uriComponentNullStr(uri.query),
),
}; };
} }
@@ -102,7 +105,7 @@ pub const URL = struct {
var q = std.ArrayList(u8).init(alloc); var q = std.ArrayList(u8).init(alloc);
defer q.deinit(); defer q.deinit();
try self.search_params.values.encode(q.writer()); try self.search_params.values.encode(q.writer());
self.uri.query = q.items; self.uri.query = .{ .percent_encoded = q.items };
return try self.format(alloc); return try self.format(alloc);
} }
@@ -116,9 +119,9 @@ pub const URL = struct {
.scheme = true, .scheme = true,
.authentication = true, .authentication = true,
.authority = true, .authority = true,
.path = self.uri.path.len > 0, .path = uriComponentNullStr(self.uri.path).len > 0,
.query = self.uri.query != null and self.uri.query.?.len > 0, .query = uriComponentNullStr(self.uri.query).len > 0,
.fragment = self.uri.fragment != null and self.uri.fragment.?.len > 0, .fragment = uriComponentNullStr(self.uri.fragment).len > 0,
}, buf.writer()); }, buf.writer());
return try buf.toOwnedSlice(); return try buf.toOwnedSlice();
} }
@@ -131,11 +134,11 @@ pub const URL = struct {
} }
pub fn get_username(self: *URL) []const u8 { pub fn get_username(self: *URL) []const u8 {
return self.uri.user orelse ""; return uriComponentNullStr(self.uri.user);
} }
pub fn get_password(self: *URL) []const u8 { pub fn get_password(self: *URL) []const u8 {
return self.uri.password orelse ""; return uriComponentNullStr(self.uri.password);
} }
// the caller must free the returned string. // the caller must free the returned string.
@@ -157,7 +160,7 @@ pub const URL = struct {
} }
pub fn get_hostname(self: *URL) []const u8 { pub fn get_hostname(self: *URL) []const u8 {
return self.uri.host orelse ""; return uriComponentNullStr(self.uri.host);
} }
// the caller must free the returned string. // the caller must free the returned string.
@@ -174,8 +177,8 @@ pub const URL = struct {
} }
pub fn get_pathname(self: *URL) []const u8 { pub fn get_pathname(self: *URL) []const u8 {
if (self.uri.path.len == 0) return "/"; if (uriComponentStr(self.uri.path).len == 0) return "/";
return self.uri.path; return uriComponentStr(self.uri.path);
} }
// the caller must free the returned string. // the caller must free the returned string.
@@ -198,7 +201,7 @@ pub const URL = struct {
pub fn get_hash(self: *URL, alloc: std.mem.Allocator) ![]const u8 { pub fn get_hash(self: *URL, alloc: std.mem.Allocator) ![]const u8 {
if (self.uri.fragment == null) return try alloc.dupe(u8, ""); if (self.uri.fragment == null) return try alloc.dupe(u8, "");
return try std.mem.concat(alloc, u8, &[_][]const u8{ "#", self.uri.fragment.? }); return try std.mem.concat(alloc, u8, &[_][]const u8{ "#", uriComponentNullStr(self.uri.fragment) });
} }
pub fn get_searchParams(self: *URL) *URLSearchParams { pub fn get_searchParams(self: *URL) *URLSearchParams {
@@ -210,6 +213,21 @@ pub const URL = struct {
} }
}; };
// uriComponentNullStr converts an optional std.Uri.Component to string value.
// The string value can be undecoded.
fn uriComponentNullStr(c: ?std.Uri.Component) []const u8 {
if (c == null) return "";
return uriComponentStr(c.?);
}
fn uriComponentStr(c: std.Uri.Component) []const u8 {
return switch (c) {
.raw => |v| v,
.percent_encoded => |v| v,
};
}
// https://url.spec.whatwg.org/#interface-urlsearchparams // https://url.spec.whatwg.org/#interface-urlsearchparams
// TODO array like // TODO array like
pub const URLSearchParams = struct { pub const URLSearchParams = struct {

View File

@@ -1,5 +1,5 @@
const std = @import("std"); const std = @import("std");
const parser = @import("netsurf.zig"); const parser = @import("netsurf");
const Client = @import("async/Client.zig"); const Client = @import("async/Client.zig");
pub const UserContext = struct { pub const UserContext = struct {

View File

@@ -21,7 +21,7 @@ const fspath = std.fs.path;
const FileLoader = @import("fileloader.zig").FileLoader; const FileLoader = @import("fileloader.zig").FileLoader;
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const jsruntime = @import("jsruntime"); const jsruntime = @import("jsruntime");
const Loop = jsruntime.Loop; const Loop = jsruntime.Loop;

View File

@@ -22,8 +22,9 @@ const jsruntime = @import("jsruntime");
const Callback = jsruntime.Callback; const Callback = jsruntime.Callback;
const EventTarget = @import("../dom/event_target.zig").EventTarget; const EventTarget = @import("../dom/event_target.zig").EventTarget;
const EventHandler = @import("../events/event.zig").EventHandler;
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const log = std.log.scoped(.xhr); const log = std.log.scoped(.xhr);
@@ -41,8 +42,20 @@ pub const XMLHttpRequestEventTarget = struct {
ontimeout_cbk: ?Callback = null, ontimeout_cbk: ?Callback = null,
onloadend_cbk: ?Callback = null, onloadend_cbk: ?Callback = null,
fn register(self: *XMLHttpRequestEventTarget, alloc: std.mem.Allocator, typ: []const u8, cbk: Callback) !void { fn register(
try parser.eventTargetAddEventListener(@as(*parser.EventTarget, @ptrCast(self)), alloc, typ, cbk, false); self: *XMLHttpRequestEventTarget,
alloc: std.mem.Allocator,
typ: []const u8,
cbk: Callback,
) !void {
try parser.eventTargetAddEventListener(
@as(*parser.EventTarget, @ptrCast(self)),
alloc,
typ,
cbk,
false,
EventHandler,
);
} }
fn unregister(self: *XMLHttpRequestEventTarget, alloc: std.mem.Allocator, typ: []const u8, cbk: Callback) !void { fn unregister(self: *XMLHttpRequestEventTarget, alloc: std.mem.Allocator, typ: []const u8, cbk: Callback) !void {
const et = @as(*parser.EventTarget, @ptrCast(self)); const et = @as(*parser.EventTarget, @ptrCast(self));

View File

@@ -22,7 +22,7 @@ const jsruntime = @import("jsruntime");
const Case = jsruntime.test_utils.Case; const Case = jsruntime.test_utils.Case;
const checkCases = jsruntime.test_utils.checkCases; const checkCases = jsruntime.test_utils.checkCases;
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const Event = @import("../events/event.zig").Event; const Event = @import("../events/event.zig").Event;
const DOMException = @import("../dom/exceptions.zig").DOMException; const DOMException = @import("../dom/exceptions.zig").DOMException;

View File

@@ -23,7 +23,7 @@ const Case = jsruntime.test_utils.Case;
const checkCases = jsruntime.test_utils.checkCases; const checkCases = jsruntime.test_utils.checkCases;
const generate = @import("../generate.zig"); const generate = @import("../generate.zig");
const DOMError = @import("../netsurf.zig").DOMError; const DOMError = @import("netsurf").DOMError;
const DOMException = @import("../dom/exceptions.zig").DOMException; const DOMException = @import("../dom/exceptions.zig").DOMException;
const ProgressEvent = @import("progress_event.zig").ProgressEvent; const ProgressEvent = @import("progress_event.zig").ProgressEvent;
@@ -35,7 +35,7 @@ const Loop = jsruntime.Loop;
const YieldImpl = Loop.Yield(XMLHttpRequest); const YieldImpl = Loop.Yield(XMLHttpRequest);
const Client = @import("../async/Client.zig"); const Client = @import("../async/Client.zig");
const parser = @import("../netsurf.zig"); const parser = @import("netsurf");
const UserContext = @import("../user_context.zig").UserContext; const UserContext = @import("../user_context.zig").UserContext;
@@ -95,6 +95,50 @@ pub const XMLHttpRequestBodyInit = union(XMLHttpRequestBodyInitTag) {
}; };
pub const XMLHttpRequest = struct { pub const XMLHttpRequest = struct {
proto: XMLHttpRequestEventTarget = XMLHttpRequestEventTarget{},
alloc: std.mem.Allocator,
cli: *Client,
impl: YieldImpl,
priv_state: PrivState = .new,
req: ?Client.Request = null,
method: std.http.Method,
state: u16,
url: ?[]const u8,
uri: std.Uri,
// request headers
headers: Headers,
sync: bool = true,
err: ?anyerror = null,
// TODO uncomment this field causes casting issue with
// XMLHttpRequestEventTarget. I think it's dueto an alignement issue, but
// not sure. see
// https://lightpanda.slack.com/archives/C05TRU6RBM1/p1707819010681019
// upload: ?XMLHttpRequestUpload = null,
// TODO uncomment this field causes casting issue with
// XMLHttpRequestEventTarget. I think it's dueto an alignement issue, but
// not sure. see
// https://lightpanda.slack.com/archives/C05TRU6RBM1/p1707819010681019
// timeout: u32 = 0,
withCredentials: bool = false,
// TODO: response readonly attribute any response;
response_bytes: ?[]const u8 = null,
response_type: ResponseType = .Empty,
response_headers: Headers,
// used by zig client to parse reponse headers.
response_header_buffer: [1024]u8 = undefined,
response_status: u10 = 0,
response_override_mime_type: ?[]const u8 = null,
response_mime: Mime = undefined,
response_obj: ?ResponseObj = null,
send_flag: bool = false,
payload: ?[]const u8 = null,
pub const prototype = *XMLHttpRequestEventTarget; pub const prototype = *XMLHttpRequestEventTarget;
pub const mem_guarantied = true; pub const mem_guarantied = true;
@@ -116,6 +160,91 @@ pub const XMLHttpRequest = struct {
const JSONValue = std.json.Value; const JSONValue = std.json.Value;
const Headers = struct {
alloc: std.mem.Allocator,
list: List,
const List = std.ArrayListUnmanaged(std.http.Header);
fn init(alloc: std.mem.Allocator) Headers {
return .{
.alloc = alloc,
.list = List{},
};
}
fn deinit(self: *Headers) void {
self.free();
self.list.deinit(self.alloc);
}
fn append(self: *Headers, k: []const u8, v: []const u8) !void {
// duplicate strings
const kk = try self.alloc.dupe(u8, k);
const vv = try self.alloc.dupe(u8, v);
try self.list.append(self.alloc, .{ .name = kk, .value = vv });
}
// free all strings allocated.
fn free(self: *Headers) void {
for (self.list.items) |h| {
self.alloc.free(h.name);
self.alloc.free(h.value);
}
}
fn clearAndFree(self: *Headers) void {
self.free();
self.list.clearAndFree(self.alloc);
}
fn has(self: Headers, k: []const u8) bool {
for (self.list.items) |h| {
if (std.ascii.eqlIgnoreCase(k, h.name)) {
return true;
}
}
return false;
}
fn getFirstValue(self: Headers, k: []const u8) ?[]const u8 {
for (self.list.items) |h| {
if (std.ascii.eqlIgnoreCase(k, h.name)) {
return h.value;
}
}
return null;
}
// replace any existing header with the same key
fn set(self: *Headers, k: []const u8, v: []const u8) !void {
for (self.list.items, 0..) |h, i| {
if (std.ascii.eqlIgnoreCase(k, h.name)) {
const hh = self.list.swapRemove(i);
self.alloc.free(hh.name);
self.alloc.free(hh.value);
}
}
self.append(k, v);
}
// TODO
fn sort(_: *Headers) void {}
fn all(self: Headers) []std.http.Header {
return self.list.items;
}
fn load(self: *Headers, it: *std.http.HeaderIterator) !void {
while (true) {
const h = it.next() orelse break;
_ = try self.append(h.name, h.value);
}
}
};
const Response = union(ResponseType) { const Response = union(ResponseType) {
Empty: void, Empty: void,
Text: []const u8, Text: []const u8,
@@ -149,49 +278,13 @@ pub const XMLHttpRequest = struct {
const PrivState = enum { new, open, send, write, finish, wait, done }; const PrivState = enum { new, open, send, write, finish, wait, done };
proto: XMLHttpRequestEventTarget = XMLHttpRequestEventTarget{},
alloc: std.mem.Allocator,
cli: *Client,
impl: YieldImpl,
priv_state: PrivState = .new,
req: ?Client.Request = null,
method: std.http.Method,
state: u16,
url: ?[]const u8,
uri: std.Uri,
headers: std.http.Headers,
sync: bool = true,
err: ?anyerror = null,
// TODO uncomment this field causes casting issue with
// XMLHttpRequestEventTarget. I think it's dueto an alignement issue, but
// not sure. see
// https://lightpanda.slack.com/archives/C05TRU6RBM1/p1707819010681019
// upload: ?XMLHttpRequestUpload = null,
timeout: u32 = 0,
withCredentials: bool = false,
// TODO: response readonly attribute any response;
response_bytes: ?[]const u8 = null,
response_type: ResponseType = .Empty,
response_headers: std.http.Headers,
response_status: u10 = 0,
response_override_mime_type: ?[]const u8 = null,
response_mime: Mime = undefined,
response_obj: ?ResponseObj = null,
send_flag: bool = false,
payload: ?[]const u8 = null,
const min_delay: u64 = 50000000; // 50ms const min_delay: u64 = 50000000; // 50ms
pub fn constructor(alloc: std.mem.Allocator, loop: *Loop, userctx: UserContext) !XMLHttpRequest { pub fn constructor(alloc: std.mem.Allocator, loop: *Loop, userctx: UserContext) !XMLHttpRequest {
return .{ return .{
.alloc = alloc, .alloc = alloc,
.headers = .{ .allocator = alloc, .owned = true }, .headers = Headers.init(alloc),
.response_headers = .{ .allocator = alloc, .owned = true }, .response_headers = Headers.init(alloc),
.impl = YieldImpl.init(loop), .impl = YieldImpl.init(loop),
.method = undefined, .method = undefined,
.url = null, .url = null,
@@ -242,16 +335,16 @@ pub const XMLHttpRequest = struct {
return self.state; return self.state;
} }
pub fn get_timeout(self: *XMLHttpRequest) u32 { pub fn get_timeout(_: *XMLHttpRequest) u32 {
return self.timeout; return 0;
} }
pub fn set_timeout(self: *XMLHttpRequest, timeout: u32) !void { // TODO, the value is ignored for now.
pub fn set_timeout(_: *XMLHttpRequest, _: u32) !void {
// TODO If the current global object is a Window object and thiss // TODO If the current global object is a Window object and thiss
// synchronous flag is set, then throw an "InvalidAccessError" // synchronous flag is set, then throw an "InvalidAccessError"
// DOMException. // DOMException.
// https://xhr.spec.whatwg.org/#dom-xmlhttprequest-timeout // https://xhr.spec.whatwg.org/#dom-xmlhttprequest-timeout
self.timeout = timeout;
} }
pub fn get_withCredentials(self: *XMLHttpRequest) bool { pub fn get_withCredentials(self: *XMLHttpRequest) bool {
@@ -385,7 +478,7 @@ pub const XMLHttpRequest = struct {
const body_init = XMLHttpRequestBodyInit{ .String = body.? }; const body_init = XMLHttpRequestBodyInit{ .String = body.? };
// keep the user content type from request headers. // keep the user content type from request headers.
if (self.headers.getFirstEntry("Content-Type") == null) { if (self.headers.has("Content-Type")) {
// https://fetch.spec.whatwg.org/#bodyinit-safely-extract // https://fetch.spec.whatwg.org/#bodyinit-safely-extract
try self.headers.append("Content-Type", try body_init.contentType()); try self.headers.append("Content-Type", try body_init.contentType());
} }
@@ -411,14 +504,17 @@ pub const XMLHttpRequest = struct {
switch (self.priv_state) { switch (self.priv_state) {
.new => { .new => {
self.priv_state = .open; self.priv_state = .open;
self.req = self.cli.open(self.method, self.uri, self.headers, .{}) catch |e| return self.onErr(e); self.req = self.cli.open(self.method, self.uri, .{
.server_header_buffer = &self.response_header_buffer,
.extra_headers = self.headers.all(),
}) catch |e| return self.onErr(e);
}, },
.open => { .open => {
// prepare payload transfert. // prepare payload transfert.
if (self.payload) |v| self.req.?.transfer_encoding = .{ .content_length = v.len }; if (self.payload) |v| self.req.?.transfer_encoding = .{ .content_length = v.len };
self.priv_state = .send; self.priv_state = .send;
self.req.?.send(.{}) catch |e| return self.onErr(e); self.req.?.send() catch |e| return self.onErr(e);
}, },
.send => { .send => {
if (self.payload) |payload| { if (self.payload) |payload| {
@@ -441,7 +537,8 @@ pub const XMLHttpRequest = struct {
log.info("{any} {any} {d}", .{ self.method, self.uri, self.req.?.response.status }); log.info("{any} {any} {d}", .{ self.method, self.uri, self.req.?.response.status });
self.priv_state = .done; self.priv_state = .done;
self.response_headers = self.req.?.response.headers.clone(self.response_headers.allocator) catch |e| return self.onErr(e); var it = self.req.?.response.iterateHeaders();
self.response_headers.load(&it) catch |e| return self.onErr(e);
// extract a mime type from headers. // extract a mime type from headers.
const ct = self.response_headers.getFirstValue("Content-Type") orelse "text/xml"; const ct = self.response_headers.getFirstValue("Content-Type") orelse "text/xml";