Skip to content

Separate stacking-context membership from layout/geometry ancestry - #807

Open
nicoburns wants to merge 11 commits into
devin/1787748926-taffy-oof-hoisting-lateboundfrom
devin/1787758076-oof-paint-stacking
Open

Separate stacking-context membership from layout/geometry ancestry#807
nicoburns wants to merge 11 commits into
devin/1787748926-taffy-oof-hoisting-lateboundfrom
devin/1787758076-oof-paint-stacking

Conversation

@nicoburns

@nicoburns nicoburns commented Aug 26, 2026

Copy link
Copy Markdown
Member

Summary

Replaces the baked-position OOF paint hoisting (from the base branch) with a browser-like architecture that keeps three relationships separate: structural/layout ancestry (layout_parent / layout_children, never mutated for paint), Taffy containing-block geometry (hoisted_children + new inverse oof_containing_block), and paint order (a new derived stacking structure).

Stacking membership (layout/stacking.rs, run post-layout, gated by STACKING_DAMAGE):

  • Walks the structural tree; each node gets paint_children (in-flow + floats only) and, if it's a real stacking context, a StackingContext { entries, negative_z_count } of its stacked descendants.
  • Node::is_stacked vs Node::is_stacking_context_root distinguish z-index: auto stacking containers (positioned boxes that contribute an entry but whose stacked descendants promote into the same enclosing context) from atomic real stacking contexts (non-auto z-index, opacity/filter/mask/clip-path, transform-ish properties via establishes_fixed_containing_block, mix-blend-mode, isolation, sticky/fixed, CSS animations).
  • OOF boxes are classified at their structural position, so effect ancestors (e.g. opacity) that are not the containing block still apply to them.

Geometry at paint time (no baked positions):

  • Node::paint_geometry_parent() = OOF containing block if out-of-flow, else layout_parent.
  • Node::stacked_entry_offset() resolves an entry's offset dynamically by walking geometry-parent chains from the entry and the stacking-context root to their common ancestor, accumulating layout locations, scroll offsets, and intermediate overflow clips.
  • stacked_entry_escapes_clip() lets entries whose geometry chain bypasses a clipping stacking context (e.g. a fixed box inside a scrollable fixed ancestor) paint outside that ancestor's clip layer.

Painting/hit testing: blitz-paint draws negative entries before content and non-negative entries after (draw_stacked_entries split on negative_z_count); hit_inner traverses the exact same entries in reverse. The old hoisted-paint-children plumbing, layout_parent repointing in attach_hoisted_children, and fixed-scroll hit-test special cases are removed.

