mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 07:30:19 +00:00
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/
This commit is contained in:
parent
9c6c3fe0d8
commit
2764966ccc
Notes:
sideshowbarker
2024-07-17 04:01:41 +09:00
Author: https://github.com/kalenikaliaksandr Commit: https://github.com/SerenityOS/serenity/commit/2764966ccc Pull-request: https://github.com/SerenityOS/serenity/pull/23411
5 changed files with 95 additions and 77 deletions
|
@ -0,0 +1,6 @@
|
|||
1 2 <DIV id="aa" >
|
||||
<DIV id="a" >
|
||||
<DIV id="bb" >
|
||||
<DIV id="b" >
|
||||
<DIV id="d" >
|
||||
<DIV id="container" >
|
|
@ -0,0 +1,66 @@
|
|||
<script src="../include.js"></script>
|
||||
<style>
|
||||
#container {
|
||||
background-color: #2196f3;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.item {
|
||||
float: left;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: yellow;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#a {
|
||||
left: 100px;
|
||||
}
|
||||
|
||||
#b {
|
||||
z-index: 1;
|
||||
left: 500px;
|
||||
}
|
||||
|
||||
.nested-item {
|
||||
float: left;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background-color: yellowgreen;
|
||||
}
|
||||
|
||||
#c {
|
||||
border: 10px solid black;
|
||||
}
|
||||
|
||||
#d {
|
||||
border: 10px solid black;
|
||||
background-color: magenta;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
float: left;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: yellow;
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
||||
<div id="container">
|
||||
<div class="wrapper" id="c">
|
||||
<div class="item" id="a"><div id="aa" class="nested-item">1</div></div>
|
||||
</div>
|
||||
<div class="wrapper" id="d">
|
||||
<div class="item" id="b"><div id="bb" class="nested-item">2</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
test(() => {
|
||||
printElement(internals.hitTest(156, 54).node);
|
||||
printElement(internals.hitTest(200, 106).node);
|
||||
printElement(internals.hitTest(548, 65).node);
|
||||
printElement(internals.hitTest(576, 105).node);
|
||||
printElement(internals.hitTest(82, 78).node);
|
||||
printElement(internals.hitTest(403, 16).node);
|
||||
});
|
||||
</script>
|
|
@ -328,34 +328,6 @@ void StackingContext::paint(PaintContext& context) const
|
|||
context.recording_painter().restore();
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
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<typename Callback>
|
||||
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<TraversalDecision(HitTestResult)> 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<PaintableBox>(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<Layout::BlockContainer>(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<PaintableBox>(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()) {
|
||||
|
|
|
@ -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<StackingContext*> m_children;
|
||||
size_t m_index_in_tree_order { 0 };
|
||||
|
||||
Vector<Paintable const&> m_positioned_descendants_with_stack_level_0_and_stacking_contexts;
|
||||
Vector<Paintable const&> m_non_positioned_floating_descendants;
|
||||
|
||||
static void paint_child(PaintContext&, StackingContext const&);
|
||||
void paint_internal(PaintContext&) const;
|
||||
};
|
||||
|
|
|
@ -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&>(paintable).invalidate_stacking_context();
|
||||
if (!paintable.layout_node().establishes_stacking_context()) {
|
||||
auto* parent_context = const_cast<Paintable&>(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&>(paintable).enclosing_stacking_context();
|
||||
VERIFY(parent_context);
|
||||
const_cast<Paintable&>(paintable).set_stacking_context(make<Painting::StackingContext>(const_cast<Paintable&>(paintable), parent_context, index_in_tree_order++));
|
||||
return TraversalDecision::Continue;
|
||||
|
|
Loading…
Reference in a new issue