[Web Animations] Use a keyframe effect stack to resolve animations on an element
authorgraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 8 Nov 2019 20:40:57 +0000 (20:40 +0000)
committergraouts@webkit.org <graouts@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 8 Nov 2019 20:40:57 +0000 (20:40 +0000)
https://bugs.webkit.org/show_bug.cgi?id=204010

Reviewed by Dean Jackson.

Until now, when resolving animations for an element, we would call animationsForElement() during each resolution which
means doing several hash table lookups to locate the various classes of animations for that given element, sorting each
of those animations and inserting them into a new Vector.

We now use a KeyframeEffectStack which keeps a list of KeyframeEffect objects that apply to a given target, provided the
effect also has a valid animation and that animation has a valid timeline, all pre-conditions for that effect to produce
an animated value. Any time one of those pre-conditions change, we update the membership of that effect in the stack.
The KeyframeEffectStack is a new member of ElementRareData.

Now, each time we resolve an animation for an element, we iterate over the KeyframeEffect objects returned by calling
sortEffects() on the KeyframeEffectStack which will sort the stack's effects only if a new effect had been added since
the last iteration, which means that simple animations that are not mutated will require sorting of the stack just once,
and the addition of several animations in a single animation frame will require sorting just once as well.

It was also found while doing this work that Style::TreeResolver::createAnimatedElementUpdate would call RenderStyle::clonePtr()
for any element that was part of a document containing a timeline, regardless of whether that element had any animations. Now
we check whether that element's KeyframeEffectStack contains any effects prior to cloning the style.

No tests or changes to existed test expectations as this should not yield any change in behavior.

* Sources.txt: Add the new KeyframeEffectStack.
* WebCore.xcodeproj/project.pbxproj:
* animation/AnimationEffect.h:
(WebCore::AnimationEffect::setAnimation):
* animation/AnimationTimeline.cpp:
(WebCore::AnimationTimeline::removeAnimation):
(WebCore::AnimationTimeline::updateCSSAnimationsForElement): Since we need to know the order of CSS @keyframes rules listed in animation-name
when sorting effects, we must compile the ordered list of those @keyframe rules as we update CSS animations for an element and store it on its
KeyframeEffectStack.
* animation/DocumentTimeline.cpp:
(WebCore::DocumentTimeline::resolveAnimationsForElement): Deleted. Replaced by Element::applyKeyframeEffects().
* animation/DocumentTimeline.h:
* animation/KeyframeEffect.cpp:
(WebCore::KeyframeEffect::animationTimelineDidChange):
(WebCore::KeyframeEffect::setAnimation):
(WebCore::KeyframeEffect::setTarget):
* animation/KeyframeEffect.h:
* animation/KeyframeEffectStack.cpp: Added.
(WebCore::KeyframeEffectStack::KeyframeEffectStack):
(WebCore::KeyframeEffectStack::~KeyframeEffectStack):
(WebCore::KeyframeEffectStack::addEffect):
(WebCore::KeyframeEffectStack::removeEffect):
(WebCore::KeyframeEffectStack::sortedEffects):
(WebCore::KeyframeEffectStack::ensureEffectsAreSorted):
(WebCore::KeyframeEffectStack::setCSSAnimationNames):
* animation/KeyframeEffectStack.h: Added.
(WebCore::KeyframeEffectStack::hasEffects const):
* animation/WebAnimation.cpp:
(WebCore::WebAnimation::setTimelineInternal):
(WebCore::WebAnimation::persist):
* dom/Element.cpp:
(WebCore::Element::ensureKeyframeEffectStack):
(WebCore::Element::hasKeyframeEffects const):
(WebCore::Element::applyKeyframeEffects):
* dom/Element.h:
* dom/ElementRareData.cpp:
* dom/ElementRareData.h:
(WebCore::ElementRareData::keyframeEffectStack):
(WebCore::ElementRareData::setKeyframeEffectStack):
* style/StyleTreeResolver.cpp:
(WebCore::Style::TreeResolver::createAnimatedElementUpdate):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@252253 268f45cc-cd09-0410-ab3c-d52691b4dbfc

17 files changed:
Source/WebCore/ChangeLog
Source/WebCore/Sources.txt
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/animation/AnimationEffect.h
Source/WebCore/animation/AnimationTimeline.cpp
Source/WebCore/animation/DocumentTimeline.cpp
Source/WebCore/animation/DocumentTimeline.h
Source/WebCore/animation/KeyframeEffect.cpp
Source/WebCore/animation/KeyframeEffect.h
Source/WebCore/animation/KeyframeEffectStack.cpp [new file with mode: 0644]
Source/WebCore/animation/KeyframeEffectStack.h [new file with mode: 0644]
Source/WebCore/animation/WebAnimation.cpp
Source/WebCore/dom/Element.cpp
Source/WebCore/dom/Element.h
Source/WebCore/dom/ElementRareData.cpp
Source/WebCore/dom/ElementRareData.h
Source/WebCore/style/StyleTreeResolver.cpp

