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-12 16:39:17 -03:00
|
|
|
#include <LibWeb/CSS/ComputedValues.h>
|
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/DOM/DOMEventListener.h>
|
|
|
|
|
#include <LibWeb/DOM/Document.h>
|
|
|
|
|
#include <LibWeb/DOM/EventTarget.h>
|
|
|
|
|
#include <LibWeb/HTML/Navigable.h>
|
2026-05-11 17:35:57 -03:00
|
|
|
#include <LibWeb/HTML/NavigableContainer.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/HTML/Window.h>
|
|
|
|
|
#include <LibWeb/Page/Page.h>
|
|
|
|
|
#include <LibWeb/Painting/DisplayList.h>
|
2026-05-13 08:13:38 -03:00
|
|
|
#include <LibWeb/Painting/DisplayListRecorder.h>
|
|
|
|
|
#include <LibWeb/Painting/DisplayListRecordingContext.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/Painting/PaintableBox.h>
|
|
|
|
|
#include <LibWeb/Painting/ScrollState.h>
|
|
|
|
|
#include <LibWeb/Painting/ViewportPaintable.h>
|
|
|
|
|
#include <LibWeb/PixelUnits.h>
|
|
|
|
|
|
|
|
|
|
namespace Web::Compositor {
|
|
|
|
|
|
|
|
|
|
static Gfx::FloatPoint css_point_to_device_point(CSSPixelPoint point, double device_pixels_per_css_pixel)
|
|
|
|
|
{
|
|
|
|
|
auto scale = static_cast<float>(device_pixels_per_css_pixel);
|
|
|
|
|
return { point.x().to_float() * scale, point.y().to_float() * scale };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static Gfx::FloatSize css_size_to_device_size(CSSPixelSize size, double device_pixels_per_css_pixel)
|
|
|
|
|
{
|
|
|
|
|
auto scale = static_cast<float>(device_pixels_per_css_pixel);
|
|
|
|
|
return { size.width().to_float() * scale, size.height().to_float() * scale };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static Gfx::FloatRect css_rect_to_device_rect(CSSPixelRect rect, double device_pixels_per_css_pixel)
|
|
|
|
|
{
|
|
|
|
|
return { css_point_to_device_point(rect.location(), device_pixels_per_css_pixel), css_size_to_device_size(rect.size(), device_pixels_per_css_pixel) };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static Optional<float> css_inset_to_device_inset(Optional<CSSPixels> inset, double device_pixels_per_css_pixel)
|
|
|
|
|
{
|
|
|
|
|
if (!inset.has_value())
|
|
|
|
|
return {};
|
|
|
|
|
return inset->to_float() * static_cast<float>(device_pixels_per_css_pixel);
|
|
|
|
|
}
|
|
|
|
|
|
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 };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool is_nested_navigable_container(Painting::PaintableBox const& paintable_box)
|
|
|
|
|
{
|
|
|
|
|
auto node = paintable_box.dom_node();
|
|
|
|
|
return node && node->is_navigable_container() && as<HTML::NavigableContainer const>(*node).content_navigable();
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
static CSSPixelPoint maximum_scroll_offset_for(Painting::PaintableBox const& paintable_box)
|
|
|
|
|
{
|
|
|
|
|
CSSPixelPoint max_scroll_offset;
|
|
|
|
|
auto scrollable_overflow_rect = paintable_box.scrollable_overflow_rect();
|
|
|
|
|
if (!scrollable_overflow_rect.has_value())
|
|
|
|
|
return max_scroll_offset;
|
|
|
|
|
|
|
|
|
|
auto scrollport_rect = paintable_box.absolute_padding_box_rect();
|
|
|
|
|
max_scroll_offset.set_x(max(CSSPixels(0), scrollable_overflow_rect->width() - scrollport_rect.width()));
|
|
|
|
|
max_scroll_offset.set_y(max(CSSPixels(0), scrollable_overflow_rect->height() - scrollport_rect.height()));
|
|
|
|
|
return max_scroll_offset;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-13 08:13:38 -03:00
|
|
|
static void record_scroll_node(Painting::PaintableBox const& paintable_box, DisplayListRecordingContext& context)
|
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
|
|
|
auto parent_scroll_frame_index = Painting::ScrollFrameIndex {};
|
|
|
|
|
if (auto scrollable_ancestor = paintable_box.nearest_scrollable_ancestor())
|
|
|
|
|
parent_scroll_frame_index = scrollable_ancestor->own_scroll_frame_index();
|
|
|
|
|
|
|
|
|
|
auto scrollport_rect = paintable_box.is_viewport_paintable()
|
|
|
|
|
? Gfx::IntRect { {}, context.device_viewport_rect().size().to_type<int>() }
|
|
|
|
|
: context.rounded_device_rect(paintable_box.absolute_padding_box_rect()).to_type<int>();
|
|
|
|
|
|
|
|
|
|
auto& recorder = context.display_list_recorder();
|
|
|
|
|
recorder.compositor_scroll_node({
|
|
|
|
|
.document_id = paintable_box.document().unique_id(),
|
|
|
|
|
.scroll_frame_index = paintable_box.own_scroll_frame_index(),
|
|
|
|
|
.parent_scroll_frame_index = parent_scroll_frame_index,
|
|
|
|
|
.scrollport_rect = scrollport_rect,
|
|
|
|
|
.max_scroll_offset = css_point_to_device_point(maximum_scroll_offset_for(paintable_box), context.device_pixels_per_css_pixel()),
|
|
|
|
|
.is_viewport = paintable_box.is_viewport_paintable(),
|
|
|
|
|
});
|
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
|
|
|
static void record_main_thread_wheel_event_region(Painting::PaintableBox const& paintable_box, DisplayListRecordingContext& context)
|
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
|
|
|
auto rect = css_rect_to_device_rect(paintable_box.absolute_united_border_box_rect(), context.device_pixels_per_css_pixel());
|
|
|
|
|
if (rect.is_empty())
|
|
|
|
|
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
|
|
|
context.display_list_recorder().compositor_main_thread_wheel_event_region({
|
|
|
|
|
.rect = 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-14 07:22:26 -03:00
|
|
|
static Optional<Painting::ScrollFrameIndex> wheel_hit_test_target_scroll_frame_index_for(Painting::PaintableBox const& paintable_box)
|
|
|
|
|
{
|
|
|
|
|
if (paintable_box.own_scroll_frame_index().value() && paintable_box.could_be_scrolled_by_wheel_event())
|
|
|
|
|
return paintable_box.own_scroll_frame_index();
|
|
|
|
|
if (auto scrollable_ancestor = paintable_box.nearest_scrollable_ancestor())
|
|
|
|
|
return scrollable_ancestor->own_scroll_frame_index();
|
|
|
|
|
if (auto viewport_paintable = paintable_box.document().paintable(); viewport_paintable && viewport_paintable->could_be_scrolled_by_wheel_event())
|
|
|
|
|
return viewport_paintable->own_scroll_frame_index();
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void record_wheel_hit_test_target(Painting::PaintableBox const& paintable_box, DisplayListRecordingContext& context)
|
|
|
|
|
{
|
|
|
|
|
if (!paintable_box.is_visible() || !paintable_box.visible_for_hit_testing())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
auto rect = css_rect_to_device_rect(paintable_box.absolute_border_box_rect(), context.device_pixels_per_css_pixel());
|
|
|
|
|
if (rect.is_empty())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
context.display_list_recorder().compositor_wheel_hit_test_target({
|
|
|
|
|
.document_id = paintable_box.document().unique_id(),
|
|
|
|
|
.target_scroll_frame_index = wheel_hit_test_target_scroll_frame_index_for(paintable_box).value_or({}),
|
|
|
|
|
.rect = rect,
|
|
|
|
|
.corner_radii = paintable_box.border_radii_data().as_corners(context.device_pixel_converter()),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-13 08:13:38 -03:00
|
|
|
static void record_viewport_scrollbar_state(Painting::PaintableBox const& paintable_box, DisplayListRecordingContext& context)
|
2026-05-12 16:39:17 -03:00
|
|
|
{
|
|
|
|
|
if (!paintable_box.is_viewport_paintable())
|
|
|
|
|
return;
|
2026-05-13 08:13:38 -03:00
|
|
|
if (!paintable_box.document().page().async_scrolling_enabled())
|
|
|
|
|
return;
|
2026-05-12 16:39:17 -03:00
|
|
|
if (!Painting::should_paint_viewport_scrollbars())
|
|
|
|
|
return;
|
|
|
|
|
if (paintable_box.computed_values().scrollbar_width() == CSS::ScrollbarWidth::None)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
auto scrollbar_colors = paintable_box.computed_values().scrollbar_color();
|
2026-05-13 08:13:38 -03:00
|
|
|
auto const& metrics = context.chrome_metrics();
|
2026-05-12 16:39:17 -03:00
|
|
|
|
|
|
|
|
for (auto direction : { Painting::PaintableBox::ScrollDirection::Vertical, Painting::PaintableBox::ScrollDirection::Horizontal }) {
|
2026-05-12 17:04:12 -03:00
|
|
|
auto scrollbar_data = paintable_box.compute_scrollbar_data(direction, metrics, nullptr, Painting::PaintableBox::ScrollbarSizing::Regular);
|
2026-05-12 16:39:17 -03:00
|
|
|
if (!scrollbar_data.has_value())
|
|
|
|
|
continue;
|
2026-05-12 17:04:12 -03:00
|
|
|
auto expanded_scrollbar_data = paintable_box.compute_scrollbar_data(direction, metrics, nullptr, Painting::PaintableBox::ScrollbarSizing::Enlarged);
|
|
|
|
|
VERIFY(expanded_scrollbar_data.has_value());
|
2026-05-12 16:39:17 -03:00
|
|
|
|
2026-05-13 08:13:38 -03:00
|
|
|
auto gutter_rect = context.rounded_device_rect(scrollbar_data->gutter_rect).to_type<int>();
|
|
|
|
|
auto max_scroll_offset = css_point_to_device_point(maximum_scroll_offset_for(paintable_box), context.device_pixels_per_css_pixel());
|
2026-05-12 16:49:42 -03:00
|
|
|
auto orientation = direction == Painting::PaintableBox::ScrollDirection::Horizontal ? Gfx::Orientation::Horizontal : Gfx::Orientation::Vertical;
|
2026-05-12 16:39:17 -03:00
|
|
|
auto thumb_color = scrollbar_colors.thumb_color;
|
|
|
|
|
if (gutter_rect.is_empty() && thumb_color == CSS::InitialValues::scrollbar_color().thumb_color)
|
|
|
|
|
thumb_color = thumb_color.with_alpha(128);
|
|
|
|
|
|
2026-05-13 08:13:38 -03:00
|
|
|
context.display_list_recorder().compositor_viewport_scrollbar({
|
|
|
|
|
.document_id = paintable_box.document().unique_id(),
|
2026-05-12 16:39:17 -03:00
|
|
|
.scroll_frame_index = paintable_box.own_scroll_frame_index(),
|
|
|
|
|
.gutter_rect = gutter_rect,
|
2026-05-13 08:13:38 -03:00
|
|
|
.thumb_rect = context.rounded_device_rect(scrollbar_data->thumb_rect).to_type<int>(),
|
|
|
|
|
.expanded_gutter_rect = context.rounded_device_rect(expanded_scrollbar_data->gutter_rect).to_type<int>(),
|
|
|
|
|
.expanded_thumb_rect = context.rounded_device_rect(expanded_scrollbar_data->thumb_rect).to_type<int>(),
|
2026-05-12 16:39:17 -03:00
|
|
|
.scroll_size = scrollbar_data->thumb_travel_to_scroll_ratio.to_double(),
|
2026-05-12 17:04:12 -03:00
|
|
|
.expanded_scroll_size = expanded_scrollbar_data->thumb_travel_to_scroll_ratio.to_double(),
|
2026-05-12 16:49:42 -03:00
|
|
|
.max_scroll_offset = max_scroll_offset.primary_offset_for_orientation(orientation),
|
2026-05-12 16:39:17 -03:00
|
|
|
.thumb_color = thumb_color,
|
|
|
|
|
.track_color = scrollbar_colors.track_color,
|
|
|
|
|
.vertical = direction == Painting::PaintableBox::ScrollDirection::Vertical,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-13 08:13:38 -03:00
|
|
|
static bool has_blocking_wheel_event_listener(DOM::EventTarget const& event_target)
|
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 listener : event_target.event_listener_list()) {
|
|
|
|
|
if (AK::first_is_one_of(listener->type, "wheel"sv, "mousewheel"sv) && listener->passive != true)
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-13 08:13:38 -03:00
|
|
|
static bool is_root_wheel_event_target(DOM::Node const& node)
|
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
|
|
|
{
|
|
|
|
|
auto& document = node.document();
|
|
|
|
|
return &node == document.document_element() || &node == document.body();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void collect_root_blocking_wheel_event_regions(AsyncScrollingState& async_scrolling_state, DOM::Document& document)
|
|
|
|
|
{
|
2026-05-13 08:13:38 -03:00
|
|
|
DOM::EventTarget* roots[] = {
|
|
|
|
|
document.navigable() ? document.navigable()->active_window() : nullptr,
|
|
|
|
|
&document,
|
|
|
|
|
document.document_element(),
|
|
|
|
|
document.body(),
|
|
|
|
|
};
|
|
|
|
|
for (auto* target : roots) {
|
|
|
|
|
if (target && has_blocking_wheel_event_listener(*target)) {
|
|
|
|
|
async_scrolling_state.has_blocking_wheel_event_listeners = true;
|
|
|
|
|
async_scrolling_state.has_blocking_wheel_event_region_covering_viewport = true;
|
|
|
|
|
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
|
|
|
void initialize_async_scrolling_metadata_recording(DisplayListRecordingContext& context, Painting::ViewportPaintable& document_paintable)
|
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
|
|
|
{
|
|
|
|
|
AsyncScrollingState async_scrolling_state;
|
|
|
|
|
collect_root_blocking_wheel_event_regions(async_scrolling_state, document_paintable.document());
|
2026-05-13 08:13:38 -03:00
|
|
|
context.set_async_scrolling_metadata_context(
|
|
|
|
|
document_paintable.document().unique_id(),
|
|
|
|
|
document_paintable.scroll_state(),
|
|
|
|
|
async_scrolling_state.has_blocking_wheel_event_listeners,
|
|
|
|
|
async_scrolling_state.has_blocking_wheel_event_region_covering_viewport);
|
|
|
|
|
}
|
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
|
|
|
void record_async_scrolling_metadata_for_paintable(Painting::PaintableBox const& paintable_box, DisplayListRecordingContext& context)
|
|
|
|
|
{
|
|
|
|
|
if (!context.is_recording_async_scrolling_metadata())
|
|
|
|
|
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
|
|
|
auto device_pixels_per_css_pixel = context.device_pixels_per_css_pixel();
|
|
|
|
|
auto& recorder = context.display_list_recorder();
|
|
|
|
|
|
2026-05-14 07:22:26 -03:00
|
|
|
record_wheel_hit_test_target(paintable_box, context);
|
|
|
|
|
|
2026-05-13 08:13:38 -03:00
|
|
|
if (!context.has_blocking_wheel_event_region_covering_viewport()) {
|
|
|
|
|
if (auto node = paintable_box.dom_node(); node && !is_root_wheel_event_target(*node) && has_blocking_wheel_event_listener(*node)) {
|
|
|
|
|
context.set_has_blocking_wheel_event_listeners(true);
|
|
|
|
|
recorder.compositor_blocking_wheel_event_region({
|
2026-05-11 17:35:57 -03:00
|
|
|
.rect = css_rect_to_device_rect(paintable_box.absolute_united_border_box_rect(), device_pixels_per_css_pixel),
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-05-13 08:13:38 -03:00
|
|
|
}
|
2026-05-11 17:35:57 -03:00
|
|
|
|
2026-05-13 08:13:38 -03:00
|
|
|
if (is_nested_navigable_container(paintable_box)) {
|
|
|
|
|
record_main_thread_wheel_event_region(paintable_box, context);
|
|
|
|
|
} else if (paintable_box.own_scroll_frame_index().value() && paintable_box.could_be_scrolled_by_wheel_event()) {
|
|
|
|
|
record_scroll_node(paintable_box, context);
|
|
|
|
|
}
|
|
|
|
|
record_viewport_scrollbar_state(paintable_box, context);
|
|
|
|
|
|
|
|
|
|
auto const& scroll_state = context.async_scrolling_scroll_state();
|
|
|
|
|
auto sticky_frame_index = paintable_box.enclosing_scroll_frame_index();
|
|
|
|
|
if (paintable_box.is_sticky_position() && sticky_frame_index.value()) {
|
|
|
|
|
auto const& frame = scroll_state.frame_at(sticky_frame_index);
|
|
|
|
|
if (frame.is_sticky() && frame.has_sticky_constraints()) {
|
|
|
|
|
auto const& constraints = frame.sticky_constraints();
|
|
|
|
|
auto const& insets = constraints.insets;
|
|
|
|
|
recorder.compositor_sticky_area({
|
|
|
|
|
.document_id = context.async_scrolling_document_id(),
|
|
|
|
|
.scroll_frame_index = sticky_frame_index,
|
|
|
|
|
.parent_scroll_frame_index = frame.parent_index(),
|
|
|
|
|
.nearest_scrolling_ancestor_index = scroll_state.nearest_scrolling_ancestor(sticky_frame_index),
|
|
|
|
|
.position_relative_to_scroll_ancestor = css_point_to_device_point(constraints.position_relative_to_scroll_ancestor, device_pixels_per_css_pixel),
|
|
|
|
|
.border_box_size = css_size_to_device_size(constraints.border_box_size, device_pixels_per_css_pixel),
|
|
|
|
|
.scrollport_size = css_size_to_device_size(constraints.scrollport_size, device_pixels_per_css_pixel),
|
|
|
|
|
.containing_block_region = css_rect_to_device_rect(constraints.containing_block_region, device_pixels_per_css_pixel),
|
|
|
|
|
.needs_parent_offset_adjustment = constraints.needs_parent_offset_adjustment,
|
|
|
|
|
.inset_top = css_inset_to_device_inset(insets.top, device_pixels_per_css_pixel),
|
|
|
|
|
.inset_right = css_inset_to_device_inset(insets.right, device_pixels_per_css_pixel),
|
|
|
|
|
.inset_bottom = css_inset_to_device_inset(insets.bottom, device_pixels_per_css_pixel),
|
|
|
|
|
.inset_left = css_inset_to_device_inset(insets.left, device_pixels_per_css_pixel),
|
|
|
|
|
});
|
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
|
|
|
}
|
|
|
|
|
}
|
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
|
|
|
void finalize_async_scrolling_metadata_recording(DisplayListRecordingContext& context, HTML::Navigable& navigable, Gfx::IntRect viewport_rect)
|
|
|
|
|
{
|
|
|
|
|
if (!context.is_recording_async_scrolling_metadata())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
context.display_list_recorder().set_async_scrolling_metadata({
|
|
|
|
|
.viewport_rect = viewport_rect,
|
|
|
|
|
.wheel_event_listener_state_generation = navigable.page().wheel_event_listener_state_generation(),
|
|
|
|
|
.has_blocking_wheel_event_listeners = context.has_blocking_wheel_event_listeners(),
|
|
|
|
|
.has_blocking_wheel_event_region_covering_viewport = context.has_blocking_wheel_event_region_covering_viewport(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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) {
|
|
|
|
|
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),
|
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,
|
|
|
|
|
});
|
|
|
|
|
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);
|
|
|
|
|
async_scrolling_state.wheel_hit_test_targets.append({
|
|
|
|
|
.visual_context_index = header.context_index,
|
|
|
|
|
.rect = command.rect,
|
|
|
|
|
.corner_radii = command.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);
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
if (state.has_blocking_wheel_event_listeners)
|
|
|
|
|
return WheelRoutingAdmission::BlockingWheelEventListeners;
|
|
|
|
|
|
|
|
|
|
bool found_viewport_node = false;
|
|
|
|
|
for (auto const& node : state.scroll_nodes) {
|
|
|
|
|
if (node.is_viewport) {
|
|
|
|
|
found_viewport_node = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!found_viewport_node)
|
|
|
|
|
return WheelRoutingAdmission::NoViewportScrollNode;
|
|
|
|
|
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;
|
|
|
|
|
case WheelRoutingAdmission::NoViewportScrollNode:
|
|
|
|
|
return "no viewport 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();
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
bool blocks_wheel_event_at_position(AsyncScrollingState const& async_scrolling_state, RefPtr<Painting::DisplayList> const& display_list, Painting::ScrollStateSnapshot const& scroll_state_snapshot, Gfx::FloatPoint position)
|
|
|
|
|
{
|
|
|
|
|
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.
|
|
|
|
|
if (!display_list)
|
|
|
|
|
return async_scrolling_state.has_blocking_wheel_event_listeners;
|
|
|
|
|
|
|
|
|
|
auto const& visual_context_tree = display_list->visual_context_tree();
|
|
|
|
|
for (auto const& region : async_scrolling_state.blocking_wheel_event_regions) {
|
|
|
|
|
auto position_in_context = visual_context_tree.transform_point_for_hit_test(region.visual_context_index, position, scroll_state_snapshot);
|
|
|
|
|
if (position_in_context.has_value() && region.rect.contains(*position_in_context))
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-13 08:13:38 -03:00
|
|
|
static WheelHitTestResult hit_test_scroll_node_at_position(AsyncScrollingState const& async_scrolling_state, RefPtr<Painting::DisplayList> const& display_list, Painting::ScrollStateSnapshot const& scroll_state_snapshot, Gfx::FloatPoint position, Gfx::FloatPoint delta)
|
2026-05-11 17:35:57 -03:00
|
|
|
{
|
|
|
|
|
if (!display_list)
|
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-14 07:22:26 -03:00
|
|
|
scroll_tree.rebuild_wheel_hit_test_targets(display_list, 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-13 08:13:38 -03:00
|
|
|
WheelScrollAdmission admit_wheel_scroll(AsyncScrollingState const& async_scrolling_state, RefPtr<Painting::DisplayList> const& display_list, 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-13 08:13:38 -03:00
|
|
|
auto hit_test_result = hit_test_scroll_node_at_position(async_scrolling_state, display_list, scroll_state_snapshot, position, delta);
|
|
|
|
|
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;
|
|
|
|
|
if (blocks_wheel_event_at_position(async_scrolling_state, display_list, scroll_state_snapshot, position))
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|