33 Commits

Author SHA1 Message Date
Pierre Tachoire
dabded8d1e build: add -Dx86 option to enable x86 backend
see https://ziglang.org/download/0.12.0/release-notes.html#x86-Backend
2024-09-19 17:49:00 +02:00
Pierre Tachoire
c3fd0dbf7a Merge pull request #223 from lightpanda-io/settimout
dom: first draft for window setTimeout
2024-09-19 16:49:55 +02:00
Pierre Tachoire
aeaa745600 clearTimeout: ignore invalid timeout ids 2024-09-19 16:37:42 +02:00
Pierre Tachoire
be27359109 dom: implement clearTimeout 2024-09-19 16:37:42 +02:00
Pierre Tachoire
e8a2ce3614 upgrade zig-js-runtime lib 2024-09-19 16:37:40 +02:00
Pierre Tachoire
0559fb9365 dom: first draft for window setTimeout 2024-09-19 16:36:34 +02:00
Pierre Tachoire
89f898cfa9 Merge pull request #268 from lightpanda-io/HttpHeadersOversize
use 64KB for header buffer
2024-09-19 15:36:25 +02:00
Pierre Tachoire
183abc4610 use 64KB for header buffer 2024-09-18 09:24:57 +02:00
Pierre Tachoire
5dfdedea0e Merge pull request #266 from lightpanda-io/ci-node20
ci: upgrade cache action to node20
2024-09-13 15:04:11 +02:00
Pierre Tachoire
8d89c6053e ci: upgrade cache action to node20 2024-09-13 14:52:06 +02:00
Pierre Tachoire
58d184dba1 Merge pull request #265 from lightpanda-io/ci-cpu
ci: target cpu x86_64 to improve CPU compat
2024-09-13 14:47:58 +02:00
Pierre Tachoire
fd0813fead ci: target cpu x86_64 to improve CPU compat
The `-Dcpu` option force compiling using less CPU's capabilities.
This will improve the binary compatibility with older CPUs.
2024-09-13 14:36:44 +02:00
Pierre Tachoire
c9fae67649 Merge pull request #258 from lightpanda-io/tls.zig
use tls.zig with async client
2024-07-22 10:44:38 +02:00
Pierre Tachoire
3e7f9aaa82 Merge pull request #262 from lightpanda-io/browser-deinit
browser: fix invalid deinit order
2024-07-19 17:20:28 +02:00
Pierre Tachoire
22b03bf7df browser: fix invalid deinit order
http client depends on io loop and must be deinit before.
2024-07-19 17:15:53 +02:00
Pierre Tachoire
6a4d64ed00 use tls.zig with async client
see https://github.com/ziglang/zig/compare/master...ianic:zig:tls23 for
http.std.Client integration
2024-07-19 14:39:50 +02:00
Pierre Tachoire
df27ce09ca Merge pull request #260 from lightpanda-io/zig0.13
upgrade to zig 0.13
2024-07-19 14:36:13 +02:00
Pierre Tachoire
6da2954e0b upgrade to zig 0.13 2024-07-19 14:32:47 +02:00
Pierre Tachoire
95245229b0 upgrade zig-js-runtime 2024-07-19 14:32:47 +02:00
Pierre Tachoire
65c4b471af browser: handle wait with unstarted env 2024-07-19 11:14:50 +02:00
Pierre Tachoire
d6e0559efd upgrade to zig 0.13 2024-07-18 16:57:16 +02:00
Pierre Tachoire
5f5e01a2cf Merge pull request #253 from lightpanda-io/refacto_exec
Adapt to js_exec changes in zig-js-runtime
2024-07-18 16:56:41 +02:00
Pierre Tachoire
8c3939b842 wpt: remove useless Suite.stack 2024-07-18 16:52:32 +02:00
Pierre Tachoire
b537e52a6d browser: use log instead of std.log 2024-07-18 16:46:12 +02:00
Pierre Tachoire
4434e11bdd wpt: restore the test results 2024-07-18 16:40:44 +02:00
Francis Bouvier
b8ec53f708 Adapt to js_exec changes in zig-js-runtime
Signed-off-by: Francis Bouvier <francis@lightpanda.io>
2024-07-18 12:10:06 +02:00
Pierre Tachoire
f8395fec5c Merge pull request #257 from lightpanda-io/build-x86
ci: target cpu x86_64_v3+aes for compatibility
2024-07-17 17:04:20 +02:00
Pierre Tachoire
28155cb8d3 ci: target cpu x86_64_v3+aes for compatibility 2024-07-17 17:00:09 +02:00
Pierre Tachoire
f793278dfe Merge pull request #255 from lightpanda-io/ci
ci: remove container usage and download v8 from release
2024-07-17 15:16:41 +02:00
Pierre Tachoire
cdbbc71b0a ci: add nightly build for macos aarch64 2024-07-17 10:28:24 +02:00
Pierre Tachoire
bfa6f55551 ci: remove container usage and download v8 from release 2024-07-17 09:56:34 +02:00
Pierre Tachoire
5fb25cf015 Merge pull request #254 from lightpanda-io/nightly
ci: add linux amd64 nightly build
2024-07-16 14:29:36 +02:00
Pierre Tachoire
5cad450e5a ci: add linux amd64 nightly build 2024-07-16 12:47:26 +02:00
23 changed files with 2136 additions and 172 deletions

