Implement Scroll Container Animation Triggers
authordino@apple.com <dino@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 17 Mar 2015 19:01:46 +0000 (19:01 +0000)
committerdino@apple.com <dino@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 17 Mar 2015 19:01:46 +0000 (19:01 +0000)
https://bugs.webkit.org/show_bug.cgi?id=142732

Reviewed by Simon Fraser.

Source/WebCore:

Test: animations/trigger-container-scroll-simple.html

Basic implementation of container-scroll. It only checks
the page scroll position for trigger values (not the scrolling
container in an overflow).

* css/CSSComputedStyleDeclaration.cpp: Add CSSPropertyWebkitAnimationTrigger
so that this property will appear in the inspector.

* page/FrameView.cpp:
(WebCore::FrameView::sendScrollEvent): If the page has scrolled, let the animation
controller know about it.

* page/animation/AnimationBase.cpp:
(WebCore::AnimationBase::updateStateMachine): Whitespace fix.
(WebCore::AnimationBase::fireAnimationEventsIfNeeded): If there is a trigger,
and the scroll position is past it, then tell the state machine that
we should start.
(WebCore::AnimationBase::timeToNextService): Use the scroll position as
an input to the update timer if a trigger is involved.

* page/animation/AnimationController.cpp:
(WebCore::AnimationControllerPrivate::ensureCompositeAnimation): Add whitespace.
(WebCore::AnimationControllerPrivate::scrollWasUpdated): Call updateAnimations.
(WebCore::AnimationController::scrollWasUpdated): Call into AnimationControllerPrivate.
* page/animation/AnimationController.h:
* page/animation/AnimationControllerPrivate.h:

* page/animation/CompositeAnimation.cpp: Keep a record of whether we have a scroll
triggered animation.
(WebCore::CompositeAnimation::CompositeAnimation):
(WebCore::CompositeAnimation::updateKeyframeAnimations):
* page/animation/CompositeAnimation.h:
(WebCore::CompositeAnimation::hasScrollTriggeredAnimation):
* platform/animation/Animation.cpp:
(WebCore::Animation::operator=):

LayoutTests:

Test that checks if an animation only triggers when the page
is scrolled.

* animations/trigger-container-scroll-simple-expected.txt: Added.
* animations/trigger-container-scroll-simple.html: Added.

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

13 files changed:
LayoutTests/ChangeLog
LayoutTests/animations/trigger-container-scroll-simple-expected.txt [new file with mode: 0644]
LayoutTests/animations/trigger-container-scroll-simple.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/css/CSSComputedStyleDeclaration.cpp
Source/WebCore/page/FrameView.cpp
Source/WebCore/page/animation/AnimationBase.cpp
Source/WebCore/page/animation/AnimationController.cpp
Source/WebCore/page/animation/AnimationController.h
Source/WebCore/page/animation/AnimationControllerPrivate.h
Source/WebCore/page/animation/CompositeAnimation.cpp
Source/WebCore/page/animation/CompositeAnimation.h
Source/WebCore/platform/animation/Animation.cpp

index 3d3d503..e4cc665 100644 (file)
@@ -1,3 +1,16 @@
+2015-03-17  Dean Jackson  <dino@apple.com>
+
+        Implement Scroll Container Animation Triggers
+        https://bugs.webkit.org/show_bug.cgi?id=142732
+
+        Reviewed by Simon Fraser.
+
+        Test that checks if an animation only triggers when the page
+        is scrolled.
+
+        * animations/trigger-container-scroll-simple-expected.txt: Added.
+        * animations/trigger-container-scroll-simple.html: Added.
+
 2015-03-17  Brent Fulgham  <bfulgham@apple.com>
 
         [Win] Skip some IndexDB tests that don't apply on Windows.
