Files
rust_browser/_bmad-output/implementation-artifacts/1-2-stacking-contexts-and-z-index.md
Zachary D. Rowitsch 53dd6646b2 Implement stacking contexts and z-index per CSS 2.1 §9.9.1 / Appendix E
Root element now creates a stacking context unconditionally. Refactored
display list builder to correctly bucket stacking participants into three
groups (negative/zero/positive z-index) per Appendix E steps 2/6/7.
Fixed z-index:0 elements painting at step 7 instead of step 6, and
descendant stacking contexts being trapped inside positioned z-index:auto
ancestors instead of participating in the parent stacking context.

Added StackingBuckets struct, unified step-6 rendering with tree-order
merge by node_id, and 7 unit tests for creates_stacking_context().
Added 6 golden tests (213-218) and 4 integration tests asserting
paint-order invariants programmatically.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-13 19:30:31 -04:00

16 KiB

Story 1.2: Stacking Contexts & Z-Index

Status: done

Story

As a web user, I want overlapping elements to render in the correct front-to-back order, so that menus, modals, and layered content display correctly.

Acceptance Criteria

  1. Given an element with position: relative/absolute/fixed and a z-index value other than auto, When the page is rendered, Then a new stacking context is created per CSS 2.1 §9.9.1
  2. Given elements within the same stacking context, When the page is rendered, Then painting order follows CSS 2.1 §E.2: backgrounds/borders → negative z-index → block flow → floats → inline flow → z-index 0/auto → positive z-index
  3. Given nested stacking contexts, When the page is rendered, Then child stacking contexts are painted atomically within their parent's z-order position
  4. Golden tests cover overlapping positioned elements with various z-index values, checklist is updated, and just ci passes

