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, url: [:0]const u8,
dump_mode: ?DumpFormat = null, dump_mode: ?DumpFormat = null,
common: Common = .{}, common: Common = .{},
withbase: bool = false, with_base: bool = false,
with_frames: bool = false,
strip: dump.Opts.Strip = .{}, 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_base Add a <base> tag in dump. Defaults to false.
\\ \\
\\--with_frames Includes the contents of iframes. Defaults to false.
\\
++ common_options ++ ++ common_options ++
\\ \\
\\serve command \\serve command
@@ -440,6 +443,10 @@ fn inferMode(opt: []const u8) ?RunMode {
return .fetch; return .fetch;
} }
if (std.mem.eql(u8, opt, "--with_frames")) {
return .fetch;
}
if (std.mem.eql(u8, opt, "--host")) { if (std.mem.eql(u8, opt, "--host")) {
return .serve; return .serve;
} }
@@ -539,7 +546,8 @@ fn parseFetchArgs(
args: *std.process.ArgIterator, args: *std.process.ArgIterator,
) !Fetch { ) !Fetch {
var dump_mode: ?DumpFormat = null; 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 url: ?[:0]const u8 = null;
var common: Common = .{}; var common: Common = .{};
var strip: dump.Opts.Strip = .{}; var strip: dump.Opts.Strip = .{};
@@ -570,7 +578,12 @@ fn parseFetchArgs(
} }
if (std.mem.eql(u8, "--with_base", opt)) { 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; continue;
} }
@@ -626,7 +639,8 @@ fn parseFetchArgs(
.dump_mode = dump_mode, .dump_mode = dump_mode,
.strip = strip, .strip = strip,
.common = common, .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); self.js = try browser.env.createContext(self);
errdefer self.js.deinit(); errdefer self.js.deinit();
document._page = self;
if (comptime builtin.is_test == false) { if (comptime builtin.is_test == false) {
// HTML test runner manually calls these as necessary // HTML test runner manually calls these as necessary
try self.js.scheduler.add(session.browser, struct { try self.js.scheduler.add(session.browser, struct {

View File

@@ -20,16 +20,15 @@ const std = @import("std");
const Page = @import("Page.zig"); const Page = @import("Page.zig");
const Node = @import("webapi/Node.zig"); const Node = @import("webapi/Node.zig");
const Slot = @import("webapi/element/html/Slot.zig"); const Slot = @import("webapi/element/html/Slot.zig");
const IFrame = @import("webapi/element/html/IFrame.zig");
pub const RootOpts = struct { const IS_DEBUG = @import("builtin").mode == .Debug;
with_base: bool = false,
strip: Opts.Strip = .{},
shadow: Opts.Shadow = .rendered,
};
pub const Opts = struct { pub const Opts = struct {
strip: Strip = .{}, with_base: bool = false,
shadow: Shadow = .rendered, with_frames: bool = false,
strip: Opts.Strip = .{},
shadow: Opts.Shadow = .rendered,
pub const Strip = struct { pub const Strip = struct {
js: bool = false, 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| { if (doc.is(Node.Document.HTMLDocument)) |html_doc| {
blk: { blk: {
// Ideally we just render the doctype which is part of the document // 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 { 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)) { if (!isVoidElement(el)) {
try writer.writeAll("</"); try writer.writeAll("</");
try writer.writeAll(el.getTagNameDump()); try writer.writeAll(el.getTagNameDump());

View File

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

View File

@@ -36,7 +36,7 @@ const IS_DEBUG = @import("builtin").mode == .Debug;
pub const FetchOpts = struct { pub const FetchOpts = struct {
wait_ms: u32 = 5000, wait_ms: u32 = 5000,
dump: dump.RootOpts, dump: dump.Opts,
dump_mode: ?Config.DumpFormat = null, dump_mode: ?Config.DumpFormat = null,
writer: ?*std.Io.Writer = 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_mode = opts.dump_mode,
.dump = .{ .dump = .{
.strip = opts.strip, .strip = opts.strip,
.with_base = opts.withbase, .with_base = opts.with_base,
.with_frames = opts.with_frames,
}, },
}; };