Compare commits

...

22 Commits

Author SHA1 Message Date
Karl Seguin
de0a04a58e Relax assertion on httpclient abort
It's ok to still have transfers, as long as whatever transfers still exists
are in an aborted state.
2026-04-02 17:59:17 +08:00
Karl Seguin
38fa9602fa Merge pull request #2067 from lightpanda-io/percent-encode-version
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / wba-demo-scripts (push) Has been cancelled
e2e-test / wba-test (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
e2e-test / browser fetch (push) Has been cancelled
zig-test / zig fmt (push) Has been cancelled
zig-test / zig test using v8 in debug mode (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
percent encode version query string for crash report
2026-04-02 07:50:31 +08:00
Pierre Tachoire
9661204c8d percent encode version query string for crash report 2026-04-01 22:15:56 +02:00
Karl Seguin
6800e53b0e Merge pull request #2014 from lightpanda-io/build-check
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / wba-demo-scripts (push) Has been cancelled
e2e-test / wba-test (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
e2e-test / browser fetch (push) Has been cancelled
zig-test / zig fmt (push) Has been cancelled
zig-test / zig test using v8 in debug mode (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
build: add check step to verify compilation
2026-04-01 21:05:04 +08:00
Nikolay Govorov
e79da3a4ad Merge pull request #2064 from lightpanda-io/network_naming
Improve network naming consistency
2026-04-01 13:10:12 +01:00
Karl Seguin
145792c4f5 Merge pull request #2061 from lightpanda-io/ariaAtomic
Add Element.ariaAtomic and Element.ariaLive properties
2026-04-01 20:06:15 +08:00
Karl Seguin
0bb3e3827d Merge pull request #2060 from lightpanda-io/HTMLAnchorElement.rel
Add HTMLAnchorElement.rel property
2026-04-01 20:04:43 +08:00
Karl Seguin
6e6e6e6fad Merge pull request #2057 from lightpanda-io/element-title
Add HTMLElement.title property
2026-04-01 19:36:12 +08:00
Karl Seguin
9d13a7ccdb Merge pull request #2065 from lightpanda-io/browser/resolve-scheme-in-path
Browser/resolve scheme in path
2026-04-01 19:30:40 +08:00
Karl Seguin
7fcaa500d8 Fix typo in variable name
protect against overflow if path stats with ':'

Minor tweaks to https://github.com/lightpanda-io/browser/pull/2046
2026-04-01 19:20:55 +08:00
Karl Seguin
0604056f76 Improve network naming consistency
1.
Runtime.zig -> Network.zig (especially since most places imported it as
`const Network = @import("Runtime.zig")`

2.
const net_http = @import(...) -> const http = @import(...)
2026-04-01 18:46:03 +08:00
Pierre Tachoire
5965d37c79 Add HTMLAnchorElement.rel property
Reflects the `rel` HTML attribute. The `relList` DOMTokenList was
already implemented but the string `rel` accessor was missing.
2026-04-01 11:15:10 +02:00
Pierre Tachoire
e430051fff Add Element.ariaAtomic and Element.ariaLive properties
ARIAMixin attribute reflection on Element, per the ARIA spec.
2026-04-01 11:13:52 +02:00
Pierre Tachoire
ca8361f5c1 Merge pull request #2059 from lightpanda-io/integration-e2e-report
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / wba-demo-scripts (push) Has been cancelled
e2e-test / wba-test (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
e2e-test / browser fetch (push) Has been cancelled
zig-test / zig fmt (push) Has been cancelled
zig-test / zig test using v8 in debug mode (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
ci: send e2e integration results to slack
2026-04-01 10:41:16 +02:00
Pierre Tachoire
bc4afcd82f Merge pull request #2058 from lightpanda-io/wpt-diff
Wpt diff
2026-04-01 10:31:12 +02:00
Pierre Tachoire
1c1bd81daa ci: send e2e integration results to slack 2026-04-01 10:29:56 +02:00
Pierre Tachoire
ea87fc2c50 ci: publish ci regression list into slack 2026-04-01 10:18:06 +02:00
Pierre Tachoire
e412dfed2f Add HTMLElement.title property
Reflects the `title` HTML attribute as a getter/setter on HTMLElement.
2026-04-01 09:15:34 +02:00
dinisimys2018
2d87f5bf47 fix(browser-url): handle specific file scheme and change error InvalidURL to TypeError 2026-03-31 18:42:03 +03:00
dinisimys2018
0a222ff397 fix(browser-url): add more combinations base+path handle 2026-03-31 16:54:06 +03:00
dinisimys2018
9a0cefad26 fix(browser-url): url resolve scheme in path 2026-03-30 18:58:19 +03:00
Adrià Arrufat
3aeba97fc9 build: add check step to verify compilation 2026-03-27 14:25:17 +09:00
19 changed files with 434 additions and 98 deletions

View File

@@ -60,7 +60,20 @@ jobs:
- run: chmod a+x ./lightpanda - run: chmod a+x ./lightpanda
- name: run end to end integration tests - name: run end to end integration tests
continue-on-error: true
run: | run: |
./lightpanda serve --log-level error & echo $! > LPD.pid ./lightpanda serve --log-level error & echo $! > LPD.pid
go run integration/main.go go run integration/main.go |tee result.log
kill `cat LPD.pid` kill `cat LPD.pid`
- name: Send result to slack
uses: slackapi/slack-github-action@v3.0.1
with:
errors: true
method: files.uploadV2
token: ${{ secrets.CI_SLACK_BOT_TOKEN }}
payload: |
channel_id: ${{ vars.E2E_SLACK_CHANNEL_ID }}
initial_comment: "Last e2e integration tests"
file: "./result.log"
filename: "e2e-integration-${{ github.sha }}.txt"

View File

@@ -153,3 +153,34 @@ jobs:
- name: format and send json result - name: format and send json result
run: /perf-fmt wpt ${{ github.sha }} wpt.json run: /perf-fmt wpt ${{ github.sha }} wpt.json
wptdiff:
name: perf-fmt
needs: perf-fmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
repository: 'lightpanda-io/demo'
fetch-depth: 0
- run: |
cd ./wptdiff
CGO_ENABLED=0 go build
- run: |
./wptdiff/wptdiff |tee diff.log
- name: Send regression to slack
uses: slackapi/slack-github-action@v3.0.1
with:
errors: true
method: files.uploadV2
token: ${{ secrets.CI_SLACK_BOT_TOKEN }}
payload: |
channel_id: ${{ vars.WPT_SLACK_CHANNEL_ID }}
initial_comment: "Last WPT regressions"
file: "./diff.log"
filename: "wpt-regression-${{ github.sha }}.txt"

View File

@@ -46,8 +46,12 @@ pub fn build(b: *Build) !void {
var stdout = std.fs.File.stdout().writer(&.{}); var stdout = std.fs.File.stdout().writer(&.{});
try stdout.interface.print("Lightpanda {f}\n", .{version}); try stdout.interface.print("Lightpanda {f}\n", .{version});
const version_string = b.fmt("{f}", .{version});
const version_encoded = std.mem.replaceOwned(u8, b.allocator, version_string, "+", "%2B") catch @panic("OOM");
var opts = b.addOptions(); var opts = b.addOptions();
opts.addOption([]const u8, "version", b.fmt("{f}", .{version})); opts.addOption([]const u8, "version", version_string);
opts.addOption([]const u8, "version_encoded", version_encoded);
opts.addOption(?[]const u8, "snapshot_path", snapshot_path); opts.addOption(?[]const u8, "snapshot_path", snapshot_path);
const enable_tsan = b.option(bool, "tsan", "Enable Thread Sanitizer") orelse false; const enable_tsan = b.option(bool, "tsan", "Enable Thread Sanitizer") orelse false;
@@ -85,6 +89,15 @@ pub fn build(b: *Build) !void {
break :blk mod; break :blk mod;
}; };
// Check compilation
const check = b.step("check", "Check if lightpanda compiles");
const check_lib = b.addLibrary(.{
.name = "lightpanda_check",
.root_module = lightpanda_module,
});
check.dependOn(&check_lib.step);
{ {
// browser // browser
const exe = b.addExecutable(.{ const exe = b.addExecutable(.{
@@ -103,6 +116,12 @@ pub fn build(b: *Build) !void {
}); });
b.installArtifact(exe); b.installArtifact(exe);
const exe_check = b.addLibrary(.{
.name = "lightpanda_exe_check",
.root_module = exe.root_module,
});
check.dependOn(&exe_check.step);
const run_cmd = b.addRunArtifact(exe); const run_cmd = b.addRunArtifact(exe);
if (b.args) |args| { if (b.args) |args| {
run_cmd.addArgs(args); run_cmd.addArgs(args);
@@ -132,6 +151,12 @@ pub fn build(b: *Build) !void {
}); });
b.installArtifact(exe); b.installArtifact(exe);
const exe_check = b.addLibrary(.{
.name = "snapshot_creator_check",
.root_module = exe.root_module,
});
check.dependOn(&exe_check.step);
const run_cmd = b.addRunArtifact(exe); const run_cmd = b.addRunArtifact(exe);
if (b.args) |args| { if (b.args) |args| {
run_cmd.addArgs(args); run_cmd.addArgs(args);
@@ -170,6 +195,12 @@ pub fn build(b: *Build) !void {
}); });
b.installArtifact(exe); b.installArtifact(exe);
const exe_check = b.addLibrary(.{
.name = "legacy_test_check",
.root_module = exe.root_module,
});
check.dependOn(&exe_check.step);
const run_cmd = b.addRunArtifact(exe); const run_cmd = b.addRunArtifact(exe);
if (b.args) |args| { if (b.args) |args| {
run_cmd.addArgs(args); run_cmd.addArgs(args);

View File

@@ -26,7 +26,7 @@ const Snapshot = @import("browser/js/Snapshot.zig");
const Platform = @import("browser/js/Platform.zig"); const Platform = @import("browser/js/Platform.zig");
const Telemetry = @import("telemetry/telemetry.zig").Telemetry; const Telemetry = @import("telemetry/telemetry.zig").Telemetry;
const Network = @import("network/Runtime.zig"); const Network = @import("network/Network.zig");
pub const ArenaPool = @import("ArenaPool.zig"); pub const ArenaPool = @import("ArenaPool.zig");
const App = @This(); const App = @This();

View File

@@ -34,7 +34,6 @@ pub const RunMode = enum {
mcp, mcp,
}; };
pub const MAX_LISTENERS = 16;
pub const CDP_MAX_HTTP_REQUEST_SIZE = 4096; pub const CDP_MAX_HTTP_REQUEST_SIZE = 4096;
// max message size // max message size

View File

@@ -30,7 +30,7 @@ const Notification = @import("../Notification.zig");
const CookieJar = @import("webapi/storage/Cookie.zig").Jar; const CookieJar = @import("webapi/storage/Cookie.zig").Jar;
const http = @import("../network/http.zig"); const http = @import("../network/http.zig");
const Runtime = @import("../network/Runtime.zig"); const Network = @import("../network/Network.zig");
const Robots = @import("../network/Robots.zig"); const Robots = @import("../network/Robots.zig");
const IS_DEBUG = builtin.mode == .Debug; const IS_DEBUG = builtin.mode == .Debug;
@@ -86,7 +86,7 @@ queue: std.DoublyLinkedList = .{},
// The main app allocator // The main app allocator
allocator: Allocator, allocator: Allocator,
network: *Runtime, network: *Network,
// Queue of requests that depend on a robots.txt. // Queue of requests that depend on a robots.txt.
// Allows us to fetch the robots.txt just once. // Allows us to fetch the robots.txt just once.
@@ -131,7 +131,7 @@ pub const CDPClient = struct {
blocking_read_end: *const fn (*anyopaque) bool, blocking_read_end: *const fn (*anyopaque) bool,
}; };
pub fn init(allocator: Allocator, network: *Runtime) !*Client { pub fn init(allocator: Allocator, network: *Network) !*Client {
var transfer_pool = std.heap.MemoryPool(Transfer).init(allocator); var transfer_pool = std.heap.MemoryPool(Transfer).init(allocator);
errdefer transfer_pool.deinit(); errdefer transfer_pool.deinit();
@@ -235,10 +235,6 @@ fn _abort(self: *Client, comptime abort_all: bool, frame_id: u32) void {
} }
} }
if (comptime IS_DEBUG and abort_all) {
std.debug.assert(self.active == 0);
}
{ {
var q = &self.queue; var q = &self.queue;
var n = q.first; var n = q.first;
@@ -259,12 +255,16 @@ fn _abort(self: *Client, comptime abort_all: bool, frame_id: u32) void {
} }
if (comptime IS_DEBUG and abort_all) { if (comptime IS_DEBUG and abort_all) {
std.debug.assert(self.in_use.first == null); // Even after an abort_all, we could still have transfers, but, at the
// very least, they should all be flagged as aborted.
const running = self.handles.perform() catch |err| { var it = self.in_use.first;
lp.assert(false, "multi perform in abort", .{ .err = err }); var leftover: usize = 0;
}; while (it) |node| : (it = node.next) {
std.debug.assert(running == 0); const conn: *http.Connection = @fieldParentPtr("node", node);
std.debug.assert((Transfer.fromConnection(conn) catch unreachable).aborted);
leftover += 1;
}
std.debug.assert(self.active == leftover);
} }
} }
@@ -695,7 +695,7 @@ fn perform(self: *Client, timeout_ms: c_int) anyerror!PerformStatus {
break :blk try self.handles.perform(); break :blk try self.handles.perform();
}; };
// Process dirty connections — return them to Runtime pool. // Process dirty connections — return them to Network pool.
while (self.dirty.popFirst()) |node| { while (self.dirty.popFirst()) |node| {
const conn: *http.Connection = @fieldParentPtr("node", node); const conn: *http.Connection = @fieldParentPtr("node", node);
self.handles.remove(conn) catch |err| { self.handles.remove(conn) catch |err| {

View File

@@ -22,7 +22,7 @@ const builtin = @import("builtin");
const log = @import("../log.zig"); const log = @import("../log.zig");
const HttpClient = @import("HttpClient.zig"); const HttpClient = @import("HttpClient.zig");
const net_http = @import("../network/http.zig"); const http = @import("../network/http.zig");
const String = @import("../string.zig").String; const String = @import("../string.zig").String;
const js = @import("js/js.zig"); const js = @import("js/js.zig");
@@ -136,7 +136,7 @@ fn clearList(list: *std.DoublyLinkedList) void {
} }
} }
fn getHeaders(self: *ScriptManager) !net_http.Headers { fn getHeaders(self: *ScriptManager) !http.Headers {
var headers = try self.client.newHeaders(); var headers = try self.client.newHeaders();
try self.page.headersForRequest(&headers); try self.page.headersForRequest(&headers);
return headers; return headers;

View File

@@ -25,28 +25,72 @@ const ResolveOpts = struct {
}; };
// path is anytype, so that it can be used with both []const u8 and [:0]const u8 // path is anytype, so that it can be used with both []const u8 and [:0]const u8
pub fn resolve(allocator: Allocator, base: [:0]const u8, path: anytype, comptime opts: ResolveOpts) ![:0]const u8 { pub fn resolve(allocator: Allocator, base: [:0]const u8, source_path: anytype, comptime opts: ResolveOpts) ![:0]const u8 {
const PT = @TypeOf(path); const PT = @TypeOf(source_path);
if (base.len == 0 or isCompleteHTTPUrl(path)) {
if (comptime opts.always_dupe or !isNullTerminated(PT)) { var path: [:0]const u8 = if (comptime !isNullTerminated(PT) or opts.always_dupe) try allocator.dupeZ(u8, source_path) else source_path;
const duped = try allocator.dupeZ(u8, path);
return processResolved(allocator, duped, opts); if (base.len == 0) {
return processResolved(allocator, path, opts);
}
// Minimum is "x:" and skip relative path (very common case)
if (path.len >= 2 and path[0] != '/') {
if (std.mem.indexOfScalar(u8, path[0..], ':')) |scheme_path_end| {
scheme_check: {
const scheme_path = path[0..scheme_path_end];
//from "ws" to "https"
if (scheme_path_end >= 2 and scheme_path_end <= 5) {
const has_double_slashes: bool = scheme_path_end + 3 <= path.len and path[scheme_path_end + 1] == '/' and path[scheme_path_end + 2] == '/';
const special_schemes = [_][]const u8{ "https", "http", "ws", "wss", "file", "ftp" };
for (special_schemes) |special_scheme| {
if (std.ascii.eqlIgnoreCase(scheme_path, special_scheme)) {
const base_scheme_end = std.mem.indexOf(u8, base, "://") orelse 0;
if (base_scheme_end > 0 and std.mem.eql(u8, base[0..base_scheme_end], scheme_path) and !has_double_slashes) {
//Skip ":" and exit as relative state
path = path[scheme_path_end + 1 ..];
break :scheme_check;
} else {
var rest_start: usize = scheme_path_end + 1;
//Skip any slashas after "scheme:"
while (rest_start < path.len and (path[rest_start] == '/' or path[rest_start] == '\\')) {
rest_start += 1;
}
// A special scheme (exclude "file") must contain at least any chars after "://"
if (rest_start == path.len and !std.ascii.eqlIgnoreCase(scheme_path, "file")) {
return error.TypeError;
}
//File scheme allow empty host
const separator: []const u8 = if (!has_double_slashes and std.ascii.eqlIgnoreCase(scheme_path, "file")) ":///" else "://";
path = try std.mem.joinZ(allocator, "", &.{ scheme_path, separator, path[rest_start..] });
return processResolved(allocator, path, opts);
}
}
}
}
if (scheme_path.len > 0) {
for (scheme_path[1..]) |c| {
if (!std.ascii.isAlphanumeric(c) and c != '+' and c != '-' and c != '.') {
//Exit as relative state
break :scheme_check;
}
}
}
//path is complete http url
return processResolved(allocator, path, opts);
}
} }
if (comptime opts.encode) {
return processResolved(allocator, path, opts);
}
return path;
} }
if (path.len == 0) { if (path.len == 0) {
if (comptime opts.always_dupe) { if (opts.always_dupe) {
const duped = try allocator.dupeZ(u8, base); const dupe = try allocator.dupeZ(u8, base);
return processResolved(allocator, duped, opts); return processResolved(allocator, dupe, opts);
} }
if (comptime opts.encode) { return processResolved(allocator, base, opts);
return processResolved(allocator, base, opts);
}
return base;
} }
if (path[0] == '?') { if (path[0] == '?') {
@@ -63,14 +107,7 @@ pub fn resolve(allocator: Allocator, base: [:0]const u8, path: anytype, comptime
if (std.mem.startsWith(u8, path, "//")) { if (std.mem.startsWith(u8, path, "//")) {
// network-path reference // network-path reference
const index = std.mem.indexOfScalar(u8, base, ':') orelse { const index = std.mem.indexOfScalar(u8, base, ':') orelse {
if (comptime isNullTerminated(PT)) { return processResolved(allocator, path, opts);
if (comptime opts.encode) {
return processResolved(allocator, path, opts);
}
return path;
}
const duped = try allocator.dupeZ(u8, path);
return processResolved(allocator, duped, opts);
}; };
const protocol = base[0 .. index + 1]; const protocol = base[0 .. index + 1];
const result = try std.mem.joinZ(allocator, "", &.{ protocol, path }); const result = try std.mem.joinZ(allocator, "", &.{ protocol, path });
@@ -96,6 +133,7 @@ pub fn resolve(allocator: Allocator, base: [:0]const u8, path: anytype, comptime
// trailing space so that we always have space to append the null terminator // trailing space so that we always have space to append the null terminator
// and so that we can compare the next two characters without needing to length check // and so that we can compare the next two characters without needing to length check
var out = try std.mem.join(allocator, "", &.{ normalized_base, "/", path, " " }); var out = try std.mem.join(allocator, "", &.{ normalized_base, "/", path, " " });
const end = out.len - 2; const end = out.len - 2;
const path_marker = path_start + 1; const path_marker = path_start + 1;
@@ -1570,3 +1608,182 @@ test "URL: getOrigin" {
} }
} }
} }
test "URL: resolve path scheme" {
const Case = struct {
base: [:0]const u8,
path: [:0]const u8,
expected: [:0]const u8,
expected_error: bool = false,
};
const cases = [_]Case{
//same schemes and path as relative path (one slash)
.{
.base = "https://www.example.com/example",
.path = "https:/about",
.expected = "https://www.example.com/about",
},
//same schemes and path as relative path (without slash)
.{
.base = "https://www.example.com/example",
.path = "https:about",
.expected = "https://www.example.com/about",
},
//same schemes and path as absolute path (two slashes)
.{
.base = "https://www.example.com/example",
.path = "https://about",
.expected = "https://about",
},
//different schemes and path as absolute (without slash)
.{
.base = "https://www.example.com/example",
.path = "http:about",
.expected = "http://about",
},
//different schemes and path as absolute (with one slash)
.{
.base = "https://www.example.com/example",
.path = "http:/about",
.expected = "http://about",
},
//different schemes and path as absolute (with two slashes)
.{
.base = "https://www.example.com/example",
.path = "http://about",
.expected = "http://about",
},
//same schemes and path as absolute (with more slashes)
.{
.base = "https://site/",
.path = "https://path",
.expected = "https://path",
},
//path scheme is not special and path as absolute (without additional slashes)
.{
.base = "http://localhost/",
.path = "data:test",
.expected = "data:test",
},
//different schemes and path as absolute (pathscheme=ws)
.{
.base = "https://www.example.com/example",
.path = "ws://about",
.expected = "ws://about",
},
//different schemes and path as absolute (path scheme=wss)
.{
.base = "https://www.example.com/example",
.path = "wss://about",
.expected = "wss://about",
},
//different schemes and path as absolute (path scheme=ftp)
.{
.base = "https://www.example.com/example",
.path = "ftp://about",
.expected = "ftp://about",
},
//different schemes and path as absolute (path scheme=file)
.{
.base = "https://www.example.com/example",
.path = "file://path/to/file",
.expected = "file://path/to/file",
},
//different schemes and path as absolute (path scheme=file, host is empty)
.{
.base = "https://www.example.com/example",
.path = "file:/path/to/file",
.expected = "file:///path/to/file",
},
//different schemes and path as absolute (path scheme=file, host is empty)
.{
.base = "https://www.example.com/example",
.path = "file:/",
.expected = "file:///",
},
//different schemes without :// and normalize "file" scheme, absolute path
.{
.base = "https://www.example.com/example",
.path = "file:path/to/file",
.expected = "file:///path/to/file",
},
//same schemes without :// in path and rest starts with scheme:/, relative path
.{
.base = "https://www.example.com/example",
.path = "https:/file:/relative/path/",
.expected = "https://www.example.com/file:/relative/path/",
},
//same schemes without :// in path and rest starts with scheme://, relative path
.{
.base = "https://www.example.com/example",
.path = "https:/http://relative/path/",
.expected = "https://www.example.com/http://relative/path/",
},
//same schemes without :// in path , relative state
.{
.base = "http://www.example.com/example",
.path = "http:relative:path",
.expected = "http://www.example.com/relative:path",
},
//repeat different schemes in path
.{
.base = "http://www.example.com/example",
.path = "http:http:/relative/path/",
.expected = "http://www.example.com/http:/relative/path/",
},
//repeat different schemes in path
.{
.base = "http://www.example.com/example",
.path = "http:https://relative:path",
.expected = "http://www.example.com/https://relative:path",
},
//NOT required :// for blob scheme
.{
.base = "http://www.example.com/example",
.path = "blob:other",
.expected = "blob:other",
},
//NOT required :// for NON-special schemes and can contains "+" or "-" or "." in scheme
.{
.base = "http://www.example.com/example",
.path = "custom+foo:other",
.expected = "custom+foo:other",
},
//NOT required :// for NON-special schemes
.{
.base = "http://www.example.com/example",
.path = "blob:",
.expected = "blob:",
},
//NOT required :// for special scheme equal base scheme
.{
.base = "http://www.example.com/example",
.path = "http:",
.expected = "http://www.example.com/example",
},
//required :// for special scheme, so throw error.InvalidURL
.{
.base = "http://www.example.com/example",
.path = "https:",
.expected = "",
.expected_error = true,
},
//incorrect symbols in path scheme
.{
.base = "https://site",
.path = "http?://host/some",
.expected = "https://site/http?://host/some",
},
};
for (cases) |case| {
if (case.expected_error) {
const result = resolve(testing.arena_allocator, case.base, case.path, .{});
try testing.expectError(error.TypeError, result);
} else {
const result = try resolve(testing.arena_allocator, case.base, case.path, .{});
try testing.expectString(case.expected, result);
}
}
}

View File

@@ -523,6 +523,31 @@ pub fn setDir(self: *Element, value: []const u8, page: *Page) !void {
return self.setAttributeSafe(comptime .wrap("dir"), .wrap(value), page); return self.setAttributeSafe(comptime .wrap("dir"), .wrap(value), page);
} }
// ARIAMixin - ARIA attribute reflection
pub fn getAriaAtomic(self: *const Element) ?[]const u8 {
return self.getAttributeSafe(comptime .wrap("aria-atomic"));
}
pub fn setAriaAtomic(self: *Element, value: ?[]const u8, page: *Page) !void {
if (value) |v| {
try self.setAttributeSafe(comptime .wrap("aria-atomic"), .wrap(v), page);
} else {
try self.removeAttribute(comptime .wrap("aria-atomic"), page);
}
}
pub fn getAriaLive(self: *const Element) ?[]const u8 {
return self.getAttributeSafe(comptime .wrap("aria-live"));
}
pub fn setAriaLive(self: *Element, value: ?[]const u8, page: *Page) !void {
if (value) |v| {
try self.setAttributeSafe(comptime .wrap("aria-live"), .wrap(v), page);
} else {
try self.removeAttribute(comptime .wrap("aria-live"), page);
}
}
pub fn getClassName(self: *const Element) []const u8 { pub fn getClassName(self: *const Element) []const u8 {
return self.getAttributeSafe(comptime .wrap("class")) orelse ""; return self.getAttributeSafe(comptime .wrap("class")) orelse "";
} }
@@ -1686,6 +1711,8 @@ pub const JsApi = struct {
pub const localName = bridge.accessor(Element.getLocalName, null, .{}); pub const localName = bridge.accessor(Element.getLocalName, null, .{});
pub const id = bridge.accessor(Element.getId, Element.setId, .{}); pub const id = bridge.accessor(Element.getId, Element.setId, .{});
pub const slot = bridge.accessor(Element.getSlot, Element.setSlot, .{}); pub const slot = bridge.accessor(Element.getSlot, Element.setSlot, .{});
pub const ariaAtomic = bridge.accessor(Element.getAriaAtomic, Element.setAriaAtomic, .{});
pub const ariaLive = bridge.accessor(Element.getAriaLive, Element.setAriaLive, .{});
pub const dir = bridge.accessor(Element.getDir, Element.setDir, .{}); pub const dir = bridge.accessor(Element.getDir, Element.setDir, .{});
pub const className = bridge.accessor(Element.getClassName, Element.setClassName, .{}); pub const className = bridge.accessor(Element.getClassName, Element.setClassName, .{});
pub const classList = bridge.accessor(Element.getClassList, Element.setClassList, .{}); pub const classList = bridge.accessor(Element.getClassList, Element.setClassList, .{});

View File

@@ -391,6 +391,14 @@ pub fn setLang(self: *HtmlElement, value: []const u8, page: *Page) !void {
try self.asElement().setAttributeSafe(comptime .wrap("lang"), .wrap(value), page); try self.asElement().setAttributeSafe(comptime .wrap("lang"), .wrap(value), page);
} }
pub fn getTitle(self: *HtmlElement) []const u8 {
return self.asElement().getAttributeSafe(comptime .wrap("title")) orelse "";
}
pub fn setTitle(self: *HtmlElement, value: []const u8, page: *Page) !void {
try self.asElement().setAttributeSafe(comptime .wrap("title"), .wrap(value), page);
}
pub fn getAttributeFunction( pub fn getAttributeFunction(
self: *HtmlElement, self: *HtmlElement,
listener_type: GlobalEventHandler, listener_type: GlobalEventHandler,
@@ -1231,6 +1239,7 @@ pub const JsApi = struct {
pub const hidden = bridge.accessor(HtmlElement.getHidden, HtmlElement.setHidden, .{}); pub const hidden = bridge.accessor(HtmlElement.getHidden, HtmlElement.setHidden, .{});
pub const lang = bridge.accessor(HtmlElement.getLang, HtmlElement.setLang, .{}); pub const lang = bridge.accessor(HtmlElement.getLang, HtmlElement.setLang, .{});
pub const tabIndex = bridge.accessor(HtmlElement.getTabIndex, HtmlElement.setTabIndex, .{}); pub const tabIndex = bridge.accessor(HtmlElement.getTabIndex, HtmlElement.setTabIndex, .{});
pub const title = bridge.accessor(HtmlElement.getTitle, HtmlElement.setTitle, .{});
pub const onabort = bridge.accessor(HtmlElement.getOnAbort, HtmlElement.setOnAbort, .{}); pub const onabort = bridge.accessor(HtmlElement.getOnAbort, HtmlElement.setOnAbort, .{});
pub const onanimationcancel = bridge.accessor(HtmlElement.getOnAnimationCancel, HtmlElement.setOnAnimationCancel, .{}); pub const onanimationcancel = bridge.accessor(HtmlElement.getOnAnimationCancel, HtmlElement.setOnAnimationCancel, .{});

View File

@@ -174,6 +174,14 @@ pub fn setType(self: *Anchor, value: []const u8, page: *Page) !void {
try self.asElement().setAttributeSafe(comptime .wrap("type"), .wrap(value), page); try self.asElement().setAttributeSafe(comptime .wrap("type"), .wrap(value), page);
} }
pub fn getRel(self: *Anchor) []const u8 {
return self.asConstElement().getAttributeSafe(comptime .wrap("rel")) orelse "";
}
pub fn setRel(self: *Anchor, value: []const u8, page: *Page) !void {
try self.asElement().setAttributeSafe(comptime .wrap("rel"), .wrap(value), page);
}
pub fn getName(self: *const Anchor) []const u8 { pub fn getName(self: *const Anchor) []const u8 {
return self.asConstElement().getAttributeSafe(comptime .wrap("name")) orelse ""; return self.asConstElement().getAttributeSafe(comptime .wrap("name")) orelse "";
} }
@@ -218,6 +226,7 @@ pub const JsApi = struct {
pub const pathname = bridge.accessor(Anchor.getPathname, Anchor.setPathname, .{}); pub const pathname = bridge.accessor(Anchor.getPathname, Anchor.setPathname, .{});
pub const search = bridge.accessor(Anchor.getSearch, Anchor.setSearch, .{}); pub const search = bridge.accessor(Anchor.getSearch, Anchor.setSearch, .{});
pub const hash = bridge.accessor(Anchor.getHash, Anchor.setHash, .{}); pub const hash = bridge.accessor(Anchor.getHash, Anchor.setHash, .{});
pub const rel = bridge.accessor(Anchor.getRel, Anchor.setRel, .{});
pub const @"type" = bridge.accessor(Anchor.getType, Anchor.setType, .{}); pub const @"type" = bridge.accessor(Anchor.getType, Anchor.setType, .{});
pub const text = bridge.accessor(Anchor.getText, Anchor.setText, .{}); pub const text = bridge.accessor(Anchor.getText, Anchor.setText, .{});
pub const relList = bridge.accessor(_getRelList, null, .{ .null_as_undefined = true }); pub const relList = bridge.accessor(_getRelList, null, .{ .null_as_undefined = true });

View File

@@ -86,8 +86,8 @@ pub fn forEach(self: *Headers, cb_: js.Function, js_this_: ?js.Object) !void {
} }
// TODO: do we really need 2 different header structs?? // TODO: do we really need 2 different header structs??
const net_http = @import("../../../network/http.zig"); const http = @import("../../../network/http.zig");
pub fn populateHttpHeader(self: *Headers, allocator: Allocator, http_headers: *net_http.Headers) !void { pub fn populateHttpHeader(self: *Headers, allocator: Allocator, http_headers: *http.Headers) !void {
for (self._list._entries.items) |entry| { for (self._list._entries.items) |entry| {
const merged = try std.mem.concatWithSentinel(allocator, u8, &.{ entry.name.str(), ": ", entry.value.str() }, 0); const merged = try std.mem.concatWithSentinel(allocator, u8, &.{ entry.name.str(), ": ", entry.value.str() }, 0);
try http_headers.add(merged); try http_headers.add(merged);

View File

@@ -19,7 +19,7 @@
const std = @import("std"); const std = @import("std");
const js = @import("../../js/js.zig"); const js = @import("../../js/js.zig");
const net_http = @import("../../../network/http.zig"); const http = @import("../../../network/http.zig");
const URL = @import("../URL.zig"); const URL = @import("../URL.zig");
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");
@@ -31,7 +31,7 @@ const Allocator = std.mem.Allocator;
const Request = @This(); const Request = @This();
_url: [:0]const u8, _url: [:0]const u8,
_method: net_http.Method, _method: http.Method,
_headers: ?*Headers, _headers: ?*Headers,
_body: ?[]const u8, _body: ?[]const u8,
_arena: Allocator, _arena: Allocator,
@@ -119,14 +119,14 @@ pub fn init(input: Input, opts_: ?InitOpts, page: *Page) !*Request {
}); });
} }
fn parseMethod(method: []const u8, page: *Page) !net_http.Method { fn parseMethod(method: []const u8, page: *Page) !http.Method {
if (method.len > "propfind".len) { if (method.len > "propfind".len) {
return error.InvalidMethod; return error.InvalidMethod;
} }
const lower = std.ascii.lowerString(&page.buf, method); const lower = std.ascii.lowerString(&page.buf, method);
const method_lookup = std.StaticStringMap(net_http.Method).initComptime(.{ const method_lookup = std.StaticStringMap(http.Method).initComptime(.{
.{ "get", .GET }, .{ "get", .GET },
.{ "post", .POST }, .{ "post", .POST },
.{ "delete", .DELETE }, .{ "delete", .DELETE },

View File

@@ -22,7 +22,7 @@ const js = @import("../../js/js.zig");
const log = @import("../../../log.zig"); const log = @import("../../../log.zig");
const HttpClient = @import("../../HttpClient.zig"); const HttpClient = @import("../../HttpClient.zig");
const net_http = @import("../../../network/http.zig"); const http = @import("../../../network/http.zig");
const URL = @import("../../URL.zig"); const URL = @import("../../URL.zig");
const Mime = @import("../../Mime.zig"); const Mime = @import("../../Mime.zig");
@@ -47,7 +47,7 @@ _transfer: ?*HttpClient.Transfer = null,
_active_request: bool = false, _active_request: bool = false,
_url: [:0]const u8 = "", _url: [:0]const u8 = "",
_method: net_http.Method = .GET, _method: http.Method = .GET,
_request_headers: *Headers, _request_headers: *Headers,
_request_body: ?[]const u8 = null, _request_body: ?[]const u8 = null,
@@ -406,7 +406,7 @@ fn httpStartCallback(transfer: *HttpClient.Transfer) !void {
self._transfer = transfer; self._transfer = transfer;
} }
fn httpHeaderCallback(transfer: *HttpClient.Transfer, header: net_http.Header) !void { fn httpHeaderCallback(transfer: *HttpClient.Transfer, header: http.Header) !void {
const self: *XMLHttpRequest = @ptrCast(@alignCast(transfer.ctx)); const self: *XMLHttpRequest = @ptrCast(@alignCast(transfer.ctx));
const joined = try std.fmt.allocPrint(self._arena, "{s}: {s}", .{ header.name, header.value }); const joined = try std.fmt.allocPrint(self._arena, "{s}: {s}", .{ header.name, header.value });
try self._response_headers.append(self._arena, joined); try self._response_headers.append(self._arena, joined);
@@ -574,7 +574,7 @@ fn stateChanged(self: *XMLHttpRequest, state: ReadyState, page: *Page) !void {
} }
} }
fn parseMethod(method: []const u8) !net_http.Method { fn parseMethod(method: []const u8) !http.Method {
if (std.ascii.eqlIgnoreCase(method, "get")) { if (std.ascii.eqlIgnoreCase(method, "get")) {
return .GET; return .GET;
} }

View File

@@ -23,7 +23,7 @@ const CDP = @import("../CDP.zig");
const log = @import("../../log.zig"); const log = @import("../../log.zig");
const HttpClient = @import("../../browser/HttpClient.zig"); const HttpClient = @import("../../browser/HttpClient.zig");
const net_http = @import("../../network/http.zig"); const http = @import("../../network/http.zig");
const Notification = @import("../../Notification.zig"); const Notification = @import("../../Notification.zig");
const network = @import("network.zig"); const network = @import("network.zig");
@@ -224,7 +224,7 @@ fn continueRequest(cmd: *CDP.Command) !void {
url: ?[]const u8 = null, url: ?[]const u8 = null,
method: ?[]const u8 = null, method: ?[]const u8 = null,
postData: ?[]const u8 = null, postData: ?[]const u8 = null,
headers: ?[]const net_http.Header = null, headers: ?[]const http.Header = null,
interceptResponse: bool = false, interceptResponse: bool = false,
})) orelse return error.InvalidParams; })) orelse return error.InvalidParams;
@@ -249,7 +249,7 @@ fn continueRequest(cmd: *CDP.Command) !void {
try transfer.updateURL(try arena.dupeZ(u8, url)); try transfer.updateURL(try arena.dupeZ(u8, url));
} }
if (params.method) |method| { if (params.method) |method| {
transfer.req.method = std.meta.stringToEnum(net_http.Method, method) orelse return error.InvalidParams; transfer.req.method = std.meta.stringToEnum(http.Method, method) orelse return error.InvalidParams;
} }
if (params.headers) |headers| { if (params.headers) |headers| {
@@ -326,7 +326,7 @@ fn fulfillRequest(cmd: *CDP.Command) !void {
const params = (try cmd.params(struct { const params = (try cmd.params(struct {
requestId: []const u8, // "INT-{d}" requestId: []const u8, // "INT-{d}"
responseCode: u16, responseCode: u16,
responseHeaders: ?[]const net_http.Header = null, responseHeaders: ?[]const http.Header = null,
binaryResponseHeaders: ?[]const u8 = null, binaryResponseHeaders: ?[]const u8 = null,
body: ?[]const u8 = null, body: ?[]const u8 = null,
responsePhrase: ?[]const u8 = null, responsePhrase: ?[]const u8 = null,

View File

@@ -86,7 +86,7 @@ fn report(reason: []const u8, begin_addr: usize, args: anytype) !void {
var url_buffer: [4096]u8 = undefined; var url_buffer: [4096]u8 = undefined;
const url = blk: { const url = blk: {
var writer: std.Io.Writer = .fixed(&url_buffer); var writer: std.Io.Writer = .fixed(&url_buffer);
try writer.print("https://crash.lightpanda.io/c?v={s}&r=", .{lp.build_config.version}); try writer.print("https://crash.lightpanda.io/c?v={s}&r=", .{lp.build_config.version_encoded});
for (reason) |b| { for (reason) |b| {
switch (b) { switch (b) {
'A'...'Z', 'a'...'z', '0'...'9', '-', '.', '_' => try writer.writeByte(b), 'A'...'Z', 'a'...'z', '0'...'9', '-', '.', '_' => try writer.writeByte(b),

View File

@@ -18,7 +18,7 @@
const std = @import("std"); const std = @import("std");
pub const App = @import("App.zig"); pub const App = @import("App.zig");
pub const Network = @import("network/Runtime.zig"); pub const Network = @import("network/Network.zig");
pub const Server = @import("Server.zig"); pub const Server = @import("Server.zig");
pub const Config = @import("Config.zig"); pub const Config = @import("Config.zig");
pub const URL = @import("browser/URL.zig"); pub const URL = @import("browser/URL.zig");

View File

@@ -26,11 +26,11 @@ const lp = @import("lightpanda");
const Config = @import("../Config.zig"); const Config = @import("../Config.zig");
const libcurl = @import("../sys/libcurl.zig"); const libcurl = @import("../sys/libcurl.zig");
const net_http = @import("http.zig"); const http = @import("http.zig");
const RobotStore = @import("Robots.zig").RobotStore; const RobotStore = @import("Robots.zig").RobotStore;
const WebBotAuth = @import("WebBotAuth.zig"); const WebBotAuth = @import("WebBotAuth.zig");
const Runtime = @This(); const Network = @This();
const Listener = struct { const Listener = struct {
socket: posix.socket_t, socket: posix.socket_t,
@@ -46,11 +46,11 @@ const MAX_TICK_CALLBACKS = 16;
allocator: Allocator, allocator: Allocator,
config: *const Config, config: *const Config,
ca_blob: ?net_http.Blob, ca_blob: ?http.Blob,
robot_store: RobotStore, robot_store: RobotStore,
web_bot_auth: ?WebBotAuth, web_bot_auth: ?WebBotAuth,
connections: []net_http.Connection, connections: []http.Connection,
available: std.DoublyLinkedList = .{}, available: std.DoublyLinkedList = .{},
conn_mutex: std.Thread.Mutex = .{}, conn_mutex: std.Thread.Mutex = .{},
@@ -63,8 +63,8 @@ wakeup_pipe: [2]posix.fd_t = .{ -1, -1 },
shutdown: std.atomic.Value(bool) = .init(false), shutdown: std.atomic.Value(bool) = .init(false),
// Multi is a heavy structure that can consume up to 2MB of RAM. // Multi is a heavy structure that can consume up to 2MB of RAM.
// Currently, Runtime is used sparingly, and we only create it on demand. // Currently, Network is used sparingly, and we only create it on demand.
// When Runtime becomes truly shared, it should become a regular field. // When Network becomes truly shared, it should become a regular field.
multi: ?*libcurl.CurlM = null, multi: ?*libcurl.CurlM = null,
submission_mutex: std.Thread.Mutex = .{}, submission_mutex: std.Thread.Mutex = .{},
submission_queue: std.DoublyLinkedList = .{}, submission_queue: std.DoublyLinkedList = .{},
@@ -200,7 +200,7 @@ fn globalDeinit() void {
libcurl.curl_global_cleanup(); libcurl.curl_global_cleanup();
} }
pub fn init(allocator: Allocator, config: *const Config) !Runtime { pub fn init(allocator: Allocator, config: *const Config) !Network {
globalInit(allocator); globalInit(allocator);
errdefer globalDeinit(); errdefer globalDeinit();
@@ -213,18 +213,18 @@ pub fn init(allocator: Allocator, config: *const Config) !Runtime {
@memset(pollfds, .{ .fd = -1, .events = 0, .revents = 0 }); @memset(pollfds, .{ .fd = -1, .events = 0, .revents = 0 });
pollfds[0] = .{ .fd = pipe[0], .events = posix.POLL.IN, .revents = 0 }; pollfds[0] = .{ .fd = pipe[0], .events = posix.POLL.IN, .revents = 0 };
var ca_blob: ?net_http.Blob = null; var ca_blob: ?http.Blob = null;
if (config.tlsVerifyHost()) { if (config.tlsVerifyHost()) {
ca_blob = try loadCerts(allocator); ca_blob = try loadCerts(allocator);
} }
const count: usize = config.httpMaxConcurrent(); const count: usize = config.httpMaxConcurrent();
const connections = try allocator.alloc(net_http.Connection, count); const connections = try allocator.alloc(http.Connection, count);
errdefer allocator.free(connections); errdefer allocator.free(connections);
var available: std.DoublyLinkedList = .{}; var available: std.DoublyLinkedList = .{};
for (0..count) |i| { for (0..count) |i| {
connections[i] = try net_http.Connection.init(ca_blob, config); connections[i] = try http.Connection.init(ca_blob, config);
available.append(&connections[i].node); available.append(&connections[i].node);
} }
@@ -249,7 +249,7 @@ pub fn init(allocator: Allocator, config: *const Config) !Runtime {
}; };
} }
pub fn deinit(self: *Runtime) void { pub fn deinit(self: *Network) void {
if (self.multi) |multi| { if (self.multi) |multi| {
libcurl.curl_multi_cleanup(multi) catch {}; libcurl.curl_multi_cleanup(multi) catch {};
} }
@@ -282,7 +282,7 @@ pub fn deinit(self: *Runtime) void {
} }
pub fn bind( pub fn bind(
self: *Runtime, self: *Network,
address: net.Address, address: net.Address,
ctx: *anyopaque, ctx: *anyopaque,
on_accept: *const fn (ctx: *anyopaque, socket: posix.socket_t) void, on_accept: *const fn (ctx: *anyopaque, socket: posix.socket_t) void,
@@ -313,7 +313,7 @@ pub fn bind(
}; };
} }
pub fn onTick(self: *Runtime, ctx: *anyopaque, callback: *const fn (*anyopaque) void) void { pub fn onTick(self: *Network, ctx: *anyopaque, callback: *const fn (*anyopaque) void) void {
self.callbacks_mutex.lock(); self.callbacks_mutex.lock();
defer self.callbacks_mutex.unlock(); defer self.callbacks_mutex.unlock();
@@ -328,7 +328,7 @@ pub fn onTick(self: *Runtime, ctx: *anyopaque, callback: *const fn (*anyopaque)
self.wakeupPoll(); self.wakeupPoll();
} }
pub fn fireTicks(self: *Runtime) void { pub fn fireTicks(self: *Network) void {
self.callbacks_mutex.lock(); self.callbacks_mutex.lock();
defer self.callbacks_mutex.unlock(); defer self.callbacks_mutex.unlock();
@@ -337,7 +337,7 @@ pub fn fireTicks(self: *Runtime) void {
} }
} }
pub fn run(self: *Runtime) void { pub fn run(self: *Network) void {
var drain_buf: [64]u8 = undefined; var drain_buf: [64]u8 = undefined;
var running_handles: c_int = 0; var running_handles: c_int = 0;
@@ -428,18 +428,18 @@ pub fn run(self: *Runtime) void {
} }
} }
pub fn submitRequest(self: *Runtime, conn: *net_http.Connection) void { pub fn submitRequest(self: *Network, conn: *http.Connection) void {
self.submission_mutex.lock(); self.submission_mutex.lock();
self.submission_queue.append(&conn.node); self.submission_queue.append(&conn.node);
self.submission_mutex.unlock(); self.submission_mutex.unlock();
self.wakeupPoll(); self.wakeupPoll();
} }
fn wakeupPoll(self: *Runtime) void { fn wakeupPoll(self: *Network) void {
_ = posix.write(self.wakeup_pipe[1], &.{1}) catch {}; _ = posix.write(self.wakeup_pipe[1], &.{1}) catch {};
} }
fn drainQueue(self: *Runtime) void { fn drainQueue(self: *Network) void {
self.submission_mutex.lock(); self.submission_mutex.lock();
defer self.submission_mutex.unlock(); defer self.submission_mutex.unlock();
@@ -455,7 +455,7 @@ fn drainQueue(self: *Runtime) void {
}; };
while (self.submission_queue.popFirst()) |node| { while (self.submission_queue.popFirst()) |node| {
const conn: *net_http.Connection = @fieldParentPtr("node", node); const conn: *http.Connection = @fieldParentPtr("node", node);
conn.setPrivate(conn) catch |err| { conn.setPrivate(conn) catch |err| {
lp.log.err(.app, "curl set private", .{ .err = err }); lp.log.err(.app, "curl set private", .{ .err = err });
self.releaseConnection(conn); self.releaseConnection(conn);
@@ -468,12 +468,12 @@ fn drainQueue(self: *Runtime) void {
} }
} }
pub fn stop(self: *Runtime) void { pub fn stop(self: *Network) void {
self.shutdown.store(true, .release); self.shutdown.store(true, .release);
self.wakeupPoll(); self.wakeupPoll();
} }
fn acceptConnections(self: *Runtime) void { fn acceptConnections(self: *Network) void {
if (self.shutdown.load(.acquire)) { if (self.shutdown.load(.acquire)) {
return; return;
} }
@@ -503,7 +503,7 @@ fn acceptConnections(self: *Runtime) void {
} }
} }
fn preparePollFds(self: *Runtime, multi: *libcurl.CurlM) void { fn preparePollFds(self: *Network, multi: *libcurl.CurlM) void {
const curl_fds = self.pollfds[PSEUDO_POLLFDS..]; const curl_fds = self.pollfds[PSEUDO_POLLFDS..];
@memset(curl_fds, .{ .fd = -1, .events = 0, .revents = 0 }); @memset(curl_fds, .{ .fd = -1, .events = 0, .revents = 0 });
@@ -514,14 +514,14 @@ fn preparePollFds(self: *Runtime, multi: *libcurl.CurlM) void {
}; };
} }
fn getCurlTimeout(self: *Runtime) i32 { fn getCurlTimeout(self: *Network) i32 {
const multi = self.multi orelse return -1; const multi = self.multi orelse return -1;
var timeout_ms: c_long = -1; var timeout_ms: c_long = -1;
libcurl.curl_multi_timeout(multi, &timeout_ms) catch return -1; libcurl.curl_multi_timeout(multi, &timeout_ms) catch return -1;
return @intCast(@min(timeout_ms, std.math.maxInt(i32))); return @intCast(@min(timeout_ms, std.math.maxInt(i32)));
} }
fn processCompletions(self: *Runtime, multi: *libcurl.CurlM) void { fn processCompletions(self: *Network, multi: *libcurl.CurlM) void {
var msgs_in_queue: c_int = 0; var msgs_in_queue: c_int = 0;
while (libcurl.curl_multi_info_read(multi, &msgs_in_queue)) |msg| { while (libcurl.curl_multi_info_read(multi, &msgs_in_queue)) |msg| {
switch (msg.data) { switch (msg.data) {
@@ -537,7 +537,7 @@ fn processCompletions(self: *Runtime, multi: *libcurl.CurlM) void {
var ptr: *anyopaque = undefined; var ptr: *anyopaque = undefined;
libcurl.curl_easy_getinfo(easy, .private, &ptr) catch libcurl.curl_easy_getinfo(easy, .private, &ptr) catch
lp.assert(false, "curl getinfo private", .{}); lp.assert(false, "curl getinfo private", .{});
const conn: *net_http.Connection = @ptrCast(@alignCast(ptr)); const conn: *http.Connection = @ptrCast(@alignCast(ptr));
libcurl.curl_multi_remove_handle(multi, easy) catch {}; libcurl.curl_multi_remove_handle(multi, easy) catch {};
self.releaseConnection(conn); self.releaseConnection(conn);
@@ -556,7 +556,7 @@ comptime {
} }
} }
pub fn getConnection(self: *Runtime) ?*net_http.Connection { pub fn getConnection(self: *Network) ?*http.Connection {
self.conn_mutex.lock(); self.conn_mutex.lock();
defer self.conn_mutex.unlock(); defer self.conn_mutex.unlock();
@@ -564,7 +564,7 @@ pub fn getConnection(self: *Runtime) ?*net_http.Connection {
return @fieldParentPtr("node", node); return @fieldParentPtr("node", node);
} }
pub fn releaseConnection(self: *Runtime, conn: *net_http.Connection) void { pub fn releaseConnection(self: *Network, conn: *http.Connection) void {
conn.reset(self.config, self.ca_blob) catch |err| { conn.reset(self.config, self.ca_blob) catch |err| {
lp.assert(false, "couldn't reset curl easy", .{ .err = err }); lp.assert(false, "couldn't reset curl easy", .{ .err = err });
}; };
@@ -575,8 +575,8 @@ pub fn releaseConnection(self: *Runtime, conn: *net_http.Connection) void {
self.available.append(&conn.node); self.available.append(&conn.node);
} }
pub fn newConnection(self: *Runtime) !net_http.Connection { pub fn newConnection(self: *Network) !http.Connection {
return net_http.Connection.init(self.ca_blob, self.config); return http.Connection.init(self.ca_blob, self.config);
} }
// Wraps lines @ 64 columns. A PEM is basically a base64 encoded DER (which is // Wraps lines @ 64 columns. A PEM is basically a base64 encoded DER (which is

View File

@@ -8,7 +8,7 @@ const log = @import("../log.zig");
const App = @import("../App.zig"); const App = @import("../App.zig");
const Config = @import("../Config.zig"); const Config = @import("../Config.zig");
const telemetry = @import("telemetry.zig"); const telemetry = @import("telemetry.zig");
const Runtime = @import("../network/Runtime.zig"); const Network = @import("../network/Network.zig");
const URL = "https://telemetry.lightpanda.io"; const URL = "https://telemetry.lightpanda.io";
const BUFFER_SIZE = 1024; const BUFFER_SIZE = 1024;
@@ -17,7 +17,7 @@ const MAX_BODY_SIZE = 500 * 1024; // 500KB server limit
const LightPanda = @This(); const LightPanda = @This();
allocator: Allocator, allocator: Allocator,
runtime: *Runtime, network: *Network,
writer: std.Io.Writer.Allocating, writer: std.Io.Writer.Allocating,
/// Protects concurrent producers in send(). /// Protects concurrent producers in send().
@@ -36,11 +36,11 @@ pub fn init(self: *LightPanda, app: *App, iid: ?[36]u8, run_mode: Config.RunMode
.iid = iid, .iid = iid,
.run_mode = run_mode, .run_mode = run_mode,
.allocator = app.allocator, .allocator = app.allocator,
.runtime = &app.network, .network = &app.network,
.writer = std.Io.Writer.Allocating.init(app.allocator), .writer = std.Io.Writer.Allocating.init(app.allocator),
}; };
self.runtime.onTick(@ptrCast(self), flushCallback); self.network.onTick(@ptrCast(self), flushCallback);
} }
pub fn deinit(self: *LightPanda) void { pub fn deinit(self: *LightPanda) void {
@@ -70,17 +70,17 @@ fn flushCallback(ctx: *anyopaque) void {
} }
fn postEvent(self: *LightPanda) !void { fn postEvent(self: *LightPanda) !void {
const conn = self.runtime.getConnection() orelse { const conn = self.network.getConnection() orelse {
return; return;
}; };
errdefer self.runtime.releaseConnection(conn); errdefer self.network.releaseConnection(conn);
const h = self.head.load(.monotonic); const h = self.head.load(.monotonic);
const t = self.tail.load(.acquire); const t = self.tail.load(.acquire);
const dropped = self.dropped.swap(0, .monotonic); const dropped = self.dropped.swap(0, .monotonic);
if (h == t and dropped == 0) { if (h == t and dropped == 0) {
self.runtime.releaseConnection(conn); self.network.releaseConnection(conn);
return; return;
} }
errdefer _ = self.dropped.fetchAdd(dropped, .monotonic); errdefer _ = self.dropped.fetchAdd(dropped, .monotonic);
@@ -104,7 +104,7 @@ fn postEvent(self: *LightPanda) !void {
try conn.setBody(self.writer.written()); try conn.setBody(self.writer.written());
self.head.store(h + sent, .release); self.head.store(h + sent, .release);
self.runtime.submitRequest(conn); self.network.submitRequest(conn);
} }
fn writeEvent(self: *LightPanda, event: telemetry.Event) !bool { fn writeEvent(self: *LightPanda, event: telemetry.Event) !bool {