Files
rust_browser/_bmad-output/implementation-artifacts/1-7-backgrounds.md
Zachary D. Rowitsch be2c5c9d05 Implement background-image URL resolution and verify all background sub-properties (CSS 2.1 §14.2)
Wire background-image url() to ImageId via ImageStore lookup during layout,
add canvas background URL resolution for root/body propagation, and verify
all background sub-properties end-to-end with 8 golden tests (241-248).
Includes code review fixes: deduplicate ImageStore.insert_url, extract
apply_styles_to_canvas helper, remove redundant Phase 2 canvas propagation.

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

22 KiB

Story 1.7: Backgrounds

Status: done

Story

As a web user, I want page and element backgrounds to render correctly with images, patterns, and positioning, so that visually rich websites display as intended.

Acceptance Criteria

  1. Given an element with the full background shorthand property, When the page is rendered, Then all background sub-properties (color, image, repeat, attachment, position) are parsed and applied correctly per CSS 2.1 §14.2.1
  2. Given an element with background-image: url(...) and background-repeat, When the page is rendered, Then the image is tiled according to the repeat value (repeat, repeat-x, repeat-y, no-repeat)
  3. Given an element with background-attachment: fixed, When the page is scrolled, Then the background image remains fixed relative to the viewport
  4. Given an element with background-position using keyword, percentage, or length values, When the page is rendered, Then the background image is positioned correctly within the element's padding box
  5. Golden tests cover each background property combination, checklist is updated, and just ci passes

Tasks / Subtasks

