Story 1.3: Complete positioning support including auto offset resolution, clip rect, fixed scroll behavior, and containing block edge cases. Key changes: - Refactored calculate_absolute_layout() for full §10.3.7/§10.6.4 compliance - Implemented clip: rect() parsing, style computation, layout, and paint - Fixed sticky child recursion in process_deferred_absolutes - Fixed shrink-to-fit abs_cb using unpositioned padding_box - Added collapsed_borders handling in offset_children - Propagated CSS clip to descendant stacking contexts - Tightened rect() parser to reject garbage between rect and ( - Added tracing::warn for unrecognized clip tokens - Replaced hardcoded epsilon with MARGIN_EPSILON constant - Added RTL unimplemented comments on over-constrained resolution - Strengthened tests: exact assertions, delta comparisons, new coverage - 4 new tests: negative offsets, clip suppression, padding edge distinction - 5 golden tests (222-226), promoted WPT absolute-tables-016 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
19 KiB
Story 1.3: Positioning Completeness
Status: done
Story
As a web user, I want fixed-position headers, tooltips, and absolutely positioned elements to appear in the correct location, so that page layouts with positioned elements render correctly.
Acceptance Criteria
- Given an element with
position: fixed, When the page is scrolled, Then the element remains fixed relative to the viewport - Given a positioned element with
top,right,bottom,leftset toauto, When the page is rendered, Then auto values resolve per CSS 2.1 §10.6.4 and §10.3.7 based on the element's static position - Given a positioned element with
clip: rect(...), When the page is rendered, Then the element's visible area is clipped to the specified rectangle per CSS 2.1 §11.1.2 - Given an absolutely positioned element within a relatively positioned container, When the page is rendered, Then the element is positioned relative to its containing block's padding edge
- Golden tests cover fixed positioning, auto resolution, and clip,
docs/CSS2.1_Implementation_Checklist.mdis updated, andjust cipasses
Tasks / Subtasks
-
Task 1: Fixed positioning scroll behavior (AC: #1)
- 1.1 Audit
process_deferred_absolutes()incrates/layout/src/engine/block/positioning.rs:316-342— verified fixed elements use viewport dimensions as containing block viaself.viewport.get() - 1.2 Audit
render_stacking_context()incrates/display_list/src/builder.rs— verified fixed-position elements reset CumulativeOffset to (0,0), immune to scroll - 1.3 In
crates/render/orcrates/display_list/, verified scroll offset is NOT applied to fixed-position display list items — scroll is handled entirely in display list builder - 1.4 Scroll behavior already correctly implemented — no code changes needed
- 1.5 Existing golden test
104-fixed-with-scroll.htmlcovers fixed positioning layout; unit tests added for scroll behavior - 1.6 Unit tests:
test_fixed_element_not_affected_by_scrollandtest_fixed_element_containing_block_is_viewportin scroll_offset_tests.rs
- 1.1 Audit
-
Task 2: Auto offset resolution completeness (AC: #2)
- 2.1 Audited and fixed
calculate_absolute_layout()for CSS 2.1 §10.3.7 — all horizontal cases handled: both auto (static position), left/right auto, width auto with constraint equation, over-constrained (ignore right for LTR), auto margins with abs-pos constraint - 2.2 Audited and fixed §10.6.4 (vertical) — auto margin centering when top+bottom+height specified, bottom positioning when top is auto
- 2.3 Verified static position fallback —
static_x/static_ycorrectly passed frompending_absolutesand used when both left/right or top/bottom are auto - 2.4 Over-constrained resolution implemented — when none of left/right/width is auto, left wins for LTR (right ignored)
- 2.5 Unit tests: 7 abs-pos tests in
crates/layout/src/engine/block/tests.rs - 2.6 Golden tests: 222 (static position), 223 (left-auto right-specified), 224 (over-constrained + auto-margin centering)
- 2.1 Audited and fixed
-
Task 3:
clip: rect(...)implementation (AC: #3)- 3.1 Added
clipproperty parsing incrates/css/src/parser/mod.rs— parsesclip: rect(top, right, bottom, left),clip: auto, comma/space-separated - 3.2 Added
ClipRecttype incrates/style/src/types/primitives.rs— struct with fourOption<f32>values (None = auto) - 3.3 Added
clip: Option<ClipRect>field toComputedStylesincrates/style/src/types/computed.rs - 3.4 Wired cascade resolution in
apply_declaration—PropertyId::Clipvariant added, ClipRect mapped from CssValue::ClipRect - 3.5 Propagated clip to
LayoutBoxincrates/layout/src/types.rsandbox_tree.rs— only set for position:absolute/fixed per §11.1.2 - 3.6 Applied clip in display list builder —
PushClip/PopClipcommands emitted around clipped elements - 3.7 Rasterizer already handles clip via
PushClip/PopClipandapply_clip_stack— no additional changes needed - 3.8 Unit tests: 4 clip tests in
crates/style/src/tests/positioning.rs(parsing, auto values, reset, full pipeline) - 3.9 Golden test: 225-clip-rect.html (absolute and fixed elements with clip)
- 3.1 Added
-
Task 4: Containing block edge cases (AC: #4)
- 4.1 Verified
establishes_containing_block()returnsposition != Static— correct per CSS 2.1. All callers use padding box. - 4.2 Verified
abs_cb_for_childrenuseslayout_box.dimensions.padding_box()at layout.rs:200-201 - 4.3 Test:
test_abspos_containing_block_is_padding_edge— verifies padding edge, not content edge - 4.4 Test:
test_abspos_deeply_nested_chains— A(rel) > B(static) > C(abs) > D(abs) chain verified - 4.5 Golden test: 226-abspos-containing-block-chains.html
- 4.1 Verified
-
Task 5: Golden tests and checklist update (AC: #5)
- 5.1 Added golden test fixtures: 222-225 (auto offset, over-constrained, clip, containing block chains)
- 5.2 Verified all existing golden tests pass: 219 tests total including 052-055, 097, 100, 104, 168
- 5.3 Updated
docs/CSS2.1_Implementation_Checklist.md— checked off: fixed scroll behavior, auto resolution, clip rect - 5.4
just cipasses — also promoted WPT testabsolute-tables-016from known_fail to pass
Dev Notes
Current Implementation Status
Positioning is partially implemented. What works:
position: static— normal flow, fully workingposition: relative— offset viaapply_relative_offset()inpositioning.rs:346-365, left takes precedence over right, top over bottomposition: absolute— two-phase layout viacalculate_absolute_layout()+process_deferred_absolutes(), containing block resolution workingposition: fixed— uses viewport as containing block, basic paint worksposition: sticky— constraints computed viaStickyConstraintsstruct, scroll-based offset- Deferred absolute layout — children stored in
pending_absolutes, processed after parent height is known
What is missing or incomplete (this story's scope):
- Fixed positioning scroll behavior — basic paint exists but scroll offset exemption may be incomplete
- Auto offset resolution — some edge cases in §10.3.7 and §10.6.4 may not be fully handled (over-constrained, direction:rtl)
clip: rect(...)— NOT IMPLEMENTED (no parsing, no style field, no paint clipping)- Containing block edge cases — padding-edge vs content-edge verification needed
Key Code Locations
| Component | File | Key Functions/Lines |
|---|---|---|
| Position enum | crates/style/src/types/primitives.rs:35-43 |
Position { Static, Relative, Absolute, Fixed, Sticky } |
| Computed position props | crates/style/src/types/computed.rs:83-89 |
position, top, right, bottom, left, z_index |
| LayoutBox position state | crates/layout/src/types.rs:222-231 |
position, offsets, render_offset_x/y, pending_absolutes |
| Stacking context predicate | crates/layout/src/types.rs:523-525 |
creates_stacking_context() — positioned + z_index.is_some() |
| Containing block predicate | crates/layout/src/types.rs:516-518 |
establishes_containing_block() — position != Static |
| StickyConstraints | crates/layout/src/types.rs:17-51 |
normal_flow_y, containing_block_bottom, thresholds |
| Block layout dispatcher | crates/layout/src/engine/block/layout.rs:49-74 |
Position dispatch, containing block resolution for absolutes |
| Absolute layout | crates/layout/src/engine/block/positioning.rs:168-312 |
Width/height resolution, static position fallback, offset calc |
| Deferred absolutes | crates/layout/src/engine/block/positioning.rs:316-342 |
Process after parent height known, handle fixed via viewport |
| Relative offset | crates/layout/src/engine/block/positioning.rs:346-365 |
Apply render_offset for relative position |
| Sticky constraints | crates/layout/src/engine/block/positioning.rs:370-399 |
Post-layout sticky computation |
| Float + relative | crates/layout/src/engine/block/positioning.rs:11-163 |
Float layout with relative offset applied after |
| Display list painting | crates/display_list/src/builder.rs:148-350 |
render_stacking_context() — Appendix E paint order |
| Existing positioning tests | crates/style/src/tests/positioning.rs:1-368 |
Style computation tests for position, offsets, z-index |
| Existing layout tests | crates/layout/src/engine/block/tests.rs:206-292 |
Sticky, BFC establishment tests |
Existing Golden Tests (Do NOT Regress)
| Fixture | Coverage |
|---|---|
052-position-relative.html |
Basic relative positioning |
053-position-relative-offset.html |
Relative with top/left offsets |
054-position-absolute.html |
Absolute positioning (viewport) |
055-absolute-in-relative.html |
Absolute inside relative container |
056-z-index-basic.html |
Z-index stacking |
097-position-fixed.html |
Basic fixed positioning |
100-fixed-nested.html |
Nested fixed elements |
104-fixed-with-scroll.html |
Fixed positioning with scroll |
168-bootstrap-fixed-top.html |
Real-world Bootstrap navbar fixed-top |
Implementation Approach
Task 1 (Fixed scroll behavior):
The key question is whether the render/paint path correctly exempts fixed-position elements from scroll offset. Check crates/display_list/src/builder.rs and crates/render/ for scroll offset application. Fixed elements should paint at their viewport-relative coordinates regardless of the document's scroll position. The layout side already uses viewport as containing block for fixed elements (positioning.rs:56-60).
Task 2 (Auto offset resolution):
The core function is calculate_absolute_layout() at positioning.rs:168-312. CSS 2.1 §10.3.7 defines 6 cases for horizontal auto resolution based on which of {left, width, right} are auto. The current implementation handles some but may miss:
- Over-constrained case (none are auto): ignore right for LTR
- Direction:rtl affecting which value is ignored when over-constrained
- Two autos case (width auto + left auto): shrink-to-fit + static position
Similarly §10.6.4 for vertical: if top/bottom/height has autos, resolve per spec rules.
Task 3 (clip: rect()): Full pipeline implementation required:
- Parse
clip: rect(top, right, bottom, left)in CSS parser — note: comma-separated OR space-separated per CSS 2.1 §11.1.2 - Add
ClipRectstruct and computed style field - Clip applies ONLY to absolutely positioned elements (absolute or fixed)
- In display list builder, emit clip commands
- In render, apply scissor rect during rasterization
Task 4 (Containing block):
Verify that containing_block_for_absolutes in layout.rs uses .padding_box() not .content_box(). The padding box of the nearest positioned ancestor is the containing block per CSS 2.1 §10.1. Check the computation at layout.rs where abs_cb_for_children is set.
Architecture Constraints
- Layer rule: Changes span
layout(Layer 1),css(Layer 1),style(Layer 1),display_list(Layer 1),render(Layer 1),shared(Layer 0) — horizontal deps within layers allowed - No unsafe: All affected crates have
unsafe_code = "forbid"(except potentiallyrenderwhich may delegate tographics) - Pipeline order: CSS parse → style → layout → display_list → render. Each phase reads previous, writes its own representation. No reaching back.
- CSS Property Implementation Order: Parse in
css/→ computed style instyle/→ layout effect inlayout/→ paint effect indisplay_list/→ golden tests → update checklist →just ci - Arena IDs: Layout boxes link back to DOM via
NodeId— don't break this. Access DOM throughDocumentarena.
Previous Story Intelligence
From Story 1.1 (Margin Collapsing):
- Golden test infrastructure: fixtures in
tests/goldens/fixtures/, expected intests/goldens/expected/ - Unit tests pattern: construct DOM + style manually, run layout, assert positions
- Checklist update at
docs/CSS2.1_Implementation_Checklist.mdis mandatory just ciis the single validation gate (~1 minute, run once, capture output)
From Story 1.2 (Stacking Contexts & Z-Index):
- Display list builder (
crates/display_list/src/builder.rs) is the paint order engine render_stacking_context()implements CSS 2.1 Appendix E step-by-stepcollect_stacking_participants()classifies elements into stacking/positioned-auto buckets- Stable sort is required for equal z-index (tree order preserved)
- Changes to
creates_stacking_context()must be coordinated with stacking context work
Testing Strategy
- Unit tests for clip parsing in
crates/css/src/orcrates/style/src/tests/positioning.rs - Unit tests for auto offset resolution in
crates/layout/src/tests/— construct positioned boxes with various auto combinations, verify resolved positions - Golden tests in
tests/goldens/fixtures/— check latest fixture number and use next sequential numbers. Each fixture gets.layout.txtand.dl.txtexpected outputs. - Regression verification — run all existing positioning golden tests (052-055, 097, 100, 104, 168) to confirm no regression
- Regenerate goldens after implementation:
cargo test -p rust_browser --test regen_goldens -- --nocapture - Run
just ciat the end — captures fmt + lint + test + policy in one pass
CSS 2.1 Spec References
- §9.3 — Positioning schemes: static, relative, absolute, fixed
- §9.3.1 — Choosing a positioning scheme (
positionproperty) - §9.3.2 — Box offsets:
top,right,bottom,left - §10.1 — Definition of containing block (padding edge of positioned ancestor)
- §10.3.7 — Absolutely positioned, non-replaced elements: horizontal auto resolution (6 cases)
- §10.6.4 — Absolutely positioned, non-replaced elements: vertical auto resolution
- §11.1.2 — Clipping:
clipproperty (applies to absolutely positioned elements) - §9.6.1 — Fixed positioning: containing block is viewport
Project Structure Notes
- All changes within Layer 0/1 crates — no new crates or cross-layer violations
- New CSS property (
clip) follows: parse incss/→ computed instyle/→ propagate tolayout/→ paint indisplay_list/→ render inrender/ - New golden test fixtures go in
tests/goldens/fixtures/with next available number - Expected golden outputs go in
tests/goldens/expected/ - Checklist update in
docs/CSS2.1_Implementation_Checklist.md
References
- [Source: crates/layout/src/engine/block/positioning.rs] — Core positioning layout (absolute, fixed, relative, sticky)
- [Source: crates/layout/src/engine/block/layout.rs#49-74] — Position dispatch and containing block resolution
- [Source: crates/layout/src/types.rs#StickyConstraints] — Sticky positioning constraints
- [Source: crates/layout/src/types.rs#LayoutBox] — Positioning state fields on layout boxes
- [Source: crates/style/src/types/computed.rs#83-89] — Position computed style fields
- [Source: crates/style/src/types/primitives.rs#Position] — Position enum definition
- [Source: crates/display_list/src/builder.rs#render_stacking_context] — Paint order for positioned elements
- [Source: crates/style/src/tests/positioning.rs] — Existing style computation tests
- [Source: crates/layout/src/engine/block/tests.rs] — Existing layout unit tests
- [Source: docs/CSS2.1_Implementation_Checklist.md#Phase-11] — Positioning implementation status
- [Source: _bmad-output/planning-artifacts/architecture.md#CSS-Property-Implementation-Order] — Feature implementation checklist
Dev Agent Record
Agent Model Used
Claude Opus 4.6 (1M context)
Debug Log References
Completion Notes List
- Task 1: Fixed positioning scroll behavior already correctly implemented. Added 2 unit tests verifying scroll immunity and viewport containing block.
- Task 2: Refactored
calculate_absolute_layout()to properly handle CSS 2.1 §10.3.7 (horizontal) and §10.6.4 (vertical). Added auto-margin computation for abs-pos with both left/right or top/bottom specified. Extractedoffset_children()helper. Added 7 unit tests, 3 golden tests. - Task 3: Full pipeline
clip: rect(...)implementation — CSS parsing (PropertyId::Clip, CssValue::ClipRect), ClipRect type, ComputedStyles field, LayoutBox field (only for abs/fixed per §11.1.2), display list PushClip/PopClip emission. Added 4 unit tests, 1 golden test. - Task 4: Verified containing block uses padding box throughout. Added 2 unit tests (padding edge, nested chains), 1 golden test.
- Task 5: All golden tests pass (219 total). CSS checklist updated.
just cipasses. Promoted 1 WPT test (absolute-tables-016) from known_fail to pass.
Change Log
- 2026-03-13: Implemented positioning completeness (all 5 tasks). CSS 2.1 §10.3.7, §10.6.4, §11.1.2, §9.6.1 compliance.
File List
crates/layout/src/engine/block/positioning.rs— refactoredcalculate_absolute_layout()for §10.3.7/§10.6.4 completeness, addedoffset_children()helpercrates/layout/src/engine/block/tests.rs— added 9 abs-pos/containing-block unit testscrates/layout/src/types.rs— addedclip: Option<ClipRect>field to LayoutBoxcrates/layout/src/engine/box_tree.rs— wire clip from computed styles (only for abs/fixed)crates/css/src/types.rs— addedPropertyId::Clip,CssValue::ClipRectvariantcrates/css/src/parser/mod.rs— addedparse_clip_value()andparse_clip_rect_args()crates/style/src/types/primitives.rs— addedClipRectstructcrates/style/src/types/computed.rs— addedclipfield, apply_declaration, reset_to_initial, copy_property_from_parentcrates/style/src/types/mod.rs— export ClipRectcrates/style/src/lib.rs— re-export ClipRectcrates/style/src/tests/positioning.rs— added 4 clip unit testscrates/display_list/src/builder.rs— added PushClip/PopClip for CSS clip propertycrates/display_list/src/tests/scroll_offset_tests.rs— added 2 fixed-element scroll teststests/goldens.rs— registered 5 new golden tests (222-226)tests/goldens/fixtures/222-abspos-auto-offset-static.html— new golden fixturetests/goldens/fixtures/223-abspos-left-auto-right-specified.html— new golden fixturetests/goldens/fixtures/224-abspos-overconstrained.html— new golden fixturetests/goldens/fixtures/225-clip-rect.html— new golden fixturetests/goldens/fixtures/226-abspos-containing-block-chains.html— new golden fixturetests/goldens/expected/222-*.txt— generated expected outputstests/goldens/expected/223-*.txt— generated expected outputstests/goldens/expected/224-*.txt— generated expected outputstests/goldens/expected/225-*.txt— generated expected outputstests/goldens/expected/226-*.txt— generated expected outputsdocs/CSS2.1_Implementation_Checklist.md— updated positioning sectiontests/external/wpt/wpt_manifest.toml— promoted absolute-tables-016 to pass