Browse Source

LibWeb: Add fast path for animated style properties update

Patch up existing style properties instead of using the regular style
invalidation path, which requires rule matching for each element in the
invalidated subtree.

- !important properties: this change introduces a flag used to skip the
  update of animated properties overridden by !important.
- inherited animated properties: for now, these are invalidated by
  traversing animated element's subtree to propagate the update.
- StyleProperties has a separate array for animated properties that
  allows the removal animated properties after animation has ended,
  without requiring full style invalidation.
Aliaksandr Kalenik 1 year ago
parent
commit
a9b8840a82

+ 3 - 2
Userland/Libraries/LibWeb/Animations/Animation.cpp

@@ -15,6 +15,7 @@
 #include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
 #include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
 #include <LibWeb/HTML/Window.h>
 #include <LibWeb/HTML/Window.h>
 #include <LibWeb/HighResolutionTime/Performance.h>
 #include <LibWeb/HighResolutionTime/Performance.h>
+#include <LibWeb/Painting/Paintable.h>
 #include <LibWeb/WebIDL/ExceptionOr.h>
 #include <LibWeb/WebIDL/ExceptionOr.h>
 #include <LibWeb/WebIDL/Promise.h>
 #include <LibWeb/WebIDL/Promise.h>
 
 
@@ -1315,8 +1316,8 @@ JS::NonnullGCPtr<WebIDL::Promise> Animation::current_finished_promise() const
 void Animation::invalidate_effect()
 void Animation::invalidate_effect()
 {
 {
     if (m_effect) {
     if (m_effect) {
-        if (auto target = m_effect->target())
-            target->invalidate_style();
+        if (auto target = m_effect->target(); target && target->paintable())
+            target->paintable()->set_needs_display();
     }
     }
 }
 }
 
 

+ 2 - 0
Userland/Libraries/LibWeb/Animations/AnimationEffect.h

@@ -146,6 +146,8 @@ public:
     virtual DOM::Element* target() const { return {}; }
     virtual DOM::Element* target() const { return {}; }
     virtual bool is_keyframe_effect() const { return false; }
     virtual bool is_keyframe_effect() const { return false; }
 
 
+    virtual void update_style_properties() = 0;
+
 protected:
 protected:
     AnimationEffect(JS::Realm&);
     AnimationEffect(JS::Realm&);
     virtual ~AnimationEffect() = default;
     virtual ~AnimationEffect() = default;

+ 54 - 0
Userland/Libraries/LibWeb/Animations/KeyframeEffect.cpp

@@ -10,6 +10,8 @@
 #include <LibWeb/Animations/KeyframeEffect.h>
 #include <LibWeb/Animations/KeyframeEffect.h>
 #include <LibWeb/CSS/Parser/Parser.h>
 #include <LibWeb/CSS/Parser/Parser.h>
 #include <LibWeb/CSS/StyleComputer.h>
 #include <LibWeb/CSS/StyleComputer.h>
+#include <LibWeb/Layout/Node.h>
+#include <LibWeb/Painting/Paintable.h>
 #include <LibWeb/WebIDL/ExceptionOr.h>
 #include <LibWeb/WebIDL/ExceptionOr.h>
 
 
 namespace Web::Animations {
 namespace Web::Animations {
@@ -869,4 +871,56 @@ void KeyframeEffect::visit_edges(Cell::Visitor& visitor)
         visitor.visit(keyframe);
         visitor.visit(keyframe);
 }
 }
 
 
+void KeyframeEffect::update_style_properties()
+{
+    if (!target())
+        return;
+
+    if (pseudo_element_type().has_value()) {
+        // StyleProperties are not saved for pseudo-elements so there is nothing to patch
+        target()->invalidate_style();
+        return;
+    }
+
+    auto* style = target()->computed_css_values();
+    if (!style)
+        return;
+
+    auto style_before_animation_update = style->clone();
+
+    auto& document = target()->document();
+    document.style_computer().collect_animation_into(*this, *style, CSS::StyleComputer::AnimationRefresh::Yes);
+
+    // Traversal of the subtree is necessary to update the animated properties inherited from the target element.
+    target()->for_each_in_subtree_of_type<DOM::Element>([&](auto& element) {
+        auto* element_style = element.computed_css_values();
+        if (!element_style || !element.layout_node())
+            return IterationDecision::Continue;
+
+        for (auto i = to_underlying(CSS::first_property_id); i <= to_underlying(CSS::last_property_id); ++i) {
+            if (element_style->is_property_inherited(static_cast<CSS::PropertyID>(i))) {
+                auto new_value = CSS::StyleComputer::get_inherit_value(document.realm(), static_cast<CSS::PropertyID>(i), &element);
+                element_style->set_property(static_cast<CSS::PropertyID>(i), *new_value, nullptr, CSS::StyleProperties::Inherited::Yes);
+            }
+        }
+
+        element.layout_node()->apply_style(*element_style);
+        return IterationDecision::Continue;
+    });
+
+    auto invalidation = DOM::Element::compute_required_invalidation(style_before_animation_update, *style);
+
+    if (target()->layout_node())
+        target()->layout_node()->apply_style(*style);
+
+    if (invalidation.relayout)
+        document.set_needs_layout();
+    if (invalidation.rebuild_layout_tree)
+        document.invalidate_layout();
+    if (invalidation.repaint)
+        document.set_needs_to_resolve_paint_only_properties();
+    if (invalidation.rebuild_stacking_context_tree)
+        document.invalidate_stacking_context_tree();
+}
+
 }
 }

