mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-08-02 18:59:36 -04:00
Merge pull request #2796 from lightpanda-io/strip-mode-invisible
cli: add strip-mode invisible
This commit is contained in:
@@ -326,6 +326,15 @@ pub fn hasDisplayNone(self: *StyleManager, el: *Element) bool {
|
||||
return self.isElementHidden(el, .{});
|
||||
}
|
||||
|
||||
/// Computed display:none coming only from inline style or an author stylesheet
|
||||
/// rule — the UA stylesheet's hidden elements (<head>, <script>, [hidden], …)
|
||||
/// are NOT counted, so document scaffolding is preserved. Used by the HTML
|
||||
/// dump's "invisible" strip mode.
|
||||
pub fn hasAuthorDisplayNone(self: *StyleManager, el: *Element) bool {
|
||||
self.rebuildIfDirty() catch return false;
|
||||
return self.isElementHidden(el, .{ .ua_display_none = false });
|
||||
}
|
||||
|
||||
/// Centralizes UA-stylesheet display:none truth so `getComputedStyle().display`
|
||||
/// (via `hasDisplayNone`) and `el.checkVisibility()` (via `isHidden`) agree.
|
||||
/// Spec: HTML Rendering §15.3.1 "Hidden elements".
|
||||
@@ -523,8 +532,10 @@ fn isElementHidden(self: *StyleManager, el: *Element, options: CheckVisibilityOp
|
||||
// element — per CSS Cascade §6.1 any normal-origin author rule beats UA
|
||||
// origin regardless of specificity, so `.x { display: flex }` on a
|
||||
// `<div class="x" hidden>` must report visible.
|
||||
if (options.check_display and display_priority == 0) {
|
||||
if (matchesUaDisplayNoneRule(el)) display_none = true;
|
||||
if (options.check_display and options.ua_display_none and display_priority == 0) {
|
||||
if (matchesUaDisplayNoneRule(el)) {
|
||||
display_none = true;
|
||||
}
|
||||
}
|
||||
|
||||
return (display_none orelse false) or (visibility_hidden orelse false) or (opacity_zero orelse false);
|
||||
@@ -846,6 +857,7 @@ const CheckVisibilityOptions = struct {
|
||||
check_display: bool = true,
|
||||
check_visibility: bool = false,
|
||||
check_opacity: bool = false,
|
||||
ua_display_none: bool = true,
|
||||
};
|
||||
|
||||
// Inline styles always win over stylesheets - use max u64 as sentinel
|
||||
|
||||
@@ -30,10 +30,11 @@ pub const Opts = struct {
|
||||
strip: Opts.Strip = .{},
|
||||
shadow: Opts.Shadow = .rendered,
|
||||
|
||||
pub const Strip = packed struct(u3) {
|
||||
pub const Strip = packed struct(u4) {
|
||||
js: bool = false,
|
||||
ui: bool = false,
|
||||
css: bool = false,
|
||||
invisible: bool = false,
|
||||
};
|
||||
|
||||
pub const Shadow = enum {
|
||||
@@ -99,7 +100,7 @@ fn _deep(node: *Node, opts: Opts, comptime force_slot: bool, writer: *std.Io.Wri
|
||||
}
|
||||
},
|
||||
.element => |el| {
|
||||
if (shouldStripElement(el, opts)) {
|
||||
if (shouldStripElement(el, opts, frame)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -265,7 +266,12 @@ fn isVoidElement(el: *const Node.Element) bool {
|
||||
};
|
||||
}
|
||||
|
||||
fn shouldStripElement(el: *const Node.Element, opts: Opts) bool {
|
||||
fn shouldStripElement(el: *Node.Element, opts: Opts, frame: *Frame) bool {
|
||||
// Fast path: with no strip flags set (every innerHTML/outerHTML call)
|
||||
if (@as(u4, @bitCast(opts.strip)) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const tag_name = el.getTagNameDump();
|
||||
|
||||
if (opts.strip.js) {
|
||||
@@ -306,6 +312,10 @@ fn shouldStripElement(el: *const Node.Element, opts: Opts) bool {
|
||||
if (std.mem.eql(u8, tag_name, "iframe")) return true;
|
||||
}
|
||||
|
||||
if (opts.strip.invisible and frame._style_manager.hasAuthorDisplayNone(el)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -358,3 +368,60 @@ fn writeEscapedByte(input: []const u8, index: usize, writer: *std.Io.Writer) ![]
|
||||
}
|
||||
return input[index + 1 ..];
|
||||
}
|
||||
|
||||
const testing = @import("../testing.zig");
|
||||
|
||||
// A fresh page per assertion: `with_base` mutates the document (it inserts a
|
||||
// <base> element), so reusing one frame across opts would leak that mutation
|
||||
// into later dumps.
|
||||
fn expectDump(opts: Opts, expected: []const u8) !void {
|
||||
var frame = try testing.pageTest("dump.html", .{});
|
||||
defer testing.reset();
|
||||
defer frame._session.removePage();
|
||||
|
||||
var aw: std.Io.Writer.Allocating = .init(testing.arena_allocator);
|
||||
try root(frame.window._document, opts, &aw.writer, frame);
|
||||
try testing.expectString(expected, aw.written());
|
||||
}
|
||||
|
||||
test "dump: default dumps the whole document" {
|
||||
try expectDump(.{},
|
||||
\\<!DOCTYPE html>
|
||||
\\<html><head><style>.hidden{display:none}</style><link rel="stylesheet" href="data:text/css,"><script>var a=1;</script></head><body><h1>Title</h1><p class="hidden">secret</p><img><svg></svg><noscript>nojs</noscript><p>visible & well</p></body></html>
|
||||
);
|
||||
}
|
||||
|
||||
test "dump: with_base injects a <base> element" {
|
||||
try expectDump(.{ .with_base = true },
|
||||
\\<!DOCTYPE html>
|
||||
\\<html><head><base base="http://127.0.0.1:9582/src/browser/tests/dump.html"></base><style>.hidden{display:none}</style><link rel="stylesheet" href="data:text/css,"><script>var a=1;</script></head><body><h1>Title</h1><p class="hidden">secret</p><img><svg></svg><noscript>nojs</noscript><p>visible & well</p></body></html>
|
||||
);
|
||||
}
|
||||
|
||||
test "dump: strip.js removes script and noscript" {
|
||||
try expectDump(.{ .strip = .{ .js = true } },
|
||||
\\<!DOCTYPE html>
|
||||
\\<html><head><style>.hidden{display:none}</style><link rel="stylesheet" href="data:text/css,"></head><body><h1>Title</h1><p class="hidden">secret</p><img><svg></svg><p>visible & well</p></body></html>
|
||||
);
|
||||
}
|
||||
|
||||
test "dump: strip.css removes style and stylesheet links" {
|
||||
try expectDump(.{ .strip = .{ .css = true } },
|
||||
\\<!DOCTYPE html>
|
||||
\\<html><head><script>var a=1;</script></head><body><h1>Title</h1><p class="hidden">secret</p><img><svg></svg><noscript>nojs</noscript><p>visible & well</p></body></html>
|
||||
);
|
||||
}
|
||||
|
||||
test "dump: strip.ui removes css plus visual elements" {
|
||||
try expectDump(.{ .strip = .{ .ui = true } },
|
||||
\\<!DOCTYPE html>
|
||||
\\<html><head><script>var a=1;</script></head><body><h1>Title</h1><p class="hidden">secret</p><noscript>nojs</noscript><p>visible & well</p></body></html>
|
||||
);
|
||||
}
|
||||
|
||||
test "dump: strip.invisible removes author display:none elements" {
|
||||
try expectDump(.{ .strip = .{ .invisible = true } },
|
||||
\\<!DOCTYPE html>
|
||||
\\<html><head><style>.hidden{display:none}</style><link rel="stylesheet" href="data:text/css,"><script>var a=1;</script></head><body><h1>Title</h1><img><svg></svg><noscript>nojs</noscript><p>visible & well</p></body></html>
|
||||
);
|
||||
}
|
||||
|
||||
1
src/browser/tests/dump.html
Normal file
1
src/browser/tests/dump.html
Normal file
@@ -0,0 +1 @@
|
||||
<!DOCTYPE html><html><head><style>.hidden{display:none}</style><link rel="stylesheet" href="data:text/css,"><script>var a=1;</script></head><body><h1>Title</h1><p class="hidden">secret</p><img><svg></svg><noscript>nojs</noscript><p>visible & well</p></body></html>
|
||||
@@ -63,10 +63,11 @@
|
||||
\\ Comma-separated list of tag groups to remove from dump.
|
||||
\\ Defaults to no-strip.
|
||||
\\ Allowed values:
|
||||
\\ js script and link[as=script, rel=preload].
|
||||
\\ ui Includes img, picture, video, CSS and SVG.
|
||||
\\ css Includes style and link[rel=stylesheet].
|
||||
\\ full Strip everything.
|
||||
\\ js Script and link[as=script, rel=preload].
|
||||
\\ ui Includes img, picture, video, CSS and SVG.
|
||||
\\ css Includes style and link[rel=stylesheet].
|
||||
\\ invisible Best-effort (e.g. display:none) hidden elements
|
||||
\\ full Strip everything.
|
||||
\\ --json
|
||||
\\ Capture and print the status of the fetch in a JSON string and output
|
||||
\\ it. When used with --dump <MODE> this will wrap the dumped content
|
||||
|
||||
Reference in New Issue
Block a user