Tasks / Subtasks

  • Task 1: Root element stacking context (AC: #1, #2)

    • 1.1 Update creates_stacking_context() in crates/layout/src/types.rs (~L523-525) to return true for the root element (NodeId(0) or document root), regardless of positioning/z-index
    • 1.2 Unit test confirming root element creates stacking context without positioning
  • Task 2: Verify and fix Appendix E paint order completeness (AC: #2)

    • 2.1 Audit render_stacking_context() in crates/display_list/src/builder.rs (L148-350) against CSS 2.1 §E.2 step-by-step
    • 2.2 Verify Step 2 (negative z-index descendants) sorts and renders correctly — infrastructure exists but checklist shows incomplete
    • 2.3 Verify Step 6 (positioned descendants with z-index: auto) renders in tree order within their stacking context
    • 2.4 Verify stable sort preserves tree order for elements with equal z-index values
    • 2.5 Fix any deviations found in the audit
    • 2.6 Golden tests for each Appendix E step: negative z-index, block flow behind positioned, floats between blocks and inlines, positioned-auto tree order
  • Task 3: Atomic painting of nested stacking contexts (AC: #3)

    • 3.1 Verify that child stacking contexts paint atomically — all descendants within a stacking context paint together before the next stacking context at the same level
    • 3.2 Test nested stacking contexts: parent z-index:1 with children at various z-indices should all paint within parent's z-order slot
    • 3.3 Golden test with 3+ levels of nesting to confirm atomicity
  • Task 4: Edge cases and correctness (AC: #1, #2, #3)

    • 4.1 Verify position: relative + z-index: 0 creates a stacking context (z-index is not auto)
    • 4.2 Verify position: relative + z-index: auto does NOT create a stacking context (per §9.9.1)
    • 4.3 Verify position: static + z-index value has no effect (z-index only applies to positioned elements per §9.5.1)
    • 4.4 Test interleaved positioned and non-positioned siblings — non-positioned paints at Step 3/4/5, positioned paints at Step 6/7
    • 4.5 Unit tests for creates_stacking_context() covering all position/z-index combinations
  • Task 5: Golden tests and checklist update (AC: #4)

    • 5.1 Add golden test fixtures covering: nested stacking contexts with mixed z-indices, negative z-index behind parent background, positioned-auto tree order, interleaved positioned/non-positioned
    • 5.2 Update docs/CSS2.1_Implementation_Checklist.md — check off stacking context creation rules and z-index stacking contexts
    • 5.3 Run just ci and ensure all tests pass

Dev Notes

Current Implementation Status

Stacking contexts and z-index are partially implemented. What works:

  • Z-index parsingz-index property parsed in CSS, stored as Option<i32> in ComputedStyles and LayoutBox
  • Stacking context creation — positioned elements with explicit z-index create stacking contexts
  • Positive z-index sorting (Step 7) — children sorted ascending and rendered
  • Appendix E steps 1, 3, 4, 5 — background/borders, block flow, floats, inlines all render in order
  • Existing golden tests — 9 golden tests already cover basic z-index scenarios (056, 057, 058, 177, 178, 179, 123, 192, 194)

What needs verification and fixing (this story's scope):

  • Root element stacking contextcreates_stacking_context() does NOT handle root element
  • Negative z-index (Step 2) — infrastructure exists but checklist shows incomplete
  • Positioned-auto (Step 6) — renders but tree-order correctness unverified
  • Stable sort / tree order — equal z-index elements must paint in tree order
  • Nested atomicity — needs verification that nested contexts paint atomically

Key Code Locations

Component File Key Functions/Lines
Stacking context creation crates/layout/src/types.rs:523-525 creates_stacking_context() — currently is_positioned() && z_index.is_some()
is_positioned() crates/layout/src/types.rs:510-512 Returns position != Static
z_index field crates/layout/src/types.rs:228 z_index: Option<i32> on LayoutBox
Paint order algorithm crates/display_list/src/builder.rs:148-350 render_stacking_context() — CSS 2.1 Appendix E implementation
StackingParticipant crates/display_list/src/builder.rs:13-20 Struct collecting positioned/stacking descendants
RenderPhase enum crates/display_list/src/builder.rs:53-62 Controls phased rendering for Appendix E
Participant collection crates/display_list/src/builder.rs:529-598 collect_stacking_participants() — recursive tree walk
Z-index sorting crates/display_list/src/builder.rs:236-249 Stable sort of negative/positive children
Z-index computed style crates/style/src/types/computed.rs:89 z_index: Option<i32> — None = auto
Positioned layout crates/layout/src/engine/block/positioning.rs Absolute/fixed/relative layout calculations
Existing golden tests tests/goldens/expected/056-*.txt, 057-*, 058-*, 177-*, 178-*, 179-* Basic z-index, negative, stacking, deep nesting, tree order, overflow clip

Implementation Approach

Task 1 (Root stacking context): Modify creates_stacking_context() in crates/layout/src/types.rs. Add a check: if this LayoutBox represents the root element (check node_id == NodeId(0) or parent is document), return true unconditionally. The root element always creates a stacking context per CSS 2.1 §9.9.1.

Task 2 (Appendix E audit): Walk through render_stacking_context() in builder.rs line by line against the spec:

  • Step 1 (L178-186): Background, borders — likely correct
  • Step 2 (L251-257): Negative z-index — verify sorting is ascending (most negative first) and rendering is recursive
  • Step 3 (L259-280): Block descendants BG/borders only — verify RenderPhase::BackgroundsOnly
  • Step 4 (L282-302): Floats — verify they paint as mini stacking contexts
  • Step 5 (L304-310): Inline content — verify inline formatting context rendering
  • Step 6 (L312-332): Positioned-auto — verify tree-order rendering for z-index: auto positioned elements
  • Step 7 (L334-340): Positive z-index — verify ascending sort

Key question: Does collect_stacking_participants() correctly distinguish between "creates stacking context" (goes into negative/positive buckets) vs "positioned-auto" (goes into auto bucket)?

Task 3 (Nested atomicity): This should work by design — each stacking context calls render_stacking_context() recursively, which renders all its descendants before returning. Verify with a golden test showing a z-index:1 parent with z-index:999 child does NOT paint above a z-index:2 sibling.

Architecture Constraints

  • Layer rule: Changes span layout (Layer 1) and display_list (Layer 1) — both at same layer, horizontal deps allowed
  • No unsafe: Both crates have unsafe_code = "forbid"
  • Pipeline order: Display list building reads layout tree (after layout phase). Do not modify layout boxes during display list building.
  • CSS Property Implementation Order: This story is primarily display_list (paint) with a small layout touch. Follow: verify parsing → verify style computation → verify layout propagation → fix display list painting → tests → docs.

Previous Story Intelligence (Story 1.1)

From Story 1.1 (Margin Collapsing):

  • Golden test infrastructure is well-established: fixtures in tests/goldens/fixtures/, expected in tests/goldens/expected/
  • Unit tests follow the pattern of constructing DOM + style manually, running layout, asserting positions
  • CSS 2.1 checklist at docs/CSS2.1_Implementation_Checklist.md must be updated
  • just ci is the single validation gate

Testing Strategy

  1. Unit tests for creates_stacking_context() — add tests in crates/layout/src/tests/ or alongside existing positioning tests in crates/style/src/tests/positioning.rs
  2. Golden tests — add new fixtures (check latest number in tests/goldens/fixtures/). Key scenarios:
    • Nested stacking contexts with mixed positive/negative z-indices
    • Root element as stacking context
    • Positioned-auto elements in tree order
    • Interleaved positioned + non-positioned siblings
    • Atomicity test: child z-index:999 inside parent z-index:1 must NOT paint above sibling z-index:2
  3. Verify existing golden tests still pass — 9 existing z-index goldens should not regress
  4. Run just ci at the end

CSS 2.1 Spec References

  • §9.9.1 — Stacking context creation rules: positioned elements with z-index != auto, root element
  • §E.2 — Painting order within a stacking context (Appendix E): 7-step algorithm
  • §9.5.1 — z-index only applies to positioned elements
  • §9.3 — Positioning schemes: static, relative, absolute, fixed

WPT Impact

Stacking context and z-index tests are spread across WPT css2-visuren and css2-zindex categories. Fixing root stacking context and verifying Appendix E compliance should improve WPT pass rates in these areas.

Project Structure Notes

  • Changes primarily in crates/display_list/src/builder.rs (paint order) and crates/layout/src/types.rs (stacking context predicate)
  • No new crates or cross-layer changes
  • 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/display_list/src/builder.rs#render_stacking_context] — Core paint order algorithm (Appendix E)
  • [Source: crates/display_list/src/builder.rs#collect_stacking_participants] — Stacking participant collection
  • [Source: crates/layout/src/types.rs#creates_stacking_context] — Stacking context creation predicate
  • [Source: crates/layout/src/types.rs#is_positioned] — Position check helper
  • [Source: crates/style/src/types/computed.rs] — z_index computed style field
  • [Source: tests/goldens/expected/056-z-index-basic.*] — Existing basic z-index golden tests
  • [Source: tests/goldens/expected/058-stacking-context.*] — Existing stacking context golden tests
  • [Source: tests/goldens/expected/177-z-index-deep-stacking.*] — Deep nesting golden tests
  • [Source: docs/CSS2.1_Implementation_Checklist.md] — Implementation status checklist
  • [Source: _bmad-output/planning-artifacts/architecture.md#CSS-Property-Implementation-Order] — CSS feature implementation checklist

Dev Agent Record

Agent Model Used

Claude Opus 4.6 (1M context)

Debug Log References

None required — implementation was straightforward with no blocking issues.

Completion Notes List

  • Task 1: Added is_root_element check to creates_stacking_context() in crates/layout/src/types.rs. The existing is_root_element field (from story 1.1) was leveraged instead of checking NodeId(0).
  • Task 2: Audit of render_stacking_context() found two bugs: (1) z-index:0 stacking contexts were in the step-7 bucket instead of step-6, (2) descendant stacking contexts under positioned z-index:auto ancestors were trapped instead of participating in the parent stacking context. Both fixed by refactoring collect_stacking_participants into three buckets (negative/zero/positive) with a StackingBuckets struct, recursing into positioned-auto ancestors, and merging all step-6 participants by node_id for tree-order rendering. Golden tests 218 and 219 exercise both fixed scenarios.
  • Task 3: Verified atomic painting of nested stacking contexts. The recursive render_stacking_context() design inherently ensures atomicity — all descendants render before returning. Confirmed with golden test 216 (z:999 child inside z:1 parent paints before z:2 sibling).
  • Task 4: Added 7 unit tests for creates_stacking_context() covering all position/z-index combinations including root element, positioned+z-index, positioned+auto, static+z-index.
  • Task 5: Added 7 new golden tests (213-219), updated CSS2.1 checklist, registered 4 pre-existing margin collapse golden tests (209-212). just ci passes with 0 failures.

Change Log

  • 2026-03-13: Implemented stacking contexts and z-index compliance (Story 1.2). Root element now creates stacking context per CSS 2.1 §9.9.1. Fixed z-index:0 step-6 bucketing and descendant SC hoisting through positioned-auto ancestors. Added 7 unit tests and 7 golden tests.
  • 2026-03-13: Code review fixes — updated stale comments (steps 2/7 → 2/6/7), added NodeId monotonicity invariant comment, updated collect_stacking_participants doc comment for three-bucket design, clarified owns_stacking_context relationship to unconditional SC skip, added golden test 220 exercising all three buckets (negative+zero+positive) in one container.

File List

  • crates/layout/src/types.rs — Modified creates_stacking_context() to include root element check; added 7 unit tests
  • crates/display_list/src/builder.rs — Added StackingBuckets struct; refactored collect_stacking_participants to three-bucket collection; fixed z-index:0 to step 6; recurse into positioned-auto ancestors; unified step-6 rendering with tree-order merge; render_stacking_context skips SC collection for non-SC boxes
  • tests/goldens.rs — Added golden test functions for 209-219
  • tests/goldens/fixtures/213-z-index-negative.html — New golden fixture
  • tests/goldens/fixtures/214-z-index-positioned-auto-tree-order.html — New golden fixture
  • tests/goldens/fixtures/215-z-index-interleaved-positioned.html — New golden fixture
  • tests/goldens/fixtures/216-z-index-nested-atomicity.html — New golden fixture
  • tests/goldens/fixtures/217-z-index-three-level-nesting.html — New golden fixture
  • tests/goldens/fixtures/218-z-index-zero-step6.html — New golden fixture (z-index:0 at step 6)
  • tests/goldens/fixtures/219-z-index-auto-ancestor-descendant-sc.html — New golden fixture (SC hoisting through auto ancestor)
  • tests/goldens/expected/213-z-index-negative.{layout,dl}.txt — New golden expected
  • tests/goldens/expected/214-z-index-positioned-auto-tree-order.{layout,dl}.txt — New golden expected
  • tests/goldens/expected/215-z-index-interleaved-positioned.{layout,dl}.txt — New golden expected
  • tests/goldens/expected/216-z-index-nested-atomicity.{layout,dl}.txt — New golden expected
  • tests/goldens/expected/217-z-index-three-level-nesting.{layout,dl}.txt — New golden expected
  • tests/goldens/expected/218-z-index-zero-step6.{layout,dl}.txt — New golden expected
  • tests/goldens/expected/219-z-index-auto-ancestor-descendant-sc.{layout,dl}.txt — New golden expected
  • tests/goldens/expected/211-margin-collapse-nested-chain.layout.txt — Updated (stale NodeIds)
  • docs/CSS2.1_Implementation_Checklist.md — Checked off stacking context and z-index items
  • tests/goldens/fixtures/220-z-index-three-buckets.html — New golden fixture (all three buckets)
  • tests/goldens/expected/220-z-index-three-buckets.{layout,dl}.txt — New golden expected
  • _bmad-output/implementation-artifacts/sprint-status.yaml — Updated story status