Commit graph

16 commits

Author SHA1 Message Date
Aliaksandr Kalenik
08e448b119 LibWeb: Shrink wheel hit-test display-list targets
Every compositor wheel hit-test target carried a Gfx::CornerRadii
payload even though most targets have square corners. On YouTube this
made wheel hit-test targets the largest display-list bucket, with
thousands of targets paying for empty radii.

Keep CompositorWheelHitTestTarget as the compact square-corner command
and add CompositorWheelHitTestTargetWithCornerRadii for the rounded
case. Painting emits the larger command only when the computed corner
radii are non-empty, and async scrolling normalizes both payloads back
into the same target state.

A YouTube recording with 5,483 square-corner wheel hit-test targets used
80-byte compact commands instead of the 112-byte rounded command,
reducing the top-level display list by about 175 KiB for that page.
2026-05-31 18:26:23 +01:00
Aliaksandr Kalenik
b36e6c9b97 Compositor+LibWeb: Pass AVC trees separately from display lists
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.
2026-05-27 18:29:42 +01:00
Aliaksandr Kalenik
c78dd59cc3 LibWeb: Move async scrolling recording to paintables
Async scrolling display-list items were still recorded by helper
functions in LibWeb/Compositor even though they are produced while
walking the paint tree. That kept paint-time knowledge about boxes,
viewport state, and wheel-event regions in the compositor-facing code.

Move viewport setup and final metadata emission to ViewportPaintable,
and move per-box scroll-node, hit-test, scrollbar, wheel-region, and
sticky-area item recording to PaintableBox. AsyncScrollingState now
keeps the compositor-side conversion and lookup logic for consuming the
recorded metadata.
2026-05-25 00:45:24 +02:00
Andreas Kling
541828dbb1 LibWeb: Respect overflow axes for wheel scrolling
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.
2026-05-17 14:19:36 +02:00
Aliaksandr Kalenik
f44fc34737 LibWeb: Avoid GC::Root churn in wheel listener checks
Async scrolling metadata recording checks many paintable nodes for
blocking wheel listeners while building a display list. The helper used
EventTarget::event_listener_list(), which returns a snapshot backed by
GC::Root entries, so the hot paint path allocated and destroyed roots
just to answer a boolean query.

Add a non-allocating EventTarget predicate that scans the stored
listener references directly, and use it from async scrolling metadata
recording. Event dispatch still uses event_listener_list() so it keeps
the required snapshot semantics.
2026-05-15 17:25:10 +02:00
Aliaksandr Kalenik
1687a955d3 LibWeb: Respect blocking wheel regions for async scrolling
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.
2026-05-14 19:41:32 +02:00
Aliaksandr Kalenik
dbf26d1073 LibWeb: Track stable async scroll node identities
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.
2026-05-14 19:41:32 +02:00
Aliaksandr Kalenik
21245e7adb LibWeb: Follow paint order for async wheel hit testing
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.
2026-05-14 15:32:03 +02:00
Aliaksandr Kalenik
82b016ed19 LibWeb: Rebuild async scrolling from display list
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.
2026-05-13 18:36:07 +02:00
Andreas Kling
84e20ec6f3 LibWeb: Snap compositor-owned viewport scrollbars on hover
Collect both regular and enlarged viewport scrollbar geometry so the
compositor can expand the overlay while hovered or captured. Keep the
visibility decision on compositor-owned input state: the overlay appears
while a viewport scrollbar is hovered or captured, and disappears when
that state clears.

Keep the existing non-viewport scrollbar path unchanged.
2026-05-13 13:15:45 +02:00
Andreas Kling
99129f37a2 LibWeb: Handle viewport scrollbar drags in the compositor
Route mouse events over the compositor IPC path while async scrolling is
enabled, and let the compositor capture viewport scrollbar drags before
the main-thread event path sees them. Dragging mutates the compositor
scroll tree and schedules an async-scroll present, so the viewport thumb
and content can move without a main-thread round trip.

Keep normal main-thread event handling for misses and preserve the wheel
bypass behavior for page events.
2026-05-13 13:15:45 +02:00
Andreas Kling
a422db9dfb LibWeb: Paint viewport scrollbars in the compositor
Move async-scrolling viewport scrollbar painting out of the page
display list and into the compositor overlay. The main thread now
collects immutable scrollbar geometry and style alongside the async
scrolling snapshot, while the compositor applies the current scroll
snapshot when painting the thumb.

Keep the existing display-list path for nested scroll containers and
for pages that are not using async scrolling.
2026-05-13 13:15:45 +02:00
Andreas Kling
b57cc2bb7a LibWeb: Invalidate async wheel state on listener changes
Bump a page-level wheel listener generation whenever non-passive wheel
listeners are added or removed. Store that generation in async scrolling
snapshots, and mark compositor wheel admission stale when listener state
changes before a current snapshot arrives.

This keeps the UI-process compositor wheel bypass from trusting an old
no-listener snapshot after script has installed a cancelable wheel
listener. Add async-scrolling coverage for the listener generation bump
and the fresh routing state after a blocking wheel listener is added.
2026-05-12 20:57:08 +02:00
Andreas Kling
d36d336c82 LibWeb: Allow viewport async scroll with nested scrollers
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.
2026-05-12 20:57:08 +02:00
Andreas Kling
ce1521441f LibWeb: Reject async wheels over nested navigables
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.
2026-05-12 20:57:08 +02:00
Andreas Kling
191bd46cb8 LibWeb: Add compositor scroll state snapshots
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.
2026-05-12 20:57:08 +02:00