From 566fa72bcda1a62bcd5eb111cfc0c4626b0f6183 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Fri, 19 Dec 2025 10:05:42 +0800 Subject: [PATCH] various small backports from main --- README.md | 2 +- src/browser/Page.zig | 70 +++++++++++++++++++++++++++++++++++ src/browser/Scheduler.zig | 6 +-- src/browser/ScriptManager.zig | 4 +- src/main.zig | 2 +- 5 files changed, 77 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a54d6be5..79194354 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Lightpanda is the open-source browser made for headless usage: - Javascript execution - Support of Web APIs (partial, WIP) -- Compatible with Playwright[^1], Puppeteer, chromedp through CDP +- Compatible with Playwright[^1], Puppeteer, chromedp through [CDP](https://chromedevtools.github.io/devtools-protocol/) Fast web automation for AI agents, LLM training, scraping and testing: diff --git a/src/browser/Page.zig b/src/browser/Page.zig index b47f5f4b..c7b453d8 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -769,6 +769,76 @@ fn _wait(self: *Page, wait_ms: u32) !Session.WaitResult { } } +fn printWaitAnalysis(self: *Page) void { + std.debug.print("load_state: {s}\n", .{@tagName(self._load_state)}); + std.debug.print("parse_state: {s}\n", .{@tagName(std.meta.activeTag(self._parse_state))}); + { + std.debug.print("\nactive requests: {d}\n", .{self._session.browser.http_client.active}); + var n_ = self._session.browser.http_client.handles.in_use.first; + while (n_) |n| { + const handle: *Http.Client.Handle = @fieldParentPtr("node", n); + const transfer = Http.Transfer.fromEasy(handle.conn.easy) catch |err| { + std.debug.print(" - failed to load transfer: {any}\n", .{err}); + break; + }; + std.debug.print(" - {f}\n", .{transfer}); + n_ = n.next; + } + } + + { + std.debug.print("\nqueued requests: {d}\n", .{self._session.browser.http_client.queue.len()}); + var n_ = self._session.browser.http_client.queue.first; + while (n_) |n| { + const transfer: *Http.Transfer = @fieldParentPtr("_node", n); + std.debug.print(" - {f}\n", .{transfer}); + n_ = n.next; + } + } + + + { + std.debug.print("\ndeferreds: {d}\n", .{self._script_manager.defer_scripts.len()}); + var n_ = self._script_manager.defer_scripts.first; + while (n_) |n| { + const script: *ScriptManager.Script = @fieldParentPtr("node", n); + std.debug.print(" - {s} complete: {any}\n", .{ script.url, script.complete }); + n_ = n.next; + } + } + + { + std.debug.print("\nasyncs: {d}\n", .{self._script_manager.async_scripts.len()}); + } + + { + std.debug.print("\nasyncs ready: {d}\n", .{self._script_manager.ready_scripts.len()}); + var n_ = self._script_manager.ready_scripts.first; + while (n_) |n| { + const script: *ScriptManager.Script = @fieldParentPtr("node", n); + std.debug.print(" - {s} complete: {any}\n", .{ script.url, script.complete }); + n_ = n.next; + } + } + + const now = milliTimestamp(.monotonic); + { + std.debug.print("\nhigh_priority schedule: {d}\n", .{self.scheduler.high_priority.count()}); + var it = self.scheduler.high_priority.iterator(); + while (it.next()) |task| { + std.debug.print(" - {s} schedule: {d}ms\n", .{ task.name, task.run_at - now }); + } + } + + { + std.debug.print("\nlow_priority schedule: {d}\n", .{self.scheduler.low_priority.count()}); + var it = self.scheduler.low_priority.iterator(); + while (it.next()) |task| { + std.debug.print(" - {s} schedule: {d}ms\n", .{ task.name, task.run_at - now }); + } + } +} + pub fn tick(self: *Page) void { if (comptime IS_DEBUG) { log.debug(.page, "tick", .{}); diff --git a/src/browser/Scheduler.zig b/src/browser/Scheduler.zig index 78a7ca1e..01ada86c 100644 --- a/src/browser/Scheduler.zig +++ b/src/browser/Scheduler.zig @@ -20,7 +20,7 @@ const std = @import("std"); const builtin = @import("builtin"); const log = @import("../log.zig"); -const timestamp = @import("../datetime.zig").milliTimestamp; +const milliTimestamp = @import("../datetime.zig").milliTimestamp; const IS_DEBUG = builtin.mode == .Debug; @@ -71,7 +71,7 @@ pub fn add(self: *Scheduler, ctx: *anyopaque, cb: Callback, run_in_ms: u32, opts .callback = cb, .sequence = seq, .name = opts.name, - .run_at = timestamp(.monotonic) + run_in_ms, + .run_at = milliTimestamp(.monotonic) + run_in_ms, }); } @@ -85,7 +85,7 @@ fn runQueue(self: *Scheduler, queue: *Queue) !?u64 { return null; } - const now = timestamp(.monotonic); + const now = milliTimestamp(.monotonic); while (queue.peek()) |*task_| { if (task_.run_at > now) { diff --git a/src/browser/ScriptManager.zig b/src/browser/ScriptManager.zig index 5f916fed..cfd5d568 100644 --- a/src/browser/ScriptManager.zig +++ b/src/browser/ScriptManager.zig @@ -360,7 +360,7 @@ pub fn preloadImport(self: *ScriptManager, url: [:0]const u8, referrer: []const // This seems wrong since we're not dealing with an async import (unlike // getAsyncModule below), but all we're trying to do here is pre-load the - // script for execution at some point in the future (when waitForModule is + // script for execution at some point in the future (when waitForImport is // called). self.async_scripts.append(&script.node); } @@ -564,7 +564,7 @@ fn parseImportmap(self: *ScriptManager, script: *const Script) !void { } } -const Script = struct { +pub const Script = struct { complete: bool, kind: Kind, status: u16 = 0, diff --git a/src/main.zig b/src/main.zig index 06f65a01..f01019f5 100644 --- a/src/main.zig +++ b/src/main.zig @@ -311,7 +311,7 @@ const Command = struct { \\ Filter out too verbose logs per scope: \\ http, unknown_prop, event, ... \\ - \\ --user_agent_suffix + \\--user_agent_suffix \\ Suffix to append to the Lightpanda/X.Y User-Agent \\ ;