Skip to content

Fix bare text nodes becoming layout children through display:contents - #658

Open
nicoburns wants to merge 2 commits into
mainfrom
devin/1786205047-display-contents-text-hoisting
Open

Fix bare text nodes becoming layout children through display:contents#658
nicoburns wants to merge 2 commits into
mainfrom
devin/1786205047-display-contents-text-hoisting

Conversation

@nicoburns

@nicoburns nicoburns commented Aug 8, 2026

Copy link
Copy Markdown
Member

Summary

Fixes the `unrounded_layout` is not available on this node kind panic (node/node.rs:160) hit by 3 css/css-display/display-contents-* WPT tests. Root cause: several box-construction paths pushed the text children of display: contents nodes (or a contents node itself) directly as layout children, so Taffy's round_layout walked into a bare Text node, which has no layout fields.

Changes in construct.rs:

  • collect_complex_layout_children now recurses transparently through display: contents children itself (new visit_child helper) instead of delegating to collect_layout_children, so hoisted text goes through the same needs_wrap anonymous-block wrapping as any other text child (e.g. in flex containers). Replaced elements and form controls (input/textarea) keep their special handling, since display: contents does not apply to them (contents are suppressed, not hoisted).
  • Flow classification now also classifies the children of display: contents ::before/::after pseudos, so generated text makes the container an inline root instead of leaving the contents pseudo to be pushed as a box whose text child leaks out unwrapped.
  • The flex/grid has_text_node_or_contents check now includes ::before/::after pseudos.
  • push_hoisted_children_and_pseudos now applies the contents check to pseudos as well as regular children.
  • Table-internal boxes laid out outside a table context (e.g. a standalone display: table-row) now use the complex path, wrapping text and recursing through contents instead of pushing raw text/contents nodes.

WPT: the 3 crashing tests now run without panicking (they FAIL for unrelated reasons); together with #656 the full css run goes from 244 to 147 crashes (many other first-letter/display:contents crashes share this root cause), +29 passing, no previously-passing test regressed, no new crashes.

Link to Devin session: https://dioxus.staging.devinenterprise.com/sessions/dde09f37a79d43a2ac7993911b21d308
Requested by: @nicoburns

WPT results

20 newly passing, 0 newly failing (net +20), 71 other status changes.

