ladybird/Libraries/LibWeb/Compositor/AsyncScrollingState.h
Aliaksandr Kalenik b36e6c9b97 Compositor+LibWeb: Pass AVC trees separately from display lists
Display lists owned the accumulated visual context tree through a
ref-counted pointer. That tied visual-context state to display-list
lifetime and made compositor updates treat the two as one unit, even
though AVC trees need to become independently updateable compositor
state.

Make accumulated visual context trees plain versioned values, have each
display list store the compatible tree version, and pass the matching
tree alongside display-list updates and replay calls. Replay verifies
that the provided tree matches the display list before executing it.

This prepares the compositor for receiving AVC tree updates separately
from display-list updates: it now accepts the tree as a separate update
parameter, stores it next to the display list, and uses that stored tree
for replay and async-scroll hit testing. Nested display-list resources
carry their own tree snapshots for the same version check.
2026-05-27 18:29:42 +01:00

164 lines
6 KiB
C++

/*
* Copyright (c) 2026-present, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Optional.h>
#include <AK/StringView.h>
#include <AK/Types.h>
#include <AK/Vector.h>
#include <LibGfx/Color.h>
#include <LibGfx/CornerRadii.h>
#include <LibGfx/Point.h>
#include <LibGfx/Rect.h>
#include <LibWeb/Export.h>
#include <LibWeb/Forward.h>
#include <LibWeb/Painting/AccumulatedVisualContext.h>
#include <LibWeb/Painting/ScrollFrame.h>
namespace Web::Compositor {
using AsyncScrollOperationID = u64;
// Stable identifier for a scroll frame in a document; the frame index alone is not unique across nested documents.
struct AsyncScrollNodeID {
UniqueNodeID document_id;
Painting::ScrollFrameIndex scroll_frame_index;
bool operator==(AsyncScrollNodeID const&) const = default;
};
enum class AsyncScrollNodeKind : u8 {
Viewport,
Element,
PseudoElement,
};
// Stable identity for reconciling compositor-side scroll offsets after the paint snapshot has been rebuilt.
struct AsyncScrollNodeStableID {
UniqueNodeID node_id;
AsyncScrollNodeKind kind { AsyncScrollNodeKind::Element };
u8 pseudo_element_type { 0 };
bool operator==(AsyncScrollNodeStableID const&) const = default;
};
struct AsyncScrollOffset {
AsyncScrollNodeStableID stable_node_id;
Gfx::FloatPoint compositor_scroll_offset;
Gfx::FloatPoint unadopted_scroll_delta;
};
// One scrollable area from the paint snapshot. Non-viewport scrollports are stored in hit_test_visual_context_index
// coordinates and transformed to viewport coordinates when the compositor rebuilds wheel targets.
struct AsyncScrollNode {
AsyncScrollNodeID node_id;
AsyncScrollNodeStableID stable_node_id;
Optional<AsyncScrollNodeID> parent_node_id;
Painting::VisualContextIndex hit_test_visual_context_index;
Gfx::IntRect scrollport_rect;
Gfx::FloatPoint max_scroll_offset;
bool is_viewport { false };
bool can_be_wheel_scrolled_horizontally { false };
bool can_be_wheel_scrolled_vertically { false };
};
// Sticky elements are represented as scroll frames whose offset is derived from ancestor scroll offsets. Keep only
// the precomputed geometry needed to replay that calculation on the compositor thread after an async scroll mutation.
struct AsyncStickyArea {
UniqueNodeID document_id;
Painting::ScrollFrameIndex scroll_frame_index;
Painting::ScrollFrameIndex parent_scroll_frame_index;
Painting::ScrollFrameIndex nearest_scrolling_ancestor_index;
Gfx::FloatPoint position_relative_to_scroll_ancestor;
Gfx::FloatSize border_box_size;
Gfx::FloatSize scrollport_size;
Gfx::FloatRect containing_block_region;
bool needs_parent_offset_adjustment { false };
Optional<float> inset_top;
Optional<float> inset_right;
Optional<float> inset_bottom;
Optional<float> inset_left;
};
// A region with a non-passive wheel listener. Wheels inside it must stay on the main thread because script may cancel.
struct BlockingWheelEventRegion {
Painting::VisualContextIndex visual_context_index;
Gfx::FloatRect rect;
};
struct WheelHitTestTarget {
Painting::VisualContextIndex visual_context_index;
Gfx::FloatRect rect;
Gfx::CornerRadii corner_radii;
Optional<AsyncScrollNodeID> target_node_id;
};
// A region that must always use main-thread wheel routing even without a blocking listener, such as a nested navigable.
struct MainThreadWheelEventRegion {
Painting::VisualContextIndex visual_context_index;
Gfx::FloatRect rect;
};
struct ViewportScrollbar {
AsyncScrollNodeID scroll_node_id;
Painting::ScrollFrameIndex scroll_frame_index;
Gfx::IntRect gutter_rect;
Gfx::IntRect thumb_rect;
Gfx::IntRect expanded_gutter_rect;
Gfx::IntRect expanded_thumb_rect;
double scroll_size { 0 };
double expanded_scroll_size { 0 };
float max_scroll_offset { 0 };
Color thumb_color;
Color track_color;
bool vertical { false };
};
struct AsyncScrollingState {
Vector<AsyncScrollNode> scroll_nodes;
Vector<AsyncStickyArea> sticky_areas;
Vector<WheelHitTestTarget> wheel_hit_test_targets;
Vector<MainThreadWheelEventRegion> main_thread_wheel_event_regions;
Vector<ViewportScrollbar> viewport_scrollbars;
// Non-passive wheel listeners can cancel scrolling, so async scrolling must treat them as hard barriers.
// Viewport-wide barriers cover listeners on the root targets; element regions let input hit-testing accept
// async scrolling elsewhere.
Vector<BlockingWheelEventRegion> blocking_wheel_event_regions;
Gfx::IntRect viewport_rect;
// Bumped whenever wheel listener state changes so queued compositor snapshots
// cannot re-enable async wheel routing after a non-passive listener has been
// added.
u64 wheel_event_listener_state_generation { 0 };
bool has_blocking_wheel_event_listeners { false };
bool has_blocking_wheel_event_region_covering_viewport { false };
};
enum class WheelRoutingAdmission {
Accepted,
NoAsyncScrollingState,
BlockingWheelEventListeners,
NoScrollNode,
StaleWheelEventListeners,
};
enum class WheelScrollAdmission {
Accepted,
NoScrollableTarget,
BlockedByMainThreadRegion,
StaleBlockingWheelEventRegions,
BlockedByWheelEventRegion,
};
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);
WEB_API bool blocks_wheel_event_at_position(AsyncScrollingState const&, RefPtr<Painting::DisplayList const> const&, Painting::AccumulatedVisualContextTree const*, Painting::ScrollStateSnapshot const&, Gfx::FloatPoint position);
WEB_API WheelScrollAdmission admit_wheel_scroll(AsyncScrollingState const&, RefPtr<Painting::DisplayList const> const&, Painting::AccumulatedVisualContextTree const*, Painting::ScrollStateSnapshot const&, Gfx::FloatPoint position, Gfx::FloatPoint delta, bool blocking_wheel_event_regions_are_current);
}