index 5ddb763c3af055cfd97e37846021c1e72b5229d2..4f2ca9a386fad63f3667bb60ee3148e2bc3bb39c 100644 (file)
@@ -1,3 +1,72 @@
+2019-11-08  Antoine Quint  <graouts@apple.com>
+
+        [Web Animations] Use a keyframe effect stack to resolve animations on an element
+        https://bugs.webkit.org/show_bug.cgi?id=204010
+
+        Reviewed by Dean Jackson.
+
+        Until now, when resolving animations for an element, we would call animationsForElement() during each resolution which
+        means doing several hash table lookups to locate the various classes of animations for that given element, sorting each
+        of those animations and inserting them into a new Vector.
+
+        We now use a KeyframeEffectStack which keeps a list of KeyframeEffect objects that apply to a given target, provided the
+        effect also has a valid animation and that animation has a valid timeline, all pre-conditions for that effect to produce
+        an animated value. Any time one of those pre-conditions change, we update the membership of that effect in the stack.
+        The KeyframeEffectStack is a new member of ElementRareData.
+
+        Now, each time we resolve an animation for an element, we iterate over the KeyframeEffect objects returned by calling
+        sortEffects() on the KeyframeEffectStack which will sort the stack's effects only if a new effect had been added since
+        the last iteration, which means that simple animations that are not mutated will require sorting of the stack just once,
+        and the addition of several animations in a single animation frame will require sorting just once as well.
+
+        It was also found while doing this work that Style::TreeResolver::createAnimatedElementUpdate would call RenderStyle::clonePtr()
+        for any element that was part of a document containing a timeline, regardless of whether that element had any animations. Now
+        we check whether that element's KeyframeEffectStack contains any effects prior to cloning the style.
+
+        No tests or changes to existed test expectations as this should not yield any change in behavior.
+
+        * Sources.txt: Add the new KeyframeEffectStack.
+        * WebCore.xcodeproj/project.pbxproj:
+        * animation/AnimationEffect.h:
+        (WebCore::AnimationEffect::setAnimation):
+        * animation/AnimationTimeline.cpp:
+        (WebCore::AnimationTimeline::removeAnimation):
+        (WebCore::AnimationTimeline::updateCSSAnimationsForElement): Since we need to know the order of CSS @keyframes rules listed in animation-name
+        when sorting effects, we must compile the ordered list of those @keyframe rules as we update CSS animations for an element and store it on its
+        KeyframeEffectStack.
+        * animation/DocumentTimeline.cpp:
+        (WebCore::DocumentTimeline::resolveAnimationsForElement): Deleted. Replaced by Element::applyKeyframeEffects().
+        * animation/DocumentTimeline.h:
+        * animation/KeyframeEffect.cpp:
+        (WebCore::KeyframeEffect::animationTimelineDidChange):
+        (WebCore::KeyframeEffect::setAnimation):
+        (WebCore::KeyframeEffect::setTarget):
+        * animation/KeyframeEffect.h:
+        * animation/KeyframeEffectStack.cpp: Added.
+        (WebCore::KeyframeEffectStack::KeyframeEffectStack):
+        (WebCore::KeyframeEffectStack::~KeyframeEffectStack):
+        (WebCore::KeyframeEffectStack::addEffect):
+        (WebCore::KeyframeEffectStack::removeEffect):
+        (WebCore::KeyframeEffectStack::sortedEffects):
+        (WebCore::KeyframeEffectStack::ensureEffectsAreSorted):
+        (WebCore::KeyframeEffectStack::setCSSAnimationNames):
+        * animation/KeyframeEffectStack.h: Added.
+        (WebCore::KeyframeEffectStack::hasEffects const):
+        * animation/WebAnimation.cpp:
+        (WebCore::WebAnimation::setTimelineInternal):
+        (WebCore::WebAnimation::persist):
+        * dom/Element.cpp:
+        (WebCore::Element::ensureKeyframeEffectStack):
+        (WebCore::Element::hasKeyframeEffects const):
+        (WebCore::Element::applyKeyframeEffects):
+        * dom/Element.h:
+        * dom/ElementRareData.cpp:
+        * dom/ElementRareData.h:
+        (WebCore::ElementRareData::keyframeEffectStack):
+        (WebCore::ElementRareData::setKeyframeEffectStack):
+        * style/StyleTreeResolver.cpp:
+        (WebCore::Style::TreeResolver::createAnimatedElementUpdate):
+
 2019-11-07  Dean Jackson  <dino@apple.com>
 
         Add ANGLE backend for iOS device