+ 2 - 0
Userland/Libraries/LibWeb/Animations/KeyframeEffect.h

@@ -103,6 +103,8 @@ public:
 
 
     virtual bool is_keyframe_effect() const override { return true; }
     virtual bool is_keyframe_effect() const override { return true; }
 
 
+    virtual void update_style_properties() override;
+
 private:
 private:
     KeyframeEffect(JS::Realm&);
     KeyframeEffect(JS::Realm&);
     virtual ~KeyframeEffect() override = default;
     virtual ~KeyframeEffect() override = default;

+ 27 - 16
Userland/Libraries/LibWeb/CSS/StyleComputer.cpp

@@ -90,7 +90,6 @@ struct Traits<Web::CSS::FontFaceKey> : public DefaultTraits<Web::CSS::FontFaceKe
 namespace Web::CSS {
 namespace Web::CSS {
 
 
 static DOM::Element const* element_to_inherit_style_from(DOM::Element const*, Optional<CSS::Selector::PseudoElement::Type>);
 static DOM::Element const* element_to_inherit_style_from(DOM::Element const*, Optional<CSS::Selector::PseudoElement::Type>);
-static NonnullRefPtr<StyleValue const> get_inherit_value(JS::Realm& initial_value_context_realm, CSS::PropertyID, DOM::Element const*, Optional<CSS::Selector::PseudoElement::Type>);
 
 
 StyleComputer::StyleComputer(DOM::Document& document)
 StyleComputer::StyleComputer(DOM::Document& document)
     : m_document(document)
     : m_document(document)
@@ -668,26 +667,27 @@ void StyleComputer::for_each_property_expanding_shorthands(PropertyID property_i
     set_longhand_property(property_id, value);
     set_longhand_property(property_id, value);
 }
 }
 
 
-void StyleComputer::set_property_expanding_shorthands(StyleProperties& style, CSS::PropertyID property_id, StyleValue const& value, CSS::CSSStyleDeclaration const* declaration, StyleProperties::PropertyValues const& properties_for_revert)
+void StyleComputer::set_property_expanding_shorthands(StyleProperties& style, CSS::PropertyID property_id, StyleValue const& value, CSS::CSSStyleDeclaration const* declaration, StyleProperties::PropertyValues const& properties_for_revert, StyleProperties::Important important)
 {
 {
     for_each_property_expanding_shorthands(property_id, value, [&](PropertyID shorthand_id, StyleValue const& shorthand_value) {
     for_each_property_expanding_shorthands(property_id, value, [&](PropertyID shorthand_id, StyleValue const& shorthand_value) {
         if (shorthand_value.is_revert()) {
         if (shorthand_value.is_revert()) {
             auto& property_in_previous_cascade_origin = properties_for_revert[to_underlying(shorthand_id)];
             auto& property_in_previous_cascade_origin = properties_for_revert[to_underlying(shorthand_id)];
             if (property_in_previous_cascade_origin.has_value())
             if (property_in_previous_cascade_origin.has_value())
-                style.set_property(shorthand_id, property_in_previous_cascade_origin->style, property_in_previous_cascade_origin->declaration);
+                style.set_property(shorthand_id, property_in_previous_cascade_origin->style, property_in_previous_cascade_origin->declaration, StyleProperties::Inherited::No, important);
         } else {
         } else {
-            style.set_property(shorthand_id, shorthand_value, declaration);
+            style.set_property(shorthand_id, shorthand_value, declaration, StyleProperties::Inherited::No, important);
         }
         }
     });
     });
 }
 }
 
 
-void StyleComputer::set_all_properties(DOM::Element& element, Optional<CSS::Selector::PseudoElement::Type> pseudo_element, StyleProperties& style, StyleValue const& value, DOM::Document& document, CSS::CSSStyleDeclaration const* declaration, StyleProperties::PropertyValues const& properties_for_revert) const
+void StyleComputer::set_all_properties(DOM::Element& element, Optional<CSS::Selector::PseudoElement::Type> pseudo_element, StyleProperties& style, StyleValue const& value, DOM::Document& document, CSS::CSSStyleDeclaration const* declaration, StyleProperties::PropertyValues const& properties_for_revert, StyleProperties::Important important) const
 {
 {
     for (auto i = to_underlying(CSS::first_longhand_property_id); i <= to_underlying(CSS::last_longhand_property_id); ++i) {
     for (auto i = to_underlying(CSS::first_longhand_property_id); i <= to_underlying(CSS::last_longhand_property_id); ++i) {
         auto property_id = (CSS::PropertyID)i;
         auto property_id = (CSS::PropertyID)i;
 
 
         if (value.is_revert()) {
         if (value.is_revert()) {
             style.m_property_values[to_underlying(property_id)] = properties_for_revert[to_underlying(property_id)];
             style.m_property_values[to_underlying(property_id)] = properties_for_revert[to_underlying(property_id)];
+            style.m_property_values[to_underlying(property_id)]->important = important;
             continue;
             continue;
         }
         }
 
 
@@ -696,6 +696,7 @@ void StyleComputer::set_all_properties(DOM::Element& element, Optional<CSS::Sele
                 style.m_property_values[to_underlying(property_id)] = { { get_inherit_value(document.realm(), property_id, &element, pseudo_element), nullptr } };
                 style.m_property_values[to_underlying(property_id)] = { { get_inherit_value(document.realm(), property_id, &element, pseudo_element), nullptr } };
             else
             else
                 style.m_property_values[to_underlying(property_id)] = { { property_initial_value(document.realm(), property_id), nullptr } };
                 style.m_property_values[to_underlying(property_id)] = { { property_initial_value(document.realm(), property_id), nullptr } };
+            style.m_property_values[to_underlying(property_id)]->important = important;
             continue;
             continue;
         }
         }
 
 
@@ -705,7 +706,9 @@ void StyleComputer::set_all_properties(DOM::Element& element, Optional<CSS::Sele
         if (!property_value->is_unresolved())
         if (!property_value->is_unresolved())
             set_property_expanding_shorthands(style, property_id, property_value, declaration, properties_for_revert);
             set_property_expanding_shorthands(style, property_id, property_value, declaration, properties_for_revert);
 
 
-        set_property_expanding_shorthands(style, property_id, value, declaration, properties_for_revert);
+        style.m_property_values[to_underlying(property_id)]->important = important;
+
+        set_property_expanding_shorthands(style, property_id, value, declaration, properties_for_revert, important);
     }
     }
 }
 }
 
 
@@ -719,7 +722,7 @@ void StyleComputer::cascade_declarations(StyleProperties& style, DOM::Element& e
                 continue;
                 continue;
 
 
             if (property.property_id == CSS::PropertyID::All) {
             if (property.property_id == CSS::PropertyID::All) {
-                set_all_properties(element, pseudo_element, style, property.value, m_document, &match.rule->declaration(), properties_for_revert);
+                set_all_properties(element, pseudo_element, style, property.value, m_document, &match.rule->declaration(), properties_for_revert, important == Important::Yes ? StyleProperties::Important::Yes : StyleProperties::Important::No);
                 continue;
                 continue;
             }
             }
 
 
@@ -727,7 +730,7 @@ void StyleComputer::cascade_declarations(StyleProperties& style, DOM::Element& e
             if (property.value->is_unresolved())
             if (property.value->is_unresolved())
                 property_value = Parser::Parser::resolve_unresolved_style_value({}, Parser::ParsingContext { document() }, element, pseudo_element, property.property_id, property.value->as_unresolved());
                 property_value = Parser::Parser::resolve_unresolved_style_value({}, Parser::ParsingContext { document() }, element, pseudo_element, property.property_id, property.value->as_unresolved());
             if (!property_value->is_unresolved())
             if (!property_value->is_unresolved())
-                set_property_expanding_shorthands(style, property.property_id, property_value, &match.rule->declaration(), properties_for_revert);
+                set_property_expanding_shorthands(style, property.property_id, property_value, &match.rule->declaration(), properties_for_revert, important == Important::Yes ? StyleProperties::Important::Yes : StyleProperties::Important::No);
         }
         }
     }
     }
 
 
@@ -738,7 +741,7 @@ void StyleComputer::cascade_declarations(StyleProperties& style, DOM::Element& e
                     continue;
                     continue;
 
 
                 if (property.property_id == CSS::PropertyID::All) {
                 if (property.property_id == CSS::PropertyID::All) {
-                    set_all_properties(element, pseudo_element, style, property.value, m_document, inline_style, properties_for_revert);
+                    set_all_properties(element, pseudo_element, style, property.value, m_document, inline_style, properties_for_revert, important == Important::Yes ? StyleProperties::Important::Yes : StyleProperties::Important::No);
                     continue;
                     continue;
                 }
                 }
 
 
@@ -746,7 +749,7 @@ void StyleComputer::cascade_declarations(StyleProperties& style, DOM::Element& e
                 if (property.value->is_unresolved())
                 if (property.value->is_unresolved())
                     property_value = Parser::Parser::resolve_unresolved_style_value({}, Parser::ParsingContext { document() }, element, pseudo_element, property.property_id, property.value->as_unresolved());
                     property_value = Parser::Parser::resolve_unresolved_style_value({}, Parser::ParsingContext { document() }, element, pseudo_element, property.property_id, property.value->as_unresolved());
                 if (!property_value->is_unresolved())
                 if (!property_value->is_unresolved())
-                    set_property_expanding_shorthands(style, property.property_id, property_value, inline_style, properties_for_revert);
+                    set_property_expanding_shorthands(style, property.property_id, property_value, inline_style, properties_for_revert, important == Important::Yes ? StyleProperties::Important::Yes : StyleProperties::Important::No);
             }
             }
         }
         }
     }
     }