View File

@@ -1,23 +1,73 @@
name: "Browsercore install"
description: "Install deps for the project browsercore"
inputs:
zig:
description: 'Zig version to install'
required: false
default: '0.13.0'
arch:
description: 'CPU arch used to select the v8 lib'
required: false
default: 'x86_64'
os:
description: 'OS used to select the v8 lib'
required: false
default: 'linux'
zig-v8:
description: 'zig v8 version to install'
required: false
default: 'v0.1.6'
v8:
description: 'v8 version to install'
required: false
default: '11.1.134'
cache-dir:
description: 'cache dir to use'
required: false
default: '~/.cache'
runs:
using: "composite"
steps:
- name: Install apt deps
if: ${{ inputs.os == 'linux' }}
shell: bash
run: sudo apt-get install -y wget xz-utils python3 ca-certificates git pkg-config libglib2.0-dev gperf libexpat1-dev cmake clang
- uses: mlugg/setup-zig@v1
with:
version: ${{ inputs.zig }}
- name: Cache v8
id: cache-v8
uses: actions/cache@v4
env:
cache-name: cache-v8
with:
path: ${{ inputs.cache-dir }}/v8
key: libc_v8_${{ inputs.v8 }}_${{ inputs.os }}_${{ inputs.arch }}.a
- if: ${{ steps.cache-v8.outputs.cache-hit != 'true' }}
shell: bash
run: |
mkdir -p ${{ inputs.cache-dir }}/v8
wget -O ${{ inputs.cache-dir }}/v8/libc_v8.a https://github.com/lightpanda-io/zig-v8-fork/releases/download/${{ inputs.zig-v8 }}/libc_v8_${{ inputs.v8 }}_${{ inputs.os }}_${{ inputs.arch }}.a
- name: install v8
shell: bash
run: |
mkdir -p vendor/zig-js-runtime/vendor/v8/${{env.ARCH}}/debug
ln -s /usr/local/lib/libc_v8.a vendor/zig-js-runtime/vendor/v8/${{env.ARCH}}/debug/libc_v8.a
mkdir -p vendor/zig-js-runtime/vendor/v8/${{inputs.arch}}-${{inputs.os}}/debug
ln -s ${{ inputs.cache-dir }}/v8/libc_v8.a vendor/zig-js-runtime/vendor/v8/${{inputs.arch}}-${{inputs.os}}/debug/libc_v8.a
mkdir -p vendor/zig-js-runtime/vendor/v8/${{env.ARCH}}/release
ln -s /usr/local/lib/libc_v8.a vendor/zig-js-runtime/vendor/v8/${{env.ARCH}}/release/libc_v8.a
mkdir -p vendor/zig-js-runtime/vendor/v8/${{inputs.arch}}-${{inputs.os}}/release
ln -s ${{ inputs.cache-dir }}/v8/libc_v8.a vendor/zig-js-runtime/vendor/v8/${{inputs.arch}}-${{inputs.os}}/release/libc_v8.a
- name: libiconv
shell: bash
run: |
ln -s /usr/local/lib/libiconv vendor/libiconv
run: make install-libiconv
- name: build mimalloc
shell: bash

75
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,75 @@
name: nightly build
on:
schedule:
- cron: "2 2 * * *"
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
permissions:
contents: write
jobs:
build-linux-x86_64:
env:
ARCH: x86_64
OS: linux
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GH_CI_PAT }}
# fetch submodules recusively, to get zig-js-runtime submodules also.
submodules: recursive
- uses: ./.github/actions/install
- name: zig build
run: zig build --release=safe -Doptimize=ReleaseSafe -Dengine=v8 -Dcpu=x86_64
- name: Rename binary
run: mv zig-out/bin/browsercore-get lightpanda-get-${{ env.ARCH }}-${{ env.OS }}
- name: Upload the build
uses: ncipollo/release-action@v1
with:
allowUpdates: true
artifacts: lightpanda-get-${{ env.ARCH }}-${{ env.OS }}
tag: nightly
build-macos-aarch64:
env:
ARCH: aarch64
OS: macos
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GH_CI_PAT }}
# fetch submodules recusively, to get zig-js-runtime submodules also.
submodules: recursive
- uses: ./.github/actions/install
with:
os: ${{env.OS}}
arch: ${{env.ARCH}}
- name: zig build
run: zig build --release=safe -Doptimize=ReleaseSafe -Dengine=v8
- name: Rename binary
run: mv zig-out/bin/browsercore-get lightpanda-get-${{ env.ARCH }}-${{ env.OS }}
- name: Upload the build
uses: ncipollo/release-action@v1
with:
allowUpdates: true
artifacts: lightpanda-get-${{ env.ARCH }}-${{ env.OS }}
tag: nightly

