mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-29 16:10:04 +00:00
Compare commits
24 Commits
0.2.7
...
url-userin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c04ccf5e87 | ||
|
|
a0dd14aaad | ||
|
|
7ea8f3f766 | ||
|
|
28cc60adb0 | ||
|
|
c14a9ad986 | ||
|
|
679f2104f4 | ||
|
|
c6b0c75106 | ||
|
|
93485c1ef3 | ||
|
|
0324d5c232 | ||
|
|
a75c0cf08d | ||
|
|
2812b8f07c | ||
|
|
e2afbec29d | ||
|
|
a45f9cb810 | ||
|
|
cf641ed458 | ||
|
|
0fc959dcc5 | ||
|
|
077376ea04 | ||
|
|
6ed8d1d201 | ||
|
|
5207bd4202 | ||
|
|
11ed95290b | ||
|
|
ca41bb5fa2 | ||
|
|
0dd0495ab8 | ||
|
|
ae080f32eb | ||
|
|
b19f30d865 | ||
|
|
35be9f897f |
2
.github/actions/install/action.yml
vendored
2
.github/actions/install/action.yml
vendored
@@ -13,7 +13,7 @@ inputs:
|
|||||||
zig-v8:
|
zig-v8:
|
||||||
description: 'zig v8 version to install'
|
description: 'zig v8 version to install'
|
||||||
required: false
|
required: false
|
||||||
default: 'v0.3.4'
|
default: 'v0.3.7'
|
||||||
v8:
|
v8:
|
||||||
description: 'v8 version to install'
|
description: 'v8 version to install'
|
||||||
required: false
|
required: false
|
||||||
|
|||||||
10
.github/workflows/nightly.yml
vendored
10
.github/workflows/nightly.yml
vendored
@@ -7,7 +7,7 @@ env:
|
|||||||
AWS_REGION: ${{ vars.NIGHTLY_BUILD_AWS_REGION }}
|
AWS_REGION: ${{ vars.NIGHTLY_BUILD_AWS_REGION }}
|
||||||
|
|
||||||
RELEASE: ${{ github.ref_type == 'tag' && github.ref_name || 'nightly' }}
|
RELEASE: ${{ github.ref_type == 'tag' && github.ref_name || 'nightly' }}
|
||||||
VERSION: ${{ github.ref_type == 'tag' && github.ref_name || '1.0.0-nightly' }}
|
VERSION_FLAG: ${{ github.ref_type == 'tag' && format('-Dversion_string={0}', github.ref_name) || format('-Dpre_version={0}', 'nightly') }}
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -45,7 +45,7 @@ jobs:
|
|||||||
run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast snapshot_creator -- src/snapshot.bin
|
run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast snapshot_creator -- src/snapshot.bin
|
||||||
|
|
||||||
- name: zig build
|
- name: zig build
|
||||||
run: zig build -Dsnapshot_path=../../snapshot.bin -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -Dcpu=x86_64 -Dversion_string=${{ env.VERSION }}
|
run: zig build -Dsnapshot_path=../../snapshot.bin -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -Dcpu=x86_64 ${{ env.VERSION_FLAG }}
|
||||||
|
|
||||||
- name: Rename binary
|
- name: Rename binary
|
||||||
run: mv zig-out/bin/lightpanda lightpanda-${{ env.ARCH }}-${{ env.OS }}
|
run: mv zig-out/bin/lightpanda lightpanda-${{ env.ARCH }}-${{ env.OS }}
|
||||||
@@ -85,7 +85,7 @@ jobs:
|
|||||||
run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast snapshot_creator -- src/snapshot.bin
|
run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast snapshot_creator -- src/snapshot.bin
|
||||||
|
|
||||||
- name: zig build
|
- name: zig build
|
||||||
run: zig build -Dsnapshot_path=../../snapshot.bin -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -Dcpu=generic -Dversion_string=${{ env.VERSION }}
|
run: zig build -Dsnapshot_path=../../snapshot.bin -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -Dcpu=generic ${{ env.VERSION_FLAG }}
|
||||||
|
|
||||||
- name: Rename binary
|
- name: Rename binary
|
||||||
run: mv zig-out/bin/lightpanda lightpanda-${{ env.ARCH }}-${{ env.OS }}
|
run: mv zig-out/bin/lightpanda lightpanda-${{ env.ARCH }}-${{ env.OS }}
|
||||||
@@ -127,7 +127,7 @@ jobs:
|
|||||||
run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast snapshot_creator -- src/snapshot.bin
|
run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast snapshot_creator -- src/snapshot.bin
|
||||||
|
|
||||||
- name: zig build
|
- name: zig build
|
||||||
run: zig build -Dsnapshot_path=../../snapshot.bin -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -Dversion_string=${{ env.VERSION }}
|
run: zig build -Dsnapshot_path=../../snapshot.bin -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast ${{ env.VERSION_FLAG }}
|
||||||
|
|
||||||
- name: Rename binary
|
- name: Rename binary
|
||||||
run: mv zig-out/bin/lightpanda lightpanda-${{ env.ARCH }}-${{ env.OS }}
|
run: mv zig-out/bin/lightpanda lightpanda-${{ env.ARCH }}-${{ env.OS }}
|
||||||
@@ -167,7 +167,7 @@ jobs:
|
|||||||
run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast snapshot_creator -- src/snapshot.bin
|
run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast snapshot_creator -- src/snapshot.bin
|
||||||
|
|
||||||
- name: zig build
|
- name: zig build
|
||||||
run: zig build -Dsnapshot_path=../../snapshot.bin -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -Dversion_string=${{ env.VERSION }}
|
run: zig build -Dsnapshot_path=../../snapshot.bin -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast ${{ env.VERSION_FLAG }}
|
||||||
|
|
||||||
- name: Rename binary
|
- name: Rename binary
|
||||||
run: mv zig-out/bin/lightpanda lightpanda-${{ env.ARCH }}-${{ env.OS }}
|
run: mv zig-out/bin/lightpanda lightpanda-${{ env.ARCH }}-${{ env.OS }}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ FROM debian:stable-slim
|
|||||||
ARG MINISIG=0.12
|
ARG MINISIG=0.12
|
||||||
ARG ZIG_MINISIG=RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U
|
ARG ZIG_MINISIG=RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U
|
||||||
ARG V8=14.0.365.4
|
ARG V8=14.0.365.4
|
||||||
ARG ZIG_V8=v0.3.4
|
ARG ZIG_V8=v0.3.7
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
RUN apt-get update -yq && \
|
RUN apt-get update -yq && \
|
||||||
|
|||||||
13
build.zig
13
build.zig
@@ -728,8 +728,17 @@ fn resolveVersion(b: *std.Build) std.SemanticVersion {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pre_version = b.option([]const u8, "pre_version", "Override the pre version of this build");
|
||||||
|
const pre = blk: {
|
||||||
|
if (pre_version) |pre| {
|
||||||
|
break :blk pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
break :blk lightpanda_version.pre;
|
||||||
|
};
|
||||||
|
|
||||||
// If it's a stable release (no pre or build metadata in build.zig.zon), use it as is
|
// If it's a stable release (no pre or build metadata in build.zig.zon), use it as is
|
||||||
if (lightpanda_version.pre == null and lightpanda_version.build == null) return lightpanda_version;
|
if (pre == null and lightpanda_version.build == null) return lightpanda_version;
|
||||||
|
|
||||||
// For dev/nightly versions, calculate the commit count and hash
|
// For dev/nightly versions, calculate the commit count and hash
|
||||||
const git_hash_raw = runGit(b, &.{ "rev-parse", "--short", "HEAD" }) catch return lightpanda_version;
|
const git_hash_raw = runGit(b, &.{ "rev-parse", "--short", "HEAD" }) catch return lightpanda_version;
|
||||||
@@ -742,7 +751,7 @@ fn resolveVersion(b: *std.Build) std.SemanticVersion {
|
|||||||
.major = lightpanda_version.major,
|
.major = lightpanda_version.major,
|
||||||
.minor = lightpanda_version.minor,
|
.minor = lightpanda_version.minor,
|
||||||
.patch = lightpanda_version.patch,
|
.patch = lightpanda_version.patch,
|
||||||
.pre = b.fmt("{s}.{s}", .{ lightpanda_version.pre.?, commit_count }),
|
.pre = b.fmt("{s}.{s}", .{ pre.?, commit_count }),
|
||||||
.build = commit_hash,
|
.build = commit_hash,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
.minimum_zig_version = "0.15.2",
|
.minimum_zig_version = "0.15.2",
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
.v8 = .{
|
.v8 = .{
|
||||||
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/refs/tags/v0.3.4.tar.gz",
|
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/refs/tags/v0.3.7.tar.gz",
|
||||||
.hash = "v8-0.0.0-xddH6_F3BAAiFvKY6R1H-gkuQlk19BkDQ0--uZuTrSup",
|
.hash = "v8-0.0.0-xddH67uBBAD95hWsPQz3Ni1PlZjdywtPXrGUAp8rSKco",
|
||||||
},
|
},
|
||||||
// .v8 = .{ .path = "../zig-v8-fork" },
|
// .v8 = .{ .path = "../zig-v8-fork" },
|
||||||
.brotli = .{
|
.brotli = .{
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ pub const Fetch = struct {
|
|||||||
with_frames: bool = false,
|
with_frames: bool = false,
|
||||||
strip: dump.Opts.Strip = .{},
|
strip: dump.Opts.Strip = .{},
|
||||||
wait_ms: u32 = 5000,
|
wait_ms: u32 = 5000,
|
||||||
wait_until: WaitUntil = .load,
|
wait_until: WaitUntil = .done,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Common = struct {
|
pub const Common = struct {
|
||||||
@@ -665,7 +665,7 @@ fn parseFetchArgs(
|
|||||||
var common: Common = .{};
|
var common: Common = .{};
|
||||||
var strip: dump.Opts.Strip = .{};
|
var strip: dump.Opts.Strip = .{};
|
||||||
var wait_ms: u32 = 5000;
|
var wait_ms: u32 = 5000;
|
||||||
var wait_until: WaitUntil = .load;
|
var wait_until: WaitUntil = .done;
|
||||||
|
|
||||||
while (args.next()) |opt| {
|
while (args.next()) |opt| {
|
||||||
if (std.mem.eql(u8, "--wait-ms", opt) or std.mem.eql(u8, "--wait_ms", opt)) {
|
if (std.mem.eql(u8, "--wait-ms", opt) or std.mem.eql(u8, "--wait_ms", opt)) {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const ArenaAllocator = std.heap.ArenaAllocator;
|
|||||||
const log = @import("log.zig");
|
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 CDP = @import("cdp/cdp.zig").CDP;
|
const CDP = @import("cdp/CDP.zig");
|
||||||
const Net = @import("network/websocket.zig");
|
const Net = @import("network/websocket.zig");
|
||||||
const HttpClient = @import("browser/HttpClient.zig");
|
const HttpClient = @import("browser/HttpClient.zig");
|
||||||
|
|
||||||
@@ -212,7 +212,7 @@ pub const Client = struct {
|
|||||||
http: *HttpClient,
|
http: *HttpClient,
|
||||||
ws: Net.WsConnection,
|
ws: Net.WsConnection,
|
||||||
|
|
||||||
fn init(
|
pub fn init(
|
||||||
socket: posix.socket_t,
|
socket: posix.socket_t,
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
app: *App,
|
app: *App,
|
||||||
@@ -250,7 +250,7 @@ pub const Client = struct {
|
|||||||
self.ws.shutdown();
|
self.ws.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deinit(self: *Client) void {
|
pub fn deinit(self: *Client) void {
|
||||||
switch (self.mode) {
|
switch (self.mode) {
|
||||||
.cdp => |*cdp| cdp.deinit(),
|
.cdp => |*cdp| cdp.deinit(),
|
||||||
.http => {},
|
.http => {},
|
||||||
@@ -461,7 +461,7 @@ pub const Client = struct {
|
|||||||
|
|
||||||
fn upgradeConnection(self: *Client, request: []u8) !void {
|
fn upgradeConnection(self: *Client, request: []u8) !void {
|
||||||
try self.ws.upgrade(request);
|
try self.ws.upgrade(request);
|
||||||
self.mode = .{ .cdp = try CDP.init(self.app, self.http, self) };
|
self.mode = .{ .cdp = try CDP.init(self) };
|
||||||
}
|
}
|
||||||
|
|
||||||
fn writeHTTPErrorResponse(self: *Client, comptime status: u16, comptime body: []const u8) void {
|
fn writeHTTPErrorResponse(self: *Client, comptime status: u16, comptime body: []const u8) void {
|
||||||
|
|||||||
@@ -300,7 +300,9 @@ pub fn init(self: *Page, frame_id: u32, session: *Session, parent: ?*Page) !void
|
|||||||
._performance = Performance.init(),
|
._performance = Performance.init(),
|
||||||
._screen = screen,
|
._screen = screen,
|
||||||
._visual_viewport = visual_viewport,
|
._visual_viewport = visual_viewport,
|
||||||
|
._cross_origin_wrapper = undefined,
|
||||||
});
|
});
|
||||||
|
self.window._cross_origin_wrapper = .{ .window = self.window };
|
||||||
|
|
||||||
self._style_manager = try StyleManager.init(self);
|
self._style_manager = try StyleManager.init(self);
|
||||||
errdefer self._style_manager.deinit();
|
errdefer self._style_manager.deinit();
|
||||||
@@ -3587,12 +3589,7 @@ test "WebApi: Page" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "WebApi: Frames" {
|
test "WebApi: Frames" {
|
||||||
// TOO FLAKY, disabled for now
|
try testing.htmlRunner("frames", .{});
|
||||||
|
|
||||||
// const filter: testing.LogFilter = .init(&.{.js});
|
|
||||||
// defer filter.deinit();
|
|
||||||
|
|
||||||
// try testing.htmlRunner("frames", .{});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test "WebApi: Integration" {
|
test "WebApi: Integration" {
|
||||||
|
|||||||
@@ -404,10 +404,6 @@ pub fn getOrigin(allocator: Allocator, raw: [:0]const u8) !?[]const u8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var authority_start = scheme_end + 3;
|
var authority_start = scheme_end + 3;
|
||||||
const has_user_info = if (std.mem.indexOf(u8, raw[authority_start..], "@")) |pos| blk: {
|
|
||||||
authority_start += pos + 1;
|
|
||||||
break :blk true;
|
|
||||||
} else false;
|
|
||||||
|
|
||||||
// Find end of authority (start of path/query/fragment or end of string)
|
// Find end of authority (start of path/query/fragment or end of string)
|
||||||
const authority_end_relative = std.mem.indexOfAny(u8, raw[authority_start..], "/?#");
|
const authority_end_relative = std.mem.indexOfAny(u8, raw[authority_start..], "/?#");
|
||||||
@@ -416,6 +412,12 @@ pub fn getOrigin(allocator: Allocator, raw: [:0]const u8) !?[]const u8 {
|
|||||||
else
|
else
|
||||||
raw.len;
|
raw.len;
|
||||||
|
|
||||||
|
// We mustn't search the `@` after the first path separator.
|
||||||
|
const has_user_info = if (std.mem.indexOf(u8, raw[authority_start..authority_end], "@")) |pos| blk: {
|
||||||
|
authority_start += pos + 1;
|
||||||
|
break :blk true;
|
||||||
|
} else false;
|
||||||
|
|
||||||
// Check for port in the host:port section
|
// Check for port in the host:port section
|
||||||
const host_part = raw[authority_start..authority_end];
|
const host_part = raw[authority_start..authority_end];
|
||||||
if (std.mem.lastIndexOfScalar(u8, host_part, ':')) |colon_pos_in_host| {
|
if (std.mem.lastIndexOfScalar(u8, host_part, ':')) |colon_pos_in_host| {
|
||||||
@@ -461,8 +463,15 @@ fn getUserInfo(raw: [:0]const u8) ?[]const u8 {
|
|||||||
const scheme_end = std.mem.indexOf(u8, raw, "://") orelse return null;
|
const scheme_end = std.mem.indexOf(u8, raw, "://") orelse return null;
|
||||||
const authority_start = scheme_end + 3;
|
const authority_start = scheme_end + 3;
|
||||||
|
|
||||||
const pos = std.mem.indexOfScalar(u8, raw[authority_start..], '@') orelse return null;
|
// We mustn't search the `@` after the first path separator.
|
||||||
const path_start = std.mem.indexOfScalarPos(u8, raw, authority_start, '/') orelse raw.len;
|
const path_start = blk: {
|
||||||
|
if (std.mem.indexOfAny(u8, raw[authority_start..], "/?#")) |idx| {
|
||||||
|
break :blk authority_start + idx;
|
||||||
|
}
|
||||||
|
break :blk raw.len;
|
||||||
|
};
|
||||||
|
|
||||||
|
const pos = std.mem.indexOfScalar(u8, raw[authority_start..path_start], '@') orelse return null;
|
||||||
|
|
||||||
const full_pos = authority_start + pos;
|
const full_pos = authority_start + pos;
|
||||||
if (full_pos < path_start) {
|
if (full_pos < path_start) {
|
||||||
@@ -476,13 +485,20 @@ pub fn getHost(raw: [:0]const u8) []const u8 {
|
|||||||
const scheme_end = std.mem.indexOf(u8, raw, "://") orelse return "";
|
const scheme_end = std.mem.indexOf(u8, raw, "://") orelse return "";
|
||||||
|
|
||||||
var authority_start = scheme_end + 3;
|
var authority_start = scheme_end + 3;
|
||||||
if (std.mem.indexOf(u8, raw[authority_start..], "@")) |pos| {
|
|
||||||
|
// We mustn't search the `@` after the first path separator.
|
||||||
|
const path_start = blk: {
|
||||||
|
if (std.mem.indexOfAny(u8, raw[authority_start..], "/?#")) |idx| {
|
||||||
|
break :blk authority_start + idx;
|
||||||
|
}
|
||||||
|
break :blk raw.len;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (std.mem.indexOf(u8, raw[authority_start..path_start], "@")) |pos| {
|
||||||
authority_start += pos + 1;
|
authority_start += pos + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const authority = raw[authority_start..];
|
return raw[authority_start..path_start];
|
||||||
const path_start = std.mem.indexOfAny(u8, authority, "/?#") orelse return authority;
|
|
||||||
return authority[0..path_start];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if these two URLs point to the same document.
|
// Returns true if these two URLs point to the same document.
|
||||||
@@ -1449,3 +1465,36 @@ test "URL: setPathname percent-encodes" {
|
|||||||
const result3 = try setPathname("https://example.com/path?a=b#hash", "/new path", allocator);
|
const result3 = try setPathname("https://example.com/path?a=b#hash", "/new path", allocator);
|
||||||
try testing.expectEqualSlices(u8, "https://example.com/new%20path?a=b#hash", result3);
|
try testing.expectEqualSlices(u8, "https://example.com/new%20path?a=b#hash", result3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "URL: getOrigin" {
|
||||||
|
var arena = std.heap.ArenaAllocator.init(testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
const allocator = arena.allocator();
|
||||||
|
try testing.expectEqualSlices(u8, "http://example.com:8080", try getOrigin(allocator, "http://example.com:8080/path") orelse unreachable);
|
||||||
|
try testing.expectEqualSlices(u8, "https://example.com:8080", try getOrigin(allocator, "https://example.com:8080/path") orelse unreachable);
|
||||||
|
try testing.expectEqualSlices(u8, "https://example.com", try getOrigin(allocator, "https://example.com/path") orelse unreachable);
|
||||||
|
try testing.expectEqualSlices(u8, "https://example.com", try getOrigin(allocator, "https://example.com:443/") orelse unreachable);
|
||||||
|
try testing.expectEqualSlices(u8, "https://example.com", try getOrigin(allocator, "https://user:pass@example.com/page") orelse unreachable);
|
||||||
|
try testing.expectEqualSlices(u8, "https://example.com:8080", try getOrigin(allocator, "https://user:pass@example.com:8080/page") orelse unreachable);
|
||||||
|
try testing.expectEqual(null, try getOrigin(allocator, "not-a-url"));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "URL: SOP bypass" {
|
||||||
|
// SOP Bypass
|
||||||
|
try testing.expectEqualSlices(u8, "attacker.com", getHost("http://attacker.com/@bank.com/"));
|
||||||
|
try testing.expectEqualSlices(u8, "attacker.com", getHost("https://attacker.com/@bank.com/"));
|
||||||
|
try testing.expectEqualSlices(u8, "attacker.com", getHost("http://attacker.com?@bank.com/"));
|
||||||
|
try testing.expectEqualSlices(u8, "attacker.com", getHost("http://attacker.com#@bank.com/"));
|
||||||
|
try testing.expectEqualSlices(u8, "attacker.com", getHostname("http://attacker.com/@bank.com/"));
|
||||||
|
try testing.expectEqualSlices(u8, "attacker.com", getHostname("https://attacker.com/@bank.com/"));
|
||||||
|
try testing.expectEqualSlices(u8, "attacker.com", getHostname("http://attacker.com?@bank.com/"));
|
||||||
|
try testing.expectEqualSlices(u8, "attacker.com", getHostname("http://attacker.com#@bank.com/"));
|
||||||
|
|
||||||
|
var arena = std.heap.ArenaAllocator.init(testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
const allocator = arena.allocator();
|
||||||
|
try testing.expectEqualSlices(u8, "http://attacker.com", try getOrigin(allocator, "http://attacker.com/@bank.com/") orelse unreachable);
|
||||||
|
try testing.expectEqualSlices(u8, "https://attacker.com", try getOrigin(allocator, "https://attacker.com/@bank.com/") orelse unreachable);
|
||||||
|
try testing.expectEqualSlices(u8, "http://attacker.com", try getOrigin(allocator, "http://attacker.com?bank.com/") orelse unreachable);
|
||||||
|
try testing.expectEqualSlices(u8, "http://attacker.com", try getOrigin(allocator, "http://attacker.com#bank.com/") orelse unreachable);
|
||||||
|
}
|
||||||
|
|||||||
@@ -505,6 +505,7 @@ pub const Function = struct {
|
|||||||
pub const Opts = struct {
|
pub const Opts = struct {
|
||||||
noop: bool = false,
|
noop: bool = false,
|
||||||
static: bool = false,
|
static: bool = false,
|
||||||
|
deletable: bool = true,
|
||||||
dom_exception: bool = false,
|
dom_exception: bool = false,
|
||||||
as_typed_array: bool = false,
|
as_typed_array: bool = false,
|
||||||
null_as_undefined: bool = false,
|
null_as_undefined: bool = false,
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ pub fn create() !Snapshot {
|
|||||||
defer v8.v8__HandleScope__DESTRUCT(&handle_scope);
|
defer v8.v8__HandleScope__DESTRUCT(&handle_scope);
|
||||||
|
|
||||||
// Create templates (constructors only) FIRST
|
// Create templates (constructors only) FIRST
|
||||||
var templates: [JsApis.len]*v8.FunctionTemplate = undefined;
|
var templates: [JsApis.len]*const v8.FunctionTemplate = undefined;
|
||||||
inline for (JsApis, 0..) |JsApi, i| {
|
inline for (JsApis, 0..) |JsApi, i| {
|
||||||
@setEvalBranchQuota(10_000);
|
@setEvalBranchQuota(10_000);
|
||||||
templates[i] = generateConstructor(JsApi, isolate);
|
templates[i] = generateConstructor(JsApi, isolate);
|
||||||
@@ -419,7 +419,7 @@ fn collectExternalReferences() [countExternalReferences()]isize {
|
|||||||
// via `new ClassName()` - but they could, for example, be created in
|
// via `new ClassName()` - but they could, for example, be created in
|
||||||
// Zig and returned from a function call, which is why we need the
|
// Zig and returned from a function call, which is why we need the
|
||||||
// FunctionTemplate.
|
// FunctionTemplate.
|
||||||
fn generateConstructor(comptime JsApi: type, isolate: *v8.Isolate) *v8.FunctionTemplate {
|
fn generateConstructor(comptime JsApi: type, isolate: *v8.Isolate) *const v8.FunctionTemplate {
|
||||||
const callback = blk: {
|
const callback = blk: {
|
||||||
if (@hasDecl(JsApi, "constructor")) {
|
if (@hasDecl(JsApi, "constructor")) {
|
||||||
break :blk JsApi.constructor.func;
|
break :blk JsApi.constructor.func;
|
||||||
@@ -429,7 +429,7 @@ fn generateConstructor(comptime JsApi: type, isolate: *v8.Isolate) *v8.FunctionT
|
|||||||
break :blk illegalConstructorCallback;
|
break :blk illegalConstructorCallback;
|
||||||
};
|
};
|
||||||
|
|
||||||
const template = @constCast(v8.v8__FunctionTemplate__New__DEFAULT2(isolate, callback).?);
|
const template = v8.v8__FunctionTemplate__New__DEFAULT2(isolate, callback).?;
|
||||||
{
|
{
|
||||||
const internal_field_count = comptime countInternalFields(JsApi);
|
const internal_field_count = comptime countInternalFields(JsApi);
|
||||||
if (internal_field_count > 0) {
|
if (internal_field_count > 0) {
|
||||||
@@ -482,10 +482,15 @@ pub fn countInternalFields(comptime JsApi: type) u8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Attaches JsApi members to the prototype template (normal case)
|
// Attaches JsApi members to the prototype template (normal case)
|
||||||
fn attachClass(comptime JsApi: type, isolate: *v8.Isolate, template: *v8.FunctionTemplate) void {
|
fn attachClass(comptime JsApi: type, isolate: *v8.Isolate, template: *const v8.FunctionTemplate) void {
|
||||||
const instance = v8.v8__FunctionTemplate__InstanceTemplate(template);
|
const instance = v8.v8__FunctionTemplate__InstanceTemplate(template);
|
||||||
const prototype = v8.v8__FunctionTemplate__PrototypeTemplate(template);
|
const prototype = v8.v8__FunctionTemplate__PrototypeTemplate(template);
|
||||||
|
|
||||||
|
// Create a signature that validates the receiver is an instance of this template.
|
||||||
|
// This prevents crashes when JavaScript extracts a getter/method and calls it
|
||||||
|
// with the wrong `this` (e.g., documentGetter.call(null)).
|
||||||
|
const signature = v8.v8__Signature__New(isolate, template);
|
||||||
|
|
||||||
const declarations = @typeInfo(JsApi).@"struct".decls;
|
const declarations = @typeInfo(JsApi).@"struct".decls;
|
||||||
var has_named_index_getter = false;
|
var has_named_index_getter = false;
|
||||||
|
|
||||||
@@ -497,23 +502,47 @@ fn attachClass(comptime JsApi: type, isolate: *v8.Isolate, template: *v8.Functio
|
|||||||
switch (definition) {
|
switch (definition) {
|
||||||
bridge.Accessor => {
|
bridge.Accessor => {
|
||||||
const js_name = v8.v8__String__NewFromUtf8(isolate, name.ptr, v8.kNormal, @intCast(name.len));
|
const js_name = v8.v8__String__NewFromUtf8(isolate, name.ptr, v8.kNormal, @intCast(name.len));
|
||||||
const getter_callback = @constCast(v8.v8__FunctionTemplate__New__Config(isolate, &.{ .callback = value.getter }).?);
|
const getter_signature = if (value.static) null else signature;
|
||||||
|
const getter_callback = v8.v8__FunctionTemplate__New__Config(isolate, &.{
|
||||||
|
.callback = value.getter,
|
||||||
|
.signature = getter_signature,
|
||||||
|
}).?;
|
||||||
|
const setter_callback = if (value.setter) |setter|
|
||||||
|
v8.v8__FunctionTemplate__New__Config(isolate, &.{
|
||||||
|
.callback = setter,
|
||||||
|
.signature = getter_signature,
|
||||||
|
}).?
|
||||||
|
else
|
||||||
|
null;
|
||||||
|
|
||||||
|
var attribute: v8.PropertyAttribute = 0;
|
||||||
if (value.setter == null) {
|
if (value.setter == null) {
|
||||||
|
attribute |= v8.ReadOnly;
|
||||||
|
}
|
||||||
|
if (value.deletable == false) {
|
||||||
|
attribute |= v8.DontDelete;
|
||||||
|
}
|
||||||
|
|
||||||
if (value.static) {
|
if (value.static) {
|
||||||
v8.v8__Template__SetAccessorProperty__DEFAULT(@ptrCast(template), js_name, getter_callback);
|
// Static accessors: use Template's SetAccessorProperty
|
||||||
|
v8.v8__Template__SetAccessorProperty(@ptrCast(template), js_name, getter_callback, setter_callback, attribute);
|
||||||
} else {
|
} else {
|
||||||
v8.v8__ObjectTemplate__SetAccessorProperty__DEFAULT(prototype, js_name, getter_callback);
|
v8.v8__ObjectTemplate__SetAccessorProperty__Config(prototype, &.{
|
||||||
}
|
.key = js_name,
|
||||||
} else {
|
.getter = getter_callback,
|
||||||
if (comptime IS_DEBUG) {
|
.setter = setter_callback,
|
||||||
std.debug.assert(value.static == false);
|
.attribute = attribute,
|
||||||
}
|
});
|
||||||
const setter_callback = @constCast(v8.v8__FunctionTemplate__New__Config(isolate, &.{ .callback = value.setter.? }).?);
|
|
||||||
v8.v8__ObjectTemplate__SetAccessorProperty__DEFAULT2(prototype, js_name, getter_callback, setter_callback);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
bridge.Function => {
|
bridge.Function => {
|
||||||
const function_template = @constCast(v8.v8__FunctionTemplate__New__Config(isolate, &.{ .callback = value.func, .length = value.arity }).?);
|
// For non-static functions, use the signature to validate the receiver
|
||||||
|
const func_signature = if (value.static) null else signature;
|
||||||
|
const function_template = v8.v8__FunctionTemplate__New__Config(isolate, &.{
|
||||||
|
.callback = value.func,
|
||||||
|
.length = value.arity,
|
||||||
|
.signature = func_signature,
|
||||||
|
}).?;
|
||||||
const js_name = v8.v8__String__NewFromUtf8(isolate, name.ptr, v8.kNormal, @intCast(name.len));
|
const js_name = v8.v8__String__NewFromUtf8(isolate, name.ptr, v8.kNormal, @intCast(name.len));
|
||||||
if (value.static) {
|
if (value.static) {
|
||||||
v8.v8__Template__Set(@ptrCast(template), js_name, @ptrCast(function_template), v8.None);
|
v8.v8__Template__Set(@ptrCast(template), js_name, @ptrCast(function_template), v8.None);
|
||||||
@@ -551,7 +580,7 @@ fn attachClass(comptime JsApi: type, isolate: *v8.Isolate, template: *v8.Functio
|
|||||||
has_named_index_getter = true;
|
has_named_index_getter = true;
|
||||||
},
|
},
|
||||||
bridge.Iterator => {
|
bridge.Iterator => {
|
||||||
const function_template = @constCast(v8.v8__FunctionTemplate__New__Config(isolate, &.{ .callback = value.func }).?);
|
const function_template = v8.v8__FunctionTemplate__New__Config(isolate, &.{ .callback = value.func }).?;
|
||||||
const js_name = if (value.async)
|
const js_name = if (value.async)
|
||||||
v8.v8__Symbol__GetAsyncIterator(isolate)
|
v8.v8__Symbol__GetAsyncIterator(isolate)
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -198,6 +198,7 @@ pub const Function = struct {
|
|||||||
|
|
||||||
pub const Accessor = struct {
|
pub const Accessor = struct {
|
||||||
static: bool = false,
|
static: bool = false,
|
||||||
|
deletable: bool = true,
|
||||||
cache: ?Caller.Function.Opts.Caching = null,
|
cache: ?Caller.Function.Opts.Caching = null,
|
||||||
getter: ?*const fn (?*const v8.FunctionCallbackInfo) callconv(.c) void = null,
|
getter: ?*const fn (?*const v8.FunctionCallbackInfo) callconv(.c) void = null,
|
||||||
setter: ?*const fn (?*const v8.FunctionCallbackInfo) callconv(.c) void = null,
|
setter: ?*const fn (?*const v8.FunctionCallbackInfo) callconv(.c) void = null,
|
||||||
@@ -206,6 +207,7 @@ pub const Accessor = struct {
|
|||||||
var accessor = Accessor{
|
var accessor = Accessor{
|
||||||
.cache = opts.cache,
|
.cache = opts.cache,
|
||||||
.static = opts.static,
|
.static = opts.static,
|
||||||
|
.deletable = opts.deletable,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (@typeInfo(@TypeOf(getter)) != .null) {
|
if (@typeInfo(@TypeOf(getter)) != .null) {
|
||||||
|
|||||||
@@ -118,24 +118,24 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script id=link_click>
|
<script id=link_click type=module>
|
||||||
testing.async(async (restore) => {
|
const state = await testing.async();
|
||||||
let f6;
|
|
||||||
await new Promise((resolve) => {
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
f6 = document.createElement('iframe');
|
let f6 = document.createElement('iframe');
|
||||||
f6.id = 'f6';
|
f6.id = 'f6';
|
||||||
f6.addEventListener('load', () => {
|
f6.addEventListener('load', () => {
|
||||||
if (++count == 2) {
|
if (++count == 2) {
|
||||||
resolve();
|
state.resolve();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
f6.contentDocument.querySelector('#link').click();
|
f6.contentDocument.querySelector('#link').click();
|
||||||
});
|
});
|
||||||
f6.src = "support/with_link.html";
|
|
||||||
|
f6.src = 'support/with_link.html';
|
||||||
document.documentElement.appendChild(f6);
|
document.documentElement.appendChild(f6);
|
||||||
});
|
|
||||||
restore();
|
await state.done(() => {
|
||||||
testing.expectEqual("<html><head></head><body>It was clicked!\n</body></html>", f6.contentDocument.documentElement.outerHTML);
|
testing.expectEqual("<html><head></head><body>It was clicked!\n</body></html>", f6.contentDocument.documentElement.outerHTML);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
{
|
{
|
||||||
let reply = null;
|
let reply = null;
|
||||||
window.addEventListener('message', (e) => {
|
window.addEventListener('message', (e) => {
|
||||||
console.warn('reply')
|
|
||||||
reply = e.data;
|
reply = e.data;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<script>
|
<script>
|
||||||
window.addEventListener('message', (e) => {
|
window.addEventListener('message', (e) => {
|
||||||
console.warn('Frame Message', e.data);
|
|
||||||
if (e.data === 'ping') {
|
if (e.data === 'ping') {
|
||||||
window.top.postMessage({data: 'pong', origin: e.origin}, '*');
|
window.top.postMessage({data: 'pong', origin: e.origin}, '*');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
let eventuallies = [];
|
let eventuallies = [];
|
||||||
let async_capture = null;
|
let async_capture = null;
|
||||||
let current_script_id = null;
|
let current_script_id = null;
|
||||||
|
let async_pending = 0;
|
||||||
|
|
||||||
function expectTrue(actual) {
|
function expectTrue(actual) {
|
||||||
expectEqual(true, actual);
|
expectEqual(true, actual);
|
||||||
@@ -64,6 +65,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function async(cb) {
|
async function async(cb) {
|
||||||
|
if (cb == undefined) {
|
||||||
|
let resolve = null
|
||||||
|
const promise = new Promise((r) => { resolve = r});
|
||||||
|
async_pending += 1;
|
||||||
|
|
||||||
|
return {
|
||||||
|
promise: promise,
|
||||||
|
resolve: resolve,
|
||||||
|
capture: {script_id: document.currentScript.id, stack: new Error().stack},
|
||||||
|
done: async function(cb) {
|
||||||
|
await this.promise;
|
||||||
|
async_pending -= 1;
|
||||||
|
async_capture = this.capture;
|
||||||
|
cb();
|
||||||
|
async_capture = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
let capture = {script_id: document.currentScript.id, stack: new Error().stack};
|
let capture = {script_id: document.currentScript.id, stack: new Error().stack};
|
||||||
await cb(() => { async_capture = capture; });
|
await cb(() => { async_capture = capture; });
|
||||||
async_capture = null;
|
async_capture = null;
|
||||||
@@ -74,6 +94,10 @@
|
|||||||
throw new Error('Failed');
|
throw new Error('Failed');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (async_pending > 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
for (let e of eventuallies) {
|
for (let e of eventuallies) {
|
||||||
current_script_id = e.script_id;
|
current_script_id = e.script_id;
|
||||||
e.callback();
|
e.callback();
|
||||||
@@ -97,6 +121,8 @@
|
|||||||
throw new Error(`script id: '${script_id}' failed: ${status || 'no assertions'}`);
|
throw new Error(`script id: '${script_id}' failed: ${status || 'no assertions'}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const IS_TEST_RUNNER = window.navigator.userAgent.startsWith("Lightpanda/");
|
const IS_TEST_RUNNER = window.navigator.userAgent.startsWith("Lightpanda/");
|
||||||
|
|||||||
@@ -871,3 +871,33 @@
|
|||||||
testing.expectEqual('', url.search);
|
testing.expectEqual('', url.search);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script id="SOP Bypass">
|
||||||
|
{
|
||||||
|
const url = new URL('http://example.com/@bank.com');
|
||||||
|
testing.expectEqual('http:', url.protocol);
|
||||||
|
testing.expectEqual('example.com', url.hostname);
|
||||||
|
testing.expectEqual('', url.port);
|
||||||
|
testing.expectEqual('http://example.com', url.origin);
|
||||||
|
testing.expectEqual('', url.username);
|
||||||
|
testing.expectEqual('', url.password);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const url = new URL('http://example.com?@bank.com');
|
||||||
|
testing.expectEqual('http:', url.protocol);
|
||||||
|
testing.expectEqual('example.com', url.hostname);
|
||||||
|
testing.expectEqual('', url.port);
|
||||||
|
testing.expectEqual('http://example.com', url.origin);
|
||||||
|
testing.expectEqual('', url.username);
|
||||||
|
testing.expectEqual('', url.password);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const url = new URL('http://example.com#@bank.com');
|
||||||
|
testing.expectEqual('http:', url.protocol);
|
||||||
|
testing.expectEqual('example.com', url.hostname);
|
||||||
|
testing.expectEqual('', url.port);
|
||||||
|
testing.expectEqual('http://example.com', url.origin);
|
||||||
|
testing.expectEqual('', url.username);
|
||||||
|
testing.expectEqual('', url.password);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|||||||
51
src/browser/tests/window/cross_origin.html
Normal file
51
src/browser/tests/window/cross_origin.html
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<script src="../testing.js"></script>
|
||||||
|
|
||||||
|
<iframe src=support/frame1.html></iframe>
|
||||||
|
|
||||||
|
<script id=post_message type=module>
|
||||||
|
const state = await testing.async();
|
||||||
|
|
||||||
|
{
|
||||||
|
const ALT_BASE = testing.BASE_URL.replace('127.0.0.1', 'localhost');
|
||||||
|
|
||||||
|
{
|
||||||
|
let iframe2 = document.createElement('iframe');
|
||||||
|
iframe2.src = ALT_BASE + 'window/support/frame1.html';
|
||||||
|
document.documentElement.appendChild(iframe2);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let iframe3 = document.createElement('iframe');
|
||||||
|
iframe3.src = ALT_BASE + 'window/support/frame2.html';
|
||||||
|
document.documentElement.appendChild(iframe3);
|
||||||
|
}
|
||||||
|
|
||||||
|
let captures = [];
|
||||||
|
window.addEventListener('message', (e) => {
|
||||||
|
captures.push(e.data);
|
||||||
|
if (captures.length == 3) {
|
||||||
|
state.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await state.done(() => {
|
||||||
|
const expected_urls = [
|
||||||
|
testing.BASE_URL + 'window/support/frame1.html',
|
||||||
|
ALT_BASE + 'window/support/frame1.html',
|
||||||
|
ALT_BASE + 'window/support/frame2.html',
|
||||||
|
];
|
||||||
|
|
||||||
|
// No strong order guarantee for messaages, and we don't care about the order
|
||||||
|
// so long as it's the correct data.
|
||||||
|
testing.expectEqual(expected_urls.sort(), captures.map((c) => {return c.url}).sort());
|
||||||
|
captures.forEach((c) => {
|
||||||
|
if (c.url.includes(testing.BASE_URL)) {
|
||||||
|
testing.expectEqual(false, c.document_is_undefined);
|
||||||
|
} else {
|
||||||
|
testing.expectEqual(true, c.document_is_undefined);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
7
src/browser/tests/window/support/frame1.html
Normal file
7
src/browser/tests/window/support/frame1.html
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<script>
|
||||||
|
window.parent.postMessage({
|
||||||
|
url: location.toString(),
|
||||||
|
document_is_undefined: window.parent.document === undefined,
|
||||||
|
}, '*')
|
||||||
|
</script>
|
||||||
7
src/browser/tests/window/support/frame2.html
Normal file
7
src/browser/tests/window/support/frame2.html
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<script>
|
||||||
|
window.top.postMessage({
|
||||||
|
url: location.toString(),
|
||||||
|
document_is_undefined: window.top.document === undefined,
|
||||||
|
}, '*')
|
||||||
|
</script>
|
||||||
@@ -262,6 +262,31 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script id=cached_getter_wrong_this>
|
||||||
|
// Test that extracting a cached property getter and calling it with wrong `this`
|
||||||
|
// doesn't crash (V8 internal field out of bounds). V8's Signature validation
|
||||||
|
// should throw "Illegal invocation" for wrong receiver types.
|
||||||
|
const documentGetter = Object.getOwnPropertyDescriptor(Window.prototype, 'document').get;
|
||||||
|
|
||||||
|
// Verify we get an error with wrong this values
|
||||||
|
let errorCount = 0;
|
||||||
|
const testValues = [{}, null, undefined, 42, 'string', [], () => {}];
|
||||||
|
for (const val of testValues) {
|
||||||
|
try {
|
||||||
|
documentGetter.call(val);
|
||||||
|
} catch (e) {
|
||||||
|
if (e.message.includes('Illegal invocation')) {
|
||||||
|
errorCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// At least some should throw (null/undefined/primitives get coerced in sloppy mode)
|
||||||
|
testing.expectEqual(true, errorCount > 0);
|
||||||
|
|
||||||
|
// Calling with correct this should still work
|
||||||
|
testing.expectEqual(document, documentGetter.call(window));
|
||||||
|
</script>
|
||||||
|
|
||||||
<script id=unhandled_rejection>
|
<script id=unhandled_rejection>
|
||||||
{
|
{
|
||||||
let unhandledCalled = 0;
|
let unhandledCalled = 0;
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ const Allocator = std.mem.Allocator;
|
|||||||
|
|
||||||
const Page = @import("../Page.zig");
|
const Page = @import("../Page.zig");
|
||||||
const Session = @import("../Session.zig");
|
const Session = @import("../Session.zig");
|
||||||
|
|
||||||
|
const Node = @import("Node.zig");
|
||||||
const Element = @import("Element.zig");
|
const Element = @import("Element.zig");
|
||||||
const DOMRect = @import("DOMRect.zig");
|
const DOMRect = @import("DOMRect.zig");
|
||||||
|
|
||||||
@@ -55,7 +57,7 @@ var zero_rect: DOMRect = .{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const ObserverInit = struct {
|
pub const ObserverInit = struct {
|
||||||
root: ?*Element = null,
|
root: ?*Node = null,
|
||||||
rootMargin: ?[]const u8 = null,
|
rootMargin: ?[]const u8 = null,
|
||||||
threshold: Threshold = .{ .scalar = 0.0 },
|
threshold: Threshold = .{ .scalar = 0.0 },
|
||||||
|
|
||||||
@@ -81,11 +83,25 @@ pub fn init(callback: js.Function.Temp, options: ?ObserverInit, page: *Page) !*I
|
|||||||
.array => |arr| try arena.dupe(f64, arr),
|
.array => |arr| try arena.dupe(f64, arr),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const root: ?*Element = blk: {
|
||||||
|
const root_opt = opts.root orelse break :blk null;
|
||||||
|
switch (root_opt._type) {
|
||||||
|
.element => |el| break :blk el,
|
||||||
|
.document => {
|
||||||
|
// not strictly correct, `null` means the viewport, not the
|
||||||
|
// entire document, but since we don't render anything, this
|
||||||
|
// should be fine.
|
||||||
|
break :blk null;
|
||||||
|
},
|
||||||
|
else => return error.TypeError,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const self = try arena.create(IntersectionObserver);
|
const self = try arena.create(IntersectionObserver);
|
||||||
self.* = .{
|
self.* = .{
|
||||||
._arena = arena,
|
._arena = arena,
|
||||||
._callback = callback,
|
._callback = callback,
|
||||||
._root = opts.root,
|
._root = root,
|
||||||
._root_margin = root_margin,
|
._root_margin = root_margin,
|
||||||
._threshold = threshold,
|
._threshold = threshold,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ const IS_DEBUG = builtin.mode == .Debug;
|
|||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
pub fn registerTypes() []const type {
|
||||||
|
return &.{ Window, CrossOriginWindow };
|
||||||
|
}
|
||||||
|
|
||||||
const Window = @This();
|
const Window = @This();
|
||||||
|
|
||||||
_proto: *EventTarget,
|
_proto: *EventTarget,
|
||||||
@@ -87,6 +91,8 @@ _scroll_pos: struct {
|
|||||||
.y = 0,
|
.y = 0,
|
||||||
.state = .done,
|
.state = .done,
|
||||||
},
|
},
|
||||||
|
// A cross origin wrapper for this window
|
||||||
|
_cross_origin_wrapper: CrossOriginWindow,
|
||||||
|
|
||||||
pub fn asEventTarget(self: *Window) *EventTarget {
|
pub fn asEventTarget(self: *Window) *EventTarget {
|
||||||
return self._proto;
|
return self._proto;
|
||||||
@@ -104,19 +110,19 @@ pub fn getWindow(self: *Window) *Window {
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getTop(self: *Window) *Window {
|
pub fn getTop(self: *Window, page: *Page) Access {
|
||||||
var p = self._page;
|
var p = self._page;
|
||||||
while (p.parent) |parent| {
|
while (p.parent) |parent| {
|
||||||
p = parent;
|
p = parent;
|
||||||
}
|
}
|
||||||
return p.window;
|
return Access.init(page.window, p.window);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getParent(self: *Window) *Window {
|
pub fn getParent(self: *Window, page: *Page) Access {
|
||||||
if (self._page.parent) |p| {
|
if (self._page.parent) |p| {
|
||||||
return p.window;
|
return Access.init(page.window, p.window);
|
||||||
}
|
}
|
||||||
return self;
|
return .{ .window = self };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getDocument(self: *Window) *Document {
|
pub fn getDocument(self: *Window) *Document {
|
||||||
@@ -606,6 +612,25 @@ pub fn unhandledPromiseRejection(self: *Window, no_handler: bool, rejection: js.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const Access = union(enum) {
|
||||||
|
window: *Window,
|
||||||
|
cross_origin: *CrossOriginWindow,
|
||||||
|
|
||||||
|
pub fn init(callee: *Window, accessing: *Window) Access {
|
||||||
|
if (callee == accessing) {
|
||||||
|
// common enough that it's worth the check
|
||||||
|
return .{ .window = accessing };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callee._page.js.origin == accessing._page.js.origin) {
|
||||||
|
// two different windows, but same origin, return the full window
|
||||||
|
return .{ .window = accessing };
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{ .cross_origin = &accessing._cross_origin_wrapper };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const ScheduleOpts = struct {
|
const ScheduleOpts = struct {
|
||||||
repeat: bool,
|
repeat: bool,
|
||||||
params: []js.Value.Temp,
|
params: []js.Value.Temp,
|
||||||
@@ -804,7 +829,7 @@ pub const JsApi = struct {
|
|||||||
pub var class_id: bridge.ClassId = undefined;
|
pub var class_id: bridge.ClassId = undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const document = bridge.accessor(Window.getDocument, null, .{ .cache = .{ .internal = 1 } });
|
pub const document = bridge.accessor(Window.getDocument, null, .{ .cache = .{ .internal = 1 }, .deletable = false });
|
||||||
pub const console = bridge.accessor(Window.getConsole, null, .{ .cache = .{ .internal = 2 } });
|
pub const console = bridge.accessor(Window.getConsole, null, .{ .cache = .{ .internal = 2 } });
|
||||||
|
|
||||||
pub const top = bridge.accessor(Window.getTop, null, .{});
|
pub const top = bridge.accessor(Window.getTop, null, .{});
|
||||||
@@ -817,7 +842,7 @@ pub const JsApi = struct {
|
|||||||
pub const performance = bridge.accessor(Window.getPerformance, null, .{});
|
pub const performance = bridge.accessor(Window.getPerformance, null, .{});
|
||||||
pub const localStorage = bridge.accessor(Window.getLocalStorage, null, .{});
|
pub const localStorage = bridge.accessor(Window.getLocalStorage, null, .{});
|
||||||
pub const sessionStorage = bridge.accessor(Window.getSessionStorage, null, .{});
|
pub const sessionStorage = bridge.accessor(Window.getSessionStorage, null, .{});
|
||||||
pub const location = bridge.accessor(Window.getLocation, Window.setLocation, .{});
|
pub const location = bridge.accessor(Window.getLocation, Window.setLocation, .{ .deletable = false });
|
||||||
pub const history = bridge.accessor(Window.getHistory, null, .{});
|
pub const history = bridge.accessor(Window.getHistory, null, .{});
|
||||||
pub const navigation = bridge.accessor(Window.getNavigation, null, .{});
|
pub const navigation = bridge.accessor(Window.getNavigation, null, .{});
|
||||||
pub const crypto = bridge.accessor(Window.getCrypto, null, .{});
|
pub const crypto = bridge.accessor(Window.getCrypto, null, .{});
|
||||||
@@ -892,6 +917,41 @@ pub const JsApi = struct {
|
|||||||
}.prompt, .{});
|
}.prompt, .{});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const CrossOriginWindow = struct {
|
||||||
|
window: *Window,
|
||||||
|
|
||||||
|
pub fn postMessage(self: *CrossOriginWindow, message: js.Value.Temp, target_origin: ?[]const u8, page: *Page) !void {
|
||||||
|
return self.window.postMessage(message, target_origin, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getTop(self: *CrossOriginWindow, page: *Page) Access {
|
||||||
|
return self.window.getParent(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getParent(self: *CrossOriginWindow, page: *Page) Access {
|
||||||
|
return self.window.getParent(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getFramesLength(self: *const CrossOriginWindow) u32 {
|
||||||
|
return self.window.getFramesLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const JsApi = struct {
|
||||||
|
pub const bridge = js.Bridge(CrossOriginWindow);
|
||||||
|
|
||||||
|
pub const Meta = struct {
|
||||||
|
pub const name = "CrossOriginWindow";
|
||||||
|
pub const prototype_chain = bridge.prototypeChain();
|
||||||
|
pub var class_id: bridge.ClassId = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const postMessage = bridge.function(CrossOriginWindow.postMessage, .{});
|
||||||
|
pub const top = bridge.accessor(CrossOriginWindow.getTop, null, .{});
|
||||||
|
pub const parent = bridge.accessor(CrossOriginWindow.getParent, null, .{});
|
||||||
|
pub const length = bridge.accessor(CrossOriginWindow.getFramesLength, null, .{});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const testing = @import("../../testing.zig");
|
const testing = @import("../../testing.zig");
|
||||||
test "WebApi: Window" {
|
test "WebApi: Window" {
|
||||||
try testing.htmlRunner("window", .{});
|
try testing.htmlRunner("window", .{});
|
||||||
|
|||||||
@@ -39,8 +39,9 @@ pub fn asNode(self: *IFrame) *Node {
|
|||||||
return self.asElement().asNode();
|
return self.asElement().asNode();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getContentWindow(self: *const IFrame) ?*Window {
|
pub fn getContentWindow(self: *const IFrame, page: *Page) ?Window.Access {
|
||||||
return self._window;
|
const frame_window = self._window orelse return null;
|
||||||
|
return Window.Access.init(page.window, frame_window);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getContentDocument(self: *const IFrame) ?*Document {
|
pub fn getContentDocument(self: *const IFrame) ?*Document {
|
||||||
|
|||||||
@@ -22,36 +22,36 @@ const lp = @import("lightpanda");
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const json = std.json;
|
const json = std.json;
|
||||||
|
|
||||||
const log = @import("../log.zig");
|
const Incrementing = @import("id.zig").Incrementing;
|
||||||
const js = @import("../browser/js/js.zig");
|
|
||||||
|
|
||||||
|
const log = @import("../log.zig");
|
||||||
const App = @import("../App.zig");
|
const App = @import("../App.zig");
|
||||||
|
const Notification = @import("../Notification.zig");
|
||||||
|
|
||||||
|
const Client = @import("../Server.zig").Client;
|
||||||
|
|
||||||
|
const js = @import("../browser/js/js.zig");
|
||||||
const Browser = @import("../browser/Browser.zig");
|
const Browser = @import("../browser/Browser.zig");
|
||||||
const Session = @import("../browser/Session.zig");
|
const Session = @import("../browser/Session.zig");
|
||||||
const HttpClient = @import("../browser/HttpClient.zig");
|
|
||||||
const Page = @import("../browser/Page.zig");
|
const Page = @import("../browser/Page.zig");
|
||||||
const Incrementing = @import("id.zig").Incrementing;
|
|
||||||
const Notification = @import("../Notification.zig");
|
|
||||||
const InterceptState = @import("domains/fetch.zig").InterceptState;
|
|
||||||
const Mime = @import("../browser/Mime.zig");
|
const Mime = @import("../browser/Mime.zig");
|
||||||
|
const HttpClient = @import("../browser/HttpClient.zig");
|
||||||
|
|
||||||
|
const InterceptState = @import("domains/fetch.zig").InterceptState;
|
||||||
|
|
||||||
pub const URL_BASE = "chrome://newtab/";
|
pub const URL_BASE = "chrome://newtab/";
|
||||||
|
|
||||||
const IS_DEBUG = @import("builtin").mode == .Debug;
|
const IS_DEBUG = @import("builtin").mode == .Debug;
|
||||||
|
|
||||||
pub const CDP = CDPT(struct {
|
|
||||||
const Client = *@import("../Server.zig").Client;
|
|
||||||
});
|
|
||||||
|
|
||||||
const SessionIdGen = Incrementing(u32, "SID");
|
|
||||||
const TargetIdGen = Incrementing(u32, "TID");
|
const TargetIdGen = Incrementing(u32, "TID");
|
||||||
|
const SessionIdGen = Incrementing(u32, "SID");
|
||||||
const BrowserContextIdGen = Incrementing(u32, "BID");
|
const BrowserContextIdGen = Incrementing(u32, "BID");
|
||||||
|
|
||||||
// Generic so that we can inject mocks into it.
|
// Generic so that we can inject mocks into it.
|
||||||
pub fn CDPT(comptime TypeProvider: type) type {
|
const CDP = @This();
|
||||||
return struct {
|
|
||||||
// Used for sending message to the client and closing on error
|
// Used for sending message to the client and closing on error
|
||||||
client: TypeProvider.Client,
|
client: *Client,
|
||||||
|
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ pub fn CDPT(comptime TypeProvider: type) type {
|
|||||||
session_id_gen: SessionIdGen = .{},
|
session_id_gen: SessionIdGen = .{},
|
||||||
browser_context_id_gen: BrowserContextIdGen = .{},
|
browser_context_id_gen: BrowserContextIdGen = .{},
|
||||||
|
|
||||||
browser_context: ?BrowserContext(Self),
|
browser_context: ?BrowserContext(CDP),
|
||||||
|
|
||||||
// Re-used arena for processing a message. We're assuming that we're getting
|
// Re-used arena for processing a message. We're assuming that we're getting
|
||||||
// 1 message at a time.
|
// 1 message at a time.
|
||||||
@@ -81,13 +81,12 @@ pub fn CDPT(comptime TypeProvider: type) type {
|
|||||||
// (or altogether elimiate) our use of this.
|
// (or altogether elimiate) our use of this.
|
||||||
browser_context_arena: std.heap.ArenaAllocator,
|
browser_context_arena: std.heap.ArenaAllocator,
|
||||||
|
|
||||||
const Self = @This();
|
pub fn init(client: *Client) !CDP {
|
||||||
|
const app = client.app;
|
||||||
pub fn init(app: *App, http_client: *HttpClient, client: TypeProvider.Client) !Self {
|
|
||||||
const allocator = app.allocator;
|
const allocator = app.allocator;
|
||||||
const browser = try Browser.init(app, .{
|
const browser = try Browser.init(app, .{
|
||||||
.env = .{ .with_inspector = true },
|
.env = .{ .with_inspector = true },
|
||||||
.http_client = http_client,
|
.http_client = client.http,
|
||||||
});
|
});
|
||||||
errdefer browser.deinit();
|
errdefer browser.deinit();
|
||||||
|
|
||||||
@@ -103,7 +102,7 @@ pub fn CDPT(comptime TypeProvider: type) type {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit(self: *CDP) void {
|
||||||
if (self.browser_context) |*bc| {
|
if (self.browser_context) |*bc| {
|
||||||
bc.deinit();
|
bc.deinit();
|
||||||
}
|
}
|
||||||
@@ -114,13 +113,13 @@ pub fn CDPT(comptime TypeProvider: type) type {
|
|||||||
self.browser_context_arena.deinit();
|
self.browser_context_arena.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handleMessage(self: *Self, msg: []const u8) bool {
|
pub fn handleMessage(self: *CDP, msg: []const u8) bool {
|
||||||
// if there's an error, it's already been logged
|
// if there's an error, it's already been logged
|
||||||
self.processMessage(msg) catch return false;
|
self.processMessage(msg) catch return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn processMessage(self: *Self, msg: []const u8) !void {
|
pub fn processMessage(self: *CDP, msg: []const u8) !void {
|
||||||
const arena = &self.message_arena;
|
const arena = &self.message_arena;
|
||||||
defer _ = arena.reset(.{ .retain_with_limit = 1024 * 16 });
|
defer _ = arena.reset(.{ .retain_with_limit = 1024 * 16 });
|
||||||
return self.dispatch(arena.allocator(), self, msg);
|
return self.dispatch(arena.allocator(), self, msg);
|
||||||
@@ -130,7 +129,7 @@ pub fn CDPT(comptime TypeProvider: type) type {
|
|||||||
// A bit hacky right now. The main server loop doesn't unblock for
|
// A bit hacky right now. The main server loop doesn't unblock for
|
||||||
// scheduled task. So we run this directly in order to process any
|
// scheduled task. So we run this directly in order to process any
|
||||||
// timeouts (or http events) which are ready to be processed.
|
// timeouts (or http events) which are ready to be processed.
|
||||||
pub fn pageWait(self: *Self, ms: u32) !Session.Runner.CDPWaitResult {
|
pub fn pageWait(self: *CDP, ms: u32) !Session.Runner.CDPWaitResult {
|
||||||
const session = &(self.browser.session orelse return error.NoPage);
|
const session = &(self.browser.session orelse return error.NoPage);
|
||||||
var runner = try session.runner(.{});
|
var runner = try session.runner(.{});
|
||||||
return runner.waitCDP(.{ .ms = ms });
|
return runner.waitCDP(.{ .ms = ms });
|
||||||
@@ -139,12 +138,12 @@ pub fn CDPT(comptime TypeProvider: type) type {
|
|||||||
// Called from above, in processMessage which handles client messages
|
// Called from above, in processMessage which handles client messages
|
||||||
// but can also be called internally. For example, Target.sendMessageToTarget
|
// but can also be called internally. For example, Target.sendMessageToTarget
|
||||||
// calls back into dispatch to capture the response.
|
// calls back into dispatch to capture the response.
|
||||||
pub fn dispatch(self: *Self, arena: Allocator, sender: anytype, str: []const u8) !void {
|
pub fn dispatch(self: *CDP, arena: Allocator, sender: anytype, str: []const u8) !void {
|
||||||
const input = json.parseFromSliceLeaky(InputMessage, arena, str, .{
|
const input = json.parseFromSliceLeaky(InputMessage, arena, str, .{
|
||||||
.ignore_unknown_fields = true,
|
.ignore_unknown_fields = true,
|
||||||
}) catch return error.InvalidJSON;
|
}) catch return error.InvalidJSON;
|
||||||
|
|
||||||
var command = Command(Self, @TypeOf(sender)){
|
var command = Command(CDP, @TypeOf(sender)){
|
||||||
.input = .{
|
.input = .{
|
||||||
.json = str,
|
.json = str,
|
||||||
.id = input.id,
|
.id = input.id,
|
||||||
@@ -264,26 +263,26 @@ pub fn CDPT(comptime TypeProvider: type) type {
|
|||||||
return error.UnknownDomain;
|
return error.UnknownDomain;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn isValidSessionId(self: *const Self, input_session_id: []const u8) bool {
|
fn isValidSessionId(self: *const CDP, input_session_id: []const u8) bool {
|
||||||
const browser_context = &(self.browser_context orelse return false);
|
const browser_context = &(self.browser_context orelse return false);
|
||||||
const session_id = browser_context.session_id orelse return false;
|
const session_id = browser_context.session_id orelse return false;
|
||||||
return std.mem.eql(u8, session_id, input_session_id);
|
return std.mem.eql(u8, session_id, input_session_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn createBrowserContext(self: *Self) ![]const u8 {
|
pub fn createBrowserContext(self: *CDP) ![]const u8 {
|
||||||
if (self.browser_context != null) {
|
if (self.browser_context != null) {
|
||||||
return error.AlreadyExists;
|
return error.AlreadyExists;
|
||||||
}
|
}
|
||||||
const id = self.browser_context_id_gen.next();
|
const id = self.browser_context_id_gen.next();
|
||||||
|
|
||||||
self.browser_context = @as(BrowserContext(Self), undefined);
|
self.browser_context = @as(BrowserContext(CDP), undefined);
|
||||||
const browser_context = &self.browser_context.?;
|
const browser_context = &self.browser_context.?;
|
||||||
|
|
||||||
try BrowserContext(Self).init(browser_context, id, self);
|
try BrowserContext(CDP).init(browser_context, id, self);
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn disposeBrowserContext(self: *Self, browser_context_id: []const u8) bool {
|
pub fn disposeBrowserContext(self: *CDP, browser_context_id: []const u8) bool {
|
||||||
const bc = &(self.browser_context orelse return false);
|
const bc = &(self.browser_context orelse return false);
|
||||||
if (std.mem.eql(u8, bc.id, browser_context_id) == false) {
|
if (std.mem.eql(u8, bc.id, browser_context_id) == false) {
|
||||||
return false;
|
return false;
|
||||||
@@ -297,7 +296,7 @@ pub fn CDPT(comptime TypeProvider: type) type {
|
|||||||
const SendEventOpts = struct {
|
const SendEventOpts = struct {
|
||||||
session_id: ?[]const u8 = null,
|
session_id: ?[]const u8 = null,
|
||||||
};
|
};
|
||||||
pub fn sendEvent(self: *Self, method: []const u8, p: anytype, opts: SendEventOpts) !void {
|
pub fn sendEvent(self: *CDP, method: []const u8, p: anytype, opts: SendEventOpts) !void {
|
||||||
return self.sendJSON(.{
|
return self.sendJSON(.{
|
||||||
.method = method,
|
.method = method,
|
||||||
.params = if (comptime @typeInfo(@TypeOf(p)) == .null) struct {}{} else p,
|
.params = if (comptime @typeInfo(@TypeOf(p)) == .null) struct {}{} else p,
|
||||||
@@ -305,13 +304,11 @@ pub fn CDPT(comptime TypeProvider: type) type {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sendJSON(self: *Self, message: anytype) !void {
|
pub fn sendJSON(self: *CDP, message: anytype) !void {
|
||||||
return self.client.sendJSON(message, .{
|
return self.client.sendJSON(message, .{
|
||||||
.emit_null_optional_fields = false,
|
.emit_null_optional_fields = false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn BrowserContext(comptime CDP_T: type) type {
|
pub fn BrowserContext(comptime CDP_T: type) type {
|
||||||
const Node = @import("Node.zig");
|
const Node = @import("Node.zig");
|
||||||
@@ -958,7 +955,7 @@ fn asUint(comptime T: type, comptime string: []const u8) T {
|
|||||||
|
|
||||||
const testing = @import("testing.zig");
|
const testing = @import("testing.zig");
|
||||||
test "cdp: invalid json" {
|
test "cdp: invalid json" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
try testing.expectError(error.InvalidJSON, ctx.processMessage("invalid"));
|
try testing.expectError(error.InvalidJSON, ctx.processMessage("invalid"));
|
||||||
@@ -983,7 +980,7 @@ test "cdp: invalid json" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp: invalid sessionId" {
|
test "cdp: invalid sessionId" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -1008,7 +1005,7 @@ test "cdp: invalid sessionId" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp: STARTUP sessionId" {
|
test "cdp: STARTUP sessionId" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -1021,13 +1018,13 @@ test "cdp: STARTUP sessionId" {
|
|||||||
// we have a brower context but no session_id
|
// we have a brower context but no session_id
|
||||||
_ = try ctx.loadBrowserContext(.{});
|
_ = try ctx.loadBrowserContext(.{});
|
||||||
try ctx.processMessage(.{ .id = 3, .method = "Hi", .sessionId = "STARTUP" });
|
try ctx.processMessage(.{ .id = 3, .method = "Hi", .sessionId = "STARTUP" });
|
||||||
try ctx.expectSentResult(null, .{ .id = 3, .index = 0, .session_id = "STARTUP" });
|
try ctx.expectSentResult(null, .{ .id = 3, .index = 1, .session_id = "STARTUP" });
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
// we have a brower context with a different session_id
|
// we have a brower context with a different session_id
|
||||||
_ = try ctx.loadBrowserContext(.{ .session_id = "SESS-2" });
|
_ = try ctx.loadBrowserContext(.{ .session_id = "SESS-2" });
|
||||||
try ctx.processMessage(.{ .id = 4, .method = "Hi", .sessionId = "STARTUP" });
|
try ctx.processMessage(.{ .id = 4, .method = "Hi", .sessionId = "STARTUP" });
|
||||||
try ctx.expectSentResult(null, .{ .id = 4, .index = 0, .session_id = "STARTUP" });
|
try ctx.expectSentResult(null, .{ .id = 4, .index = 2, .session_id = "STARTUP" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,7 +112,7 @@ fn resetPermissions(cmd: anytype) !void {
|
|||||||
|
|
||||||
const testing = @import("../testing.zig");
|
const testing = @import("../testing.zig");
|
||||||
test "cdp.browser: getVersion" {
|
test "cdp.browser: getVersion" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
try ctx.processMessage(.{
|
try ctx.processMessage(.{
|
||||||
@@ -131,7 +131,7 @@ test "cdp.browser: getVersion" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.browser: getWindowForTarget" {
|
test "cdp.browser: getWindowForTarget" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
try ctx.processMessage(.{
|
try ctx.processMessage(.{
|
||||||
|
|||||||
@@ -547,7 +547,7 @@ fn requestNode(cmd: anytype) !void {
|
|||||||
|
|
||||||
const testing = @import("../testing.zig");
|
const testing = @import("../testing.zig");
|
||||||
test "cdp.dom: getSearchResults unknown search id" {
|
test "cdp.dom: getSearchResults unknown search id" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
try ctx.processMessage(.{
|
try ctx.processMessage(.{
|
||||||
@@ -559,7 +559,7 @@ test "cdp.dom: getSearchResults unknown search id" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.dom: search flow" {
|
test "cdp.dom: search flow" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
_ = try ctx.loadBrowserContext(.{ .id = "BID-A", .url = "cdp/dom1.html" });
|
_ = try ctx.loadBrowserContext(.{ .id = "BID-A", .url = "cdp/dom1.html" });
|
||||||
@@ -614,7 +614,7 @@ test "cdp.dom: search flow" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.dom: querySelector unknown search id" {
|
test "cdp.dom: querySelector unknown search id" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
_ = try ctx.loadBrowserContext(.{ .id = "BID-A", .url = "cdp/dom1.html" });
|
_ = try ctx.loadBrowserContext(.{ .id = "BID-A", .url = "cdp/dom1.html" });
|
||||||
@@ -635,7 +635,7 @@ test "cdp.dom: querySelector unknown search id" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.dom: querySelector Node not found" {
|
test "cdp.dom: querySelector Node not found" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
_ = try ctx.loadBrowserContext(.{ .id = "BID-A", .url = "cdp/dom1.html" });
|
_ = try ctx.loadBrowserContext(.{ .id = "BID-A", .url = "cdp/dom1.html" });
|
||||||
@@ -663,7 +663,7 @@ test "cdp.dom: querySelector Node not found" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.dom: querySelector Nodes found" {
|
test "cdp.dom: querySelector Nodes found" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
_ = try ctx.loadBrowserContext(.{ .id = "BID-A", .url = "cdp/dom2.html" });
|
_ = try ctx.loadBrowserContext(.{ .id = "BID-A", .url = "cdp/dom2.html" });
|
||||||
@@ -693,7 +693,7 @@ test "cdp.dom: querySelector Nodes found" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.dom: getBoxModel" {
|
test "cdp.dom: getBoxModel" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
_ = try ctx.loadBrowserContext(.{ .id = "BID-A", .url = "cdp/dom2.html" });
|
_ = try ctx.loadBrowserContext(.{ .id = "BID-A", .url = "cdp/dom2.html" });
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ fn waitForSelector(cmd: anytype) !void {
|
|||||||
|
|
||||||
const testing = @import("../testing.zig");
|
const testing = @import("../testing.zig");
|
||||||
test "cdp.lp: getMarkdown" {
|
test "cdp.lp: getMarkdown" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
const bc = try ctx.loadBrowserContext(.{});
|
const bc = try ctx.loadBrowserContext(.{});
|
||||||
@@ -290,12 +290,12 @@ test "cdp.lp: getMarkdown" {
|
|||||||
.method = "LP.getMarkdown",
|
.method = "LP.getMarkdown",
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = ctx.client.?.sent.items[0].object.get("result").?.object;
|
const result = (try ctx.getSentMessage(0)).?.object.get("result").?.object;
|
||||||
try testing.expect(result.get("markdown") != null);
|
try testing.expect(result.get("markdown") != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.lp: getInteractiveElements" {
|
test "cdp.lp: getInteractiveElements" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
const bc = try ctx.loadBrowserContext(.{});
|
const bc = try ctx.loadBrowserContext(.{});
|
||||||
@@ -306,13 +306,13 @@ test "cdp.lp: getInteractiveElements" {
|
|||||||
.method = "LP.getInteractiveElements",
|
.method = "LP.getInteractiveElements",
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = ctx.client.?.sent.items[0].object.get("result").?.object;
|
const result = (try ctx.getSentMessage(0)).?.object.get("result").?.object;
|
||||||
try testing.expect(result.get("elements") != null);
|
try testing.expect(result.get("elements") != null);
|
||||||
try testing.expect(result.get("nodeIds") != null);
|
try testing.expect(result.get("nodeIds") != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.lp: getStructuredData" {
|
test "cdp.lp: getStructuredData" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
const bc = try ctx.loadBrowserContext(.{});
|
const bc = try ctx.loadBrowserContext(.{});
|
||||||
@@ -323,12 +323,12 @@ test "cdp.lp: getStructuredData" {
|
|||||||
.method = "LP.getStructuredData",
|
.method = "LP.getStructuredData",
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = ctx.client.?.sent.items[0].object.get("result").?.object;
|
const result = (try ctx.getSentMessage(0)).?.object.get("result").?.object;
|
||||||
try testing.expect(result.get("structuredData") != null);
|
try testing.expect(result.get("structuredData") != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.lp: action tools" {
|
test "cdp.lp: action tools" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
const bc = try ctx.loadBrowserContext(.{});
|
const bc = try ctx.loadBrowserContext(.{});
|
||||||
@@ -389,7 +389,7 @@ test "cdp.lp: action tools" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.lp: waitForSelector" {
|
test "cdp.lp: waitForSelector" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
const bc = try ctx.loadBrowserContext(.{});
|
const bc = try ctx.loadBrowserContext(.{});
|
||||||
@@ -405,9 +405,8 @@ test "cdp.lp: waitForSelector" {
|
|||||||
.method = "LP.waitForSelector",
|
.method = "LP.waitForSelector",
|
||||||
.params = .{ .selector = "#existing", .timeout = 2000 },
|
.params = .{ .selector = "#existing", .timeout = 2000 },
|
||||||
});
|
});
|
||||||
var result = ctx.client.?.sent.items[0].object.get("result").?.object;
|
var result = (try ctx.getSentMessage(0)).?.object.get("result").?.object;
|
||||||
try testing.expect(result.get("backendNodeId") != null);
|
try testing.expect(result.get("backendNodeId") != null);
|
||||||
ctx.client.?.sent.clearRetainingCapacity();
|
|
||||||
|
|
||||||
// 2. Delayed element
|
// 2. Delayed element
|
||||||
try ctx.processMessage(.{
|
try ctx.processMessage(.{
|
||||||
@@ -415,9 +414,8 @@ test "cdp.lp: waitForSelector" {
|
|||||||
.method = "LP.waitForSelector",
|
.method = "LP.waitForSelector",
|
||||||
.params = .{ .selector = "#delayed", .timeout = 5000 },
|
.params = .{ .selector = "#delayed", .timeout = 5000 },
|
||||||
});
|
});
|
||||||
result = ctx.client.?.sent.items[0].object.get("result").?.object;
|
result = (try ctx.getSentMessage(1)).?.object.get("result").?.object;
|
||||||
try testing.expect(result.get("backendNodeId") != null);
|
try testing.expect(result.get("backendNodeId") != null);
|
||||||
ctx.client.?.sent.clearRetainingCapacity();
|
|
||||||
|
|
||||||
// 3. Timeout error
|
// 3. Timeout error
|
||||||
try ctx.processMessage(.{
|
try ctx.processMessage(.{
|
||||||
@@ -425,6 +423,6 @@ test "cdp.lp: waitForSelector" {
|
|||||||
.method = "LP.waitForSelector",
|
.method = "LP.waitForSelector",
|
||||||
.params = .{ .selector = "#nonexistent", .timeout = 100 },
|
.params = .{ .selector = "#nonexistent", .timeout = 100 },
|
||||||
});
|
});
|
||||||
const err_obj = ctx.client.?.sent.items[0].object.get("error").?.object;
|
const err_obj = (try ctx.getSentMessage(2)).?.object.get("error").?.object;
|
||||||
try testing.expect(err_obj.get("code") != null);
|
try testing.expect(err_obj.get("code") != null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -439,7 +439,7 @@ fn idFromRequestId(request_id: []const u8) !u64 {
|
|||||||
|
|
||||||
const testing = @import("../testing.zig");
|
const testing = @import("../testing.zig");
|
||||||
test "cdp.network setExtraHTTPHeaders" {
|
test "cdp.network setExtraHTTPHeaders" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
_ = try ctx.loadBrowserContext(.{ .id = "NID-A", .session_id = "NESI-A" });
|
_ = try ctx.loadBrowserContext(.{ .id = "NID-A", .session_id = "NESI-A" });
|
||||||
@@ -465,7 +465,7 @@ test "cdp.Network: cookies" {
|
|||||||
const ResCookie = CdpStorage.ResCookie;
|
const ResCookie = CdpStorage.ResCookie;
|
||||||
const CdpCookie = CdpStorage.CdpCookie;
|
const CdpCookie = CdpStorage.CdpCookie;
|
||||||
|
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
_ = try ctx.loadBrowserContext(.{ .id = "BID-S" });
|
_ = try ctx.loadBrowserContext(.{ .id = "BID-S" });
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ pub fn processMessage(cmd: anytype) !void {
|
|||||||
addScriptToEvaluateOnNewDocument,
|
addScriptToEvaluateOnNewDocument,
|
||||||
createIsolatedWorld,
|
createIsolatedWorld,
|
||||||
navigate,
|
navigate,
|
||||||
|
reload,
|
||||||
stopLoading,
|
stopLoading,
|
||||||
close,
|
close,
|
||||||
captureScreenshot,
|
captureScreenshot,
|
||||||
@@ -52,6 +53,7 @@ pub fn processMessage(cmd: anytype) !void {
|
|||||||
.addScriptToEvaluateOnNewDocument => return addScriptToEvaluateOnNewDocument(cmd),
|
.addScriptToEvaluateOnNewDocument => return addScriptToEvaluateOnNewDocument(cmd),
|
||||||
.createIsolatedWorld => return createIsolatedWorld(cmd),
|
.createIsolatedWorld => return createIsolatedWorld(cmd),
|
||||||
.navigate => return navigate(cmd),
|
.navigate => return navigate(cmd),
|
||||||
|
.reload => return doReload(cmd),
|
||||||
.stopLoading => return cmd.sendResult(null, .{}),
|
.stopLoading => return cmd.sendResult(null, .{}),
|
||||||
.close => return close(cmd),
|
.close => return close(cmd),
|
||||||
.captureScreenshot => return captureScreenshot(cmd),
|
.captureScreenshot => return captureScreenshot(cmd),
|
||||||
@@ -82,7 +84,7 @@ fn getFrameTree(cmd: anytype) !void {
|
|||||||
.frame = .{
|
.frame = .{
|
||||||
.id = "TID-STARTUP",
|
.id = "TID-STARTUP",
|
||||||
.loaderId = "LID-STARTUP",
|
.loaderId = "LID-STARTUP",
|
||||||
.securityOrigin = @import("../cdp.zig").URL_BASE,
|
.securityOrigin = @import("../CDP.zig").URL_BASE,
|
||||||
.url = "about:blank",
|
.url = "about:blank",
|
||||||
.secureContextType = "Secure",
|
.secureContextType = "Secure",
|
||||||
},
|
},
|
||||||
@@ -252,6 +254,36 @@ fn navigate(cmd: anytype) !void {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn doReload(cmd: anytype) !void {
|
||||||
|
const params = try cmd.params(struct {
|
||||||
|
ignoreCache: ?bool = null,
|
||||||
|
scriptToEvaluateOnLoad: ?[]const u8 = null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
||||||
|
|
||||||
|
if (bc.session_id == null) {
|
||||||
|
return error.SessionIdNotLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
const session = bc.session;
|
||||||
|
var page = session.currentPage() orelse return error.PageNotLoaded;
|
||||||
|
|
||||||
|
// Dupe URL before replacePage() frees the old page's arena.
|
||||||
|
const reload_url = try cmd.arena.dupeZ(u8, page.url);
|
||||||
|
|
||||||
|
if (page._load_state != .waiting) {
|
||||||
|
page = try session.replacePage();
|
||||||
|
}
|
||||||
|
|
||||||
|
try page.navigate(reload_url, .{
|
||||||
|
.reason = .address_bar,
|
||||||
|
.cdp_id = cmd.input.id,
|
||||||
|
.kind = .reload,
|
||||||
|
.force = if (params) |p| p.ignoreCache orelse false else false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub fn pageNavigate(bc: anytype, event: *const Notification.PageNavigate) !void {
|
pub fn pageNavigate(bc: anytype, event: *const Notification.PageNavigate) !void {
|
||||||
// detachTarget could be called, in which case, we still have a page doing
|
// detachTarget could be called, in which case, we still have a page doing
|
||||||
// things, but no session.
|
// things, but no session.
|
||||||
@@ -642,7 +674,7 @@ fn getLayoutMetrics(cmd: anytype) !void {
|
|||||||
|
|
||||||
const testing = @import("../testing.zig");
|
const testing = @import("../testing.zig");
|
||||||
test "cdp.page: getFrameTree" {
|
test "cdp.page: getFrameTree" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -712,7 +744,7 @@ test "cdp.page: captureScreenshot" {
|
|||||||
const filter: LogFilter = .init(&.{.not_implemented});
|
const filter: LogFilter = .init(&.{.not_implemented});
|
||||||
defer filter.deinit();
|
defer filter.deinit();
|
||||||
|
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
{
|
{
|
||||||
try ctx.processMessage(.{ .id = 10, .method = "Page.captureScreenshot", .params = .{ .format = "jpg" } });
|
try ctx.processMessage(.{ .id = 10, .method = "Page.captureScreenshot", .params = .{ .format = "jpg" } });
|
||||||
@@ -728,7 +760,7 @@ test "cdp.page: captureScreenshot" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.page: getLayoutMetrics" {
|
test "cdp.page: getLayoutMetrics" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
_ = try ctx.loadBrowserContext(.{ .id = "BID-9", .url = "hi.html", .target_id = "FID-000000000X".* });
|
_ = try ctx.loadBrowserContext(.{ .id = "BID-9", .url = "hi.html", .target_id = "FID-000000000X".* });
|
||||||
@@ -784,3 +816,27 @@ test "cdp.page: getLayoutMetrics" {
|
|||||||
},
|
},
|
||||||
}, .{ .id = 12 });
|
}, .{ .id = 12 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "cdp.page: reload" {
|
||||||
|
var ctx = try testing.context();
|
||||||
|
defer ctx.deinit();
|
||||||
|
|
||||||
|
{
|
||||||
|
// reload without browser context — should error
|
||||||
|
try ctx.processMessage(.{ .id = 30, .method = "Page.reload" });
|
||||||
|
try ctx.expectSentError(-31998, "BrowserContextNotLoaded", .{ .id = 30 });
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = try ctx.loadBrowserContext(.{ .id = "BID-9", .url = "hi.html", .target_id = "FID-000000000X".* });
|
||||||
|
|
||||||
|
{
|
||||||
|
// reload with no params — should not error (navigation is async,
|
||||||
|
// so no result is sent synchronously; we just verify no error)
|
||||||
|
try ctx.processMessage(.{ .id = 31, .method = "Page.reload" });
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// reload with ignoreCache param
|
||||||
|
try ctx.processMessage(.{ .id = 32, .method = "Page.reload", .params = .{ .ignoreCache = true } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ fn setIgnoreCertificateErrors(cmd: anytype) !void {
|
|||||||
const testing = @import("../testing.zig");
|
const testing = @import("../testing.zig");
|
||||||
|
|
||||||
test "cdp.Security: setIgnoreCertificateErrors" {
|
test "cdp.Security: setIgnoreCertificateErrors" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
_ = try ctx.loadBrowserContext(.{ .id = "BID-9" });
|
_ = try ctx.loadBrowserContext(.{ .id = "BID-9" });
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ pub fn writeCookie(cookie: *const Cookie, w: anytype) !void {
|
|||||||
const testing = @import("../testing.zig");
|
const testing = @import("../testing.zig");
|
||||||
|
|
||||||
test "cdp.Storage: cookies" {
|
test "cdp.Storage: cookies" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
_ = try ctx.loadBrowserContext(.{ .id = "BID-S" });
|
_ = try ctx.loadBrowserContext(.{ .id = "BID-S" });
|
||||||
|
|
||||||
|
|||||||
@@ -512,7 +512,7 @@ const TargetInfo = struct {
|
|||||||
|
|
||||||
const testing = @import("../testing.zig");
|
const testing = @import("../testing.zig");
|
||||||
test "cdp.target: getBrowserContexts" {
|
test "cdp.target: getBrowserContexts" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
// {
|
// {
|
||||||
@@ -536,7 +536,7 @@ test "cdp.target: getBrowserContexts" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.target: createBrowserContext" {
|
test "cdp.target: createBrowserContext" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -554,7 +554,7 @@ test "cdp.target: createBrowserContext" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.target: disposeBrowserContext" {
|
test "cdp.target: disposeBrowserContext" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -585,7 +585,7 @@ test "cdp.target: disposeBrowserContext" {
|
|||||||
|
|
||||||
test "cdp.target: createTarget" {
|
test "cdp.target: createTarget" {
|
||||||
{
|
{
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
try ctx.processMessage(.{ .id = 10, .method = "Target.createTarget", .params = .{ .url = "about:blank" } });
|
try ctx.processMessage(.{ .id = 10, .method = "Target.createTarget", .params = .{ .url = "about:blank" } });
|
||||||
|
|
||||||
@@ -595,7 +595,7 @@ test "cdp.target: createTarget" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
// active auto attach to get the Target.attachedToTarget event.
|
// active auto attach to get the Target.attachedToTarget event.
|
||||||
try ctx.processMessage(.{ .id = 9, .method = "Target.setAutoAttach", .params = .{ .autoAttach = true, .waitForDebuggerOnStart = false } });
|
try ctx.processMessage(.{ .id = 9, .method = "Target.setAutoAttach", .params = .{ .autoAttach = true, .waitForDebuggerOnStart = false } });
|
||||||
@@ -607,7 +607,7 @@ test "cdp.target: createTarget" {
|
|||||||
try ctx.expectSentEvent("Target.attachedToTarget", .{ .sessionId = bc.session_id.?, .targetInfo = .{ .url = "about:blank", .title = "", .attached = true, .type = "page", .canAccessOpener = false, .browserContextId = bc.id, .targetId = bc.target_id.? } }, .{});
|
try ctx.expectSentEvent("Target.attachedToTarget", .{ .sessionId = bc.session_id.?, .targetInfo = .{ .url = "about:blank", .title = "", .attached = true, .type = "page", .canAccessOpener = false, .browserContextId = bc.id, .targetId = bc.target_id.? } }, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
const bc = try ctx.loadBrowserContext(.{ .id = "BID-9" });
|
const bc = try ctx.loadBrowserContext(.{ .id = "BID-9" });
|
||||||
{
|
{
|
||||||
@@ -624,7 +624,7 @@ test "cdp.target: createTarget" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.target: closeTarget" {
|
test "cdp.target: closeTarget" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -655,7 +655,7 @@ test "cdp.target: closeTarget" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.target: attachToTarget" {
|
test "cdp.target: attachToTarget" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -686,7 +686,7 @@ test "cdp.target: attachToTarget" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.target: getTargetInfo" {
|
test "cdp.target: getTargetInfo" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -737,7 +737,7 @@ test "cdp.target: getTargetInfo" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.target: issue#474: attach to just created target" {
|
test "cdp.target: issue#474: attach to just created target" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
const bc = try ctx.loadBrowserContext(.{ .id = "BID-9" });
|
const bc = try ctx.loadBrowserContext(.{ .id = "BID-9" });
|
||||||
{
|
{
|
||||||
@@ -752,7 +752,7 @@ test "cdp.target: issue#474: attach to just created target" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.target: detachFromTarget" {
|
test "cdp.target: detachFromTarget" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
const bc = try ctx.loadBrowserContext(.{ .id = "BID-9" });
|
const bc = try ctx.loadBrowserContext(.{ .id = "BID-9" });
|
||||||
{
|
{
|
||||||
@@ -775,19 +775,19 @@ test "cdp.target: detachFromTarget" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.target: detachFromTarget without session" {
|
test "cdp.target: detachFromTarget without session" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
_ = try ctx.loadBrowserContext(.{ .id = "BID-9" });
|
_ = try ctx.loadBrowserContext(.{ .id = "BID-9" });
|
||||||
{
|
{
|
||||||
// detach when no session is attached should not send event
|
// detach when no session is attached should not send event
|
||||||
try ctx.processMessage(.{ .id = 10, .method = "Target.detachFromTarget" });
|
try ctx.processMessage(.{ .id = 10, .method = "Target.detachFromTarget" });
|
||||||
try ctx.expectSentResult(null, .{ .id = 10 });
|
try ctx.expectSentResult(null, .{ .id = 10 });
|
||||||
try ctx.expectSentCount(0);
|
try ctx.expectSentCount(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test "cdp.target: setAutoAttach false sends detachedFromTarget" {
|
test "cdp.target: setAutoAttach false sends detachedFromTarget" {
|
||||||
var ctx = testing.context();
|
var ctx = try testing.context();
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
const bc = try ctx.loadBrowserContext(.{ .id = "BID-9" });
|
const bc = try ctx.loadBrowserContext(.{ .id = "BID-9" });
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,12 +18,14 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const json = std.json;
|
const json = std.json;
|
||||||
|
const posix = std.posix;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
|
|
||||||
const Testing = @This();
|
const Testing = @This();
|
||||||
|
|
||||||
const main = @import("cdp.zig");
|
const CDP = @import("CDP.zig");
|
||||||
|
const Server = @import("../Server.zig");
|
||||||
|
|
||||||
const base = @import("../testing.zig");
|
const base = @import("../testing.zig");
|
||||||
pub const allocator = base.allocator;
|
pub const allocator = base.allocator;
|
||||||
@@ -35,61 +37,27 @@ pub const expectEqualSlices = base.expectEqualSlices;
|
|||||||
pub const pageTest = base.pageTest;
|
pub const pageTest = base.pageTest;
|
||||||
pub const newString = base.newString;
|
pub const newString = base.newString;
|
||||||
|
|
||||||
const Client = struct {
|
|
||||||
allocator: Allocator,
|
|
||||||
send_arena: ArenaAllocator,
|
|
||||||
sent: std.ArrayList(json.Value) = .{},
|
|
||||||
serialized: std.ArrayList([]const u8) = .{},
|
|
||||||
|
|
||||||
fn init(alloc: Allocator) Client {
|
|
||||||
return .{
|
|
||||||
.allocator = alloc,
|
|
||||||
.send_arena = ArenaAllocator.init(alloc),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sendAllocator(self: *Client) Allocator {
|
|
||||||
return self.send_arena.allocator();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sendJSON(self: *Client, message: anytype, opts: json.Stringify.Options) !void {
|
|
||||||
var opts_copy = opts;
|
|
||||||
opts_copy.whitespace = .indent_2;
|
|
||||||
const serialized = try json.Stringify.valueAlloc(self.allocator, message, opts_copy);
|
|
||||||
try self.serialized.append(self.allocator, serialized);
|
|
||||||
|
|
||||||
const value = try json.parseFromSliceLeaky(json.Value, self.allocator, serialized, .{});
|
|
||||||
try self.sent.append(self.allocator, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sendJSONRaw(self: *Client, buf: std.ArrayList(u8)) !void {
|
|
||||||
const value = try json.parseFromSliceLeaky(json.Value, self.allocator, buf.items, .{});
|
|
||||||
try self.sent.append(self.allocator, value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const TestCDP = main.CDPT(struct {
|
|
||||||
pub const Client = *Testing.Client;
|
|
||||||
});
|
|
||||||
|
|
||||||
const TestContext = struct {
|
const TestContext = struct {
|
||||||
client: ?Client = null,
|
read_at: usize = 0,
|
||||||
cdp_: ?TestCDP = null,
|
read_buf: [1024 * 32]u8 = undefined,
|
||||||
arena: ArenaAllocator,
|
cdp_: ?CDP = null,
|
||||||
|
client: Server.Client,
|
||||||
|
socket: posix.socket_t,
|
||||||
|
received: std.ArrayList(json.Value) = .empty,
|
||||||
|
received_raw: std.ArrayList([]const u8) = .empty,
|
||||||
|
|
||||||
pub fn deinit(self: *TestContext) void {
|
pub fn deinit(self: *TestContext) void {
|
||||||
if (self.cdp_) |*c| {
|
if (self.cdp_) |*c| {
|
||||||
c.deinit();
|
c.deinit();
|
||||||
}
|
}
|
||||||
self.arena.deinit();
|
self.client.deinit();
|
||||||
|
posix.close(self.socket);
|
||||||
|
base.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cdp(self: *TestContext) *TestCDP {
|
pub fn cdp(self: *TestContext) *CDP {
|
||||||
if (self.cdp_ == null) {
|
if (self.cdp_ == null) {
|
||||||
self.client = Client.init(self.arena.allocator());
|
self.cdp_ = CDP.init(&self.client) catch |err| @panic(@errorName(err));
|
||||||
// Don't use the arena here. We want to detect leaks in CDP.
|
|
||||||
// The arena is only for test-specific stuff
|
|
||||||
self.cdp_ = TestCDP.init(base.test_app, base.test_http, &self.client.?) catch unreachable;
|
|
||||||
}
|
}
|
||||||
return &self.cdp_.?;
|
return &self.cdp_.?;
|
||||||
}
|
}
|
||||||
@@ -100,7 +68,7 @@ const TestContext = struct {
|
|||||||
session_id: ?[]const u8 = null,
|
session_id: ?[]const u8 = null,
|
||||||
url: ?[:0]const u8 = null,
|
url: ?[:0]const u8 = null,
|
||||||
};
|
};
|
||||||
pub fn loadBrowserContext(self: *TestContext, opts: BrowserContextOpts) !*main.BrowserContext(TestCDP) {
|
pub fn loadBrowserContext(self: *TestContext, opts: BrowserContextOpts) !*CDP.BrowserContext(CDP) {
|
||||||
var c = self.cdp();
|
var c = self.cdp();
|
||||||
if (c.browser_context) |bc| {
|
if (c.browser_context) |bc| {
|
||||||
_ = c.disposeBrowserContext(bc.id);
|
_ = c.disposeBrowserContext(bc.id);
|
||||||
@@ -130,7 +98,7 @@ const TestContext = struct {
|
|||||||
}
|
}
|
||||||
const page = try bc.session.createPage();
|
const page = try bc.session.createPage();
|
||||||
const full_url = try std.fmt.allocPrintSentinel(
|
const full_url = try std.fmt.allocPrintSentinel(
|
||||||
self.arena.allocator(),
|
base.arena_allocator,
|
||||||
"http://127.0.0.1:9582/src/browser/tests/{s}",
|
"http://127.0.0.1:9582/src/browser/tests/{s}",
|
||||||
.{url},
|
.{url},
|
||||||
0,
|
0,
|
||||||
@@ -143,19 +111,20 @@ const TestContext = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn processMessage(self: *TestContext, msg: anytype) !void {
|
pub fn processMessage(self: *TestContext, msg: anytype) !void {
|
||||||
var json_message: []const u8 = undefined;
|
const json_message: []const u8 = blk: {
|
||||||
if (@typeInfo(@TypeOf(msg)) != .pointer) {
|
if (@typeInfo(@TypeOf(msg)) != .pointer) {
|
||||||
json_message = try std.json.Stringify.valueAlloc(self.arena.allocator(), msg, .{});
|
break :blk try std.json.Stringify.valueAlloc(base.arena_allocator, msg, .{});
|
||||||
} else {
|
}
|
||||||
// assume this is a string we want to send as-is, if it isn't, we'll
|
// assume this is a string we want to send as-is, if it isn't, we'll
|
||||||
// get a compile error, so no big deal.
|
// get a compile error, so no big deal.
|
||||||
json_message = msg;
|
break :blk msg;
|
||||||
}
|
};
|
||||||
return self.cdp().processMessage(json_message);
|
return self.cdp().processMessage(json_message);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expectSentCount(self: *TestContext, expected: usize) !void {
|
pub fn expectSentCount(self: *TestContext, expected: usize) !void {
|
||||||
try expectEqual(expected, self.client.?.sent.items.len);
|
try self.read();
|
||||||
|
try expectEqual(expected, self.received.items.len);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExpectResultOpts = struct {
|
const ExpectResultOpts = struct {
|
||||||
@@ -203,37 +172,135 @@ const TestContext = struct {
|
|||||||
index: ?usize = null,
|
index: ?usize = null,
|
||||||
};
|
};
|
||||||
pub fn expectSent(self: *TestContext, expected: anytype, opts: SentOpts) !void {
|
pub fn expectSent(self: *TestContext, expected: anytype, opts: SentOpts) !void {
|
||||||
const serialized = try json.Stringify.valueAlloc(self.arena.allocator(), expected, .{
|
const serialized = try json.Stringify.valueAlloc(base.arena_allocator, expected, .{
|
||||||
.whitespace = .indent_2,
|
.whitespace = .indent_2,
|
||||||
.emit_null_optional_fields = false,
|
.emit_null_optional_fields = false,
|
||||||
});
|
});
|
||||||
|
for (0..5) |_| {
|
||||||
for (self.client.?.sent.items, 0..) |sent, i| {
|
for (self.received.items, 0..) |received, i| {
|
||||||
if (try compareExpectedToSent(serialized, sent) == false) {
|
if (try compareExpectedToSent(serialized, received) == false) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.index) |expected_index| {
|
if (opts.index) |expected_index| {
|
||||||
if (expected_index != i) {
|
if (expected_index != i) {
|
||||||
|
std.debug.print("Expected message at index: {d}, was at index: {d}\n", .{ expected_index, i });
|
||||||
|
self.dumpReceived();
|
||||||
return error.ErrorAtWrongIndex;
|
return error.ErrorAtWrongIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = self.client.?.sent.orderedRemove(i);
|
return;
|
||||||
_ = self.client.?.serialized.orderedRemove(i);
|
}
|
||||||
|
std.Thread.sleep(5 * std.time.ns_per_ms);
|
||||||
|
try self.read();
|
||||||
|
}
|
||||||
|
self.dumpReceived();
|
||||||
|
return error.ErrorNotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dumpReceived(self: *const TestContext) void {
|
||||||
|
std.debug.print("CDP Message Received ({d})\n", .{self.received_raw.items.len});
|
||||||
|
for (self.received_raw.items, 0..) |received, i| {
|
||||||
|
std.debug.print("===Message: {d}===\n{s}\n\n", .{ i, received });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getSentMessage(self: *TestContext, index: usize) !?json.Value {
|
||||||
|
for (0..5) |_| {
|
||||||
|
if (index < self.received.items.len) {
|
||||||
|
return self.received.items[index];
|
||||||
|
}
|
||||||
|
std.Thread.sleep(5 * std.time.ns_per_ms);
|
||||||
|
try self.read();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(self: *TestContext) !void {
|
||||||
|
while (true) {
|
||||||
|
const n = posix.read(self.socket, self.read_buf[self.read_at..]) catch |err| switch (err) {
|
||||||
|
error.WouldBlock => return,
|
||||||
|
else => return err,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (n == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std.debug.print("Error not found. Expecting:\n{s}\n\nGot:\n", .{serialized});
|
self.read_at += n;
|
||||||
for (self.client.?.serialized.items, 0..) |sent, i| {
|
|
||||||
std.debug.print("#{d}\n{s}\n\n", .{ i, sent });
|
// Try to parse complete WebSocket frames
|
||||||
|
var pos: usize = 0;
|
||||||
|
while (pos < self.read_at) {
|
||||||
|
// Need at least 2 bytes for header
|
||||||
|
if (self.read_at - pos < 2) break;
|
||||||
|
|
||||||
|
const opcode = self.read_buf[pos] & 0x0F;
|
||||||
|
const payload_len_byte = self.read_buf[pos + 1] & 0x7F;
|
||||||
|
|
||||||
|
var header_size: usize = 2;
|
||||||
|
var payload_len: usize = payload_len_byte;
|
||||||
|
|
||||||
|
if (payload_len_byte == 126) {
|
||||||
|
if (self.read_at - pos < 4) break;
|
||||||
|
payload_len = std.mem.readInt(u16, self.read_buf[pos + 2 ..][0..2], .big);
|
||||||
|
header_size = 4;
|
||||||
|
}
|
||||||
|
// Skip 8-byte length case (127) - not needed
|
||||||
|
|
||||||
|
const frame_size = header_size + payload_len;
|
||||||
|
if (self.read_at - pos < frame_size) break;
|
||||||
|
|
||||||
|
// We have a complete frame - process text (1) or binary (2), skip others
|
||||||
|
if (opcode == 1 or opcode == 2) {
|
||||||
|
const payload = self.read_buf[pos + header_size ..][0..payload_len];
|
||||||
|
const parsed = try std.json.parseFromSliceLeaky(json.Value, base.arena_allocator, payload, .{});
|
||||||
|
try self.received.append(base.arena_allocator, parsed);
|
||||||
|
try self.received_raw.append(base.arena_allocator, try base.arena_allocator.dupe(u8, payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
pos += frame_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move remaining partial data to beginning of buffer
|
||||||
|
if (pos > 0 and pos < self.read_at) {
|
||||||
|
std.mem.copyForwards(u8, &self.read_buf, self.read_buf[pos..self.read_at]);
|
||||||
|
self.read_at -= pos;
|
||||||
|
} else if (pos == self.read_at) {
|
||||||
|
self.read_at = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return error.ErrorNotFound;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn context() TestContext {
|
pub fn context() !TestContext {
|
||||||
|
var pair: [2]posix.socket_t = undefined;
|
||||||
|
const rc = std.c.socketpair(posix.AF.LOCAL, posix.SOCK.STREAM, 0, &pair);
|
||||||
|
if (rc != 0) {
|
||||||
|
return error.SocketPairFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
posix.close(pair[0]);
|
||||||
|
posix.close(pair[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeout = std.mem.toBytes(posix.timeval{ .sec = 0, .usec = 5_000 });
|
||||||
|
try posix.setsockopt(pair[0], posix.SOL.SOCKET, posix.SO.RCVTIMEO, &timeout);
|
||||||
|
try posix.setsockopt(pair[0], posix.SOL.SOCKET, posix.SO.SNDTIMEO, &timeout);
|
||||||
|
try posix.setsockopt(pair[1], posix.SOL.SOCKET, posix.SO.RCVTIMEO, &timeout);
|
||||||
|
try posix.setsockopt(pair[1], posix.SOL.SOCKET, posix.SO.SNDTIMEO, &timeout);
|
||||||
|
|
||||||
|
try posix.setsockopt(pair[0], posix.SOL.SOCKET, posix.SO.RCVBUF, &std.mem.toBytes(@as(c_int, 32_768)));
|
||||||
|
try posix.setsockopt(pair[0], posix.SOL.SOCKET, posix.SO.SNDBUF, &std.mem.toBytes(@as(c_int, 32_768)));
|
||||||
|
try posix.setsockopt(pair[1], posix.SOL.SOCKET, posix.SO.RCVBUF, &std.mem.toBytes(@as(c_int, 32_768)));
|
||||||
|
try posix.setsockopt(pair[1], posix.SOL.SOCKET, posix.SO.SNDBUF, &std.mem.toBytes(@as(c_int, 32_768)));
|
||||||
|
|
||||||
|
const client = try Server.Client.init(pair[1], base.arena_allocator, base.test_app, "json-version", 2000);
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.arena = ArenaAllocator.init(std.testing.allocator),
|
.client = client,
|
||||||
|
.socket = pair[0],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -324,7 +324,9 @@ pub const WsConnection = struct {
|
|||||||
pub fn init(socket: posix.socket_t, allocator: Allocator, json_version_response: []const u8, timeout_ms: u32) !WsConnection {
|
pub fn init(socket: posix.socket_t, allocator: Allocator, json_version_response: []const u8, timeout_ms: u32) !WsConnection {
|
||||||
const socket_flags = try posix.fcntl(socket, posix.F.GETFL, 0);
|
const socket_flags = try posix.fcntl(socket, posix.F.GETFL, 0);
|
||||||
const nonblocking = @as(u32, @bitCast(posix.O{ .NONBLOCK = true }));
|
const nonblocking = @as(u32, @bitCast(posix.O{ .NONBLOCK = true }));
|
||||||
|
if (builtin.is_test == false) {
|
||||||
assert(socket_flags & nonblocking == nonblocking, "WsConnection.init blocking", .{});
|
assert(socket_flags & nonblocking == nonblocking, "WsConnection.init blocking", .{});
|
||||||
|
}
|
||||||
|
|
||||||
var reader = try Reader(true).init(allocator);
|
var reader = try Reader(true).init(allocator);
|
||||||
errdefer reader.deinit();
|
errdefer reader.deinit();
|
||||||
|
|||||||
@@ -410,21 +410,46 @@ fn runWebApiTest(test_file: [:0]const u8) !void {
|
|||||||
page.js.localScope(&ls);
|
page.js.localScope(&ls);
|
||||||
defer ls.deinit();
|
defer ls.deinit();
|
||||||
|
|
||||||
|
{
|
||||||
var try_catch: js.TryCatch = undefined;
|
var try_catch: js.TryCatch = undefined;
|
||||||
try_catch.init(&ls.local);
|
try_catch.init(&ls.local);
|
||||||
defer try_catch.deinit();
|
defer try_catch.deinit();
|
||||||
|
|
||||||
try page.navigate(url, .{});
|
try page.navigate(url, .{});
|
||||||
|
}
|
||||||
|
|
||||||
var runner = try test_session.runner(.{});
|
var runner = try test_session.runner(.{});
|
||||||
try runner.wait(.{ .ms = 2000 });
|
try runner.wait(.{ .ms = 2000 });
|
||||||
|
|
||||||
test_browser.runMicrotasks();
|
var wait_ms: u32 = 2000;
|
||||||
|
var timer = try std.time.Timer.start();
|
||||||
|
while (true) {
|
||||||
|
var try_catch: js.TryCatch = undefined;
|
||||||
|
try_catch.init(&ls.local);
|
||||||
|
defer try_catch.deinit();
|
||||||
|
|
||||||
ls.local.eval("testing.assertOk()", "testing.assertOk()") catch |err| {
|
const js_val = ls.local.exec("testing.assertOk()", "testing.assertOk()") catch |err| {
|
||||||
const caught = try_catch.caughtOrError(arena_allocator, err);
|
const caught = try_catch.caughtOrError(arena_allocator, err);
|
||||||
std.debug.print("{s}: test failure\nError: {f}\n", .{ test_file, caught });
|
std.debug.print("{s}: test failure\nError: {f}\n", .{ test_file, caught });
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
|
if (js_val.isTrue()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (try runner.tick(.{ .ms = 20 })) {
|
||||||
|
.done => return error.TestNeverSignaledCompletion,
|
||||||
|
.ok => |next_ms| {
|
||||||
|
const ms_elapsed = timer.lap() / 1_000_000;
|
||||||
|
if (ms_elapsed >= wait_ms) {
|
||||||
|
return error.TestTimedOut;
|
||||||
|
}
|
||||||
|
wait_ms -= @intCast(ms_elapsed);
|
||||||
|
if (next_ms > 0) {
|
||||||
|
std.Thread.sleep(std.time.ns_per_ms * next_ms);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used by a few CDP tests - wouldn't be sad to see this go.
|
// Used by a few CDP tests - wouldn't be sad to see this go.
|
||||||
@@ -445,10 +470,6 @@ pub fn pageTest(comptime test_file: []const u8) !*Page {
|
|||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
|
||||||
std.testing.refAllDecls(@This());
|
|
||||||
}
|
|
||||||
|
|
||||||
const log = @import("log.zig");
|
const log = @import("log.zig");
|
||||||
const TestHTTPServer = @import("TestHTTPServer.zig");
|
const TestHTTPServer = @import("TestHTTPServer.zig");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user