Also fixes flex/grid order sorting to ignore out-of-flow children (they aren't flex/grid items, per flexbox-paint-ordering-003.html).

WPT

vs the base branch: css-position +5, css-flexbox +3, css-grid +1, CSS2/positioning +4, css-transforms +4/-1, CSS2/zindex and CSS2/abspos unchanged. The one new failure, css/css-transforms/perspective-split-by-zero-w.html, is a pre-existing preserve-3d/perspective rendering limitation that was previously masked: the test's reference hides a red patch under a 3D-transformed div that Blitz can't fully render; the old code incorrectly painted the ref's negative-z patch below the canvas background, making test and ref equally wrong.

Tests

New paint_order.rs cases: abspos under an opacity ancestor that is not its CB keeps the opacity effect; negative-z abspos paints below in-flow sibling content. Existing oof_dynamic_cb.rs suite passes.

Link to Devin session: https://dioxus.staging.devinenterprise.com/sessions/2e5b5e83db014e14be353d6e89c14433
Open in Devin Desktop: https://dioxus.staging.devinenterprise.com/desktop/session/2e5b5e83db014e14be353d6e89c14433?variant=devin-insiders
Requested by: @nicoburns

WPT results

138 newly passing, 44 newly failing (net +94).

Full diff (182 changed tests)
+ Fail => Pass css/CSS2/abspos/static-inside-inline-002.html
+ Fail => Pass css/CSS2/backgrounds/background-bg-pos-204.xht
- Pass => Fail css/CSS2/backgrounds/background-position-002.xht
+ Fail => Pass css/CSS2/backgrounds/background-root-023.xht
+ Fail => Pass css/CSS2/box-display/containing-block-007.xht
+ Fail => Pass css/CSS2/box-display/containing-block-008.xht
+ Fail => Pass css/CSS2/box-display/containing-block-009.xht
+ Fail => Pass css/CSS2/box-display/containing-block-010.xht
+ Fail => Pass css/CSS2/floats-clear/clear-applies-to-012.xht
+ Fail => Pass css/CSS2/floats-clear/clear-applies-to-014.xht
+ Fail => Pass css/CSS2/floats-clear/clear-clearance-calculation-004.xht
+ Fail => Pass css/CSS2/floats-clear/clear-clearance-calculation-005.xht
+ Fail => Pass css/CSS2/floats-clear/floats-019.xht
+ Fail => Pass css/CSS2/generated-content/before-after-positioned-002.html
+ Fail => Pass css/CSS2/generated-content/before-after-positioned-003.html
+ Fail => Pass css/CSS2/margin-padding-clear/margin-collapse-037.xht
+ Fail => Pass css/CSS2/positioning/absolute-non-replaced-width-026.xht
+ Fail => Pass css/CSS2/positioning/abspos-014.xht
+ Fail => Pass css/CSS2/positioning/abspos-015.xht
+ Fail => Pass css/CSS2/positioning/abspos-016.xht
+ Fail => Pass css/CSS2/positioning/abspos-017.xht
+ Fail => Pass css/CSS2/positioning/abspos-018.xht
+ Fail => Pass css/CSS2/positioning/abspos-022.xht
+ Fail => Pass css/CSS2/positioning/abspos-026.xht
+ Fail => Pass css/CSS2/positioning/abspos-containing-block-005.xht
+ Fail => Pass css/CSS2/positioning/abspos-containing-block-006.xht
+ Fail => Pass css/CSS2/positioning/abspos-overflow-001.xht
+ Fail => Pass css/CSS2/positioning/abspos-overflow-002.xht
+ Fail => Pass css/CSS2/positioning/abspos-overflow-003.xht
+ Fail => Pass css/CSS2/positioning/abspos-overflow-004.xht
+ Fail => Pass css/CSS2/positioning/abspos-overflow-005.xht
+ Fail => Pass css/CSS2/positioning/abspos-overflow-006.xht
+ Fail => Pass css/CSS2/positioning/abspos-overflow-007.xht
+ Fail => Pass css/CSS2/positioning/abspos-overflow-008.xht
+ Fail => Pass css/CSS2/positioning/abspos-overflow-009.xht
+ Fail => Pass css/CSS2/positioning/abspos-overflow-012.xht
+ Fail => Pass css/CSS2/positioning/dynamic-top-change-003.xht
+ Fail => Pass css/CSS2/positioning/left-applies-to-009.xht
+ Fail => Pass css/CSS2/positioning/left-applies-to-012.xht
+ Fail => Pass css/CSS2/positioning/left-applies-to-013.xht
+ Fail => Pass css/CSS2/positioning/left-applies-to-014.xht
+ Fail => Pass css/CSS2/positioning/position-fixed-007.xht
+ Fail => Pass css/CSS2/positioning/position-static-001.xht
+ Fail => Pass css/CSS2/positioning/relpos-calcs-001.xht
+ Fail => Pass css/CSS2/positioning/relpos-calcs-002.xht
+ Fail => Pass css/CSS2/positioning/relpos-calcs-007.xht
+ Fail => Pass css/CSS2/positioning/right-applies-to-009.xht
+ Fail => Pass css/CSS2/positioning/right-applies-to-012.xht
+ Fail => Pass css/CSS2/positioning/right-applies-to-013.xht
+ Fail => Pass css/CSS2/positioning/right-applies-to-014.xht
+ Fail => Pass css/CSS2/positioning/top-applies-to-009.xht
+ Fail => Pass css/CSS2/positioning/top-applies-to-012.xht
+ Fail => Pass css/CSS2/positioning/top-applies-to-013.xht
+ Fail => Pass css/CSS2/positioning/top-applies-to-014.xht
+ Fail => Pass css/CSS2/tables/separated-border-model-007.xht
+ Fail => Pass css/CSS2/tables/separated-border-model-008.xht
+ Fail => Pass css/CSS2/tables/separated-border-model-009.xht
+ Fail => Pass css/CSS2/visuren/anonymous-boxes-001b.xht
+ Fail => Pass css/CSS2/visuren/inherit-static-offset-001.xht
+ Fail => Pass css/CSS2/visuren/inherit-static-offset-002.xht
+ Fail => Pass css/CSS2/visuren/inherit-static-offset-003.xht
+ Fail => Pass css/CSS2/visuren/position-absolute-percentage-inherit-001.xht
- Pass => Fail css/WOFF2/blocks-extraneous-data-003.xht
- Pass => Fail css/WOFF2/blocks-extraneous-data-004.xht
- Pass => Fail css/WOFF2/blocks-extraneous-data-005.xht
- Pass => Fail css/WOFF2/blocks-extraneous-data-006.xht
- Pass => Fail css/WOFF2/blocks-extraneous-data-007.xht
- Pass => Fail css/WOFF2/blocks-extraneous-data-008.xht
- Pass => Fail css/WOFF2/blocks-overlap-003.xht
- Pass => Fail css/WOFF2/header-reserved-001.xht
- Pass => Fail css/WOFF2/tabledata-extraneous-data-001.xht
- Pass => Fail css/WOFF2/tabledata-recontruct-loca-001.xht
+ Fail => Pass css/compositing/isolation/isolation-establishes-stacking-context.html
+ Fail => Pass css/css-align/abspos/align-self-with-flex-grid-parent.html
- Pass => Fail css/css-anchor-position/anchor-position-dynamic-005.html
- Pass => Fail css/css-anchor-position/anchor-scroll-fixedpos-003.html
- Pass => Fail css/css-anchor-position/anchor-scroll-fixedpos-004.html
- Pass => Fail css/css-anchor-position/anchor-scroll-nested.html
- Pass => Fail css/css-anchor-position/position-visibility-anchors-visible-in-overflow.html
+ Fail => Pass css/css-backgrounds/background-origin-006.html
+ Fail => Pass css/css-backgrounds/background-rounded-image-clip-001.html
+ Fail => Pass css/css-backgrounds/background-size-percentage-root.html
+ Fail => Pass css/css-backgrounds/box-shadow/slice-inline-fragmentation-002.html
- Pass => Fail css/css-break/grid/grid-item-oof-012.html
+ Fail => Pass css/css-break/out-of-flow-in-multicolumn-091.html
+ Fail => Pass css/css-break/out-of-flow-in-multicolumn-092.html
+ Fail => Pass css/css-break/out-of-flow-in-multicolumn-104.html
+ Fail => Pass css/css-conditional/container-queries/no-layout-containment-abspos.html
+ Fail => Pass css/css-conditional/container-queries/no-layout-containment-fixedpos.html
- Pass => Fail css/css-contain/contain-layout-020.html
- Pass => Fail css/css-contain/contain-layout-ignored-cases-ib-split-001.html
+ Fail => Pass css/css-contain/contain-layout-stacking-context-001.html
+ Fail => Pass css/css-contain/contain-paint-clip-001.html
- Pass => Fail css/css-contain/contain-paint-ignored-cases-ib-split-001.html
- Pass => Fail css/css-contain/contain-paint-ignored-cases-ruby-stacking-and-clipping-001.html
+ Fail => Pass css/css-contain/contain-paint-stacking-context-001a.html
+ Fail => Pass css/css-contain/contain-paint-stacking-context-001b.html
- Pass => Fail css/css-contain/content-visibility/content-visibility-095.html
+ Fail => Pass css/css-flexbox/abspos/position-absolute-002.html
+ Fail => Pass css/css-flexbox/flex-item-position-relative-001.html
+ Fail => Pass css/css-flexbox/flexbox-items-as-stacking-contexts-001.xhtml
+ Fail => Pass css/css-flexbox/flexbox-paint-ordering-003.html
+ Fail => Pass css/css-flexbox/flexbox_margin-left-ex.html
+ Fail => Pass css/css-flexbox/order/order-abs-children-painting-order.html
- Pass => Fail css/css-flexbox/table-as-item-large-intrinsic-size.html
+ Fail => Pass css/css-grid/abspos/absolute-positioning-grid-container-containing-block-001.html
- Pass => Fail css/css-grid/abspos/grid-abspos-staticpos-align-self-safe-outer-cb-003.tentative.html
+ Fail => Pass css/css-grid/abspos/positioned-grid-descendant-containing-block-004.html
+ Fail => Pass css/css-grid/abspos/positioned-grid-descendant-containing-block-006.html
+ Fail => Pass css/css-grid/abspos/positioned-grid-descendant-containing-block-007.html
+ Fail => Pass css/css-grid/abspos/positioned-grid-descendant-containing-block-008.html
+ Fail => Pass css/css-grid/abspos/positioned-grid-descendant-containing-block-012.html
+ Fail => Pass css/css-grid/abspos/positioned-grid-descendant-containing-block-014.html
+ Fail => Pass css/css-grid/abspos/positioned-grid-descendant-containing-block-015.html
+ Fail => Pass css/css-grid/abspos/positioned-grid-descendant-containing-block-016.html
+ Fail => Pass css/css-grid/abspos/positioned-grid-descendants-017.html
+ Fail => Pass css/css-grid/abspos/positioned-grid-items-019.html
+ Fail => Pass css/css-grid/abspos/positioned-grid-items-024.html
+ Fail => Pass css/css-grid/abspos/positioned-grid-items-should-not-take-up-space-001.html
+ Fail => Pass css/css-grid/grid-items/grid-items-percentage-paddings-015.html
+ Fail => Pass css/css-grid/grid-lanes/subgrid/grid-subgridded-to-grid-lanes/column-subgrid-abs-pos-001.html
+ Fail => Pass css/css-grid/subgrid/abs-pos-001.html
- Pass => Fail css/css-images/image-orientation/image-orientation-img-object-fit.html
+ Fail => Pass css/css-multicol/multicol-contained-absolute.html
+ Fail => Pass css/css-multicol/multicol-oof-inline-cb-001.html
+ Fail => Pass css/css-overflow/overflow-clip-margin-021.html
+ Fail => Pass css/css-overflow/overflow-clip-margin-022.html
+ Fail => Pass css/css-page/fixedpos-005-print.html
+ Fail => Pass css/css-page/fixedpos-007-print.html
+ Fail => Pass css/css-page/fixedpos-008-print.html
- Pass => Fail css/css-position/nested-inline-abspos-child-with-siblings.html
- Pass => Fail css/css-position/nested-inline-abspos-child.html
+ Fail => Pass css/css-position/position-absolute-center-003.html
+ Fail => Pass css/css-position/position-absolute-center-004.html
+ Fail => Pass css/css-position/position-fixed-overflow-print.html
+ Fail => Pass css/css-position/position-fixed-scroll-nested-fixed.html
+ Fail => Pass css/css-position/position-relative-table-td-left.html
+ Fail => Pass css/css-position/position-relative-table-td-top.html
+ Fail => Pass css/css-position/position-static-001.html
+ Fail => Pass css/css-position/sticky/position-sticky-large-top-2.tentative.html
+ Fail => Pass css/css-position/sticky/position-sticky-large-top.tentative.html
- Pass => Fail css/css-pseudo/first-letter-width-2.html
+ Fail => Pass css/css-pseudo/highlight-z-index-001.html
+ Fail => Pass css/css-tables/absolute-tables-010.tentative.html
+ Fail => Pass css/css-text-decor/text-shadow/text-shadow-emoji-transparent.html
- Pass => Fail css/css-transforms/backface-visibility-hidden-animated-001.html
- Pass => Fail css/css-transforms/backface-visibility-hidden-animated-002.html
+ Fail => Pass css/css-transforms/individual-transform/stacking-context-002.html
+ Fail => Pass css/css-transforms/individual-transform/stacking-context-003.html
+ Fail => Pass css/css-transforms/individual-transform/stacking-context-004.html
- Pass => Fail css/css-transforms/perspective-containing-block-dynamic-1b.html
- Pass => Fail css/css-transforms/perspective-split-by-zero-w.html
+ Fail => Pass css/css-transforms/preserve-3d-flat-grouping-properties-containing-block-inline.tentative.html
- Pass => Fail css/css-transforms/transform-containing-block-dynamic-1b.html
+ Fail => Pass css/css-transforms/transform-stacking-001.html
+ Fail => Pass css/css-transforms/ttwf-transform-translatex-001.html
+ Fail => Pass css/css-transforms/ttwf-transform-translatey-001.html
- Pass => Fail css/css-view-transitions/massive-element-below-and-on-top-of-viewport-partially-onscreen-new.html
- Pass => Fail css/css-view-transitions/massive-element-below-and-on-top-of-viewport-partially-onscreen-old.html
- Pass => Fail css/css-view-transitions/massive-element-right-and-left-of-viewport-partially-onscreen-new.html
- Pass => Fail css/css-view-transitions/massive-element-right-and-left-of-viewport-partially-onscreen-old.html
- Pass => Fail css/css-view-transitions/snapshot-containing-block-includes-scrollbar-gutter.html
+ Fail => Pass css/css-viewport/zoom/explicit-inherit/bottom.html
+ Fail => Pass css/css-viewport/zoom/explicit-inherit/left.html
+ Fail => Pass css/css-viewport/zoom/explicit-inherit/right.html
+ Fail => Pass css/css-viewport/zoom/explicit-inherit/top.html
- Pass => Fail css/css-will-change/will-change-abspos-cb-dynamic-001.html
+ Fail => Pass css/css-will-change/will-change-fixpos-cb-height-1.html
+ Fail => Pass css/css-will-change/will-change-fixpos-cb-position-1.html
+ Fail => Pass css/css-will-change/will-change-stacking-context-backdrop-filter-1.html
+ Fail => Pass css/css-will-change/will-change-stacking-context-filter-1.html
+ Fail => Pass css/css-will-change/will-change-stacking-context-offset-path-1.html
+ Fail => Pass css/css-will-change/will-change-stacking-context-perspective-1.html
+ Fail => Pass css/css-will-change/will-change-stacking-context-transform-1.html
+ Fail => Pass css/css-will-change/will-change-stacking-context-transform-style-1.html
+ Fail => Pass css/css-will-change/will-change-stacking-context-translate-1.html
+ Fail => Pass css/css-writing-modes/float-rgt-orthog-htb-in-vrl-003.xht
- Pass => Fail css/cssom-view/cssom-getBoundingClientRect-vertical-rl.html
- Pass => Fail css/filter-effects/backdrop-filter-clip-rect-zoom.html
- Pass => Fail css/filter-effects/backdrop-filter-plus-mask-large.html
- Pass => Fail css/filter-effects/filter-cb-dynamic-1b.html
- Pass => Fail css/filter-effects/filtered-inline-is-container.html

Generated by the WPT workflow.

…ize) passes