View File

@@ -1,7 +1,6 @@
name: wpt
env:
ARCH: x86_64-linux
AWS_ACCESS_KEY_ID: ${{ vars.LPD_PERF_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.LPD_PERF_AWS_SECRET_ACCESS_KEY }}
AWS_BUCKET: ${{ vars.LPD_PERF_AWS_BUCKET }}
@@ -46,16 +45,6 @@ jobs:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
container:
image: ghcr.io/lightpanda-io/zig-browsercore:0.12.1
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# docker blocks io_uring syscalls by default now.
# see https://github.com/tigerbeetle/tigerbeetle/pull/1995
# see https://github.com/moby/moby/pull/46762
options: "--security-opt seccomp=unconfined"
steps:
- uses: actions/checkout@v4

View File

@@ -1,5 +1,8 @@
name: zig-fmt
env:
ZIG_VERSION: 0.13.0
on:
pull_request:
@@ -26,15 +29,12 @@ jobs:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
container:
image: ghcr.io/lightpanda-io/zig:0.12.1
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
outputs:
zig_fmt_errs: ${{ steps.fmt.outputs.zig_fmt_errs }}
steps:
- uses: mlugg/setup-zig@v1
with:
version: ${{ env.ZIG_VERSION }}
- uses: actions/checkout@v4
with:
fetch-depth: 0

View File

@@ -1,7 +1,6 @@
name: zig-test
env:
ARCH: x86_64-linux
AWS_ACCESS_KEY_ID: ${{ vars.LPD_PERF_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.LPD_PERF_AWS_SECRET_ACCESS_KEY }}
AWS_BUCKET: ${{ vars.LPD_PERF_AWS_BUCKET }}
@@ -44,11 +43,6 @@ jobs:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
container:
image: ghcr.io/lightpanda-io/zig-browsercore:0.12.1
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v4
@@ -70,11 +64,6 @@ jobs:
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
container:
image: ghcr.io/lightpanda-io/zig-browsercore:0.12.1
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v4
@@ -96,16 +85,6 @@ jobs:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
container:
image: ghcr.io/lightpanda-io/zig-browsercore:0.12.1
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# docker blocks io_uring syscalls by default now.
# see https://github.com/tigerbeetle/tigerbeetle/pull/1995
# see https://github.com/moby/moby/pull/46762
options: "--security-opt seccomp=unconfined"
steps:
- uses: actions/checkout@v4

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
zig-cache
/.zig-cache/
zig-out
/vendor/netsurf/build/
/vendor/netsurf/lib/

3
.gitmodules vendored
View File

