LibWeb: Preserve compatible visual context tree versions

Let accumulated visual context updates keep the previous tree version
when rebuilt with the same shape. Display lists reference visual
context tree versions, so keep compositor-only updates on the old
version unless the tree structure changes.

Add coverage for version reuse and incompatible tree shapes.
This commit is contained in:
Andreas Kling 2026-06-16 21:13:20 +02:00 committed by Andreas Kling
parent be5320b67c
commit 1b8072371b
5 changed files with 95 additions and 4 deletions

View file

@ -482,6 +482,34 @@ void AccumulatedVisualContextTree::set_visual_viewport_transform(TransformData t
m_nodes[VISUAL_VIEWPORT_NODE_INDEX.value()].data = move(transform);
}
bool AccumulatedVisualContextTree::is_compatible_with(AccumulatedVisualContextTree const& other) const
{
if (m_nodes.size() != other.m_nodes.size())
return false;
for (size_t i = 0; i < m_nodes.size(); ++i) {
auto const& node = m_nodes[i];
auto const& other_node = other.m_nodes[i];
if (node.parent_index != other_node.parent_index)
return false;
if (node.has_empty_effective_clip != other_node.has_empty_effective_clip)
return false;
if (!node.data.visit([&](auto const& data) {
using DataType = RemoveCVReference<decltype(data)>;
return other_node.data.has<DataType>();
}))
return false;
}
return true;
}
void AccumulatedVisualContextTree::reuse_version_from(AccumulatedVisualContextTree const& other)
{
VERIFY(is_compatible_with(other));
m_version = other.m_version;
}
VisualContextIndex AccumulatedVisualContextTree::find_common_ancestor(VisualContextIndex a, VisualContextIndex b) const
{
VERIFY(a.value() < m_nodes.size());

View file

@ -98,8 +98,8 @@ public:
Yes,
};
static AccumulatedVisualContextTree create();
static AccumulatedVisualContextTree create(TransformData visual_viewport_transform);
static WEB_API AccumulatedVisualContextTree create();
static WEB_API AccumulatedVisualContextTree create(TransformData visual_viewport_transform);
AccumulatedVisualContextTree(AccumulatedVisualContextTree const&) = default;
AccumulatedVisualContextTree& operator=(AccumulatedVisualContextTree const&) = default;
@ -109,8 +109,10 @@ public:
u64 version() const { return m_version; }
VisualContextIndex append(VisualContextData data, VisualContextIndex parent_index);
WEB_API VisualContextIndex append(VisualContextData data, VisualContextIndex parent_index);
WEB_API void set_visual_viewport_transform(TransformData);
WEB_API bool is_compatible_with(AccumulatedVisualContextTree const&) const;
WEB_API void reuse_version_from(AccumulatedVisualContextTree const&);
AccumulatedVisualContextNode const& node_at(VisualContextIndex index) const { return m_nodes[index.value()]; }
ReadonlySpan<AccumulatedVisualContextNode> nodes() const { return m_nodes.span(); }

View file

@ -218,7 +218,10 @@ void ViewportPaintable::assign_scroll_frames()
void ViewportPaintable::assign_accumulated_visual_contexts()
{
m_visual_context_tree = build_accumulated_visual_context_tree(*this);
auto visual_context_tree = build_accumulated_visual_context_tree(*this);
if (m_visual_context_tree.has_value() && visual_context_tree.is_compatible_with(*m_visual_context_tree))
visual_context_tree.reuse_version_from(*m_visual_context_tree);
m_visual_context_tree = move(visual_context_tree);
m_visual_context_tree_needs_compositor_update = true;
}

View file

@ -1,5 +1,6 @@
set(TEST_SOURCES
TestCSSIDSpeed.cpp
TestAccumulatedVisualContext.cpp
TestContentBlocker.cpp
TestControlMessageQueue.cpp
TestCSSInheritedProperty.cpp
@ -32,6 +33,7 @@ target_link_libraries(TestContentBlocker PRIVATE LibURL)
target_link_libraries(TestControlMessageQueue PRIVATE LibSync)
target_link_libraries(TestFetchResponse PRIVATE LibGC LibHTTP LibJS)
target_link_libraries(TestFetchURL PRIVATE LibURL)
target_link_libraries(TestAccumulatedVisualContext PRIVATE LibGfx)
target_link_libraries(TestPage PRIVATE LibGC LibJS)
target_link_libraries(TestSecureContexts PRIVATE LibURL)
target_link_libraries(TestSessionHistoryEntry PRIVATE LibJS LibURL)

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2026-present, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibTest/TestCase.h>
#include <LibWeb/Painting/AccumulatedVisualContext.h>
using namespace Web::Painting;
static TransformData make_transform(float translation)
{
auto matrix = Gfx::FloatMatrix4x4::identity();
matrix[0, 3] = translation;
matrix[1, 3] = translation;
return { matrix, { translation, translation } };
}
TEST_CASE(compatible_trees_can_reuse_versions)
{
auto tree = AccumulatedVisualContextTree::create(make_transform(1));
tree.append(make_transform(2), VISUAL_VIEWPORT_NODE_INDEX);
auto updated_tree = AccumulatedVisualContextTree::create(make_transform(3));
updated_tree.append(make_transform(4), VISUAL_VIEWPORT_NODE_INDEX);
EXPECT_NE(tree.version(), updated_tree.version());
EXPECT(updated_tree.is_compatible_with(tree));
updated_tree.reuse_version_from(tree);
EXPECT_EQ(updated_tree.version(), tree.version());
}
TEST_CASE(compatibility_requires_same_shape)
{
auto tree = AccumulatedVisualContextTree::create();
tree.append(make_transform(1), VISUAL_VIEWPORT_NODE_INDEX);
auto shorter_tree = AccumulatedVisualContextTree::create();
EXPECT(!shorter_tree.is_compatible_with(tree));
auto different_type_tree = AccumulatedVisualContextTree::create();
different_type_tree.append(EffectsData {}, VISUAL_VIEWPORT_NODE_INDEX);
EXPECT(!different_type_tree.is_compatible_with(tree));
auto different_parent_tree = AccumulatedVisualContextTree::create();
auto parent = different_parent_tree.append(make_transform(1), VISUAL_VIEWPORT_NODE_INDEX);
different_parent_tree.append(make_transform(2), parent);
auto same_node_count_tree = AccumulatedVisualContextTree::create();
same_node_count_tree.append(make_transform(1), VISUAL_VIEWPORT_NODE_INDEX);
same_node_count_tree.append(make_transform(2), VISUAL_VIEWPORT_NODE_INDEX);
EXPECT(!different_parent_tree.is_compatible_with(same_node_count_tree));
}