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" name: "Browsercore install"
description: "Install deps for the project browsercore" 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: runs:
using: "composite" using: "composite"
steps: 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 - name: install v8
shell: bash shell: bash
run: | run: |
mkdir -p vendor/zig-js-runtime/vendor/v8/${{env.ARCH}}/debug mkdir -p vendor/zig-js-runtime/vendor/v8/${{inputs.arch}}-${{inputs.os}}/debug
ln -s /usr/local/lib/libc_v8.a vendor/zig-js-runtime/vendor/v8/${{env.ARCH}}/debug/libc_v8.a 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 mkdir -p vendor/zig-js-runtime/vendor/v8/${{inputs.arch}}-${{inputs.os}}/release
ln -s /usr/local/lib/libc_v8.a vendor/zig-js-runtime/vendor/v8/${{env.ARCH}}/release/libc_v8.a 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 - name: libiconv
shell: bash shell: bash
run: | run: make install-libiconv
ln -s /usr/local/lib/libiconv vendor/libiconv
- name: build mimalloc - name: build mimalloc
shell: bash 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 name: wpt
env: env:
ARCH: x86_64-linux
AWS_ACCESS_KEY_ID: ${{ vars.LPD_PERF_AWS_ACCESS_KEY_ID }} AWS_ACCESS_KEY_ID: ${{ vars.LPD_PERF_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.LPD_PERF_AWS_SECRET_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.LPD_PERF_AWS_SECRET_ACCESS_KEY }}
AWS_BUCKET: ${{ vars.LPD_PERF_AWS_BUCKET }} AWS_BUCKET: ${{ vars.LPD_PERF_AWS_BUCKET }}
@@ -46,16 +45,6 @@ jobs:
if: github.event.pull_request.draft == false if: github.event.pull_request.draft == false
runs-on: ubuntu-latest 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: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

View File

@@ -1,5 +1,8 @@
name: zig-fmt name: zig-fmt
env:
ZIG_VERSION: 0.13.0
on: on:
pull_request: pull_request:
@@ -26,15 +29,12 @@ jobs:
if: github.event.pull_request.draft == false if: github.event.pull_request.draft == false
runs-on: ubuntu-latest 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: steps:
- uses: mlugg/setup-zig@v1
with:
version: ${{ env.ZIG_VERSION }}
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0

View File

@@ -1,7 +1,6 @@
name: zig-test name: zig-test
env: env:
ARCH: x86_64-linux
AWS_ACCESS_KEY_ID: ${{ vars.LPD_PERF_AWS_ACCESS_KEY_ID }} AWS_ACCESS_KEY_ID: ${{ vars.LPD_PERF_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.LPD_PERF_AWS_SECRET_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.LPD_PERF_AWS_SECRET_ACCESS_KEY }}
AWS_BUCKET: ${{ vars.LPD_PERF_AWS_BUCKET }} AWS_BUCKET: ${{ vars.LPD_PERF_AWS_BUCKET }}
@@ -44,11 +43,6 @@ jobs:
if: github.event.pull_request.draft == false if: github.event.pull_request.draft == false
runs-on: ubuntu-latest runs-on: ubuntu-latest
container:
image: ghcr.io/lightpanda-io/zig-browsercore:0.12.1
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -70,11 +64,6 @@ jobs:
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
runs-on: ubuntu-latest runs-on: ubuntu-latest
container:
image: ghcr.io/lightpanda-io/zig-browsercore:0.12.1
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -96,16 +85,6 @@ jobs:
if: github.event.pull_request.draft == false if: github.event.pull_request.draft == false
runs-on: ubuntu-latest 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: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

1
.gitignore vendored
View File

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

3
.gitmodules vendored
View File

@@ -22,3 +22,6 @@
[submodule "vendor/mimalloc"] [submodule "vendor/mimalloc"]
path = vendor/mimalloc path = vendor/mimalloc
url = git@github.com:microsoft/mimalloc.git 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 ### 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. install it with the right version in order to build the project.
Lightpanda also depends on Lightpanda also depends on

