Hoist out-of-flow (absolute/fixed) boxes to their containing block - #1170
Open
nicoburns wants to merge 6 commits into
Open
Hoist out-of-flow (absolute/fixed) boxes to their containing block#1170nicoburns wants to merge 6 commits into
nicoburns wants to merge 6 commits into
Conversation
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
This was referenced Aug 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Objective
Implement browser-correct out-of-flow hoisting:
position: absolute/fixedboxes are now laid out by their containing block (nearest positioned ancestor forabsolute, the root forfixed) rather than always by their DOM parent, withLayout.locationrelative to that containing block. Stacked on #1140 (Position::Static/Fixed).Architecture follows Blink's model (candidate bubbling + per-fragment persistence), as discussed in the design conversation.
This PR replaces #1141 (closed; branch preserved as
backup/oof-hoisting-prelatebound). The only difference from #1141 is that static positions are now recorded late-bound (see below), addressing the lossy-encoding concern raised by the Servo compatibility analysis.Context
Candidate bubbling (Blink's
oof_positioned_candidates/oof_positioned_descendants)New
OofCandidate { node, order, position, static_position: Point<StaticPosition> }where the static position is recorded late-bound per axis:Emitters (block/flex/grid) resolve writing-mode/flex-direction/RTL down to a physical keyword but do not collapse the area to an anchor coordinate; the final offset is only computed once the box's final size and margins are known, by the new public helper:
This keeps the raw per-axis start/end coordinates and alignment keywords available to consumers driving taffy's layout algorithms directly (Servo's
PositioningContext-style late-bound resolution, future anchor positioning), instead of a lossy pre-resolved anchor.Candidates live in
LayoutOutput.oof_candidates: OofCandidates— lazyOption<Box<Vec<_>>>storage so the no-OOF case allocates nothing; lists are moved (take/append), not copied. Bubbling translates both area endpoints by the child's location.Block/flex/grid emit their direct OOF children as candidates and merge candidates bubbled out of in-flow children's outputs. A shared containing-block pass,
perform_oof_layout(newsrc/compute/oof.rs, refactored from the old per-algorithm absolute-layout code), claims candidates the current node is the containing block for, lays them out against its padding box, re-sweeps candidates surfaced by OOF children (fixed inside absolute), and passes unclaimed candidates up through its ownLayoutOutput.compute_root_layoutruns the final pass: the root is the initial containing block forfixedand forabsolutewith no positioned ancestor.Candidates are persisted in the layout cache (
LayoutOutputis nowCloneinstead ofCopy), so a cache hit at an intermediate node re-propagates hoisted descendants without descending into the skipped subtree — this is what keeps incremental layout working.Containing-block-relative locations + rounding
Layout.locationis relative to its containing block's border box; scrollable overflow contributions likewise move to the containing block.LayoutPartialTree::set_hoisted_children/add_hoisted_children;round_layoutskips hoisted boxes when walking DOM children and instead recurses into them via newRoundTree::is_hoisted/hoisted_child_count/get_hoisted_child_id, so rounding accumulates offsets through the containing block (which the location is relative to) and each node is visited exactly once.Test fixtures
The gentest fixture diff is large but mechanical: the test base stylesheet sets
position: relativeon all divs (matching taffy's old default), but the generator captured only the inlinepositionstyle — so withstaticas the new default, generated tests silently disagreed with what Chrome actually laid out. The generator now records the computed position (and measures OOF boxes against their containing block), making every fixture'sposition: relativeexplicit. Hand-written tests intests/hand_written/oof_hoisting.rscover hoisting past static parents, containing-block-relative static positions per layout mode, fixed-to-root (including fixed inside absolute re-sweep), cache-hit re-propagation and invalidation, and rounding via the containing block; unit tests insrc/compute/oof.rscoverresolve_static_offset(start/center/end, margins, safe fallback boundary, degenerate areas).Feedback wanted
DetailedGridInfo::resolve_absolute_grid_area(needed for non-direct descendants whose CB is a grid) is the next PR in the stack.Position::Fixedcurrently only treats the root as its containing block; style-created fixed containing blocks (transforms etc.) are left to embedders (Blitz) via the trait layer.Link to Devin session: https://dioxus.staging.devinenterprise.com/sessions/65c5463061c8403bade7888271d5eeb4
Open in Devin Desktop: https://dioxus.staging.devinenterprise.com/desktop/session/65c5463061c8403bade7888271d5eeb4?variant=devin-insiders
Requested by: @nicoburns