NOTE: background-color is fully implemented. CSS parsing and shorthand expansion for all background properties already exist. Computed style fields, layout box fields, display list items, and rasterizer drawing for background images, gradients, repeat, attachment, and position are all wired. The main gap is that background-image: url(...) images are not loaded during layout — only <img>/<object> elements resolve image URLs to ImageId. This story completes the background-image URL loading pipeline and verifies all sub-properties work end-to-end with golden tests.

  • Task 1: Wire background-image URL resolution to ImageId (AC: #2)

    • 1.1 In crates/layout/src/engine/box_tree.rs build_layout_box(): after apply_computed_styles_to_box(), when computed.background_image is BackgroundImage::Url(url), resolve via image_store.get_by_url(url) and store as background_image_id on the LayoutBox
    • 1.2 Used Option B: Added url_to_image: HashMap<String, ImageId> to ImageStore with insert_url() and get_by_url() methods
    • 1.3 Background images use URL-based lookup via ImageStore.get_by_url(), separate from element images' NodeId-based image_map
    • 1.4 Extended run_pipeline_with_images() in tests/goldens.rs to scan computed styles for BackgroundImage::Url and load images via image_store.insert_url()
    • 1.5 Fallback works: if URL not in ImageStore, get_by_url returns None and background_image_id stays None — background-color still renders
    • 1.6 Unit tests: 4 ImageStore URL tests + 4 layout-level background-image URL resolution tests
  • Task 2: Verify background-repeat works end-to-end (AC: #2)

    • 2.1 BackgroundRepeat enum verified in crates/shared/src/lib.rs
    • 2.2 Parsing verified in computed styles
    • 2.3 LayoutBox background_repeat field verified
    • 2.4 Display list builder passes repeat to DisplayItem::BackgroundImage verified
    • 2.5 Rasterizer tiling logic verified
    • 2.6 Tiling logic verified correct per CSS 2.1 §14.2.2
    • 2.7 Golden test 242 verifies all 4 repeat modes end-to-end
  • Task 3: Verify background-attachment works end-to-end (AC: #3)

    • 3.1 BackgroundAttachment enum verified
    • 3.2 Parsing and computed style field verified
    • 3.3 Display list builder handles Fixed attachment verified
    • 3.4 Rasterizer handles Fixed by positioning relative to viewport verified
    • 3.5 scroll (default) works — normal behavior verified
    • 3.6 fixed works — viewport-relative positioning verified via golden test 245
    • 3.7 local (CSS3) — parsing works, implementation deferred
    • 3.8 Golden test 245 verifies background-attachment: fixed
  • Task 4: Verify background-position works end-to-end (AC: #4)

    • 4.1 BackgroundPositionValue enum verified
    • 4.2 Keyword, percentage, and length parsing verified
    • 4.3 Position-x/y separate computed fields verified
    • 4.4 Position calculation verified per CSS 2.1 §14.2.1
    • 4.5 Position applies to padding box verified
    • 4.6 Golden tests 243 (keywords) and 244 (percentages) verify positioning
  • Task 5: Verify background shorthand end-to-end (AC: #1)

    • 5.1 Shorthand parsing verified (1009 lines in background.rs)
    • 5.2 Shorthand correctly expands to all sub-properties verified via golden test 246
    • 5.3 Omitted components reset to initial values verified
    • 5.4 Golden test 246: background: #f0f0f0 url(image.png) no-repeat center top — output shows SolidRect (#f0f0f0) + BackgroundImage with Percentage(50.0) center, Percentage(0.0) top, NoRepeat
  • Task 6: Canvas/root element background (AC: #1)

    • 6.1 Root background paints over entire canvas verified
    • 6.2 Canvas background fields verified
    • 6.3 Display list builder renders canvas background first verified
    • 6.4 Canvas background image wired via image_store.get_by_url() in mod.rs — handles both root element and body propagation
    • 6.5 Body background propagation verified via unit test and golden test 248
    • 6.6 Golden test 248 shows canvas SolidRect(0,0,800,600) + BackgroundImage(0,0,800,600) covering full viewport
  • Task 7: Linear gradient rendering verification (AC: #1)

    • 7.1 Gradient parsing verified
    • 7.2 BackgroundImage::LinearGradient variant verified
    • 7.3 DisplayItem::LinearGradient rendered by display list builder verified
    • 7.4 Gradient direction parsing verified (golden test 247 uses to right = 90deg)
    • 7.5 Color stop interpolation verified (2-stop red→blue gradient)
    • 7.6 Golden test 247: linear-gradient(to right, red, blue)LinearGradient rect=(8,8,100,100) angle=90.0deg stops=2
  • Task 8: Golden tests and checklist update (AC: #5)

    • 8.1 Added 8 golden tests (241-248, numbers adjusted from story's 209-216 since 209-240 were already taken):
      • 241-background-image-url.html — basic background-image with url() and no-repeat
      • 242-background-repeat-modes.html — all 4 repeat values
      • 243-background-position-keywords.html — center, right bottom, left top
      • 244-background-position-percentage.html — percentage-based positioning
      • 245-background-attachment-fixed.html — fixed attachment
      • 246-background-shorthand.html — full shorthand with all components
      • 247-background-gradient.html — linear-gradient background
      • 248-background-canvas.html — root element background covering canvas
    • 8.2 Existing background golden tests (012, 022, 086) verified passing — zero regressions
    • 8.3 Updated docs/CSS2.1_Implementation_Checklist.md — checked off Phase 16 items
    • 8.4 just ci passes — all 241 golden tests pass, all unit tests pass, zero regressions

Dev Notes

Current Implementation Status

Background support is largely implemented across the pipeline. What works:

  • background-color — FULLY WORKING: parsed, computed, rendered as DisplayItem::SolidRect
  • background-image parsing — CSS parsing and shorthand expansion for url() and linear-gradient() work
  • background-image gradientsLinearGradient fully wired: parsed → computed → layout box → display item → rasterized
  • background-repeat — enum, parsing, computed field, layout box field, display item field, and rasterizer tiling all exist
  • background-attachment — enum, parsing, computed field, display item field, and viewport-relative rasterizer logic exist
  • background-position — X/Y split, keyword/percentage/length parsing, resolve() method, and rasterizer positioning exist
  • background shorthand — full shorthand parser in background.rs (1009 lines) with comprehensive tests
  • Canvas background — separate fields for root element background, rendered first in display list
  • Image loading infrastructurecrates/image/ handles PNG/JPEG/GIF/WebP/SVG decode, ImageStore with ImageId

What is missing (this story's scope):

  • background-image: url(...) loading — NOT WIRED: CSS-specified image URLs are not fetched/decoded/stored during layout. Only <img> and <object> elements resolve URLs to ImageId. This is the primary gap.
  • Golden test coverage — only background-color (012) and inline backgrounds (022) have golden tests; no tests for background-image, repeat, position, attachment, or gradients as standalone features
  • End-to-end verification — most sub-properties are wired but never tested individually with golden tests

Key Code Locations

Component File Key Functions/Lines
Background shorthand parser crates/css/src/parser/shorthands/background.rs:1-1009 Full shorthand + position expansion + tests
Gradient parser crates/css/src/parser/gradient.rs linear-gradient(), vendor prefixes
PropertyId variants crates/css/src/types.rs:373-378,458,473-478 BackgroundColor, BackgroundImage, BackgroundRepeat, BackgroundAttachment, BackgroundPositionX/Y, Background
Property name lookup crates/css/src/types.rs:521-527 String → PropertyId mapping
BackgroundImage enum crates/style/src/types/primitives.rs:208-213 None, Url(String), LinearGradient(LinearGradient)
ComputedStyles fields crates/style/src/types/computed.rs:75-80 background_color, background_image, background_position_x/y, background_repeat, background_attachment
Style application crates/style/src/types/computed.rs:755-835 Background property → computed value conversion
Shared types crates/shared/src/lib.rs:777-816 BackgroundRepeat, BackgroundAttachment, BackgroundPositionValue enums
LayoutBox fields crates/layout/src/types.rs:290-298 background_image_id, background_gradient, background_position_x/y, background_repeat, background_attachment
Canvas background fields crates/layout/src/types.rs:975-981 Canvas-level background fields
Style → LayoutBox crates/layout/src/engine/box_tree.rs:570-681 apply_computed_styles_to_box() — gradients wired, URL images NOT wired
Element image loading crates/layout/src/engine/box_tree.rs:80-108 <img>/<object> image resolution — pattern to follow for background images
DisplayItem types crates/display_list/src/lib.rs:19-73 BackgroundImage, LinearGradient, SolidRect
Background rendering crates/display_list/src/builder.rs:788-825 render_background() — color → gradient → image layering
Canvas background render crates/display_list/src/builder.rs:100-126 Canvas background painted first
Rasterizer image draw crates/render/src/rasterizer/drawing.rs:1756+ draw_background_image() — tiling, position, clip
Rasterizer dispatch crates/render/src/rasterizer/mod.rs:245-273 DisplayItem::BackgroundImage handling with viewport-relative fixed
Image store crates/image/src/lib.rs:25-52 ImageStore — insert/get by ImageId
Image decode crates/image/src/lib.rs:106-177 PNG/JPEG/GIF/WebP/SVG decode pipeline
Golden test pipeline tests/goldens.rs:47-125 run_pipeline_with_images()extend for background-image URLs

Existing Golden Tests (Do NOT Regress — 3 tests)

Fixture Coverage
012-background-color.html Basic background-color on elements
022-inline-background-border.html Inline element background and border
086-table-backgrounds.html Table cell backgrounds

Implementation Approach

Task 1 (background-image URL loading) — PRIMARY WORK: This is the main engineering task. The entire rendering pipeline already exists — the only missing link is loading CSS-specified image URLs.

Current image loading for <img> elements (in box_tree.rs:80-108):

  • During box tree construction, an image_map: HashMap<NodeId, ImageId> is available
  • <img> elements look up their NodeId in this map to get ImageId
  • The map is populated externally before layout

For background images, the approach should be:

  1. Pre-scan computed styles for BackgroundImage::Url(url) during the resource loading phase (before layout)
  2. Fetch and decode each unique URL using the existing crates/image/ pipeline
  3. Create a background_image_map: HashMap<String, ImageId> (URL string → ImageId)
  4. Pass this map into box tree construction alongside the existing image_map
  5. In apply_computed_styles_to_box(), when encountering BackgroundImage::Url(url), look up the URL in the background image map and set background_image_id

Alternative (simpler): Add a url_image_map: HashMap<String, ImageId> to the existing ImageStore so all URL-based images (both element and background) use the same resolution path.

Tasks 2-7 (verification tasks): These are primarily verification with golden tests. The code paths already exist — the work is:

  1. Create targeted golden test HTML files exercising each feature
  2. Run through the pipeline to verify correct output
  3. Fix any bugs found during verification

Task 8 (golden tests + checklist): Standard completion tasks. Use fixture numbers starting at 209 (or whatever is next after story 1-6's fixtures).

Architecture Constraints

  • Layer rule: Changes span css (Layer 1), style (Layer 1), layout (Layer 1), display_list (Layer 1), render (Layer 1) — all horizontal Layer 1 deps
  • No unsafe: All affected crates except graphics/ and platform/ forbid unsafe_code
  • CSS Property Implementation Order: Already followed — parsing, computed, layout, paint all exist. This story wires the missing link (URL → ImageId) and adds tests
  • Reuse existing infrastructure: Use crates/image/ for image loading. Do NOT add a new image loading system
  • Image store: Use existing ImageStore with ImageId — extend if needed for URL-based lookups

Previous Story Intelligence

From Stories 1.1-1.6 (Common Patterns):

  • CSS Property Implementation Order: parse → style → layout → paint → test → docs (consistently followed)
  • Golden test infrastructure: fixtures in tests/goldens/fixtures/, expected in tests/goldens/expected/
  • Regen goldens: cargo test -p rust_browser --test regen_goldens -- --nocapture
  • Checklist update at docs/CSS2.1_Implementation_Checklist.md is mandatory
  • just ci is the single validation gate (~1 minute, run once per change)

From Story 1.5 (Table Layout) — Background Context:

  • Table cell backgrounds already work via 086-table-backgrounds.html golden test
  • Background painting in display list builder is called from normal box rendering flow
  • render_background() at builder.rs:788-825 paints in order: color → gradient → image

From Story 1.6 (Lists) — Image Loading Context:

  • Story 1.6 introduced list-style-image which also needs image URL loading
  • If story 1.6 has already implemented a URL → ImageId mapping for list marker images, reuse that same mechanism for background images
  • Check story 1.6 implementation status — shared image URL resolution would benefit both features

Testing Strategy

  1. Image loading tests — verify background-image URLs resolve to ImageId during box tree construction
  2. Regression tests — existing 3 background golden tests (012, 022, 086) must not regress
  3. End-to-end golden tests — 8 new fixtures covering:
    • URL-based background images with repeat modes
    • Background positioning (keywords, percentages, lengths)
    • Fixed attachment
    • Full shorthand
    • Linear gradients
    • Canvas/root element backgrounds
  4. Shorthand parser tests — already extensive (1009 lines in background.rs), verify completeness
  5. Run just ci at the end

CSS 2.1 Spec References

  • §14.2 — The background: canvas, background properties overview
  • §14.2.1 — Background properties: background-color, background-image, background-repeat, background-attachment, background-position, background shorthand
  • §14.2.1 — Background shorthand resets: omitted values use initial values
  • §14.2 — Root element background paints over entire canvas; <body> background propagates to canvas if <html> has none

Project Structure Notes

  • Primary change is wiring URL → ImageId resolution in crates/layout/src/engine/box_tree.rs
  • Golden test pipeline in tests/goldens.rs needs to also resolve background-image URLs
  • Image loading uses existing crates/image/ — no new image infrastructure
  • Display list, rasterizer changes should be minimal (paths already exist)
  • New golden test fixtures start at 209 (or after story 1-6 fixtures)

References

  • [Source: crates/css/src/parser/shorthands/background.rs#1-1009] — Background shorthand parser (complete)
  • [Source: crates/css/src/parser/gradient.rs] — Gradient parser
  • [Source: crates/css/src/types.rs#373-378,458,473-478] — Background PropertyId variants
  • [Source: crates/style/src/types/primitives.rs#208-213] — BackgroundImage enum
  • [Source: crates/style/src/types/computed.rs#75-80,755-835] — Computed background fields and conversion
  • [Source: crates/shared/src/lib.rs#777-816] — BackgroundRepeat, BackgroundAttachment, BackgroundPositionValue
  • [Source: crates/layout/src/types.rs#290-298] — LayoutBox background fields
  • [Source: crates/layout/src/types.rs#975-981] — Canvas background fields
  • [Source: crates/layout/src/engine/box_tree.rs#80-108] — Element image loading pattern (follow for backgrounds)
  • [Source: crates/layout/src/engine/box_tree.rs#570-681] — apply_computed_styles_to_box() (modify for URL images)
  • [Source: crates/display_list/src/builder.rs#788-825] — render_background() (color → gradient → image)
  • [Source: crates/display_list/src/builder.rs#100-126] — Canvas background rendering
  • [Source: crates/display_list/src/lib.rs#19-73] — DisplayItem types (BackgroundImage, LinearGradient, SolidRect)
  • [Source: crates/render/src/rasterizer/drawing.rs#1756+] — draw_background_image() tiling/position
  • [Source: crates/render/src/rasterizer/mod.rs#245-273] — BackgroundImage dispatch with fixed viewport
  • [Source: crates/image/src/lib.rs#25-177] — ImageStore and decode pipeline
  • [Source: tests/goldens.rs#47-125] — run_pipeline_with_images() (extend for background URLs)
  • [Source: docs/CSS2.1_Implementation_Checklist.md#Phase-16] — Backgrounds and Borders checklist

Dev Agent Record

Agent Model Used

Claude Opus 4.6 (1M context)

Debug Log References

No blocking issues encountered. All existing pipeline code was correct — the primary gap was URL→ImageId resolution.

Completion Notes List

  • Primary implementation: Added url_to_image: HashMap<String, ImageId> to ImageStore with insert_url() and get_by_url() methods, providing URL-based image lookup for CSS background-image: url(...) and future list-style-image: url(...) support.
  • Layout wiring: In build_layout_box(), after apply_computed_styles_to_box(), background-image URLs are resolved via image_store.get_by_url() to set background_image_id on the LayoutBox.
  • Canvas background wiring: In layout_with_images_and_inputs(), both root element and body propagation paths now resolve BackgroundImage::Url via image_store.get_by_url() to set canvas_background_image_id.
  • Golden test pipeline: Extended run_pipeline_with_images() to scan computed styles for BackgroundImage::Url and load images via image_store.insert_url().
  • Tests added: 4 ImageStore unit tests (insert_url, get_by_url_missing, url_dedup, multiple_urls), 4 layout-level tests (URL resolution, fallback, canvas from html, canvas from body propagation), 8 golden tests (241-248).
  • Verification: All existing background sub-properties (repeat, attachment, position, shorthand, gradients, canvas) verified working end-to-end via golden tests. Zero code changes needed in display_list, render, or rasterizer — the pipeline was already complete.

Change Log

  • 2026-03-13: Implemented background-image URL→ImageId resolution pipeline and verified all background sub-properties end-to-end with 8 golden tests (241-248). Updated CSS2.1 checklist.

File List

  • crates/image/src/lib.rs — Added url_to_image HashMap, insert_url(), get_by_url() methods, 4 unit tests
  • crates/layout/src/engine/box_tree.rs — Added background-image URL→ImageId resolution after apply_computed_styles_to_box()
  • crates/layout/src/engine/mod.rs — Canvas background URL resolution for both root element and body propagation paths
  • crates/layout/src/tests/canvas_background_tests.rs — 4 new background-image URL resolution tests
  • tests/goldens.rs — Extended run_pipeline_with_images() for background-image URL loading; added 8 golden test functions (241-248)
  • tests/goldens/fixtures/241-background-image-url.html — NEW golden fixture
  • tests/goldens/fixtures/242-background-repeat-modes.html — NEW golden fixture
  • tests/goldens/fixtures/243-background-position-keywords.html — NEW golden fixture
  • tests/goldens/fixtures/244-background-position-percentage.html — NEW golden fixture
  • tests/goldens/fixtures/245-background-attachment-fixed.html — NEW golden fixture
  • tests/goldens/fixtures/246-background-shorthand.html — NEW golden fixture
  • tests/goldens/fixtures/247-background-gradient.html — NEW golden fixture
  • tests/goldens/fixtures/248-background-canvas.html — NEW golden fixture
  • tests/goldens/expected/241-background-image-url.{layout,dl}.txt — NEW golden expected
  • tests/goldens/expected/242-background-repeat-modes.{layout,dl}.txt — NEW golden expected
  • tests/goldens/expected/243-background-position-keywords.{layout,dl}.txt — NEW golden expected
  • tests/goldens/expected/244-background-position-percentage.{layout,dl}.txt — NEW golden expected
  • tests/goldens/expected/245-background-attachment-fixed.{layout,dl}.txt — NEW golden expected
  • tests/goldens/expected/246-background-shorthand.{layout,dl}.txt — NEW golden expected
  • tests/goldens/expected/247-background-gradient.{layout,dl}.txt — NEW golden expected
  • tests/goldens/expected/248-background-canvas.{layout,dl}.txt — NEW golden expected
  • docs/CSS2.1_Implementation_Checklist.md — Checked off Phase 16 background items