From c78dd59cc3f00db8046f37db090755d97864affc Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Sun, 24 May 2026 21:42:38 +0200 Subject: [PATCH] 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. --- .../LibWeb/Compositor/AsyncScrollingState.cpp | 301 ------------------ .../LibWeb/Compositor/AsyncScrollingState.h | 3 - Libraries/LibWeb/DOM/Document.cpp | 5 +- Libraries/LibWeb/Painting/PaintableBox.cpp | 251 ++++++++++++++- Libraries/LibWeb/Painting/PaintableBox.h | 1 + .../LibWeb/Painting/ViewportPaintable.cpp | 51 +++ Libraries/LibWeb/Painting/ViewportPaintable.h | 2 + 7 files changed, 305 insertions(+), 309 deletions(-) diff --git a/Libraries/LibWeb/Compositor/AsyncScrollingState.cpp b/Libraries/LibWeb/Compositor/AsyncScrollingState.cpp index 6324ed128a..24c875c25b 100644 --- a/Libraries/LibWeb/Compositor/AsyncScrollingState.cpp +++ b/Libraries/LibWeb/Compositor/AsyncScrollingState.cpp @@ -4,86 +4,18 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include #include #include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include #include -#include -#include - -#include namespace Web::Compositor { -static Gfx::FloatPoint css_point_to_device_point(CSSPixelPoint point, double device_pixels_per_css_pixel) -{ - auto scale = static_cast(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(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 css_inset_to_device_inset(Optional inset, double device_pixels_per_css_pixel) -{ - if (!inset.has_value()) - return {}; - return inset->to_float() * static_cast(device_pixels_per_css_pixel); -} - 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 Optional scroll_node_kind_for(Painting::PaintableBox const& paintable_box) -{ - if (paintable_box.is_viewport_paintable()) - return Painting::CompositorScrollNodeKind::Viewport; - if (paintable_box.layout_node().generated_for_pseudo_element().has_value()) - return Painting::CompositorScrollNodeKind::PseudoElement; - if (paintable_box.dom_node() && is(*paintable_box.dom_node())) - return Painting::CompositorScrollNodeKind::Element; - return {}; -} - -static UniqueNodeID scrollable_node_id_for(Painting::PaintableBox const& paintable_box) -{ - if (paintable_box.is_viewport_paintable()) - return paintable_box.document().unique_id(); - if (paintable_box.layout_node().generated_for_pseudo_element().has_value()) - return paintable_box.layout_node().pseudo_element_generator()->unique_id(); - return paintable_box.dom_node()->unique_id(); -} - -static u8 pseudo_element_type_for(Painting::PaintableBox const& paintable_box) -{ - auto pseudo_element = paintable_box.layout_node().generated_for_pseudo_element(); - if (!pseudo_element.has_value()) - return 0; - return static_cast(to_underlying(*pseudo_element)); -} - static AsyncScrollNodeKind async_scroll_node_kind_for(Painting::CompositorScrollNodeKind kind) { switch (kind) { @@ -106,239 +38,6 @@ static AsyncScrollNodeStableID stable_scroll_node_id_for(UniqueNodeID scrollable }; } -static bool is_nested_navigable_container(Painting::PaintableBox const& paintable_box) -{ - auto node = paintable_box.dom_node(); - return node && node->is_navigable_container() && as(*node).content_navigable(); -} - -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; -} - -static void record_scroll_node(Painting::PaintableBox const& paintable_box, DisplayListRecordingContext& context) -{ - auto scroll_node_kind = scroll_node_kind_for(paintable_box); - if (!scroll_node_kind.has_value()) - return; - - 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() } - : context.rounded_device_rect(paintable_box.absolute_padding_box_rect()).to_type(); - - auto& recorder = context.display_list_recorder(); - recorder.compositor_scroll_node({ - .document_id = paintable_box.document().unique_id(), - .scrollable_node_id = scrollable_node_id_for(paintable_box), - .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()), - .scroll_node_kind = *scroll_node_kind, - .pseudo_element_type = pseudo_element_type_for(paintable_box), - .is_viewport = paintable_box.is_viewport_paintable(), - .can_be_wheel_scrolled_horizontally = paintable_box.could_be_scrolled_by_wheel_event(Painting::PaintableBox::ScrollDirection::Horizontal), - .can_be_wheel_scrolled_vertically = paintable_box.could_be_scrolled_by_wheel_event(Painting::PaintableBox::ScrollDirection::Vertical), - }); -} - -static void record_main_thread_wheel_event_region(Painting::PaintableBox const& paintable_box, DisplayListRecordingContext& context) -{ - 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; - - context.display_list_recorder().compositor_main_thread_wheel_event_region({ - .rect = rect, - }); -} - -static Optional 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()), - }); -} - -static void record_viewport_scrollbar_state(Painting::PaintableBox const& paintable_box, DisplayListRecordingContext& context) -{ - if (!paintable_box.is_viewport_paintable()) - return; - if (!paintable_box.document().page().async_scrolling_enabled()) - return; - 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(); - auto const& metrics = context.chrome_metrics(); - - for (auto direction : { Painting::PaintableBox::ScrollDirection::Vertical, Painting::PaintableBox::ScrollDirection::Horizontal }) { - auto scrollbar_data = paintable_box.compute_scrollbar_data(direction, metrics, nullptr, Painting::PaintableBox::ScrollbarSizing::Regular); - if (!scrollbar_data.has_value()) - continue; - auto expanded_scrollbar_data = paintable_box.compute_scrollbar_data(direction, metrics, nullptr, Painting::PaintableBox::ScrollbarSizing::Enlarged); - VERIFY(expanded_scrollbar_data.has_value()); - - auto gutter_rect = context.rounded_device_rect(scrollbar_data->gutter_rect).to_type(); - auto max_scroll_offset = css_point_to_device_point(maximum_scroll_offset_for(paintable_box), context.device_pixels_per_css_pixel()); - auto orientation = direction == Painting::PaintableBox::ScrollDirection::Horizontal ? Gfx::Orientation::Horizontal : Gfx::Orientation::Vertical; - 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); - - context.display_list_recorder().compositor_viewport_scrollbar({ - .document_id = paintable_box.document().unique_id(), - .scroll_frame_index = paintable_box.own_scroll_frame_index(), - .gutter_rect = gutter_rect, - .thumb_rect = context.rounded_device_rect(scrollbar_data->thumb_rect).to_type(), - .expanded_gutter_rect = context.rounded_device_rect(expanded_scrollbar_data->gutter_rect).to_type(), - .expanded_thumb_rect = context.rounded_device_rect(expanded_scrollbar_data->thumb_rect).to_type(), - .scroll_size = scrollbar_data->thumb_travel_to_scroll_ratio.to_double(), - .expanded_scroll_size = expanded_scrollbar_data->thumb_travel_to_scroll_ratio.to_double(), - .max_scroll_offset = max_scroll_offset.primary_offset_for_orientation(orientation), - .thumb_color = thumb_color, - .track_color = scrollbar_colors.track_color, - .vertical = direction == Painting::PaintableBox::ScrollDirection::Vertical, - }); - } -} - -static bool is_root_wheel_event_target(DOM::Node const& node) -{ - 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) -{ - DOM::EventTarget* roots[] = { - document.navigable() ? document.navigable()->active_window() : nullptr, - &document, - document.document_element(), - document.body(), - }; - for (auto* target : roots) { - if (target && target->has_blocking_wheel_event_listener()) { - async_scrolling_state.has_blocking_wheel_event_listeners = true; - async_scrolling_state.has_blocking_wheel_event_region_covering_viewport = true; - return; - } - } -} - -void initialize_async_scrolling_metadata_recording(DisplayListRecordingContext& context, Painting::ViewportPaintable& document_paintable) -{ - AsyncScrollingState async_scrolling_state; - collect_root_blocking_wheel_event_regions(async_scrolling_state, document_paintable.document()); - 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); -} - -void record_async_scrolling_metadata_for_paintable(Painting::PaintableBox const& paintable_box, DisplayListRecordingContext& context) -{ - if (!context.is_recording_async_scrolling_metadata()) - return; - - auto device_pixels_per_css_pixel = context.device_pixels_per_css_pixel(); - auto& recorder = context.display_list_recorder(); - - record_wheel_hit_test_target(paintable_box, context); - - if (!context.has_blocking_wheel_event_region_covering_viewport()) { - if (auto node = paintable_box.dom_node(); node && !is_root_wheel_event_target(*node) && node->has_blocking_wheel_event_listener()) { - context.set_has_blocking_wheel_event_listeners(true); - recorder.compositor_blocking_wheel_event_region({ - .rect = css_rect_to_device_rect(paintable_box.absolute_united_border_box_rect(), device_pixels_per_css_pixel), - }); - } - } - - 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), - }); - } - } -} - -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; diff --git a/Libraries/LibWeb/Compositor/AsyncScrollingState.h b/Libraries/LibWeb/Compositor/AsyncScrollingState.h index 27892daca6..af4320d91a 100644 --- a/Libraries/LibWeb/Compositor/AsyncScrollingState.h +++ b/Libraries/LibWeb/Compositor/AsyncScrollingState.h @@ -155,9 +155,6 @@ enum class WheelScrollAdmission { BlockedByWheelEventRegion, }; -WEB_API void initialize_async_scrolling_metadata_recording(DisplayListRecordingContext&, Painting::ViewportPaintable&); -WEB_API void record_async_scrolling_metadata_for_paintable(Painting::PaintableBox const&, DisplayListRecordingContext&); -WEB_API void finalize_async_scrolling_metadata_recording(DisplayListRecordingContext&, HTML::Navigable&, Gfx::IntRect viewport_rect); WEB_API AsyncScrollingState async_scrolling_state_from_display_list(Painting::DisplayList const&); WEB_API WheelRoutingAdmission wheel_routing_admission_for(AsyncScrollingState const&); WEB_API StringView wheel_routing_admission_to_string(WheelRoutingAdmission); diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index bac2fec005..c70f4fb18f 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -74,7 +74,6 @@ #include #include #include -#include #include #include #include @@ -8233,10 +8232,10 @@ RefPtr Document::record_display_list(HTML::PaintConfig co auto& viewport_paintable = *paintable(); viewport_paintable.refresh_scroll_state(); - Compositor::initialize_async_scrolling_metadata_recording(context, viewport_paintable); + viewport_paintable.initialize_async_scrolling_metadata_recording(context); viewport_paintable.paint_all_phases(context); - Compositor::finalize_async_scrolling_metadata_recording(context, *navigable(), viewport_rect.to_type()); + viewport_paintable.finalize_async_scrolling_metadata_recording(context, *navigable(), viewport_rect.to_type()); if (highlighted_node() && highlighted_node()->paintable()) { highlighted_node()->paintable()->paint_inspector_overlay(context); diff --git a/Libraries/LibWeb/Painting/PaintableBox.cpp b/Libraries/LibWeb/Painting/PaintableBox.cpp index 1422a4fd38..33696b735a 100644 --- a/Libraries/LibWeb/Painting/PaintableBox.cpp +++ b/Libraries/LibWeb/Painting/PaintableBox.cpp @@ -8,18 +8,20 @@ */ #include +#include #include #include #include #include #include -#include #include #include +#include #include #include #include #include +#include #include #include #include @@ -28,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -85,6 +88,198 @@ bool should_paint_viewport_scrollbars() return g_paint_viewport_scrollbars; } +static Gfx::FloatPoint css_point_to_device_point(CSSPixelPoint point, double device_pixels_per_css_pixel) +{ + auto scale = static_cast(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(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 css_inset_to_device_inset(Optional inset, double device_pixels_per_css_pixel) +{ + if (!inset.has_value()) + return {}; + return inset->to_float() * static_cast(device_pixels_per_css_pixel); +} + +static Optional scroll_node_kind_for(PaintableBox const& paintable_box) +{ + if (paintable_box.is_viewport_paintable()) + return CompositorScrollNodeKind::Viewport; + if (paintable_box.layout_node().generated_for_pseudo_element().has_value()) + return CompositorScrollNodeKind::PseudoElement; + if (paintable_box.dom_node() && is(*paintable_box.dom_node())) + return CompositorScrollNodeKind::Element; + return {}; +} + +static UniqueNodeID scrollable_node_id_for(PaintableBox const& paintable_box) +{ + if (paintable_box.is_viewport_paintable()) + return paintable_box.document().unique_id(); + if (paintable_box.layout_node().generated_for_pseudo_element().has_value()) + return paintable_box.layout_node().pseudo_element_generator()->unique_id(); + return paintable_box.dom_node()->unique_id(); +} + +static u8 pseudo_element_type_for(PaintableBox const& paintable_box) +{ + auto pseudo_element = paintable_box.layout_node().generated_for_pseudo_element(); + if (!pseudo_element.has_value()) + return 0; + return static_cast(to_underlying(*pseudo_element)); +} + +static bool is_nested_navigable_container(PaintableBox const& paintable_box) +{ + auto node = paintable_box.dom_node(); + return node && node->is_navigable_container() && as(*node).content_navigable(); +} + +static CSSPixelPoint maximum_scroll_offset_for(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; +} + +static void record_scroll_node(PaintableBox const& paintable_box, DisplayListRecordingContext& context) +{ + auto scroll_node_kind = scroll_node_kind_for(paintable_box); + if (!scroll_node_kind.has_value()) + return; + + auto parent_scroll_frame_index = 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() } + : context.rounded_device_rect(paintable_box.absolute_padding_box_rect()).to_type(); + + auto& recorder = context.display_list_recorder(); + recorder.compositor_scroll_node({ + .document_id = paintable_box.document().unique_id(), + .scrollable_node_id = scrollable_node_id_for(paintable_box), + .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()), + .scroll_node_kind = *scroll_node_kind, + .pseudo_element_type = pseudo_element_type_for(paintable_box), + .is_viewport = paintable_box.is_viewport_paintable(), + .can_be_wheel_scrolled_horizontally = paintable_box.could_be_scrolled_by_wheel_event(PaintableBox::ScrollDirection::Horizontal), + .can_be_wheel_scrolled_vertically = paintable_box.could_be_scrolled_by_wheel_event(PaintableBox::ScrollDirection::Vertical), + }); +} + +static void record_main_thread_wheel_event_region(PaintableBox const& paintable_box, DisplayListRecordingContext& context) +{ + 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; + + context.display_list_recorder().compositor_main_thread_wheel_event_region({ + .rect = rect, + }); +} + +static Optional wheel_hit_test_target_scroll_frame_index_for(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(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()), + }); +} + +static void record_viewport_scrollbar_state(PaintableBox const& paintable_box, DisplayListRecordingContext& context) +{ + if (!paintable_box.is_viewport_paintable()) + return; + if (!paintable_box.document().page().async_scrolling_enabled()) + return; + if (!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(); + auto const& metrics = context.chrome_metrics(); + + for (auto direction : { PaintableBox::ScrollDirection::Vertical, PaintableBox::ScrollDirection::Horizontal }) { + auto scrollbar_data = paintable_box.compute_scrollbar_data(direction, metrics, nullptr, PaintableBox::ScrollbarSizing::Regular); + if (!scrollbar_data.has_value()) + continue; + auto expanded_scrollbar_data = paintable_box.compute_scrollbar_data(direction, metrics, nullptr, PaintableBox::ScrollbarSizing::Enlarged); + VERIFY(expanded_scrollbar_data.has_value()); + + auto gutter_rect = context.rounded_device_rect(scrollbar_data->gutter_rect).to_type(); + auto max_scroll_offset = css_point_to_device_point(maximum_scroll_offset_for(paintable_box), context.device_pixels_per_css_pixel()); + auto orientation = direction == PaintableBox::ScrollDirection::Horizontal ? Gfx::Orientation::Horizontal : Gfx::Orientation::Vertical; + 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); + + context.display_list_recorder().compositor_viewport_scrollbar({ + .document_id = paintable_box.document().unique_id(), + .scroll_frame_index = paintable_box.own_scroll_frame_index(), + .gutter_rect = gutter_rect, + .thumb_rect = context.rounded_device_rect(scrollbar_data->thumb_rect).to_type(), + .expanded_gutter_rect = context.rounded_device_rect(expanded_scrollbar_data->gutter_rect).to_type(), + .expanded_thumb_rect = context.rounded_device_rect(expanded_scrollbar_data->thumb_rect).to_type(), + .scroll_size = scrollbar_data->thumb_travel_to_scroll_ratio.to_double(), + .expanded_scroll_size = expanded_scrollbar_data->thumb_travel_to_scroll_ratio.to_double(), + .max_scroll_offset = max_scroll_offset.primary_offset_for_orientation(orientation), + .thumb_color = thumb_color, + .track_color = scrollbar_colors.track_color, + .vertical = direction == PaintableBox::ScrollDirection::Vertical, + }); + } +} + +static bool is_root_wheel_event_target(DOM::Node const& node) +{ + auto& document = node.document(); + return &node == document.document_element() || &node == document.body(); +} + ResolvedCSSFilter resolve_css_filter(CSS::Filter const& computed_filter, PaintableBox const& paintable_box) { auto const& computed_values = paintable_box.computed_values(); @@ -740,10 +935,62 @@ Optional PaintableBox::compute_scrollbar_data(Scrol return scrollbar_data; } +void PaintableBox::record_async_scrolling_metadata(DisplayListRecordingContext& context) const +{ + if (!context.is_recording_async_scrolling_metadata()) + return; + + auto device_pixels_per_css_pixel = context.device_pixels_per_css_pixel(); + auto& recorder = context.display_list_recorder(); + + record_wheel_hit_test_target(*this, context); + + if (!context.has_blocking_wheel_event_region_covering_viewport()) { + if (auto node = dom_node(); node && !is_root_wheel_event_target(*node) && node->has_blocking_wheel_event_listener()) { + context.set_has_blocking_wheel_event_listeners(true); + recorder.compositor_blocking_wheel_event_region({ + .rect = css_rect_to_device_rect(absolute_united_border_box_rect(), device_pixels_per_css_pixel), + }); + } + } + + if (is_nested_navigable_container(*this)) { + record_main_thread_wheel_event_region(*this, context); + } else if (own_scroll_frame_index().value() && could_be_scrolled_by_wheel_event()) { + record_scroll_node(*this, context); + } + record_viewport_scrollbar_state(*this, context); + + auto const& scroll_state = context.async_scrolling_scroll_state(); + auto sticky_frame_index = enclosing_scroll_frame_index(); + if (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), + }); + } + } +} + void PaintableBox::paint(DisplayListRecordingContext& context, PaintPhase phase) const { if (phase == PaintPhase::Background) - Compositor::record_async_scrolling_metadata_for_paintable(*this, context); + record_async_scrolling_metadata(context); if (!is_visible()) return; diff --git a/Libraries/LibWeb/Painting/PaintableBox.h b/Libraries/LibWeb/Painting/PaintableBox.h index 8318931c52..6b6fdbad2f 100644 --- a/Libraries/LibWeb/Painting/PaintableBox.h +++ b/Libraries/LibWeb/Painting/PaintableBox.h @@ -44,6 +44,7 @@ public: virtual void reset_for_relayout(); virtual void paint(DisplayListRecordingContext&, PaintPhase) const override; + void record_async_scrolling_metadata(DisplayListRecordingContext&) const; RefPtr stacking_context(); RefPtr stacking_context() const; diff --git a/Libraries/LibWeb/Painting/ViewportPaintable.cpp b/Libraries/LibWeb/Painting/ViewportPaintable.cpp index 61729a8866..bb9e6e755c 100644 --- a/Libraries/LibWeb/Painting/ViewportPaintable.cpp +++ b/Libraries/LibWeb/Painting/ViewportPaintable.cpp @@ -8,9 +8,12 @@ #include #include #include +#include #include #include #include +#include +#include #include #include #include @@ -19,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -39,6 +43,53 @@ ViewportPaintable::ViewportPaintable(Layout::Viewport const& layout_viewport) ViewportPaintable::~ViewportPaintable() = default; +struct BlockingWheelEventRegionState { + bool has_blocking_wheel_event_listeners { false }; + bool has_blocking_wheel_event_region_covering_viewport { false }; +}; + +static BlockingWheelEventRegionState collect_root_blocking_wheel_event_regions(DOM::Document& document) +{ + DOM::EventTarget* roots[] = { + document.navigable() ? document.navigable()->active_window() : nullptr, + &document, + document.document_element(), + document.body(), + }; + for (auto* target : roots) { + if (target && target->has_blocking_wheel_event_listener()) { + return { + .has_blocking_wheel_event_listeners = true, + .has_blocking_wheel_event_region_covering_viewport = true, + }; + } + } + return {}; +} + +void ViewportPaintable::initialize_async_scrolling_metadata_recording(DisplayListRecordingContext& context) +{ + auto blocking_wheel_event_region_state = collect_root_blocking_wheel_event_regions(document()); + context.set_async_scrolling_metadata_context( + document().unique_id(), + scroll_state(), + blocking_wheel_event_region_state.has_blocking_wheel_event_listeners, + blocking_wheel_event_region_state.has_blocking_wheel_event_region_covering_viewport); +} + +void ViewportPaintable::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(), + }); +} + void ViewportPaintable::reset_for_relayout() { PaintableWithLines::reset_for_relayout(); diff --git a/Libraries/LibWeb/Painting/ViewportPaintable.h b/Libraries/LibWeb/Painting/ViewportPaintable.h index fda5b807f1..a9bbf0cd8e 100644 --- a/Libraries/LibWeb/Painting/ViewportPaintable.h +++ b/Libraries/LibWeb/Painting/ViewportPaintable.h @@ -22,6 +22,8 @@ public: virtual void reset_for_relayout() override; void paint_all_phases(DisplayListRecordingContext&); + void initialize_async_scrolling_metadata_recording(DisplayListRecordingContext&); + void finalize_async_scrolling_metadata_recording(DisplayListRecordingContext&, HTML::Navigable&, Gfx::IntRect viewport_rect); void build_stacking_context_tree_if_needed(); void assign_scroll_frames();