diff --git a/LayoutTests/animations/trigger-container-scroll-simple-expected.txt b/LayoutTests/animations/trigger-container-scroll-simple-expected.txt
new file mode 100644 (file)
index 0000000..a3ad7e2
--- /dev/null
@@ -0,0 +1,6 @@
+This element should begin animating only when the page scrolls to 20px from the top. The animation is almost instantaneous, so it will snap to its final position. Remember to scroll to the top of the page before reloading!
+
+Value before animation is applied: auto (should be auto)
+Value with animation but no scroll: 0px (should be 0px)
+Value with animation after scroll: 100px (should be 100px)
+
diff --git a/LayoutTests/animations/trigger-container-scroll-simple.html b/LayoutTests/animations/trigger-container-scroll-simple.html
new file mode 100644 (file)
index 0000000..fe49258
--- /dev/null
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<style>
+body {
+    height: 2000px;
+}
+
+#box {
+    position: relative;
+    width: 20px;
+    height: 20px;
+    background-color: blue;
+}
+
+.animating {
+    animation-name: slide;
+    animation-duration: 1ms;
+    animation-fill-mode: forwards;
+    -webkit-animation-trigger: container-scroll(20px);
+}
+
+@-webkit-keyframes slide {
+  from {
+      left: 0px;
+  }
+  to {
+      left: 100px;
+  }
+}
+</style>
+<script>
+
+var results;
+var box;
+
+if (window.testRunner) {
+    window.testRunner.dumpAsText();
+    window.testRunner.waitUntilDone();
+}
+
+function runTest() {
+    results = document.getElementById("results");
+    box = document.getElementById("box");
+    results.innerHTML = "Value before animation is applied: " + window.getComputedStyle(box).left + " (should be auto)<br>";
+    box.className = "animating";
+    setTimeout(checkValueWithoutScroll, 0);
+}
+
+function checkValueWithoutScroll() {
+    results.innerHTML += "Value with animation but no scroll: " + window.getComputedStyle(box).left + " (should be 0px)<br>";
+    window.scrollTo(0, 30);
+    setTimeout(checkValueWithScroll, 0);
+}
+
+function checkValueWithScroll() {
+    results.innerHTML += "Value with animation after scroll: " + window.getComputedStyle(box).left + " (should be 100px)<br>";
+    if (window.testRunner)
+        window.testRunner.notifyDone();
+}
+
+window.addEventListener("load", runTest, false);
+
+</script>
+
+<p>This element should begin animating only when the page scrolls to 20px from
+the top. The animation is almost instantaneous, so it will snap to its final
+position. Remember to scroll to the top of the page before reloading!</p>
+<div id="box"></div>
+
+<div id="results"></div>
index a614a14..5384a6b 100644 (file)
@@ -1,3 +1,47 @@
+2015-03-17  Dean Jackson  <dino@apple.com>
+
+        Implement Scroll Container Animation Triggers
+        https://bugs.webkit.org/show_bug.cgi?id=142732
+
+        Reviewed by Simon Fraser.
+
+        Test: animations/trigger-container-scroll-simple.html
+
+        Basic implementation of container-scroll. It only checks
+        the page scroll position for trigger values (not the scrolling
+        container in an overflow).
+
+        * css/CSSComputedStyleDeclaration.cpp: Add CSSPropertyWebkitAnimationTrigger
+        so that this property will appear in the inspector.
+
+        * page/FrameView.cpp:
+        (WebCore::FrameView::sendScrollEvent): If the page has scrolled, let the animation
+        controller know about it.
+
+        * page/animation/AnimationBase.cpp:
+        (WebCore::AnimationBase::updateStateMachine): Whitespace fix.
+        (WebCore::AnimationBase::fireAnimationEventsIfNeeded): If there is a trigger,
+        and the scroll position is past it, then tell the state machine that
+        we should start.
+        (WebCore::AnimationBase::timeToNextService): Use the scroll position as
+        an input to the update timer if a trigger is involved.
+
+        * page/animation/AnimationController.cpp:
+        (WebCore::AnimationControllerPrivate::ensureCompositeAnimation): Add whitespace.
+        (WebCore::AnimationControllerPrivate::scrollWasUpdated): Call updateAnimations.
+        (WebCore::AnimationController::scrollWasUpdated): Call into AnimationControllerPrivate.
+        * page/animation/AnimationController.h:
+        * page/animation/AnimationControllerPrivate.h:
+
+        * page/animation/CompositeAnimation.cpp: Keep a record of whether we have a scroll
+        triggered animation.
+        (WebCore::CompositeAnimation::CompositeAnimation):
+        (WebCore::CompositeAnimation::updateKeyframeAnimations):
+        * page/animation/CompositeAnimation.h:
+        (WebCore::CompositeAnimation::hasScrollTriggeredAnimation):
+        * platform/animation/Animation.cpp:
+        (WebCore::Animation::operator=):
+
 2015-03-17  Simon Fraser  <simon.fraser@apple.com>
 
         Move some code from LogicalSelectionOffsetCaches into RenderElement