index 84ede5ce80f64845a30fefc841acafd7d8be4b98..9c639bc55b47b430698c10e68ff289ed85ba25f7 100644 (file)
@@ -448,6 +448,7 @@ accessibility/isolatedtree/AXIsolatedTree.cpp
 accessibility/isolatedtree/AXIsolatedTreeNode.cpp
 
 animation/AnimationEffect.cpp
+animation/KeyframeEffectStack.cpp
 animation/AnimationPlaybackEvent.cpp
 animation/AnimationTimeline.cpp
 animation/CSSAnimation.cpp
index 13032ec12114ed109726b98d3430a9301df2f992..c401b46c3fdb6cce774d8349173c527dd717873c 100644 (file)
                71729F7E20F3BB4700801CE6 /* JSDocumentTimelineOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 71729F7C20F3BAB900801CE6 /* JSDocumentTimelineOptions.h */; };
                71A1B6081DEE5AD70073BCFB /* modern-media-controls-localized-strings.js in Resources */ = {isa = PBXBuildFile; fileRef = 71A1B6061DEE5A820073BCFB /* modern-media-controls-localized-strings.js */; };
                71A57DF2154BE25C0009D120 /* SVGPathUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 71A57DF0154BE25C0009D120 /* SVGPathUtilities.h */; };
+               71A58196236F467600D81A24 /* KeyframeEffectStack.h in Headers */ = {isa = PBXBuildFile; fileRef = 71A58193236F466400D81A24 /* KeyframeEffectStack.h */; settings = {ATTRIBUTES = (Private, ); }; };
                71B28427203CEC4C0036AA5D /* JSCSSAnimation.h in Headers */ = {isa = PBXBuildFile; fileRef = 71B28426203CEC0D0036AA5D /* JSCSSAnimation.h */; settings = {ATTRIBUTES = (Private, ); }; };
                71B5AB2621F1D9F400376E5C /* PointerCaptureController.h in Headers */ = {isa = PBXBuildFile; fileRef = 71B5AB2421F1D9E200376E5C /* PointerCaptureController.h */; settings = {ATTRIBUTES = (Private, ); }; };
                71B7EE0D21B5C6870031C1EF /* TouchAction.h in Headers */ = {isa = PBXBuildFile; fileRef = 71AEE4EB21B5A49C00DDB036 /* TouchAction.h */; settings = {ATTRIBUTES = (Private, ); }; };
                71A1B6071DEE5A820073BCFB /* en */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; name = en; path = "en.lproj/modern-media-controls-localized-strings.js"; sourceTree = SOURCE_ROOT; };
                71A57DEF154BE25C0009D120 /* SVGPathUtilities.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SVGPathUtilities.cpp; sourceTree = "<group>"; };
                71A57DF0154BE25C0009D120 /* SVGPathUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVGPathUtilities.h; sourceTree = "<group>"; };
+               71A58193236F466400D81A24 /* KeyframeEffectStack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KeyframeEffectStack.h; sourceTree = "<group>"; };
+               71A58195236F466500D81A24 /* KeyframeEffectStack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KeyframeEffectStack.cpp; sourceTree = "<group>"; };
                71AEE4EB21B5A49C00DDB036 /* TouchAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TouchAction.h; sourceTree = "<group>"; };
                71B0460A1DD3C2EE00EE19CF /* status-support.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "status-support.js"; sourceTree = "<group>"; };
                71B28424203CEC0B0036AA5D /* JSCSSAnimation.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSCSSAnimation.cpp; sourceTree = "<group>"; };
                                71556CAB1F9F099C00E78D08 /* KeyframeEffect.idl */,
                                71247E321FEA5F7F008C08CE /* KeyframeEffectOptions.h */,
                                71247E301FEA5F7E008C08CE /* KeyframeEffectOptions.idl */,
+                               71A58195236F466500D81A24 /* KeyframeEffectStack.cpp */,
+                               71A58193236F466400D81A24 /* KeyframeEffectStack.h */,
                                7120733D216DFAF100C78329 /* OptionalEffectTiming.h */,
                                7120733F216DFAF200C78329 /* OptionalEffectTiming.idl */,
                                712BE47E1FE8649D002031CC /* PlaybackDirection.h */,
                                77A17AA712F28B2A004E02F6 /* JSOESVertexArrayObject.h in Headers */,
                                FDF6BAF9134A4C9800822920 /* JSOfflineAudioCompletionEvent.h in Headers */,
                                FDA9326716703BA9008982DC /* JSOfflineAudioContext.h in Headers */,
-                               E45BA6AA2374926C004DFC07 /* MatchedDeclarationsCache.h in Headers */,
                                314877E61FAAB02500C05759 /* JSOffscreenCanvas.h in Headers */,
                                3140C5271FDF558200D2A873 /* JSOffscreenCanvasRenderingContext2D.h in Headers */,
                                57E233651DC7DB1F00F28D01 /* JsonWebKey.h in Headers */,
                                71247E391FEA5F86008C08CE /* KeyframeAnimationOptions.h in Headers */,
                                71556CB41F9F09BA00E78D08 /* KeyframeEffect.h in Headers */,
                                71247E3A1FEA5F86008C08CE /* KeyframeEffectOptions.h in Headers */,
