[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 5ddb763..4f2ca9a 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 84ede5c..9c639bc 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 13032ec..c401b46 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 f3ff3e9..e1eced4 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 0066bd0..c4dea3c 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 efc6657..b0c481e 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 43007cc..7c481f2 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 69edd84..75776c5 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 17758aa..3cd4879 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 c23f9c0..b91184e 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 3887a11..a826681 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 5ee046e..42a7f22 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 b04ed8a..8aaeaec 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 b8469a0..b7a6863 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 44ef943..7d1dc35 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);
     }