View File

@@ -47,6 +47,8 @@ pub fn build(b: *std.Build) !void {
const options = try jsruntime.buildOptions(b); const options = try jsruntime.buildOptions(b);
const x86 = b.option(bool, "x86", "Use x86 backend") orelse false;
// browser // browser
// ------- // -------
@@ -56,6 +58,8 @@ pub fn build(b: *std.Build) !void {
.root_source_file = b.path("src/main.zig"), .root_source_file = b.path("src/main.zig"),
.target = target, .target = target,
.optimize = mode, .optimize = mode,
.use_llvm = !x86,
.use_lld = !x86,
}); });
try common(b, exe, options); try common(b, exe, options);
b.installArtifact(exe); b.installArtifact(exe);
@@ -79,6 +83,8 @@ pub fn build(b: *std.Build) !void {
.root_source_file = b.path("src/main_shell.zig"), .root_source_file = b.path("src/main_shell.zig"),
.target = target, .target = target,
.optimize = mode, .optimize = mode,
.use_llvm = !x86,
.use_lld = !x86,
}); });
try common(b, shell, options); try common(b, shell, options);
try jsruntime_pkgs.add_shell(shell); 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"), .test_runner = b.path("src/test_runner.zig"),
.target = target, .target = target,
.optimize = mode, .optimize = mode,
.use_llvm = !x86,
.use_lld = !x86,
}); });
try common(b, tests, options); 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"), .root_source_file = b.path("src/main_wpt.zig"),
.target = target, .target = target,
.optimize = mode, .optimize = mode,
.use_llvm = !x86,
.use_lld = !x86,
}); });
try common(b, wpt, options); 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"), .root_source_file = b.path("src/main_get.zig"),
.target = target, .target = target,
.optimize = mode, .optimize = mode,
.use_llvm = !x86,
.use_lld = !x86,
}); });
try common(b, get, options); try common(b, get, options);
b.installArtifact(get); b.installArtifact(get);
@@ -179,6 +191,11 @@ fn common(
const netsurf = moduleNetSurf(b); const netsurf = moduleNetSurf(b);
netsurf.addImport("jsruntime", jsruntimemod); netsurf.addImport("jsruntime", jsruntimemod);
step.root_module.addImport("netsurf", netsurf); 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 { 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 use_vectors = builtin.zig_backend != .stage2_x86_64;
const Client = @This(); const Client = @This();
const proto = http.protocol; const proto = std.http.protocol;
const tls23 = @import("tls");
const Loop = @import("jsruntime").Loop; const Loop = @import("jsruntime").Loop;
const tcp = @import("tcp.zig"); const tcp = @import("tcp.zig");
@@ -217,7 +219,7 @@ pub const ConnectionPool = struct {
pub const Connection = struct { pub const Connection = struct {
stream: Stream, stream: Stream,
/// undefined unless protocol is tls. /// 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. /// The protocol that this connection is using.
protocol: Protocol, protocol: Protocol,
@@ -246,12 +248,12 @@ pub const Connection = struct {
pub const Protocol = enum { plain, tls }; pub const Protocol = enum { plain, tls };
pub fn readvDirectTls(conn: *Connection, buffers: []std.posix.iovec) ReadError!usize { 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 // https://github.com/ziglang/zig/issues/2473
if (mem.startsWith(u8, @errorName(err), "TlsAlert")) return error.TlsAlert; if (mem.startsWith(u8, @errorName(err), "TlsAlert")) return error.TlsAlert;
switch (err) { 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.ConnectionTimedOut => return error.ConnectionTimedOut,
error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer, error.ConnectionResetByPeer, error.BrokenPipe => return error.ConnectionResetByPeer,
else => return error.UnexpectedReadFailure, else => return error.UnexpectedReadFailure,
@@ -278,7 +280,7 @@ pub const Connection = struct {
if (conn.read_end != conn.read_start) return; if (conn.read_end != conn.read_start) return;
var iovecs = [1]std.posix.iovec{ 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); const nread = try conn.readvDirect(&iovecs);
if (nread == 0) return error.EndOfStream; if (nread == 0) return error.EndOfStream;
@@ -314,8 +316,8 @@ pub const Connection = struct {
} }
var iovecs = [2]std.posix.iovec{ var iovecs = [2]std.posix.iovec{
.{ .iov_base = buffer.ptr, .iov_len = buffer.len }, .{ .base = buffer.ptr, .len = buffer.len },
.{ .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); const nread = try conn.readvDirect(&iovecs);
@@ -344,7 +346,7 @@ pub const Connection = struct {
} }
pub fn writeAllDirectTls(conn: *Connection, buffer: []const u8) WriteError!void { 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, error.BrokenPipe, error.ConnectionResetByPeer => return error.ConnectionResetByPeer,
else => return error.UnexpectedWriteFailure, else => return error.UnexpectedWriteFailure,
}; };
@@ -412,7 +414,7 @@ pub const Connection = struct {
if (disable_tls) unreachable; if (disable_tls) unreachable;
// try to cleanly close the TLS connection, for any server that cares. // 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); 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 (protocol == .tls) {
if (disable_tls) unreachable; 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); 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; conn.data.tls_client.* = tls23.client(stream, .{
// This is appropriate for HTTPS because the HTTP headers contain .host = host,
// the content length which is used to detect truncation attacks. .root_ca = client.ca_bundle,
conn.data.tls_client.allow_truncation_attacks = true; }) catch return error.TlsInitializationFailed;
} }
client.connection_pool.addUsed(conn); client.connection_pool.addUsed(conn);
@@ -1560,7 +1562,7 @@ pub const RequestOptions = struct {
}; };
fn validateUri(uri: Uri, arena: Allocator) !struct { Connection.Protocol, Uri } { 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 }, .{ "http", .plain },
.{ "ws", .plain }, .{ "ws", .plain },
.{ "https", .tls }, .{ "https", .tls },

View File

@@ -107,7 +107,7 @@ pub const Stream = struct {
/// See equivalent function: `std.fs.File.writev`. /// See equivalent function: `std.fs.File.writev`.
pub fn writev(self: Stream, iovecs: []const posix.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].base[0..iovecs[0].len];
return try self.write(first_buffer); return try self.write(first_buffer);
} }
@@ -121,13 +121,13 @@ pub const Stream = struct {
var i: usize = 0; var i: usize = 0;
while (true) { while (true) {
var amt = try self.writev(iovecs[i..]); var amt = try self.writev(iovecs[i..]);
while (amt >= iovecs[i].iov_len) { while (amt >= iovecs[i].len) {
amt -= iovecs[i].iov_len; amt -= iovecs[i].len;
i += 1; i += 1;
if (i >= iovecs.len) return; if (i >= iovecs.len) return;
} }
iovecs[i].iov_base += amt; iovecs[i].base += amt;
iovecs[i].iov_len -= 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 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 UserContext = @import("../user_context.zig").UserContext;
const HttpClient = @import("../async/Client.zig"); const HttpClient = @import("../async/Client.zig");
@@ -125,10 +125,10 @@ pub const Session = struct {
self.env.deinit(); self.env.deinit();
self.arena.deinit(); self.arena.deinit();
self.loader.deinit();
self.loop.deinit();
self.storageShed.deinit();
self.httpClient.deinit(); self.httpClient.deinit();
self.loader.deinit();
self.storageShed.deinit();
self.loop.deinit();
self.alloc.destroy(self); self.alloc.destroy(self);
} }
@@ -198,21 +198,24 @@ pub const Page = struct {
} }
pub fn wait(self: *Page) !void { 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) { // try catch
log.debug("wait: {s}", .{res.result}); var try_catch: jsruntime.TryCatch = undefined;
} else { try_catch.init(self.session.env);
if (builtin.mode == .Debug and res.stack != null) { defer try_catch.deinit();
log.info("wait: {s}", .{res.stack.?});
} else { self.session.env.wait() catch |err| {
log.info("wait: {s}", .{res.result}); // 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;
} }
} };
log.debug("wait: OK", .{});
return;
} }
// spec reference: https://html.spec.whatwg.org/#document-lifecycle // spec reference: https://html.spec.whatwg.org/#document-lifecycle
@@ -322,7 +325,7 @@ pub const Page = struct {
// start JS env // start JS env
// TODO load the js env concurrently with the HTML parsing. // TODO load the js env concurrently with the HTML parsing.
log.debug("start js env", .{}); 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. // replace the user context document with the new one.
try self.session.env.setUserContext(.{ try self.session.env.setUserContext(.{
@@ -473,22 +476,26 @@ pub const Page = struct {
return; 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)); const opt_text = try parser.nodeTextContent(parser.elementToNode(e));
if (opt_text) |text| { if (opt_text) |text| {
// TODO handle charset attribute // TODO handle charset attribute
var res = try self.session.env.execTryCatch(alloc, text, ""); const res = self.session.env.exec(text, "") catch {
defer res.deinit(alloc); if (try try_catch.err(alloc, self.session.env)) |msg| {
defer alloc.free(msg);
if (res.success) { log.info("eval inline {s}: {s}", .{ text, msg });
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});
} }
} 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; return;
} }
@@ -530,18 +537,22 @@ pub const Page = struct {
// check no body // check no body
if (body.len == 0) return FetchError.NoBody; if (body.len == 0) return FetchError.NoBody;
var res = try self.session.env.execTryCatch(alloc, body, src); var try_catch: jsruntime.TryCatch = undefined;
defer res.deinit(alloc); try_catch.init(self.session.env);
defer try_catch.deinit();
if (res.success) { const res = self.session.env.exec(body, src) catch {
log.debug("eval remote {s}: {s}", .{ src, res.result }); if (try try_catch.err(alloc, self.session.env)) |msg| {
} else { defer alloc.free(msg);
if (builtin.mode == .Debug and res.stack != null) { log.info("eval remote {s}: {s}", .{ src, msg });
log.info("eval remote {s}: {s}", .{ src, res.stack.? });
} else {
log.info("eval remote {s}: {s}", .{ src, res.result });
} }
return FetchError.JsErr; 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/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const Client = @import("../http/Client.zig");
const user_agent = "Lightpanda.io/1.0"; const user_agent = "Lightpanda.io/1.0";
pub const Loader = struct { pub const Loader = struct {
client: std.http.Client, client: Client,
// use 16KB for headers buffer size. // use 64KB for headers buffer size.
server_header_buffer: [1024 * 16]u8 = undefined, server_header_buffer: [1024 * 64]u8 = undefined,
pub const Response = struct { pub const Response = struct {
alloc: std.mem.Allocator, alloc: std.mem.Allocator,
req: *std.http.Client.Request, req: *Client.Request,
pub fn deinit(self: *Response) void { pub fn deinit(self: *Response) void {
self.req.deinit(); self.req.deinit();
@@ -37,7 +38,7 @@ pub const Loader = struct {
pub fn init(alloc: std.mem.Allocator) Loader { pub fn init(alloc: std.mem.Allocator) Loader {
return Loader{ return Loader{
.client = std.http.Client{ .client = Client{
.allocator = alloc, .allocator = alloc,
}, },
}; };
@@ -54,7 +55,7 @@ pub const Loader = struct {
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 resp = Response{ var resp = Response{
.alloc = alloc, .alloc = alloc,
.req = try alloc.create(std.http.Client.Request), .req = try alloc.create(Client.Request),
}; };
errdefer alloc.destroy(resp.req); errdefer alloc.destroy(resp.req);

View File

@@ -19,6 +19,10 @@
const std = @import("std"); const std = @import("std");
const parser = @import("netsurf"); 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; const EventTarget = @import("../dom/event_target.zig").EventTarget;
@@ -39,6 +43,11 @@ pub const Window = struct {
storageShelf: ?*storage.Shelf = null, 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 { pub fn create(target: ?[]const u8) Window {
return Window{ return Window{
.target = target orelse "", .target = target orelse "",
@@ -82,4 +91,26 @@ pub const Window = struct {
if (self.storageShelf == null) return parser.DOMError.NotSupported; if (self.storageShelf == null) return parser.DOMError.NotSupported;
return &self.storageShelf.?.bucket.session; 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, js_env: *jsruntime.Env,
) anyerror!void { ) anyerror!void {
// start JS env // start JS env
try js_env.start(alloc); try js_env.start();
defer js_env.stop(); defer js_env.stop();
// alias global as self and window // alias global as self and window
@@ -45,6 +45,11 @@ fn execJS(
window.replaceDocument(doc); window.replaceDocument(doc);
try js_env.bindGlobal(window); try js_env.bindGlobal(window);
// try catch
var try_catch: jsruntime.TryCatch = undefined;
try_catch.init(js_env.*);
defer try_catch.deinit();
while (true) { while (true) {
// read cmd // read cmd
@@ -57,11 +62,12 @@ fn execJS(
break; break;
} }
const res = try js_env.execTryCatch(alloc, cmd, "cdp"); const res = try js_env.exec(cmd, "cdp");
if (res.success) { const res_str = try res.toString(alloc, js_env.*);
std.debug.print("-> {s}\n", .{res.result}); defer alloc.free(res_str);
} std.debug.print("-> {s}\n", .{res_str});
_ = try conn.stream.write(res.result);
_ = try conn.stream.write(res_str);
} }
} }

View File

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

View File

@@ -142,7 +142,7 @@ pub fn main() !void {
defer arena.deinit(); defer arena.deinit();
const res = wpt.run(&arena, wpt_dir, tc, &loader) catch |err| { 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); try results.append(suite);
if (out == .text) { if (out == .text) {
@@ -151,9 +151,9 @@ pub fn main() !void {
failures += 1; failures += 1;
continue; 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); try results.append(suite);
if (out == .json) { if (out == .json) {
@@ -196,7 +196,7 @@ pub fn main() !void {
try cases.append(Case{ try cases.append(Case{
.pass = suite.pass, .pass = suite.pass,
.name = suite.name, .name = suite.name,
.message = suite.stack orelse suite.message, .message = suite.message,
}); });
} }
@@ -288,7 +288,7 @@ fn runSafe(
argv.appendAssumeCapacity(tc); argv.appendAssumeCapacity(tc);
defer _ = argv.pop(); defer _ = argv.pop();
const run = try std.ChildProcess.run(.{ const run = try std.process.Child.run(.{
.allocator = alloc, .allocator = alloc,
.argv = argv.items, .argv = argv.items,
.max_output_bytes = 1024 * 1024, .max_output_bytes = 1024 * 1024,

View File

@@ -71,7 +71,7 @@ fn testExecFn(
defer parser.deinit(); defer parser.deinit();
// start JS env // start JS env
try js_env.start(alloc); try js_env.start();
defer js_env.stop(); defer js_env.stop();
var storageShelf = storage.Shelf.init(alloc); 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 // runWPT parses the given HTML file, starts a js env and run the first script
// tags containing javascript sources. // tags containing javascript sources.
// It loads first the js libs files. // 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(); const alloc = arena.allocator();
try parser.init(); try parser.init();
defer parser.deinit(); 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); try js_env.load(&js_types);
// start JS env // start JS env
try js_env.start(alloc); try js_env.start();
defer js_env.stop(); defer js_env.stop();
// display console logs // display console logs
defer { 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); 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); window.setStorageShelf(&storageShelf);
try js_env.bindGlobal(&window); try js_env.bindGlobal(&window);
// thanks to the arena, we don't need to deinit res.
var res: jsruntime.JSResult = undefined;
const init = const init =
\\console = []; \\console = [];
\\console.log = function () { \\console.log = function () {
@@ -100,10 +98,8 @@ pub fn run(arena: *std.heap.ArenaAllocator, comptime dir: []const u8, f: []const
\\ console.push("debug", ...arguments); \\ console.push("debug", ...arguments);
\\}; \\};
; ;
res = try evalJS(js_env, alloc, init, "init"); var res = try evalJS(js_env, alloc, init, "init");
if (!res.success) { if (!res.ok) return res;
return res;
}
res.deinit(alloc); res.deinit(alloc);
// loop hover the scripts. // 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); res = try evalJS(js_env, alloc, try loader.get(path), src);
if (!res.success) { if (!res.ok) return res;
return res;
}
res.deinit(alloc); res.deinit(alloc);
} }
// If the script as a source text, execute it. // If the script as a source text, execute it.
const src = try parser.nodeTextContent(s) orelse continue; const src = try parser.nodeTextContent(s) orelse continue;
res = try evalJS(js_env, alloc, src, ""); res = try evalJS(js_env, alloc, src, "");
if (!res.ok) return res;
// return the first failure.
if (!res.success) {
return res;
}
res.deinit(alloc); 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 // wait for all async executions
res = try js_env.waitTryCatch(alloc); var try_catch: jsruntime.TryCatch = undefined;
if (!res.success) { try_catch.init(js_env);
return res; defer try_catch.deinit();
} js_env.wait() catch {
res.deinit(alloc); return .{
.ok = false,
.msg = try try_catch.err(alloc, js_env),
};
};
// Check the final test status. // Check the final test status.
res = try evalJS(js_env, alloc, "report.status;", "teststatus"); res = try evalJS(js_env, alloc, "report.status;", "teststatus");
if (!res.success) { if (!res.ok) return res;
return res;
}
res.deinit(alloc); res.deinit(alloc);
// return the detailed result. // return the detailed result.
return try evalJS(js_env, alloc, "report.log", "teststatus"); 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 { pub const Res = struct {
return try env.execTryCatch(alloc, script, name); 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. // browse the path to find the tests list.

View File

@@ -67,28 +67,22 @@ pub const Suite = struct {
pass: bool, pass: bool,
name: []const u8, name: []const u8,
message: ?[]const u8, message: ?[]const u8,
stack: ?[]const u8,
cases: ?[]Case, cases: ?[]Case,
// caller owns the wpt.Suite. // caller owns the wpt.Suite.
// owner must call deinit(). // 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{ var suite = Suite{
.alloc = alloc, .alloc = alloc,
.pass = false, .pass = false,
.name = try alloc.dupe(u8, name), .name = try alloc.dupe(u8, name),
.message = null, .message = null,
.stack = null,
.cases = null, .cases = null,
}; };
// handle JS error. // handle JS error.
if (!pass) { if (!pass) {
suite.message = try alloc.dupe(u8, res); suite.message = try alloc.dupe(u8, res);
if (stack) |st| {
suite.stack = try alloc.dupe(u8, st);
}
return suite; return suite;
} }
@@ -155,10 +149,6 @@ pub const Suite = struct {
pub fn deinit(self: Suite) void { pub fn deinit(self: Suite) void {
self.alloc.free(self.name); self.alloc.free(self.name);
if (self.stack) |stack| {
self.alloc.free(stack);
}
if (self.message) |res| { if (self.message) |res| {
self.alloc.free(res); self.alloc.free(res);
} }
@@ -175,9 +165,6 @@ pub const Suite = struct {
if (self.message) |v| { if (self.message) |v| {
return v; return v;
} }
if (self.stack) |v| {
return v;
}
return ""; 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(); defer suite.deinit();
try testing.expect(suite.pass == true); 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(); defer suite.deinit();
try testing.expect(suite.pass == false); 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(); defer suite.deinit();
try testing.expect(suite.pass == false); 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(); defer suite2.deinit();
try testing.expect(suite2.pass == false); try testing.expect(suite2.pass == false);

1
vendor/tls.zig vendored Submodule

Submodule vendor/tls.zig added at 0ea9e6d769