Files
rust_browser/_bmad-output/implementation-artifacts/1-3-positioning-completeness.md
Zachary D. Rowitsch 327e31795b Implement positioning completeness (CSS 2.1 §10.3.7, §10.6.4, §11.1.2, §9.6.1)
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>
2026-03-13 20:20:08 -04:00

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

  1. Given an element with position: fixed, When the page is scrolled, Then the element remains fixed relative to the viewport
  2. Given a positioned element with top, right, bottom, left set to auto, 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
  3. 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
  4. 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
  5. Golden tests cover fixed positioning, auto resolution, and clip, docs/CSS2.1_Implementation_Checklist.md is updated, and just ci passes

Tasks / Subtasks

  • Task 1: Fixed positioning scroll behavior (AC: #1)

    • 1.1 Audit process_deferred_absolutes() in crates/layout/src/engine/block/positioning.rs:316-342 — verified fixed elements use viewport dimensions as containing block via self.viewport.get()
    • 1.2 Audit render_stacking_context() in crates/display_list/src/builder.rs — verified fixed-position elements reset CumulativeOffset to (0,0), immune to scroll
    • 1.3 In crates/render/ or crates/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.html covers fixed positioning layout; unit tests added for scroll behavior
    • 1.6 Unit tests: test_fixed_element_not_affected_by_scroll and test_fixed_element_containing_block_is_viewport in scroll_offset_tests.rs
  • 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_y correctly passed from pending_absolutes and 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)
  • Task 3: clip: rect(...) implementation (AC: #3)

    • 3.1 Added clip property parsing in crates/css/src/parser/mod.rs — parses clip: rect(top, right, bottom, left), clip: auto, comma/space-separated
    • 3.2 Added ClipRect type in crates/style/src/types/primitives.rs — struct with four Option<f32> values (None = auto)
    • 3.3 Added clip: Option<ClipRect> field to ComputedStyles in crates/style/src/types/computed.rs
    • 3.4 Wired cascade resolution in apply_declarationPropertyId::Clip variant added, ClipRect mapped from CssValue::ClipRect
    • 3.5 Propagated clip to LayoutBox in crates/layout/src/types.rs and box_tree.rs — only set for position:absolute/fixed per §11.1.2
    • 3.6 Applied clip in display list builder — PushClip/PopClip commands emitted around clipped elements
    • 3.7 Rasterizer already handles clip via PushClip/PopClip and apply_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)
  • Task 4: Containing block edge cases (AC: #4)

    • 4.1 Verified establishes_containing_block() returns position != Static — correct per CSS 2.1. All callers use padding box.
    • 4.2 Verified abs_cb_for_children uses layout_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
  • 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 ci passes — also promoted WPT test absolute-tables-016 from known_fail to pass

Dev Notes

Current Implementation Status

Positioning is partially implemented. What works:

  • position: static — normal flow, fully working
  • position: relative — offset via apply_relative_offset() in positioning.rs:346-365, left takes precedence over right, top over bottom
  • position: absolute — two-phase layout via calculate_absolute_layout() + process_deferred_absolutes(), containing block resolution working
  • position: fixed — uses viewport as containing block, basic paint works
  • position: sticky — constraints computed via StickyConstraints struct, 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:

  1. Parse clip: rect(top, right, bottom, left) in CSS parser — note: comma-separated OR space-separated per CSS 2.1 §11.1.2
  2. Add ClipRect struct and computed style field
  3. Clip applies ONLY to absolutely positioned elements (absolute or fixed)
  4. In display list builder, emit clip commands
  5. 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 potentially render which may delegate to graphics)
  • 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 in style/ → layout effect in layout/ → paint effect in display_list/ → golden tests → update checklist → just ci
  • Arena IDs: Layout boxes link back to DOM via NodeId — don't break this. Access DOM through Document arena.

Previous Story Intelligence

From Story 1.1 (Margin Collapsing):

  • Golden test infrastructure: fixtures in tests/goldens/fixtures/, expected in tests/goldens/expected/
  • Unit tests pattern: construct DOM + style manually, run layout, assert positions
  • Checklist update at docs/CSS2.1_Implementation_Checklist.md is mandatory
  • just ci is 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-step
  • collect_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

  1. Unit tests for clip parsing in crates/css/src/ or crates/style/src/tests/positioning.rs
  2. Unit tests for auto offset resolution in crates/layout/src/tests/ — construct positioned boxes with various auto combinations, verify resolved positions
  3. Golden tests in tests/goldens/fixtures/ — check latest fixture number and use next sequential numbers. Each fixture gets .layout.txt and .dl.txt expected outputs.
  4. Regression verification — run all existing positioning golden tests (052-055, 097, 100, 104, 168) to confirm no regression
  5. Regenerate goldens after implementation: cargo test -p rust_browser --test regen_goldens -- --nocapture
  6. Run just ci at 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 (position property)
  • §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: clip property (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 in css/ → computed in style/ → propagate to layout/ → paint in display_list/ → render in render/
  • 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. Extracted offset_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 ci passes. 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 — refactored calculate_absolute_layout() for §10.3.7/§10.6.4 completeness, added offset_children() helper
  • crates/layout/src/engine/block/tests.rs — added 9 abs-pos/containing-block unit tests
  • crates/layout/src/types.rs — added clip: Option<ClipRect> field to LayoutBox
  • crates/layout/src/engine/box_tree.rs — wire clip from computed styles (only for abs/fixed)
  • crates/css/src/types.rs — added PropertyId::Clip, CssValue::ClipRect variant
  • crates/css/src/parser/mod.rs — added parse_clip_value() and parse_clip_rect_args()
  • crates/style/src/types/primitives.rs — added ClipRect struct
  • crates/style/src/types/computed.rs — added clip field, apply_declaration, reset_to_initial, copy_property_from_parent
  • crates/style/src/types/mod.rs — export ClipRect
  • crates/style/src/lib.rs — re-export ClipRect
  • crates/style/src/tests/positioning.rs — added 4 clip unit tests
  • crates/display_list/src/builder.rs — added PushClip/PopClip for CSS clip property
  • crates/display_list/src/tests/scroll_offset_tests.rs — added 2 fixed-element scroll tests
  • tests/goldens.rs — registered 5 new golden tests (222-226)
  • tests/goldens/fixtures/222-abspos-auto-offset-static.html — new golden fixture
  • tests/goldens/fixtures/223-abspos-left-auto-right-specified.html — new golden fixture
  • tests/goldens/fixtures/224-abspos-overconstrained.html — new golden fixture
  • tests/goldens/fixtures/225-clip-rect.html — new golden fixture
  • tests/goldens/fixtures/226-abspos-containing-block-chains.html — new golden fixture
  • tests/goldens/expected/222-*.txt — generated expected outputs
  • tests/goldens/expected/223-*.txt — generated expected outputs
  • tests/goldens/expected/224-*.txt — generated expected outputs
  • tests/goldens/expected/225-*.txt — generated expected outputs
  • tests/goldens/expected/226-*.txt — generated expected outputs
  • docs/CSS2.1_Implementation_Checklist.md — updated positioning section
  • tests/external/wpt/wpt_manifest.toml — promoted absolute-tables-016 to pass