mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-09-15 23:37:42 -04:00
Merge pull request #3305 from lightpanda-io/disable-iframes-and-workers
breaking: disable worker and iframe loading by default
This commit is contained in:
14 files changed
+167
-185
No files matched your search
@@ -65,6 +65,7 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
LPD_PATH: ${{ github.workspace }}/bin/lightpanda
|
||||
LPD_ARGS: --load-resources iframe --load-resources worker
|
||||
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
||||
LP_MODEL: ${{ github.event.inputs.model }}
|
||||
# Optional: news.ycombinator.com often blocks datacenter IPs. Reuse
|
||||
|
||||
@@ -106,7 +106,7 @@ jobs:
|
||||
- id: args
|
||||
name: build LP args
|
||||
run: |
|
||||
args="--http-cache-dir /tmp/lp-cache"
|
||||
args="--http-cache-dir /tmp/lp-cache --load-resources worker --load-resources iframe"
|
||||
[ "${{ matrix.proxy }}" = "true" ] && args="$args --http-proxy http://127.0.0.1:3000"
|
||||
[ "${{ matrix.robotstxt }}" = "true" ] && args="$args --obey-robots"
|
||||
[ "${{ matrix.wba }}" = "true" ] && args="$args --web-bot-auth-key-file private_key.pem"
|
||||
@@ -162,7 +162,7 @@ jobs:
|
||||
- id: args
|
||||
name: build LP args
|
||||
run: |
|
||||
args="--http-cache-dir /tmp/lp-cache"
|
||||
args="--http-cache-dir /tmp/lp-cache --load-resources worker --load-resources iframe"
|
||||
[ "${{ matrix.robotstxt }}" = "true" ] && args="$args --obey-robots"
|
||||
[ "${{ matrix.wba }}" = "true" ] && args="$args --web-bot-auth-key-file private_key.pem"
|
||||
[ "${{ matrix.wba }}" = "true" ] && args="$args --web-bot-auth-domain ${{ vars.WBA_DOMAIN }}"
|
||||
@@ -429,6 +429,7 @@ jobs:
|
||||
- name: deterministic agent replay
|
||||
env:
|
||||
LPD_PATH: ${{ github.workspace }}/bin/lightpanda
|
||||
LPD_ARGS: --load-resources iframe --load-resources worker
|
||||
run: ./agent/run.sh deterministic
|
||||
|
||||
mcp-smoke:
|
||||
|
||||
+57
-62
@@ -40,9 +40,11 @@ pub const CDP_TCP_USER_TIMEOUT_MS: c_int = 10_000;
|
||||
|
||||
const Config = @This();
|
||||
|
||||
fn logFilterScopesValidator(allocator: Allocator, args: *std.process.Args.Iterator, list: *std.ArrayList(log.FilterRule)) !void {
|
||||
fn logFilterValidator(allocator: Allocator, args: *std.process.Args.Iterator, list: *std.ArrayList(log.FilterRule)) !void {
|
||||
const str = args.next() orelse return error.InvalidOption;
|
||||
|
||||
defer log.opts.scope_enabled = log.resolveFilters(list.items);
|
||||
|
||||
var it = std.mem.splitScalar(u8, str, ',');
|
||||
while (it.next()) |part| {
|
||||
if (part.len == 0) continue;
|
||||
@@ -66,7 +68,7 @@ fn logFilterScopesValidator(allocator: Allocator, args: *std.process.Args.Iterat
|
||||
}
|
||||
|
||||
const v = std.meta.stringToEnum(log.Scope, name) orelse {
|
||||
log.fatal(.app, "invalid option choice", .{ .arg = "--log-filter-scopes", .value = part });
|
||||
log.fatal(.app, "invalid option choice", .{ .arg = "--log-filter", .value = part });
|
||||
return error.InvalidOption;
|
||||
};
|
||||
|
||||
@@ -78,6 +80,7 @@ fn logLevelValidator(_: Allocator, args: *std.process.Args.Iterator, target: *?l
|
||||
const str = args.next() orelse return error.MissingArgument;
|
||||
if (std.mem.eql(u8, str, "error")) {
|
||||
target.* = .err;
|
||||
log.opts.level = .err;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -85,6 +88,24 @@ fn logLevelValidator(_: Allocator, args: *std.process.Args.Iterator, target: *?l
|
||||
log.fatal(.app, "invalid option choice", .{ .arg = "--log-level", .value = str });
|
||||
return error.InvalidArgument;
|
||||
};
|
||||
log.opts.level = target.*.?;
|
||||
}
|
||||
|
||||
// The MCP host captures stderr into a log file, where pretty's ANSI
|
||||
// escapes and multi-line entries are noise. Runs before any option is
|
||||
// read so parse-time lines match; --log-format still overrides.
|
||||
fn mcpLogDefaults() void {
|
||||
log.opts.format = .logfmt;
|
||||
}
|
||||
|
||||
fn logFormatValidator(_: Allocator, args: *std.process.Args.Iterator, target: *?log.Format) !void {
|
||||
const str = args.next() orelse return error.MissingArgument;
|
||||
const format = std.meta.stringToEnum(log.Format, str) orelse {
|
||||
log.fatal(.app, "invalid option choice", .{ .arg = "--log-format", .value = str });
|
||||
return error.InvalidArgument;
|
||||
};
|
||||
target.* = format;
|
||||
log.opts.format = format;
|
||||
}
|
||||
|
||||
fn httpHeaderValidator(allocator: Allocator, args: *std.process.Args.Iterator, list: *std.ArrayList(HttpHeader)) !void {
|
||||
@@ -211,8 +232,11 @@ fn caPathValidator(
|
||||
}
|
||||
}
|
||||
|
||||
pub const LoadResources = packed struct(u1) {
|
||||
pub const LoadResources = packed struct(u4) {
|
||||
image: bool = false,
|
||||
iframe: bool = false,
|
||||
worker: bool = false,
|
||||
stylesheet: bool = false,
|
||||
};
|
||||
|
||||
/// Common CLI args.
|
||||
@@ -231,8 +255,9 @@ const CommonOptions = .{
|
||||
.{ .name = "ws_max_concurrent", .type = ?u8 },
|
||||
.{ .name = "insecure_disable_tls_host_verification", .type = bool },
|
||||
.{ .name = "log_level", .type = ?log.Level, .validator = logLevelValidator },
|
||||
.{ .name = "log_format", .type = ?log.Format },
|
||||
.{ .name = "log_filter_scopes", .type = log.FilterRule, .multiple = true, .validator = logFilterScopesValidator },
|
||||
.{ .name = "log_format", .type = ?log.Format, .validator = logFormatValidator },
|
||||
.{ .name = "log_filter", .type = log.FilterRule, .multiple = true, .validator = logFilterValidator },
|
||||
.{ .name = "log_filter_scopes", .type = log.FilterRule, .multiple = true, .validator = logFilterValidator, .deprecated = "use --log-filter" },
|
||||
.{ .name = "user_agent_suffix", .type = ?[]const u8 },
|
||||
.{ .name = "http_cache_dir", .type = ?[]const u8 },
|
||||
.{ .name = "http_cache_entry_limit", .type = ?u32, .default = 1000 },
|
||||
@@ -246,9 +271,9 @@ const CommonOptions = .{
|
||||
.{ .name = "adblock_lists", .type = ?[]const u8 },
|
||||
.{ .name = "cookie", .type = ?[]const u8 },
|
||||
.{ .name = "cookie_jar", .type = ?[]const u8 },
|
||||
.{ .name = "disable_subframes", .type = bool },
|
||||
.{ .name = "disable_workers", .type = bool },
|
||||
.{ .name = "enable_external_stylesheets", .type = bool },
|
||||
.{ .name = "disable_subframes", .type = bool, .deprecated = "subframes are now disabled by default, use \"--load-resources iframe\" to enable" },
|
||||
.{ .name = "disable_workers", .type = bool, .deprecated = "workers are now disabled by default, use \"--load-resources worker\" to enable" },
|
||||
.{ .name = "enable_external_stylesheets", .type = bool, .deprecated = "use \"--load-resources stylesheet\" to enable" },
|
||||
.{ .name = "load_resources", .type = LoadResources, .default = LoadResources{} },
|
||||
.{ .name = "v8_flags_unsafe", .type = ?[]const u8 },
|
||||
.{ .name = "v8_max_heap_mb", .type = ?u32 },
|
||||
@@ -355,7 +380,6 @@ const Commands = cli.Builder(.{
|
||||
.{ .name = "host", .type = []const u8, .default = "127.0.0.1" },
|
||||
.{ .name = "port", .type = u16, .default = 9222 },
|
||||
.{ .name = "advertise_host", .type = ?[]const u8 },
|
||||
.{ .name = "timeout", .type = ?u31 },
|
||||
.{ .name = "cdp_max_connections", .type = u16, .default = 16 },
|
||||
.{ .name = "cdp_max_pending_connections", .type = u16, .default = 128 },
|
||||
.{ .name = "cdp_max_message_size", .type = u32, .default = 1024 * 1024 },
|
||||
@@ -403,6 +427,7 @@ const Commands = cli.Builder(.{
|
||||
},
|
||||
.{
|
||||
.name = "mcp",
|
||||
.before_parse = mcpLogDefaults,
|
||||
.options = .{
|
||||
.{ .name = "port", .type = ?u16 },
|
||||
.{ .name = "host", .type = []const u8, .default = "127.0.0.1" },
|
||||
@@ -469,6 +494,18 @@ pub fn init(allocator: Allocator, exec_name: []const u8, mode: Mode) !Config {
|
||||
if (modeNeedsHttp(mode)) {
|
||||
config.http_headers = try HttpHeaders.init(allocator, &config);
|
||||
}
|
||||
|
||||
switch (config.mode) {
|
||||
inline else => |*m| {
|
||||
if (@hasField(@TypeOf(m.*), "enable_external_stylesheets")) {
|
||||
if (m.enable_external_stylesheets) {
|
||||
// map deprecated property onto updated one
|
||||
m.load_resources.stylesheet = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -503,20 +540,6 @@ pub fn obeyRobots(self: *const Config) bool {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn disableSubframes(self: *const Config) bool {
|
||||
return switch (self.mode) {
|
||||
inline .serve, .fetch, .mcp, .agent => |opts| opts.disable_subframes,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn disableWorkers(self: *const Config) bool {
|
||||
return switch (self.mode) {
|
||||
inline .serve, .fetch, .mcp, .agent => |opts| opts.disable_workers,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn watchdogMs(self: *const Config) ?u32 {
|
||||
return switch (self.mode) {
|
||||
inline .serve, .fetch, .mcp, .agent => |opts| {
|
||||
@@ -527,13 +550,6 @@ pub fn watchdogMs(self: *const Config) ?u32 {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn enableExternalStylesheets(self: *const Config) bool {
|
||||
return switch (self.mode) {
|
||||
inline .serve, .fetch, .mcp, .agent => |opts| opts.enable_external_stylesheets,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn loadResources(self: *const Config) LoadResources {
|
||||
return switch (self.mode) {
|
||||
inline .serve, .fetch, .mcp, .agent => |opts| opts.load_resources,
|
||||
@@ -641,18 +657,6 @@ pub fn wsMaxConcurrent(self: *const Config) u8 {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn logLevel(self: *const Config) ?log.Level {
|
||||
return switch (self.mode) {
|
||||
// Agent mode quiets page-driven `console.error` noise unless verbosity=high.
|
||||
.agent => |opts| opts.log_level orelse switch (agentVerbosity(opts)) {
|
||||
.low, .medium => .err,
|
||||
.high => null,
|
||||
},
|
||||
inline .serve, .fetch, .mcp => |opts| opts.log_level,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
/// Resolve --verbosity. Explicit value wins. Else: --task with stderr
|
||||
/// captured (pipe/file) defaults to .high so benchmark harnesses and
|
||||
/// other programmatic consumers get the [tool/result] trace; REPL and
|
||||
@@ -676,23 +680,6 @@ fn stderrIsTty() bool {
|
||||
return stderr_tty_cached;
|
||||
}
|
||||
|
||||
pub fn logFormat(self: *const Config) ?log.Format {
|
||||
return switch (self.mode) {
|
||||
// The MCP host captures stderr into a log file, where pretty's ANSI
|
||||
// escapes and multi-line entries are noise.
|
||||
.mcp => |opts| opts.log_format orelse .logfmt,
|
||||
inline .serve, .fetch, .agent => |opts| opts.log_format,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn logFilterScopes(self: *const Config) std.ArrayList(log.FilterRule) {
|
||||
return switch (self.mode) {
|
||||
inline .serve, .fetch, .mcp, .agent => |opts| opts.log_filter_scopes,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn userAgentSuffix(self: *const Config) ?[]const u8 {
|
||||
return switch (self.mode) {
|
||||
inline .serve, .fetch, .mcp, .agent => |opts| opts.user_agent_suffix,
|
||||
@@ -1075,9 +1062,6 @@ fn printPaged(allocator: Allocator, text: []const u8) void {
|
||||
|
||||
pub fn parseArgs(allocator: Allocator, proc_args: std.process.Args) !Config {
|
||||
const exec_name, var command = try Commands.parse(allocator, proc_args);
|
||||
if (command == .serve and command.serve.timeout != null) {
|
||||
log.warn(.app, "--timeout is deprecated", .{});
|
||||
}
|
||||
const invoked = std.meta.activeTag(command);
|
||||
// Rewrite `run` to `.agent` so nothing downstream needs a `.run` case.
|
||||
if (command == .run) {
|
||||
@@ -1093,6 +1077,17 @@ pub fn parseArgs(allocator: Allocator, proc_args: std.process.Args) !Config {
|
||||
}
|
||||
command = .{ .agent = agent_opts };
|
||||
}
|
||||
|
||||
// Agent mode quiets page-driven `console.error` noise unless
|
||||
// verbosity=high. Depends on --verbosity/--task, so it can only be
|
||||
// resolved after the options are parsed; an explicit --log-level wins.
|
||||
if (command == .agent) {
|
||||
const opts = command.agent;
|
||||
if (opts.log_level == null and agentVerbosity(opts) != .high) {
|
||||
log.opts.level = .err;
|
||||
}
|
||||
}
|
||||
|
||||
var config = try Config.init(allocator, exec_name, command);
|
||||
config.command = invoked;
|
||||
return config;
|
||||
|
||||
@@ -1855,8 +1855,8 @@ pub fn iframeAddedCallback(self: *Frame, iframe: *IFrame) !void {
|
||||
if (iframe._executed) {
|
||||
return;
|
||||
}
|
||||
if (!self._session.subframe_loading_enabled) {
|
||||
// configured not to load frames
|
||||
if (self._session.load_resources.iframe == false) {
|
||||
log.warnDisabledIFrame();
|
||||
iframe._executed = true;
|
||||
return;
|
||||
}
|
||||
@@ -2269,9 +2269,7 @@ pub fn loadExternalStylesheet(self: *Frame, link: *Element.Html.Link, href: []co
|
||||
|
||||
const session = self._session;
|
||||
|
||||
// this feature is disabled by default, and can be turned on via a command
|
||||
// line flag or via an CDP command
|
||||
if (session.load_external_stylesheets == false) {
|
||||
if (session.load_resources.stylesheet == false) {
|
||||
return self.queueLoad(Factory.protoOf(link));
|
||||
}
|
||||
|
||||
@@ -3796,9 +3794,9 @@ test "Frame: iframeAddedCallback does not create a frame when termination is pen
|
||||
defer testing.test_session.closeAllPages();
|
||||
|
||||
const session = frame._session;
|
||||
const subframe_loading_enabled = session.subframe_loading_enabled;
|
||||
session.subframe_loading_enabled = true;
|
||||
defer session.subframe_loading_enabled = subframe_loading_enabled;
|
||||
const subframe_loading_enabled = session.load_resources.iframe;
|
||||
session.load_resources.iframe = true;
|
||||
defer session.load_resources.iframe = subframe_loading_enabled;
|
||||
|
||||
const element = try frame.document.createElement("iframe", null, frame);
|
||||
const iframe = element.as(HtmlElement.IFrame);
|
||||
|
||||
+2
-25
@@ -92,33 +92,14 @@ _tool_frame_override: ?u32 = null,
|
||||
// connection (see `Browser.frame_id_gen` and issue #2472).
|
||||
loader_id_gen: u32 = 0,
|
||||
|
||||
// configuration (or CDP command) to disable iframe loading
|
||||
subframe_loading_enabled: bool = true,
|
||||
|
||||
// configuration (or CDP command) to disable Web Worker loading. When false,
|
||||
// `new Worker(url)` returns a Worker object whose script is never fetched
|
||||
// and never evaluated. Set from the `--disable-workers` CLI flag at
|
||||
// session init; the LP.configureLoading CDP method can flip it per-session.
|
||||
worker_loading_enabled: bool = true,
|
||||
|
||||
// Console.* capture for the `consoleLogs` tool, capped at `max_console_bytes`.
|
||||
// Opt-in via `enableConsoleCapture`: plain CDP `serve` never drains it, so
|
||||
// leaving the listener off keeps the buffer at zero bytes.
|
||||
_console_messages: std.Io.Writer.Allocating,
|
||||
_console_capture: bool = false,
|
||||
|
||||
// Opt-in fetch of external <link rel=stylesheet> resources. Defaults to
|
||||
// false to preserve the current rendering-free fast path: drivers that
|
||||
// don't need accurate visibility checks pay nothing. Set from the
|
||||
// `--enable-external-stylesheets` CLI flag at session init; the
|
||||
// LP.configureLoading CDP method can flip it per-session. When true,
|
||||
// `Link.linkAddedCallback` routes to `Frame.loadExternalStylesheet`
|
||||
// (synchronous fetch + parse + register on `document.styleSheets`).
|
||||
load_external_stylesheets: bool = false,
|
||||
|
||||
// Sub-resources to actually request. Off by default: a driver that only
|
||||
// reads the DOM shouldn't pay for bytes it never looks at.
|
||||
load_resources: Config.LoadResources = .{},
|
||||
// configured external resources (images, stylesheet, worker, iframe) to load
|
||||
load_resources: Config.LoadResources,
|
||||
|
||||
/// Caller-supplied cancellation probe. `Runner._wait` polls it between
|
||||
/// ticks; once `check` returns true the wait returns `error.Cancelled`.
|
||||
@@ -180,11 +161,7 @@ pub fn init(self: *Session, browser: *Browser, notification: *Notification) !voi
|
||||
.browser = browser,
|
||||
.notification = notification,
|
||||
.cookie_jar = storage.Cookie.Jar.init(allocator, notification),
|
||||
// CLI defaults; LP.configureLoading can flip these per-session.
|
||||
.subframe_loading_enabled = !browser.app.config.disableSubframes(),
|
||||
.worker_loading_enabled = !browser.app.config.disableWorkers(),
|
||||
._console_messages = .init(allocator),
|
||||
.load_external_stylesheets = browser.app.config.enableExternalStylesheets(),
|
||||
.load_resources = browser.app.config.loadResources(),
|
||||
};
|
||||
errdefer self._console_messages.deinit();
|
||||
|
||||
@@ -92,8 +92,8 @@ pub fn init(frame: *Frame, url: [:0]const u8, name: []const u8, worker_type: Wor
|
||||
const proto = self._proto;
|
||||
errdefer proto.deinit();
|
||||
|
||||
if (!session.worker_loading_enabled) {
|
||||
log.debug(.browser, "shared worker disabled", .{ .url = owned_url });
|
||||
if (session.load_resources.worker == false) {
|
||||
log.warnDisabledWorker();
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,14 +91,8 @@ pub fn init(url: []const u8, options: ?WorkerOptions, frame: *Frame) !*Worker {
|
||||
self._worker_scope = dedicated_worker;
|
||||
try frame.trackWorker(self);
|
||||
|
||||
// `--disable-workers` (or `LP.configureLoading { worker: false }`):
|
||||
// skip the script fetch and eval. The Worker object is still
|
||||
// constructed so JS `new Worker(url)` does not throw, but the
|
||||
// worker's eval never runs (postMessage from the page is queued
|
||||
// indefinitely with no handler to drain it). Mirrors the
|
||||
// `subframe_loading_enabled` pattern for iframes.
|
||||
if (!session.worker_loading_enabled) {
|
||||
log.debug(.browser, "worker disabled", .{ .url = resolved_url });
|
||||
if (session.load_resources.worker == false) {
|
||||
log.warnDisabledWorker();
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
@@ -193,7 +193,7 @@ test "WebApi: HTML.Link" {
|
||||
|
||||
test "WebApi: HTML.Link external stylesheet" {
|
||||
testing.silenceLog(&.{.http});
|
||||
try testing.htmlRunner("css/external_stylesheet.html", .{ .load_external_stylesheets = true });
|
||||
try testing.htmlRunner("css/external_stylesheet.html", .{ .load_resources = .{ .stylesheet = true } });
|
||||
}
|
||||
|
||||
// Regression: a synchronous external-stylesheet fetch must not strand the
|
||||
@@ -202,5 +202,5 @@ test "WebApi: HTML.Link external stylesheet" {
|
||||
// never drains and the document is stuck at readyState "loading".
|
||||
test "WebApi: HTML.Link deferred script then external stylesheet" {
|
||||
testing.silenceLog(&.{.http});
|
||||
try testing.htmlRunner("css/deferred_script_then_stylesheet.html", .{ .load_external_stylesheets = true });
|
||||
try testing.htmlRunner("css/deferred_script_then_stylesheet.html", .{ .load_resources = .{ .stylesheet = true } });
|
||||
}
|
||||
+18
-18
@@ -102,10 +102,10 @@ fn configureLoading(cmd: *CDP.Command) !void {
|
||||
})) orelse return error.InvalidParams;
|
||||
|
||||
const bc = cmd.browser_context orelse return error.NoBrowserContext;
|
||||
if (params.subFrame) |v| bc.session.subframe_loading_enabled = v;
|
||||
if (params.worker) |v| bc.session.worker_loading_enabled = v;
|
||||
if (params.externalStylesheets) |v| bc.session.load_external_stylesheets = v;
|
||||
if (params.images) |v| bc.session.load_resources.image = v;
|
||||
if (params.worker) |v| bc.session.load_resources.worker = v;
|
||||
if (params.subFrame) |v| bc.session.load_resources.iframe = v;
|
||||
if (params.externalStylesheets) |v| bc.session.load_resources.stylesheet = v;
|
||||
return cmd.sendResult(null, .{});
|
||||
}
|
||||
|
||||
@@ -771,8 +771,8 @@ test "cdp.lp: configureLoading toggles subFrame and worker independently" {
|
||||
_ = try bc.session.createPage();
|
||||
|
||||
// Defaults: both loading types enabled.
|
||||
try testing.expectEqual(true, bc.session.subframe_loading_enabled);
|
||||
try testing.expectEqual(true, bc.session.worker_loading_enabled);
|
||||
try testing.expectEqual(true, bc.session.load_resources.iframe);
|
||||
try testing.expectEqual(true, bc.session.load_resources.worker);
|
||||
|
||||
// subFrame-only: leaves worker untouched.
|
||||
try ctx.processMessage(.{
|
||||
@@ -781,8 +781,8 @@ test "cdp.lp: configureLoading toggles subFrame and worker independently" {
|
||||
.params = .{ .subFrame = false },
|
||||
});
|
||||
try ctx.expectSentResult(null, .{ .id = 1 });
|
||||
try testing.expectEqual(false, bc.session.subframe_loading_enabled);
|
||||
try testing.expectEqual(true, bc.session.worker_loading_enabled);
|
||||
try testing.expectEqual(false, bc.session.load_resources.iframe);
|
||||
try testing.expectEqual(true, bc.session.load_resources.worker);
|
||||
|
||||
// worker-only: leaves subFrame untouched.
|
||||
try ctx.processMessage(.{
|
||||
@@ -791,8 +791,8 @@ test "cdp.lp: configureLoading toggles subFrame and worker independently" {
|
||||
.params = .{ .worker = false },
|
||||
});
|
||||
try ctx.expectSentResult(null, .{ .id = 2 });
|
||||
try testing.expectEqual(false, bc.session.subframe_loading_enabled);
|
||||
try testing.expectEqual(false, bc.session.worker_loading_enabled);
|
||||
try testing.expectEqual(false, bc.session.load_resources.iframe);
|
||||
try testing.expectEqual(false, bc.session.load_resources.worker);
|
||||
|
||||
// Both at once.
|
||||
try ctx.processMessage(.{
|
||||
@@ -801,8 +801,8 @@ test "cdp.lp: configureLoading toggles subFrame and worker independently" {
|
||||
.params = .{ .subFrame = true, .worker = true },
|
||||
});
|
||||
try ctx.expectSentResult(null, .{ .id = 3 });
|
||||
try testing.expectEqual(true, bc.session.subframe_loading_enabled);
|
||||
try testing.expectEqual(true, bc.session.worker_loading_enabled);
|
||||
try testing.expectEqual(true, bc.session.load_resources.iframe);
|
||||
try testing.expectEqual(true, bc.session.load_resources.worker);
|
||||
}
|
||||
|
||||
test "cdp.lp: configureLoading toggles externalStylesheets independently" {
|
||||
@@ -813,7 +813,7 @@ test "cdp.lp: configureLoading toggles externalStylesheets independently" {
|
||||
_ = try bc.session.createPage();
|
||||
|
||||
// Default is opt-in: off unless the CLI flag or CDP toggle enables it.
|
||||
try testing.expectEqual(false, bc.session.load_external_stylesheets);
|
||||
try testing.expectEqual(false, bc.session.load_resources.stylesheet);
|
||||
|
||||
// Enable via CDP; the other two loading toggles stay at their defaults.
|
||||
try ctx.processMessage(.{
|
||||
@@ -822,9 +822,9 @@ test "cdp.lp: configureLoading toggles externalStylesheets independently" {
|
||||
.params = .{ .externalStylesheets = true },
|
||||
});
|
||||
try ctx.expectSentResult(null, .{ .id = 1 });
|
||||
try testing.expectEqual(true, bc.session.load_external_stylesheets);
|
||||
try testing.expectEqual(true, bc.session.subframe_loading_enabled);
|
||||
try testing.expectEqual(true, bc.session.worker_loading_enabled);
|
||||
try testing.expectEqual(true, bc.session.load_resources.stylesheet);
|
||||
try testing.expectEqual(true, bc.session.load_resources.iframe);
|
||||
try testing.expectEqual(true, bc.session.load_resources.worker);
|
||||
|
||||
// Flip back off; partial params must not reset the other fields.
|
||||
try ctx.processMessage(.{
|
||||
@@ -833,7 +833,7 @@ test "cdp.lp: configureLoading toggles externalStylesheets independently" {
|
||||
.params = .{ .externalStylesheets = false },
|
||||
});
|
||||
try ctx.expectSentResult(null, .{ .id = 2 });
|
||||
try testing.expectEqual(false, bc.session.load_external_stylesheets);
|
||||
try testing.expectEqual(true, bc.session.subframe_loading_enabled);
|
||||
try testing.expectEqual(true, bc.session.worker_loading_enabled);
|
||||
try testing.expectEqual(false, bc.session.load_resources.stylesheet);
|
||||
try testing.expectEqual(true, bc.session.load_resources.iframe);
|
||||
try testing.expectEqual(true, bc.session.load_resources.worker);
|
||||
}
|
||||
+13
-1
@@ -52,6 +52,11 @@ const log = lp.log;
|
||||
/// that appears in both with the same field name and type is collapsed
|
||||
/// into one field (the command's own option wins); reusing a name with
|
||||
/// a different type is a compile error.
|
||||
/// - `before_parse: fn () void` (optional) — called once the command is
|
||||
/// known (by name or sniffed from a legacy flag), before any option is
|
||||
/// read. For mode-level process defaults that must already hold while
|
||||
/// the options themselves are parsed, e.g. log settings; an explicit
|
||||
/// option parsed later still wins.
|
||||
/// - `positional: struct` (optional) — a positional argument with `.name`
|
||||
/// and `.type` that may appear anywhere in argv. By default it holds a
|
||||
/// single value: `.type` must be an optional pointer-to-u8 slice (e.g.
|
||||
@@ -79,6 +84,8 @@ const log = lp.log;
|
||||
/// built-in type switch. See the validator section below.
|
||||
/// - `variants: tuple` (optional) — alternate flag names that write into
|
||||
/// the same field. See the variants section below.
|
||||
/// - `deprecated: []const u8` (optional) — the option still parses, but
|
||||
/// each use logs a warning carrying this note.
|
||||
///
|
||||
/// ## Supported types and their defaults
|
||||
///
|
||||
@@ -470,7 +477,6 @@ pub fn Builder(comptime commands: anytype) type {
|
||||
inline for (.{
|
||||
"--host",
|
||||
"--port",
|
||||
"--timeout",
|
||||
}) |heuristic| {
|
||||
if (std.mem.eql(u8, cmd_str, heuristic)) {
|
||||
return .serve;
|
||||
@@ -526,6 +532,9 @@ pub fn Builder(comptime commands: anytype) type {
|
||||
const OptionType = @TypeOf(option);
|
||||
const is_multiple = @hasField(OptionType, "multiple") and option.multiple;
|
||||
const has_validator = @hasField(OptionType, "validator");
|
||||
if (@hasField(OptionType, "deprecated")) {
|
||||
log.warn(.app, "deprecated CLI parameter", .{ .name = option.name, .note = option.deprecated });
|
||||
}
|
||||
|
||||
// Prefer validator for parsing if provided. The validator writes
|
||||
// through the field pointer (the list itself for multiples).
|
||||
@@ -682,6 +691,9 @@ pub fn Builder(comptime commands: anytype) type {
|
||||
args: *std.process.Args.Iterator,
|
||||
) !Union {
|
||||
const Command = @FieldType(Union, command.name);
|
||||
if (@hasField(@TypeOf(command), "before_parse")) {
|
||||
command.before_parse();
|
||||
}
|
||||
var c = Command{};
|
||||
|
||||
const options = blk: {
|
||||
|
||||
+19
-21
@@ -351,28 +351,26 @@
|
||||
\\ --cookie-jar <PATH>
|
||||
\\ Path to a JSON file to save cookies to on exit (write-only).
|
||||
\\ Defaults to no cookie saving.
|
||||
\\ --disable-subframes
|
||||
\\ Skip loading <iframe> elements. The parser still registers them in the
|
||||
\\ DOM, but no child frame or Page.frameAttached events are produced.
|
||||
\\ Defaults to false.
|
||||
\\ --disable-workers
|
||||
\\ Skip loading dedicated Web Workers. The Worker constructor still
|
||||
\\ returns a Worker object, but no script fetch is initiated and its scope
|
||||
\\ never runs.
|
||||
\\ Defaults to false.
|
||||
\\ --enable-external-stylesheets
|
||||
\\ Fetch external <link rel=stylesheet> resources so their rules
|
||||
\\ contribute to computed styles (and therefore to visibility checks like
|
||||
\\ display, visibility, opacity, pointer-events).
|
||||
\\ Defaults to false, except in agent mode with an LLM, where it is on.
|
||||
\\ --load-resources <RESOURCE>
|
||||
\\ Sub-resource to actually request. Can be passed multiple times.
|
||||
\\ Defaults to requesting none of them.
|
||||
\\ Allowed values:
|
||||
\\ image <img> sources, so that load/error reflects the real
|
||||
\\ HTTP status. Only the response headers are read;
|
||||
\\ images are never decoded, so naturalWidth and
|
||||
\\ naturalHeight stay 0. Delays the window load event.
|
||||
\\ iframe When enabled, <iframe> elements are fully loaded.
|
||||
\\
|
||||
\\ image <img> sources, so that load/error reflects the real
|
||||
\\ HTTP status. Only the response headers are read;
|
||||
\\ images are never decoded, so naturalWidth and
|
||||
\\ naturalHeight stay 0. Delays the window load event.
|
||||
\\
|
||||
\\ stylesheet Fetch external <link rel=stylesheet> resources so
|
||||
\\ their rules contribute to computed styles (and
|
||||
\\ therefore to visibility checks like display,
|
||||
\\ visibility, opacity, pointer-events).
|
||||
\\
|
||||
\\ worker Enable loading dedicated and shared workers. When
|
||||
\\ disabled, the Worker constructor still returns a
|
||||
\\ Worker, but no script fetch is initiated and the
|
||||
\\ Worker never runs.
|
||||
\\ --http-cache-dir <PATH>
|
||||
\\ Directory used as a filesystem cache for network resources. Omitting
|
||||
\\ this disables caching.
|
||||
@@ -423,13 +421,13 @@
|
||||
\\ --insecure-disable-tls-host-verification
|
||||
\\ Disables host verification on all HTTP requests.
|
||||
\\ Only set this if you understand and accept the risk.
|
||||
\\ --log-filter-scopes <SCOPE>
|
||||
\\ --log-filter <SCOPE>
|
||||
\\ Filter logs per scope, applied first-to-last. Can be passed multiple times.
|
||||
\\ "-X" (or bare "X") filters out a scope, "+X" filters it in, and
|
||||
\\ "all" targets every scope.
|
||||
\\ e.g. --log-filter-scopes http --log-filter-scopes unknown_prop
|
||||
\\ e.g. --log-filter http --log-filter unknown_prop
|
||||
\\ hides those two.
|
||||
\\ --log-filter-scopes -all --log-filter-scopes +cdp
|
||||
\\ --log-filter -all --log-filter +cdp
|
||||
\\ hides everything except cdp.
|
||||
\\ --log-format <FORMAT>
|
||||
\\ The log format.
|
||||
|
||||
+32
-20
@@ -21,23 +21,24 @@ const lp = @import("lightpanda");
|
||||
|
||||
pub const Scope = enum {
|
||||
app,
|
||||
dom,
|
||||
bug,
|
||||
browser,
|
||||
bug,
|
||||
cache,
|
||||
cdp,
|
||||
console,
|
||||
http,
|
||||
frame,
|
||||
js,
|
||||
disabled,
|
||||
dom,
|
||||
event,
|
||||
scheduler,
|
||||
frame,
|
||||
http,
|
||||
js,
|
||||
mcp,
|
||||
not_implemented,
|
||||
scheduler,
|
||||
storage,
|
||||
telemetry,
|
||||
unknown_prop,
|
||||
mcp,
|
||||
cache,
|
||||
websocket,
|
||||
storage,
|
||||
};
|
||||
|
||||
pub const num_scopes = @typeInfo(Scope).@"enum".fields.len;
|
||||
@@ -54,7 +55,7 @@ pub const FilterRule = struct {
|
||||
/// array. Directives apply left-to-right, so `-all,+cdp` disables every
|
||||
/// scope then re-enables `cdp`. Scopes untouched by any directive stay
|
||||
/// enabled.
|
||||
pub fn resolveFilterScopes(rules: []const FilterRule) [num_scopes]bool {
|
||||
pub fn resolveFilters(rules: []const FilterRule) [num_scopes]bool {
|
||||
var scope_enabled = [_]bool{true} ** num_scopes;
|
||||
for (rules) |rule| {
|
||||
if (rule.scope) |scope| {
|
||||
@@ -70,7 +71,6 @@ const Opts = struct {
|
||||
format: Format = if (lp.IS_DEBUG) .pretty else .logfmt,
|
||||
level: Level = if (lp.IS_DEBUG) .info else .warn,
|
||||
// Per-scope enabled flags; a `false` entry suppresses that scope's logs.
|
||||
// Only consulted in Debug builds. Default: everything enabled.
|
||||
scope_enabled: [num_scopes]bool = [_]bool{true} ** num_scopes,
|
||||
};
|
||||
|
||||
@@ -86,10 +86,8 @@ pub fn enabled(scope: Scope, level: Level) bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (comptime lp.IS_DEBUG) {
|
||||
if (opts.scope_enabled[@intFromEnum(scope)] == false) {
|
||||
return false;
|
||||
}
|
||||
if (opts.scope_enabled[@intFromEnum(scope)] == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -164,6 +162,20 @@ pub fn note(scope: Scope, msg: []const u8, data: anytype) void {
|
||||
}
|
||||
}
|
||||
|
||||
var warned_disabled_worker = std.atomic.Value(bool).init(false);
|
||||
pub fn warnDisabledWorker() void {
|
||||
if (warned_disabled_worker.swap(true, .monotonic) == false) {
|
||||
warn(.disabled, "workers disabled", .{ .hint = "enable via --load-resources worker" });
|
||||
}
|
||||
}
|
||||
|
||||
var warned_disabled_iframe = std.atomic.Value(bool).init(false);
|
||||
pub fn warnDisabledIFrame() void {
|
||||
if (warned_disabled_iframe.swap(true, .monotonic) == false) {
|
||||
warn(.disabled, "iframes disabled", .{ .hint = "enable via --load-resources iframe" });
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log(scope: Scope, level: Level, msg: []const u8, data: anytype) void {
|
||||
if (enabled(scope, level) == false) {
|
||||
return;
|
||||
@@ -602,17 +614,17 @@ test "log: string escape" {
|
||||
}
|
||||
}
|
||||
|
||||
test "log: resolveFilterScopes" {
|
||||
test "log: resolveFilters" {
|
||||
// No directives: everything enabled.
|
||||
{
|
||||
const se = resolveFilterScopes(&.{});
|
||||
const se = resolveFilters(&.{});
|
||||
try testing.expectEqual(true, se[@intFromEnum(Scope.cdp)]);
|
||||
try testing.expectEqual(true, se[@intFromEnum(Scope.http)]);
|
||||
}
|
||||
|
||||
// Backward compatible: bare/`-` scope filters that scope out, rest stay in.
|
||||
{
|
||||
const se = resolveFilterScopes(&.{
|
||||
const se = resolveFilters(&.{
|
||||
.{ .scope = .cdp, .enable = false },
|
||||
.{ .scope = .http, .enable = false },
|
||||
});
|
||||
@@ -623,7 +635,7 @@ test "log: resolveFilterScopes" {
|
||||
|
||||
// `-all,+cdp`: disable everything, then re-enable cdp.
|
||||
{
|
||||
const se = resolveFilterScopes(&.{
|
||||
const se = resolveFilters(&.{
|
||||
.{ .scope = null, .enable = false },
|
||||
.{ .scope = .cdp, .enable = true },
|
||||
});
|
||||
@@ -634,7 +646,7 @@ test "log: resolveFilterScopes" {
|
||||
|
||||
// `+all,-cdp`: enable everything, then disable cdp. Order matters.
|
||||
{
|
||||
const se = resolveFilterScopes(&.{
|
||||
const se = resolveFilters(&.{
|
||||
.{ .scope = null, .enable = true },
|
||||
.{ .scope = .cdp, .enable = false },
|
||||
});
|
||||
|
||||
@@ -83,16 +83,6 @@ fn run(allocator: Allocator, main_arena: Allocator, proc_args: std.process.Args)
|
||||
else => {},
|
||||
}
|
||||
|
||||
if (args.logLevel()) |ll| {
|
||||
log.opts.level = ll;
|
||||
}
|
||||
if (args.logFormat()) |lf| {
|
||||
log.opts.format = lf;
|
||||
}
|
||||
|
||||
// Set log filter scopes.
|
||||
log.opts.scope_enabled = log.resolveFilterScopes(args.logFilterScopes().items);
|
||||
|
||||
// must be installed before any other threads
|
||||
const sighandler = try main_arena.create(SigHandler);
|
||||
sighandler.* = .{ .arena = main_arena };
|
||||
|
||||
+10
-6
@@ -341,8 +341,10 @@ const WEB_API_TEST_ROOT = "src/browser/tests/";
|
||||
const HtmlRunnerOpts = struct {
|
||||
timeout_ms: u32 = 2000,
|
||||
inject_script: ?[]const u8 = null,
|
||||
load_external_stylesheets: bool = false,
|
||||
load_resources: Config.LoadResources = .{},
|
||||
load_resources: Config.LoadResources = .{
|
||||
.worker = true,
|
||||
.iframe = true,
|
||||
},
|
||||
};
|
||||
|
||||
// Create a fresh page on `test_session` and return its root frame — for tests
|
||||
@@ -368,11 +370,12 @@ pub fn htmlRunner(comptime path: []const u8, opts: HtmlRunnerOpts) !void {
|
||||
}
|
||||
defer test_session.inject_scripts = &.{};
|
||||
|
||||
test_session.load_external_stylesheets = opts.load_external_stylesheets;
|
||||
defer test_session.load_external_stylesheets = false;
|
||||
|
||||
test_session.load_resources = opts.load_resources;
|
||||
defer test_session.load_resources = .{};
|
||||
defer test_session.load_resources = .{
|
||||
// original defaults, tests expect these to be on
|
||||
.worker = true,
|
||||
.iframe = true,
|
||||
};
|
||||
|
||||
const root = try std.fs.path.joinZ(arena_allocator, &.{ WEB_API_TEST_ROOT, path });
|
||||
const stat = std.Io.Dir.cwd().statFile(io, root, .{}) catch |err| {
|
||||
@@ -533,6 +536,7 @@ test "tests:beforeAll" {
|
||||
.insecure_disable_tls_host_verification = true,
|
||||
.user_agent_suffix = "internal-tester",
|
||||
.ws_max_concurrent = 50,
|
||||
.load_resources = .{ .worker = true, .iframe = true },
|
||||
} });
|
||||
|
||||
test_app = try App.init(test_allocator, &test_config);
|
||||
|
||||
Reference in new issue
Block a user