diff --git a/Libraries/LibWeb/Painting/AccumulatedVisualContext.cpp b/Libraries/LibWeb/Painting/AccumulatedVisualContext.cpp index 29a2058dbd..790b6b4945 100644 --- a/Libraries/LibWeb/Painting/AccumulatedVisualContext.cpp +++ b/Libraries/LibWeb/Painting/AccumulatedVisualContext.cpp @@ -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; + return other_node.data.has(); + })) + 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()); diff --git a/Libraries/LibWeb/Painting/AccumulatedVisualContext.h b/Libraries/LibWeb/Painting/AccumulatedVisualContext.h index 9ef4930c6f..1e7564c525 100644 --- a/Libraries/LibWeb/Painting/AccumulatedVisualContext.h +++ b/Libraries/LibWeb/Painting/AccumulatedVisualContext.h @@ -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 nodes() const { return m_nodes.span(); } diff --git a/Libraries/LibWeb/Painting/ViewportPaintable.cpp b/Libraries/LibWeb/Painting/ViewportPaintable.cpp index 7e0f9e5376..a049d32d15 100644 --- a/Libraries/LibWeb/Painting/ViewportPaintable.cpp +++ b/Libraries/LibWeb/Painting/ViewportPaintable.cpp @@ -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; } diff --git a/Tests/LibWeb/CMakeLists.txt b/Tests/LibWeb/CMakeLists.txt index d906bbabc5..bd965d44fd 100644 --- a/Tests/LibWeb/CMakeLists.txt +++ b/Tests/LibWeb/CMakeLists.txt @@ -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) diff --git a/Tests/LibWeb/TestAccumulatedVisualContext.cpp b/Tests/LibWeb/TestAccumulatedVisualContext.cpp new file mode 100644 index 0000000000..5241447df7 --- /dev/null +++ b/Tests/LibWeb/TestAccumulatedVisualContext.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2026-present, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +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)); +}