Add Overflow::Scroll and Auto enum variants, extend clipping to all non-visible overflow values, implement visual scroll indicators, and fix positioned children clipping per CSS 2.1 §11.1.1. Code review fixes include: DRY overflow parsing helper, rasterizer clip consistency, scroll indicator corner overlap fix, abspos escape in overflow detection (recovering 6 WPT tests), and additional golden/unit tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
18 KiB
Story 1.11: Overflow
Status: done
Story
As a web user, I want content that exceeds its container to be clipped or scrollable as specified, So that pages with constrained containers display correctly.
Acceptance Criteria
-
Given an element with
overflow: hiddenand content exceeding its bounds When the page is rendered Then overflowing content is clipped at the element's padding edge -
Given an element with
overflow: scrollWhen the page is rendered Then a scrolling mechanism is provided regardless of whether content overflows -
Given an element with
overflow: autoand content exceeding its bounds When the page is rendered Then a scrolling mechanism is provided only when content overflows -
Given an element with overflow clipping and positioned children When the page is rendered Then clipping is applied correctly to the element's content and descendants per CSS 2.1 §11.1.1
-
Golden tests cover each overflow value, checklist is updated, and
just cipasses.
Tasks / Subtasks
-
Task 1: Add
ScrollandAutovariants to Overflow enum (AC: #2, #3)- 1.1 Add
ScrollandAutovariants toOverflowenum incrates/style/src/types/primitives.rs(~line 101) - 1.2 Update
apply_declaredincrates/style/src/types/computed.rs(~line 953) to handle"scroll"and"auto"keyword values forPropertyId::Overflow,OverflowX,OverflowY - 1.3 Add unit tests for parsing and applying
scrollandautovalues
- 1.1 Add
-
Task 2: Ensure BFC triggering for all non-visible overflow values (AC: #2, #3)
- 2.1 Verify that
overflow: scrollandoverflow: autotrigger a new Block Formatting Context, just likeoverflow: hiddenalready does - 2.2 Check
crates/layout/src/engine/for BFC triggering logic — ensure it checks forOverflow != Visible(not just== Hidden) - 2.3 Add tests verifying BFC behavior for
overflow: autoandoverflow: scroll
- 2.1 Verify that
-
Task 3: Extend clipping to
scrollandautooverflow values (AC: #2, #3)- 3.1 In
crates/display_list/src/builder.rs(~line 188), updateneeds_clipto includeOverflow::ScrollandOverflow::Auto(currently only checksOverflow::Hidden) - 3.2 Similarly update inline formatting context overflow clipping (~line 393)
- 3.3 Update ancestor clip tracking for positioned elements (~line 546)
- 3.4 Add display list unit tests for clipping with scroll/auto values
- 3.1 In
-
Task 4: Visual scroll indicator for
overflow: scrollandoverflow: auto(AC: #2, #3)- 4.1 Add a
DisplayItem::ScrollIndicatorvariant (or similar) to paint a simple scrollbar visual cue when overflow isscroll(always) orauto(when content exceeds container) - 4.2 In the display list builder, calculate whether content overflows the container bounds
- 4.3 Emit scroll indicator display items for the right edge (vertical) and/or bottom edge (horizontal) as needed
- 4.4 In
crates/render/src/rasterizer/mod.rs, render the scroll indicator as a simple rectangular track + thumb - 4.5 Note: Actual interactive scrolling is NOT in scope for this story — only the visual rendering of scrollbars. Interactive scrolling belongs in Epic 5 (Story 5-5: Page Scrolling).
- 4.1 Add a
-
Task 5: Positioned children clipping correctness (AC: #4)
- 5.1 Review CSS 2.1 §11.1.1 rules for overflow clipping of positioned descendants
- 5.2 Verify that
overflow: hidden/scroll/autoon a non-positioned ancestor does NOT clipposition: absolutechildren whose containing block is a higher ancestor (per spec, clipping applies only to descendants whose containing block is the overflow element or a descendant of it) - 5.3 Verify that
overflow: hidden/scroll/autoon a positioned element (position: relative/absolute/fixed) DOES clip its positioned descendants - 5.4 Check/fix the ancestor clip logic in
crates/display_list/src/builder.rs(~line 546) to properly handle this distinction - 5.5 Add golden tests for positioned children with overflow clipping edge cases
-
Task 6: Golden tests and documentation (AC: #5)
- 6.1 Verify existing golden fixtures (059-overflow-hidden.html, 060-overflow-nested.html, 179-z-index-overflow-clip.html) still pass
- 6.2 Create new golden test fixture for
overflow: scrollvisual rendering - 6.3 Create new golden test fixture for
overflow: autowith overflowing content - 6.4 Create new golden test fixture for
overflow: autowith non-overflowing content (no scrollbar) - 6.5 Create golden test fixture for positioned children clipping edge case
- 6.6 Update
docs/CSS2.1_Implementation_Checklist.md— check off overflow items in relevant phases - 6.7 Run
just ci 2>&1 | tee /tmp/ci-output.txtand fix any issues
Dev Notes
CSS 2.1 Spec References
- §11.1
overflowproperty: applies to block containers and elements withoverflowother thanvisible. Values:visible | hidden | scroll | auto. Initial value:visible. Not inherited. - §11.1.1 Clipping: "When an element's box has
overflowother thanvisible, the box acts as a clipping region for its content." For positioned descendants, clipping only applies if the overflow element is the containing block (or an ancestor of the containing block). - §9.4.1 BFC establishment: "Floats, absolutely positioned elements, block containers that are not block boxes, and block boxes with
overflowother thanvisibleestablish new block formatting contexts."
Current Overflow Pipeline State
Already working (DO NOT reimplement):
PropertyId::Overflow,OverflowX,OverflowY— CSS parsing, shorthand expansion viaexpand_logical_pair_shorthand()ComputedStyles.overflow_x,overflow_yfields — style computation forvisibleandhiddenLayoutBox.overflow_x,overflow_y— layout propagationDisplayItem::PushClip { rect }/PopClip— clip stack in display list- Rasterizer clip stack —
apply_clip_stack()incrates/render/src/rasterizer/mod.rs overflow: hiddenfully clips content end-to-end including nested overflow contexts- BFC triggering for non-visible overflow (already checks
!= Overflow::Visiblein layout) - Golden tests: 059-overflow-hidden, 060-overflow-nested, 110-float-overflow-contain, 121-float-nested-bfc, 179-z-index-overflow-clip
Must be added:
Overflow::ScrollandOverflow::Autoenum variants"scroll"and"auto"keyword recognition inapply_declared- Clipping for
scroll/auto(updateneeds_clipcheck from== Hiddento!= Visible) - Visual scroll indicator rendering (non-interactive)
- Positioned children clipping correctness per §11.1.1
Key Code Locations
| Component | File | What to modify |
|---|---|---|
| Overflow enum | crates/style/src/types/primitives.rs (~line 101) |
Add Scroll, Auto variants |
| apply_declared | crates/style/src/types/computed.rs (~line 953) |
Add "scroll" and "auto" keyword arms |
| Display list clipping | crates/display_list/src/builder.rs (~line 188) |
Change == Hidden to != Visible for needs_clip |
| Inline clipping | crates/display_list/src/builder.rs (~line 393) |
Same pattern: != Visible |
| Positioned clip tracking | crates/display_list/src/builder.rs (~line 546) |
Verify positioned descendant logic |
| Rasterizer | crates/render/src/rasterizer/mod.rs (~line 58) |
Add scroll indicator rendering |
| Display item enum | crates/display_list/src/lib.rs (~line 68) |
Add ScrollIndicator variant |
| Layout BFC check | crates/layout/src/engine/ |
Verify != Visible check (likely already correct) |
| Shorthand tests | crates/css/src/parser/shorthands/mod.rs (~line 919) |
Existing tests cover scroll/auto parsing |
| Golden fixtures | tests/goldens/fixtures/ |
New HTML fixtures for scroll/auto |
| Golden expected | tests/goldens/expected/ |
Expected layout/display list outputs |
| Checklist | docs/CSS2.1_Implementation_Checklist.md |
Update overflow items |
Architecture Constraints
- No unsafe code — all changes in Layer 1 crates only (style, display_list, render, layout)
- Phase-based mutation — overflow computed in style phase, clipping applied in display list phase, rendered in paint phase. No back-references.
- Arena IDs — overflow properties flow through the pipeline via ComputedStyles → LayoutBox → display list builder logic. No shared mutable state.
- Scroll indicator is visual only — actual interactive scrolling (scroll offset, event handling) is out of scope. That belongs in Epic 5 (Story 5-5: Page Scrolling) which involves the event loop in
app_browser(Layer 3).
Implementation Pattern (Follow Exactly)
Follow the established CSS property implementation order:
- Enum in
style/types/primitives.rs— addScrollandAutovariants to existingOverflowenum - Compute in
style/types/computed.rs— add"scroll"and"auto"keyword handling inapply_declared - Layout verify in
layout/— confirm BFC triggering already handles new variants (should work since existing check is!= Visible) - Display list in
display_list/builder.rs— update needs_clip to!= Visible, add scroll indicator emission - Render in
render/rasterizer/— render scroll indicator display items - Positioned clipping in
display_list/builder.rs— verify/fix §11.1.1 compliance - Golden tests in
tests/goldens/— new fixtures + expected outputs - Checklist update —
docs/CSS2.1_Implementation_Checklist.md - CI validation —
just ci 2>&1 | tee /tmp/ci-output.txt
Scroll Indicator Implementation Strategy
CSS 2.1 §11.1 says overflow: scroll "should" indicate a scrolling mechanism. Since actual interactive scrolling is deferred to Epic 5:
- Render a minimal visual scrollbar (e.g., a thin rectangular track on the right/bottom edge of the overflow container)
- For
scroll: always show the scrollbar track (even if content doesn't overflow) - For
auto: show the scrollbar track only if content exceeds the container's padding box - Track dimensions: ~8px wide for vertical, ~8px tall for horizontal
- Track color: use a neutral gray (e.g.,
#cccccctrack,#888888thumb) - Thumb size proportional to visible content ratio (or a fixed small rectangle if no overflow)
- This is intentionally minimal — a full scrollbar widget is a later concern
Positioned Children Clipping Strategy
CSS 2.1 §11.1.1 key rule: An element's overflow clipping applies to its descendants, BUT for absolutely positioned elements, the containing block relationship determines whether clipping applies:
- If the overflow element establishes a containing block (has
position: relative/absolute/fixed), then its absolutely positioned descendants ARE clipped - If the overflow element is NOT a containing block for the positioned element, the positioned element "escapes" the clip
- The display list builder's
ancestor_clipstracking (~line 546) needs to correctly handle this: when building display items for an absolutely positioned element, only include clips from ancestors that are containing blocks (or descendants of the containing block)
Previous Story Learnings
From story 1-10 (Font Properties) and earlier stories:
- Follow the enum + apply_declared + golden test pattern exactly
- New enum types go in
crates/style/src/types/primitives.rs(see Visibility, Overflow enums) - Golden test fixtures:
tests/goldens/fixtures/NNN-descriptive-name.html(sequential numbering) - Regen goldens:
cargo test -p rust_browser --test regen_goldens -- --nocapture - Handle
inherit,initial,unsetkeywords (overflow is NOT inherited, butinherit/initial/unsetmust still be handled) just cimust pass clean at the end
Git Intelligence
Recent relevant commits:
7c8d2b8— :link/:visited pseudo-class support (shows style computation pattern)077426b— Flex row overflow containment fix (shows overflow interaction with flex layout)- Existing overflow golden tests added in earlier milestones (059, 060, 110, 121, 179)
Testing Strategy
- Unit tests for
Overflowenum: parsescrollandauto(in style or CSS tests) - Unit tests for
apply_declaredwith new overflow keywords - Display list tests for clipping with scroll/auto values
- Golden tests — minimum 4 new fixtures:
overflow: scrollwith content (always show scrollbar)overflow: autowith overflowing content (show scrollbar)overflow: autowithout overflowing content (no scrollbar)- Positioned children with overflow clipping edge case
- Regression tests — verify existing overflow golden tests (059, 060, 110, 121, 179) still pass unchanged
Project Structure Notes
- All changes span Layer 1 crates only — no cross-layer violations
- Main work in
style/,display_list/,render/crates - Layout changes minimal (BFC already handles non-visible overflow)
- No new external dependencies needed
- Existing
DisplayItem::PushClip/PopClipmechanism reused for scroll/auto
References
- [Source: CSS 2.1 §11.1 — Overflow property specification]
- [Source: CSS 2.1 §11.1.1 — Overflow clipping rules for positioned descendants]
- [Source: CSS 2.1 §9.4.1 — BFC establishment rules]
- [Source: docs/CSS2.1_Implementation_Checklist.md — Overflow implementation status]
- [Source: crates/style/src/types/primitives.rs — Overflow enum definition]
- [Source: crates/style/src/types/computed.rs — apply_declared for overflow]
- [Source: crates/display_list/src/builder.rs — PushClip/PopClip emission, ancestor clip tracking]
- [Source: crates/render/src/rasterizer/mod.rs — Clip stack rendering]
- [Source: crates/css/src/parser/shorthands/mod.rs — Overflow shorthand expansion + tests]
- [Source: _bmad-output/project-context.md — Full project rules and patterns]
Dev Agent Record
Agent Model Used
Claude Opus 4.6 (1M context)
Debug Log References
- WPT abspos-overflow tests (001-003, 007-009) restored to pass after code review fixed abspos escape in scroll indicator overflow detection
- WPT abspos-overflow-010 remains known_fail (overflow:auto on auto-sized div incorrectly detects inline content overflow)
- WPT flexbox-min-width-auto-003 and overflow-scroll-float-paint-order promoted from known_fail to pass
- WPT table-cell-007 remains known_fail due to scroll indicator pixel mismatch (overflow-y:scroll on table cell)
Completion Notes List
- Added
Overflow::ScrollandOverflow::Autoenum variants with full CSS parsing support (keyword and CssValue::Auto) - BFC triggering already used
!= Visiblecheck — no changes needed, just verified + added tests - Updated all overflow clipping in display list builder from
== Hiddento!= Visible - Added
DisplayItem::ScrollIndicatorwith track/thumb rendering in both rasterizer paths - Scroll indicators: 8px wide, #cccccc track, #888888 thumb, proportional thumb sizing
overflow: scrollalways shows scrollbar;overflow: autoonly when content overflows- Implemented CSS 2.1 §11.1.1 positioned children clipping: non-positioned overflow elements no longer incorrectly clip absolutely positioned descendants whose containing block is above them
- 4 new golden tests (263-266) covering scroll, auto-overflow, auto-no-overflow, and positioned children
- Updated CSS 2.1 implementation checklist
just cipasses clean
Code Review Fixes (2026-03-14):
- Extracted triplicated overflow parsing logic into
parse_overflow_value()helper - Fixed rasterizer clip inconsistency: first
rasterize()path now applies clip stack to ScrollIndicator items - Fixed scroll indicator corner overlap: vertical/horizontal tracks now leave a gap when both are shown
- Fixed scroll indicator overflow detection: abspos children that escape container are now excluded from extent calculation
- Recovered 6 WPT tests (abspos-overflow 001-003, 007-009) from known_fail back to pass
- Added golden test 267 for CSS 2.1 §11.1.1 abspos-escapes-non-positioned-overflow case
- Added 3 new ScrollIndicator unit tests (emission for scroll, auto+overflow, auto+no-overflow)
Change Log
- 2026-03-14: Implemented overflow: scroll and auto (Story 1.11)
- 2026-03-14: Code review fixes — DRY overflow parsing, rasterizer clip fix, scroll indicator corner/abspos fixes, +6 WPT recoveries, +1 golden test, +3 unit tests
File List
- crates/style/src/types/primitives.rs (modified — added Scroll, Auto to Overflow enum)
- crates/style/src/types/computed.rs (modified — added scroll/auto/CssValue::Auto handling in apply_declared)
- crates/style/src/tests/positioning.rs (modified — added 4 overflow tests, updated dual-value test)
- crates/layout/src/engine/block/tests.rs (modified — added BFC tests for scroll/auto)
- crates/display_list/src/lib.rs (modified — added ScrollIndicator variant + dump + scale)
- crates/display_list/src/builder.rs (modified — !Visible clipping, scroll indicator emission, §11.1.1 positioned clip fix)
- crates/display_list/src/tests/clipping_tests.rs (modified — added scroll/auto clipping tests)
- crates/render/src/rasterizer/mod.rs (modified — added ScrollIndicator rendering in both rasterize paths)
- tests/goldens.rs (modified — registered 4 new golden tests)
- tests/goldens/fixtures/263-overflow-scroll.html (new)
- tests/goldens/fixtures/264-overflow-auto-overflow.html (new)
- tests/goldens/fixtures/265-overflow-auto-no-overflow.html (new)
- tests/goldens/fixtures/266-overflow-positioned-children.html (new)
- tests/goldens/expected/263-overflow-scroll.dl.txt (new)
- tests/goldens/expected/263-overflow-scroll.layout.txt (new)
- tests/goldens/expected/264-overflow-auto-overflow.dl.txt (new)
- tests/goldens/expected/264-overflow-auto-overflow.layout.txt (new)
- tests/goldens/expected/265-overflow-auto-no-overflow.dl.txt (new)
- tests/goldens/expected/265-overflow-auto-no-overflow.layout.txt (new)
- tests/goldens/expected/266-overflow-positioned-children.dl.txt (new)
- tests/goldens/expected/266-overflow-positioned-children.layout.txt (new)
- tests/goldens/fixtures/267-overflow-abspos-escape.html (new — §11.1.1 escape case)
- tests/goldens/expected/267-overflow-abspos-escape.dl.txt (new)
- tests/goldens/expected/267-overflow-abspos-escape.layout.txt (new)
- tests/external/wpt/wpt_manifest.toml (modified — promoted 2+6 tests, 2 remain known_fail)
- docs/CSS2.1_Implementation_Checklist.md (modified — checked off overflow items)
- _bmad-output/implementation-artifacts/sprint-status.yaml (modified — status updates)
- _bmad-output/implementation-artifacts/1-11-overflow.md (modified — task checkboxes, dev record)