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>
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
- Given an element with the full
backgroundshorthand 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 - Given an element with
background-image: url(...)andbackground-repeat, When the page is rendered, Then the image is tiled according to the repeat value (repeat, repeat-x, repeat-y, no-repeat) - Given an element with
background-attachment: fixed, When the page is scrolled, Then the background image remains fixed relative to the viewport - Given an element with
background-positionusing keyword, percentage, or length values, When the page is rendered, Then the background image is positioned correctly within the element's padding box - Golden tests cover each background property combination, checklist is updated, and
just cipasses
Tasks / Subtasks
NOTE:
background-coloris 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 thatbackground-image: url(...)images are not loaded during layout — only<img>/<object>elements resolve image URLs toImageId. 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.rsbuild_layout_box(): afterapply_computed_styles_to_box(), whencomputed.background_imageisBackgroundImage::Url(url), resolve viaimage_store.get_by_url(url)and store asbackground_image_idon theLayoutBox - 1.2 Used Option B: Added
url_to_image: HashMap<String, ImageId>toImageStorewithinsert_url()andget_by_url()methods - 1.3 Background images use URL-based lookup via
ImageStore.get_by_url(), separate from element images'NodeId-basedimage_map - 1.4 Extended
run_pipeline_with_images()intests/goldens.rsto scan computed styles forBackgroundImage::Urland load images viaimage_store.insert_url() - 1.5 Fallback works: if URL not in ImageStore,
get_by_urlreturnsNoneandbackground_image_idstaysNone— background-color still renders - 1.6 Unit tests: 4 ImageStore URL tests + 4 layout-level background-image URL resolution tests
- 1.1 In
-
Task 2: Verify background-repeat works end-to-end (AC: #2)
- 2.1
BackgroundRepeatenum verified incrates/shared/src/lib.rs - 2.2 Parsing verified in computed styles
- 2.3
LayoutBoxbackground_repeatfield verified - 2.4 Display list builder passes repeat to
DisplayItem::BackgroundImageverified - 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
- 2.1
-
Task 3: Verify background-attachment works end-to-end (AC: #3)
- 3.1
BackgroundAttachmentenum verified - 3.2 Parsing and computed style field verified
- 3.3 Display list builder handles
Fixedattachment verified - 3.4 Rasterizer handles
Fixedby positioning relative to viewport verified - 3.5
scroll(default) works — normal behavior verified - 3.6
fixedworks — 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
- 3.1
-
Task 4: Verify background-position works end-to-end (AC: #4)
- 4.1
BackgroundPositionValueenum 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
- 4.1
-
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()inmod.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::LinearGradientvariant verified - 7.3
DisplayItem::LinearGradientrendered 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-repeat242-background-repeat-modes.html— all 4 repeat values243-background-position-keywords.html— center, right bottom, left top244-background-position-percentage.html— percentage-based positioning245-background-attachment-fixed.html— fixed attachment246-background-shorthand.html— full shorthand with all components247-background-gradient.html— linear-gradient background248-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 cipasses — all 241 golden tests pass, all unit tests pass, zero regressions
- 8.1 Added 8 golden tests (241-248, numbers adjusted from story's 209-216 since 209-240 were already taken):
Dev Notes
Current Implementation Status
Background support is largely implemented across the pipeline. What works:
background-color— FULLY WORKING: parsed, computed, rendered asDisplayItem::SolidRectbackground-imageparsing — CSS parsing and shorthand expansion forurl()andlinear-gradient()workbackground-imagegradients —LinearGradientfully wired: parsed → computed → layout box → display item → rasterizedbackground-repeat— enum, parsing, computed field, layout box field, display item field, and rasterizer tiling all existbackground-attachment— enum, parsing, computed field, display item field, and viewport-relative rasterizer logic existbackground-position— X/Y split, keyword/percentage/length parsing, resolve() method, and rasterizer positioning existbackgroundshorthand — full shorthand parser inbackground.rs(1009 lines) with comprehensive tests- Canvas background — separate fields for root element background, rendered first in display list
- Image loading infrastructure —
crates/image/handles PNG/JPEG/GIF/WebP/SVG decode,ImageStorewithImageId
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 toImageId. 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 theirNodeIdin this map to getImageId- The map is populated externally before layout
For background images, the approach should be:
- Pre-scan computed styles for
BackgroundImage::Url(url)during the resource loading phase (before layout) - Fetch and decode each unique URL using the existing
crates/image/pipeline - Create a
background_image_map: HashMap<String, ImageId>(URL string → ImageId) - Pass this map into box tree construction alongside the existing
image_map - In
apply_computed_styles_to_box(), when encounteringBackgroundImage::Url(url), look up the URL in the background image map and setbackground_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:
- Create targeted golden test HTML files exercising each feature
- Run through the pipeline to verify correct output
- 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/andplatform/forbidunsafe_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
ImageStorewithImageId— 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 intests/goldens/expected/ - Regen goldens:
cargo test -p rust_browser --test regen_goldens -- --nocapture - Checklist update at
docs/CSS2.1_Implementation_Checklist.mdis mandatory just ciis 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.htmlgolden test - Background painting in display list builder is called from normal box rendering flow
render_background()atbuilder.rs:788-825paints in order: color → gradient → image
From Story 1.6 (Lists) — Image Loading Context:
- Story 1.6 introduced
list-style-imagewhich 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
- Image loading tests — verify background-image URLs resolve to ImageId during box tree construction
- Regression tests — existing 3 background golden tests (012, 022, 086) must not regress
- 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
- Shorthand parser tests — already extensive (1009 lines in background.rs), verify completeness
- Run
just ciat 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,backgroundshorthand - §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.rsneeds 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>toImageStorewithinsert_url()andget_by_url()methods, providing URL-based image lookup for CSSbackground-image: url(...)and futurelist-style-image: url(...)support. - Layout wiring: In
build_layout_box(), afterapply_computed_styles_to_box(), background-image URLs are resolved viaimage_store.get_by_url()to setbackground_image_idon theLayoutBox. - Canvas background wiring: In
layout_with_images_and_inputs(), both root element and body propagation paths now resolveBackgroundImage::Urlviaimage_store.get_by_url()to setcanvas_background_image_id. - Golden test pipeline: Extended
run_pipeline_with_images()to scan computed styles forBackgroundImage::Urland load images viaimage_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— Addedurl_to_imageHashMap,insert_url(),get_by_url()methods, 4 unit testscrates/layout/src/engine/box_tree.rs— Added background-image URL→ImageId resolution afterapply_computed_styles_to_box()crates/layout/src/engine/mod.rs— Canvas background URL resolution for both root element and body propagation pathscrates/layout/src/tests/canvas_background_tests.rs— 4 new background-image URL resolution teststests/goldens.rs— Extendedrun_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 fixturetests/goldens/fixtures/242-background-repeat-modes.html— NEW golden fixturetests/goldens/fixtures/243-background-position-keywords.html— NEW golden fixturetests/goldens/fixtures/244-background-position-percentage.html— NEW golden fixturetests/goldens/fixtures/245-background-attachment-fixed.html— NEW golden fixturetests/goldens/fixtures/246-background-shorthand.html— NEW golden fixturetests/goldens/fixtures/247-background-gradient.html— NEW golden fixturetests/goldens/fixtures/248-background-canvas.html— NEW golden fixturetests/goldens/expected/241-background-image-url.{layout,dl}.txt— NEW golden expectedtests/goldens/expected/242-background-repeat-modes.{layout,dl}.txt— NEW golden expectedtests/goldens/expected/243-background-position-keywords.{layout,dl}.txt— NEW golden expectedtests/goldens/expected/244-background-position-percentage.{layout,dl}.txt— NEW golden expectedtests/goldens/expected/245-background-attachment-fixed.{layout,dl}.txt— NEW golden expectedtests/goldens/expected/246-background-shorthand.{layout,dl}.txt— NEW golden expectedtests/goldens/expected/247-background-gradient.{layout,dl}.txt— NEW golden expectedtests/goldens/expected/248-background-canvas.{layout,dl}.txt— NEW golden expecteddocs/CSS2.1_Implementation_Checklist.md— Checked off Phase 16 background items