Guard inline child taffy layout writes behind RunMode::PerformLayout,
snapshot/restore the Parley layout around measure passes, and only run
the out-of-flow positioning pass on PerformLayout passes.
Update the taffy pin to the current stack-top rev (grid-area edge-case
fixes and cache-invalidation tests).

Hoisted out-of-flow boxes were appended to the end of their containing
block's paint_children, painting them above every in-flow sibling.
Insert them at their (paint level, tree order) position instead, so
z-index:auto positioned boxes paint in tree order among positioned
siblings (CSS 2.1 Appendix E step 8). Fixes
grid-items-relative-positioned-containing-block-004/005/006.
Replace the pre-layout paint hoisting (HoistedPaintChildren with baked
positions) with a post-layout stacking membership pass:

- Taffy's OOF containing-block lists are kept, with an inverse
  oof_containing_block pointer; layout_parent is never repointed.
- paint_geometry_parent() resolves geometry ancestry (containing block
  for out-of-flow boxes, layout parent otherwise).
- resolve_stacking_contexts derives paint_children (in-flow only) and
  per-stacking-context entry lists from the structural tree, gated by
  stacking damage. z-index:auto positioned boxes are stacking
  containers (non-atomic); real stacking contexts are atomic.
- Entries carry no baked geometry: paint and hit testing resolve entry
  offsets (and intermediate scroll clips) on demand by walking geometry
  chains to the common ancestor, handling fixed-position scroll
  exemption uniformly.
- Hit testing traverses the same representation in reverse paint order.
…tacking context

A fixed-position box inside a scrollable fixed-position box (or any
stacked entry whose containing-block chain bypasses the stacking
context root's overflow clip) must not be clipped or scrolled by it.
Draw such entries outside the clip layer, keyed on whether the entry's
geometry chain passes through the clipping node.
@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

@staging-devin-ai-integration
staging-devin-ai-integration Bot force-pushed the devin/1787748926-taffy-oof-hoisting-latebound branch from 4f6fbef to 89c20f9 Compare August 29, 2026 13:24
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