Skip to content

fix(render): give flattened inline wrappers real DOM geometry - #727

Open
Vrejf wants to merge 1 commit into
h4ckf0r0day:mainfrom
Vrejf:pr/inline-geometry
Open

fix(render): give flattened inline wrappers real DOM geometry#727
Vrejf wants to merge 1 commit into
h4ckf0r0day:mainfrom
Vrejf:pr/inline-geometry

Conversation

@Vrejf

@Vrejf Vrejf commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Fixes #722.

A plain inline wrapper whose run holds a replaced element (<label>x</label><input>, <span>x</span><img>) fails try_build_run, so build_mixed_block takes the mixed-run path and is_flattenable_inline splices the wrapper's children into the parent's taffy children. The wrapper owns no box, nothing records a rect for it, and getBoundingClientRect() returned 0x0, so Puppeteer/Playwright handle.click() failed with "not clickable".

The rect is now synthesized after layout from the wrapper's rendered descendants, grouped per line. Out-of-flow and floated descendants are excluded, since they are not part of the inline box; without that an absolutely positioned child at left:900px turned a 28px span into a 918px one. Positioned inlines keep their own box because they are a containing block for absolute descendants. A padded inline now keeps a real box instead of being flattened, matching how a bordered inline is already handled.

The walk is iterative and bounded by a node budget rather than by depth, and wrappers are resolved deepest-first so each subtree is visited once and a nested wrapper contributes its own line fragments rather than a collapsed union.

Verification

Measured against headless Chrome 145 unless stated.

probe Chrome before after
<div><label>toggle</label><input></div> 48x17 0x0 47x18
absolute descendant inside the wrapper 28px wide 918px 30px
wrapped inline, getClientRects() 2 4 (one per word) 2
padded inline width 43px 27px 47px
70 nested inline wrappers, outermost 39x19 0x0 38x18
  • cargo nextest run --release --features render --no-fail-fast: 1488 run, 1486 passed. The two failures are intersection_observer_* tests that fail on unmodified main as well, at 2/10 and 3/10 over ten runs each on both trees.
  • Obstacle course: 32/33, failing observer-intersection identically on main.
  • All 69 render-repros fixtures produce metrics byte-identical to the pre-change build, so nothing repaints differently.
  • Interleaved performance, same build and fixtures: 58 vs 55 ms median on a fixture, 1358 vs 1351 ms on a synthetic page with 3000 flattened wrappers. Inside the noise floor.

No render-repros fixture is included: the change is DOM geometry and produces no pixel difference, so a screenshot fixture would assert nothing. Coverage is in layout_test.rs.

Known limitations

  • An inline wrapping a block child reports one client rect where Chromium reports one per fragment. The bounding box matches.
  • An empty inline reports 0x0 where Chromium reports 0x<line height>, and an inline wrapping only a replaced element gets that element's height rather than the line box's. Both need the inline strut that synthesize_ordinary_inline_fragments derives from the text engine.

A plain inline wrapper whose run holds a replaced element fails
try_build_run, so build_mixed_block takes the mixed-run path and
is_flattenable_inline splices the wrapper's children into the parent's
taffy children. The wrapper owns no box and nothing records a rect for
it, so getBoundingClientRect returned 0x0 and a coordinate click had no
target.

Synthesize the rect after layout from the wrapper's rendered descendants,
grouped per line. Out-of-flow and floated descendants are excluded, and
positioned inlines keep their own box because they are a containing block
for absolute descendants. A padded inline now keeps a real box instead of
being flattened, matching the existing treatment of a bordered inline.

An inline wrapping a block child still reports one client rect where
Chromium reports one per fragment; the bounding box matches.

Fixes h4ckf0r0day#722.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

getBoundingClientRect returns 0x0 for inline elements that share a run with a replaced sibling (label + input pattern)

1 participant