Files
obscura/crates/obscura-render/tests/layout_test.rs
T
SGavrl 920977776c Fix render engine: CSS grid, cascade correctness, SVG, inline flow
The render layer had no CSS Grid support, so Vector 2022's page layout
  (three nested grids) collapsed to a vertical stack with the article
  landing tens of thousands of pixels down the page. Wikipedia rendered
  as a near-blank page and Hacker News only worked through per-site
  hardcoded CSS overrides.

  Removed the HN-specific hardcoding and fixed the general engine instead:

  - CSS Grid layout (grid-template-areas/columns/rows via taffy's grid mode)
  - Indexed, specificity-correct cascade in a new css.rs, replacing the
    old test-every-selector-against-every-node scan. Cascade time on
    Wikipedia went from 9.5s to under 200ms.
  - Ancestor bloom-filter fast-rejection for selector matching in
    obscura-dom, needed to make the indexed cascade actually fast on
    descendant combinators.
  - :link/:visited pseudo-classes were previously unparseable, so any
    rule using them (a:link{color:...}, extremely common) silently
    dropped the whole rule from the cascade.
  - overflow:hidden clipping, fixing the pervasive visually-hidden-but-
    accessible pattern used for skip links and screen-reader-only labels.
  - @media parser robustness: it did a literal substring search for
    max-width: and silently dropped the constraint on max-width : 750px
    (space before colon) or max-width:calc(750px - 1px), treating
    mobile-only or narrow-viewport rules as always active. The calc()
    case was hiding Wikipedia's TOC sidebar via a rule meant only for
    narrow screens.
  - Table auto-layout approximation: the trailing auto-width td in a row
    absorbs leftover space, so a width:100% table's unsized last column
    reaches the right edge instead of shrinking to content.
  - Inline formatting context approximation: a block holding inline-level
    children (a paragraph or list item with links) is promoted to a
    wrapping flex row so text and links read as continuous prose instead
    of one fragment per line.
  - Whitespace-only text nodes no longer get a full line-height box. They
    used to stack up as blank rows, turning Wikipedia's collapsed TOC
    subsections and the page header into hundreds of pixels of dead space.
  - SVG rendering via resvg/usvg, plus resolving relative image URLs
    against the page's base URL.
  - background-image support including background-size/background-position
    for correctly sized and positioned icons.
  - Per-screenshot image fetch cache.
  - Fixed the layout-only (no paint feature) build, which didn't compile
    due to an unconditional call into paint-gated code.
2026-07-03 12:04:03 +00:00

85 lines
3.7 KiB
Rust

//! Regression test: a Hacker-News-shaped nested `<table>` layout must come out
//! right using only the general engine (UA defaults + real CSS cascade), with
//! no per-site hardcoded selectors. This guards against reintroducing the
//! site-specific hacks that used to live in obscura-render for this exact markup.
use obscura_dom::tree::NodeData;
use obscura_dom::tree_sink::parse_html;
use obscura_render::layout_dom;
const HN_HTML: &str = r##"
<table border="0" cellpadding="0" cellspacing="0" width="85%" bgcolor="#f6f6ef">
<tr>
<td bgcolor="#ff6600">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="padding:2px">
<tr>
<td style="width:18px;padding-right:4px"><a href="https://news.ycombinator.com"><img src="y18.svg" width="18" height="18"></a></td>
<td style="line-height:12pt; height:10px;"><span class="pagetop"><b class="hnname"><a href="news">Hacker News</a></b> <a href="newest">new</a> | <a href="front">past</a></span></td>
<td style="text-align:right;padding-right:4px;"><span class="pagetop"><a href="login?goto=news">login</a></span></td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<table border="0" cellpadding="0" cellspacing="0">
<tr class="athing submission" id="48761229">
<td align="right" valign="top" class="title"><span class="rank">24.</span></td>
<td valign="top" class="votelinks"><center><a id="up_48761229" href="vote?id=48761229"><div class="votearrow" title="upvote"></div></a></center></td>
<td class="title"><span class="titleline"><a href="https://www.zachtronics.com/exapunks/">Exapunks (2018)</a></span></td>
</tr>
</table>
</td>
</tr>
</table>
"##;
fn find_by_text<'a>(
tree: &'a obscura_dom::tree::DomTree,
layout: &'a obscura_render::DomLayout,
needle: &str,
) -> Option<(obscura_dom::tree::NodeId, &'a obscura_render::Rect)> {
for (id, rect) in &layout.rects {
if let Some(node) = tree.get_node(*id) {
if let NodeData::Text { contents } = &node.data {
if contents.trim() == needle {
return Some((*id, rect));
}
}
}
}
None
}
#[test]
fn hn_shaped_table_lays_out_without_site_hardcoding() {
let tree = parse_html(HN_HTML);
let layout = layout_dom(&tree, (1000.0, 1000.0));
// Every element got a rect; the tree isn't just being dropped.
assert!(layout.rects.len() > 10, "expected many laid-out elements, got {}", layout.rects.len());
// "Hacker News" (a bold link in the top bar) sits above the first
// headline row ("Exapunks (2018)"): normal top-to-bottom flow, not a
// hardcoded absolute position.
let (_, brand_rect) = find_by_text(&tree, &layout, "Hacker News").expect("brand text laid out");
let (_, headline_rect) = find_by_text(&tree, &layout, "Exapunks (2018)").expect("headline text laid out");
assert!(
headline_rect.y > brand_rect.y,
"headline should be below the header bar: brand.y={} headline.y={}",
brand_rect.y,
headline_rect.y
);
// Within the header row, "login" (right-aligned cell) sits to the right
// of "Hacker News" (left cell) — plain flex/table layout, not a magic
// per-class x-offset.
let (_, login_rect) = find_by_text(&tree, &layout, "login").expect("login text laid out");
assert!(
login_rect.x > brand_rect.x,
"login cell should be right of the brand: brand.x={} login.x={}",
brand_rect.x,
login_rect.x
);
}