diff --git a/src/Server.zig b/src/Server.zig
index 0b5d8ef5..dd8e75ba 100644
--- a/src/Server.zig
+++ b/src/Server.zig
@@ -44,23 +44,22 @@ conns_mutex: std.Thread.Mutex = .{},
conns_pool: std.heap.MemoryPool(CDP),
pub fn init(app: *App, address: net.Address) !*Server {
- const json_version_response = try buildJSONVersionResponse(app);
- errdefer app.allocator.free(json_version_response);
-
const self = try app.allocator.create(Server);
errdefer app.allocator.destroy(self);
self.* = .{
.app = app,
.conns_pool = .init(app.allocator),
- .json_version_response = json_version_response,
+ .json_version_response = "",
};
errdefer self.conns_pool.deinit();
+ // Bind first so /json/version can advertise the OS-assigned port (--port 0).
var bound_address = address;
try self.app.network.bind(&bound_address, self, onAccept);
log.info(.app, "server running", .{ .address = bound_address });
+ self.json_version_response = try buildJSONVersionResponse(app, bound_address.getPort());
return self;
}
@@ -237,10 +236,7 @@ fn unregisterConn(self: *Server, conn: *CDP) void {
// Utils
// --------
-fn buildJSONVersionResponse(
- app: *const App,
-) ![]const u8 {
- const port = app.config.port();
+fn buildJSONVersionResponse(app: *const App, port: u16) ![]const u8 {
const host = app.config.advertiseHost();
if (std.mem.eql(u8, host, "0.0.0.0")) {
log.info(.cdp, "unreachable advertised host", .{
@@ -276,7 +272,7 @@ pub const milliTimestamp = @import("datetime.zig").milliTimestamp;
const testing = @import("testing.zig");
test "server: buildJSONVersionResponse" {
- const res = try buildJSONVersionResponse(testing.test_app);
+ const res = try buildJSONVersionResponse(testing.test_app, testing.test_app.config.port());
defer testing.test_app.allocator.free(res);
// The response includes the build version, so check structure rather than exact bytes.
@@ -509,7 +505,7 @@ test "server: get /json/version" {
try testing.expect(std.mem.startsWith(u8, res1, "HTTP/1.1 200 OK\r\n"));
try testing.expect(std.mem.indexOf(u8, res1, "\"Browser\": \"Lightpanda/") != null);
try testing.expect(std.mem.indexOf(u8, res1, "\"Protocol-Version\": \"1.3\"") != null);
- try testing.expect(std.mem.indexOf(u8, res1, "\"webSocketDebuggerUrl\": \"ws://127.0.0.1:9222/\"") != null);
+ try testing.expect(std.mem.indexOf(u8, res1, "\"webSocketDebuggerUrl\": \"ws://127.0.0.1:9583/\"") != null);
}
{
diff --git a/src/browser/Frame.zig b/src/browser/Frame.zig
index af9a1784..0fc1949c 100644
--- a/src/browser/Frame.zig
+++ b/src/browser/Frame.zig
@@ -407,6 +407,10 @@ pub fn deinit(self: *Frame) void {
}
const browser = page.session.browser;
+
+ // don't abort pending frames.
+ browser.http_client.abortFrame(self._frame_id, .{});
+
browser.env.destroyContext(self.js);
// Must be after context is destroyed. A finalizer can reach into the *Worker
@@ -417,9 +421,6 @@ pub fn deinit(self: *Frame) void {
self._script_manager.base.shutdown = true;
- // don't abort pending frames.
- browser.http_client.abortFrame(self._frame_id, .{});
-
self._script_manager.deinit();
self._style_manager.deinit();
diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig
index f2ec997e..6ffe578a 100644
--- a/src/browser/js/bridge.zig
+++ b/src/browser/js/bridge.zig
@@ -940,6 +940,7 @@ pub const PageJsApis = flattenTypes(&.{
// TODO: Expand this list to include all worker-appropriate APIs.
pub const WorkerJsApis = flattenTypes(&.{
@import("../webapi/WorkerGlobalScope.zig"),
+ @import("../webapi/WorkerLocation.zig"),
@import("../webapi/EventTarget.zig"),
@import("../webapi/DOMException.zig"),
@import("../webapi/net/URLSearchParams.zig"),
@@ -949,6 +950,7 @@ pub const WorkerJsApis = flattenTypes(&.{
@import("../webapi/File.zig"),
@import("../webapi/Console.zig"),
@import("../webapi/Crypto.zig"),
+ @import("../webapi/SubtleCrypto.zig"),
@import("../webapi/net/FormData.zig"),
@import("../webapi/net/Headers.zig"),
@import("../webapi/net/Request.zig"),
@@ -977,7 +979,10 @@ pub const WorkerJsApis = flattenTypes(&.{
// to know about all possible types. Individual snapshots use their own
// subsets (PageJsApis, WorkerSnapshot.JsApis).
pub const JsApis = blk: {
- const base = PageJsApis ++ [_]type{@import("../webapi/WorkerGlobalScope.zig").JsApi};
+ const base = PageJsApis ++ [_]type{
+ @import("../webapi/WorkerGlobalScope.zig").JsApi,
+ @import("../webapi/WorkerLocation.zig").JsApi,
+ };
if (lp.build_config.wpt_extensions == false) {
break :blk base;
}
diff --git a/src/browser/tests/crypto-worker.js b/src/browser/tests/crypto-worker.js
new file mode 100644
index 00000000..90d948d0
--- /dev/null
+++ b/src/browser/tests/crypto-worker.js
@@ -0,0 +1,92 @@
+// Exercises crypto APIs inside a worker. Posts 'ready' once the message
+// handler is wired so the page knows it can send a command without racing
+// worker startup. Receives the command, runs the crypto operation, and
+// posts the result back.
+self.onmessage = async function(e) {
+ const cmd = e.data;
+ try {
+ if (cmd.kind === 'getRandomValues') {
+ const ta = new Uint8Array(32);
+ const same = crypto.getRandomValues(ta) === ta;
+ const uniq = new Set(Array.from(ta));
+ postMessage({ ok: true, same, looks_random: uniq.size > 8 });
+ return;
+ }
+
+ if (cmd.kind === 'randomUUID') {
+ const uuid = crypto.randomUUID();
+ const regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
+ postMessage({ ok: true, type: typeof uuid, length: uuid.length, valid: regex.test(uuid) });
+ return;
+ }
+
+ if (cmd.kind === 'digest') {
+ const buffer = await crypto.subtle.digest('sha-256', new TextEncoder().encode('over 9000'));
+ const hex = [...new Uint8Array(buffer)].map(x => x.toString(16).padStart(2, '0')).join('');
+ postMessage({ ok: true, hex });
+ return;
+ }
+
+ if (cmd.kind === 'hmac') {
+ const key = await crypto.subtle.generateKey(
+ { name: 'HMAC', hash: { name: 'SHA-512' } },
+ true,
+ ['sign', 'verify'],
+ );
+ const raw = await crypto.subtle.exportKey('raw', key);
+ const encoder = new TextEncoder();
+ const signature = await crypto.subtle.sign('HMAC', key, encoder.encode('Hello, world!'));
+ const verified = await crypto.subtle.verify(
+ { name: 'HMAC' },
+ key,
+ signature,
+ encoder.encode('Hello, world!'),
+ );
+ postMessage({
+ ok: true,
+ key_type: typeof key,
+ raw_byte_length: raw.byteLength,
+ is_array_buffer: signature instanceof ArrayBuffer,
+ verified,
+ });
+ return;
+ }
+
+ if (cmd.kind === 'x25519') {
+ const { privateKey, publicKey } = await crypto.subtle.generateKey(
+ { name: 'X25519' },
+ true,
+ ['deriveBits'],
+ );
+ const sharedKey = await crypto.subtle.deriveBits(
+ { name: 'X25519', public: publicKey },
+ privateKey,
+ 128,
+ );
+ postMessage({
+ ok: true,
+ private_key_type: typeof privateKey,
+ public_key_type: typeof publicKey,
+ shared_byte_length: sharedKey.byteLength,
+ });
+ return;
+ }
+
+ if (cmd.kind === 'generateKey-rejection') {
+ let err_name = null;
+ try {
+ await crypto.subtle.generateKey({ name: 'AES-CBC', length: 128 }, true, ['sign']);
+ } catch (err) {
+ err_name = err.name;
+ }
+ postMessage({ ok: true, err_name });
+ return;
+ }
+
+ postMessage({ ok: false, err: 'unknown command' });
+ } catch (err) {
+ postMessage({ ok: false, err: String(err), stack: err.stack });
+ }
+};
+
+postMessage({ ready: true });
diff --git a/src/browser/tests/crypto.html b/src/browser/tests/crypto.html
index 59d6522e..f588b743 100644
--- a/src/browser/tests/crypto.html
+++ b/src/browser/tests/crypto.html
@@ -261,3 +261,96 @@
testing.expectEqual("6cad17e6f3f76680d6dd18ed043b75b4f6e1aa1d08b917294942e882fb6466c3510948c34af8b903ed0725b582b3b39c0e485ae2c1b7dfdb192ee38b79c782b6", await hash('sha-512', 'over 9000'));
});
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/browser/tests/worker/api-worker.js b/src/browser/tests/worker/api-worker.js
index edfea446..1f75d7e1 100644
--- a/src/browser/tests/worker/api-worker.js
+++ b/src/browser/tests/worker/api-worker.js
@@ -50,6 +50,15 @@
const blob_url_is_blob = blob_url.startsWith('blob:');
URL.revokeObjectURL(blob_url);
+ // self.location
+ const loc = self.location;
+ const loc_is_worker_location = loc instanceof WorkerLocation;
+ const loc_identity_stable = self.location === loc;
+ const loc_href = loc.href;
+ const loc_protocol = loc.protocol;
+ const loc_pathname = loc.pathname;
+ const loc_to_string = String(loc);
+
postMessage({
ok: true,
results: {
@@ -75,6 +84,12 @@
pre_aborted,
pre_threw,
blob_url_is_blob,
+ loc_is_worker_location,
+ loc_identity_stable,
+ loc_href,
+ loc_protocol,
+ loc_pathname,
+ loc_to_string,
},
});
} catch (e) {
diff --git a/src/browser/tests/worker/worker.html b/src/browser/tests/worker/worker.html
index e660b492..2740d7c0 100644
--- a/src/browser/tests/worker/worker.html
+++ b/src/browser/tests/worker/worker.html
@@ -220,6 +220,14 @@
// URL.createObjectURL / revokeObjectURL
testing.expectEqual(true, r.blob_url_is_blob);
+
+ // WorkerLocation
+ testing.expectEqual(true, r.loc_is_worker_location);
+ testing.expectEqual(true, r.loc_identity_stable);
+ testing.expectTrue(r.loc_href.endsWith('api-worker.js'));
+ testing.expectEqual(r.loc_href, r.loc_to_string);
+ testing.expectEqual('http:', r.loc_protocol);
+ testing.expectTrue(r.loc_pathname.endsWith('api-worker.js'));
});
}
diff --git a/src/browser/webapi/SubtleCrypto.zig b/src/browser/webapi/SubtleCrypto.zig
index f6b45ccf..3220352c 100644
--- a/src/browser/webapi/SubtleCrypto.zig
+++ b/src/browser/webapi/SubtleCrypto.zig
@@ -20,7 +20,6 @@ const std = @import("std");
const lp = @import("lightpanda");
const crypto = @import("../../sys/libcrypto.zig");
-const Frame = @import("../Frame.zig");
const js = @import("../js/js.zig");
const CryptoKey = @import("CryptoKey.zig");
@@ -34,6 +33,7 @@ const X25519 = @import("crypto/X25519.zig");
const log = lp.log;
const String = lp.String;
+const Execution = js.Execution;
/// The SubtleCrypto interface of the Web Crypto API provides a number of low-level
/// cryptographic functions.
@@ -49,11 +49,11 @@ pub fn generateKey(
algo: algorithm.Init,
extractable: bool,
key_usages: []const []const u8,
- frame: *Frame,
+ exec: *const Execution,
) !js.Promise {
- const local = frame.js.local.?;
+ const local = exec.context.local.?;
switch (algo) {
- .hmac_key_gen => |params| return HMAC.init(params, extractable, key_usages, frame),
+ .hmac_key_gen => |params| return HMAC.init(params, extractable, key_usages, exec),
.aes_key_gen => |params| {
AES.validate(params, key_usages) catch |err| {
return local.rejectPromise(.{ .dom_exception = .{ .err = err } });
@@ -72,8 +72,8 @@ pub fn generateKey(
};
log.warn(.not_implemented, "generateKey", .{ .name = params.name });
},
- .name => |js_name| return generateKeyFromName(try js_name.toSSO(false), extractable, key_usages, frame),
- .object => |object| return generateKeyFromName(try object.name.toSSO(false), extractable, key_usages, frame),
+ .name => |js_name| return generateKeyFromName(try js_name.toSSO(false), extractable, key_usages, exec),
+ .object => |object| return generateKeyFromName(try object.name.toSSO(false), extractable, key_usages, exec),
.invalid => return local.rejectPromise(.{ .type_error = "invalid algorithm" }),
}
@@ -84,10 +84,10 @@ fn generateKeyFromName(
name: String,
extractable: bool,
key_usages: []const []const u8,
- frame: *Frame,
+ exec: *const Execution,
) !js.Promise {
- return _generateKeyFromName(name, extractable, key_usages, frame) catch |err| {
- return frame.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = err } });
+ return _generateKeyFromName(name, extractable, key_usages, exec) catch |err| {
+ return exec.context.local.?.rejectPromise(.{ .dom_exception = .{ .err = err } });
};
}
@@ -95,10 +95,10 @@ fn _generateKeyFromName(
name: String,
extractable: bool,
key_usages: []const []const u8,
- frame: *Frame,
+ exec: *const Execution,
) !js.Promise {
if (name.eql(comptime .wrap("X25519"))) {
- return X25519.init(extractable, key_usages, frame);
+ return X25519.init(extractable, key_usages, exec);
}
{
@@ -145,14 +145,15 @@ pub fn exportKey(
_: *const SubtleCrypto,
format: []const u8,
key: *CryptoKey,
- frame: *Frame,
+ exec: *const Execution,
) !js.Promise {
+ const local = exec.context.local.?;
if (!key.canExportKey()) {
- return frame.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } });
+ return local.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } });
}
if (std.mem.eql(u8, format, "raw")) {
- return frame.js.local.?.resolvePromise(js.ArrayBuffer{ .values = key._key });
+ return local.resolvePromise(js.ArrayBuffer{ .values = key._key });
}
const is_unsupported = std.mem.eql(u8, format, "pkcs8") or
@@ -160,10 +161,10 @@ pub fn exportKey(
if (is_unsupported) {
log.warn(.not_implemented, "SubtleCrypto.exportKey", .{ .format = format });
- return frame.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
+ return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
}
- return frame.js.local.?.rejectPromise(.{ .type_error = "invalid format" });
+ return local.rejectPromise(.{ .type_error = "invalid format" });
}
/// Derive a secret key from a master key.
@@ -172,27 +173,28 @@ pub fn deriveBits(
algo: algorithm.Derive,
base_key: *const CryptoKey, // Private key.
length: usize,
- frame: *Frame,
+ exec: *const Execution,
) !js.Promise {
+ const local = exec.context.local.?;
return switch (algo) {
.ecdh_or_x25519 => |params| {
const name = params.name;
if (std.mem.eql(u8, name, "X25519")) {
- const result = X25519.deriveBits(base_key, params.public, length, frame) catch |err| switch (err) {
- error.InvalidAccessError => return frame.js.local.?.rejectPromise(.{
+ const result = X25519.deriveBits(base_key, params.public, length, exec) catch |err| switch (err) {
+ error.InvalidAccessError => return local.rejectPromise(.{
.dom_exception = .{ .err = error.InvalidAccessError },
}),
else => return err,
};
- return frame.js.local.?.resolvePromise(result);
+ return local.resolvePromise(result);
}
if (std.mem.eql(u8, name, "ECDH")) {
log.warn(.not_implemented, "SubtleCrypto.deriveBits", .{ .name = name });
}
- return frame.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
+ return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
},
};
}
@@ -204,14 +206,14 @@ pub fn sign(
algo: algorithm.Sign,
key: *CryptoKey,
data: []const u8, // ArrayBuffer.
- frame: *Frame,
+ exec: *const Execution,
) !js.Promise {
return switch (key._type) {
// Call sign for HMAC.
- .hmac => return HMAC.sign(algo, key, data, frame),
+ .hmac => return HMAC.sign(algo, key, data, exec),
else => {
log.warn(.not_implemented, "SubtleCrypto.sign", .{ .key_type = key._type });
- return frame.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } });
+ return exec.context.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } });
},
};
}
@@ -223,33 +225,34 @@ pub fn verify(
key: *const CryptoKey,
signature: []const u8, // ArrayBuffer.
data: []const u8, // ArrayBuffer.
- frame: *Frame,
+ exec: *const Execution,
) !js.Promise {
+ const local = exec.context.local.?;
if (!algo.isHMAC()) {
- return frame.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } });
+ return local.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } });
}
return switch (key._type) {
- .hmac => HMAC.verify(key, signature, data, frame),
- else => frame.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } }),
+ .hmac => HMAC.verify(key, signature, data, exec),
+ else => local.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } }),
};
}
/// Generates a digest of the given data, using the specified hash function.
-pub fn digest(_: *const SubtleCrypto, algo: []const u8, data: js.TypedArray(u8), frame: *Frame) !js.Promise {
- const local = frame.js.local.?;
+pub fn digest(_: *const SubtleCrypto, algo: []const u8, data: js.TypedArray(u8), exec: *const Execution) !js.Promise {
+ const local = exec.context.local.?;
if (algo.len > 10) {
return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
}
- const normalized = std.ascii.upperString(&frame.buf, algo);
+ const normalized = std.ascii.upperString(exec.buf, algo);
const digest_type = crypto.findDigest(normalized) catch {
return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
};
const bytes = data.values;
- const out = frame.buf[0..crypto.EVP_MAX_MD_SIZE];
+ const out = exec.buf[0..crypto.EVP_MAX_MD_SIZE];
var out_size: c_uint = 0;
const result = crypto.EVP_Digest(bytes.ptr, bytes.len, out, &out_size, digest_type, null);
lp.assert(result == 1, "SubtleCrypto.digest", .{ .algo = algo });
diff --git a/src/browser/webapi/WorkerGlobalScope.zig b/src/browser/webapi/WorkerGlobalScope.zig
index 1034f0e6..e9b8fe13 100644
--- a/src/browser/webapi/WorkerGlobalScope.zig
+++ b/src/browser/webapi/WorkerGlobalScope.zig
@@ -39,6 +39,7 @@ const Crypto = @import("Crypto.zig");
const Console = @import("Console.zig");
const Timers = @import("Timers.zig");
const EventTarget = @import("EventTarget.zig");
+const WorkerLocation = @import("WorkerLocation.zig");
const MessageEvent = @import("event/MessageEvent.zig");
const ErrorEvent = @import("event/ErrorEvent.zig");
const Fetch = @import("net/Fetch.zig");
@@ -98,6 +99,8 @@ _on_unhandled_rejection: ?JS.Function.Global = null,
_on_message: ?JS.Function.Global = null,
_on_messageerror: ?JS.Function.Global = null,
+_location: WorkerLocation,
+
_timers: Timers = .{},
pub fn init(worker: *Worker, url: [:0]const u8) !*WorkerGlobalScope {
@@ -125,6 +128,7 @@ pub fn init(worker: *Worker, url: [:0]const u8) !*WorkerGlobalScope {
._loader_id = worker._loader_id,
._event_manager = .init(arena),
._script_manager = undefined,
+ ._location = .{ ._url = url },
});
errdefer factory.destroy(self);
@@ -218,6 +222,10 @@ pub fn getCrypto(self: *WorkerGlobalScope) *Crypto {
return &self._crypto;
}
+pub fn getLocation(self: *WorkerGlobalScope) *WorkerLocation {
+ return &self._location;
+}
+
pub fn getOnError(self: *const WorkerGlobalScope) ?JS.Function.Global {
return self._on_error;
}
@@ -566,6 +574,7 @@ pub const JsApi = struct {
pub const self = bridge.accessor(WorkerGlobalScope.getSelf, null, .{});
pub const console = bridge.accessor(WorkerGlobalScope.getConsole, null, .{});
pub const crypto = bridge.accessor(WorkerGlobalScope.getCrypto, null, .{});
+ pub const location = bridge.accessor(WorkerGlobalScope.getLocation, null, .{});
pub const onerror = bridge.accessor(WorkerGlobalScope.getOnError, WorkerGlobalScope.setOnError, .{});
pub const onrejectionhandled = bridge.accessor(WorkerGlobalScope.getOnRejectionHandled, WorkerGlobalScope.setOnRejectionHandled, .{});
diff --git a/src/browser/webapi/WorkerLocation.zig b/src/browser/webapi/WorkerLocation.zig
new file mode 100644
index 00000000..6de8e9fd
--- /dev/null
+++ b/src/browser/webapi/WorkerLocation.zig
@@ -0,0 +1,83 @@
+// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
+//
+// Francis Bouvier
+// Pierre Tachoire
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+const js = @import("../js/js.zig");
+
+const U = @import("../URL.zig");
+
+const WorkerLocation = @This();
+
+// Workers can't navigate, so the URL is fixed for the lifetime of the worker.
+_url: [:0]const u8,
+
+pub fn getProtocol(self: *const WorkerLocation) []const u8 {
+ return U.getProtocol(self._url);
+}
+
+pub fn getHostname(self: *const WorkerLocation) []const u8 {
+ return U.getHostname(self._url);
+}
+
+pub fn getHost(self: *const WorkerLocation) []const u8 {
+ return U.getHost(self._url);
+}
+
+pub fn getPort(self: *const WorkerLocation) []const u8 {
+ return U.getPort(self._url);
+}
+
+pub fn getPathname(self: *const WorkerLocation) []const u8 {
+ return U.getPathname(self._url);
+}
+
+pub fn getSearch(self: *const WorkerLocation) []const u8 {
+ return U.getSearch(self._url);
+}
+
+pub fn getHash(self: *const WorkerLocation) []const u8 {
+ return U.getHash(self._url);
+}
+
+pub fn getOrigin(self: *const WorkerLocation, exec: *const js.Execution) ![]const u8 {
+ return (try U.getOrigin(exec.call_arena, self._url)) orelse "null";
+}
+
+pub fn toString(self: *const WorkerLocation) [:0]const u8 {
+ return self._url;
+}
+
+pub const JsApi = struct {
+ pub const bridge = js.Bridge(WorkerLocation);
+
+ pub const Meta = struct {
+ pub const name = "WorkerLocation";
+ pub const prototype_chain = bridge.prototypeChain();
+ pub var class_id: bridge.ClassId = undefined;
+ };
+
+ pub const toString = bridge.function(WorkerLocation.toString, .{});
+ pub const href = bridge.accessor(WorkerLocation.toString, null, .{});
+ pub const origin = bridge.accessor(WorkerLocation.getOrigin, null, .{});
+ pub const protocol = bridge.accessor(WorkerLocation.getProtocol, null, .{});
+ pub const host = bridge.accessor(WorkerLocation.getHost, null, .{});
+ pub const hostname = bridge.accessor(WorkerLocation.getHostname, null, .{});
+ pub const port = bridge.accessor(WorkerLocation.getPort, null, .{});
+ pub const pathname = bridge.accessor(WorkerLocation.getPathname, null, .{});
+ pub const search = bridge.accessor(WorkerLocation.getSearch, null, .{});
+ pub const hash = bridge.accessor(WorkerLocation.getHash, null, .{});
+};
diff --git a/src/browser/webapi/crypto/HMAC.zig b/src/browser/webapi/crypto/HMAC.zig
index 9d0d4335..3ab0e507 100644
--- a/src/browser/webapi/crypto/HMAC.zig
+++ b/src/browser/webapi/crypto/HMAC.zig
@@ -22,19 +22,20 @@ const std = @import("std");
const lp = @import("lightpanda");
const crypto = @import("../../../sys/libcrypto.zig");
-const Frame = @import("../../Frame.zig");
const js = @import("../../js/js.zig");
const algorithm = @import("algorithm.zig");
const CryptoKey = @import("../CryptoKey.zig");
+const Execution = js.Execution;
+
pub fn init(
params: algorithm.Init.HmacKeyGen,
extractable: bool,
key_usages: []const []const u8,
- frame: *Frame,
+ exec: *const Execution,
) !js.Promise {
- const local = frame.js.local.?;
+ const local = exec.context.local.?;
// Per spec, an unrecognized hash is caught during algorithm normalization
// and surfaces as NotSupportedError.
const digest = crypto.findDigest(switch (params.hash) {
@@ -74,14 +75,14 @@ pub fn init(
};
// Should we reject this in promise too?
- const key = try frame.arena.alloc(u8, block_size);
- errdefer frame.arena.free(key);
+ const key = try exec.arena.alloc(u8, block_size);
+ errdefer exec.arena.free(key);
// HMAC is simply CSPRNG.
const res = crypto.RAND_bytes(key.ptr, key.len);
lp.assert(res == 1, "HMAC.init", .{ .res = res });
- const crypto_key = try frame._factory.create(CryptoKey{
+ const crypto_key = try exec._factory.create(CryptoKey{
._type = .hmac,
._extractable = extractable,
._usages = mask,
@@ -96,16 +97,16 @@ pub fn sign(
algo: algorithm.Sign,
crypto_key: *const CryptoKey,
data: []const u8,
- frame: *Frame,
+ exec: *const Execution,
) !js.Promise {
- var resolver = frame.js.local.?.createPromiseResolver();
+ var resolver = exec.context.local.?.createPromiseResolver();
if (!algo.isHMAC() or !crypto_key.canSign()) {
resolver.rejectError("HMAC.sign", .{ .dom_exception = .{ .err = error.InvalidAccessError } });
return resolver.promise();
}
- const buffer = try frame.call_arena.alloc(u8, crypto.EVP_MD_size(crypto_key.getDigest()));
+ const buffer = try exec.call_arena.alloc(u8, crypto.EVP_MD_size(crypto_key.getDigest()));
var out_len: u32 = 0;
// Try to sign.
_ = crypto.HMAC(
@@ -117,7 +118,7 @@ pub fn sign(
buffer.ptr,
&out_len,
) orelse {
- frame.call_arena.free(buffer);
+ exec.call_arena.free(buffer);
// Failure.
resolver.rejectError("HMAC.sign", .{ .dom_exception = .{ .err = error.InvalidAccessError } });
return resolver.promise();
@@ -132,9 +133,9 @@ pub fn verify(
crypto_key: *const CryptoKey,
signature: []const u8,
data: []const u8,
- frame: *Frame,
+ exec: *const Execution,
) !js.Promise {
- var resolver = frame.js.local.?.createPromiseResolver();
+ var resolver = exec.context.local.?.createPromiseResolver();
if (!crypto_key.canVerify()) {
resolver.rejectError("HMAC.verify", .{ .dom_exception = .{ .err = error.InvalidAccessError } });
diff --git a/src/browser/webapi/crypto/X25519.zig b/src/browser/webapi/crypto/X25519.zig
index 968d1833..aaa1798c 100644
--- a/src/browser/webapi/crypto/X25519.zig
+++ b/src/browser/webapi/crypto/X25519.zig
@@ -22,21 +22,22 @@ const std = @import("std");
const lp = @import("lightpanda");
const crypto = @import("../../../sys/libcrypto.zig");
-const Frame = @import("../../Frame.zig");
const js = @import("../../js/js.zig");
const CryptoKey = @import("../CryptoKey.zig");
+const Execution = js.Execution;
+
pub fn init(
extractable: bool,
key_usages: []const []const u8,
- frame: *Frame,
+ exec: *const Execution,
) !js.Promise {
// This code has too many allocations here and there, might be nice to
// gather them together with a single alloc call. Not sure if factory
// pattern is suitable for it though.
- const local = frame.js.local.?;
+ const local = exec.context.local.?;
// Calculate usages; only matters for private key.
// Only deriveKey() and deriveBits() be used for X25519.
@@ -59,11 +60,11 @@ pub fn init(
});
}
- const public_value = try frame.arena.alloc(u8, crypto.X25519_PUBLIC_VALUE_LEN);
- errdefer frame.arena.free(public_value);
+ const public_value = try exec.arena.alloc(u8, crypto.X25519_PUBLIC_VALUE_LEN);
+ errdefer exec.arena.free(public_value);
- const private_key = try frame.arena.alloc(u8, crypto.X25519_PRIVATE_KEY_LEN);
- errdefer frame.arena.free(private_key);
+ const private_key = try exec.arena.alloc(u8, crypto.X25519_PRIVATE_KEY_LEN);
+ errdefer exec.arena.free(private_key);
// There's no info about whether this can fail; so I assume it cannot.
crypto.X25519_keypair(@ptrCast(public_value), @ptrCast(private_key));
@@ -90,16 +91,16 @@ pub fn init(
private_key.len,
) orelse return error.OutOfMemory;
- const private = try frame._factory.create(CryptoKey{
+ const private = try exec._factory.create(CryptoKey{
._type = .x25519,
._extractable = extractable,
._usages = mask,
._key = private_key,
._vary = .{ .pkey = private_pkey },
});
- errdefer frame._factory.destroy(private);
+ errdefer exec._factory.destroy(private);
- const public = try frame._factory.create(CryptoKey{
+ const public = try exec._factory.create(CryptoKey{
._type = .x25519,
// Public keys are always extractable.
._extractable = true,
@@ -116,7 +117,7 @@ pub fn deriveBits(
private: *const CryptoKey,
public: *const CryptoKey,
length_in_bits: usize,
- frame: *Frame,
+ exec: *const Execution,
) !js.ArrayBuffer {
if (!private.canDeriveBits()) {
return error.InvalidAccessError;
@@ -137,8 +138,8 @@ pub fn deriveBits(
return error.Internal;
}
- const derived_key = try frame.call_arena.alloc(u8, 32);
- errdefer frame.call_arena.free(derived_key);
+ const derived_key = try exec.call_arena.alloc(u8, 32);
+ errdefer exec.call_arena.free(derived_key);
var out_key_len: usize = derived_key.len;
const result = crypto.EVP_PKEY_derive(ctx, derived_key.ptr, &out_key_len);
diff --git a/src/cdp/domains/page.zig b/src/cdp/domains/page.zig
index 65068028..b714da1e 100644
--- a/src/cdp/domains/page.zig
+++ b/src/cdp/domains/page.zig
@@ -301,6 +301,20 @@ fn navigate(cmd: *CDP.Command) !void {
const frame = session.currentFrame() orelse return error.FrameNotLoaded;
const encoded_url = try URL.ensureEncoded(frame.call_arena, params.url, "UTF-8");
+
+ // Fast path: a freshly-created target whose root frame hasn't navigated
+ // yet has nothing to preserve across the HTTP round-trip. Skip the
+ // pending-Page allocation (which would create a V8 context just to
+ // throw the OLD blank one away at commit) and navigate the active
+ // frame in place.
+ if (frame._load_state == .waiting) {
+ return frame.navigate(encoded_url, .{
+ .reason = .address_bar,
+ .cdp_id = cmd.input.id,
+ .kind = .{ .push = null },
+ });
+ }
+
try session.initiateRootNavigation(frame._frame_id, encoded_url, .{
.reason = .address_bar,
.cdp_id = cmd.input.id,