ladybird/Libraries/LibWeb/Compositor/AsyncScrollTree.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

96 lines
4.4 KiB
C++

/*
* Copyright (c) 2026-present, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Optional.h>
#include <AK/RefPtr.h>
#include <AK/Vector.h>
#include <LibGfx/CornerRadii.h>
#include <LibGfx/Point.h>
#include <LibGfx/Rect.h>
#include <LibWeb/Compositor/AsyncScrollingState.h>
#include <LibWeb/Export.h>
#include <LibWeb/Forward.h>
#include <LibWeb/Painting/AccumulatedVisualContext.h>
#include <LibWeb/Painting/ScrollState.h>
namespace Web::Compositor {
struct WheelHitTestResult {
Optional<AsyncScrollNodeID> node_id;
bool blocked_by_main_thread_region { false };
bool blocked_by_wheel_event_region { false };
};
struct CachedWheelHitTestTarget {
Optional<AsyncScrollNodeID> target_node_id;
Painting::VisualContextIndex visual_context_index;
Gfx::FloatRect rect;
Gfx::CornerRadii corner_radii;
Gfx::FloatRect viewport_rect;
};
struct CachedMainThreadWheelEventTarget {
Painting::VisualContextIndex visual_context_index;
Gfx::FloatRect rect;
Gfx::FloatRect viewport_rect;
};
// Viewport-space cache of regions containing non-passive wheel listeners.
struct CachedBlockingWheelEventTarget {
Painting::VisualContextIndex visual_context_index;
Gfx::FloatRect rect;
Gfx::FloatRect viewport_rect;
};
// Mutable compositor-side copy of AsyncScrollingState. Current scroll offsets live in ScrollStateSnapshot; this tree
// owns scroll node geometry and derived hit-test targets.
class WEB_API AsyncScrollTree {
public:
void set_state(AsyncScrollingState&&);
void rebuild_wheel_hit_test_targets(RefPtr<Painting::DisplayList const> const&, Painting::AccumulatedVisualContextTree const*, Painting::ScrollStateSnapshot const&);
void clear_wheel_hit_test_targets();
Optional<Gfx::FloatPoint> scroll_offset_for_node(AsyncScrollNodeID, Painting::ScrollStateSnapshot const&) const;
Optional<AsyncScrollNodeID> viewport_scroll_node_id() const;
Optional<AsyncScrollNodeID> scroll_node_id_for_stable_id(AsyncScrollNodeStableID) const;
WheelHitTestResult hit_test_scroll_node_for_wheel(Gfx::FloatPoint position, Gfx::FloatPoint delta) const;
bool scroll_node_is_viewport(AsyncScrollNodeID) const;
Vector<AsyncScrollOffset> apply_scroll_delta(AsyncScrollNodeID, Gfx::FloatPoint delta, Painting::ScrollStateSnapshot&);
Optional<Gfx::FloatPoint> set_scroll_offset(AsyncScrollNodeID, Gfx::FloatPoint, Painting::ScrollStateSnapshot&);
private:
static Gfx::FloatPoint clamp_scroll_offset_to_node(AsyncScrollNode const&, Gfx::FloatPoint);
static Gfx::FloatPoint scroll_offset_for_node(AsyncScrollNode const&, Painting::ScrollStateSnapshot const&);
static bool can_scroll_node_by_delta(AsyncScrollNode const&, Painting::ScrollStateSnapshot const&, Gfx::FloatPoint);
static bool has_non_zero_scroll_delta(Gfx::FloatPoint);
AsyncScrollNode const* scroll_node_for_id(AsyncScrollNodeID) const;
WheelHitTestResult hit_test_result_for_scroll_node(AsyncScrollNodeID, Gfx::FloatPoint delta) const;
AsyncScrollNode const* scroll_node_for_stable_id(AsyncScrollNodeStableID) const;
AsyncStickyArea const* sticky_area_for_scroll_frame_index(Painting::ScrollFrameIndex) const;
Optional<AsyncScrollNodeID> scrollable_ancestor_for_node(AsyncScrollNodeID, Painting::ScrollStateSnapshot const&, Gfx::FloatPoint delta) const;
Optional<Painting::ScrollFrameIndex> parent_scroll_frame_index(Painting::ScrollFrameIndex) const;
Gfx::FloatPoint cumulative_device_offset_for_frame(Painting::ScrollFrameIndex, Painting::ScrollStateSnapshot const&) const;
Gfx::FloatPoint apply_scroll_delta_to_node(AsyncScrollNode const&, Gfx::FloatPoint delta, Painting::ScrollStateSnapshot&);
void update_sticky_offsets(Painting::ScrollStateSnapshot&) const;
Vector<AsyncScrollNode> m_scroll_nodes;
Vector<AsyncStickyArea> m_sticky_areas;
Vector<WheelHitTestTarget> m_wheel_hit_test_regions;
Vector<MainThreadWheelEventRegion> m_main_thread_wheel_event_regions;
Vector<CachedWheelHitTestTarget> m_cached_wheel_hit_test_targets;
Vector<BlockingWheelEventRegion> m_blocking_wheel_event_regions;
Vector<CachedMainThreadWheelEventTarget> m_cached_main_thread_wheel_event_targets;
Vector<CachedBlockingWheelEventTarget> m_cached_blocking_wheel_event_targets;
Painting::AccumulatedVisualContextTree const* m_visual_context_tree { nullptr };
Painting::ScrollStateSnapshot m_scroll_state_snapshot;
bool m_has_blocking_wheel_event_region_covering_viewport { false };
};
}