Display lists owned the accumulated visual context tree through a
ref-counted pointer. That tied visual-context state to display-list
lifetime and made compositor updates treat the two as one unit, even
though AVC trees need to become independently updateable compositor
state.
Make accumulated visual context trees plain versioned values, have each
display list store the compatible tree version, and pass the matching
tree alongside display-list updates and replay calls. Replay verifies
that the provided tree matches the display list before executing it.
This prepares the compositor for receiving AVC tree updates separately
from display-list updates: it now accepts the tree as a separate update
parameter, stores it next to the display list, and uses that stored tree
for replay and async-scroll hit testing. Nested display-list resources
carry their own tree snapshots for the same version check.
Keep the compositor scroll node max offset as the real scroll range,
even for axes that cannot be scrolled by wheel input. Track wheel
scrollability separately so hidden axes are skipped during async wheel
scrolling without clamping away an existing programmatic offset.
Use the viewport-propagated root and body overflow values when deciding
whether viewport axes accept wheel input. Apply wheel deltas only on
axes that can be wheel-scrolled in async metadata and main-thread wheel
default actions, while preserving CSSOM scroll offsets on hidden axes.
Add async scrolling coverage for hidden-axis wheel targeting, preserved
programmatic hidden-axis offsets, and body overflow-x: hidden blocking a
horizontal viewport wheel scroll despite pseudo-element overflow.
Async scrolling kept only the compositor-visible absolute scroll offset
for pending scrolls. If script changed the same scrolling box before
the main thread adopted the pending offset, adoption could write the
stale absolute compositor offset back to the DOM and lose the script
update.
Track the unadopted compositor delta alongside the absolute offset
used for compositor presentation. When fresh scroll state reaches the
compositor, and when the main thread adopts pending async scrolls,
apply that delta on top of the current main-thread offset instead of
assigning the old absolute value.
Adoption can run during the event-loop scroll steps, before the later
rendering-update layout pass has made paintables safe to query. Keep
element adoption on the stored DOM scroll offset and apply viewport
deltas through the navigable, so a dirty layout tree does not trip the
paintable freshness invariant.
The async scrolling text test covers the script-scroll race and also
dirties layout before adoption. The old paintable-box adoption path
crashed in that case.
Async scrolling handled a single wheel event differently from the
main-thread default action. When a nested scroll node consumed part of a
large delta and reached its edge, the compositor handed the remaining
delta to an ancestor in the same event. The sync path treats the first
scrollable target that moves as having handled the event.
Stop the async handoff after any scroll node consumes part of the delta
so async and sync wheel handling agree. Hit testing still selects an
ancestor when the original target cannot scroll in that direction, which
preserves the existing boundary fallback behavior.
AsyncScrollTree stores display-list-derived regions separately from the
viewport-space data rebuilt from the current scroll state. The wheel hit
test and main-thread wheel-event caches were still named like source
metadata, which made them harder to distinguish from the region vectors.
Prefix the cached vectors and the main-thread cached target type with
Cached so they match CachedWheelHitTestTarget and blocking-wheel cache.
Root-level blocking wheel listeners still need to disable async wheel
routing globally, but non-root listeners only block the regions where
their event path can cancel the wheel event. The previous routing
admission treated any blocking listener as global, which prevented
async scrolling in unaffected nested scrollports.
Base routing admission on viewport-covering blocking regions instead of
the presence of any blocking listener, leaving local blocker rejection
to the compositor hit test. Update internals so blocked positions report
no async wheel target. Update the admission test to cover the routing
distinction and add a nested scroller test where a non-passive wheel
listener calls preventDefault(), keeping both the scroller and the
viewport at their original offsets.
Nested scroll nodes were present in the async scroll tree, but the
compositor rejected any non-viewport wheel target and tracked only one
pending viewport offset. That prevented element scrollports from being
scrolled asynchronously and made compositor-side offsets impossible to
adopt after the main thread rebuilt scroll state.
Let async wheel commands target any scroll node selected by the tree.
Store every offset produced by a compositor scroll and reapply pending
offsets by stable ID across display-list and scroll-state updates. The
main thread now adopts viewport, element and pseudo-element offsets
before rendering-update observers run, including scroll event
bookkeeping for element scrollers.
Carry the wheel hit-test rejection reason through the enqueue path while
doing this, so main-thread and blocking-wheel regions remain explicit
rather than being collapsed into a missing target. Existing nested
async-scrolling text tests cover the successful nested scroll path.
Scroll frame indices are tied to a particular display-list snapshot.
That is enough while the compositor only preserves the viewport
offset, but it cannot reconcile async offsets after repaint for
element or pseudo-element scroll nodes whose frame indices were rebuilt.
Record each compositor scroll node with a stable DOM-backed identity:
the document for viewport nodes, the DOM element for element scrollers,
and the pseudo-element generator plus pseudo type for pseudo-element
scrollers. Parse that identity into AsyncScrollingState and expose
lookup by stable ID in AsyncScrollTree so later commits can preserve
non-viewport compositor offsets across display-list rebuilds. Existing
async-scrolling metadata tests continue to cover the generated
scroll-node structure.
Async wheel routing used scroll node scrollports as the hit-test
surface. That works while the scrollable content is the topmost content
under the pointer, but it ignores the rest of the painted scene. A
sibling with a higher z-index can visually cover a nested scroller
without being part of that scroller's subtree. In that case the
compositor could still pick the covered scroller from its scrollport
rect and consume the wheel event, even though normal hit testing would
target the covering element and let the page or an ancestor handle the
scroll.
Record wheel hit-test targets as display-list metadata in paint order
and resolve each target to the scroll frame that should receive wheel
deltas. The compositor can then walk those targets from front to back,
preserve non-passive wheel regions as main-thread barriers, and only
scroll the node associated with the topmost hit-testable box.
Use compositor hit-test commands in the display list to rebuild async
wheel targets, and serialize the async scroll metadata needed to
reconstruct AsyncScrollingState from the same display-list snapshot.
Driving the async scroll tree off the display list rather than a
separately collected tree has a few benefits:
- No additional full paintable tree traversal is required, since the
information needed by the compositor is gathered while recording
the display list.
- The display list is already serializable, so the async scroll tree
no longer needs its own serialization path.
- It is more debuggable, as the existing display list dump now also
covers the data used to reconstruct the async scroll tree.
- In the future we will want to include other areas that can
interfere with hit-testing; recording them during display list
construction makes it straightforward to preserve a hit-testing
order that matches painting order.
Keep compositor-side scroll snapshots aligned with async viewport
scrolling when main-thread scroll state arrives while an async viewport
offset is pending. A stale scroll-state-only update could otherwise
replace the snapshot used for display list replay and wheel hit-testing
with an older viewport offset, even though the compositor had already
presented newer async scroll positions.
Teach AsyncScrollTree to set a node scroll offset directly and use it
when reconciling both display-list and scroll-state updates. Recompute
the main-thread viewport rect after display list recording as pending
async scroll adoption can move the viewport before presentation.
Allow compositor wheel routing to stay enabled when the scroll tree also
contains non-viewport scroll nodes. The compositor still rejects an
individual wheel whose hit-test target is not the viewport, so element
scrollers continue to fall back to the main thread until their scroll
offsets can be adopted safely.
Move the routing admission helper next to the async scrolling state and
expose test-only internals for the routing result and wheel target. Add
coverage for a page that has both a viewport scroller and a nested
element scroller.
Add main-thread wheel regions to async scrolling snapshots for embedded
navigable containers. A viewport scroll node covers the iframe box, so
the compositor must explicitly treat that rectangle as a synchronous
routing boundary before it accepts a UI-process wheel bypass.
Hit-test those regions in both internals admission and the compositor's
async enqueue path. The enqueue path now stores the hit-tested scroll
target in the queued command, ensuring accepted input is applied to the
same target that passed admission instead of being reselected later.
Add a text test for iframe routing. A wheel over the nested navigable is
rejected for async scrolling, while a wheel over the parent viewport
remains admitted.
Record the scroll geometry that CompositorThread can safely reason about
while the main thread is painting. The snapshot contains stable scroll
node IDs, parent links, scroll bounds, sticky inputs, and wheel blocker
regions in display-list coordinate space.
Add AsyncScrollingState and AsyncScrollTree under LibWeb/Compositor. The
state is the immutable main-thread snapshot; the tree is the mutable
compositor-side copy that can replay scroll deltas and sticky offsets
without touching DOM, layout, or paintables.
Expose the state through internals and add text tests for tree shape,
parent links, sticky areas, blocker hit testing, nested scrollers, and
admission decisions. Keep the directory skipped unless the feature is
enabled with --enable-async-scrolling.