Skip to content

Fix display:contents transparency in box-tree construction#453

Open
UMCEKO wants to merge 3 commits into
DioxusLabs:mainfrom
UMCEKO:pr/display-contents
Open

Fix display:contents transparency in box-tree construction#453
UMCEKO wants to merge 3 commits into
DioxusLabs:mainfrom
UMCEKO:pr/display-contents

Conversation

@UMCEKO

@UMCEKO UMCEKO commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Stacked on #452 (contains its commit; will rebase once it lands).

display: contents is transparent for box generation, but three places in collect_layout_children treated the contents element as opaque. Together they made a common Dioxus/Tailwind structure (a class="contents" shell between a scroll container and absolutely-positioned pages) lay out as zero-size or content-size boxes. Each fix is covered by a test in packages/blitz-html/tests/display_contents.rs:

  1. Classification didn't recurse into contents children. A container whose in-flow children were all contents wrappers classified as all_inline (the existing TODO: make "all_inline" detection work in the presence of display:contents) and became an inline root, embedding hoisted block children as zero-width inline boxes. The scan now recurses through contents children; the contents node itself casts no vote — including not clearing all_out_of_flow, so a container whose only box-generating descendants are out-of-flow takes the out-of-flow path instead of becoming an inline root.

  2. The DisplayInside::Contents arm hoisted the wrong generation. It called collect_layout_children on each child, which pushes the child's layout children — so leaf children contributed nothing and vanished from the layout tree. Extracted push_hoisted_children_and_pseudos, which pushes the children themselves and recurses only through nested contents nodes; the arm also gains ::before/::after support. The same helper now serves the all_out_of_flow early-return, which previously pushed the container's direct children — a contents node holding the out-of-flow elements was itself pushed as a layout box, and an absolute inset:0 child then sized against it instead of the real containing block (content-sized: a real-app page measured 1745x50517 inside a 1004x1251 scroller, leaving nothing scrollable).

  3. Whitespace-only anonymous-block cleanup corrupted the child list. It removed the anon block with layout_children.pop(), assuming it was the most recent entry. Hoisted contents children can land after it, so pop() dropped a real child and left the removed anon block's stale id in the list — a slab invalid key panic in resolve_layout_children. Removal is now by identity.

WPT css/css-grid + css/css-flexbox: 1154 tests pass before and after.

@Phirios

This comment was marked as resolved.

@Phirios

This comment was marked as resolved.

UMCEKO added 3 commits June 16, 2026 01:29
Three related fixes, covered by packages/blitz-html/tests/display_contents.rs:

1. The inline-vs-block classification in collect_layout_children treated a
   display:contents child as opaque (only setting has_contents), so a
   container whose in-flow children were all contents wrappers classified
   as all_inline and became an inline root, embedding hoisted BLOCK
   children as zero-width inline boxes.  Classification now recurses into
   contents children (work-stack), since they are transparent for box
   generation.

2. The DisplayInside::Contents arm hoisted each child's LAYOUT CHILDREN
   (via collect_layout_children) instead of the children themselves,
   skipping a generation: leaf children contributed nothing and
   disappeared from the layout tree.  It now pushes the children directly,
   recursing only through nested contents nodes (comments and whitespace
   filtered like the other collection paths).

3. The whitespace-only anonymous-block cleanup removed the anon block with
   layout_children.pop(), assuming it was the most recent entry.  Hoisted
   contents children can land after it, so pop() dropped a real child and
   left the removed anon block's stale id in the list (slab 'invalid key'
   panic in resolve_layout_children).  Remove by identity instead.
…casts no classification vote

Two refinements to contents transparency in collect_layout_children:

1. The all_out_of_flow early-return pushed the container's DIRECT children,
   so a display:contents node holding the out-of-flow elements was itself
   pushed as a layout box and the abspos children were laid out against it
   (content-sized, e.g. 0x5000) instead of stretching to the real
   containing block. Extracted push_hoisted_children_and_pseudos (also
   reused by the DisplayInside::Contents arm, which gains ::before/::after
   support) and used it there.

2. The classification scan cleared all_out_of_flow for any contents child.
   Transparency means the contents node casts no vote — its children
   (already visited via the work-stack) decide. A container whose only
   box-generating descendants are out-of-flow now correctly takes the
   all_out_of_flow path instead of becoming an inline root.

Repro shape (kopuz route shell): scroller > [comment, contents > abspos
inset:0 page] — the page sized to its content (no scroll capacity, page
50k px tall) instead of the scroller's padding box.
WPT css-grid+css-flexbox: no changes (1154 passes before and after).
@UMCEKO UMCEKO force-pushed the pr/display-contents branch from 057ff20 to 818a19f Compare June 15, 2026 22:30
@UMCEKO UMCEKO marked this pull request as ready for review June 15, 2026 22:31
@UMCEKO

UMCEKO commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

Rebased the mac ci fix

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.

2 participants