+                               71A58196236F467600D81A24 /* KeyframeEffectStack.h in Headers */,
                                BC5EBA110E823E4700B25965 /* KeyframeList.h in Headers */,
                                E15FF7D518C9553800FE4C87 /* KeypressCommand.h in Headers */,
                                450CEBF115073BBE002BB149 /* LabelableElement.h in Headers */,
                                93309DF8099E64920056E581 /* markup.h in Headers */,
                                9728C3141268E4390041E89B /* MarkupAccumulator.h in Headers */,
                                00C60E3F13D76D7E0092A275 /* MarkupTokenizerInlines.h in Headers */,
+                               E45BA6AA2374926C004DFC07 /* MatchedDeclarationsCache.h in Headers */,
                                FABE72F51059C1EB00D888CC /* MathMLAnnotationElement.h in Headers */,
                                FABE72F51059C1EB00D999DD /* MathMLElement.h in Headers */,
                                44A28AAC12DFB8AC00AE923B /* MathMLElementFactory.h in Headers */,
index f3ff3e98892ce24e44a8c7b1e7990aafb3efce2b..e1eced4d8efd91fe73ed2a4a9e5c3abc08dfecb4 100644 (file)
@@ -47,7 +47,7 @@
 
 namespace WebCore {
 
-class AnimationEffect : public RefCounted<AnimationEffect> {
+class AnimationEffect : public RefCounted<AnimationEffect>, public CanMakeWeakPtr<AnimationEffect> {
 public:
     virtual ~AnimationEffect();
 
@@ -62,9 +62,10 @@ public:
     virtual void invalidate() = 0;
     virtual void animationDidSeek() = 0;
     virtual void animationSuspensionStateDidChange(bool) = 0;
+    virtual void animationTimelineDidChange(AnimationTimeline*) = 0;
 
     WebAnimation* animation() const { return m_animation.get(); }
-    void setAnimation(WebAnimation* animation) { m_animation = makeWeakPtr(animation); }
+    virtual void setAnimation(WebAnimation* animation) { m_animation = makeWeakPtr(animation); }
 
     Seconds delay() const { return m_delay; }
     void setDelay(const Seconds&);
index 0066bd0feb9d0415096ea933185f4f3e8cfc217a..c4dea3cdfead60ebc1708e86e1e3637067eb2aed 100644 (file)
@@ -36,6 +36,7 @@
 #include "DocumentTimeline.h"
 #include "Element.h"
 #include "KeyframeEffect.h"
+#include "KeyframeEffectStack.h"
 #include "RenderStyle.h"
 #include "RenderView.h"
 #include "StylePropertyShorthand.h"
@@ -75,8 +76,10 @@ void AnimationTimeline::removeAnimation(WebAnimation& animation)
     ASSERT(!animation.timeline() || animation.timeline() == this);
     m_animations.remove(&animation);
     if (is<KeyframeEffect>(animation.effect())) {
-        if (auto* target = downcast<KeyframeEffect>(animation.effect())->target())
+        if (auto* target = downcast<KeyframeEffect>(animation.effect())->target()) {
             animationWasRemovedFromElement(animation, *target);
+            target->ensureKeyframeEffectStack().removeEffect(*downcast<KeyframeEffect>(animation.effect()));
+        }
     }
 }
 
@@ -243,12 +246,15 @@ static bool shouldConsiderAnimation(Element& element, const Animation& animation
 
 void AnimationTimeline::updateCSSAnimationsForElement(Element& element, const RenderStyle* currentStyle, const RenderStyle& afterChangeStyle)
 {
+    Vector<String> animationNames;
+
     // In case this element is newly getting a "display: none" we need to cancel all of its animations and disregard new ones.
     if (currentStyle && currentStyle->hasAnimations() && currentStyle->display() != DisplayType::None && afterChangeStyle.display() == DisplayType::None) {
         if (m_elementToCSSAnimationByName.contains(&element)) {
             for (const auto& cssAnimationsByNameMapItem : m_elementToCSSAnimationByName.take(&element))
                 cancelDeclarativeAnimation(*cssAnimationsByNameMapItem.value);
         }
+        element.ensureKeyframeEffectStack().setCSSAnimationNames(WTFMove(animationNames));
         return;
     }
 
@@ -275,6 +281,7 @@ void AnimationTimeline::updateCSSAnimationsForElement(Element& element, const Re
         for (size_t i = 0; i < currentAnimations->size(); ++i) {
             auto& currentAnimation = currentAnimations->animation(i);
             auto& name = currentAnimation.name();
+            animationNames.append(name);
             if (namesOfPreviousAnimations.contains(name)) {
                 // We've found the name of this animation in our list of previous animations, this means we've already
                 // created a CSSAnimation object for it and need to ensure that this CSSAnimation is backed by the current
@@ -296,6 +303,8 @@ void AnimationTimeline::updateCSSAnimationsForElement(Element& element, const Re
         if (auto animation = cssAnimationsByName.take(nameOfAnimationToRemove))
             cancelDeclarativeAnimation(*animation);
     }
+
+    element.ensureKeyframeEffectStack().setCSSAnimationNames(WTFMove(animationNames));
 }
 
 RefPtr<WebAnimation> AnimationTimeline::cssAnimationForElementAndProperty(Element& element, CSSPropertyID property)
index efc6657abb8d0f4d6aa15abaf76f7f3f42540d2f..b0c481ef5c61688622ac2923afcbb793bd4c8711 100644 (file)
@@ -28,7 +28,6 @@
 
 #include "AnimationPlaybackEvent.h"
 #include "CSSAnimation.h"
-#include "CSSPropertyAnimation.h"
 #include "CSSTransition.h"
 #include "DOMWindow.h"
 #include "DeclarativeAnimation.h"
@@ -673,32 +672,6 @@ void DocumentTimeline::applyPendingAcceleratedAnimations()
     }
 }
 
-bool DocumentTimeline::resolveAnimationsForElement(Element& element, RenderStyle& targetStyle)
-{
-    bool hasNonAcceleratedAnimationProperty = false;
-
-    for (const auto& animation : animationsForElement(element)) {
-        animation->resolve(targetStyle);
-
-        if (hasNonAcceleratedAnimationProperty)
-            continue;
-
-        auto* effect = animation->effect();
-        if (!effect || !is<KeyframeEffect>(effect))
-            continue;
-
-        auto* keyframeEffect = downcast<KeyframeEffect>(effect);
-        for (auto cssPropertyId : keyframeEffect->animatedProperties()) {
-            if (!CSSPropertyAnimation::animationOfPropertyIsAccelerated(cssPropertyId)) {
-                hasNonAcceleratedAnimationProperty = true;
-                break;
-            }
-        }
-    }
-
-    return !hasNonAcceleratedAnimationProperty;
-}
-
 bool DocumentTimeline::runningAnimationsForElementAreAllAccelerated(Element& element) const
 {
     return m_elementsWithRunningAcceleratedAnimations.contains(&element);
index 43007cc81e07cbb592132225b501e91a028a560a..7c481f27ba4a11e7a496c522ec19bd33096d79e5 100644 (file)
@@ -67,7 +67,6 @@ public:
     void animationAcceleratedRunningStateDidChange(WebAnimation&);
     void applyPendingAcceleratedAnimations();
     bool runningAnimationsForElementAreAllAccelerated(Element&) const;
-    bool resolveAnimationsForElement(Element&, RenderStyle&);
     void detachFromDocument();
 
     void enqueueAnimationPlaybackEvent(AnimationPlaybackEvent&);
index 69edd84508f5569ddec25f3013fe8781129af4c4..75776c5cde5fdad60a9a7cb373174cb33312fee9 100644 (file)
@@ -44,6 +44,7 @@
 #include "JSCompositeOperationOrAuto.h"
 #include "JSDOMConvert.h"
 #include "JSKeyframeEffect.h"
+#include "KeyframeEffectStack.h"
 #include "RenderBox.h"
 #include "RenderBoxModelObject.h"
 #include "RenderElement.h"
@@ -990,6 +991,29 @@ void KeyframeEffect::computeStackingContextImpact()
     }
 }
 
+void KeyframeEffect::animationTimelineDidChange(AnimationTimeline* timeline)
+{
+    if (!m_target)
+        return;
+
+    if (timeline)
+        m_target->ensureKeyframeEffectStack().addEffect(*this);
+    else
+        m_target->ensureKeyframeEffectStack().removeEffect(*this);
+}
+
+void KeyframeEffect::setAnimation(WebAnimation* animation)
+{
+    bool animationChanged = animation != this->animation();
+    AnimationEffect::setAnimation(animation);
+    if (m_target && animationChanged) {
+        if (animation)
+            m_target->ensureKeyframeEffectStack().addEffect(*this);
+        else
+            m_target->ensureKeyframeEffectStack().removeEffect(*this);
+    }
+}
+
 void KeyframeEffect::setTarget(RefPtr<Element>&& newTarget)
 {
     if (m_target == newTarget)
@@ -1009,6 +1033,11 @@ void KeyframeEffect::setTarget(RefPtr<Element>&& newTarget)
     // Likewise, we need to invalidate styles on the previous target so that
     // any animated styles are removed immediately.
     invalidateElement(previousTarget.get());
+
+    if (previousTarget)
+        previousTarget->ensureKeyframeEffectStack().removeEffect(*this);
+    if (m_target)
+        m_target->ensureKeyframeEffectStack().addEffect(*this);
 }
 
 void KeyframeEffect::apply(RenderStyle& targetStyle)
index 17758aa8efd35bb623489eaa047fe16f3d737360..3cd4879d27fd604391f9f5c52a5f816f65b66709 100644 (file)
@@ -114,10 +114,13 @@ public:
     void invalidate() override;
     void animationDidSeek() final;
     void animationSuspensionStateDidChange(bool) final;
+    void animationTimelineDidChange(AnimationTimeline*) final;
     void applyPendingAcceleratedActions();
     bool isRunningAccelerated() const { return m_lastRecordedAcceleratedAction != AcceleratedAction::Stop; }
     bool hasPendingAcceleratedAction() const { return !m_pendingAcceleratedActions.isEmpty() && isRunningAccelerated(); }
 
+    void setAnimation(WebAnimation*) final;
+
     RenderElement* renderer() const override;
     const RenderStyle& currentStyle() const override;
     bool isAccelerated() const override { return m_shouldRunAccelerated; }
diff --git a/Source/WebCore/animation/KeyframeEffectStack.cpp b/Source/WebCore/animation/KeyframeEffectStack.cpp
new file mode 100644 (file)
index 0000000..747464e
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "KeyframeEffectStack.h"
+
+#include "CSSAnimation.h"
+#include "CSSTransition.h"
+#include "KeyframeEffect.h"
+#include "WebAnimation.h"
+
+namespace WebCore {
+
+KeyframeEffectStack::KeyframeEffectStack()
+{
+}
+
+KeyframeEffectStack::~KeyframeEffectStack()
+{
+    ASSERT(m_effects.isEmpty());
+}
+
+void KeyframeEffectStack::addEffect(KeyframeEffect& effect)
+{
+    // To qualify for membership in an effect stack, an effect must have a target, an animation and a timeline.
+    // This method will be called in WebAnimation and KeyframeEffect as those properties change.
+    if (!effect.target() || !effect.animation() || !effect.animation()->timeline())
+        return;
+
+    m_effects.append(makeWeakPtr(&effect));
+    m_isSorted = false;
+}
+
+void KeyframeEffectStack::removeEffect(KeyframeEffect& effect)
+{
+    m_effects.removeFirst(&effect);
+}
+
+Vector<WeakPtr<KeyframeEffect>> KeyframeEffectStack::sortedEffects()
+{
+    ensureEffectsAreSorted();
+    return m_effects;
+}
+
+void KeyframeEffectStack::ensureEffectsAreSorted()
+{
+    if (m_isSorted || m_effects.size() < 2)
+        return;
+
+    std::sort(m_effects.begin(), m_effects.end(), [&](auto& lhs, auto& rhs) {
+        auto* lhsAnimation = lhs->animation();
+        auto* rhsAnimation = rhs->animation();
+
+        ASSERT(lhsAnimation);
+        ASSERT(rhsAnimation);
+
+        // CSS Transitions sort first.
+        bool lhsIsCSSTransition = is<CSSTransition>(lhsAnimation);
+        bool rhsIsCSSTransition = is<CSSTransition>(rhsAnimation);
+        if (lhsIsCSSTransition || rhsIsCSSTransition) {
+            if (lhsIsCSSTransition == rhsIsCSSTransition) {
+                // Sort transitions first by their generation time, and then by transition-property.
+                // https://drafts.csswg.org/css-transitions-2/#animation-composite-order
+                auto* lhsCSSTransition = downcast<CSSTransition>(lhsAnimation);
+                auto* rhsCSSTransition = downcast<CSSTransition>(rhsAnimation);
+                if (lhsCSSTransition->generationTime() != rhsCSSTransition->generationTime())
+                    return lhsCSSTransition->generationTime() < rhsCSSTransition->generationTime();
+                return lhsCSSTransition->transitionProperty().utf8() < rhsCSSTransition->transitionProperty().utf8();
+            }
+            return !rhsIsCSSTransition;
+        }
+
+        // CSS Animations sort next.
+        bool lhsIsCSSAnimation = is<CSSAnimation>(lhsAnimation);
+        bool rhsIsCSSAnimation = is<CSSAnimation>(rhsAnimation);
+        if (lhsIsCSSAnimation || rhsIsCSSAnimation) {
+            if (lhsIsCSSAnimation == rhsIsCSSAnimation) {
+                // https://drafts.csswg.org/css-animations-2/#animation-composite-order
+                // Sort A and B based on their position in the computed value of the animation-name property of the (common) owning element.
+                auto& lhsCSSAnimationName = downcast<CSSAnimation>(lhsAnimation)->backingAnimation().name();
+                auto& rhsCSSAnimationName = downcast<CSSAnimation>(rhsAnimation)->backingAnimation().name();
+
+                for (auto& animationName : m_cssAnimationNames) {
+                    if (animationName == lhsCSSAnimationName)
+                        return true;
+                    if (animationName == rhsCSSAnimationName)
+                        return false;
+                }
+                // We should have found either of those CSS animations in the CSS animations list.
+                ASSERT_NOT_REACHED();
+            }
+            return !rhsIsCSSAnimation;
+        }
+
+        // JS-originated animations sort last based on their position in the global animation list.
+        // https://drafts.csswg.org/web-animations-1/#animation-composite-order
+        return lhsAnimation->globalPosition() < rhsAnimation->globalPosition();
+    });
+
+    m_isSorted = true;
+}
+
+void KeyframeEffectStack::setCSSAnimationNames(Vector<String>&& animationNames)
+{
+    m_cssAnimationNames = WTFMove(animationNames);
+    // Since the list of animation names has changed, the sorting order of the animation effects may have changed as well.
+    m_isSorted = false;
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/animation/KeyframeEffectStack.h b/Source/WebCore/animation/KeyframeEffectStack.h
new file mode 100644 (file)
index 0000000..e656828
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <wtf/Vector.h>
+#include <wtf/WeakPtr.h>
+
+namespace WebCore {
+
+class KeyframeEffect;
+
+class KeyframeEffectStack {
+    WTF_MAKE_FAST_ALLOCATED;
+public:
+    explicit KeyframeEffectStack();
+    ~KeyframeEffectStack();
+
+    void addEffect(KeyframeEffect&);
+    void removeEffect(KeyframeEffect&);
+    bool hasEffects() const { return !m_effects.isEmpty(); }
+    Vector<WeakPtr<KeyframeEffect>> sortedEffects();
+    void setCSSAnimationNames(Vector<String>&&);
+
+private:
+    void ensureEffectsAreSorted();
+
+    Vector<WeakPtr<KeyframeEffect>> m_effects;
+    Vector<String> m_cssAnimationNames;
+    bool m_isSorted { true };
+
+};
+
+} // namespace WebCore
index c23f9c0241610535e7558111d2787f18a8d01e7a..b91184eb120c539728fb7d0976235a5c7dc9d8ba 100644 (file)
@@ -237,6 +237,9 @@ void WebAnimation::setTimelineInternal(RefPtr<AnimationTimeline>&& timeline)
         m_timeline->removeAnimation(*this);
 
     m_timeline = WTFMove(timeline);
+
+    if (m_effect)
+        m_effect->animationTimelineDidChange(m_timeline.get());
 }
 
 void WebAnimation::effectTargetDidChange(Element* previousTarget, Element* newTarget)
@@ -1258,8 +1261,12 @@ void WebAnimation::persist()
     auto previousReplaceState = std::exchange(m_replaceState, ReplaceState::Persisted);
 
     if (previousReplaceState == ReplaceState::Removed && m_timeline) {
-        if (is<KeyframeEffect>(m_effect))
-            m_timeline->animationWasAddedToElement(*this, *downcast<KeyframeEffect>(m_effect.get())->target());
+        if (is<KeyframeEffect>(m_effect)) {
+            auto& keyframeEffect = downcast<KeyframeEffect>(*m_effect);
+            auto& target = *keyframeEffect.target();
+            m_timeline->animationWasAddedToElement(*this, target);
+            target.ensureKeyframeEffectStack().addEffect(keyframeEffect);
+        }
     }
 }
 
index 3887a11911487be2229b0feea0fafbb50fcf7cdb..a8266817d2394b93ef4e263eb718879ade2eec70 100644 (file)
@@ -31,6 +31,7 @@
 #include "AttributeChangeInvalidation.h"
 #include "CSSAnimationController.h"
 #include "CSSParser.h"
+#include "CSSPropertyAnimation.h"
 #include "Chrome.h"
 #include "ChromeClient.h"
 #include "ClassChangeInvalidation.h"
@@ -3677,6 +3678,47 @@ IntersectionObserverData* Element::intersectionObserverData()
 }
 #endif
 
+KeyframeEffectStack& Element::ensureKeyframeEffectStack()
+{
+    auto& rareData = ensureElementRareData();
+    if (!rareData.keyframeEffectStack())
+        rareData.setKeyframeEffectStack(makeUnique<KeyframeEffectStack>());
+    return *rareData.keyframeEffectStack();
+}
+
+bool Element::hasKeyframeEffects() const
+{
+    if (!hasRareData())
+        return false;
+
+    auto* keyframeEffectStack = elementRareData()->keyframeEffectStack();
+    return keyframeEffectStack && keyframeEffectStack->hasEffects();
+}
+
+bool Element::applyKeyframeEffects(RenderStyle& targetStyle)
+{
+    bool hasNonAcceleratedAnimationProperty = false;
+
+    for (const auto& effect : ensureKeyframeEffectStack().sortedEffects()) {
+        ASSERT(effect->animation());
+        effect->animation()->resolve(targetStyle);
+
+        if (hasNonAcceleratedAnimationProperty)
+            continue;
+
+        // FIXME: https://bugs.webkit.org/show_bug.cgi?id=204009
+        // KeyframeEffectStack and KeyframeEffect should indicate whether it only contains accelerated animation properties
+        for (auto cssPropertyId : effect->animatedProperties()) {
+            if (!CSSPropertyAnimation::animationOfPropertyIsAccelerated(cssPropertyId)) {
+                hasNonAcceleratedAnimationProperty = true;
+                break;
+            }
+        }
+    }
+
+    return !hasNonAcceleratedAnimationProperty;
+}
+
 #if ENABLE(RESIZE_OBSERVER)
 void Element::disconnectFromResizeObservers()
 {
index 5ee046ef597241a749b0514b07a3502e685c2c0f..42a7f225d7a3dbaee7957ca0d8d76dc4f9a1e3dc 100644 (file)
@@ -47,6 +47,7 @@ class Frame;
 class HTMLDocument;
 class IntSize;
 class JSCustomElementInterface;
+class KeyframeEffectStack;
 class KeyboardEvent;
 class Locale;
 class PlatformKeyboardEvent;
@@ -489,6 +490,10 @@ public:
     void setHasCSSAnimation();
     void clearHasCSSAnimation();
 
+    KeyframeEffectStack& ensureKeyframeEffectStack();
+    bool hasKeyframeEffects() const;
+    bool applyKeyframeEffects(RenderStyle&);
+
 #if ENABLE(FULLSCREEN_API)
     WEBCORE_EXPORT bool containsFullScreenElement() const;
     void setContainsFullScreenElement(bool);
index b04ed8a6cfa52f297d2472b3df26596124d9fc6d..8aaeaec56f017cba372197acd93ca83f967113e5 100644 (file)
@@ -43,7 +43,7 @@ struct SameSizeAsElementRareData : NodeRareData {
 #endif
     LayoutSize sizeForResizing;
     IntPoint savedLayerScrollPosition;
-    void* pointers[10];
+    void* pointers[11];
 #if ENABLE(INTERSECTION_OBSERVER)
     void* intersectionObserverData;
 #endif
index b8469a09888f504cd61ccce1cb021724ad404e4e..b7a686353d7c68849a22bb6f9810e0ef6e23b490 100644 (file)
@@ -25,6 +25,7 @@
 #include "DOMTokenList.h"
 #include "DatasetDOMStringMap.h"
 #include "IntersectionObserver.h"
+#include "KeyframeEffectStack.h"
 #include "NamedNodeMap.h"
 #include "NodeRareData.h"
 #include "PseudoElement.h"
@@ -100,6 +101,9 @@ public:
     bool hasCSSAnimation() const { return m_hasCSSAnimation; }
     void setHasCSSAnimation(bool value) { m_hasCSSAnimation = value; }
 
+    KeyframeEffectStack* keyframeEffectStack() { return m_keyframeEffectStack.get(); }
+    void setKeyframeEffectStack(std::unique_ptr<KeyframeEffectStack>&& keyframeEffectStack) { m_keyframeEffectStack = WTFMove(keyframeEffectStack); }
+
     bool hasElementIdentifier() const { return m_hasElementIdentifier; }
     void setHasElementIdentifier(bool value) { m_hasElementIdentifier = value; }
 
@@ -186,6 +190,8 @@ private:
     std::unique_ptr<ResizeObserverData> m_resizeObserverData;
 #endif
 
+    std::unique_ptr<KeyframeEffectStack> m_keyframeEffectStack;
+
     RefPtr<PseudoElement> m_beforePseudoElement;
     RefPtr<PseudoElement> m_afterPseudoElement;
 
index 44ef943f2c03816c6ba861aed70dfa054925d350..7d1dc35fca34746fa330f6bfeb5ab73816a2a0cb 100644 (file)
@@ -317,11 +317,11 @@ ElementUpdate TreeResolver::createAnimatedElementUpdate(std::unique_ptr<RenderSt
         }
     }
 
-    if (auto timeline = m_document.existingTimeline()) {
-        // Now we can update all Web animations, which will include CSS Animations as well
-        // as animations created via the JS API.
+    // Now we can update all Web animations, which will include CSS Animations as well
+    // as animations created via the JS API.
+    if (element.hasKeyframeEffects()) {
         auto animatedStyle = RenderStyle::clonePtr(*newStyle);
-        shouldRecompositeLayer = timeline->resolveAnimationsForElement(element, *animatedStyle);
+        shouldRecompositeLayer = element.applyKeyframeEffects(*animatedStyle);
         newStyle = WTFMove(animatedStyle);
     }