@@ -22,3 +22,6 @@
[submodule "vendor/mimalloc"]
path = vendor/mimalloc
url = git@github.com:microsoft/mimalloc.git
[submodule "vendor/tls.zig"]
path = vendor/tls.zig
url = git@github.com:ianic/tls.zig.git

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.1`. You have to
Lightpanda is written with [Zig](https://ziglang.org/) `0.13.0`. You have to
install it with the right version in order to build the project.
Lightpanda also depends on

View File

@@ -47,6 +47,8 @@ pub fn build(b: *std.Build) !void {
const options = try jsruntime.buildOptions(b);
const x86 = b.option(bool, "x86", "Use x86 backend") orelse false;
// browser
// -------
@@ -56,6 +58,8 @@ pub fn build(b: *std.Build) !void {
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = mode,
.use_llvm = !x86,
.use_lld = !x86,
});
try common(b, exe, options);
b.installArtifact(exe);
@@ -79,6 +83,8 @@ pub fn build(b: *std.Build) !void {
.root_source_file = b.path("src/main_shell.zig"),
.target = target,
.optimize = mode,
.use_llvm = !x86,
.use_lld = !x86,
});
try common(b, shell, options);
try jsruntime_pkgs.add_shell(shell);
@@ -102,6 +108,8 @@ pub fn build(b: *std.Build) !void {
.test_runner = b.path("src/test_runner.zig"),
.target = target,
.optimize = mode,
.use_llvm = !x86,
.use_lld = !x86,
});
try common(b, tests, options);
@@ -128,6 +136,8 @@ pub fn build(b: *std.Build) !void {
.root_source_file = b.path("src/main_wpt.zig"),
.target = target,
.optimize = mode,
.use_llvm = !x86,
.use_lld = !x86,
});
try common(b, wpt, options);
@@ -149,6 +159,8 @@ pub fn build(b: *std.Build) !void {
.root_source_file = b.path("src/main_get.zig"),
.target = target,
.optimize = mode,
.use_llvm = !x86,
.use_lld = !x86,
});
try common(b, get, options);
b.installArtifact(get);
@@ -179,6 +191,11 @@ fn common(
const netsurf = moduleNetSurf(b);
netsurf.addImport("jsruntime", jsruntimemod);
step.root_module.addImport("netsurf", netsurf);
const tlsmod = b.addModule("tls", .{
.root_source_file = b.path("vendor/tls.zig/src/main.zig"),
});
step.root_module.addImport("tls", tlsmod);
}
fn moduleNetSurf(b: *std.Build) *std.Build.Module {

View File

@@ -35,7 +35,9 @@ const assert = std.debug.assert;
const use_vectors = builtin.zig_backend != .stage2_x86_64;
const Client = @This();
const proto = http.protocol;
const proto = std.http.protocol;
const tls23 = @import("tls");
const Loop = @import("jsruntime").Loop;
const tcp = @import("tcp.zig");
@@ -217,7 +219,7 @@ pub const ConnectionPool = struct {
pub const Connection = struct {
stream: Stream,
/// undefined unless protocol is tls.
tls_client: if (!disable_tls) *std.crypto.tls.Client else void,
tls_client: if (!disable_tls) *tls23.Connection(Stream) else void,
/// The protocol that this connection is using.
protocol: Protocol,
@@ -246,12 +248,12 @@ pub const Connection = struct {
pub const Protocol = enum { plain, tls };
pub fn readvDirectTls(conn: *Connection, buffers: []std.posix.iovec) ReadError!usize {
return conn.tls_client.readv(conn.stream, buffers) catch |err| {
return conn.tls_client.readv(buffers) catch |err| {
// https://github.com/ziglang/zig/issues/2473
if (mem.startsWith(u8, @errorName(err), "TlsAlert")) return error.TlsAlert;
switch (err) {
error.TlsConnectionTruncated, error.TlsRecordOverflow, error.TlsDecodeError, error.TlsBadRecordMac, error.TlsBadLength, error.TlsIllegalParameter, error.TlsUnexpectedMessage => return error.TlsFailure,
error.TlsRecordOverflow, error.TlsBadRecordMac, error.TlsUnexpectedMessage => return error.TlsFailure,
error.ConnectionTimedOut => return error.ConnectionTimedOut,
error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,
else => return error.UnexpectedReadFailure,
@@ -278,7 +280,7 @@ pub const Connection = struct {
if (conn.read_end != conn.read_start) return;
var iovecs = [1]std.posix.iovec{
.{ .iov_base = &conn.read_buf, .iov_len = conn.read_buf.len },
.{ .base = &conn.read_buf, .len = conn.read_buf.len },
};
const nread = try conn.readvDirect(&iovecs);
if (nread == 0) return error.EndOfStream;
@@ -314,8 +316,8 @@ pub const Connection = struct {
}
var iovecs = [2]std.posix.iovec{
.{ .iov_base = buffer.ptr, .iov_len = buffer.len },
.{ .iov_base = &conn.read_buf, .iov_len = conn.read_buf.len },
.{ .base = buffer.ptr, .len = buffer.len },
.{ .base = &conn.read_buf, .len = conn.read_buf.len },
};
const nread = try conn.readvDirect(&iovecs);
@@ -344,7 +346,7 @@ pub const Connection = struct {
}
pub fn writeAllDirectTls(conn: *Connection, buffer: []const u8) WriteError!void {
return conn.tls_client.writeAll(conn.stream, buffer) catch |err| switch (err) {
return conn.tls_client.writeAll(buffer) catch |err| switch (err) {
error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
else => return error.UnexpectedWriteFailure,
};
@@ -412,7 +414,7 @@ pub const Connection = struct {
if (disable_tls) unreachable;
// try to cleanly close the TLS connection, for any server that cares.
_ = conn.tls_client.writeEnd(conn.stream, "", true) catch {};
conn.tls_client.close() catch {};
allocator.destroy(conn.tls_client);
}
@@ -1376,13 +1378,13 @@ pub fn connectTcp(client: *Client, host: []const u8, port: u16, protocol: Connec
if (protocol == .tls) {
if (disable_tls) unreachable;
conn.data.tls_client = try client.allocator.create(std.crypto.tls.Client);
conn.data.tls_client = try client.allocator.create(tls23.Connection(Stream));
errdefer client.allocator.destroy(conn.data.tls_client);
conn.data.tls_client.* = std.crypto.tls.Client.init(stream, client.ca_bundle, host) catch return error.TlsInitializationFailed;
// This is appropriate for HTTPS because the HTTP headers contain
// the content length which is used to detect truncation attacks.
conn.data.tls_client.allow_truncation_attacks = true;
conn.data.tls_client.* = tls23.client(stream, .{
.host = host,
.root_ca = client.ca_bundle,
}) catch return error.TlsInitializationFailed;
}
client.connection_pool.addUsed(conn);
@@ -1560,7 +1562,7 @@ pub const RequestOptions = struct {
};
fn validateUri(uri: Uri, arena: Allocator) !struct { Connection.Protocol, Uri } {
const protocol_map = std.ComptimeStringMap(Connection.Protocol, .{
const protocol_map = std.StaticStringMap(Connection.Protocol).initComptime(.{
.{ "http", .plain },
.{ "ws", .plain },
.{ "https", .tls },

View File

@@ -107,7 +107,7 @@ pub const Stream = struct {
/// See equivalent function: `std.fs.File.writev`.
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];
const first_buffer = iovecs[0].base[0..iovecs[0].len];
return try self.write(first_buffer);
}
@@ -121,13 +121,13 @@ pub const Stream = struct {
var i: usize = 0;
while (true) {
var amt = try self.writev(iovecs[i..]);
while (amt >= iovecs[i].iov_len) {
amt -= iovecs[i].iov_len;
while (amt >= iovecs[i].len) {
amt -= iovecs[i].len;
i += 1;
if (i >= iovecs.len) return;
}
iovecs[i].iov_base += amt;
iovecs[i].iov_len -= amt;
iovecs[i].base += amt;
iovecs[i].len -= amt;
}
}
};

View File

@@ -37,7 +37,7 @@ const Walker = @import("../dom/walker.zig").WalkerDepthFirst;
const storage = @import("../storage/storage.zig");
const FetchResult = std.http.Client.FetchResult;
const FetchResult = @import("../http/Client.zig").Client.FetchResult;
const UserContext = @import("../user_context.zig").UserContext;
const HttpClient = @import("../async/Client.zig");
@@ -125,10 +125,10 @@ pub const Session = struct {
self.env.deinit();
self.arena.deinit();
self.loader.deinit();
self.loop.deinit();
self.storageShed.deinit();
self.httpClient.deinit();
self.loader.deinit();
self.storageShed.deinit();
self.loop.deinit();
self.alloc.destroy(self);
}
@@ -198,21 +198,24 @@ pub const Page = struct {
}
pub fn wait(self: *Page) !void {
const alloc = self.arena.allocator();
var res = try self.session.env.waitTryCatch(alloc);
defer res.deinit(alloc);
if (res.success) {
log.debug("wait: {s}", .{res.result});
} else {
if (builtin.mode == .Debug and res.stack != null) {
log.info("wait: {s}", .{res.stack.?});
} else {
log.info("wait: {s}", .{res.result});
// try catch
var try_catch: jsruntime.TryCatch = undefined;
try_catch.init(self.session.env);
defer try_catch.deinit();
self.session.env.wait() catch |err| {
// the js env could not be started if the document wasn't an HTML.
if (err == error.EnvNotStarted) return;
const alloc = self.arena.allocator();
if (try try_catch.err(alloc, self.session.env)) |msg| {
defer alloc.free(msg);
log.info("wait error: {s}", .{msg});
return;
}
}
return;
};
log.debug("wait: OK", .{});
}
// spec reference: https://html.spec.whatwg.org/#document-lifecycle
@@ -322,7 +325,7 @@ pub const Page = struct {
// start JS env
// TODO load the js env concurrently with the HTML parsing.
log.debug("start js env", .{});
try self.session.env.start(alloc);
try self.session.env.start();
// replace the user context document with the new one.
try self.session.env.setUserContext(.{
@@ -473,22 +476,26 @@ pub const Page = struct {
return;
}
var try_catch: jsruntime.TryCatch = undefined;
try_catch.init(self.session.env);
defer try_catch.deinit();
const opt_text = try parser.nodeTextContent(parser.elementToNode(e));
if (opt_text) |text| {
// TODO handle charset attribute
var res = try self.session.env.execTryCatch(alloc, text, "");
defer res.deinit(alloc);
if (res.success) {
log.debug("eval inline: {s}", .{res.result});
} else {
if (builtin.mode == .Debug and res.stack != null) {
log.info("eval inline: {s}", .{res.stack.?});
} else {
log.info("eval inline: {s}", .{res.result});
const res = self.session.env.exec(text, "") catch {
if (try try_catch.err(alloc, self.session.env)) |msg| {
defer alloc.free(msg);
log.info("eval inline {s}: {s}", .{ text, msg });
}
}
return;
};
if (builtin.mode == .Debug) {
const msg = try res.toString(alloc, self.session.env);
defer alloc.free(msg);
log.debug("eval inline {s}", .{msg});
}
return;
}
@@ -530,18 +537,22 @@ pub const Page = struct {
// check no body
if (body.len == 0) return FetchError.NoBody;
var res = try self.session.env.execTryCatch(alloc, body, src);
defer res.deinit(alloc);
var try_catch: jsruntime.TryCatch = undefined;
try_catch.init(self.session.env);
defer try_catch.deinit();
if (res.success) {
log.debug("eval remote {s}: {s}", .{ src, res.result });
} else {
if (builtin.mode == .Debug and res.stack != null) {
log.info("eval remote {s}: {s}", .{ src, res.stack.? });
} else {
log.info("eval remote {s}: {s}", .{ src, res.result });
const res = self.session.env.exec(body, src) catch {
if (try try_catch.err(alloc, self.session.env)) |msg| {
defer alloc.free(msg);
log.info("eval remote {s}: {s}", .{ src, msg });
}
return FetchError.JsErr;
};
if (builtin.mode == .Debug) {
const msg = try res.toString(alloc, self.session.env);
defer alloc.free(msg);
log.debug("eval remote {s}: {s}", .{ src, msg });
}
}

View File

@@ -17,17 +17,18 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const Client = @import("../http/Client.zig");
const user_agent = "Lightpanda.io/1.0";
pub const Loader = struct {
client: std.http.Client,
// use 16KB for headers buffer size.
server_header_buffer: [1024 * 16]u8 = undefined,
client: Client,
// use 64KB for headers buffer size.
server_header_buffer: [1024 * 64]u8 = undefined,
pub const Response = struct {
alloc: std.mem.Allocator,
req: *std.http.Client.Request,
req: *Client.Request,
pub fn deinit(self: *Response) void {
self.req.deinit();
@@ -37,7 +38,7 @@ pub const Loader = struct {
pub fn init(alloc: std.mem.Allocator) Loader {
return Loader{
.client = std.http.Client{
.client = Client{
.allocator = alloc,
},
};
@@ -54,7 +55,7 @@ pub const Loader = struct {
pub fn get(self: *Loader, alloc: std.mem.Allocator, uri: std.Uri) !Response {
var resp = Response{
.alloc = alloc,
.req = try alloc.create(std.http.Client.Request),
.req = try alloc.create(Client.Request),
};
errdefer alloc.destroy(resp.req);

View File

@@ -19,6 +19,10 @@
const std = @import("std");
const parser = @import("netsurf");
const jsruntime = @import("jsruntime");
const Callback = jsruntime.Callback;
const CallbackArg = jsruntime.CallbackArg;
const Loop = jsruntime.Loop;
const EventTarget = @import("../dom/event_target.zig").EventTarget;
@@ -39,6 +43,11 @@ pub const Window = struct {
storageShelf: ?*storage.Shelf = null,
// store a map between internal timeouts ids and pointers to uint.
// the maximum number of possible timeouts is fixed.
timeoutid: u32 = 0,
timeoutids: [512]u64 = undefined,
pub fn create(target: ?[]const u8) Window {
return Window{
.target = target orelse "",
@@ -82,4 +91,26 @@ pub const Window = struct {
if (self.storageShelf == null) return parser.DOMError.NotSupported;
return &self.storageShelf.?.bucket.session;
}
// TODO handle callback arguments.
pub fn _setTimeout(self: *Window, loop: *Loop, cbk: Callback, delay: ?u32) !u32 {
if (self.timeoutid >= self.timeoutids.len) return error.TooMuchTimeout;
const ddelay: u63 = delay orelse 0;
const id = loop.timeout(ddelay * std.time.ns_per_ms, cbk);
self.timeoutids[self.timeoutid] = id;
defer self.timeoutid += 1;
return self.timeoutid;
}
pub fn _clearTimeout(self: *Window, loop: *Loop, id: u32) void {
// I do would prefer return an error in this case, but it seems some JS
// uses invalid id, in particular id 0.
// So we silently ignore invalid id for now.
if (id >= self.timeoutid) return;
loop.cancel(self.timeoutids[id], null);
}
};

1794
src/http/Client.zig Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -37,7 +37,7 @@ fn execJS(
js_env: *jsruntime.Env,
) anyerror!void {
// start JS env
try js_env.start(alloc);
try js_env.start();
defer js_env.stop();
// alias global as self and window
@@ -45,6 +45,11 @@ fn execJS(
window.replaceDocument(doc);
try js_env.bindGlobal(window);
// try catch
var try_catch: jsruntime.TryCatch = undefined;
try_catch.init(js_env.*);
defer try_catch.deinit();
while (true) {
// read cmd
@@ -57,11 +62,12 @@ fn execJS(
break;
}
const res = try js_env.execTryCatch(alloc, cmd, "cdp");
if (res.success) {
std.debug.print("-> {s}\n", .{res.result});
}
_ = try conn.stream.write(res.result);
const res = try js_env.exec(cmd, "cdp");
const res_str = try res.toString(alloc, js_env.*);
defer alloc.free(res_str);
std.debug.print("-> {s}\n", .{res_str});
_ = try conn.stream.write(res_str);
}
}

View File

@@ -38,7 +38,7 @@ fn execJS(
js_env: *jsruntime.Env,
) anyerror!void {
// start JS env
try js_env.start(alloc);
try js_env.start();
defer js_env.stop();
var cli = Client{ .allocator = alloc, .loop = js_env.nat_ctx.loop };

View File

@@ -142,7 +142,7 @@ pub fn main() !void {
defer arena.deinit();
const res = wpt.run(&arena, wpt_dir, tc, &loader) catch |err| {
const suite = try Suite.init(alloc, tc, false, @errorName(err), null);
const suite = try Suite.init(alloc, tc, false, @errorName(err));
try results.append(suite);
if (out == .text) {
@@ -151,9 +151,9 @@ pub fn main() !void {
failures += 1;
continue;
};
// no need to call res.deinit() thanks to the arena allocator.
defer res.deinit(arena.allocator());
const suite = try Suite.init(alloc, tc, res.success, res.result, res.stack);
const suite = try Suite.init(alloc, tc, res.ok, res.msg orelse "");
try results.append(suite);
if (out == .json) {
@@ -196,7 +196,7 @@ pub fn main() !void {
try cases.append(Case{
.pass = suite.pass,
.name = suite.name,
.message = suite.stack orelse suite.message,
.message = suite.message,
});
}
@@ -288,7 +288,7 @@ fn runSafe(
argv.appendAssumeCapacity(tc);
defer _ = argv.pop();
const run = try std.ChildProcess.run(.{
const run = try std.process.Child.run(.{
.allocator = alloc,
.argv = argv.items,
.max_output_bytes = 1024 * 1024,

View File

@@ -71,7 +71,7 @@ fn testExecFn(
defer parser.deinit();
// start JS env
try js_env.start(alloc);
try js_env.start();
defer js_env.stop();
var storageShelf = storage.Shelf.init(alloc);

View File

@@ -36,7 +36,7 @@ const Client = @import("../async/Client.zig");
// runWPT parses the given HTML file, starts a js env and run the first script
// tags containing javascript sources.
// It loads first the js libs files.
pub fn run(arena: *std.heap.ArenaAllocator, comptime dir: []const u8, f: []const u8, loader: *FileLoader) !jsruntime.JSResult {
pub fn run(arena: *std.heap.ArenaAllocator, comptime dir: []const u8, f: []const u8, loader: *FileLoader) !Res {
const alloc = arena.allocator();
try parser.init();
defer parser.deinit();
@@ -70,15 +70,16 @@ pub fn run(arena: *std.heap.ArenaAllocator, comptime dir: []const u8, f: []const
try js_env.load(&js_types);
// start JS env
try js_env.start(alloc);
try js_env.start();
defer js_env.stop();
// display console logs
defer {
var res = evalJS(js_env, alloc, "console.join('\\n');", "console") catch unreachable;
const res = evalJS(js_env, alloc, "console.join('\\n');", "console") catch unreachable;
defer res.deinit(alloc);
if (res.result.len > 0) {
std.debug.print("-- CONSOLE LOG\n{s}\n--\n", .{res.result});
if (res.msg != null and res.msg.?.len > 0) {
std.debug.print("-- CONSOLE LOG\n{s}\n--\n", .{res.msg.?});
}
}
@@ -88,9 +89,6 @@ pub fn run(arena: *std.heap.ArenaAllocator, comptime dir: []const u8, f: []const
window.setStorageShelf(&storageShelf);
try js_env.bindGlobal(&window);
// thanks to the arena, we don't need to deinit res.
var res: jsruntime.JSResult = undefined;
const init =
\\console = [];
\\console.log = function () {
@@ -100,10 +98,8 @@ pub fn run(arena: *std.heap.ArenaAllocator, comptime dir: []const u8, f: []const
\\ console.push("debug", ...arguments);
\\};
;
res = try evalJS(js_env, alloc, init, "init");
if (!res.success) {
return res;
}
var res = try evalJS(js_env, alloc, init, "init");
if (!res.ok) return res;
res.deinit(alloc);
// loop hover the scripts.
@@ -122,20 +118,14 @@ pub fn run(arena: *std.heap.ArenaAllocator, comptime dir: []const u8, f: []const
}
res = try evalJS(js_env, alloc, try loader.get(path), src);
if (!res.success) {
return res;
}
if (!res.ok) return res;
res.deinit(alloc);
}
// If the script as a source text, execute it.
const src = try parser.nodeTextContent(s) orelse continue;
res = try evalJS(js_env, alloc, src, "");
// return the first failure.
if (!res.success) {
return res;
}
if (!res.ok) return res;
res.deinit(alloc);
}
@@ -150,25 +140,52 @@ pub fn run(arena: *std.heap.ArenaAllocator, comptime dir: []const u8, f: []const
);
// wait for all async executions
res = try js_env.waitTryCatch(alloc);
if (!res.success) {
return res;
}
res.deinit(alloc);
var try_catch: jsruntime.TryCatch = undefined;
try_catch.init(js_env);
defer try_catch.deinit();
js_env.wait() catch {
return .{
.ok = false,
.msg = try try_catch.err(alloc, js_env),
};
};
// Check the final test status.
res = try evalJS(js_env, alloc, "report.status;", "teststatus");
if (!res.success) {
return res;
}
if (!res.ok) return res;
res.deinit(alloc);
// return the detailed result.
return try evalJS(js_env, alloc, "report.log", "teststatus");
}
fn evalJS(env: jsruntime.Env, alloc: std.mem.Allocator, script: []const u8, name: ?[]const u8) !jsruntime.JSResult {
return try env.execTryCatch(alloc, script, name);
pub const Res = struct {
ok: bool,
msg: ?[]const u8,
pub fn deinit(res: Res, alloc: std.mem.Allocator) void {
if (res.msg) |msg| {
alloc.free(msg);
}
}
};
fn evalJS(env: jsruntime.Env, alloc: std.mem.Allocator, script: []const u8, name: ?[]const u8) !Res {
var try_catch: jsruntime.TryCatch = undefined;
try_catch.init(env);
defer try_catch.deinit();
const v = env.exec(script, name) catch {
return .{
.ok = false,
.msg = try try_catch.err(alloc, env),
};
};
return .{
.ok = true,
.msg = try v.toString(alloc, env),
};
}
// browse the path to find the tests list.

View File

@@ -67,28 +67,22 @@ pub const Suite = struct {
pass: bool,
name: []const u8,
message: ?[]const u8,
stack: ?[]const u8,
cases: ?[]Case,
// caller owns the wpt.Suite.
// owner must call deinit().
pub fn init(alloc: std.mem.Allocator, name: []const u8, pass: bool, res: []const u8, stack: ?[]const u8) !Suite {
pub fn init(alloc: std.mem.Allocator, name: []const u8, pass: bool, res: []const u8) !Suite {
var suite = Suite{
.alloc = alloc,
.pass = false,
.name = try alloc.dupe(u8, name),
.message = null,
.stack = null,
.cases = null,
};
// handle JS error.
if (!pass) {
suite.message = try alloc.dupe(u8, res);
if (stack) |st| {
suite.stack = try alloc.dupe(u8, st);
}
return suite;
}
@@ -155,10 +149,6 @@ pub const Suite = struct {
pub fn deinit(self: Suite) void {
self.alloc.free(self.name);
if (self.stack) |stack| {
self.alloc.free(stack);
}
if (self.message) |res| {
self.alloc.free(res);
}
@@ -175,9 +165,6 @@ pub const Suite = struct {
if (self.message) |v| {
return v;
}
if (self.stack) |v| {
return v;
}
return "";
}
};
@@ -199,7 +186,7 @@ test "success test case" {
,
};
const suite = Suite.init(alloc, "foo", res.pass, res.result, null) catch unreachable; // TODO
const suite = Suite.init(alloc, "foo", res.pass, res.result) catch unreachable; // TODO
defer suite.deinit();
try testing.expect(suite.pass == true);
@@ -226,7 +213,7 @@ test "failed test case" {
,
};
const suite = Suite.init(alloc, "foo", res.pass, res.result, null) catch unreachable; // TODO
const suite = Suite.init(alloc, "foo", res.pass, res.result) catch unreachable; // TODO
defer suite.deinit();
try testing.expect(suite.pass == false);
@@ -251,7 +238,7 @@ test "invalid result" {
,
};
const suite = Suite.init(alloc, "foo", res.pass, res.result, null) catch unreachable; // TODO
const suite = Suite.init(alloc, "foo", res.pass, res.result) catch unreachable; // TODO
defer suite.deinit();
try testing.expect(suite.pass == false);
@@ -266,7 +253,7 @@ test "invalid result" {
,
};
const suite2 = Suite.init(alloc, "foo", res2.pass, res2.result, null) catch unreachable; // TODO
const suite2 = Suite.init(alloc, "foo", res2.pass, res2.result) catch unreachable; // TODO
defer suite2.deinit();
try testing.expect(suite2.pass == false);

1
vendor/tls.zig vendored Submodule

Submodule vendor/tls.zig added at 0ea9e6d769