mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
Merge branch 'main' into css-improvements
This commit is contained in:
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.2'
|
default: 'v0.3.3'
|
||||||
v8:
|
v8:
|
||||||
description: 'v8 version to install'
|
description: 'v8 version to install'
|
||||||
required: false
|
required: false
|
||||||
|
|||||||
@@ -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.2
|
ARG ZIG_V8=v0.3.3
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
RUN apt-get update -yq && \
|
RUN apt-get update -yq && \
|
||||||
|
|||||||
@@ -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.2.tar.gz",
|
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/refs/tags/v0.3.3.tar.gz",
|
||||||
.hash = "v8-0.0.0-xddH6wx-BABNgL7YIDgbnFgKZuXZ68yZNngNSrV6OjrY",
|
.hash = "v8-0.0.0-xddH6yx3BAAGD9jSoq_ttt_bk9MectTU44s_HZxxE5LD",
|
||||||
},
|
},
|
||||||
// .v8 = .{ .path = "../zig-v8-fork" },
|
// .v8 = .{ .path = "../zig-v8-fork" },
|
||||||
.brotli = .{
|
.brotli = .{
|
||||||
|
|||||||
@@ -265,13 +265,15 @@ pub fn blob(_: *const Factory, arena: Allocator, child: anytype) !*@TypeOf(child
|
|||||||
return chain.get(1);
|
return chain.get(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn abstractRange(self: *Factory, child: anytype, page: *Page) !*@TypeOf(child) {
|
pub fn abstractRange(_: *const Factory, arena: Allocator, child: anytype, page: *Page) !*@TypeOf(child) {
|
||||||
const allocator = self._slab.allocator();
|
const chain = try PrototypeChain(&.{ AbstractRange, @TypeOf(child) }).allocate(arena);
|
||||||
const chain = try PrototypeChain(&.{ AbstractRange, @TypeOf(child) }).allocate(allocator);
|
|
||||||
|
|
||||||
const doc = page.document.asNode();
|
const doc = page.document.asNode();
|
||||||
const abstract_range = chain.get(0);
|
const abstract_range = chain.get(0);
|
||||||
abstract_range.* = AbstractRange{
|
abstract_range.* = AbstractRange{
|
||||||
|
._rc = 0,
|
||||||
|
._arena = arena,
|
||||||
|
._page_id = page.id,
|
||||||
._type = unionInit(AbstractRange.Type, chain.get(1)),
|
._type = unionInit(AbstractRange.Type, chain.get(1)),
|
||||||
._end_offset = 0,
|
._end_offset = 0,
|
||||||
._start_offset = 0,
|
._start_offset = 0,
|
||||||
|
|||||||
@@ -66,9 +66,18 @@ active: usize,
|
|||||||
// 'networkAlmostIdle' Page.lifecycleEvent in CDP).
|
// 'networkAlmostIdle' Page.lifecycleEvent in CDP).
|
||||||
intercepted: usize,
|
intercepted: usize,
|
||||||
|
|
||||||
// Our easy handles, managed by a curl multi.
|
// Our curl multi handle.
|
||||||
handles: Net.Handles,
|
handles: Net.Handles,
|
||||||
|
|
||||||
|
// Connections currently in this client's curl_multi.
|
||||||
|
in_use: std.DoublyLinkedList = .{},
|
||||||
|
|
||||||
|
// Connections that failed to be removed from curl_multi during perform.
|
||||||
|
dirty: std.DoublyLinkedList = .{},
|
||||||
|
|
||||||
|
// Whether we're currently inside a curl_multi_perform call.
|
||||||
|
performing: bool = false,
|
||||||
|
|
||||||
// Use to generate the next request ID
|
// Use to generate the next request ID
|
||||||
next_request_id: u32 = 0,
|
next_request_id: u32 = 0,
|
||||||
|
|
||||||
@@ -88,8 +97,8 @@ pending_robots_queue: std.StringHashMapUnmanaged(std.ArrayList(Request)) = .empt
|
|||||||
// request. These wil come and go with each request.
|
// request. These wil come and go with each request.
|
||||||
transfer_pool: std.heap.MemoryPool(Transfer),
|
transfer_pool: std.heap.MemoryPool(Transfer),
|
||||||
|
|
||||||
// only needed for CDP which can change the proxy and then restore it. When
|
// The current proxy. CDP can change it, restoreOriginalProxy restores
|
||||||
// restoring, this originally-configured value is what it goes to.
|
// from config.
|
||||||
http_proxy: ?[:0]const u8 = null,
|
http_proxy: ?[:0]const u8 = null,
|
||||||
|
|
||||||
// track if the client use a proxy for connections.
|
// track if the client use a proxy for connections.
|
||||||
@@ -97,6 +106,9 @@ http_proxy: ?[:0]const u8 = null,
|
|||||||
// CDP.
|
// CDP.
|
||||||
use_proxy: bool,
|
use_proxy: bool,
|
||||||
|
|
||||||
|
// Current TLS verification state, applied per-connection in makeRequest.
|
||||||
|
tls_verify: bool = true,
|
||||||
|
|
||||||
cdp_client: ?CDPClient = null,
|
cdp_client: ?CDPClient = null,
|
||||||
|
|
||||||
// libcurl can monitor arbitrary sockets, this lets us use libcurl to poll
|
// libcurl can monitor arbitrary sockets, this lets us use libcurl to poll
|
||||||
@@ -126,13 +138,8 @@ pub fn init(allocator: Allocator, network: *Network) !*Client {
|
|||||||
const client = try allocator.create(Client);
|
const client = try allocator.create(Client);
|
||||||
errdefer allocator.destroy(client);
|
errdefer allocator.destroy(client);
|
||||||
|
|
||||||
var handles = try Net.Handles.init(allocator, network.ca_blob, network.config);
|
var handles = try Net.Handles.init(network.config);
|
||||||
errdefer handles.deinit(allocator);
|
errdefer handles.deinit();
|
||||||
|
|
||||||
// Set transfer callbacks on each connection.
|
|
||||||
for (handles.connections) |*conn| {
|
|
||||||
try conn.setCallbacks(Transfer.headerCallback, Transfer.dataCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
const http_proxy = network.config.httpProxy();
|
const http_proxy = network.config.httpProxy();
|
||||||
|
|
||||||
@@ -145,6 +152,7 @@ pub fn init(allocator: Allocator, network: *Network) !*Client {
|
|||||||
.network = network,
|
.network = network,
|
||||||
.http_proxy = http_proxy,
|
.http_proxy = http_proxy,
|
||||||
.use_proxy = http_proxy != null,
|
.use_proxy = http_proxy != null,
|
||||||
|
.tls_verify = network.config.tlsVerifyHost(),
|
||||||
.transfer_pool = transfer_pool,
|
.transfer_pool = transfer_pool,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -153,7 +161,7 @@ pub fn init(allocator: Allocator, network: *Network) !*Client {
|
|||||||
|
|
||||||
pub fn deinit(self: *Client) void {
|
pub fn deinit(self: *Client) void {
|
||||||
self.abort();
|
self.abort();
|
||||||
self.handles.deinit(self.allocator);
|
self.handles.deinit();
|
||||||
|
|
||||||
self.transfer_pool.deinit();
|
self.transfer_pool.deinit();
|
||||||
|
|
||||||
@@ -182,14 +190,14 @@ pub fn abortFrame(self: *Client, frame_id: u32) void {
|
|||||||
// but abort can avoid the frame_id check at comptime.
|
// but abort can avoid the frame_id check at comptime.
|
||||||
fn _abort(self: *Client, comptime abort_all: bool, frame_id: u32) void {
|
fn _abort(self: *Client, comptime abort_all: bool, frame_id: u32) void {
|
||||||
{
|
{
|
||||||
var q = &self.handles.in_use;
|
var q = &self.in_use;
|
||||||
var n = q.first;
|
var n = q.first;
|
||||||
while (n) |node| {
|
while (n) |node| {
|
||||||
n = node.next;
|
n = node.next;
|
||||||
const conn: *Net.Connection = @fieldParentPtr("node", node);
|
const conn: *Net.Connection = @fieldParentPtr("node", node);
|
||||||
var transfer = Transfer.fromConnection(conn) catch |err| {
|
var transfer = Transfer.fromConnection(conn) catch |err| {
|
||||||
// Let's cleanup what we can
|
// Let's cleanup what we can
|
||||||
self.handles.remove(conn);
|
self.removeConn(conn);
|
||||||
log.err(.http, "get private info", .{ .err = err, .source = "abort" });
|
log.err(.http, "get private info", .{ .err = err, .source = "abort" });
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
@@ -226,8 +234,7 @@ fn _abort(self: *Client, comptime abort_all: bool, frame_id: u32) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (comptime IS_DEBUG and abort_all) {
|
if (comptime IS_DEBUG and abort_all) {
|
||||||
std.debug.assert(self.handles.in_use.first == null);
|
std.debug.assert(self.in_use.first == null);
|
||||||
std.debug.assert(self.handles.available.len() == self.handles.connections.len);
|
|
||||||
|
|
||||||
const running = self.handles.perform() catch |err| {
|
const running = self.handles.perform() catch |err| {
|
||||||
lp.assert(false, "multi perform in abort", .{ .err = err });
|
lp.assert(false, "multi perform in abort", .{ .err = err });
|
||||||
@@ -237,15 +244,12 @@ fn _abort(self: *Client, comptime abort_all: bool, frame_id: u32) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(self: *Client, timeout_ms: u32) !PerformStatus {
|
pub fn tick(self: *Client, timeout_ms: u32) !PerformStatus {
|
||||||
while (true) {
|
while (self.queue.popFirst()) |queue_node| {
|
||||||
if (self.handles.hasAvailable() == false) {
|
const conn = self.network.getConnection() orelse {
|
||||||
|
self.queue.prepend(queue_node);
|
||||||
break;
|
break;
|
||||||
}
|
};
|
||||||
const queue_node = self.queue.popFirst() orelse break;
|
|
||||||
const transfer: *Transfer = @fieldParentPtr("_node", queue_node);
|
const transfer: *Transfer = @fieldParentPtr("_node", queue_node);
|
||||||
|
|
||||||
// we know this exists, because we checked hasAvailable() above
|
|
||||||
const conn = self.handles.get().?;
|
|
||||||
try self.makeRequest(conn, transfer);
|
try self.makeRequest(conn, transfer);
|
||||||
}
|
}
|
||||||
return self.perform(@intCast(timeout_ms));
|
return self.perform(@intCast(timeout_ms));
|
||||||
@@ -529,8 +533,8 @@ fn waitForInterceptedResponse(self: *Client, transfer: *Transfer) !bool {
|
|||||||
fn process(self: *Client, transfer: *Transfer) !void {
|
fn process(self: *Client, transfer: *Transfer) !void {
|
||||||
// libcurl doesn't allow recursive calls, if we're in a `perform()` operation
|
// libcurl doesn't allow recursive calls, if we're in a `perform()` operation
|
||||||
// then we _have_ to queue this.
|
// then we _have_ to queue this.
|
||||||
if (self.handles.performing == false) {
|
if (self.performing == false) {
|
||||||
if (self.handles.get()) |conn| {
|
if (self.network.getConnection()) |conn| {
|
||||||
return self.makeRequest(conn, transfer);
|
return self.makeRequest(conn, transfer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -644,10 +648,7 @@ fn requestFailed(transfer: *Transfer, err: anyerror, comptime execute_callback:
|
|||||||
// can be changed at any point in the easy's lifecycle.
|
// can be changed at any point in the easy's lifecycle.
|
||||||
pub fn changeProxy(self: *Client, proxy: [:0]const u8) !void {
|
pub fn changeProxy(self: *Client, proxy: [:0]const u8) !void {
|
||||||
try self.ensureNoActiveConnection();
|
try self.ensureNoActiveConnection();
|
||||||
|
self.http_proxy = proxy;
|
||||||
for (self.handles.connections) |*conn| {
|
|
||||||
try conn.setProxy(proxy.ptr);
|
|
||||||
}
|
|
||||||
self.use_proxy = true;
|
self.use_proxy = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -656,31 +657,21 @@ pub fn changeProxy(self: *Client, proxy: [:0]const u8) !void {
|
|||||||
pub fn restoreOriginalProxy(self: *Client) !void {
|
pub fn restoreOriginalProxy(self: *Client) !void {
|
||||||
try self.ensureNoActiveConnection();
|
try self.ensureNoActiveConnection();
|
||||||
|
|
||||||
const proxy = if (self.http_proxy) |p| p.ptr else null;
|
self.http_proxy = self.network.config.httpProxy();
|
||||||
for (self.handles.connections) |*conn| {
|
self.use_proxy = self.http_proxy != null;
|
||||||
try conn.setProxy(proxy);
|
|
||||||
}
|
|
||||||
self.use_proxy = proxy != null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable TLS verification on all connections.
|
// Enable TLS verification on all connections.
|
||||||
pub fn enableTlsVerify(self: *Client) !void {
|
pub fn setTlsVerify(self: *Client, verify: bool) !void {
|
||||||
// Remove inflight connections check on enable TLS b/c chromiumoxide calls
|
// Remove inflight connections check on enable TLS b/c chromiumoxide calls
|
||||||
// the command during navigate and Curl seems to accept it...
|
// the command during navigate and Curl seems to accept it...
|
||||||
|
|
||||||
for (self.handles.connections) |*conn| {
|
var it = self.in_use.first;
|
||||||
try conn.setTlsVerify(true, self.use_proxy);
|
while (it) |node| : (it = node.next) {
|
||||||
}
|
const conn: *Net.Connection = @fieldParentPtr("node", node);
|
||||||
}
|
try conn.setTlsVerify(verify, self.use_proxy);
|
||||||
|
|
||||||
// Disable TLS verification on all connections.
|
|
||||||
pub fn disableTlsVerify(self: *Client) !void {
|
|
||||||
// Remove inflight connections check on disable TLS b/c chromiumoxide calls
|
|
||||||
// the command during navigate and Curl seems to accept it...
|
|
||||||
|
|
||||||
for (self.handles.connections) |*conn| {
|
|
||||||
try conn.setTlsVerify(false, self.use_proxy);
|
|
||||||
}
|
}
|
||||||
|
self.tls_verify = verify;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn makeRequest(self: *Client, conn: *Net.Connection, transfer: *Transfer) anyerror!void {
|
fn makeRequest(self: *Client, conn: *Net.Connection, transfer: *Transfer) anyerror!void {
|
||||||
@@ -691,9 +682,14 @@ fn makeRequest(self: *Client, conn: *Net.Connection, transfer: *Transfer) anyerr
|
|||||||
errdefer {
|
errdefer {
|
||||||
transfer._conn = null;
|
transfer._conn = null;
|
||||||
transfer.deinit();
|
transfer.deinit();
|
||||||
self.handles.isAvailable(conn);
|
self.releaseConn(conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set callbacks and per-client settings on the pooled connection.
|
||||||
|
try conn.setCallbacks(Transfer.headerCallback, Transfer.dataCallback);
|
||||||
|
try conn.setProxy(self.http_proxy);
|
||||||
|
try conn.setTlsVerify(self.tls_verify, self.use_proxy);
|
||||||
|
|
||||||
try conn.setURL(req.url);
|
try conn.setURL(req.url);
|
||||||
try conn.setMethod(req.method);
|
try conn.setMethod(req.method);
|
||||||
if (req.body) |b| {
|
if (req.body) |b| {
|
||||||
@@ -728,10 +724,12 @@ fn makeRequest(self: *Client, conn: *Net.Connection, transfer: *Transfer) anyerr
|
|||||||
// fails BEFORE `curl_multi_add_handle` succeeds, the we still need to do
|
// fails BEFORE `curl_multi_add_handle` succeeds, the we still need to do
|
||||||
// cleanup. But if things fail after `curl_multi_add_handle`, we expect
|
// cleanup. But if things fail after `curl_multi_add_handle`, we expect
|
||||||
// perfom to pickup the failure and cleanup.
|
// perfom to pickup the failure and cleanup.
|
||||||
|
self.in_use.append(&conn.node);
|
||||||
self.handles.add(conn) catch |err| {
|
self.handles.add(conn) catch |err| {
|
||||||
transfer._conn = null;
|
transfer._conn = null;
|
||||||
transfer.deinit();
|
transfer.deinit();
|
||||||
self.handles.isAvailable(conn);
|
self.in_use.remove(&conn.node);
|
||||||
|
self.releaseConn(conn);
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -752,7 +750,22 @@ pub const PerformStatus = enum {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fn perform(self: *Client, timeout_ms: c_int) !PerformStatus {
|
fn perform(self: *Client, timeout_ms: c_int) !PerformStatus {
|
||||||
const running = try self.handles.perform();
|
const running = blk: {
|
||||||
|
self.performing = true;
|
||||||
|
defer self.performing = false;
|
||||||
|
|
||||||
|
break :blk try self.handles.perform();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Process dirty connections — return them to Runtime pool.
|
||||||
|
while (self.dirty.popFirst()) |node| {
|
||||||
|
const conn: *Net.Connection = @fieldParentPtr("node", node);
|
||||||
|
self.handles.remove(conn) catch |err| {
|
||||||
|
log.fatal(.http, "multi remove handle", .{ .err = err, .src = "perform" });
|
||||||
|
@panic("multi_remove_handle");
|
||||||
|
};
|
||||||
|
self.releaseConn(conn);
|
||||||
|
}
|
||||||
|
|
||||||
// We're potentially going to block for a while until we get data. Process
|
// We're potentially going to block for a while until we get data. Process
|
||||||
// whatever messages we have waiting ahead of time.
|
// whatever messages we have waiting ahead of time.
|
||||||
@@ -871,11 +884,26 @@ fn processMessages(self: *Client) !bool {
|
|||||||
|
|
||||||
fn endTransfer(self: *Client, transfer: *Transfer) void {
|
fn endTransfer(self: *Client, transfer: *Transfer) void {
|
||||||
const conn = transfer._conn.?;
|
const conn = transfer._conn.?;
|
||||||
self.handles.remove(conn);
|
self.removeConn(conn);
|
||||||
transfer._conn = null;
|
transfer._conn = null;
|
||||||
self.active -= 1;
|
self.active -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn removeConn(self: *Client, conn: *Net.Connection) void {
|
||||||
|
self.in_use.remove(&conn.node);
|
||||||
|
if (self.handles.remove(conn)) {
|
||||||
|
self.releaseConn(conn);
|
||||||
|
} else |_| {
|
||||||
|
// Can happen if we're in a perform() call, so we'll queue this
|
||||||
|
// for cleanup later.
|
||||||
|
self.dirty.append(&conn.node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn releaseConn(self: *Client, conn: *Net.Connection) void {
|
||||||
|
self.network.releaseConnection(conn);
|
||||||
|
}
|
||||||
|
|
||||||
fn ensureNoActiveConnection(self: *const Client) !void {
|
fn ensureNoActiveConnection(self: *const Client) !void {
|
||||||
if (self.active > 0) {
|
if (self.active > 0) {
|
||||||
return error.InflightConnection;
|
return error.InflightConnection;
|
||||||
@@ -1023,7 +1051,7 @@ pub const Transfer = struct {
|
|||||||
fn deinit(self: *Transfer) void {
|
fn deinit(self: *Transfer) void {
|
||||||
self.req.headers.deinit();
|
self.req.headers.deinit();
|
||||||
if (self._conn) |conn| {
|
if (self._conn) |conn| {
|
||||||
self.client.handles.remove(conn);
|
self.client.removeConn(conn);
|
||||||
}
|
}
|
||||||
self.arena.deinit();
|
self.arena.deinit();
|
||||||
self.client.transfer_pool.destroy(self);
|
self.client.transfer_pool.destroy(self);
|
||||||
@@ -1093,7 +1121,7 @@ pub const Transfer = struct {
|
|||||||
requestFailed(self, err, true);
|
requestFailed(self, err, true);
|
||||||
|
|
||||||
const client = self.client;
|
const client = self.client;
|
||||||
if (self._performing or client.handles.performing) {
|
if (self._performing or client.performing) {
|
||||||
// We're currently in a curl_multi_perform. We cannot call endTransfer
|
// We're currently in a curl_multi_perform. We cannot call endTransfer
|
||||||
// as that calls curl_multi_remove_handle, and you can't do that
|
// as that calls curl_multi_remove_handle, and you can't do that
|
||||||
// from a curl callback. Instead, we flag this transfer and all of
|
// from a curl callback. Instead, we flag this transfer and all of
|
||||||
@@ -1258,6 +1286,16 @@ pub const Transfer = struct {
|
|||||||
|
|
||||||
if (buf_len < 3) {
|
if (buf_len < 3) {
|
||||||
// could be \r\n or \n.
|
// could be \r\n or \n.
|
||||||
|
// We get the last header line.
|
||||||
|
if (transfer._redirecting) {
|
||||||
|
// parse and set cookies for the redirection.
|
||||||
|
redirectionCookies(transfer, &conn) catch |err| {
|
||||||
|
if (comptime IS_DEBUG) {
|
||||||
|
log.debug(.http, "redirection cookies", .{ .err = err });
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
return buf_len;
|
return buf_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1324,7 +1362,6 @@ pub const Transfer = struct {
|
|||||||
transfer.bytes_received += buf_len;
|
transfer.bytes_received += buf_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buf_len > 2) {
|
|
||||||
if (transfer._auth_challenge != null) {
|
if (transfer._auth_challenge != null) {
|
||||||
// try to parse auth challenge.
|
// try to parse auth challenge.
|
||||||
if (std.ascii.startsWithIgnoreCase(header, "WWW-Authenticate") or
|
if (std.ascii.startsWithIgnoreCase(header, "WWW-Authenticate") or
|
||||||
@@ -1342,21 +1379,6 @@ pub const Transfer = struct {
|
|||||||
transfer._auth_challenge = ac;
|
transfer._auth_challenge = ac;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return buf_len;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Starting here, we get the last header line.
|
|
||||||
|
|
||||||
if (transfer._redirecting) {
|
|
||||||
// parse and set cookies for the redirection.
|
|
||||||
redirectionCookies(transfer, &conn) catch |err| {
|
|
||||||
if (comptime IS_DEBUG) {
|
|
||||||
log.debug(.http, "redirection cookies", .{ .err = err });
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
return buf_len;
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf_len;
|
return buf_len;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,6 +80,8 @@ pub const BUF_SIZE = 1024;
|
|||||||
|
|
||||||
const Page = @This();
|
const Page = @This();
|
||||||
|
|
||||||
|
id: u32,
|
||||||
|
|
||||||
// This is the "id" of the frame. It can be re-used from page-to-page, e.g.
|
// This is the "id" of the frame. It can be re-used from page-to-page, e.g.
|
||||||
// when navigating.
|
// when navigating.
|
||||||
_frame_id: u32,
|
_frame_id: u32,
|
||||||
@@ -254,6 +256,7 @@ pub fn init(self: *Page, frame_id: u32, session: *Session, parent: ?*Page) !void
|
|||||||
})).asDocument();
|
})).asDocument();
|
||||||
|
|
||||||
self.* = .{
|
self.* = .{
|
||||||
|
.id = session.nextPageId(),
|
||||||
.js = undefined,
|
.js = undefined,
|
||||||
.parent = parent,
|
.parent = parent,
|
||||||
.arena = session.page_arena,
|
.arena = session.page_arena,
|
||||||
@@ -404,6 +407,18 @@ pub fn isSameOrigin(self: *const Page, url: [:0]const u8) !bool {
|
|||||||
return std.mem.startsWith(u8, url, current_origin);
|
return std.mem.startsWith(u8, url, current_origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Look up a blob URL in this page's registry, walking up the parent chain.
|
||||||
|
pub fn lookupBlobUrl(self: *Page, url: []const u8) ?*Blob {
|
||||||
|
var current: ?*Page = self;
|
||||||
|
while (current) |page| {
|
||||||
|
if (page._blob_urls.get(url)) |blob| {
|
||||||
|
return blob;
|
||||||
|
}
|
||||||
|
current = page.parent;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !void {
|
pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !void {
|
||||||
lp.assert(self._load_state == .waiting, "page.renavigate", .{});
|
lp.assert(self._load_state == .waiting, "page.renavigate", .{});
|
||||||
const session = self._session;
|
const session = self._session;
|
||||||
@@ -419,12 +434,17 @@ pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !voi
|
|||||||
.type = self._type,
|
.type = self._type,
|
||||||
});
|
});
|
||||||
|
|
||||||
// if the url is about:blank, we load an empty HTML document in the
|
// Handle synthetic navigations: about:blank and blob: URLs
|
||||||
// page and dispatch the events.
|
const is_about_blank = std.mem.eql(u8, "about:blank", request_url);
|
||||||
if (std.mem.eql(u8, "about:blank", request_url)) {
|
const is_blob = !is_about_blank and std.mem.startsWith(u8, request_url, "blob:");
|
||||||
self.url = "about:blank";
|
|
||||||
|
|
||||||
if (self.parent) |parent| {
|
if (is_about_blank or is_blob) {
|
||||||
|
self.url = if (is_about_blank) "about:blank" else try self.arena.dupeZ(u8, request_url);
|
||||||
|
|
||||||
|
if (is_blob) {
|
||||||
|
// strip out blob:
|
||||||
|
self.origin = try URL.getOrigin(self.arena, request_url[5.. :0]);
|
||||||
|
} else if (self.parent) |parent| {
|
||||||
self.origin = parent.origin;
|
self.origin = parent.origin;
|
||||||
} else {
|
} else {
|
||||||
self.origin = null;
|
self.origin = null;
|
||||||
@@ -435,10 +455,22 @@ pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !voi
|
|||||||
// It's important to force a reset during the following navigation.
|
// It's important to force a reset during the following navigation.
|
||||||
self._parse_state = .complete;
|
self._parse_state = .complete;
|
||||||
|
|
||||||
|
// Content injection
|
||||||
|
if (is_blob) {
|
||||||
|
const blob = self.lookupBlobUrl(request_url) orelse {
|
||||||
|
log.warn(.js, "invalid blob", .{ .url = request_url });
|
||||||
|
return error.BlobNotFound;
|
||||||
|
};
|
||||||
|
const parse_arena = try self.getArena(.{ .debug = "Page.parseBlob" });
|
||||||
|
defer self.releaseArena(parse_arena);
|
||||||
|
var parser = Parser.init(parse_arena, self.document.asNode(), self);
|
||||||
|
parser.parse(blob._slice);
|
||||||
|
} else {
|
||||||
self.document.injectBlank(self) catch |err| {
|
self.document.injectBlank(self) catch |err| {
|
||||||
log.err(.browser, "inject blank", .{ .err = err });
|
log.err(.browser, "inject blank", .{ .err = err });
|
||||||
return error.InjectBlankFailed;
|
return error.InjectBlankFailed;
|
||||||
};
|
};
|
||||||
|
}
|
||||||
self.documentIsComplete();
|
self.documentIsComplete();
|
||||||
|
|
||||||
session.notification.dispatch(.page_navigate, &.{
|
session.notification.dispatch(.page_navigate, &.{
|
||||||
@@ -452,7 +484,7 @@ pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !voi
|
|||||||
// Record telemetry for navigation
|
// Record telemetry for navigation
|
||||||
session.browser.app.telemetry.record(.{
|
session.browser.app.telemetry.record(.{
|
||||||
.navigate = .{
|
.navigate = .{
|
||||||
.tls = false, // about:blank is not TLS
|
.tls = false, // about:blank and blob: are not TLS
|
||||||
.proxy = session.browser.app.config.httpProxy() != null,
|
.proxy = session.browser.app.config.httpProxy() != null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ queued_navigation: std.ArrayList(*Page),
|
|||||||
// about:blank navigations (which may add to queued_navigation).
|
// about:blank navigations (which may add to queued_navigation).
|
||||||
queued_queued_navigation: std.ArrayList(*Page),
|
queued_queued_navigation: std.ArrayList(*Page),
|
||||||
|
|
||||||
|
page_id_gen: u32,
|
||||||
frame_id_gen: u32,
|
frame_id_gen: u32,
|
||||||
|
|
||||||
pub fn init(self: *Session, browser: *Browser, notification: *Notification) !void {
|
pub fn init(self: *Session, browser: *Browser, notification: *Notification) !void {
|
||||||
@@ -103,6 +104,7 @@ pub fn init(self: *Session, browser: *Browser, notification: *Notification) !voi
|
|||||||
.page_arena = page_arena,
|
.page_arena = page_arena,
|
||||||
.factory = Factory.init(page_arena),
|
.factory = Factory.init(page_arena),
|
||||||
.history = .{},
|
.history = .{},
|
||||||
|
.page_id_gen = 0,
|
||||||
.frame_id_gen = 0,
|
.frame_id_gen = 0,
|
||||||
// The prototype (EventTarget) for Navigation is created when a Page is created.
|
// The prototype (EventTarget) for Navigation is created when a Page is created.
|
||||||
.navigation = .{ ._proto = undefined },
|
.navigation = .{ ._proto = undefined },
|
||||||
@@ -297,9 +299,24 @@ pub const WaitResult = enum {
|
|||||||
cdp_socket,
|
cdp_socket,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn findPage(self: *Session, frame_id: u32) ?*Page {
|
pub fn findPageByFrameId(self: *Session, frame_id: u32) ?*Page {
|
||||||
const page = self.currentPage() orelse return null;
|
const page = self.currentPage() orelse return null;
|
||||||
return if (page._frame_id == frame_id) page else null;
|
return findPageBy(page, "_frame_id", frame_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn findPageById(self: *Session, id: u32) ?*Page {
|
||||||
|
const page = self.currentPage() orelse return null;
|
||||||
|
return findPageBy(page, "id", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn findPageBy(page: *Page, comptime field: []const u8, id: u32) ?*Page {
|
||||||
|
if (@field(page, field) == id) return page;
|
||||||
|
for (page.frames.items) |f| {
|
||||||
|
if (findPageBy(f, field, id)) |found| {
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wait(self: *Session, wait_ms: u32) WaitResult {
|
pub fn wait(self: *Session, wait_ms: u32) WaitResult {
|
||||||
@@ -636,3 +653,9 @@ pub fn nextFrameId(self: *Session) u32 {
|
|||||||
self.frame_id_gen = id;
|
self.frame_id_gen = id;
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn nextPageId(self: *Session) u32 {
|
||||||
|
const id = self.page_id_gen +% 1;
|
||||||
|
self.page_id_gen = id;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|||||||
@@ -277,6 +277,11 @@ pub fn isCompleteHTTPUrl(url: []const u8) bool {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// blob: and data: URLs are complete but don't follow scheme:// pattern
|
||||||
|
if (std.mem.startsWith(u8, url, "blob:") or std.mem.startsWith(u8, url, "data:")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if there's a scheme (protocol) ending with ://
|
// Check if there's a scheme (protocol) ending with ://
|
||||||
const colon_pos = std.mem.indexOfScalar(u8, url, ':') orelse return false;
|
const colon_pos = std.mem.indexOfScalar(u8, url, ':') orelse return false;
|
||||||
|
|
||||||
|
|||||||
@@ -89,6 +89,41 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script id="CanvasRenderingContext2D#getImageData">
|
||||||
|
{
|
||||||
|
const element = document.createElement("canvas");
|
||||||
|
element.width = 100;
|
||||||
|
element.height = 50;
|
||||||
|
const ctx = element.getContext("2d");
|
||||||
|
|
||||||
|
const imageData = ctx.getImageData(0, 0, 10, 20);
|
||||||
|
testing.expectEqual(true, imageData instanceof ImageData);
|
||||||
|
testing.expectEqual(imageData.width, 10);
|
||||||
|
testing.expectEqual(imageData.height, 20);
|
||||||
|
testing.expectEqual(imageData.data.length, 10 * 20 * 4);
|
||||||
|
testing.expectEqual(true, imageData.data instanceof Uint8ClampedArray);
|
||||||
|
|
||||||
|
// Undrawn canvas should return transparent black pixels.
|
||||||
|
testing.expectEqual(imageData.data[0], 0);
|
||||||
|
testing.expectEqual(imageData.data[1], 0);
|
||||||
|
testing.expectEqual(imageData.data[2], 0);
|
||||||
|
testing.expectEqual(imageData.data[3], 0);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="CanvasRenderingContext2D#getImageData invalid">
|
||||||
|
{
|
||||||
|
const element = document.createElement("canvas");
|
||||||
|
const ctx = element.getContext("2d");
|
||||||
|
|
||||||
|
// Zero or negative width/height should throw IndexSizeError.
|
||||||
|
testing.expectError('Index or size', () => ctx.getImageData(0, 0, 0, 10));
|
||||||
|
testing.expectError('Index or size', () => ctx.getImageData(0, 0, 10, 0));
|
||||||
|
testing.expectError('Index or size', () => ctx.getImageData(0, 0, -5, 10));
|
||||||
|
testing.expectError('Index or size', () => ctx.getImageData(0, 0, 10, -5));
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<script id="getter">
|
<script id="getter">
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -62,3 +62,26 @@
|
|||||||
testing.expectEqual(offscreen.height, 96);
|
testing.expectEqual(offscreen.height, 96);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script id=OffscreenCanvasRenderingContext2D#getImageData>
|
||||||
|
{
|
||||||
|
const canvas = new OffscreenCanvas(100, 50);
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
|
const imageData = ctx.getImageData(0, 0, 10, 20);
|
||||||
|
testing.expectEqual(true, imageData instanceof ImageData);
|
||||||
|
testing.expectEqual(imageData.width, 10);
|
||||||
|
testing.expectEqual(imageData.height, 20);
|
||||||
|
testing.expectEqual(imageData.data.length, 10 * 20 * 4);
|
||||||
|
|
||||||
|
// Undrawn canvas should return transparent black pixels.
|
||||||
|
testing.expectEqual(imageData.data[0], 0);
|
||||||
|
testing.expectEqual(imageData.data[1], 0);
|
||||||
|
testing.expectEqual(imageData.data[2], 0);
|
||||||
|
testing.expectEqual(imageData.data[3], 0);
|
||||||
|
|
||||||
|
// Zero or negative dimensions should throw.
|
||||||
|
testing.expectError('Index or size', () => ctx.getImageData(0, 0, 0, 10));
|
||||||
|
testing.expectError('Index or size', () => ctx.getImageData(0, 0, 10, -5));
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|||||||
41
src/browser/tests/page/blob.html
Normal file
41
src/browser/tests/page/blob.html
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<body></body>
|
||||||
|
<script src="../testing.js"></script>
|
||||||
|
|
||||||
|
<script id="basic_blob_navigation">
|
||||||
|
{
|
||||||
|
const html = '<html><head></head><body><div id="test">Hello Blob</div></body></html>';
|
||||||
|
const blob = new Blob([html], { type: 'text/html' });
|
||||||
|
const blob_url = URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
const iframe = document.createElement('iframe');
|
||||||
|
document.body.appendChild(iframe);
|
||||||
|
iframe.src = blob_url;
|
||||||
|
|
||||||
|
testing.eventually(() => {
|
||||||
|
testing.expectEqual('Hello Blob', iframe.contentDocument.getElementById('test').textContent);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="multiple_blobs">
|
||||||
|
{
|
||||||
|
const blob1 = new Blob(['<html><body>First</body></html>'], { type: 'text/html' });
|
||||||
|
const blob2 = new Blob(['<html><body>Second</body></html>'], { type: 'text/html' });
|
||||||
|
const url1 = URL.createObjectURL(blob1);
|
||||||
|
const url2 = URL.createObjectURL(blob2);
|
||||||
|
|
||||||
|
const iframe1 = document.createElement('iframe');
|
||||||
|
document.body.appendChild(iframe1);
|
||||||
|
iframe1.src = url1;
|
||||||
|
|
||||||
|
const iframe2 = document.createElement('iframe');
|
||||||
|
document.body.appendChild(iframe2);
|
||||||
|
iframe2.src = url2;
|
||||||
|
|
||||||
|
testing.eventually(() => {
|
||||||
|
testing.expectEqual('First', iframe1.contentDocument.body.textContent);
|
||||||
|
testing.expectEqual('Second', iframe2.contentDocument.body.textContent);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -19,15 +19,22 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const js = @import("../js/js.zig");
|
const js = @import("../js/js.zig");
|
||||||
|
|
||||||
|
const Session = @import("../Session.zig");
|
||||||
|
|
||||||
const Node = @import("Node.zig");
|
const Node = @import("Node.zig");
|
||||||
const Range = @import("Range.zig");
|
const Range = @import("Range.zig");
|
||||||
|
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const IS_DEBUG = @import("builtin").mode == .Debug;
|
||||||
|
|
||||||
const AbstractRange = @This();
|
const AbstractRange = @This();
|
||||||
|
|
||||||
pub const _prototype_root = true;
|
pub const _prototype_root = true;
|
||||||
|
|
||||||
|
_rc: u8,
|
||||||
_type: Type,
|
_type: Type,
|
||||||
|
_page_id: u32,
|
||||||
|
_arena: Allocator,
|
||||||
_end_offset: u32,
|
_end_offset: u32,
|
||||||
_start_offset: u32,
|
_start_offset: u32,
|
||||||
_end_container: *Node,
|
_end_container: *Node,
|
||||||
@@ -36,6 +43,27 @@ _start_container: *Node,
|
|||||||
// Intrusive linked list node for tracking live ranges on the Page.
|
// Intrusive linked list node for tracking live ranges on the Page.
|
||||||
_range_link: std.DoublyLinkedList.Node = .{},
|
_range_link: std.DoublyLinkedList.Node = .{},
|
||||||
|
|
||||||
|
pub fn acquireRef(self: *AbstractRange) void {
|
||||||
|
self._rc += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *AbstractRange, shutdown: bool, session: *Session) void {
|
||||||
|
_ = shutdown;
|
||||||
|
const rc = self._rc;
|
||||||
|
if (comptime IS_DEBUG) {
|
||||||
|
std.debug.assert(rc != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rc == 1) {
|
||||||
|
if (session.findPageById(self._page_id)) |page| {
|
||||||
|
page._live_ranges.remove(&self._range_link);
|
||||||
|
}
|
||||||
|
session.releaseArena(self._arena);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self._rc = rc - 1;
|
||||||
|
}
|
||||||
|
|
||||||
pub const Type = union(enum) {
|
pub const Type = union(enum) {
|
||||||
range: *Range,
|
range: *Range,
|
||||||
// TODO: static_range: *StaticRange,
|
// TODO: static_range: *StaticRange,
|
||||||
@@ -310,6 +338,8 @@ pub const JsApi = struct {
|
|||||||
pub const name = "AbstractRange";
|
pub const name = "AbstractRange";
|
||||||
pub const prototype_chain = bridge.prototypeChain();
|
pub const prototype_chain = bridge.prototypeChain();
|
||||||
pub var class_id: bridge.ClassId = undefined;
|
pub var class_id: bridge.ClassId = undefined;
|
||||||
|
pub const weak = true;
|
||||||
|
pub const finalizer = bridge.finalizer(AbstractRange.deinit);
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const startContainer = bridge.accessor(AbstractRange.getStartContainer, null, .{});
|
pub const startContainer = bridge.accessor(AbstractRange.getStartContainer, null, .{});
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ pub const ConstructorSettings = struct {
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// We currently support only the first 2.
|
/// We currently support only the first 2.
|
||||||
pub fn constructor(
|
pub fn init(
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
maybe_settings: ?ConstructorSettings,
|
maybe_settings: ?ConstructorSettings,
|
||||||
@@ -106,7 +106,7 @@ pub const JsApi = struct {
|
|||||||
pub var class_id: bridge.ClassId = undefined;
|
pub var class_id: bridge.ClassId = undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const constructor = bridge.constructor(ImageData.constructor, .{ .dom_exception = true });
|
pub const constructor = bridge.constructor(ImageData.init, .{ .dom_exception = true });
|
||||||
|
|
||||||
pub const colorSpace = bridge.property("srgb", .{ .template = false, .readonly = true });
|
pub const colorSpace = bridge.property("srgb", .{ .template = false, .readonly = true });
|
||||||
pub const pixelFormat = bridge.property("rgba-unorm8", .{ .template = false, .readonly = true });
|
pub const pixelFormat = bridge.property("rgba-unorm8", .{ .template = false, .readonly = true });
|
||||||
|
|||||||
@@ -21,22 +21,31 @@ const String = @import("../../string.zig").String;
|
|||||||
|
|
||||||
const js = @import("../js/js.zig");
|
const js = @import("../js/js.zig");
|
||||||
const Page = @import("../Page.zig");
|
const Page = @import("../Page.zig");
|
||||||
|
const Session = @import("../Session.zig");
|
||||||
|
|
||||||
const Node = @import("Node.zig");
|
const Node = @import("Node.zig");
|
||||||
const DocumentFragment = @import("DocumentFragment.zig");
|
const DocumentFragment = @import("DocumentFragment.zig");
|
||||||
const AbstractRange = @import("AbstractRange.zig");
|
const AbstractRange = @import("AbstractRange.zig");
|
||||||
const DOMRect = @import("DOMRect.zig");
|
const DOMRect = @import("DOMRect.zig");
|
||||||
|
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
const Range = @This();
|
const Range = @This();
|
||||||
|
|
||||||
_proto: *AbstractRange,
|
_proto: *AbstractRange,
|
||||||
|
|
||||||
pub fn asAbstractRange(self: *Range) *AbstractRange {
|
pub fn init(page: *Page) !*Range {
|
||||||
return self._proto;
|
const arena = try page.getArena(.{ .debug = "Range" });
|
||||||
|
errdefer page.releaseArena(arena);
|
||||||
|
return page._factory.abstractRange(arena, Range{ ._proto = undefined }, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(page: *Page) !*Range {
|
pub fn deinit(self: *Range, shutdown: bool, session: *Session) void {
|
||||||
return page._factory.abstractRange(Range{ ._proto = undefined }, page);
|
self._proto.deinit(shutdown, session);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn asAbstractRange(self: *Range) *AbstractRange {
|
||||||
|
return self._proto;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setStart(self: *Range, node: *Node, offset: u32) !void {
|
pub fn setStart(self: *Range, node: *Node, offset: u32) !void {
|
||||||
@@ -309,7 +318,10 @@ pub fn intersectsNode(self: *const Range, node: *Node) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn cloneRange(self: *const Range, page: *Page) !*Range {
|
pub fn cloneRange(self: *const Range, page: *Page) !*Range {
|
||||||
const clone = try page._factory.abstractRange(Range{ ._proto = undefined }, page);
|
const arena = try page.getArena(.{ .debug = "Range.clone" });
|
||||||
|
errdefer page.releaseArena(arena);
|
||||||
|
|
||||||
|
const clone = try page._factory.abstractRange(arena, Range{ ._proto = undefined }, page);
|
||||||
clone._proto._end_offset = self._proto._end_offset;
|
clone._proto._end_offset = self._proto._end_offset;
|
||||||
clone._proto._start_offset = self._proto._start_offset;
|
clone._proto._start_offset = self._proto._start_offset;
|
||||||
clone._proto._end_container = self._proto._end_container;
|
clone._proto._end_container = self._proto._end_container;
|
||||||
@@ -687,6 +699,8 @@ pub const JsApi = struct {
|
|||||||
pub const name = "Range";
|
pub const name = "Range";
|
||||||
pub const prototype_chain = bridge.prototypeChain();
|
pub const prototype_chain = bridge.prototypeChain();
|
||||||
pub var class_id: bridge.ClassId = undefined;
|
pub var class_id: bridge.ClassId = undefined;
|
||||||
|
pub const weak = true;
|
||||||
|
pub const finalizer = bridge.finalizer(Range.deinit);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Constants for compareBoundaryPoints
|
// Constants for compareBoundaryPoints
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ const log = @import("../../log.zig");
|
|||||||
|
|
||||||
const js = @import("../js/js.zig");
|
const js = @import("../js/js.zig");
|
||||||
const Page = @import("../Page.zig");
|
const Page = @import("../Page.zig");
|
||||||
|
const Session = @import("../Session.zig");
|
||||||
|
|
||||||
const Range = @import("Range.zig");
|
const Range = @import("Range.zig");
|
||||||
const AbstractRange = @import("AbstractRange.zig");
|
const AbstractRange = @import("AbstractRange.zig");
|
||||||
const Node = @import("Node.zig");
|
const Node = @import("Node.zig");
|
||||||
@@ -37,13 +39,22 @@ _direction: SelectionDirection = .none,
|
|||||||
|
|
||||||
pub const init: Selection = .{};
|
pub const init: Selection = .{};
|
||||||
|
|
||||||
|
pub fn deinit(self: *Selection, shutdown: bool, session: *Session) void {
|
||||||
|
if (self._range) |r| {
|
||||||
|
r.deinit(shutdown, session);
|
||||||
|
self._range = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn dispatchSelectionChangeEvent(page: *Page) !void {
|
fn dispatchSelectionChangeEvent(page: *Page) !void {
|
||||||
const event = try Event.init("selectionchange", .{}, page);
|
const event = try Event.init("selectionchange", .{}, page);
|
||||||
try page._event_manager.dispatch(page.document.asEventTarget(), event);
|
try page._event_manager.dispatch(page.document.asEventTarget(), event);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn isInTree(self: *const Selection) bool {
|
fn isInTree(self: *const Selection) bool {
|
||||||
if (self._range == null) return false;
|
if (self._range == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const anchor_node = self.getAnchorNode() orelse return false;
|
const anchor_node = self.getAnchorNode() orelse return false;
|
||||||
const focus_node = self.getFocusNode() orelse return false;
|
const focus_node = self.getFocusNode() orelse return false;
|
||||||
return anchor_node.isConnected() and focus_node.isConnected();
|
return anchor_node.isConnected() and focus_node.isConnected();
|
||||||
@@ -104,21 +115,33 @@ pub fn getIsCollapsed(self: *const Selection) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn getRangeCount(self: *const Selection) u32 {
|
pub fn getRangeCount(self: *const Selection) u32 {
|
||||||
if (self._range == null) return 0;
|
if (self._range == null) {
|
||||||
if (!self.isInTree()) return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
if (!self.isInTree()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getType(self: *const Selection) []const u8 {
|
pub fn getType(self: *const Selection) []const u8 {
|
||||||
if (self._range == null) return "None";
|
if (self._range == null) {
|
||||||
if (!self.isInTree()) return "None";
|
return "None";
|
||||||
if (self.getIsCollapsed()) return "Caret";
|
}
|
||||||
|
if (!self.isInTree()) {
|
||||||
|
return "None";
|
||||||
|
}
|
||||||
|
if (self.getIsCollapsed()) {
|
||||||
|
return "Caret";
|
||||||
|
}
|
||||||
return "Range";
|
return "Range";
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn addRange(self: *Selection, range: *Range, page: *Page) !void {
|
pub fn addRange(self: *Selection, range: *Range, page: *Page) !void {
|
||||||
if (self._range != null) return;
|
if (self._range != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Only add the range if its root node is in the document associated with this selection
|
// Only add the range if its root node is in the document associated with this selection
|
||||||
const start_node = range.asAbstractRange().getStartContainer();
|
const start_node = range.asAbstractRange().getStartContainer();
|
||||||
@@ -126,22 +149,25 @@ pub fn addRange(self: *Selection, range: *Range, page: *Page) !void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self._range = range;
|
self.setRange(range, page);
|
||||||
try dispatchSelectionChangeEvent(page);
|
try dispatchSelectionChangeEvent(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn removeRange(self: *Selection, range: *Range, page: *Page) !void {
|
pub fn removeRange(self: *Selection, range: *Range, page: *Page) !void {
|
||||||
if (self._range == range) {
|
const existing_range = self._range orelse return error.NotFound;
|
||||||
self._range = null;
|
if (existing_range != range) {
|
||||||
try dispatchSelectionChangeEvent(page);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
return error.NotFound;
|
return error.NotFound;
|
||||||
}
|
}
|
||||||
|
self.setRange(null, page);
|
||||||
|
try dispatchSelectionChangeEvent(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn removeAllRanges(self: *Selection, page: *Page) !void {
|
pub fn removeAllRanges(self: *Selection, page: *Page) !void {
|
||||||
self._range = null;
|
if (self._range == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.setRange(null, page);
|
||||||
self._direction = .none;
|
self._direction = .none;
|
||||||
try dispatchSelectionChangeEvent(page);
|
try dispatchSelectionChangeEvent(page);
|
||||||
}
|
}
|
||||||
@@ -157,7 +183,7 @@ pub fn collapseToEnd(self: *Selection, page: *Page) !void {
|
|||||||
try new_range.setStart(last_node, last_offset);
|
try new_range.setStart(last_node, last_offset);
|
||||||
try new_range.setEnd(last_node, last_offset);
|
try new_range.setEnd(last_node, last_offset);
|
||||||
|
|
||||||
self._range = new_range;
|
self.setRange(new_range, page);
|
||||||
self._direction = .none;
|
self._direction = .none;
|
||||||
try dispatchSelectionChangeEvent(page);
|
try dispatchSelectionChangeEvent(page);
|
||||||
}
|
}
|
||||||
@@ -173,7 +199,7 @@ pub fn collapseToStart(self: *Selection, page: *Page) !void {
|
|||||||
try new_range.setStart(first_node, first_offset);
|
try new_range.setStart(first_node, first_offset);
|
||||||
try new_range.setEnd(first_node, first_offset);
|
try new_range.setEnd(first_node, first_offset);
|
||||||
|
|
||||||
self._range = new_range;
|
self.setRange(new_range, page);
|
||||||
self._direction = .none;
|
self._direction = .none;
|
||||||
try dispatchSelectionChangeEvent(page);
|
try dispatchSelectionChangeEvent(page);
|
||||||
}
|
}
|
||||||
@@ -255,7 +281,7 @@ pub fn extend(self: *Selection, node: *Node, _offset: ?u32, page: *Page) !void {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
self._range = new_range;
|
self.setRange(new_range, page);
|
||||||
try dispatchSelectionChangeEvent(page);
|
try dispatchSelectionChangeEvent(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -560,7 +586,8 @@ fn applyModify(self: *Selection, alter: ModifyAlter, new_node: *Node, new_offset
|
|||||||
const new_range = try Range.init(page);
|
const new_range = try Range.init(page);
|
||||||
try new_range.setStart(new_node, new_offset);
|
try new_range.setStart(new_node, new_offset);
|
||||||
try new_range.setEnd(new_node, new_offset);
|
try new_range.setEnd(new_node, new_offset);
|
||||||
self._range = new_range;
|
|
||||||
|
self.setRange(new_range, page);
|
||||||
self._direction = .none;
|
self._direction = .none;
|
||||||
try dispatchSelectionChangeEvent(page);
|
try dispatchSelectionChangeEvent(page);
|
||||||
},
|
},
|
||||||
@@ -582,7 +609,7 @@ pub fn selectAllChildren(self: *Selection, parent: *Node, page: *Page) !void {
|
|||||||
const child_count = parent.getChildrenCount();
|
const child_count = parent.getChildrenCount();
|
||||||
try range.setEnd(parent, @intCast(child_count));
|
try range.setEnd(parent, @intCast(child_count));
|
||||||
|
|
||||||
self._range = range;
|
self.setRange(range, page);
|
||||||
self._direction = .forward;
|
self._direction = .forward;
|
||||||
try dispatchSelectionChangeEvent(page);
|
try dispatchSelectionChangeEvent(page);
|
||||||
}
|
}
|
||||||
@@ -630,7 +657,7 @@ pub fn setBaseAndExtent(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
self._range = range;
|
self.setRange(range, page);
|
||||||
try dispatchSelectionChangeEvent(page);
|
try dispatchSelectionChangeEvent(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -656,7 +683,7 @@ pub fn collapse(self: *Selection, _node: ?*Node, _offset: ?u32, page: *Page) !vo
|
|||||||
try range.setStart(node, offset);
|
try range.setStart(node, offset);
|
||||||
try range.setEnd(node, offset);
|
try range.setEnd(node, offset);
|
||||||
|
|
||||||
self._range = range;
|
self.setRange(range, page);
|
||||||
self._direction = .none;
|
self._direction = .none;
|
||||||
try dispatchSelectionChangeEvent(page);
|
try dispatchSelectionChangeEvent(page);
|
||||||
}
|
}
|
||||||
@@ -666,6 +693,16 @@ pub fn toString(self: *const Selection, page: *Page) ![]const u8 {
|
|||||||
return try range.toString(page);
|
return try range.toString(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn setRange(self: *Selection, new_range: ?*Range, page: *Page) void {
|
||||||
|
if (self._range) |existing| {
|
||||||
|
existing.deinit(false, page._session);
|
||||||
|
}
|
||||||
|
if (new_range) |nr| {
|
||||||
|
nr.asAbstractRange().acquireRef();
|
||||||
|
}
|
||||||
|
self._range = new_range;
|
||||||
|
}
|
||||||
|
|
||||||
pub const JsApi = struct {
|
pub const JsApi = struct {
|
||||||
pub const bridge = js.Bridge(Selection);
|
pub const bridge = js.Bridge(Selection);
|
||||||
|
|
||||||
@@ -673,6 +710,7 @@ pub const JsApi = struct {
|
|||||||
pub const name = "Selection";
|
pub const name = "Selection";
|
||||||
pub const prototype_chain = bridge.prototypeChain();
|
pub const prototype_chain = bridge.prototypeChain();
|
||||||
pub var class_id: bridge.ClassId = undefined;
|
pub var class_id: bridge.ClassId = undefined;
|
||||||
|
pub const finalizer = bridge.finalizer(Selection.deinit);
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const anchorNode = bridge.accessor(Selection.getAnchorNode, null, .{});
|
pub const anchorNode = bridge.accessor(Selection.getAnchorNode, null, .{});
|
||||||
|
|||||||
@@ -249,6 +249,8 @@ pub fn createObjectURL(blob: *Blob, page: *Page) ![]const u8 {
|
|||||||
.{ page.origin orelse "null", uuid_buf },
|
.{ page.origin orelse "null", uuid_buf },
|
||||||
);
|
);
|
||||||
try page._blob_urls.put(page.arena, blob_url, blob);
|
try page._blob_urls.put(page.arena, blob_url, blob);
|
||||||
|
// prevent GC from cleaning up the blob while it's in the registry
|
||||||
|
page.js.strongRef(blob);
|
||||||
return blob_url;
|
return blob_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,8 +260,10 @@ pub fn revokeObjectURL(url: []const u8, page: *Page) void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove from registry (no-op if not found)
|
// Remove from registry and release strong ref (no-op if not found)
|
||||||
_ = page._blob_urls.remove(url);
|
if (page._blob_urls.fetchRemove(url)) |entry| {
|
||||||
|
page.js.weakRef(entry.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const JsApi = struct {
|
pub const JsApi = struct {
|
||||||
|
|||||||
@@ -64,15 +64,30 @@ pub fn createImageData(
|
|||||||
switch (width_or_image_data) {
|
switch (width_or_image_data) {
|
||||||
.width => |width| {
|
.width => |width| {
|
||||||
const height = maybe_height orelse return error.TypeError;
|
const height = maybe_height orelse return error.TypeError;
|
||||||
return ImageData.constructor(width, height, maybe_settings, page);
|
return ImageData.init(width, height, maybe_settings, page);
|
||||||
},
|
},
|
||||||
.image_data => |image_data| {
|
.image_data => |image_data| {
|
||||||
return ImageData.constructor(image_data._width, image_data._height, null, page);
|
return ImageData.init(image_data._width, image_data._height, null, page);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn putImageData(_: *const CanvasRenderingContext2D, _: *ImageData, _: f64, _: f64, _: ?f64, _: ?f64, _: ?f64, _: ?f64) void {}
|
pub fn putImageData(_: *const CanvasRenderingContext2D, _: *ImageData, _: f64, _: f64, _: ?f64, _: ?f64, _: ?f64, _: ?f64) void {}
|
||||||
|
|
||||||
|
pub fn getImageData(
|
||||||
|
_: *const CanvasRenderingContext2D,
|
||||||
|
_: i32, // sx
|
||||||
|
_: i32, // sy
|
||||||
|
sw: i32,
|
||||||
|
sh: i32,
|
||||||
|
page: *Page,
|
||||||
|
) !*ImageData {
|
||||||
|
if (sw <= 0 or sh <= 0) {
|
||||||
|
return error.IndexSizeError;
|
||||||
|
}
|
||||||
|
return ImageData.init(@intCast(sw), @intCast(sh), null, page);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn save(_: *CanvasRenderingContext2D) void {}
|
pub fn save(_: *CanvasRenderingContext2D) void {}
|
||||||
pub fn restore(_: *CanvasRenderingContext2D) void {}
|
pub fn restore(_: *CanvasRenderingContext2D) void {}
|
||||||
pub fn scale(_: *CanvasRenderingContext2D, _: f64, _: f64) void {}
|
pub fn scale(_: *CanvasRenderingContext2D, _: f64, _: f64) void {}
|
||||||
@@ -125,6 +140,7 @@ pub const JsApi = struct {
|
|||||||
pub const createImageData = bridge.function(CanvasRenderingContext2D.createImageData, .{ .dom_exception = true });
|
pub const createImageData = bridge.function(CanvasRenderingContext2D.createImageData, .{ .dom_exception = true });
|
||||||
|
|
||||||
pub const putImageData = bridge.function(CanvasRenderingContext2D.putImageData, .{ .noop = true });
|
pub const putImageData = bridge.function(CanvasRenderingContext2D.putImageData, .{ .noop = true });
|
||||||
|
pub const getImageData = bridge.function(CanvasRenderingContext2D.getImageData, .{ .dom_exception = true });
|
||||||
pub const save = bridge.function(CanvasRenderingContext2D.save, .{ .noop = true });
|
pub const save = bridge.function(CanvasRenderingContext2D.save, .{ .noop = true });
|
||||||
pub const restore = bridge.function(CanvasRenderingContext2D.restore, .{ .noop = true });
|
pub const restore = bridge.function(CanvasRenderingContext2D.restore, .{ .noop = true });
|
||||||
pub const scale = bridge.function(CanvasRenderingContext2D.scale, .{ .noop = true });
|
pub const scale = bridge.function(CanvasRenderingContext2D.scale, .{ .noop = true });
|
||||||
|
|||||||
@@ -63,15 +63,30 @@ pub fn createImageData(
|
|||||||
switch (width_or_image_data) {
|
switch (width_or_image_data) {
|
||||||
.width => |width| {
|
.width => |width| {
|
||||||
const height = maybe_height orelse return error.TypeError;
|
const height = maybe_height orelse return error.TypeError;
|
||||||
return ImageData.constructor(width, height, maybe_settings, page);
|
return ImageData.init(width, height, maybe_settings, page);
|
||||||
},
|
},
|
||||||
.image_data => |image_data| {
|
.image_data => |image_data| {
|
||||||
return ImageData.constructor(image_data._width, image_data._height, null, page);
|
return ImageData.init(image_data._width, image_data._height, null, page);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn putImageData(_: *const OffscreenCanvasRenderingContext2D, _: *ImageData, _: f64, _: f64, _: ?f64, _: ?f64, _: ?f64, _: ?f64) void {}
|
pub fn putImageData(_: *const OffscreenCanvasRenderingContext2D, _: *ImageData, _: f64, _: f64, _: ?f64, _: ?f64, _: ?f64, _: ?f64) void {}
|
||||||
|
|
||||||
|
pub fn getImageData(
|
||||||
|
_: *const OffscreenCanvasRenderingContext2D,
|
||||||
|
_: i32, // sx
|
||||||
|
_: i32, // sy
|
||||||
|
sw: i32,
|
||||||
|
sh: i32,
|
||||||
|
page: *Page,
|
||||||
|
) !*ImageData {
|
||||||
|
if (sw <= 0 or sh <= 0) {
|
||||||
|
return error.IndexSizeError;
|
||||||
|
}
|
||||||
|
return ImageData.init(@intCast(sw), @intCast(sh), null, page);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn save(_: *OffscreenCanvasRenderingContext2D) void {}
|
pub fn save(_: *OffscreenCanvasRenderingContext2D) void {}
|
||||||
pub fn restore(_: *OffscreenCanvasRenderingContext2D) void {}
|
pub fn restore(_: *OffscreenCanvasRenderingContext2D) void {}
|
||||||
pub fn scale(_: *OffscreenCanvasRenderingContext2D, _: f64, _: f64) void {}
|
pub fn scale(_: *OffscreenCanvasRenderingContext2D, _: f64, _: f64) void {}
|
||||||
@@ -124,6 +139,7 @@ pub const JsApi = struct {
|
|||||||
pub const createImageData = bridge.function(OffscreenCanvasRenderingContext2D.createImageData, .{ .dom_exception = true });
|
pub const createImageData = bridge.function(OffscreenCanvasRenderingContext2D.createImageData, .{ .dom_exception = true });
|
||||||
|
|
||||||
pub const putImageData = bridge.function(OffscreenCanvasRenderingContext2D.putImageData, .{ .noop = true });
|
pub const putImageData = bridge.function(OffscreenCanvasRenderingContext2D.putImageData, .{ .noop = true });
|
||||||
|
pub const getImageData = bridge.function(OffscreenCanvasRenderingContext2D.getImageData, .{ .dom_exception = true });
|
||||||
pub const save = bridge.function(OffscreenCanvasRenderingContext2D.save, .{ .noop = true });
|
pub const save = bridge.function(OffscreenCanvasRenderingContext2D.save, .{ .noop = true });
|
||||||
pub const restore = bridge.function(OffscreenCanvasRenderingContext2D.restore, .{ .noop = true });
|
pub const restore = bridge.function(OffscreenCanvasRenderingContext2D.restore, .{ .noop = true });
|
||||||
pub const scale = bridge.function(OffscreenCanvasRenderingContext2D.scale, .{ .noop = true });
|
pub const scale = bridge.function(OffscreenCanvasRenderingContext2D.scale, .{ .noop = true });
|
||||||
|
|||||||
@@ -53,8 +53,8 @@ fn getFullAXTree(cmd: anytype) !void {
|
|||||||
const frame_id = params.frameId orelse {
|
const frame_id = params.frameId orelse {
|
||||||
break :blk session.currentPage() orelse return error.PageNotLoaded;
|
break :blk session.currentPage() orelse return error.PageNotLoaded;
|
||||||
};
|
};
|
||||||
const page_id = try id.toPageId(.frame_id, frame_id);
|
const page_frame_id = try id.toPageId(.frame_id, frame_id);
|
||||||
break :blk session.findPage(page_id) orelse {
|
break :blk session.findPageByFrameId(page_frame_id) orelse {
|
||||||
return cmd.sendError(-32000, "Frame with the given id does not belong to the target.", .{});
|
return cmd.sendError(-32000, "Frame with the given id does not belong to the target.", .{});
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -502,9 +502,9 @@ fn getFrameOwner(cmd: anytype) !void {
|
|||||||
})) orelse return error.InvalidParams;
|
})) orelse return error.InvalidParams;
|
||||||
|
|
||||||
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
||||||
const page_id = try id.toPageId(.frame_id, params.frameId);
|
const page_frame_id = try id.toPageId(.frame_id, params.frameId);
|
||||||
|
|
||||||
const page = bc.session.findPage(page_id) orelse {
|
const page = bc.session.findPageByFrameId(page_frame_id) orelse {
|
||||||
return cmd.sendError(-32000, "Frame with the given id does not belong to the target.", .{});
|
return cmd.sendError(-32000, "Frame with the given id does not belong to the target.", .{});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ pub fn httpRequestStart(bc: anytype, msg: *const Notification.RequestStart) !voi
|
|||||||
const transfer = msg.transfer;
|
const transfer = msg.transfer;
|
||||||
const req = &transfer.req;
|
const req = &transfer.req;
|
||||||
const frame_id = req.frame_id;
|
const frame_id = req.frame_id;
|
||||||
const page = bc.session.findPage(frame_id) orelse return;
|
const page = bc.session.findPageByFrameId(frame_id) orelse return;
|
||||||
|
|
||||||
// Modify request with extra CDP headers
|
// Modify request with extra CDP headers
|
||||||
for (bc.extra_headers.items) |extra| {
|
for (bc.extra_headers.items) |extra| {
|
||||||
|
|||||||
@@ -35,12 +35,7 @@ fn setIgnoreCertificateErrors(cmd: anytype) !void {
|
|||||||
ignore: bool,
|
ignore: bool,
|
||||||
})) orelse return error.InvalidParams;
|
})) orelse return error.InvalidParams;
|
||||||
|
|
||||||
if (params.ignore) {
|
try cmd.cdp.browser.http_client.setTlsVerify(!params.ignore);
|
||||||
try cmd.cdp.browser.http_client.disableTlsVerify();
|
|
||||||
} else {
|
|
||||||
try cmd.cdp.browser.http_client.enableTlsVerify();
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd.sendResult(null, .{});
|
return cmd.sendResult(null, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ config: *const Config,
|
|||||||
ca_blob: ?net_http.Blob,
|
ca_blob: ?net_http.Blob,
|
||||||
robot_store: RobotStore,
|
robot_store: RobotStore,
|
||||||
|
|
||||||
|
connections: []net_http.Connection,
|
||||||
|
available: std.DoublyLinkedList = .{},
|
||||||
|
conn_mutex: std.Thread.Mutex = .{},
|
||||||
|
|
||||||
pollfds: []posix.pollfd,
|
pollfds: []posix.pollfd,
|
||||||
listener: ?Listener = null,
|
listener: ?Listener = null,
|
||||||
|
|
||||||
@@ -191,11 +195,23 @@ pub fn init(allocator: Allocator, config: *const Config) !Runtime {
|
|||||||
ca_blob = try loadCerts(allocator);
|
ca_blob = try loadCerts(allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const count: usize = config.httpMaxConcurrent();
|
||||||
|
const connections = try allocator.alloc(net_http.Connection, count);
|
||||||
|
errdefer allocator.free(connections);
|
||||||
|
|
||||||
|
var available: std.DoublyLinkedList = .{};
|
||||||
|
for (0..count) |i| {
|
||||||
|
connections[i] = try net_http.Connection.init(ca_blob, config);
|
||||||
|
available.append(&connections[i].node);
|
||||||
|
}
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.config = config,
|
.config = config,
|
||||||
.ca_blob = ca_blob,
|
.ca_blob = ca_blob,
|
||||||
.robot_store = RobotStore.init(allocator),
|
.robot_store = RobotStore.init(allocator),
|
||||||
|
.connections = connections,
|
||||||
|
.available = available,
|
||||||
.pollfds = pollfds,
|
.pollfds = pollfds,
|
||||||
.wakeup_pipe = pipe,
|
.wakeup_pipe = pipe,
|
||||||
};
|
};
|
||||||
@@ -216,6 +232,11 @@ pub fn deinit(self: *Runtime) void {
|
|||||||
self.allocator.free(data[0..ca_blob.len]);
|
self.allocator.free(data[0..ca_blob.len]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (self.connections) |*conn| {
|
||||||
|
conn.deinit();
|
||||||
|
}
|
||||||
|
self.allocator.free(self.connections);
|
||||||
|
|
||||||
self.robot_store.deinit();
|
self.robot_store.deinit();
|
||||||
|
|
||||||
globalDeinit();
|
globalDeinit();
|
||||||
@@ -310,6 +331,25 @@ pub fn stop(self: *Runtime) void {
|
|||||||
_ = posix.write(self.wakeup_pipe[1], &.{1}) catch {};
|
_ = posix.write(self.wakeup_pipe[1], &.{1}) catch {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn getConnection(self: *Runtime) ?*net_http.Connection {
|
||||||
|
self.conn_mutex.lock();
|
||||||
|
defer self.conn_mutex.unlock();
|
||||||
|
|
||||||
|
const node = self.available.popFirst() orelse return null;
|
||||||
|
return @fieldParentPtr("node", node);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn releaseConnection(self: *Runtime, conn: *net_http.Connection) void {
|
||||||
|
conn.reset() catch |err| {
|
||||||
|
lp.assert(false, "couldn't reset curl easy", .{ .err = err });
|
||||||
|
};
|
||||||
|
|
||||||
|
self.conn_mutex.lock();
|
||||||
|
defer self.conn_mutex.unlock();
|
||||||
|
|
||||||
|
self.available.append(&conn.node);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn newConnection(self: *Runtime) !net_http.Connection {
|
pub fn newConnection(self: *Runtime) !net_http.Connection {
|
||||||
return net_http.Connection.init(self.ca_blob, self.config);
|
return net_http.Connection.init(self.ca_blob, self.config);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ pub const ResponseHead = struct {
|
|||||||
|
|
||||||
pub const Connection = struct {
|
pub const Connection = struct {
|
||||||
easy: *libcurl.Curl,
|
easy: *libcurl.Curl,
|
||||||
node: Handles.HandleList.Node = .{},
|
node: std.DoublyLinkedList.Node = .{},
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
ca_blob_: ?libcurl.CurlBlob,
|
ca_blob_: ?libcurl.CurlBlob,
|
||||||
@@ -385,8 +385,16 @@ pub const Connection = struct {
|
|||||||
try libcurl.curl_easy_setopt(self.easy, .write_function, data_cb);
|
try libcurl.curl_easy_setopt(self.easy, .write_function, data_cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setProxy(self: *const Connection, proxy: ?[*:0]const u8) !void {
|
pub fn reset(self: *const Connection) !void {
|
||||||
try libcurl.curl_easy_setopt(self.easy, .proxy, proxy);
|
try libcurl.curl_easy_setopt(self.easy, .header_data, null);
|
||||||
|
try libcurl.curl_easy_setopt(self.easy, .header_function, null);
|
||||||
|
try libcurl.curl_easy_setopt(self.easy, .write_data, null);
|
||||||
|
try libcurl.curl_easy_setopt(self.easy, .write_function, null);
|
||||||
|
try libcurl.curl_easy_setopt(self.easy, .proxy, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setProxy(self: *const Connection, proxy: ?[:0]const u8) !void {
|
||||||
|
try libcurl.curl_easy_setopt(self.easy, .proxy, if (proxy) |p| p.ptr else null);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setTlsVerify(self: *const Connection, verify: bool, use_proxy: bool) !void {
|
pub fn setTlsVerify(self: *const Connection, verify: bool, use_proxy: bool) !void {
|
||||||
@@ -467,111 +475,32 @@ pub const Connection = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const Handles = struct {
|
pub const Handles = struct {
|
||||||
connections: []Connection,
|
|
||||||
dirty: HandleList,
|
|
||||||
in_use: HandleList,
|
|
||||||
available: HandleList,
|
|
||||||
multi: *libcurl.CurlM,
|
multi: *libcurl.CurlM,
|
||||||
performing: bool = false,
|
|
||||||
|
|
||||||
pub const HandleList = std.DoublyLinkedList;
|
|
||||||
|
|
||||||
pub fn init(
|
|
||||||
allocator: Allocator,
|
|
||||||
ca_blob: ?libcurl.CurlBlob,
|
|
||||||
config: *const Config,
|
|
||||||
) !Handles {
|
|
||||||
const count: usize = config.httpMaxConcurrent();
|
|
||||||
if (count == 0) return error.InvalidMaxConcurrent;
|
|
||||||
|
|
||||||
|
pub fn init(config: *const Config) !Handles {
|
||||||
const multi = libcurl.curl_multi_init() orelse return error.FailedToInitializeMulti;
|
const multi = libcurl.curl_multi_init() orelse return error.FailedToInitializeMulti;
|
||||||
errdefer libcurl.curl_multi_cleanup(multi) catch {};
|
errdefer libcurl.curl_multi_cleanup(multi) catch {};
|
||||||
|
|
||||||
try libcurl.curl_multi_setopt(multi, .max_host_connections, config.httpMaxHostOpen());
|
try libcurl.curl_multi_setopt(multi, .max_host_connections, config.httpMaxHostOpen());
|
||||||
|
|
||||||
const connections = try allocator.alloc(Connection, count);
|
return .{ .multi = multi };
|
||||||
errdefer allocator.free(connections);
|
|
||||||
|
|
||||||
var available: HandleList = .{};
|
|
||||||
for (0..count) |i| {
|
|
||||||
connections[i] = try Connection.init(ca_blob, config);
|
|
||||||
available.append(&connections[i].node);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return .{
|
pub fn deinit(self: *Handles) void {
|
||||||
.dirty = .{},
|
|
||||||
.in_use = .{},
|
|
||||||
.connections = connections,
|
|
||||||
.available = available,
|
|
||||||
.multi = multi,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Handles, allocator: Allocator) void {
|
|
||||||
for (self.connections) |*conn| {
|
|
||||||
conn.deinit();
|
|
||||||
}
|
|
||||||
allocator.free(self.connections);
|
|
||||||
libcurl.curl_multi_cleanup(self.multi) catch {};
|
libcurl.curl_multi_cleanup(self.multi) catch {};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hasAvailable(self: *const Handles) bool {
|
|
||||||
return self.available.first != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(self: *Handles) ?*Connection {
|
|
||||||
if (self.available.popFirst()) |node| {
|
|
||||||
self.in_use.append(node);
|
|
||||||
return @as(*Connection, @fieldParentPtr("node", node));
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add(self: *Handles, conn: *const Connection) !void {
|
pub fn add(self: *Handles, conn: *const Connection) !void {
|
||||||
try libcurl.curl_multi_add_handle(self.multi, conn.easy);
|
try libcurl.curl_multi_add_handle(self.multi, conn.easy);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(self: *Handles, conn: *Connection) void {
|
pub fn remove(self: *Handles, conn: *const Connection) !void {
|
||||||
if (libcurl.curl_multi_remove_handle(self.multi, conn.easy)) {
|
try libcurl.curl_multi_remove_handle(self.multi, conn.easy);
|
||||||
self.isAvailable(conn);
|
|
||||||
} else |err| {
|
|
||||||
// can happen if we're in a perform() call, so we'll queue this
|
|
||||||
// for cleanup later.
|
|
||||||
const node = &conn.node;
|
|
||||||
self.in_use.remove(node);
|
|
||||||
self.dirty.append(node);
|
|
||||||
log.warn(.http, "multi remove handle", .{ .err = err });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn isAvailable(self: *Handles, conn: *Connection) void {
|
|
||||||
const node = &conn.node;
|
|
||||||
self.in_use.remove(node);
|
|
||||||
self.available.append(node);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn perform(self: *Handles) !c_int {
|
pub fn perform(self: *Handles) !c_int {
|
||||||
self.performing = true;
|
|
||||||
defer self.performing = false;
|
|
||||||
|
|
||||||
const multi = self.multi;
|
|
||||||
var running: c_int = undefined;
|
var running: c_int = undefined;
|
||||||
try libcurl.curl_multi_perform(self.multi, &running);
|
try libcurl.curl_multi_perform(self.multi, &running);
|
||||||
|
|
||||||
{
|
|
||||||
const list = &self.dirty;
|
|
||||||
while (list.first) |node| {
|
|
||||||
list.remove(node);
|
|
||||||
const conn: *Connection = @fieldParentPtr("node", node);
|
|
||||||
if (libcurl.curl_multi_remove_handle(multi, conn.easy)) {
|
|
||||||
self.available.append(node);
|
|
||||||
} else |err| {
|
|
||||||
log.fatal(.http, "multi remove handle", .{ .err = err, .src = "perform" });
|
|
||||||
@panic("multi_remove_handle");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return running;
|
return running;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -590,7 +590,10 @@ pub fn curl_easy_setopt(easy: *Curl, comptime option: CurlOption, value: anytype
|
|||||||
.header_data,
|
.header_data,
|
||||||
.write_data,
|
.write_data,
|
||||||
=> blk: {
|
=> blk: {
|
||||||
const ptr: *anyopaque = @ptrCast(value);
|
const ptr: ?*anyopaque = switch (@typeInfo(@TypeOf(value))) {
|
||||||
|
.null => null,
|
||||||
|
else => @ptrCast(value),
|
||||||
|
};
|
||||||
break :blk c.curl_easy_setopt(easy, opt, ptr);
|
break :blk c.curl_easy_setopt(easy, opt, ptr);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user