From 2764966ccc3068744a5fe17b0eaa0a935fc537e5 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Fri, 1 Mar 2024 11:54:44 +0100 Subject: [PATCH] LibWeb: Reduce paintable tree traversals during hit-testing By storing a list of positioned and floating descendants within the stacking context tree node, we can eliminate the need for costly paintable tree traversals during hit-testing. This optimization results in hit-testing being 2 to 2.5 times faster on https://ziglang.org/documentation/master/ --- .../positioned-z-index-0-and-floats.txt | 6 ++ .../positioned-z-index-0-and-floats.html | 66 ++++++++++++++ .../LibWeb/Painting/StackingContext.cpp | 86 +++---------------- .../LibWeb/Painting/StackingContext.h | 5 ++ .../LibWeb/Painting/ViewportPaintable.cpp | 9 +- 5 files changed, 95 insertions(+), 77 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/hit_testing/positioned-z-index-0-and-floats.txt create mode 100644 Tests/LibWeb/Text/input/hit_testing/positioned-z-index-0-and-floats.html diff --git a/Tests/LibWeb/Text/expected/hit_testing/positioned-z-index-0-and-floats.txt b/Tests/LibWeb/Text/expected/hit_testing/positioned-z-index-0-and-floats.txt new file mode 100644 index 0000000000..53e3974c19 --- /dev/null +++ b/Tests/LibWeb/Text/expected/hit_testing/positioned-z-index-0-and-floats.txt @@ -0,0 +1,6 @@ + 1 2
+
+
+
+
+
diff --git a/Tests/LibWeb/Text/input/hit_testing/positioned-z-index-0-and-floats.html b/Tests/LibWeb/Text/input/hit_testing/positioned-z-index-0-and-floats.html new file mode 100644 index 0000000000..baaae3c755 --- /dev/null +++ b/Tests/LibWeb/Text/input/hit_testing/positioned-z-index-0-and-floats.html @@ -0,0 +1,66 @@ + + +
+
+
1
+
+
+
2
+
+
+ diff --git a/Userland/Libraries/LibWeb/Painting/StackingContext.cpp b/Userland/Libraries/LibWeb/Painting/StackingContext.cpp index 4a63d1a3c3..8152049016 100644 --- a/Userland/Libraries/LibWeb/Painting/StackingContext.cpp +++ b/Userland/Libraries/LibWeb/Painting/StackingContext.cpp @@ -328,34 +328,6 @@ void StackingContext::paint(PaintContext& context) const context.recording_painter().restore(); } -template -static TraversalDecision for_each_in_inclusive_subtree_within_same_stacking_context_in_reverse(Paintable const& paintable, Callback callback) -{ - if (paintable.stacking_context()) { - // Note: Include the stacking context (so we can hit test it), but don't recurse into it. - if (auto decision = callback(paintable); decision != TraversalDecision::Continue) - return decision; - return TraversalDecision::SkipChildrenAndContinue; - } - for (auto* child = paintable.last_child(); child; child = child->previous_sibling()) { - if (for_each_in_inclusive_subtree_within_same_stacking_context_in_reverse(*child, callback) == TraversalDecision::Break) - return TraversalDecision::Break; - } - if (auto decision = callback(paintable); decision != TraversalDecision::Continue) - return decision; - return TraversalDecision::Continue; -} - -template -static TraversalDecision for_each_in_subtree_within_same_stacking_context_in_reverse(Paintable const& paintable, Callback callback) -{ - for (auto* child = paintable.last_child(); child; child = child->previous_sibling()) { - if (for_each_in_inclusive_subtree_within_same_stacking_context_in_reverse(*child, callback) == TraversalDecision::Break) - return TraversalDecision::Break; - } - return TraversalDecision::Continue; -} - TraversalDecision StackingContext::hit_test(CSSPixelPoint position, HitTestType type, Function const& callback) const { if (!paintable().is_visible()) @@ -389,39 +361,16 @@ TraversalDecision StackingContext::hit_test(CSSPixelPoint position, HitTestType return TraversalDecision::Break; } - bool should_exit = false; - // 6. the child stacking contexts with stack level 0 and the positioned descendants with stack level 0. - for_each_in_subtree_within_same_stacking_context_in_reverse(paintable(), [&](Paintable const& paintable) { - VERIFY(!should_exit); - if (!paintable.is_paintable_box()) - return TraversalDecision::Continue; - - auto const& paintable_box = verify_cast(paintable); - - auto const& z_index = paintable_box.computed_values().z_index(); - auto positioned_element_without_stacking_context = paintable_box.is_positioned() && !paintable_box.stacking_context(); - if (z_index.value_or(0) == 0 && (positioned_element_without_stacking_context || paintable_box.layout_node().is_grid_item())) { - if (paintable_box.hit_test(transformed_position, type, callback) == TraversalDecision::Break) { - should_exit = true; + for (auto const& paintable : m_positioned_descendants_with_stack_level_0_and_stacking_contexts.in_reverse()) { + if (paintable.stacking_context()) { + if (paintable.stacking_context()->hit_test(transformed_position, type, callback) == TraversalDecision::Break) + return TraversalDecision::Break; + } else { + if (paintable.hit_test(transformed_position, type, callback) == TraversalDecision::Break) return TraversalDecision::Break; - } } - - if (paintable_box.stacking_context()) { - if (z_index.value_or(0) == 0) { - if (paintable_box.stacking_context()->hit_test(transformed_position, type, callback) == TraversalDecision::Break) { - should_exit = true; - return TraversalDecision::Break; - } - } - } - - return TraversalDecision::Continue; - }); - - if (should_exit) - return TraversalDecision::Break; + } // 5. the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks. if (paintable().layout_node().children_are_inline() && is(paintable().layout_node())) { @@ -434,23 +383,10 @@ TraversalDecision StackingContext::hit_test(CSSPixelPoint position, HitTestType } // 4. the non-positioned floats. - for_each_in_subtree_within_same_stacking_context_in_reverse(paintable(), [&](Paintable const& paintable) { - VERIFY(!should_exit); - if (!paintable.is_paintable_box()) - return TraversalDecision::Continue; - - auto const& paintable_box = verify_cast(paintable); - if (paintable_box.is_floating()) { - if (paintable_box.hit_test(transformed_position, type, callback) == TraversalDecision::Break) { - should_exit = true; - return TraversalDecision::Break; - } - } - return TraversalDecision::Continue; - }); - - if (should_exit) - return TraversalDecision::Break; + for (auto const& paintable : m_non_positioned_floating_descendants.in_reverse()) { + if (paintable.hit_test(transformed_position, type, callback) == TraversalDecision::Break) + return TraversalDecision::Break; + } // 3. the in-flow, non-inline-level, non-positioned descendants. if (!paintable().layout_node().children_are_inline()) { diff --git a/Userland/Libraries/LibWeb/Painting/StackingContext.h b/Userland/Libraries/LibWeb/Painting/StackingContext.h index 0289573b7d..79bdaa4b4e 100644 --- a/Userland/Libraries/LibWeb/Painting/StackingContext.h +++ b/Userland/Libraries/LibWeb/Painting/StackingContext.h @@ -14,6 +14,8 @@ namespace Web::Painting { class StackingContext { + friend class ViewportPaintable; + public: StackingContext(Paintable&, StackingContext* parent, size_t index_in_tree_order); @@ -50,6 +52,9 @@ private: Vector m_children; size_t m_index_in_tree_order { 0 }; + Vector m_positioned_descendants_with_stack_level_0_and_stacking_contexts; + Vector m_non_positioned_floating_descendants; + static void paint_child(PaintContext&, StackingContext const&); void paint_internal(PaintContext&) const; }; diff --git a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp index 7f92c60376..d99a74de09 100644 --- a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp @@ -38,11 +38,16 @@ void ViewportPaintable::build_stacking_context_tree() size_t index_in_tree_order = 1; for_each_in_subtree([&](Paintable const& paintable) { const_cast(paintable).invalidate_stacking_context(); - if (!paintable.layout_node().establishes_stacking_context()) { + auto* parent_context = const_cast(paintable).enclosing_stacking_context(); + auto establishes_stacking_context = paintable.layout_node().establishes_stacking_context(); + if ((paintable.is_positioned() || establishes_stacking_context) && paintable.computed_values().z_index().value_or(0) == 0) + parent_context->m_positioned_descendants_with_stack_level_0_and_stacking_contexts.append(paintable); + if (!paintable.is_positioned() && paintable.is_floating()) + parent_context->m_non_positioned_floating_descendants.append(paintable); + if (!establishes_stacking_context) { VERIFY(!paintable.stacking_context()); return TraversalDecision::Continue; } - auto* parent_context = const_cast(paintable).enclosing_stacking_context(); VERIFY(parent_context); const_cast(paintable).set_stacking_context(make(const_cast(paintable), parent_context, index_in_tree_order++)); return TraversalDecision::Continue;