index f867157..2c11088 100644 (file)
@@ -245,6 +245,7 @@ static const CSSPropertyID computedProperties[] = {
     CSSPropertyWebkitAnimationName,
     CSSPropertyWebkitAnimationPlayState,
     CSSPropertyWebkitAnimationTimingFunction,
+    CSSPropertyWebkitAnimationTrigger,
     CSSPropertyWebkitAppearance,
     CSSPropertyWebkitBackfaceVisibility,
     CSSPropertyWebkitBackgroundClip,
index 1dd897b..cb415d0 100644 (file)
@@ -4374,6 +4374,9 @@ void FrameView::sendScrollEvent()
 {
     frame().eventHandler().sendScrollEvent();
     frame().eventHandler().dispatchFakeMouseMoveEventSoon();
+#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
+    frame().animation().scrollWasUpdated();
+#endif
 }
 
 void FrameView::removeChild(Widget& widget)
index 024a7ec..eb4c424 100644 (file)
@@ -40,6 +40,7 @@
 #include "Logging.h"
 #include "RenderBox.h"
 #include "RenderStyle.h"
+#include "RenderView.h"
 #include "UnitBezier.h"
 #include <algorithm>
 #include <wtf/CurrentTime.h>
@@ -198,6 +199,7 @@ void AnimationBase::updateStateMachine(AnimationStateInput input, double param)
     switch (m_animationState) {
         case AnimationState::New:
             ASSERT(input == AnimationStateInput::StartAnimation || input == AnimationStateInput::PlayStateRunning || input == AnimationStateInput::PlayStatePaused);
+
             if (input == AnimationStateInput::StartAnimation || input == AnimationStateInput::PlayStateRunning) {
                 m_requestedStartTime = beginAnimationUpdateTime();
                 LOG(Animations, "%p AnimationState %s -> StartWaitTimer", this, nameForState(m_animationState));
@@ -457,12 +459,29 @@ void AnimationBase::fireAnimationEventsIfNeeded()
     
     // Check for start timeout
     if (m_animationState == AnimationState::StartWaitTimer) {
+#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
+        if (m_animation->trigger() && m_animation->trigger()->isScrollAnimationTrigger()) {
+            if (m_object) {
+                LayoutSize offset = m_object->view().frameView().scrollOffsetForFixedPosition();
+                ScrollAnimationTrigger* scrollTrigger = static_cast<ScrollAnimationTrigger*>(m_animation->trigger().get());
+                if (offset.height().toFloat() > scrollTrigger->startValue().value())
+                    updateStateMachine(AnimationStateInput::StartTimerFired, 0);
+            }
+
+            return;
+        }
+#endif
         if (beginAnimationUpdateTime() - m_requestedStartTime >= m_animation->delay())
             updateStateMachine(AnimationStateInput::StartTimerFired, 0);
         return;
     }
     
     double elapsedDuration = beginAnimationUpdateTime() - m_startTime;
+#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
+    if (m_animation->trigger() && m_animation->trigger()->isScrollAnimationTrigger())
+        elapsedDuration = getElapsedTime();
+#endif
+
     // FIXME: we need to ensure that elapsedDuration is never < 0. If it is, this suggests that
     // we had a recalcStyle() outside of beginAnimationUpdate()/endAnimationUpdate().
     // Also check in getTimeToNextEvent().
@@ -521,6 +540,17 @@ double AnimationBase::timeToNextService()
         return -1;
     
     if (m_animationState == AnimationState::StartWaitTimer) {
+#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
+        if (m_animation->trigger()->isScrollAnimationTrigger()) {
+            if (m_object) {
+                float currentScrollOffset = m_object->view().frameView().scrollOffsetForFixedPosition().height().toFloat();
+                ScrollAnimationTrigger* scrollTrigger = static_cast<ScrollAnimationTrigger*>(m_animation->trigger().get());
+                if (currentScrollOffset >= scrollTrigger->startValue().value() && (!scrollTrigger->hasEndValue() || currentScrollOffset <= scrollTrigger->endValue().value()))
+                    return 0;
+            }
+            return -1;
+        }
+#endif
         double timeFromNow = m_animation->delay() - (beginAnimationUpdateTime() - m_requestedStartTime);
         return std::max(timeFromNow, 0.0);
     }
@@ -672,7 +702,7 @@ double AnimationBase::beginAnimationUpdateTime() const
 
 double AnimationBase::getElapsedTime() const
 {
-    if (paused())    
+    if (paused())
         return m_pauseTime - m_startTime;
     if (m_startTime <= 0)
         return 0;
index 8580063..6452a89 100644 (file)
@@ -92,6 +92,7 @@ CompositeAnimation& AnimationControllerPrivate::ensureCompositeAnimation(RenderE
         result.iterator->value = CompositeAnimation::create(this);
         renderer.setIsCSSAnimating(true);
     }
+
     return *result.iterator->value;
 }
 
@@ -514,6 +515,13 @@ void AnimationControllerPrivate::animationWillBeRemoved(AnimationBase* animation
     removeFromAnimationsWaitingForStartTimeResponse(animation);
 }
 
+#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
+void AnimationControllerPrivate::scrollWasUpdated()
+{
+    updateAnimations(CallSetChanged);
+}
+#endif
+
 AnimationController::AnimationController(Frame& frame)
     : m_data(std::make_unique<AnimationControllerPrivate>(frame))
 {
@@ -700,4 +708,11 @@ bool AnimationController::supportsAcceleratedAnimationOfProperty(CSSPropertyID p
     return CSSPropertyAnimation::animationOfPropertyIsAccelerated(property);
 }
 
+#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
+void AnimationController::scrollWasUpdated()
+{
+    m_data->scrollWasUpdated();
+}
+#endif
+
 } // namespace WebCore
index dec2d51..f5bc7c0 100644 (file)
@@ -86,6 +86,10 @@ public:
     
     static bool supportsAcceleratedAnimationOfProperty(CSSPropertyID);
 
+#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
+    void scrollWasUpdated();
+#endif
+
 private:
     const std::unique_ptr<AnimationControllerPrivate> m_data;
 };
index 9e0b8c6..0ee31d7 100644 (file)
@@ -117,6 +117,10 @@ public:
     bool allowsNewAnimationsWhileSuspended() const { return m_allowsNewAnimationsWhileSuspended; }
     void setAllowsNewAnimationsWhileSuspended(bool);
 
+#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
+    void scrollWasUpdated();
+#endif
+
 private:
     void animationTimerFired();
 
index 54c6668..adcc451 100644 (file)
@@ -43,6 +43,9 @@ namespace WebCore {
 
 CompositeAnimation::CompositeAnimation(AnimationControllerPrivate* animationController)
     : m_animationController(animationController)
+#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
+    , m_hasScrollTriggeredAnimation(false)
+#endif
 {
     m_suspended = animationController->isSuspended() && !animationController->allowsNewAnimationsWhileSuspended();
 }
@@ -222,7 +225,11 @@ void CompositeAnimation::updateKeyframeAnimations(RenderElement* renderer, Rende
         // Mark all existing animations as no longer active.
         for (AnimationNameMap::const_iterator it = m_keyframeAnimations.begin(); it != kfend; ++it)
             it->value->setIndex(-1);
-            
+
+#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
+        m_hasScrollTriggeredAnimation = false;
+#endif
+
         // Toss the animation order map.
         m_keyframeAnimationOrderMap.clear();
 
@@ -245,7 +252,12 @@ void CompositeAnimation::updateKeyframeAnimations(RenderElement* renderer, Rende
                     // If this animation is postActive, skip it so it gets removed at the end of this function.
                     if (keyframeAnim->postActive())
                         continue;
-                    
+
+#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
+                    if (animation.trigger()->isScrollAnimationTrigger())
+                        m_hasScrollTriggeredAnimation = true;
+#endif
+
                     // This one is still active.
 
                     // Animations match, but play states may differ. Update if needed.
@@ -265,6 +277,12 @@ void CompositeAnimation::updateKeyframeAnimations(RenderElement* renderer, Rende
                     for (auto it = keyframeAnim->keyframes().beginProperties(), end = keyframeAnim->keyframes().endProperties(); it != end; ++it)
                         LOG(Animations, "  property %s", getPropertyName(*it));
 #endif
+
+#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
+                    if (animation.trigger()->isScrollAnimationTrigger())
+                        m_hasScrollTriggeredAnimation = true;
+#endif
+
                     m_keyframeAnimations.set(keyframeAnim->name().impl(), keyframeAnim);
                 }
                 
index e681b68..503fc34 100644 (file)
@@ -80,6 +80,10 @@ public:
     bool pauseTransitionAtTime(CSSPropertyID, double);
     unsigned numberOfActiveAnimations() const;
 
+#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
+    bool hasScrollTriggeredAnimation() const { return m_hasScrollTriggeredAnimation; }
+#endif
+
 private:
     CompositeAnimation(AnimationControllerPrivate*);
 
@@ -87,13 +91,16 @@ private:
     void updateKeyframeAnimations(RenderElement*, RenderStyle* currentStyle, RenderStyle* targetStyle);
     
     typedef HashMap<int, RefPtr<ImplicitAnimation>> CSSPropertyTransitionsMap;
-    typedef HashMap<AtomicStringImpl*, RefPtr<KeyframeAnimation>>  AnimationNameMap;
+    typedef HashMap<AtomicStringImpl*, RefPtr<KeyframeAnimation>> AnimationNameMap;
 
     AnimationControllerPrivate* m_animationController;
     CSSPropertyTransitionsMap m_transitions;
     AnimationNameMap m_keyframeAnimations;
     Vector<AtomicStringImpl*> m_keyframeAnimationOrderMap;
     bool m_suspended;
+#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
+    bool m_hasScrollTriggeredAnimation;
+#endif
 };
 
 } // namespace WebCore
index ac050a1..1896cb3 100644 (file)
@@ -94,6 +94,9 @@ Animation& Animation::operator=(const Animation& o)
     m_delay = o.m_delay;
     m_duration = o.m_duration;
     m_timingFunction = o.m_timingFunction;
+#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
+    m_trigger = o.m_trigger;
+#endif
     m_direction = o.m_direction;
     m_fillMode = o.m_fillMode;
     m_playState = o.m_playState;