mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-28 15:40:04 +00:00
Compare commits
13 Commits
update-zig
...
url-userin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c04ccf5e87 | ||
|
|
a0dd14aaad | ||
|
|
7ea8f3f766 | ||
|
|
28cc60adb0 | ||
|
|
c14a9ad986 | ||
|
|
679f2104f4 | ||
|
|
c6b0c75106 | ||
|
|
93485c1ef3 | ||
|
|
0324d5c232 | ||
|
|
a75c0cf08d | ||
|
|
2812b8f07c | ||
|
|
e2afbec29d | ||
|
|
a45f9cb810 |
2
.github/actions/install/action.yml
vendored
2
.github/actions/install/action.yml
vendored
@@ -13,7 +13,7 @@ inputs:
|
||||
zig-v8:
|
||||
description: 'zig v8 version to install'
|
||||
required: false
|
||||
default: 'v0.3.4'
|
||||
default: 'v0.3.7'
|
||||
v8:
|
||||
description: 'v8 version to install'
|
||||
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 }}
|
||||
|
||||
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:
|
||||
push:
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast snapshot_creator -- src/snapshot.bin
|
||||
|
||||
- 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
|
||||
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
|
||||
|
||||
- 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
|
||||
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
|
||||
|
||||
- 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
|
||||
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
|
||||
|
||||
- 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
|
||||
run: mv zig-out/bin/lightpanda lightpanda-${{ env.ARCH }}-${{ env.OS }}
|
||||
|
||||
@@ -3,7 +3,7 @@ FROM debian:stable-slim
|
||||
ARG MINISIG=0.12
|
||||
ARG ZIG_MINISIG=RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U
|
||||
ARG V8=14.0.365.4
|
||||
ARG ZIG_V8=v0.3.4
|
||||
ARG ZIG_V8=v0.3.7
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
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 (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
|
||||
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,
|
||||
.minor = lightpanda_version.minor,
|
||||
.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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
.minimum_zig_version = "0.15.2",
|
||||
.dependencies = .{
|
||||
.v8 = .{
|
||||
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/refs/tags/v0.3.5.tar.gz",
|
||||
.hash = "v8-0.0.0-xddH66Z5BABx8CmC6u6qNOjrT4_42uliDSnA7Yg0pcBe",
|
||||
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/refs/tags/v0.3.7.tar.gz",
|
||||
.hash = "v8-0.0.0-xddH67uBBAD95hWsPQz3Ni1PlZjdywtPXrGUAp8rSKco",
|
||||
},
|
||||
// .v8 = .{ .path = "../zig-v8-fork" },
|
||||
.brotli = .{
|
||||
|
||||
@@ -247,7 +247,7 @@ pub const Fetch = struct {
|
||||
with_frames: bool = false,
|
||||
strip: dump.Opts.Strip = .{},
|
||||
wait_ms: u32 = 5000,
|
||||
wait_until: WaitUntil = .load,
|
||||
wait_until: WaitUntil = .done,
|
||||
};
|
||||
|
||||
pub const Common = struct {
|
||||
@@ -665,7 +665,7 @@ fn parseFetchArgs(
|
||||
var common: Common = .{};
|
||||
var strip: dump.Opts.Strip = .{};
|
||||
var wait_ms: u32 = 5000;
|
||||
var wait_until: WaitUntil = .load;
|
||||
var wait_until: WaitUntil = .done;
|
||||
|
||||
while (args.next()) |opt| {
|
||||
if (std.mem.eql(u8, "--wait-ms", opt) or std.mem.eql(u8, "--wait_ms", opt)) {
|
||||
|
||||
@@ -404,10 +404,6 @@ pub fn getOrigin(allocator: Allocator, raw: [:0]const u8) !?[]const u8 {
|
||||
}
|
||||
|
||||
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)
|
||||
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
|
||||
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
|
||||
const host_part = raw[authority_start..authority_end];
|
||||
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 authority_start = scheme_end + 3;
|
||||
|
||||
const pos = std.mem.indexOfScalar(u8, raw[authority_start..], '@') orelse return null;
|
||||
const path_start = std.mem.indexOfScalarPos(u8, raw, authority_start, '/') orelse raw.len;
|
||||
// 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;
|
||||
};
|
||||
|
||||
const pos = std.mem.indexOfScalar(u8, raw[authority_start..path_start], '@') orelse return null;
|
||||
|
||||
const full_pos = authority_start + pos;
|
||||
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 "";
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const authority = raw[authority_start..];
|
||||
const path_start = std.mem.indexOfAny(u8, authority, "/?#") orelse return authority;
|
||||
return authority[0..path_start];
|
||||
return raw[authority_start..path_start];
|
||||
}
|
||||
|
||||
// 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);
|
||||
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 {
|
||||
noop: bool = false,
|
||||
static: bool = false,
|
||||
deletable: bool = true,
|
||||
dom_exception: bool = false,
|
||||
as_typed_array: bool = false,
|
||||
null_as_undefined: bool = false,
|
||||
|
||||
@@ -137,7 +137,7 @@ pub fn create() !Snapshot {
|
||||
defer v8.v8__HandleScope__DESTRUCT(&handle_scope);
|
||||
|
||||
// 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| {
|
||||
@setEvalBranchQuota(10_000);
|
||||
templates[i] = generateConstructor(JsApi, isolate);
|
||||
@@ -419,7 +419,7 @@ fn collectExternalReferences() [countExternalReferences()]isize {
|
||||
// via `new ClassName()` - but they could, for example, be created in
|
||||
// Zig and returned from a function call, which is why we need the
|
||||
// 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: {
|
||||
if (@hasDecl(JsApi, "constructor")) {
|
||||
break :blk JsApi.constructor.func;
|
||||
@@ -429,7 +429,7 @@ fn generateConstructor(comptime JsApi: type, isolate: *v8.Isolate) *v8.FunctionT
|
||||
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);
|
||||
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)
|
||||
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 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;
|
||||
var has_named_index_getter = false;
|
||||
|
||||
@@ -497,23 +502,47 @@ fn attachClass(comptime JsApi: type, isolate: *v8.Isolate, template: *v8.Functio
|
||||
switch (definition) {
|
||||
bridge.Accessor => {
|
||||
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.static) {
|
||||
v8.v8__Template__SetAccessorProperty__DEFAULT(@ptrCast(template), js_name, getter_callback);
|
||||
} else {
|
||||
v8.v8__ObjectTemplate__SetAccessorProperty__DEFAULT(prototype, js_name, getter_callback);
|
||||
}
|
||||
attribute |= v8.ReadOnly;
|
||||
}
|
||||
if (value.deletable == false) {
|
||||
attribute |= v8.DontDelete;
|
||||
}
|
||||
|
||||
if (value.static) {
|
||||
// Static accessors: use Template's SetAccessorProperty
|
||||
v8.v8__Template__SetAccessorProperty(@ptrCast(template), js_name, getter_callback, setter_callback, attribute);
|
||||
} else {
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(value.static == false);
|
||||
}
|
||||
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);
|
||||
v8.v8__ObjectTemplate__SetAccessorProperty__Config(prototype, &.{
|
||||
.key = js_name,
|
||||
.getter = getter_callback,
|
||||
.setter = setter_callback,
|
||||
.attribute = attribute,
|
||||
});
|
||||
}
|
||||
},
|
||||
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));
|
||||
if (value.static) {
|
||||
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;
|
||||
},
|
||||
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)
|
||||
v8.v8__Symbol__GetAsyncIterator(isolate)
|
||||
else
|
||||
|
||||
@@ -198,6 +198,7 @@ pub const Function = struct {
|
||||
|
||||
pub const Accessor = struct {
|
||||
static: bool = false,
|
||||
deletable: bool = true,
|
||||
cache: ?Caller.Function.Opts.Caching = null,
|
||||
getter: ?*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{
|
||||
.cache = opts.cache,
|
||||
.static = opts.static,
|
||||
.deletable = opts.deletable,
|
||||
};
|
||||
|
||||
if (@typeInfo(@TypeOf(getter)) != .null) {
|
||||
|
||||
@@ -871,3 +871,33 @@
|
||||
testing.expectEqual('', url.search);
|
||||
}
|
||||
</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>
|
||||
|
||||
@@ -262,6 +262,31 @@
|
||||
}
|
||||
</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>
|
||||
{
|
||||
let unhandledCalled = 0;
|
||||
|
||||
@@ -829,7 +829,7 @@ pub const JsApi = struct {
|
||||
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 top = bridge.accessor(Window.getTop, null, .{});
|
||||
@@ -842,7 +842,7 @@ pub const JsApi = struct {
|
||||
pub const performance = bridge.accessor(Window.getPerformance, null, .{});
|
||||
pub const localStorage = bridge.accessor(Window.getLocalStorage, 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 navigation = bridge.accessor(Window.getNavigation, null, .{});
|
||||
pub const crypto = bridge.accessor(Window.getCrypto, null, .{});
|
||||
|
||||
@@ -39,6 +39,7 @@ pub fn processMessage(cmd: anytype) !void {
|
||||
addScriptToEvaluateOnNewDocument,
|
||||
createIsolatedWorld,
|
||||
navigate,
|
||||
reload,
|
||||
stopLoading,
|
||||
close,
|
||||
captureScreenshot,
|
||||
@@ -52,6 +53,7 @@ pub fn processMessage(cmd: anytype) !void {
|
||||
.addScriptToEvaluateOnNewDocument => return addScriptToEvaluateOnNewDocument(cmd),
|
||||
.createIsolatedWorld => return createIsolatedWorld(cmd),
|
||||
.navigate => return navigate(cmd),
|
||||
.reload => return doReload(cmd),
|
||||
.stopLoading => return cmd.sendResult(null, .{}),
|
||||
.close => return close(cmd),
|
||||
.captureScreenshot => return captureScreenshot(cmd),
|
||||
@@ -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 {
|
||||
// detachTarget could be called, in which case, we still have a page doing
|
||||
// things, but no session.
|
||||
@@ -784,3 +816,27 @@ test "cdp.page: getLayoutMetrics" {
|
||||
},
|
||||
}, .{ .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 } });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user