mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-06-10 18:10:56 +09:00
LibWeb: Add fast path to calculate invalidations for animated css props
- Compare only the animated properties - Clone only the hash map containing animated properties, instead of the entire StyleProperties. Reduces `KeyframeEffect::update_style_properties()` from 10% to 3% in GitHub profiles.
This commit is contained in:
parent
ce2bfb4a12
commit
cf7c933312
Notes:
sideshowbarker
2024-07-16 22:16:50 +09:00
Author: https://github.com/kalenikaliaksandr
Commit: cf7c933312
Pull-request: https://github.com/SerenityOS/serenity/pull/23635
Reviewed-by: https://github.com/awesomekling
9 changed files with 132 additions and 84 deletions
|
@ -871,6 +871,27 @@ void KeyframeEffect::visit_edges(Cell::Visitor& visitor)
|
|||
visitor.visit(keyframe);
|
||||
}
|
||||
|
||||
static CSS::RequiredInvalidationAfterStyleChange compute_required_invalidation(HashMap<CSS::PropertyID, NonnullRefPtr<CSS::StyleValue const>> const& old_properties, HashMap<CSS::PropertyID, NonnullRefPtr<CSS::StyleValue const>> const& new_properties)
|
||||
{
|
||||
CSS::RequiredInvalidationAfterStyleChange invalidation;
|
||||
auto old_and_new_properties = MUST(Bitmap::create(to_underlying(CSS::last_property_id) + 1, 0));
|
||||
for (auto const& [property_id, _] : old_properties)
|
||||
old_and_new_properties.set(to_underlying(property_id), 1);
|
||||
for (auto const& [property_id, _] : new_properties)
|
||||
old_and_new_properties.set(to_underlying(property_id), 1);
|
||||
for (auto i = to_underlying(CSS::first_property_id); i <= to_underlying(CSS::last_property_id); ++i) {
|
||||
if (!old_and_new_properties.get(i))
|
||||
continue;
|
||||
auto property_id = static_cast<CSS::PropertyID>(i);
|
||||
auto old_value = old_properties.get(property_id).value_or({});
|
||||
auto new_value = new_properties.get(property_id).value_or({});
|
||||
if (!old_value && !new_value)
|
||||
continue;
|
||||
invalidation |= compute_property_invalidation(property_id, old_value, new_value);
|
||||
}
|
||||
return invalidation;
|
||||
}
|
||||
|
||||
void KeyframeEffect::update_style_properties()
|
||||
{
|
||||
if (!target())
|
||||
|
@ -886,7 +907,7 @@ void KeyframeEffect::update_style_properties()
|
|||
if (!style)
|
||||
return;
|
||||
|
||||
auto style_before_animation_update = style->clone();
|
||||
auto animated_properties_before_update = style->animated_property_values();
|
||||
|
||||
auto& document = target()->document();
|
||||
document.style_computer().collect_animation_into(*this, *style, CSS::StyleComputer::AnimationRefresh::Yes);
|
||||
|
@ -908,7 +929,7 @@ void KeyframeEffect::update_style_properties()
|
|||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
auto invalidation = DOM::Element::compute_required_invalidation(style_before_animation_update, *style);
|
||||
auto invalidation = compute_required_invalidation(animated_properties_before_update, style->animated_property_values());
|
||||
|
||||
if (target()->layout_node())
|
||||
target()->layout_node()->apply_style(*style);
|
||||
|
|
|
@ -89,6 +89,7 @@ set(SOURCES
|
|||
CSS/Serialize.cpp
|
||||
CSS/Size.cpp
|
||||
CSS/StyleComputer.cpp
|
||||
CSS/StyleInvalidation.cpp
|
||||
CSS/StyleProperties.cpp
|
||||
CSS/StyleProperty.cpp
|
||||
CSS/StyleSheet.cpp
|
||||
|
|
61
Userland/Libraries/LibWeb/CSS/StyleInvalidation.cpp
Normal file
61
Userland/Libraries/LibWeb/CSS/StyleInvalidation.cpp
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/CSS/StyleInvalidation.h>
|
||||
#include <LibWeb/CSS/StyleProperties.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
RequiredInvalidationAfterStyleChange compute_property_invalidation(CSS::PropertyID property_id, RefPtr<CSS::StyleValue const> const& old_value, RefPtr<CSS::StyleValue const> const& new_value)
|
||||
{
|
||||
RequiredInvalidationAfterStyleChange invalidation;
|
||||
|
||||
bool const property_value_changed = (!old_value || !new_value) || *old_value != *new_value;
|
||||
if (!property_value_changed)
|
||||
return invalidation;
|
||||
|
||||
// NOTE: If the computed CSS display property changes, we have to rebuild the entire layout tree.
|
||||
// In the future, we should figure out ways to rebuild a smaller part of the tree.
|
||||
if (property_id == CSS::PropertyID::Display) {
|
||||
return RequiredInvalidationAfterStyleChange::full();
|
||||
}
|
||||
|
||||
// NOTE: If one of the overflow properties change, we rebuild the entire layout tree.
|
||||
// This ensures that overflow propagation from root/body to viewport happens correctly.
|
||||
// In the future, we can make this invalidation narrower.
|
||||
if (property_id == CSS::PropertyID::OverflowX || property_id == CSS::PropertyID::OverflowY) {
|
||||
return RequiredInvalidationAfterStyleChange::full();
|
||||
}
|
||||
|
||||
// OPTIMIZATION: Special handling for CSS `visibility`:
|
||||
if (property_id == CSS::PropertyID::Visibility) {
|
||||
// We don't need to relayout if the visibility changes from visible to hidden or vice versa. Only collapse requires relayout.
|
||||
if ((old_value && old_value->to_identifier() == CSS::ValueID::Collapse) != (new_value && new_value->to_identifier() == CSS::ValueID::Collapse))
|
||||
invalidation.relayout = true;
|
||||
// Of course, we still have to repaint on any visibility change.
|
||||
invalidation.repaint = true;
|
||||
} else if (CSS::property_affects_layout(property_id)) {
|
||||
invalidation.relayout = true;
|
||||
}
|
||||
|
||||
if (property_id == CSS::PropertyID::Opacity && old_value && new_value) {
|
||||
// OPTIMIZATION: An element creates a stacking context when its opacity changes from 1 to less than 1
|
||||
// and stops to create one when opacity returns to 1. So stacking context tree rebuild is
|
||||
// not required for opacity changes within the range below 1.
|
||||
auto old_value_opacity = CSS::StyleProperties::resolve_opacity_value(*old_value);
|
||||
auto new_value_opacity = CSS::StyleProperties::resolve_opacity_value(*new_value);
|
||||
if (old_value_opacity != new_value_opacity && (old_value_opacity == 1 || new_value_opacity == 1)) {
|
||||
invalidation.rebuild_stacking_context_tree = true;
|
||||
}
|
||||
} else if (CSS::property_affects_stacking_context(property_id)) {
|
||||
invalidation.rebuild_stacking_context_tree = true;
|
||||
}
|
||||
invalidation.repaint = true;
|
||||
|
||||
return invalidation;
|
||||
}
|
||||
|
||||
}
|
33
Userland/Libraries/LibWeb/CSS/StyleInvalidation.h
Normal file
33
Userland/Libraries/LibWeb/CSS/StyleInvalidation.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/CSS/PropertyID.h>
|
||||
|
||||
namespace Web::CSS {
|
||||
|
||||
struct RequiredInvalidationAfterStyleChange {
|
||||
bool repaint : 1 { false };
|
||||
bool rebuild_stacking_context_tree : 1 { false };
|
||||
bool relayout : 1 { false };
|
||||
bool rebuild_layout_tree : 1 { false };
|
||||
|
||||
void operator|=(RequiredInvalidationAfterStyleChange const& other)
|
||||
{
|
||||
repaint |= other.repaint;
|
||||
rebuild_stacking_context_tree |= other.rebuild_stacking_context_tree;
|
||||
relayout |= other.relayout;
|
||||
rebuild_layout_tree |= other.rebuild_layout_tree;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool is_none() const { return !repaint && !rebuild_stacking_context_tree && !relayout && !rebuild_layout_tree; }
|
||||
static RequiredInvalidationAfterStyleChange full() { return { true, true, true, true }; }
|
||||
};
|
||||
|
||||
RequiredInvalidationAfterStyleChange compute_property_invalidation(CSS::PropertyID property_id, RefPtr<CSS::StyleValue const> const& old_value, RefPtr<CSS::StyleValue const> const& new_value);
|
||||
|
||||
}
|
|
@ -34,17 +34,6 @@
|
|||
|
||||
namespace Web::CSS {
|
||||
|
||||
NonnullRefPtr<StyleProperties> StyleProperties::clone() const
|
||||
{
|
||||
auto clone = adopt_ref(*new StyleProperties);
|
||||
clone->m_property_values = m_property_values;
|
||||
clone->m_animated_property_values = m_animated_property_values;
|
||||
clone->m_font_list = m_font_list;
|
||||
clone->m_line_height = m_line_height;
|
||||
clone->m_math_depth = m_math_depth;
|
||||
return clone;
|
||||
}
|
||||
|
||||
bool StyleProperties::is_property_important(CSS::PropertyID property_id) const
|
||||
{
|
||||
return m_property_values[to_underlying(property_id)].style && m_property_values[to_underlying(property_id)].important == Important::Yes;
|
||||
|
@ -289,7 +278,7 @@ Optional<int> StyleProperties::z_index() const
|
|||
return {};
|
||||
}
|
||||
|
||||
static float resolve_opacity_value(CSS::StyleValue const& value)
|
||||
float StyleProperties::resolve_opacity_value(CSS::StyleValue const& value)
|
||||
{
|
||||
float unclamped_opacity = 1.0f;
|
||||
|
||||
|
|
|
@ -23,8 +23,6 @@ public:
|
|||
|
||||
static NonnullRefPtr<StyleProperties> create() { return adopt_ref(*new StyleProperties); }
|
||||
|
||||
NonnullRefPtr<StyleProperties> clone() const;
|
||||
|
||||
template<typename Callback>
|
||||
inline void for_each_property(Callback callback) const
|
||||
{
|
||||
|
@ -55,6 +53,7 @@ public:
|
|||
auto& properties() { return m_property_values; }
|
||||
auto const& properties() const { return m_property_values; }
|
||||
|
||||
HashMap<CSS::PropertyID, NonnullRefPtr<StyleValue const>> const& animated_property_values() const { return m_animated_property_values; }
|
||||
void reset_animated_properties();
|
||||
|
||||
bool is_property_important(CSS::PropertyID property_id) const;
|
||||
|
@ -182,6 +181,8 @@ public:
|
|||
|
||||
static NonnullRefPtr<Gfx::Font const> font_fallback(bool monospace, bool bold);
|
||||
|
||||
static float resolve_opacity_value(CSS::StyleValue const& value);
|
||||
|
||||
private:
|
||||
friend class StyleComputer;
|
||||
|
||||
|
|
|
@ -1116,10 +1116,10 @@ void Document::update_layout()
|
|||
m_layout_update_timer->stop();
|
||||
}
|
||||
|
||||
[[nodiscard]] static Element::RequiredInvalidationAfterStyleChange update_style_recursively(Node& node)
|
||||
[[nodiscard]] static CSS::RequiredInvalidationAfterStyleChange update_style_recursively(Node& node)
|
||||
{
|
||||
bool const needs_full_style_update = node.document().needs_full_style_update();
|
||||
Element::RequiredInvalidationAfterStyleChange invalidation;
|
||||
CSS::RequiredInvalidationAfterStyleChange invalidation;
|
||||
|
||||
// NOTE: If the current node has `display:none`, we can disregard all invalidation
|
||||
// caused by its children, as they will not be rendered anyway.
|
||||
|
|
|
@ -492,9 +492,9 @@ void Element::attribute_changed(FlyString const& name, Optional<String> const& v
|
|||
}
|
||||
}
|
||||
|
||||
Element::RequiredInvalidationAfterStyleChange Element::compute_required_invalidation(CSS::StyleProperties const& old_style, CSS::StyleProperties const& new_style)
|
||||
static CSS::RequiredInvalidationAfterStyleChange compute_required_invalidation(CSS::StyleProperties const& old_style, CSS::StyleProperties const& new_style)
|
||||
{
|
||||
Element::RequiredInvalidationAfterStyleChange invalidation;
|
||||
CSS::RequiredInvalidationAfterStyleChange invalidation;
|
||||
|
||||
if (!old_style.computed_font_list().equals(new_style.computed_font_list()))
|
||||
invalidation.relayout = true;
|
||||
|
@ -506,51 +506,12 @@ Element::RequiredInvalidationAfterStyleChange Element::compute_required_invalida
|
|||
if (!old_value && !new_value)
|
||||
continue;
|
||||
|
||||
bool const property_value_changed = (!old_value || !new_value) || *old_value != *new_value;
|
||||
if (!property_value_changed)
|
||||
continue;
|
||||
|
||||
// NOTE: If the computed CSS display property changes, we have to rebuild the entire layout tree.
|
||||
// In the future, we should figure out ways to rebuild a smaller part of the tree.
|
||||
if (property_id == CSS::PropertyID::Display) {
|
||||
return Element::RequiredInvalidationAfterStyleChange::full();
|
||||
}
|
||||
|
||||
// NOTE: If one of the overflow properties change, we rebuild the entire layout tree.
|
||||
// This ensures that overflow propagation from root/body to viewport happens correctly.
|
||||
// In the future, we can make this invalidation narrower.
|
||||
if (property_id == CSS::PropertyID::OverflowX || property_id == CSS::PropertyID::OverflowY) {
|
||||
return Element::RequiredInvalidationAfterStyleChange::full();
|
||||
}
|
||||
|
||||
// OPTIMIZATION: Special handling for CSS `visibility`:
|
||||
if (property_id == CSS::PropertyID::Visibility) {
|
||||
// We don't need to relayout if the visibility changes from visible to hidden or vice versa. Only collapse requires relayout.
|
||||
if ((old_value && old_value->to_identifier() == CSS::ValueID::Collapse) != (new_value && new_value->to_identifier() == CSS::ValueID::Collapse))
|
||||
invalidation.relayout = true;
|
||||
// Of course, we still have to repaint on any visibility change.
|
||||
invalidation.repaint = true;
|
||||
} else if (CSS::property_affects_layout(property_id)) {
|
||||
invalidation.relayout = true;
|
||||
}
|
||||
if (property_id == CSS::PropertyID::Opacity) {
|
||||
// OPTIMIZATION: An element creates a stacking context when its opacity changes from 1 to less than 1
|
||||
// and stops to create one when opacity returns to 1. So stacking context tree rebuild is
|
||||
// not required for opacity changes within the range below 1.
|
||||
auto old_value_opacity = old_style.opacity();
|
||||
auto new_value_opacity = new_style.opacity();
|
||||
if (old_value_opacity != new_value_opacity && (old_value_opacity == 1 || new_value_opacity == 1)) {
|
||||
invalidation.rebuild_stacking_context_tree = true;
|
||||
}
|
||||
} else if (CSS::property_affects_stacking_context(property_id)) {
|
||||
invalidation.rebuild_stacking_context_tree = true;
|
||||
}
|
||||
invalidation.repaint = true;
|
||||
invalidation |= CSS::compute_property_invalidation(property_id, old_value, new_value);
|
||||
}
|
||||
return invalidation;
|
||||
}
|
||||
|
||||
Element::RequiredInvalidationAfterStyleChange Element::recompute_style()
|
||||
CSS::RequiredInvalidationAfterStyleChange Element::recompute_style()
|
||||
{
|
||||
set_needs_style_update(false);
|
||||
VERIFY(parent());
|
||||
|
@ -565,11 +526,11 @@ Element::RequiredInvalidationAfterStyleChange Element::recompute_style()
|
|||
new_computed_css_values->set_property(CSS::PropertyID::TextAlign, CSS::IdentifierStyleValue::create(CSS::ValueID::Start));
|
||||
}
|
||||
|
||||
RequiredInvalidationAfterStyleChange invalidation;
|
||||
CSS::RequiredInvalidationAfterStyleChange invalidation;
|
||||
if (m_computed_css_values)
|
||||
invalidation = compute_required_invalidation(*m_computed_css_values, *new_computed_css_values);
|
||||
else
|
||||
invalidation = RequiredInvalidationAfterStyleChange::full();
|
||||
invalidation = CSS::RequiredInvalidationAfterStyleChange::full();
|
||||
|
||||
if (invalidation.is_none())
|
||||
return invalidation;
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/Bindings/ShadowRootPrototype.h>
|
||||
#include <LibWeb/CSS/Selector.h>
|
||||
#include <LibWeb/CSS/StyleInvalidation.h>
|
||||
#include <LibWeb/CSS/StyleProperty.h>
|
||||
#include <LibWeb/DOM/ChildNode.h>
|
||||
#include <LibWeb/DOM/NonDocumentTypeChildNode.h>
|
||||
|
@ -148,27 +149,7 @@ public:
|
|||
void run_attribute_change_steps(FlyString const& local_name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_);
|
||||
virtual void attribute_changed(FlyString const& name, Optional<String> const& value);
|
||||
|
||||
struct [[nodiscard]] RequiredInvalidationAfterStyleChange {
|
||||
bool repaint { false };
|
||||
bool rebuild_stacking_context_tree { false };
|
||||
bool relayout { false };
|
||||
bool rebuild_layout_tree { false };
|
||||
|
||||
void operator|=(RequiredInvalidationAfterStyleChange const& other)
|
||||
{
|
||||
repaint |= other.repaint;
|
||||
rebuild_stacking_context_tree |= other.rebuild_stacking_context_tree;
|
||||
relayout |= other.relayout;
|
||||
rebuild_layout_tree |= other.rebuild_layout_tree;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool is_none() const { return !repaint && !rebuild_stacking_context_tree && !relayout && !rebuild_layout_tree; }
|
||||
static RequiredInvalidationAfterStyleChange full() { return { true, true, true, true }; }
|
||||
};
|
||||
|
||||
static Element::RequiredInvalidationAfterStyleChange compute_required_invalidation(CSS::StyleProperties const& old_style, CSS::StyleProperties const& new_style);
|
||||
|
||||
RequiredInvalidationAfterStyleChange recompute_style();
|
||||
CSS::RequiredInvalidationAfterStyleChange recompute_style();
|
||||
|
||||
Optional<CSS::Selector::PseudoElement::Type> use_pseudo_element() const { return m_use_pseudo_element; }
|
||||
void set_use_pseudo_element(Optional<CSS::Selector::PseudoElement::Type> use_pseudo_element) { m_use_pseudo_element = move(use_pseudo_element); }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue