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
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:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

View File

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

View File

@@ -45,7 +45,7 @@ jobs:
runs-on: ubuntu-latest
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:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
@@ -71,7 +71,7 @@ jobs:
runs-on: ubuntu-latest
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:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
@@ -97,7 +97,7 @@ jobs:
runs-on: ubuntu-latest
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:
username: ${{ github.actor }}
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
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.
Lightpanda also depends on

View File

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

View File

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,7 @@
const std = @import("std");
const builtin = @import("builtin");
const os = std.os;
const posix = std.posix;
const io = std.io;
const assert = std.debug.assert;
@@ -28,15 +28,15 @@ pub const Stream = struct {
alloc: std.mem.Allocator,
conn: *tcp.Conn,
handle: std.os.socket_t,
handle: posix.socket_t,
pub fn close(self: Stream) void {
os.closeSocket(self.handle);
posix.close(self.handle);
self.alloc.destroy(self.conn);
}
pub const ReadError = os.ReadError;
pub const WriteError = os.WriteError;
pub const ReadError = posix.ReadError;
pub const WriteError = posix.WriteError;
pub const Reader = io.Reader(Stream, ReadError, read);
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 {
return os.readv(s.handle, iovecs);
pub fn readv(s: Stream, iovecs: []const posix.iovec) ReadError!usize {
return posix.readv(s.handle, iovecs);
}
/// 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 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;
const first_buffer = iovecs[0].iov_base[0..iovecs[0].iov_len];
return try self.write(first_buffer);
@@ -115,7 +115,7 @@ pub const Stream = struct {
/// order to handle partial writes from the underlying OS layer.
/// See https://github.com/ziglang/zig/issues/7699
/// 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;
var i: usize = 0;

View File

@@ -59,19 +59,19 @@ pub const Conn = struct {
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) };
cmd.impl.connect(&cmd, socket, address);
_ = 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) };
cmd.impl.send(&cmd, socket, buffer);
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) };
cmd.impl.receive(&cmd, socket, buffer);
return try cmd.wait();
@@ -93,12 +93,12 @@ pub fn tcpConnectToHost(alloc: std.mem.Allocator, loop: *Loop, name: []const u8,
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 {
const sockfd = try std.os.socket(addr.any.family, std.os.SOCK.STREAM, std.os.IPPROTO.TCP);
errdefer std.os.closeSocket(sockfd);
const sockfd = try std.posix.socket(addr.any.family, std.posix.SOCK.STREAM, std.posix.IPPROTO.TCP);
errdefer std.posix.close(sockfd);
var conn = try alloc.create(Conn);
conn.* = Conn{ .loop = loop };

View File

@@ -40,11 +40,9 @@ test "blocking mode fetch API" {
// force client's CA cert scan from system.
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) },
.payload = .none,
});
defer res.deinit();
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.
try client.ca_bundle.rescan(client.allocator);
var headers = try std.http.Headers.initList(alloc, &[_]std.http.Field{});
defer headers.deinit();
var req = try client.open(.GET, try std.Uri.parse(url), headers, .{});
var buf: [2014]u8 = undefined;
var req = try client.open(.GET, try std.Uri.parse(url), .{
.server_header_buffer = &buf,
});
defer req.deinit();
try req.send(.{});
try req.send();
try req.finish();
try req.wait();
@@ -87,7 +85,6 @@ const AsyncClient = struct {
cli: *Client,
uri: std.Uri,
headers: std.http.Headers,
req: ?Request = undefined,
state: State = .new,
@@ -95,9 +92,10 @@ const AsyncClient = struct {
impl: YieldImpl,
err: ?anyerror = null,
buf: [2014]u8 = undefined,
pub fn deinit(self: *AsyncRequest) void {
if (self.req) |*r| r.deinit();
self.headers.deinit();
}
pub fn fetch(self: *AsyncRequest) void {
@@ -116,11 +114,13 @@ const AsyncClient = struct {
switch (self.state) {
.new => {
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 => {
self.state = .send;
self.req.?.send(.{}) catch |e| return self.onerr(e);
self.req.?.send() catch |e| return self.onerr(e);
},
.send => {
self.state = .finish;
@@ -164,7 +164,6 @@ const AsyncClient = struct {
.impl = YieldImpl.init(self.cli.loop),
.cli = &self.cli,
.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 parser = @import("../netsurf.zig");
const parser = @import("netsurf");
const Loader = @import("loader.zig").Loader;
const Dump = @import("dump.zig");
const Mime = @import("mime.zig");
@@ -224,7 +224,7 @@ pub const Page = struct {
// own the url
if (self.rawuri) |prev| alloc.free(prev);
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.
var buf = std.ArrayList(u8).init(alloc);
@@ -247,29 +247,39 @@ pub const Page = struct {
// TODO handle redirection
if (req.response.status != .ok) {
log.debug("{?} {d} {s}\n{any}", .{
log.debug("{?} {d} {s}", .{
req.response.version,
req.response.status,
req.response.reason,
req.response.headers,
// TODO log headers
});
return error.BadStatusCode;
}
// TODO handle charset
// 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.
// TODO try to sniff mime type from the body.
log.info("no content-type HTTP header", .{});
return;
};
log.debug("header content-type: {s}", .{ct});
const mime = try Mime.parse(ct);
}
defer alloc.free(ct.?);
log.debug("header content-type: {s}", .{ct.?});
const mime = try Mime.parse(ct.?);
if (mime.eql(Mime.HTML)) {
try self.loadHTMLDoc(req.reader(), mime.charset orelse "utf-8");
} else {
log.info("non-HTML document: {s}", .{ct});
log.info("non-HTML document: {s}", .{ct.?});
// save the body into the page.
self.raw_data = try req.reader().readAllAlloc(alloc, 16 * 1024 * 1024);
@@ -500,22 +510,27 @@ pub const Page = struct {
log.debug("starting fetch script {s}", .{src});
const u = std.Uri.parse(src) catch try std.Uri.parseWithoutScheme(src);
const ru = try std.Uri.resolve(self.uri, u, false, alloc);
var buffer: [1024]u8 = undefined;
var b: []u8 = buffer[0..];
const u = try std.Uri.resolve_inplace(self.uri, src, &b);
var fetchres = try self.session.loader.fetch(alloc, ru);
var fetchres = try self.session.loader.get(alloc, u);
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
const body = try fetchres.req.reader().readAllAlloc(alloc, 16 * 1024 * 1024);
defer alloc.free(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);
if (res.success) {

View File

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

View File

@@ -22,6 +22,7 @@ const user_agent = "Lightpanda.io/1.0";
pub const Loader = struct {
client: std.http.Client,
server_header_buffer: [1024]u8 = undefined,
pub const Response = struct {
alloc: std.mem.Allocator,
@@ -45,46 +46,30 @@ pub const Loader = struct {
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
// https://ziglang.org/documentation/master/std/#A;std:http.Client.fetch
// for reference.
// The caller is responsible for calling `deinit()` on the `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{
.alloc = alloc,
.req = try alloc.create(std.http.Client.Request),
};
errdefer alloc.destroy(resp.req);
resp.req.* = try self.client.open(.GET, uri, headers, .{
.handle_redirects = true, // TODO handle redirects manually
resp.req.* = try self.client.open(.GET, uri, .{
.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();
try resp.req.send(.{});
try resp.req.send();
try resp.req.finish();
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;
var loader = Loader.init(alloc);
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();
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 parser = @import("../netsurf.zig");
const parser = @import("netsurf");
// Node implementation with Netsurf Libdom C lib.
pub const Node = struct {

View File

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

View File

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

View File

@@ -18,7 +18,7 @@
const std = @import("std");
const parser = @import("../netsurf.zig");
const parser = @import("netsurf");
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 generate = @import("../generate.zig");
const parser = @import("../netsurf.zig");
const parser = @import("netsurf");
const Node = @import("node.zig").Node;
const Comment = @import("comment.zig").Comment;

View File

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

View File

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

View File

@@ -18,7 +18,7 @@
const std = @import("std");
const parser = @import("../netsurf.zig");
const parser = @import("netsurf");
const jsruntime = @import("jsruntime");
const Case = jsruntime.test_utils.Case;
@@ -449,7 +449,7 @@ pub fn testExecFn(
try checkCases(js_env, &adoptNode);
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| {
const tag_name = @tagName(tag);
createElements[i * 2] = Case{

View File

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

View File

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

View File

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

View File

@@ -24,7 +24,8 @@ const JSObjectID = jsruntime.JSObjectID;
const Case = jsruntime.test_utils.Case;
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 Nod = @import("node.zig");
@@ -74,6 +75,7 @@ pub const EventTarget = struct {
eventType,
cbk,
capture orelse false,
EventHandler,
);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,7 +22,7 @@ const jsruntime = @import("jsruntime");
const Case = jsruntime.test_utils.Case;
const checkCases = jsruntime.test_utils.checkCases;
const parser = @import("../netsurf.zig");
const parser = @import("netsurf");
const Node = @import("node.zig").Node;
// 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 generate = @import("../generate.zig");
const parser = @import("../netsurf.zig");
const parser = @import("netsurf");
const CharacterData = @import("character_data.zig").CharacterData;
const CDATASection = @import("cdata_section.zig").CDATASection;

View File

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

View File

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

View File

@@ -22,10 +22,11 @@ const generate = @import("../generate.zig");
const jsruntime = @import("jsruntime");
const Callback = jsruntime.Callback;
const CallbackResult = jsruntime.CallbackResult;
const Case = jsruntime.test_utils.Case;
const checkCases = jsruntime.test_utils.checkCases;
const parser = @import("../netsurf.zig");
const parser = @import("netsurf");
const DOMException = @import("../dom/exceptions.zig").DOMException;
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 log = std.log.scoped(.events);
// Event interfaces
pub const Interfaces = generate.Tuple(.{
Event,
@@ -236,3 +239,33 @@ pub fn testExecFn(
};
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});
}
fn fmtName(comptime T: type) []const u8 {
fn fmtName(comptime T: type) [:0]const u8 {
var it = std.mem.splitBackwards(u8, @typeName(T), ".");
return it.first();
return it.first() ++ "";
}
// Union
@@ -168,7 +168,11 @@ pub const Union = struct {
T = *T;
}
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,
.alignment = @alignOf(T),
};
@@ -176,7 +180,7 @@ pub const Union = struct {
}
}
const union_info = std.builtin.Type.Union{
.layout = .Auto,
.layout = .auto,
.tag_type = enum_T,
.fields = &union_fields,
.decls = &decls,
@@ -286,7 +290,11 @@ fn TupleT(comptime tuple: anytype) type {
continue;
}
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,
.default_value = null,
.is_comptime = false,
@@ -296,7 +304,7 @@ fn TupleT(comptime tuple: anytype) type {
}
const decls: [0]std.builtin.Type.Declaration = undefined;
const info = std.builtin.Type.Struct{
.layout = .Auto,
.layout = .auto,
.fields = &fields,
.decls = &decls,
.is_tuple = true,

View File

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

View File

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

View File

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

View File

@@ -20,7 +20,7 @@ const std = @import("std");
const jsruntime = @import("jsruntime");
const parser = @import("netsurf.zig");
const parser = @import("netsurf");
const apiweb = @import("apiweb.zig");
const Window = @import("html/window.zig").Window;
@@ -30,7 +30,7 @@ pub const UserContext = apiweb.UserContext;
const socket_path = "/tmp/browsercore-server.sock";
var doc: *parser.DocumentHTML = undefined;
var server: std.net.StreamServer = undefined;
var server: std.net.Server = undefined;
fn execJS(
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
// see: https://gavv.net/articles/unix-socket-reuse/
// TODO: use a lock file instead
std.os.unlink(socket_path) catch |err| {
std.posix.unlink(socket_path) catch |err| {
if (err != error.FileNotFound) {
return err;
}
@@ -99,9 +99,8 @@ pub fn main() !void {
// server
const addr = try std.net.Address.initUnix(socket_path);
server = std.net.StreamServer.init(.{});
server = try addr.listen(.{});
defer server.deinit();
try server.listen(addr);
std.debug.print("Listening on: {s}...\n", .{socket_path});
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 UserContext = apiweb.UserContext;
pub const std_options = struct {
pub const log_level = .debug;
pub const std_options = std.Options{
.log_level = .debug,
};
const usage =
@@ -58,7 +58,7 @@ pub fn main() !void {
while (args.next()) |arg| {
if (std.mem.eql(u8, "-h", arg) or std.mem.eql(u8, "--help", arg)) {
try std.io.getStdErr().writer().print(usage, .{execname});
std.os.exit(0);
std.posix.exit(0);
}
if (std.mem.eql(u8, "--dump", arg)) {
dump = true;
@@ -67,14 +67,14 @@ pub fn main() !void {
// allow only one url
if (url.len != 0) {
try std.io.getStdErr().writer().print(usage, .{execname});
std.os.exit(1);
std.posix.exit(1);
}
url = arg;
}
if (url.len == 0) {
try std.io.getStdErr().writer().print(usage, .{execname});
std.os.exit(1);
std.posix.exit(1);
}
const vm = jsruntime.VM.init();

View File

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

View File

@@ -76,7 +76,7 @@ pub fn main() !void {
while (args.next()) |arg| {
if (std.mem.eql(u8, "-h", arg) or std.mem.eql(u8, "--help", arg)) {
try std.io.getStdErr().writer().print(usage, .{execname});
std.os.exit(0);
std.posix.exit(0);
}
if (std.mem.eql(u8, "--json", arg)) {
out = .json;
@@ -214,12 +214,12 @@ pub fn main() !void {
}
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) {
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");
});
const mimalloc = @import("mimalloc.zig");
const mimalloc = @import("mimalloc");
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 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 {
comptime {
const info = @typeInfo(Tag).Enum;
comptime var l: [info.fields.len]Tag = undefined;
inline for (info.fields, 0..) |field, i| {
var l: [info.fields.len]Tag = undefined;
for (info.fields, 0..) |field, i| {
l[i] = @as(Tag, @enumFromInt(field.value));
}
return &l;
@@ -277,7 +273,7 @@ pub const Tag = enum(u8) {
comptime {
const tags = all();
var names: [tags.len][]const u8 = undefined;
inline for (tags, 0..) |tag, i| {
for (tags, 0..) |tag, i| {
names[i] = tag.elementName();
}
return &names;
@@ -527,41 +523,11 @@ pub const EventType = enum(u8) {
};
// EventHandler
fn event_handler_cbk(data: *anyopaque) *Callback {
pub fn event_handler_cbk(data: *anyopaque) *Callback {
const ptr: *align(@alignOf(*Callback)) anyopaque = @alignCast(data);
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
pub const EventListener = c.dom_event_listener;
const EventListenerEntry = c.listener_entry;
@@ -642,12 +608,15 @@ pub fn eventTargetHasListener(
return null;
}
const EventHandler = fn (event: ?*Event, data: ?*anyopaque) callconv(.C) void;
pub fn eventTargetAddEventListener(
et: *EventTarget,
alloc: std.mem.Allocator,
typ: []const u8,
cbk: Callback,
capture: bool,
handler: EventHandler,
) !void {
// this allocation will be removed either on
// eventTargetRemoveEventListener or eventTargetRemoveAllEventListeners
@@ -661,7 +630,7 @@ pub fn eventTargetAddEventListener(
const ctx = @as(*anyopaque, @ptrCast(cbk_ptr));
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);
defer c.dom_event_listener_unref(listener);

View File

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

View File

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

View File

@@ -62,7 +62,10 @@ pub const URL = struct {
return .{
.rawuri = raw,
.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);
defer q.deinit();
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);
}
@@ -116,9 +119,9 @@ pub const URL = struct {
.scheme = true,
.authentication = true,
.authority = true,
.path = self.uri.path.len > 0,
.query = self.uri.query != null and self.uri.query.?.len > 0,
.fragment = self.uri.fragment != null and self.uri.fragment.?.len > 0,
.path = uriComponentNullStr(self.uri.path).len > 0,
.query = uriComponentNullStr(self.uri.query).len > 0,
.fragment = uriComponentNullStr(self.uri.fragment).len > 0,
}, buf.writer());
return try buf.toOwnedSlice();
}
@@ -131,11 +134,11 @@ pub const URL = struct {
}
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 {
return self.uri.password orelse "";
return uriComponentNullStr(self.uri.password);
}
// the caller must free the returned string.
@@ -157,7 +160,7 @@ pub const URL = struct {
}
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.
@@ -174,8 +177,8 @@ pub const URL = struct {
}
pub fn get_pathname(self: *URL) []const u8 {
if (self.uri.path.len == 0) return "/";
return self.uri.path;
if (uriComponentStr(self.uri.path).len == 0) return "/";
return uriComponentStr(self.uri.path);
}
// 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 {
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 {
@@ -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
// TODO array like
pub const URLSearchParams = struct {

View File

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

View File

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

View File

@@ -22,8 +22,9 @@ const jsruntime = @import("jsruntime");
const Callback = jsruntime.Callback;
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);
@@ -41,8 +42,20 @@ pub const XMLHttpRequestEventTarget = struct {
ontimeout_cbk: ?Callback = null,
onloadend_cbk: ?Callback = null,
fn register(self: *XMLHttpRequestEventTarget, alloc: std.mem.Allocator, typ: []const u8, cbk: Callback) !void {
try parser.eventTargetAddEventListener(@as(*parser.EventTarget, @ptrCast(self)), alloc, typ, cbk, false);
fn register(
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 {
const et = @as(*parser.EventTarget, @ptrCast(self));

View File

@@ -22,7 +22,7 @@ const jsruntime = @import("jsruntime");
const Case = jsruntime.test_utils.Case;
const checkCases = jsruntime.test_utils.checkCases;
const parser = @import("../netsurf.zig");
const parser = @import("netsurf");
const Event = @import("../events/event.zig").Event;
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 generate = @import("../generate.zig");
const DOMError = @import("../netsurf.zig").DOMError;
const DOMError = @import("netsurf").DOMError;
const DOMException = @import("../dom/exceptions.zig").DOMException;
const ProgressEvent = @import("progress_event.zig").ProgressEvent;
@@ -35,7 +35,7 @@ const Loop = jsruntime.Loop;
const YieldImpl = Loop.Yield(XMLHttpRequest);
const Client = @import("../async/Client.zig");
const parser = @import("../netsurf.zig");
const parser = @import("netsurf");
const UserContext = @import("../user_context.zig").UserContext;
@@ -95,6 +95,50 @@ pub const XMLHttpRequestBodyInit = union(XMLHttpRequestBodyInitTag) {
};
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 mem_guarantied = true;
@@ -116,6 +160,91 @@ pub const XMLHttpRequest = struct {
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) {
Empty: void,
Text: []const u8,
@@ -149,49 +278,13 @@ pub const XMLHttpRequest = struct {
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
pub fn constructor(alloc: std.mem.Allocator, loop: *Loop, userctx: UserContext) !XMLHttpRequest {
return .{
.alloc = alloc,
.headers = .{ .allocator = alloc, .owned = true },
.response_headers = .{ .allocator = alloc, .owned = true },
.headers = Headers.init(alloc),
.response_headers = Headers.init(alloc),
.impl = YieldImpl.init(loop),
.method = undefined,
.url = null,
@@ -242,16 +335,16 @@ pub const XMLHttpRequest = struct {
return self.state;
}
pub fn get_timeout(self: *XMLHttpRequest) u32 {
return self.timeout;
pub fn get_timeout(_: *XMLHttpRequest) u32 {
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
// synchronous flag is set, then throw an "InvalidAccessError"
// DOMException.
// https://xhr.spec.whatwg.org/#dom-xmlhttprequest-timeout
self.timeout = timeout;
}
pub fn get_withCredentials(self: *XMLHttpRequest) bool {
@@ -385,7 +478,7 @@ pub const XMLHttpRequest = struct {
const body_init = XMLHttpRequestBodyInit{ .String = body.? };
// 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
try self.headers.append("Content-Type", try body_init.contentType());
}
@@ -411,14 +504,17 @@ pub const XMLHttpRequest = struct {
switch (self.priv_state) {
.new => {
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 => {
// prepare payload transfert.
if (self.payload) |v| self.req.?.transfer_encoding = .{ .content_length = v.len };
self.priv_state = .send;
self.req.?.send(.{}) catch |e| return self.onErr(e);
self.req.?.send() catch |e| return self.onErr(e);
},
.send => {
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 });
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.
const ct = self.response_headers.getFirstValue("Content-Type") orelse "text/xml";