Full diff (91 changed tests)
! Crash => Fail css/CSS2/bidi-009.xht
! Crash => Fail css/CSS2/bidi-text/bidi-009a.xht
! Crash => Fail css/CSS2/bidi-text/bidi-009b.xht
! Crash => Fail css/CSS2/generated-content/after-content-display-008.xht
! Crash => Fail css/CSS2/generated-content/after-content-display-009.xht
! Crash => Fail css/CSS2/generated-content/after-content-display-010.xht
! Crash => Fail css/CSS2/generated-content/after-content-display-011.xht
! Crash => Fail css/CSS2/generated-content/after-content-display-012.xht
! Crash => Fail css/CSS2/generated-content/after-content-display-013.xht
! Crash => Fail css/CSS2/generated-content/before-after-display-types-001.xht
! Crash => Fail css/CSS2/generated-content/before-after-table-parts-001.xht
! Crash => Fail css/CSS2/generated-content/before-content-display-008.xht
! Crash => Fail css/CSS2/generated-content/before-content-display-009.xht
! Crash => Fail css/CSS2/generated-content/before-content-display-010.xht
! Crash => Fail css/CSS2/generated-content/before-content-display-011.xht
! Crash => Fail css/CSS2/generated-content/before-content-display-012.xht
! Crash => Fail css/CSS2/generated-content/before-content-display-013.xht
! Crash => Fail css/CSS2/tables/table-anonymous-objects-089.xht
! Crash => Fail css/CSS2/tables/table-anonymous-objects-090.xht
! Crash => Fail css/CSS2/tables/table-anonymous-objects-103.xht
! Crash => Fail css/CSS2/tables/table-anonymous-objects-104.xht
! Crash => Fail css/CSS2/tables/table-anonymous-objects-105.xht
! Crash => Fail css/CSS2/tables/table-anonymous-objects-106.xht
! Crash => Fail css/CSS2/tables/table-anonymous-objects-109.xht
! Crash => Fail css/CSS2/tables/table-anonymous-objects-110.xht
! Crash => Fail css/CSS2/tables/table-anonymous-objects-111.xht
! Crash => Fail css/CSS2/tables/table-anonymous-objects-112.xht
! Crash => Fail css/CSS2/tables/table-anonymous-objects-159.xht
! Crash => Fail css/CSS2/tables/table-anonymous-objects-160.xht
! Crash => Fail css/CSS2/tables/table-anonymous-objects-197.xht
! Crash => Fail css/CSS2/tables/table-anonymous-objects-198.xht
! Crash => Fail css/CSS2/tables/table-anonymous-objects-201.xht
! Crash => Fail css/CSS2/tables/table-anonymous-objects-202.xht
! Crash => Fail css/css-break/table/repeated-section/fixedpos-in-footer-forced-break-print.html
+ Crash => Pass css/css-contain/contain-size-007.html
+ Crash => Pass css/css-contain/contain-size-008.html
+ Crash => Pass css/css-contain/contain-size-009.html
+ Crash => Pass css/css-contain/contain-size-010.html
+ Crash => Pass css/css-display/display-contents-before-after-001.html
! Crash => Fail css/css-display/display-contents-before-after-003.html
+ Crash => Pass css/css-display/display-contents-button.html
+ Crash => Pass css/css-display/display-contents-details.html
! Crash => Fail css/css-display/display-contents-dynamic-before-after-001.html
! Crash => Fail css/css-display/display-contents-dynamic-before-after-first-letter-001.html
+ Crash => Pass css/css-display/display-contents-dynamic-flex-001-inline.html
+ Crash => Pass css/css-display/display-contents-dynamic-flex-001-none.html
! Crash => Fail css/css-display/display-contents-dynamic-flex-002-inline.html
! Crash => Fail css/css-display/display-contents-dynamic-flex-002-none.html
! Crash => Fail css/css-display/display-contents-dynamic-flex-003-inline.html
! Crash => Fail css/css-display/display-contents-dynamic-flex-003-none.html
! Crash => Fail css/css-display/display-contents-dynamic-inline-flex-001-inline.html
! Crash => Fail css/css-display/display-contents-dynamic-inline-flex-001-none.html
! Crash => Fail css/css-display/display-contents-dynamic-list-001-inline.html
! Crash => Fail css/css-display/display-contents-dynamic-list-001-none.html
! Crash => Fail css/css-display/display-contents-dynamic-table-001-inline.html
! Crash => Fail css/css-display/display-contents-dynamic-table-001-none.html
+ Crash => Pass css/css-display/display-contents-fieldset.html
+ Crash => Pass css/css-display/display-contents-flex-001.html
! Crash => Fail css/css-display/display-contents-flex-002.html
! Crash => Fail css/css-display/display-contents-flex-003.html
+ Crash => Pass css/css-display/display-contents-float-001.html
! Crash => Fail css/css-display/display-contents-inline-flex-001.html
! Crash => Fail css/css-display/display-contents-line-height.html
! Crash => Fail css/css-display/display-contents-list-001.html
+ Crash => Pass css/css-display/display-contents-oof-001.html
+ Crash => Pass css/css-display/display-contents-oof-002.html
! Crash => Fail css/css-display/display-contents-shadow-dom-1.html
! Crash => Fail css/css-display/display-contents-table-001.html
! Crash => Fail css/css-display/display-contents-table-003.html
! Crash => Fail css/css-display/display-contents-text-inherit.html
+ Crash => Pass css/css-display/display-contents-text-only-001.html
! Crash => Fail css/css-display/run-in/run-in-contains-inline-005.xht
! Crash => Fail css/css-display/run-in/run-in-contains-table-row-001.xht
! Crash => Fail css/css-display/run-in/run-in-contains-table-row-group-001.xht
+ Crash => Pass css/css-display/run-in/run-in-table-row-between-001.xht
! Crash => Fail css/css-display/run-in/run-in-table-row-between-003.xht
! Crash => Fail css/css-lists/inline-list-with-table-child.html
! Crash => Fail css/css-multicol/table/table-cell-content-change-000.html
! Crash => Fail css/css-multicol/table/table-cell-content-change-001.html
! Crash => Fail css/css-ruby/ruby-layout-internal-boxes.html
! Crash => Fail css/css-tables/insert-after-col.html
! Crash => Fail css/css-text/text-autospace/text-autospace-elements-002.html
! Crash => Fail css/css-text/text-autospace/text-autospace-elements-003.html
! Crash => Fail css/css-text/text-autospace/text-autospace-elements-004.html
! Crash => Fail css/css-text/text-autospace/text-autospace-elements-006.html
! Crash => Fail css/css-text/text-autospace/text-autospace-elements-007.html
+ Crash => Pass css/css-transforms/transform-transformable-table-footer-group.html
+ Crash => Pass css/css-transforms/transform-transformable-table-header-group.html
+ Crash => Pass css/css-transforms/transform-transformable-table-row-group.html
+ Crash => Pass css/css-transforms/transform-transformable-table-row.html
! Crash => Fail css/css-ui/text-overflow-ruby.html

Generated by the WPT workflow.

display:none children generate no boxes, but classify_flow_children was
counting them as in-flow inline content. A container whose only children
are display:none (e.g. an <html> root with display:none) was classified
as all-inline and queued for inline layout construction -- panicking when
that container was the Document node, which has no element data.
display:contents is transparent for box generation, but several
construction paths were pushing the text children of contents nodes (or
the contents node itself) directly as layout children. Taffy then walked
into a bare text node during round_layout and panicked with
"unrounded_layout is not available on this node kind".

- collect_complex_layout_children now recurses transparently through
  display:contents children itself, so hoisted text goes through the
  same anonymous-block wrapping as any other text child (e.g. in flex
  containers). Replaced elements and form controls keep their special
  handling since display:contents does not apply to them.
- Flow classification now also classifies the children of
  display:contents ::before/::after pseudos, so generated text makes the
  container an inline root instead of leaving the contents pseudo to be
  pushed as a box.
- The flex/grid text-or-contents check now includes ::before/::after
  pseudos.
- push_hoisted_children_and_pseudos now applies the display:contents
  check to the pseudos as well as regular children.
- Table-internal boxes laid out outside a table context (e.g. a
  standalone display:table-row) now use the complex path, wrapping text
  and recursing through contents instead of pushing them raw.
@nicoburns nicoburns self-assigned this Aug 8, 2026
@staging-devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

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.

1 participant