Add a --with_frames argument to fetch

When set (defaults to not set/false), --dump will include iframe contents.

I was hoping I could add a mode to strip_mode to this, but since dump is used
extensively (e.g. innerHTML), this is something that has to be off by default
(for correctness).
This commit is contained in:
Karl Seguin
2026-02-25 15:27:44 +08:00
parent 8f179becf7
commit a818560344
6 changed files with 50 additions and 16 deletions

View File

@@ -191,7 +191,8 @@ pub const Fetch = struct {
url: [:0]const u8,
dump_mode: ?DumpFormat = null,
common: Common = .{},
withbase: bool = false,
with_base: bool = false,
with_frames: bool = false,
strip: dump.Opts.Strip = .{},
};
@@ -342,6 +343,8 @@ pub fn printUsageAndExit(self: *const Config, success: bool) void {
\\
\\--with_base Add a <base> tag in dump. Defaults to false.
\\
\\--with_frames Includes the contents of iframes. Defaults to false.
\\
++ common_options ++
\\
\\serve command
@@ -440,6 +443,10 @@ fn inferMode(opt: []const u8) ?RunMode {
return .fetch;
}
if (std.mem.eql(u8, opt, "--with_frames")) {
return .fetch;
}
if (std.mem.eql(u8, opt, "--host")) {
return .serve;
}
@@ -539,7 +546,8 @@ fn parseFetchArgs(
args: *std.process.ArgIterator,
) !Fetch {
var dump_mode: ?DumpFormat = null;
var withbase: bool = false;
var with_base: bool = false;
var with_frames: bool = false;
var url: ?[:0]const u8 = null;
var common: Common = .{};
var strip: dump.Opts.Strip = .{};
@@ -570,7 +578,12 @@ fn parseFetchArgs(
}
if (std.mem.eql(u8, "--with_base", opt)) {
withbase = true;
with_base = true;
continue;
}
if (std.mem.eql(u8, "--with_frames", opt)) {
with_frames = true;
continue;
}
@@ -626,7 +639,8 @@ fn parseFetchArgs(
.dump_mode = dump_mode,
.strip = strip,
.common = common,
.withbase = withbase,
.with_base = with_base,
.with_frames = with_frames,
};
}

View File

@@ -309,6 +309,8 @@ pub fn init(self: *Page, id: u32, session: *Session, parent: ?*Page) !void {
self.js = try browser.env.createContext(self);
errdefer self.js.deinit();
document._page = self;
if (comptime builtin.is_test == false) {
// HTML test runner manually calls these as necessary
try self.js.scheduler.add(session.browser, struct {

View File

@@ -20,16 +20,15 @@ const std = @import("std");
const Page = @import("Page.zig");
const Node = @import("webapi/Node.zig");
const Slot = @import("webapi/element/html/Slot.zig");
const IFrame = @import("webapi/element/html/IFrame.zig");
pub const RootOpts = struct {
with_base: bool = false,
strip: Opts.Strip = .{},
shadow: Opts.Shadow = .rendered,
};
const IS_DEBUG = @import("builtin").mode == .Debug;
pub const Opts = struct {
strip: Strip = .{},
shadow: Shadow = .rendered,
with_base: bool = false,
with_frames: bool = false,
strip: Opts.Strip = .{},
shadow: Opts.Shadow = .rendered,
pub const Strip = struct {
js: bool = false,
@@ -49,7 +48,7 @@ pub const Opts = struct {
};
};
pub fn root(doc: *Node.Document, opts: RootOpts, writer: *std.Io.Writer, page: *Page) !void {
pub fn root(doc: *Node.Document, opts: Opts, writer: *std.Io.Writer, page: *Page) !void {
if (doc.is(Node.Document.HTMLDocument)) |html_doc| {
blk: {
// Ideally we just render the doctype which is part of the document
@@ -71,7 +70,7 @@ pub fn root(doc: *Node.Document, opts: RootOpts, writer: *std.Io.Writer, page: *
}
}
return deep(doc.asNode(), .{ .strip = opts.strip, .shadow = opts.shadow }, writer, page);
return deep(doc.asNode(), opts, writer, page);
}
pub fn deep(node: *Node, opts: Opts, writer: *std.Io.Writer, page: *Page) error{WriteFailed}!void {
@@ -140,7 +139,24 @@ fn _deep(node: *Node, opts: Opts, comptime force_slot: bool, writer: *std.Io.Wri
}
}
try children(node, opts, writer, page);
if (opts.with_frames and el.is(IFrame) != null) {
const frame = el.as(IFrame);
if (frame.getContentDocument()) |doc| {
// A frame's document should always ahave a page, but
// I'm not willing to crash a release build on that assertion.
if (comptime IS_DEBUG) {
std.debug.assert(doc._page != null);
}
if (doc._page) |frame_page| {
try writer.writeByte('\n');
root(doc, opts, writer, frame_page) catch return error.WriteFailed;
try writer.writeByte('\n');
}
}
} else {
try children(node, opts, writer, page);
}
if (!isVoidElement(el)) {
try writer.writeAll("</");
try writer.writeAll(el.getTagNameDump());

View File

@@ -44,6 +44,7 @@ const Document = @This();
_type: Type,
_proto: *Node,
_page: ?*Page = null,
_location: ?*Location = null,
_url: ?[:0]const u8 = null, // URL for documents created via DOMImplementation (about:blank)
_ready_state: ReadyState = .loading,

View File

@@ -36,7 +36,7 @@ const IS_DEBUG = @import("builtin").mode == .Debug;
pub const FetchOpts = struct {
wait_ms: u32 = 5000,
dump: dump.RootOpts,
dump: dump.Opts,
dump_mode: ?Config.DumpFormat = null,
writer: ?*std.Io.Writer = null,
};

View File

@@ -115,7 +115,8 @@ fn run(allocator: Allocator, main_arena: Allocator) !void {
.dump_mode = opts.dump_mode,
.dump = .{
.strip = opts.strip,
.with_base = opts.withbase,
.with_base = opts.with_base,
.with_frames = opts.with_frames,
},
};