@@ -1276,7 +1279,7 @@ static ValueComparingRefPtr<StyleValue const> interpolate_property(DOM::Element&
     }
     }
 }
 }
 
 
-void StyleComputer::collect_animation_into(JS::NonnullGCPtr<Animations::KeyframeEffect> effect, StyleProperties& style_properties) const
+void StyleComputer::collect_animation_into(JS::NonnullGCPtr<Animations::KeyframeEffect> effect, StyleProperties& style_properties, AnimationRefresh refresh) const
 {
 {
     auto animation = effect->associated_animation();
     auto animation = effect->associated_animation();
     if (!animation)
     if (!animation)
@@ -1329,6 +1332,8 @@ void StyleComputer::collect_animation_into(JS::NonnullGCPtr<Animations::Keyframe
         auto resolve_property = [&](auto& property) {
         auto resolve_property = [&](auto& property) {
             return property.visit(
             return property.visit(
                 [&](Animations::KeyframeEffect::KeyFrameSet::UseInitial) -> RefPtr<StyleValue const> {
                 [&](Animations::KeyframeEffect::KeyFrameSet::UseInitial) -> RefPtr<StyleValue const> {
+                    if (refresh == AnimationRefresh::Yes)
+                        return {};
                     return style_properties.maybe_null_property(it.key);
                     return style_properties.maybe_null_property(it.key);
                 },
                 },
                 [&](RefPtr<StyleValue const> value) { return value; });
                 [&](RefPtr<StyleValue const> value) { return value; });
@@ -1339,7 +1344,7 @@ void StyleComputer::collect_animation_into(JS::NonnullGCPtr<Animations::Keyframe
         auto const& end_property = keyframe_end_values.resolved_properties.get(it.key);
         auto const& end_property = keyframe_end_values.resolved_properties.get(it.key);
         if (!end_property.has_value()) {
         if (!end_property.has_value()) {
             if (resolved_start_property) {
             if (resolved_start_property) {
-                style_properties.set_property(it.key, *resolved_start_property);
+                style_properties.set_animated_property(it.key, *resolved_start_property);
                 dbgln_if(LIBWEB_CSS_ANIMATION_DEBUG, "No end property for property {}, using {}", string_from_property_id(it.key), resolved_start_property->to_string());
                 dbgln_if(LIBWEB_CSS_ANIMATION_DEBUG, "No end property for property {}, using {}", string_from_property_id(it.key), resolved_start_property->to_string());
             }
             }
             continue;
             continue;
@@ -1356,13 +1361,17 @@ void StyleComputer::collect_animation_into(JS::NonnullGCPtr<Animations::Keyframe
         auto start = resolved_start_property.release_nonnull();
         auto start = resolved_start_property.release_nonnull();
         auto end = resolved_end_property.release_nonnull();
         auto end = resolved_end_property.release_nonnull();
 
 
+        if (style_properties.is_property_important(it.key)) {
+            continue;
+        }
+
         if (auto next_value = interpolate_property(*effect->target(), it.key, *start, *end, progress_in_keyframe)) {
         if (auto next_value = interpolate_property(*effect->target(), it.key, *start, *end, progress_in_keyframe)) {
             dbgln_if(LIBWEB_CSS_ANIMATION_DEBUG, "Interpolated value for property {} at {}: {} -> {} = {}", string_from_property_id(it.key), progress_in_keyframe, start->to_string(), end->to_string(), next_value->to_string());
             dbgln_if(LIBWEB_CSS_ANIMATION_DEBUG, "Interpolated value for property {} at {}: {} -> {} = {}", string_from_property_id(it.key), progress_in_keyframe, start->to_string(), end->to_string(), next_value->to_string());
-            style_properties.set_property(it.key, *next_value);
+            style_properties.set_animated_property(it.key, *next_value);
         } else {
         } else {
             // If interpolate_property() fails, the element should not be rendered
             // If interpolate_property() fails, the element should not be rendered
             dbgln_if(LIBWEB_CSS_ANIMATION_DEBUG, "Interpolated value for property {} at {}: {} -> {} is invalid", string_from_property_id(it.key), progress_in_keyframe, start->to_string(), end->to_string());
             dbgln_if(LIBWEB_CSS_ANIMATION_DEBUG, "Interpolated value for property {} at {}: {} -> {} is invalid", string_from_property_id(it.key), progress_in_keyframe, start->to_string(), end->to_string());
-            style_properties.set_property(PropertyID::Visibility, IdentifierStyleValue::create(ValueID::Hidden));
+            style_properties.set_animated_property(PropertyID::Visibility, IdentifierStyleValue::create(ValueID::Hidden));
         }
         }
     }
     }
 }
 }
@@ -1570,7 +1579,7 @@ DOM::Element const* element_to_inherit_style_from(DOM::Element const* element, O
     return parent_element;
     return parent_element;
 }
 }
 
 
-NonnullRefPtr<StyleValue const> get_inherit_value(JS::Realm& initial_value_context_realm, CSS::PropertyID property_id, DOM::Element const* element, Optional<CSS::Selector::PseudoElement::Type> pseudo_element)
+NonnullRefPtr<StyleValue const> StyleComputer::get_inherit_value(JS::Realm& initial_value_context_realm, CSS::PropertyID property_id, DOM::Element const* element, Optional<CSS::Selector::PseudoElement::Type> pseudo_element)
 {
 {
     auto* parent_element = element_to_inherit_style_from(element, pseudo_element);
     auto* parent_element = element_to_inherit_style_from(element, pseudo_element);
 
 
@@ -1586,7 +1595,7 @@ void StyleComputer::compute_defaulted_property_value(StyleProperties& style, DOM
     auto& value_slot = style.m_property_values[to_underlying(property_id)];
     auto& value_slot = style.m_property_values[to_underlying(property_id)];
     if (!value_slot.has_value()) {
     if (!value_slot.has_value()) {
         if (is_inherited_property(property_id))
         if (is_inherited_property(property_id))
-            style.m_property_values[to_underlying(property_id)] = { { get_inherit_value(document().realm(), property_id, element, pseudo_element), nullptr } };
+            style.m_property_values[to_underlying(property_id)] = { { get_inherit_value(document().realm(), property_id, element, pseudo_element), nullptr, StyleProperties::Important::No, StyleProperties::Inherited::Yes } };
         else
         else
             style.m_property_values[to_underlying(property_id)] = { { property_initial_value(document().realm(), property_id), nullptr } };
             style.m_property_values[to_underlying(property_id)] = { { property_initial_value(document().realm(), property_id), nullptr } };
         return;
         return;
@@ -1599,6 +1608,7 @@ void StyleComputer::compute_defaulted_property_value(StyleProperties& style, DOM
 
 
     if (value_slot->style->is_inherit()) {
     if (value_slot->style->is_inherit()) {
         value_slot->style = get_inherit_value(document().realm(), property_id, element, pseudo_element);
         value_slot->style = get_inherit_value(document().realm(), property_id, element, pseudo_element);
+        value_slot->inherited = StyleProperties::Inherited::Yes;
         return;
         return;
     }
     }
 
 
@@ -1608,6 +1618,7 @@ void StyleComputer::compute_defaulted_property_value(StyleProperties& style, DOM
         if (is_inherited_property(property_id)) {
         if (is_inherited_property(property_id)) {
             // then if it is an inherited property, this is treated as inherit,
             // then if it is an inherited property, this is treated as inherit,
             value_slot->style = get_inherit_value(document().realm(), property_id, element, pseudo_element);
             value_slot->style = get_inherit_value(document().realm(), property_id, element, pseudo_element);
+            value_slot->inherited = StyleProperties::Inherited::Yes;
         } else {
         } else {
             // and if it is not, this is treated as initial.
             // and if it is not, this is treated as initial.
             value_slot->style = property_initial_value(document().realm(), property_id);
             value_slot->style = property_initial_value(document().realm(), property_id);

+ 9 - 4
Userland/Libraries/LibWeb/CSS/StyleComputer.h

@@ -55,7 +55,8 @@ struct FontFaceKey {
 class StyleComputer {
 class StyleComputer {
 public:
 public:
     static void for_each_property_expanding_shorthands(PropertyID, StyleValue const&, Function<void(PropertyID, StyleValue const&)> const& set_longhand_property);
     static void for_each_property_expanding_shorthands(PropertyID, StyleValue const&, Function<void(PropertyID, StyleValue const&)> const& set_longhand_property);
-    static void set_property_expanding_shorthands(StyleProperties&, PropertyID, StyleValue const&, CSS::CSSStyleDeclaration const*, StyleProperties::PropertyValues const& properties_for_revert);
+    static void set_property_expanding_shorthands(StyleProperties&, PropertyID, StyleValue const&, CSS::CSSStyleDeclaration const*, StyleProperties::PropertyValues const& properties_for_revert, StyleProperties::Important = StyleProperties::Important::No);
+    static NonnullRefPtr<StyleValue const> get_inherit_value(JS::Realm& initial_value_context_realm, CSS::PropertyID, DOM::Element const*, Optional<CSS::Selector::PseudoElement::Type> = {});
 
 
     explicit StyleComputer(DOM::Document&);
     explicit StyleComputer(DOM::Document&);
     ~StyleComputer();
     ~StyleComputer();
@@ -82,6 +83,12 @@ public:
 
 
     void set_viewport_rect(Badge<DOM::Document>, CSSPixelRect const& viewport_rect) { m_viewport_rect = viewport_rect; }
     void set_viewport_rect(Badge<DOM::Document>, CSSPixelRect const& viewport_rect) { m_viewport_rect = viewport_rect; }
 
 
+    enum class AnimationRefresh {
+        No,
+        Yes,
+    };
+    void collect_animation_into(JS::NonnullGCPtr<Animations::KeyframeEffect> animation, StyleProperties& style_properties, AnimationRefresh = AnimationRefresh::No) const;
+
 private:
 private:
     enum class ComputeStyleMode {
     enum class ComputeStyleMode {
         Normal,
         Normal,
@@ -105,7 +112,7 @@ private:
 
 
     void compute_defaulted_property_value(StyleProperties&, DOM::Element const*, CSS::PropertyID, Optional<CSS::Selector::PseudoElement::Type>) const;
     void compute_defaulted_property_value(StyleProperties&, DOM::Element const*, CSS::PropertyID, Optional<CSS::Selector::PseudoElement::Type>) const;
 
 
-    void set_all_properties(DOM::Element&, Optional<CSS::Selector::PseudoElement::Type>, StyleProperties&, StyleValue const&, DOM::Document&, CSS::CSSStyleDeclaration const*, StyleProperties::PropertyValues const& properties_for_revert) const;
+    void set_all_properties(DOM::Element&, Optional<CSS::Selector::PseudoElement::Type>, StyleProperties&, StyleValue const&, DOM::Document&, CSS::CSSStyleDeclaration const*, StyleProperties::PropertyValues const& properties_for_revert, StyleProperties::Important = StyleProperties::Important::No) const;
 
 
     template<typename Callback>
     template<typename Callback>
     void for_each_stylesheet(CascadeOrigin, Callback) const;
     void for_each_stylesheet(CascadeOrigin, Callback) const;
@@ -142,8 +149,6 @@ private:
 
 
     RuleCache const& rule_cache_for_cascade_origin(CascadeOrigin) const;
     RuleCache const& rule_cache_for_cascade_origin(CascadeOrigin) const;
 
 
-    void collect_animation_into(JS::NonnullGCPtr<Animations::KeyframeEffect> animation, StyleProperties& style_properties) const;
-
     OwnPtr<RuleCache> m_author_rule_cache;
     OwnPtr<RuleCache> m_author_rule_cache;
     OwnPtr<RuleCache> m_user_rule_cache;
     OwnPtr<RuleCache> m_user_rule_cache;
     OwnPtr<RuleCache> m_user_agent_rule_cache;
     OwnPtr<RuleCache> m_user_agent_rule_cache;

+ 42 - 2
Userland/Libraries/LibWeb/CSS/StyleProperties.cpp

@@ -34,13 +34,49 @@
 
 
 namespace Web::CSS {
 namespace Web::CSS {
 
 
-void StyleProperties::set_property(CSS::PropertyID id, NonnullRefPtr<StyleValue const> value, CSS::CSSStyleDeclaration const* source_declaration)
+NonnullRefPtr<StyleProperties> StyleProperties::clone() const
 {
 {
-    m_property_values[to_underlying(id)] = StyleAndSourceDeclaration { move(value), source_declaration };
+    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)].has_value() && m_property_values[to_underlying(property_id)]->important == Important::Yes;
+}
+
+bool StyleProperties::is_property_inherited(CSS::PropertyID property_id) const
+{
+    return m_property_values[to_underlying(property_id)].has_value() && m_property_values[to_underlying(property_id)]->inherited == Inherited::Yes;
+}
+
+void StyleProperties::set_property(CSS::PropertyID id, NonnullRefPtr<StyleValue const> value, CSS::CSSStyleDeclaration const* source_declaration, Inherited inherited, Important important)
+{
+    m_property_values[to_underlying(id)] = StyleAndSourceDeclaration { move(value), source_declaration, important, inherited };
+}
+
+void StyleProperties::set_animated_property(CSS::PropertyID id, NonnullRefPtr<StyleValue const> value)
+{
+    m_animated_property_values[to_underlying(id)] = move(value);
+}
+
+void StyleProperties::reset_animated_properties()
+{
+    for (auto& animated_property : m_animated_property_values)
+        animated_property.clear();
 }
 }
 
 
 NonnullRefPtr<StyleValue const> StyleProperties::property(CSS::PropertyID property_id) const
 NonnullRefPtr<StyleValue const> StyleProperties::property(CSS::PropertyID property_id) const
 {
 {
+    auto animated_value = m_animated_property_values[to_underlying(property_id)];
+    if (animated_value.has_value())
+        return *animated_value;
+
     auto value = m_property_values[to_underlying(property_id)];
     auto value = m_property_values[to_underlying(property_id)];
     // By the time we call this method, all properties have values assigned.
     // By the time we call this method, all properties have values assigned.
     VERIFY(value.has_value());
     VERIFY(value.has_value());
@@ -49,6 +85,10 @@ NonnullRefPtr<StyleValue const> StyleProperties::property(CSS::PropertyID proper
 
 
 RefPtr<StyleValue const> StyleProperties::maybe_null_property(CSS::PropertyID property_id) const
 RefPtr<StyleValue const> StyleProperties::maybe_null_property(CSS::PropertyID property_id) const
 {
 {
+    auto animated_value = m_animated_property_values[to_underlying(property_id)];
+    if (animated_value.has_value())
+        return *animated_value;
+
     auto value = m_property_values[to_underlying(property_id)];
     auto value = m_property_values[to_underlying(property_id)];
     if (value.has_value())
     if (value.has_value())
         return value->style;
         return value->style;

+ 23 - 1
Userland/Libraries/LibWeb/CSS/StyleProperties.h

@@ -23,6 +23,8 @@ public:
 
 
     static NonnullRefPtr<StyleProperties> create() { return adopt_ref(*new StyleProperties); }
     static NonnullRefPtr<StyleProperties> create() { return adopt_ref(*new StyleProperties); }
 
 
+    NonnullRefPtr<StyleProperties> clone() const;
+
     template<typename Callback>
     template<typename Callback>
     inline void for_each_property(Callback callback) const
     inline void for_each_property(Callback callback) const
     {
     {
@@ -32,16 +34,34 @@ public:
         }
         }
     }
     }
 
 
+    enum class Important {
+        No,
+        Yes
+    };
+
+    enum class Inherited {
+        No,
+        Yes
+    };
+
     struct StyleAndSourceDeclaration {
     struct StyleAndSourceDeclaration {
         NonnullRefPtr<StyleValue const> style;
         NonnullRefPtr<StyleValue const> style;
         CSS::CSSStyleDeclaration const* declaration = nullptr;
         CSS::CSSStyleDeclaration const* declaration = nullptr;
+        Important important { Important::No };
+        Inherited inherited { Inherited::No };
     };
     };
     using PropertyValues = Array<Optional<StyleAndSourceDeclaration>, to_underlying(CSS::last_property_id) + 1>;
     using PropertyValues = Array<Optional<StyleAndSourceDeclaration>, to_underlying(CSS::last_property_id) + 1>;
 
 
     auto& properties() { return m_property_values; }
     auto& properties() { return m_property_values; }
     auto const& properties() const { return m_property_values; }
     auto const& properties() const { return m_property_values; }
 
 
-    void set_property(CSS::PropertyID, NonnullRefPtr<StyleValue const> value, CSS::CSSStyleDeclaration const* source_declaration = nullptr);
+    void reset_animated_properties();
+
+    bool is_property_important(CSS::PropertyID property_id) const;
+    bool is_property_inherited(CSS::PropertyID property_id) const;
+
+    void set_property(CSS::PropertyID, NonnullRefPtr<StyleValue const> value, CSS::CSSStyleDeclaration const* source_declaration = nullptr, Inherited = Inherited::No, Important = Important::No);
+    void set_animated_property(CSS::PropertyID, NonnullRefPtr<StyleValue const> value);
     NonnullRefPtr<StyleValue const> property(CSS::PropertyID) const;
     NonnullRefPtr<StyleValue const> property(CSS::PropertyID) const;
     RefPtr<StyleValue const> maybe_null_property(CSS::PropertyID) const;
     RefPtr<StyleValue const> maybe_null_property(CSS::PropertyID) const;
     CSS::CSSStyleDeclaration const* property_source_declaration(CSS::PropertyID) const;
     CSS::CSSStyleDeclaration const* property_source_declaration(CSS::PropertyID) const;
@@ -166,6 +186,8 @@ private:
     friend class StyleComputer;
     friend class StyleComputer;
 
 
     PropertyValues m_property_values;
     PropertyValues m_property_values;
+    Array<Optional<NonnullRefPtr<StyleValue const>>, to_underlying(CSS::last_property_id) + 1> m_animated_property_values;
+
     Optional<CSS::Overflow> overflow(CSS::PropertyID) const;
     Optional<CSS::Overflow> overflow(CSS::PropertyID) const;
     Vector<CSS::ShadowData> shadow(CSS::PropertyID, Layout::Node const&) const;
     Vector<CSS::ShadowData> shadow(CSS::PropertyID, Layout::Node const&) const;
 
 

+ 15 - 1
Userland/Libraries/LibWeb/DOM/Document.cpp

@@ -1163,6 +1163,19 @@ void Document::update_style()
 {
 {
     if (!browsing_context())
     if (!browsing_context())
         return;
         return;
+
+    for (auto& timeline : m_associated_animation_timelines) {
+        for (auto& animation : timeline->associated_animations()) {
+            if (auto effect = animation->effect(); effect && effect->target())
+                effect->target()->reset_animated_css_properties();
+        }
+
+        for (auto& animation : timeline->associated_animations()) {
+            if (auto effect = animation->effect())
+                effect->update_style_properties();
+        }
+    }
+
     if (!needs_full_style_update() && !needs_style_update() && !child_needs_style_update())
     if (!needs_full_style_update() && !needs_style_update() && !child_needs_style_update())
         return;
         return;
 
 
@@ -1917,7 +1930,8 @@ void Document::dispatch_events_for_animation_if_necessary(JS::NonnullGCPtr<Anima
         return;
         return;
 
 
     auto& css_animation = verify_cast<CSS::CSSAnimation>(*animation);
     auto& css_animation = verify_cast<CSS::CSSAnimation>(*animation);
-    css_animation.owning_element()->set_needs_style_update(true);
+    if (auto target = effect->target(); target && target->paintable())
+        target->paintable()->set_needs_display();
 
 
     auto previous_phase = effect->previous_phase();
     auto previous_phase = effect->previous_phase();
     auto current_phase = effect->phase();
     auto current_phase = effect->phase();

+ 13 - 6
Userland/Libraries/LibWeb/DOM/Element.cpp

@@ -510,7 +510,7 @@ void Element::attribute_changed(FlyString const& name, Optional<String> const& v
     }
     }
 }
 }
 
 
-static Element::RequiredInvalidationAfterStyleChange compute_required_invalidation(CSS::StyleProperties const& old_style, CSS::StyleProperties const& new_style)
+Element::RequiredInvalidationAfterStyleChange Element::compute_required_invalidation(CSS::StyleProperties const& old_style, CSS::StyleProperties const& new_style)
 {
 {
     Element::RequiredInvalidationAfterStyleChange invalidation;
     Element::RequiredInvalidationAfterStyleChange invalidation;
 
 
@@ -519,12 +519,12 @@ static Element::RequiredInvalidationAfterStyleChange compute_required_invalidati
 
 
     for (auto i = to_underlying(CSS::first_property_id); i <= to_underlying(CSS::last_property_id); ++i) {
     for (auto i = to_underlying(CSS::first_property_id); i <= to_underlying(CSS::last_property_id); ++i) {
         auto property_id = static_cast<CSS::PropertyID>(i);
         auto property_id = static_cast<CSS::PropertyID>(i);
-        auto const& old_value = old_style.properties()[i];
-        auto const& new_value = new_style.properties()[i];
-        if (!old_value.has_value() && !new_value.has_value())
+        auto old_value = old_style.maybe_null_property(property_id);
+        auto new_value = new_style.maybe_null_property(property_id);
+        if (!old_value && !new_value)
             continue;
             continue;
 
 
-        bool const property_value_changed = (!old_value.has_value() || !new_value.has_value()) || *old_value->style != *new_value->style;
+        bool const property_value_changed = (!old_value || !new_value) || *old_value != *new_value;
         if (!property_value_changed)
         if (!property_value_changed)
             continue;
             continue;
 
 
@@ -544,7 +544,7 @@ static Element::RequiredInvalidationAfterStyleChange compute_required_invalidati
         // OPTIMIZATION: Special handling for CSS `visibility`:
         // OPTIMIZATION: Special handling for CSS `visibility`:
         if (property_id == CSS::PropertyID::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.
             // We don't need to relayout if the visibility changes from visible to hidden or vice versa. Only collapse requires relayout.
-            if ((old_value.has_value() && old_value->style->to_identifier() == CSS::ValueID::Collapse) != (new_value.has_value() && new_value->style->to_identifier() == CSS::ValueID::Collapse))
+            if ((old_value && old_value->to_identifier() == CSS::ValueID::Collapse) != (new_value && new_value->to_identifier() == CSS::ValueID::Collapse))
                 invalidation.relayout = true;
                 invalidation.relayout = true;
             // Of course, we still have to repaint on any visibility change.
             // Of course, we still have to repaint on any visibility change.
             invalidation.repaint = true;
             invalidation.repaint = true;
@@ -624,6 +624,13 @@ NonnullRefPtr<CSS::StyleProperties> Element::resolved_css_values()
     return properties;
     return properties;
 }
 }
 
 
+void Element::reset_animated_css_properties()
+{
+    if (!m_computed_css_values)
+        return;
+    m_computed_css_values->reset_animated_properties();
+}
+
 DOMTokenList* Element::class_list()
 DOMTokenList* Element::class_list()
 {
 {
     if (!m_class_list)
     if (!m_class_list)

+ 4 - 0
Userland/Libraries/LibWeb/DOM/Element.h

@@ -166,6 +166,8 @@ public:
         static RequiredInvalidationAfterStyleChange full() { return { true, true, true, true }; }
         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();
     RequiredInvalidationAfterStyleChange recompute_style();
 
 
     Optional<CSS::Selector::PseudoElement::Type> use_pseudo_element() const { return m_use_pseudo_element; }
     Optional<CSS::Selector::PseudoElement::Type> use_pseudo_element() const { return m_use_pseudo_element; }
@@ -179,6 +181,8 @@ public:
     void set_computed_css_values(RefPtr<CSS::StyleProperties>);
     void set_computed_css_values(RefPtr<CSS::StyleProperties>);
     NonnullRefPtr<CSS::StyleProperties> resolved_css_values();
     NonnullRefPtr<CSS::StyleProperties> resolved_css_values();
 
 
+    void reset_animated_css_properties();
+
     CSS::CSSStyleDeclaration const* inline_style() const;
     CSS::CSSStyleDeclaration const* inline_style() const;
 
 
     CSS::CSSStyleDeclaration* style_for_bindings();
     CSS::CSSStyleDeclaration* style_for_bindings();