mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-06-09 09:34:57 +09:00
LibWeb: Avoid O(n^2) traversal in play-or-cancel-animations logic
The play_or_cancel_animations_after_display_property_change() helper was being called by Node::inserted() and Node::removed_from() and then recursing into the shadow-including subtree. This had quadratic complexity since inserted() and removed_from() are themselves already invoked recursively for everything in the shadow-including subtree. Only one caller of this API actually needed the recursive behavior, so this patch moves that responsibility to the caller and puts the logic in style recomputation instead. 1.02x speedup on Speedometer's TodoMVC-jQuery.
This commit is contained in:
parent
1a9e78a774
commit
096eed35cc
Notes:
github-actions[bot]
2025-05-11 18:23:14 +00:00
Author: https://github.com/awesomekling
Commit: 096eed35cc
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/4693
4 changed files with 44 additions and 44 deletions
|
@ -651,7 +651,13 @@ CSS::RequiredInvalidationAfterStyleChange Element::recompute_style()
|
|||
set_computed_properties(move(new_computed_properties));
|
||||
|
||||
if (old_display_is_none != new_display_is_none) {
|
||||
play_or_cancel_animations_after_display_property_change();
|
||||
for_each_shadow_including_inclusive_descendant([&](auto& node) {
|
||||
if (!node.is_element())
|
||||
return TraversalDecision::Continue;
|
||||
auto& element = static_cast<Element&>(node);
|
||||
element.play_or_cancel_animations_after_display_property_change();
|
||||
return TraversalDecision::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
// Any document change that can cause this element's style to change, could also affect its pseudo-elements.
|
||||
|
@ -1275,6 +1281,8 @@ void Element::inserted()
|
|||
if (m_name.has_value())
|
||||
document().element_with_name_was_added({}, *this);
|
||||
}
|
||||
|
||||
play_or_cancel_animations_after_display_property_change();
|
||||
}
|
||||
|
||||
void Element::removed_from(Node* old_parent, Node& old_root)
|
||||
|
@ -1287,6 +1295,8 @@ void Element::removed_from(Node* old_parent, Node& old_root)
|
|||
if (m_name.has_value())
|
||||
document().element_with_name_was_removed({}, *this);
|
||||
}
|
||||
|
||||
play_or_cancel_animations_after_display_property_change();
|
||||
}
|
||||
|
||||
void Element::moved_from(GC::Ptr<Node> old_parent)
|
||||
|
@ -4014,4 +4024,35 @@ FlyString const& Element::html_uppercased_qualified_name() const
|
|||
return m_html_uppercased_qualified_name.value();
|
||||
}
|
||||
|
||||
void Element::play_or_cancel_animations_after_display_property_change()
|
||||
{
|
||||
// OPTIMIZATION: We don't care about animations in disconnected subtrees.
|
||||
if (!is_connected())
|
||||
return;
|
||||
|
||||
// https://www.w3.org/TR/css-animations-1/#animations
|
||||
// Setting the display property to none will terminate any running animation applied to the element and its descendants.
|
||||
// If an element has a display of none, updating display to a value other than none will start all animations applied to
|
||||
// the element by the animation-name property, as well as all animations applied to descendants with display other than none.
|
||||
|
||||
auto has_display_none_inclusive_ancestor = this->has_inclusive_ancestor_with_display_none();
|
||||
|
||||
auto play_or_cancel_depending_on_display = [&](Animations::Animation& animation) {
|
||||
if (has_display_none_inclusive_ancestor) {
|
||||
animation.cancel();
|
||||
} else {
|
||||
HTML::TemporaryExecutionContext context(realm());
|
||||
animation.play().release_value_but_fixme_should_propagate_errors();
|
||||
}
|
||||
};
|
||||
|
||||
if (auto animation = cached_animation_name_animation({}))
|
||||
play_or_cancel_depending_on_display(*animation);
|
||||
for (auto i = 0; i < to_underlying(CSS::PseudoElement::KnownPseudoElementCount); i++) {
|
||||
auto pseudo_element = static_cast<CSS::PseudoElement>(i);
|
||||
if (auto animation = cached_animation_name_animation(pseudo_element))
|
||||
play_or_cancel_depending_on_display(*animation);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -499,6 +499,8 @@ protected:
|
|||
|
||||
CustomElementState custom_element_state() const { return m_custom_element_state; }
|
||||
|
||||
void play_or_cancel_animations_after_display_property_change();
|
||||
|
||||
private:
|
||||
FlyString make_html_uppercased_qualified_name() const;
|
||||
|
||||
|
|
|
@ -1645,16 +1645,12 @@ void Node::post_connection()
|
|||
void Node::inserted()
|
||||
{
|
||||
set_needs_style_update(true);
|
||||
|
||||
play_or_cancel_animations_after_display_property_change();
|
||||
}
|
||||
|
||||
void Node::removed_from(Node*, Node&)
|
||||
{
|
||||
m_layout_node = nullptr;
|
||||
m_paintable = nullptr;
|
||||
|
||||
play_or_cancel_animations_after_display_property_change();
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#concept-node-move-ext
|
||||
|
@ -3119,44 +3115,6 @@ bool Node::has_inclusive_ancestor_with_display_none()
|
|||
return false;
|
||||
}
|
||||
|
||||
void Node::play_or_cancel_animations_after_display_property_change()
|
||||
{
|
||||
// OPTIMIZATION: We don't care about animations in disconnected subtrees.
|
||||
if (!is_connected())
|
||||
return;
|
||||
|
||||
// https://www.w3.org/TR/css-animations-1/#animations
|
||||
// Setting the display property to none will terminate any running animation applied to the element and its descendants.
|
||||
// If an element has a display of none, updating display to a value other than none will start all animations applied to
|
||||
// the element by the animation-name property, as well as all animations applied to descendants with display other than none.
|
||||
|
||||
auto has_display_none_inclusive_ancestor = this->has_inclusive_ancestor_with_display_none();
|
||||
|
||||
auto play_or_cancel_depending_on_display = [&](Animations::Animation& animation) {
|
||||
if (has_display_none_inclusive_ancestor) {
|
||||
animation.cancel();
|
||||
} else {
|
||||
HTML::TemporaryExecutionContext context(realm());
|
||||
animation.play().release_value_but_fixme_should_propagate_errors();
|
||||
}
|
||||
};
|
||||
|
||||
for_each_shadow_including_inclusive_descendant([&](auto& node) {
|
||||
if (!node.is_element())
|
||||
return TraversalDecision::Continue;
|
||||
|
||||
auto const& element = static_cast<Element const&>(node);
|
||||
if (auto animation = element.cached_animation_name_animation({}))
|
||||
play_or_cancel_depending_on_display(*animation);
|
||||
for (auto i = 0; i < to_underlying(CSS::PseudoElement::KnownPseudoElementCount); i++) {
|
||||
auto pseudo_element = static_cast<CSS::PseudoElement>(i);
|
||||
if (auto animation = element.cached_animation_name_animation(pseudo_element))
|
||||
play_or_cancel_depending_on_display(*animation);
|
||||
}
|
||||
return TraversalDecision::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace IPC {
|
||||
|
|
|
@ -562,7 +562,6 @@ public:
|
|||
bool is_inert() const;
|
||||
|
||||
bool has_inclusive_ancestor_with_display_none();
|
||||
void play_or_cancel_animations_after_display_property_change();
|
||||
|
||||
protected:
|
||||
Node(JS::Realm&, Document&, NodeType);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue