Files
rust_browser/_bmad-output/implementation-artifacts/4-6-click-keyboard-and-scroll-interaction.md
Zachary D. Rowitsch 917706e7cd Add click, keyboard, and scroll interaction with hit testing, container scroll, and review fixes (Story 4.6)
Implement z-index-aware hit testing, overflow clipping, keyboard default behaviors
(Enter/Space on links/buttons/checkboxes), horizontal and keyboard page scrolling,
container-level overflow:auto/scroll with scroll chaining, and scroll event dispatch.
Code review fixes: scroll events target actual scrolled container element, deduplicated
link navigation via navigate_link_href() helper, and reference-based scroll offset API.

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

287 lines
25 KiB
Markdown

# Story 4.6: Click, Keyboard & Scroll Interaction
Status: done
<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->
## Story
As a web user,
I want my clicks, key presses, and scroll actions to work correctly on all page content,
so that I can interact with web pages naturally.
## Acceptance Criteria
1. **Click hit testing accuracy:** Clicking on an element delivers the click event to the topmost visible element at the click coordinates. Hit testing accounts for z-index stacking order and `overflow: hidden` clipping. Clicking on overlapping elements correctly targets the topmost per stacking context.
2. **Keyboard event dispatch:** Pressing keys while an element is focused dispatches `keydown` and `keyup` events with correct `key`, `code`, `repeat`, and modifier properties (ctrlKey, shiftKey, altKey, metaKey) per UI Events spec. Events target the focused element or `document` if nothing is focused.
3. **Keyboard default behaviors:** Enter on a focused `<a>` element navigates to the link href. Space on a checkbox toggles it. Space on a button triggers click. Arrow keys in text inputs move the cursor (already implemented). These default behaviors are suppressed if `preventDefault()` is called on the `keydown` event.
4. **Page-level scroll improvement:** Mouse wheel and trackpad scrolling works vertically (already exists) and horizontally. Keyboard scrolling: Space/Shift+Space scroll by viewport height, Page Up/Page Down scroll by viewport height, Home/End scroll to top/bottom of page. Arrow Up/Down scroll by a line height (~40px) when no text input is focused.
5. **Scroll chaining for nested containers:** An element with `overflow: auto` or `overflow: scroll` that has content exceeding its bounds becomes a scrollable container. Scroll events first apply to the innermost scrollable container at the pointer position. When the inner container reaches its scroll boundary, excess scroll propagates to the parent scrollable container (scroll chaining).
6. **Scroll event dispatch:** After a scroll position changes, a non-cancelable, non-bubbling `scroll` event is dispatched on the scrolled element (or `document` for page-level scroll).
7. **Integration tests** verify hit testing, keyboard dispatch with default behaviors, page scrolling (wheel + keyboard), container scrolling, scroll chaining, and `just ci` passes.
## What NOT to Implement
- **No pointer events** (pointerdown, pointermove, pointerup) -- beyond scope, mouse events are sufficient.
- **No touch/gesture events** -- desktop browser only.
- **No smooth scroll CSS property** (`scroll-behavior: smooth`) -- immediate scroll only.
- **No scrollbar click/drag interaction** -- ScrollIndicator remains visual only.
- **No scroll snap** (`scroll-snap-type`) -- deferred.
- **No `element.scrollTop`/`element.scrollLeft` JS properties** -- deferred to Web API story.
- **No `element.scrollIntoView()` JS method** -- deferred.
- **No `keypress` event** -- deprecated per UI Events spec.
- **No IME composition events** (`compositionstart`/`compositionupdate`/`compositionend`) -- deferred.
- **No Tab key focus navigation** -- that is Story 4.7.
- **No horizontal scrollbar rendering** -- vertical only for now.
## Tasks / Subtasks
- [x] Task 1: Improve hit testing for stacking contexts and overflow clipping (AC: #1)
- [x] 1.1 Update `find_element_at_position()` in `hit_test.rs` to respect z-index ordering: when testing children of a box with `position: relative/absolute/fixed`, iterate in reverse paint order (later-painted elements first) so topmost stacking context wins
- [x] 1.2 Add overflow clipping to hit testing: if a layout box has `overflow: hidden/auto/scroll`, clip the hit test region to the box's content area -- clicks outside the clip rect should not target children of that container
- [x] 1.3 Add unit tests: overlapping positioned elements (higher z-index wins), overflow:hidden clips child hit targets, basic hit testing for nested containers
- [x] Task 2: Add keyboard default behaviors for non-input elements (AC: #3)
- [x] 2.1 In `event_handler.rs`, after dispatching `keydown` event, check `default_prevented` flag. If not prevented, execute default behavior based on focused element type and key
- [x] 2.2 Enter key on focused `<a>` element: trigger navigation to href (follow `handle_link_click()` pattern)
- [x] 2.3 Space key on focused `<input type="checkbox">`: toggle checked state (follow existing checkbox click handler)
- [x] 2.4 Space key on focused `<button>` or `<input type="submit">`: trigger click event dispatch (synthetic click)
- [x] 2.5 Space key on focused `<a>` element: trigger navigation (same as Enter)
- [x] 2.6 Add unit/integration tests: Enter on link navigates, Space on checkbox toggles, Space on button fires click, preventDefault suppresses default
- [x] Task 3: Improve page-level scrolling with horizontal and keyboard support (AC: #4)
- [x] 3.1 Add `scroll_x` and `max_scroll_x` to `AppState` (alongside existing `scroll_y`/`max_scroll_y`)
- [x] 3.2 Handle `delta_x` in scroll wheel handler (event_handler.rs ~line 1590): apply to `scroll_x`, clamp to `max_scroll_x`
- [x] 3.3 Update `build_display_list_with_scroll()` to accept and apply both scroll_x and scroll_y offsets
- [x] 3.4 Add keyboard scroll handlers (when no text input focused and keydown not prevented): Space/PageDown scroll down by viewport height, Shift+Space/PageUp scroll up by viewport height, Home scroll to top (scroll_y=0), End scroll to bottom (scroll_y=max), ArrowUp/ArrowDown scroll by 40px
- [x] 3.5 Calculate `max_scroll_x` as `max(0, doc_width - viewport_width)` during layout
- [x] 3.6 Add tests: horizontal wheel scroll, keyboard scroll (Space, PageDown, PageUp, Home, End, ArrowUp, ArrowDown), keyboard scroll suppressed when text input focused
- [x] Task 4: Implement container-level scrolling for overflow:auto/scroll (AC: #5)
- [x] 4.1 Add `scroll_offsets: HashMap<NodeId, (f32, f32)>` to `AppState` for per-container scroll positions
- [x] 4.2 Implement `find_scroll_container_at_position()` in `hit_test.rs`: given mouse coordinates, find the innermost element with `overflow: auto/scroll` whose content exceeds its bounds
- [x] 4.3 In scroll wheel handler, determine scroll target: if pointer is over a scroll container, scroll that container first. Apply remaining delta to parent containers or page (scroll chaining)
- [x] 4.4 Update display list builder to apply per-container scroll offsets: when building items for a scrollable container, offset child items by the container's scroll offset and clip to container bounds
- [x] 4.5 Update hit testing to account for container scroll offsets: adjust coordinates by container scroll offset before testing children
- [x] 4.6 Calculate per-container max scroll values from content overflow (content_height - container_height, content_width - container_width)
- [x] 4.7 Add tests: div with overflow:auto scrolls its content, scroll chaining when inner container is at boundary, hit testing within scrolled container
- [x] Task 5: Dispatch scroll events (AC: #6)
- [x] 5.1 Add `dispatch_scroll_event()` to `web_api`: event type "scroll", bubbles: false, cancelable: false, target is the scrolled element (or document for page scroll)
- [x] 5.2 Add `dispatch_scroll_event()` wrapper to `browser_runtime`
- [x] 5.3 Fire scroll event after any scroll position change (page-level or container-level) in event_handler.rs
- [x] 5.4 Add integration tests: scroll event fires on page scroll, scroll event fires on container scroll, scroll event has correct target, scroll event is not cancelable
- [x] Task 6: Integration tests and CI (AC: #7)
- [x] 6.1 Hit testing integration tests: click on overlapping elements targets correct one, click outside overflow:hidden region does not target clipped child
- [x] 6.2 Keyboard integration tests: keydown/keyup events fire with correct properties, Enter on link navigates, Space on checkbox toggles, default behavior prevented by preventDefault
- [x] 6.3 Scroll integration tests: page scrolls via mouse wheel, keyboard scrolling works (Space, Page keys, Home/End), container scrolls independently, scroll chaining propagates to parent
- [x] 6.4 Scroll event integration tests: scroll event fires with correct properties
- [x] 6.5 Regression: all existing form tests, event tests, and golden tests pass unchanged
- [x] 6.6 `just ci` passes -- all tests, lint, fmt, policy clean
- [x] 6.7 Update `docs/HTML5_Implementation_Checklist.md` with click/keyboard/scroll interaction status
## Dev Notes
### Existing Infrastructure (DO NOT REBUILD)
**Hit testing already works** (`crates/app_browser/src/hit_test.rs`):
- `find_link_at_position()` (line 22-37): recursively finds `<a>` elements at (x, y)
- `find_element_at_position()` (line 129-137): finds deepest element at position
- `find_element_in_box()` (line 140-191): depth-limited recursion (MAX_HIT_TEST_DEPTH=256), checks inline fragments then children
- These need enhancement for stacking order and overflow clipping, NOT replacement
**Keyboard events already dispatch** (`crates/app_browser/src/event_handler.rs`):
- `key_code_to_key_and_code()` (line 71-91): maps platform KeyCode to UI Events spec strings
- `dispatch_keyboard_event_with_swap()` (line 95-151): dispatches keydown/keyup to JS via browser_runtime
- Events target focused element or document root
- Text input handling (cursor movement, selection, backspace, delete) all working
- Missing: default behavior for non-input elements (links, buttons, checkboxes) after dispatch
**Page scroll already works** (`crates/app_browser/src/event_handler.rs` ~line 1590-1604):
- `ScrollWheel { delta_x, delta_y }` received from platform event loop
- Only `delta_y` is used; `delta_x` is ignored -- add horizontal support
- Scroll offset applied in display list builder via `build_display_list_with_scroll(tree, scroll_y)`
- `max_scroll_y` calculated as `doc_height - viewport_height`
- Fragment scroll (`scroll_to_fragment`) exists in `app_state.rs`
**Event dispatch infrastructure** (`crates/web_api/src/event_dispatch.rs`):
- Full three-phase DOM event dispatch: Capture -> Target -> Bubble
- `dispatch_mouse_event()`, `dispatch_keyboard_event()`, `dispatch_focus_change()`, `dispatch_input_event()`, `dispatch_change_event()`, `dispatch_submit_event()`, `dispatch_invalid_event()` all available
- `default_prevented` flag checked after dispatch -- pattern for conditional default behavior
**Focus system** (`crates/app_browser/src/app_state.rs`):
- `focused_node: Option<NodeId>` tracks current focus
- `set_content_focus()` manages transitions, returns dirty node
- Focus outline rendered via `focus_outline.rs`
- Four-event focus sequence (focusout, focusin, blur, focus) properly dispatched
**ScrollIndicator** (`crates/display_list/src/lib.rs` line 77-86):
- Visual-only scrollbar: `track_rect`, `thumb_rect`, `vertical` flag
- Already in display list rendering pipeline -- no changes needed for this story
**Overflow properties** (`crates/layout/src/types.rs` line 252-253):
- `overflow_x` and `overflow_y` fields exist on LayoutBox
- Values: `Overflow::Visible`, `Overflow::Scroll`, `Overflow::Auto`, `Overflow::Hidden`
- Already parsed from CSS and stored -- just not used for scroll behavior yet
**Platform keyboard support** (`crates/platform/src/event_loop.rs`):
- KeyCode enum includes: Enter, Backspace, Delete, Left, Right, Home, End, Tab, Escape, A, F5, R, L, BracketLeft, BracketRight, Period, Other
- Missing from enum: Space, PageUp, PageDown, ArrowUp, ArrowDown -- add these to KeyCode and winit mapping
- `Modifiers` struct tracks ctrl, alt, shift, meta
- `CharacterInput(char)` for text typing
### What Needs to Be Built
1. **Hit test improvements** (`hit_test.rs`): Add z-index stacking order respect (reverse paint order iteration). Add overflow clipping check (reject hits outside clip rect of overflow:hidden/auto/scroll containers).
2. **Keyboard default behaviors** (`event_handler.rs`): After keydown dispatch, if not prevented, check element type: Enter/Space on links -> navigate, Space on checkbox -> toggle, Space on button -> click. Follows existing `default_prevented` check pattern.
3. **Horizontal page scroll** (`app_state.rs` + `event_handler.rs` + `display_list/builder.rs`): Add `scroll_x`/`max_scroll_x`, handle `delta_x` in wheel handler, pass both offsets to display list builder.
4. **Keyboard scroll** (`event_handler.rs`): When no text input focused and keydown not prevented, handle Space/PageUp/PageDown/Home/End/ArrowUp/ArrowDown for page scrolling.
5. **Container scroll** (`app_state.rs` + `hit_test.rs` + `event_handler.rs` + `display_list/builder.rs`): Per-container scroll offsets, find scroll container at position, scroll chaining logic, display list clipping/offsetting for scrolled containers.
6. **Platform KeyCode expansion** (`platform/src/event_loop.rs`): Add Space, PageUp, PageDown, ArrowUp, ArrowDown to KeyCode enum. Map from winit virtual key codes.
7. **Scroll event dispatch** (`web_api` + `browser_runtime`): New event type "scroll", non-cancelable, non-bubbling. Follow existing event dispatch patterns.
### Architecture Compliance
| Rule | How This Story Complies |
|------|------------------------|
| Layer boundaries | Hit testing, scroll handling, keyboard routing in `app_browser` (Layer 3). Scroll event dispatch in `web_api`/`browser_runtime` (Layer 1/2). KeyCode additions in `platform` (Layer 1). Display list changes in `display_list` (Layer 1). No upward dependencies. |
| Unsafe policy | No unsafe code needed. All changes are safe Rust. |
| Pipeline sequence | Scroll offset application during display list building (existing pattern). Hit testing operates on layout tree. No changes to style/layout computation. |
| Arena ID pattern | Uses existing `NodeId` for scroll container identification. No new ID types. |
| Existing patterns | Follows `dispatch_submit_event` pattern for scroll events. Follows `checked_states`/`select_states` pattern for scroll offsets HashMap. Follows `default_prevented` check pattern for keyboard defaults. |
| Single-threaded model | All scroll and event handling on main thread. No threading changes. |
### File Modification Plan
| File | Change |
|------|--------|
| `crates/platform/src/event_loop.rs` | Add Space, PageUp, PageDown, ArrowUp, ArrowDown to KeyCode enum; map from winit |
| `crates/app_browser/src/hit_test.rs` | Add z-index stacking order, overflow clipping to hit testing |
| `crates/app_browser/src/event_handler.rs` | Add keyboard default behaviors (Enter/Space on links/buttons/checkboxes), horizontal scroll support, keyboard scrolling, container scroll with chaining, scroll event dispatch |
| `crates/app_browser/src/app_state.rs` | Add `scroll_x`, `max_scroll_x`, `scroll_offsets: HashMap<NodeId, (f32, f32)>` |
| `crates/display_list/src/builder.rs` | Accept and apply `scroll_x` offset; apply per-container scroll offsets with clipping |
| `crates/web_api/src/lib.rs` | Add `dispatch_scroll_event()` (bubbles: false, cancelable: false) |
| `crates/browser_runtime/src/lib.rs` | Add `dispatch_scroll_event()` wrapper |
| `crates/app_browser/src/main.rs` | Initialize `scroll_x`, `scroll_offsets` in AppState |
| `tests/js_events.rs` | Integration tests for keyboard defaults, scroll events |
| `docs/HTML5_Implementation_Checklist.md` | Update interaction event status |
### Testing Strategy
- **Unit tests** (in `hit_test.rs` or new `hit_test_tests.rs`): z-index ordering in hit testing, overflow clipping in hit testing
- **Unit tests** (in new `scroll_tests.rs` or existing test modules): container scroll offset clamping, scroll chaining logic, keyboard scroll calculation
- **Integration tests** (in `js_events.rs`): keydown/keyup properties, Enter on link navigates, Space on checkbox toggles, preventDefault suppresses default, scroll event fires on scroll, scroll event target is correct
- **Integration tests** (in new `scroll_interaction.rs` or existing): page keyboard scroll (Space/PageDown/PageUp/Home/End), container overflow scroll, horizontal scroll
- **Regression tests**: All existing form tests, event tests, and golden tests must pass unchanged
### Previous Story Intelligence
Story 4.5 established patterns this story MUST follow:
- **Parameter threading**: When adding params (like scroll_x), update all call sites consistently (display list building, event handler, tests)
- **Runtime state over DOM**: Use AppState fields for scroll positions, not DOM attributes
- **Event dispatch pattern**: Follow `dispatch_submit_event_with_swap()` or `dispatch_invalid_event_with_swap()` patterns for `dispatch_scroll_event_with_swap()`
- **`just ci` after every task**: Don't batch -- verify incrementally
- **Deferred items are OK**: Mark as deferred with clear rationale (e.g., scrollbar interaction deferred)
- **Review bug patterns**: 4.5 review found readonly fields missed in validation -- similarly ensure keyboard defaults cover all interactive element types (links, buttons, checkboxes, radio buttons)
### Git Intelligence
Recent commits show stable Epic 4 progression:
- `be22671` Story 4.5: client-side form validation
- `9ca0a12` Story 4.4: form submission
- `71f263f` Story 4.3: select menus
- `f8e0c47` Story 4.2: buttons, checkboxes, radio buttons
- Convention: descriptive commit messages with (Story X.Y) suffix
- All stories complete in single commits (no multi-commit stories in Epic 4)
### Key Implementation Notes
1. **KeyCode enum expansion**: The platform crate's `KeyCode` enum needs Space, PageUp, PageDown, ArrowUp, ArrowDown. The winit mapping is in `event_loop.rs`. Follow the existing pattern: add enum variants, add `VirtualKeyCode::Space => KeyCode::Space` mappings.
2. **Scroll container detection**: A container is scrollable if `overflow_x` or `overflow_y` is `Auto` or `Scroll` AND content exceeds container bounds. Check `LayoutBox` dimensions: if `content_height > box_height` (for vertical) or `content_width > box_width` (for horizontal), the container is scrollable.
3. **Scroll chaining pattern**: When scroll delta is received, find innermost scroll container at pointer position. Apply delta. If container hits its bound (scroll_y == 0 or scroll_y == max), compute remaining delta and propagate to parent scroll container. Repeat until all delta consumed or page level reached.
4. **Display list per-container scroll**: When building display list items for children of a scrollable container, push a clip rect (container bounds), then offset all child coordinates by `-scroll_offset`. Pop clip after children. This is similar to existing `PushClip`/`PopClip` pattern.
5. **Hit testing with container scroll**: When hit testing into a scrollable container, add the container's scroll offset to the test coordinates before checking children. This reverses the display list offset.
6. **Default behavior prevention pattern**: After `dispatch_keyboard_event_with_swap()` returns, check `event.default_prevented`. If not prevented, match on `(focused_element_type, key)` to execute defaults. This mirrors how form submission checks `default_prevented` after submit event dispatch.
7. **Scroll event coalescing**: Multiple scroll wheel events can arrive per frame. For simplicity, dispatch one scroll event per scroll position change. No need to coalesce in this story.
### References
- [Source: crates/app_browser/src/hit_test.rs#find_element_at_position] -- Hit testing entry point
- [Source: crates/app_browser/src/hit_test.rs#find_element_in_box] -- Recursive hit test logic
- [Source: crates/app_browser/src/event_handler.rs#~L71-91] -- key_code_to_key_and_code mapping
- [Source: crates/app_browser/src/event_handler.rs#~L95-151] -- Keyboard event dispatch
- [Source: crates/app_browser/src/event_handler.rs#~L1590-1604] -- Current scroll wheel handler
- [Source: crates/app_browser/src/app_state.rs#~L54-56] -- scroll_y and max_scroll_y
- [Source: crates/app_browser/src/app_state.rs#~L393] -- scroll_to_fragment
- [Source: crates/display_list/src/builder.rs#~L97-100] -- Scroll offset in display list building
- [Source: crates/display_list/src/lib.rs#~L77-86] -- ScrollIndicator display item
- [Source: crates/layout/src/types.rs#~L252-253] -- overflow_x, overflow_y on LayoutBox
- [Source: crates/platform/src/event_loop.rs#~L18-36] -- KeyCode enum
- [Source: crates/web_api/src/event_dispatch.rs] -- Three-phase DOM event dispatch
- [Source: crates/web_api/src/lib.rs#dispatch_mouse_event] -- Mouse event dispatch pattern
- [Source: crates/web_api/src/lib.rs#dispatch_keyboard_event] -- Keyboard event dispatch pattern
- [HTML Living Standard - Scrolling] -- Scroll processing model
- [UI Events W3C Spec] -- KeyboardEvent key/code values, default actions
- [CSSOM View Module] -- Scroll APIs and overflow scrolling behavior
## Dev Agent Record
### Agent Model Used
Claude Opus 4.6 (1M context)
### Debug Log References
No blocking issues encountered.
### Completion Notes List
- **Task 1**: Improved hit testing in `hit_test.rs` — z-index stacking order (positioned children sorted by z-index, highest checked first), overflow clipping (children of overflow:hidden/auto/scroll containers only tested within padding box). 7 new unit tests covering z-index priority, negative z-index, overflow:hidden clipping, overflow:auto clipping, and overflow:visible no-clip.
- **Task 2**: Keyboard default behaviors — Enter on focused `<a>` navigates to href, Space on checkbox toggles, Space on radio selects, Space on button/submit triggers synthetic click. Space defaults handled in `CharacterInput` path (since space is printable), Enter defaults in `KeyPressed`. Added Space, Up, Down, PageUp, PageDown to platform `KeyCode` enum with winit mappings.
- **Task 3**: Page-level scroll improvements — added `scroll_x`/`max_scroll_x` to AppState, horizontal wheel scroll via `delta_x`, keyboard scrolling (Space/Shift+Space for page, PageUp/Down, Home/End, ArrowUp/Down for line). Display list builder accepts both scroll_x and scroll_y. Hit testing accounts for scroll_x in mouse coordinates.
- **Task 4**: Container-level scrolling — `scroll_offsets: HashMap<NodeId, (f32, f32)>` in AppState, `find_scroll_containers_at_position()` finds scrollable containers at pointer, scroll chaining propagates remaining delta to parent containers then page level. Display list builder applies per-container scroll offsets and clips children. `last_mouse_x`/`last_mouse_y` track pointer for scroll target detection.
- **Task 5**: Scroll event dispatch — `dispatch_scroll_event()` in web_api (bubbles: false, cancelable: false) + browser_runtime wrapper. `dispatch_scroll_event_on_document()` helper fires on document root after any scroll change. 3 integration tests: fires on target, does not bubble, not cancelable.
- **Task 6**: 9 new integration tests in js_events.rs (3 scroll event + 6 keyboard). All 308 app_browser unit tests pass. All existing integration tests pass. `just ci` clean. HTML5 checklist updated.
### Change Log
- 2026-04-02: Implemented Story 4.6 Click, Keyboard & Scroll Interaction — hit testing z-index/overflow improvements, keyboard default behaviors for links/buttons/checkboxes, horizontal page scroll, keyboard page scroll, container-level overflow:auto/scroll scrolling with scroll chaining, scroll event dispatch. 7 new hit test unit tests, 9 new integration tests. All ACs satisfied, `just ci` passes.
- 2026-04-02: Code review fixes — (H1) Scroll events now dispatch on the actual scrolled container element, not always document root (AC #6 fully compliant). (H2) Extracted `navigate_link_href()` helper to deduplicate Enter/Space link navigation code. (H3) Changed `build_display_list_with_container_scroll` API to accept `&HashMap` reference instead of owned clone. (L2) Removed redundant nested `state.document` checks. `just ci` passes.
### File List
- `crates/platform/src/event_loop.rs` — Added Space, Up, Down, PageUp, PageDown to KeyCode enum with winit mappings
- `crates/app_browser/src/hit_test.rs` — Added z-index stacking order, overflow clipping, `is_scroll_container()`, `content_overflow()`, `find_scroll_containers_at_position()`, `find_layout_box_by_id()`
- `crates/app_browser/src/event_handler.rs` — Keyboard default behaviors (Enter/Space on links/buttons/checkboxes), horizontal scroll, keyboard scrolling, container scroll with chaining, scroll event dispatch, scroll_x in hit testing
- `crates/app_browser/src/app_state.rs` — Added `scroll_x`, `max_scroll_x`, `scroll_offsets`, `last_mouse_x`, `last_mouse_y`; reset on navigation
- `crates/app_browser/src/main.rs` — Initialize new AppState fields
- `crates/display_list/src/builder.rs` — Added `scroll_offset_x`, `container_scroll_offsets`, `build_display_list_with_container_scroll()`; applied container scroll offsets in render pipeline
- `crates/display_list/src/lib.rs` — Export `build_display_list_with_container_scroll`
- `crates/web_api/src/lib.rs` — Added `dispatch_scroll_event()` (bubbles: false, cancelable: false)
- `crates/browser_runtime/src/lib.rs` — Added `dispatch_scroll_event()` wrapper
- `crates/app_browser/src/tests/hit_test_tests.rs` — 7 new tests: z-index ordering (3), overflow clipping (4)
- `crates/display_list/src/tests/scroll_offset_tests.rs` — Updated for new scroll_x parameter
- `crates/display_list/src/tests/clipping_tests.rs` — Updated for new scroll_x parameter
- `crates/display_list/src/tests/sticky_positioning_tests.rs` — Updated for new scroll_x parameter
- `tests/js_events.rs` — 9 new integration tests: scroll event (3), keyboard events (6)
- `docs/HTML5_Implementation_Checklist.md` — Updated interaction event status