// Copyright (C) 2023-2025 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 std = @import("std"); const lp = @import("lightpanda"); const Inbox = @import("../Inbox.zig"); const ArenaPool = @import("../ArenaPool.zig"); const Notification = @import("../Notification.zig"); const CDP = @import("../cdp/CDP.zig"); const Watchdog = @import("../Watchdog.zig"); const URL = @import("../browser/URL.zig"); const referrer = @import("../browser/referrer.zig"); const WebSocket = @import("../browser/webapi/net/WebSocket.zig"); const CookieJar = @import("../browser/webapi/storage/Cookie.zig").Jar; const http = @import("http.zig"); const Network = @import("Network.zig"); const Cache = @import("cache/Cache.zig"); const RobotsGate = @import("RobotsGate.zig"); const UrlBlocklist = @import("UrlBlocklist.zig"); pub const BlockPattern = UrlBlocklist.Pattern; const log = lp.log; const Allocator = std.mem.Allocator; pub const Method = http.Method; pub const Header = http.Header; pub const HeaderIterator = http.HeaderIterator; // This is loosely tied to a browser Frame. Loading all the , doing // XHR requests, and loading imports all happens through here. Sine the app // currently supports 1 browser and 1 frame at-a-time, we only have 1 Client and // re-use it from frame to frame. This allows us better re-use of the various // buffers/caches (including keepalive connections) that libcurl has. // // The app has other secondary http needs, like telemetry. While we want to // share some things (namely the ca blob, and maybe some configuration // (TODO: ??? should proxy settings be global ???)), we're able to call // client.abortList() to abort the transfers being made by a frame, without // impacting those other http requests. pub const Client = @This(); // Count of active ws requests ws_active: usize = 0, // Count of active http requests http_active: usize = 0, // Our curl multi handle. handles: http.Handles, // Watchdog instrumentation for this client's worker thread. Wraps the poll // in perform (and the background-task wait in Runner) so the watchdog can // tell "parked, waiting for work" from "stuck between waits". Registered // with App.watchdog by Browser.init. heartbeat: Watchdog.Heartbeat = .{}, // Use to generate the next request ID next_request_id: u32 = 0, // Every currently-alive Transfer indexed by its id. Maintained so cross- // component code (CDP intercept state, future scheduling/debugging) can // look up a transfer by id without holding a *Transfer that might dangle. // Inserted in Client.request, removed in Transfer.deinit. The pointer is // only valid for the lifetime of the entry. transfers: std.AutoHashMapUnmanaged(u32, *Transfer) = .empty, // Connections currently in this client's curl_multi. in_use: std.DoublyLinkedList = .{}, // Queue for request that are waiting an available connection (aka, easy) pending_queue: std.DoublyLinkedList = .{}, // Queue for completed transfers that haven't had their callbacks executed yet dispatch_queue: std.DoublyLinkedList = .{}, // WebSockets with buffered incoming events awaiting delivery. Separate from // dispatch_queue because the entries aren't Transfers and the gating rule is // simpler: WS events only deliver at tick(.all). ws_dispatch_queue: std.DoublyLinkedList = .{}, ws_dispatch_count: usize = 0, // Completed transfers held back by a frame's blocking request. dispatch() // parks them here on first sight so the gate is evaluated once per transfer // per blocking window, not once per tick; releaseBlocking splices them // back. Blocking requests are common (every bare