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-11 16:27:11 -03:00
|
|
|
/*
|
|
|
|
|
* Copyright (c) 2026-present, the Ladybird developers.
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
|
*/
|
|
|
|
|
|
2026-05-13 08:13:38 -03:00
|
|
|
#include <LibWeb/Compositor/AsyncScrollTree.h>
|
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-11 16:27:11 -03:00
|
|
|
#include <LibWeb/Compositor/AsyncScrollingState.h>
|
|
|
|
|
#include <LibWeb/Painting/DisplayList.h>
|
|
|
|
|
#include <LibWeb/Painting/ScrollState.h>
|
2026-05-14 11:20:06 -03:00
|
|
|
|
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-11 16:27:11 -03:00
|
|
|
namespace Web::Compositor {
|
|
|
|
|
|
2026-05-13 08:13:38 -03:00
|
|
|
static AsyncScrollNodeID scroll_node_id_for(UniqueNodeID document_id, Painting::ScrollFrameIndex scroll_frame_index)
|
|
|
|
|
{
|
|
|
|
|
return { .document_id = document_id, .scroll_frame_index = scroll_frame_index };
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-14 11:20:06 -03:00
|
|
|
static AsyncScrollNodeKind async_scroll_node_kind_for(Painting::CompositorScrollNodeKind kind)
|
|
|
|
|
{
|
|
|
|
|
switch (kind) {
|
|
|
|
|
case Painting::CompositorScrollNodeKind::Viewport:
|
|
|
|
|
return AsyncScrollNodeKind::Viewport;
|
|
|
|
|
case Painting::CompositorScrollNodeKind::Element:
|
|
|
|
|
return AsyncScrollNodeKind::Element;
|
|
|
|
|
case Painting::CompositorScrollNodeKind::PseudoElement:
|
|
|
|
|
return AsyncScrollNodeKind::PseudoElement;
|
|
|
|
|
}
|
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static AsyncScrollNodeStableID stable_scroll_node_id_for(UniqueNodeID scrollable_node_id, Painting::CompositorScrollNodeKind kind, u8 pseudo_element_type)
|
|
|
|
|
{
|
|
|
|
|
return {
|
|
|
|
|
.node_id = scrollable_node_id,
|
|
|
|
|
.kind = async_scroll_node_kind_for(kind),
|
|
|
|
|
.pseudo_element_type = pseudo_element_type,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-13 08:13:38 -03:00
|
|
|
AsyncScrollingState async_scrolling_state_from_display_list(Painting::DisplayList const& display_list)
|
|
|
|
|
{
|
|
|
|
|
AsyncScrollingState async_scrolling_state;
|
|
|
|
|
Vector<Painting::ScrollFrameIndex> parent_scroll_frame_indices;
|
2026-05-14 07:22:26 -03:00
|
|
|
Vector<Painting::ScrollFrameIndex> wheel_hit_test_target_scroll_frame_indices;
|
|
|
|
|
Vector<UniqueNodeID> wheel_hit_test_target_document_ids;
|
2026-05-13 08:13:38 -03:00
|
|
|
|
|
|
|
|
if (auto const& metadata = display_list.async_scrolling_metadata(); metadata.has_value()) {
|
|
|
|
|
async_scrolling_state.viewport_rect = metadata->viewport_rect;
|
|
|
|
|
async_scrolling_state.wheel_event_listener_state_generation = metadata->wheel_event_listener_state_generation;
|
|
|
|
|
async_scrolling_state.has_blocking_wheel_event_listeners = metadata->has_blocking_wheel_event_listeners;
|
|
|
|
|
async_scrolling_state.has_blocking_wheel_event_region_covering_viewport = metadata->has_blocking_wheel_event_region_covering_viewport;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
display_list.for_each_command_header([&](Painting::DisplayListCommandHeader const& header, ReadonlyBytes payload) {
|
2026-05-31 12:45:07 -03:00
|
|
|
auto append_wheel_hit_test_target = [&](auto const& command, Gfx::CornerRadii corner_radii) {
|
|
|
|
|
async_scrolling_state.wheel_hit_test_targets.append({
|
|
|
|
|
.visual_context_index = header.context_index,
|
|
|
|
|
.rect = command.rect,
|
|
|
|
|
.corner_radii = corner_radii,
|
|
|
|
|
.target_node_id = {},
|
|
|
|
|
});
|
|
|
|
|
wheel_hit_test_target_scroll_frame_indices.append(command.target_scroll_frame_index);
|
|
|
|
|
wheel_hit_test_target_document_ids.append(command.document_id);
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-13 08:13:38 -03:00
|
|
|
switch (header.type) {
|
|
|
|
|
case Painting::DisplayListCommandType::CompositorBlockingWheelEventRegion: {
|
|
|
|
|
auto command = Painting::read_display_list_command_payload<Painting::CompositorBlockingWheelEventRegion>(payload);
|
|
|
|
|
async_scrolling_state.has_blocking_wheel_event_listeners = true;
|
|
|
|
|
async_scrolling_state.blocking_wheel_event_regions.append({
|
|
|
|
|
.visual_context_index = header.context_index,
|
|
|
|
|
.rect = command.rect,
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Painting::DisplayListCommandType::CompositorStickyArea: {
|
|
|
|
|
auto command = Painting::read_display_list_command_payload<Painting::CompositorStickyArea>(payload);
|
|
|
|
|
async_scrolling_state.sticky_areas.append({
|
|
|
|
|
.document_id = command.document_id,
|
|
|
|
|
.scroll_frame_index = command.scroll_frame_index,
|
|
|
|
|
.parent_scroll_frame_index = command.parent_scroll_frame_index,
|
|
|
|
|
.nearest_scrolling_ancestor_index = command.nearest_scrolling_ancestor_index,
|
|
|
|
|
.position_relative_to_scroll_ancestor = command.position_relative_to_scroll_ancestor,
|
|
|
|
|
.border_box_size = command.border_box_size,
|
|
|
|
|
.scrollport_size = command.scrollport_size,
|
|
|
|
|
.containing_block_region = command.containing_block_region,
|
|
|
|
|
.needs_parent_offset_adjustment = command.needs_parent_offset_adjustment,
|
|
|
|
|
.inset_top = command.inset_top,
|
|
|
|
|
.inset_right = command.inset_right,
|
|
|
|
|
.inset_bottom = command.inset_bottom,
|
|
|
|
|
.inset_left = command.inset_left,
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Painting::DisplayListCommandType::CompositorScrollNode: {
|
|
|
|
|
auto command = Painting::read_display_list_command_payload<Painting::CompositorScrollNode>(payload);
|
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-11 16:27:11 -03:00
|
|
|
async_scrolling_state.scroll_nodes.append({
|
2026-05-13 08:13:38 -03:00
|
|
|
.node_id = scroll_node_id_for(command.document_id, command.scroll_frame_index),
|
2026-05-14 11:20:06 -03:00
|
|
|
.stable_node_id = stable_scroll_node_id_for(command.scrollable_node_id, command.scroll_node_kind, command.pseudo_element_type),
|
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-11 16:27:11 -03:00
|
|
|
.parent_node_id = {},
|
2026-05-13 08:13:38 -03:00
|
|
|
.hit_test_visual_context_index = header.context_index,
|
|
|
|
|
.scrollport_rect = command.scrollport_rect,
|
|
|
|
|
.max_scroll_offset = command.max_scroll_offset,
|
|
|
|
|
.is_viewport = command.is_viewport,
|
2026-05-16 19:53:24 -03:00
|
|
|
.can_be_wheel_scrolled_horizontally = command.can_be_wheel_scrolled_horizontally,
|
|
|
|
|
.can_be_wheel_scrolled_vertically = command.can_be_wheel_scrolled_vertically,
|
2026-05-13 08:13:38 -03:00
|
|
|
});
|
|
|
|
|
parent_scroll_frame_indices.append(command.parent_scroll_frame_index);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2026-05-14 07:22:26 -03:00
|
|
|
case Painting::DisplayListCommandType::CompositorWheelHitTestTarget: {
|
|
|
|
|
auto command = Painting::read_display_list_command_payload<Painting::CompositorWheelHitTestTarget>(payload);
|
2026-05-31 12:45:07 -03:00
|
|
|
append_wheel_hit_test_target(command, {});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Painting::DisplayListCommandType::CompositorWheelHitTestTargetWithCornerRadii: {
|
|
|
|
|
auto command = Painting::read_display_list_command_payload<Painting::CompositorWheelHitTestTargetWithCornerRadii>(payload);
|
|
|
|
|
append_wheel_hit_test_target(command, command.corner_radii);
|
2026-05-14 07:22:26 -03:00
|
|
|
break;
|
|
|
|
|
}
|
2026-05-13 08:13:38 -03:00
|
|
|
case Painting::DisplayListCommandType::CompositorMainThreadWheelEventRegion: {
|
|
|
|
|
auto command = Painting::read_display_list_command_payload<Painting::CompositorMainThreadWheelEventRegion>(payload);
|
|
|
|
|
async_scrolling_state.main_thread_wheel_event_regions.append({
|
|
|
|
|
.visual_context_index = header.context_index,
|
|
|
|
|
.rect = command.rect,
|
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-11 16:27:11 -03:00
|
|
|
});
|
2026-05-13 08:13:38 -03:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Painting::DisplayListCommandType::CompositorViewportScrollbar: {
|
|
|
|
|
auto command = Painting::read_display_list_command_payload<Painting::CompositorViewportScrollbar>(payload);
|
|
|
|
|
async_scrolling_state.viewport_scrollbars.append({
|
|
|
|
|
.scroll_node_id = scroll_node_id_for(command.document_id, command.scroll_frame_index),
|
|
|
|
|
.scroll_frame_index = command.scroll_frame_index,
|
|
|
|
|
.gutter_rect = command.gutter_rect,
|
|
|
|
|
.thumb_rect = command.thumb_rect,
|
|
|
|
|
.expanded_gutter_rect = command.expanded_gutter_rect,
|
|
|
|
|
.expanded_thumb_rect = command.expanded_thumb_rect,
|
|
|
|
|
.scroll_size = command.scroll_size,
|
|
|
|
|
.expanded_scroll_size = command.expanded_scroll_size,
|
|
|
|
|
.max_scroll_offset = command.max_scroll_offset,
|
|
|
|
|
.thumb_color = command.thumb_color,
|
|
|
|
|
.track_color = command.track_color,
|
|
|
|
|
.vertical = command.vertical,
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
break;
|
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-11 16:27:11 -03:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
VERIFY(parent_scroll_frame_indices.size() == async_scrolling_state.scroll_nodes.size());
|
2026-05-13 08:13:38 -03:00
|
|
|
for (size_t i = 0; i < async_scrolling_state.scroll_nodes.size(); ++i) {
|
|
|
|
|
auto parent_scroll_frame_index = parent_scroll_frame_indices[i];
|
|
|
|
|
if (parent_scroll_frame_index.value())
|
|
|
|
|
async_scrolling_state.scroll_nodes[i].parent_node_id = scroll_node_id_for(async_scrolling_state.scroll_nodes[i].node_id.document_id, parent_scroll_frame_index);
|
|
|
|
|
}
|
2026-05-14 07:22:26 -03:00
|
|
|
|
|
|
|
|
VERIFY(wheel_hit_test_target_scroll_frame_indices.size() == async_scrolling_state.wheel_hit_test_targets.size());
|
|
|
|
|
VERIFY(wheel_hit_test_target_document_ids.size() == async_scrolling_state.wheel_hit_test_targets.size());
|
|
|
|
|
for (size_t i = 0; i < async_scrolling_state.wheel_hit_test_targets.size(); ++i) {
|
|
|
|
|
auto target_scroll_frame_index = wheel_hit_test_target_scroll_frame_indices[i];
|
|
|
|
|
if (target_scroll_frame_index.value())
|
|
|
|
|
async_scrolling_state.wheel_hit_test_targets[i].target_node_id = scroll_node_id_for(wheel_hit_test_target_document_ids[i], target_scroll_frame_index);
|
|
|
|
|
}
|
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-11 16:27:11 -03:00
|
|
|
return async_scrolling_state;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-11 18:51:57 -03:00
|
|
|
WheelRoutingAdmission wheel_routing_admission_for(AsyncScrollingState const& state)
|
|
|
|
|
{
|
2026-05-14 11:25:42 -03:00
|
|
|
if (state.has_blocking_wheel_event_region_covering_viewport)
|
2026-05-11 18:51:57 -03:00
|
|
|
return WheelRoutingAdmission::BlockingWheelEventListeners;
|
|
|
|
|
|
2026-05-14 11:25:42 -03:00
|
|
|
if (state.scroll_nodes.is_empty())
|
|
|
|
|
return WheelRoutingAdmission::NoScrollNode;
|
2026-05-11 18:51:57 -03:00
|
|
|
return WheelRoutingAdmission::Accepted;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
StringView wheel_routing_admission_to_string(WheelRoutingAdmission admission)
|
|
|
|
|
{
|
|
|
|
|
switch (admission) {
|
|
|
|
|
case WheelRoutingAdmission::Accepted:
|
|
|
|
|
return "accepted"sv;
|
|
|
|
|
case WheelRoutingAdmission::NoAsyncScrollingState:
|
|
|
|
|
return "no async scrolling state"sv;
|
|
|
|
|
case WheelRoutingAdmission::BlockingWheelEventListeners:
|
|
|
|
|
return "blocking wheel event listeners"sv;
|
2026-05-14 11:25:42 -03:00
|
|
|
case WheelRoutingAdmission::NoScrollNode:
|
|
|
|
|
return "no scroll node"sv;
|
2026-05-11 19:19:00 -03:00
|
|
|
case WheelRoutingAdmission::StaleWheelEventListeners:
|
|
|
|
|
return "stale wheel event listeners"sv;
|
2026-05-11 18:51:57 -03:00
|
|
|
}
|
|
|
|
|
VERIFY_NOT_REACHED();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-27 12:51:03 -03:00
|
|
|
bool blocks_wheel_event_at_position(AsyncScrollingState const& async_scrolling_state, RefPtr<Painting::DisplayList const> const& display_list, Painting::AccumulatedVisualContextTree const* visual_context_tree, Painting::ScrollStateSnapshot const& scroll_state_snapshot, Gfx::FloatPoint position)
|
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-11 16:27:11 -03:00
|
|
|
{
|
|
|
|
|
if (async_scrolling_state.has_blocking_wheel_event_region_covering_viewport)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
// If a caller knows blocking wheel listeners exist but cannot provide a display list for visual-context hit
|
|
|
|
|
// testing, async scrolling must fail closed. Sending the input to the main thread is slower, but it preserves
|
|
|
|
|
// cancelability.
|
2026-05-27 12:51:03 -03:00
|
|
|
if (!display_list || !visual_context_tree)
|
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-11 16:27:11 -03:00
|
|
|
return async_scrolling_state.has_blocking_wheel_event_listeners;
|
|
|
|
|
|
2026-05-27 12:51:03 -03:00
|
|
|
VERIFY(display_list->compatible_visual_context_tree_version() == visual_context_tree->version());
|
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-11 16:27:11 -03:00
|
|
|
for (auto const& region : async_scrolling_state.blocking_wheel_event_regions) {
|
2026-05-27 12:51:03 -03:00
|
|
|
auto position_in_context = visual_context_tree->transform_point_for_hit_test(region.visual_context_index, position, scroll_state_snapshot);
|
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-11 16:27:11 -03:00
|
|
|
if (position_in_context.has_value() && region.rect.contains(*position_in_context))
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-27 12:51:03 -03:00
|
|
|
static WheelHitTestResult hit_test_scroll_node_at_position(AsyncScrollingState const& async_scrolling_state, RefPtr<Painting::DisplayList const> const& display_list, Painting::AccumulatedVisualContextTree const* visual_context_tree, Painting::ScrollStateSnapshot const& scroll_state_snapshot, Gfx::FloatPoint position, Gfx::FloatPoint delta)
|
2026-05-11 17:35:57 -03:00
|
|
|
{
|
2026-05-27 12:51:03 -03:00
|
|
|
if (!display_list || !visual_context_tree)
|
2026-05-13 08:13:38 -03:00
|
|
|
return {};
|
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-11 16:27:11 -03:00
|
|
|
|
2026-05-13 08:13:38 -03:00
|
|
|
AsyncScrollTree scroll_tree;
|
|
|
|
|
auto async_scrolling_state_copy = async_scrolling_state;
|
|
|
|
|
scroll_tree.set_state(move(async_scrolling_state_copy));
|
2026-05-27 12:51:03 -03:00
|
|
|
scroll_tree.rebuild_wheel_hit_test_targets(display_list, visual_context_tree, scroll_state_snapshot);
|
2026-05-13 08:13:38 -03:00
|
|
|
return scroll_tree.hit_test_scroll_node_for_wheel(position, delta);
|
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-11 16:27:11 -03:00
|
|
|
}
|
|
|
|
|
|
2026-05-27 12:51:03 -03:00
|
|
|
WheelScrollAdmission admit_wheel_scroll(AsyncScrollingState const& async_scrolling_state, RefPtr<Painting::DisplayList const> const& display_list, Painting::AccumulatedVisualContextTree const* visual_context_tree, Painting::ScrollStateSnapshot const& scroll_state_snapshot, Gfx::FloatPoint position, Gfx::FloatPoint delta, bool blocking_wheel_event_regions_are_current)
|
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-11 16:27:11 -03:00
|
|
|
{
|
2026-05-27 12:51:03 -03:00
|
|
|
auto hit_test_result = hit_test_scroll_node_at_position(async_scrolling_state, display_list, visual_context_tree, scroll_state_snapshot, position, delta);
|
2026-05-13 08:13:38 -03:00
|
|
|
if (hit_test_result.blocked_by_main_thread_region)
|
2026-05-11 17:35:57 -03:00
|
|
|
return WheelScrollAdmission::BlockedByMainThreadRegion;
|
|
|
|
|
|
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-11 16:27:11 -03:00
|
|
|
// Async scrolling may only start when the snapshot can prove that the wheel event cannot be canceled by script
|
|
|
|
|
// at this position. Stale or missing blocker information sends the event back to the main thread.
|
2026-05-13 08:13:38 -03:00
|
|
|
if (async_scrolling_state.has_blocking_wheel_event_listeners) {
|
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-11 16:27:11 -03:00
|
|
|
if (!blocking_wheel_event_regions_are_current)
|
|
|
|
|
return WheelScrollAdmission::StaleBlockingWheelEventRegions;
|
2026-05-27 12:51:03 -03:00
|
|
|
if (blocks_wheel_event_at_position(async_scrolling_state, display_list, visual_context_tree, scroll_state_snapshot, position))
|
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-11 16:27:11 -03:00
|
|
|
return WheelScrollAdmission::BlockedByWheelEventRegion;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-13 08:13:38 -03:00
|
|
|
if (!hit_test_result.node_id.has_value())
|
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-11 16:27:11 -03:00
|
|
|
return WheelScrollAdmission::NoScrollableTarget;
|
|
|
|
|
return WheelScrollAdmission::Accepted;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|