Home | History | Annotate | Download | only in css
      1 /*
      2  * Copyright (C) 2013 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 #include "config.h"
     32 #include "core/animation/css/CSSAnimations.h"
     33 
     34 #include "core/animation/DocumentTimeline.h"
     35 #include "core/animation/KeyframeAnimationEffect.h"
     36 #include "core/css/CSSKeyframeRule.h"
     37 #include "core/css/resolver/StyleResolver.h"
     38 #include "core/dom/AnimationEvent.h"
     39 #include "core/dom/Element.h"
     40 #include "core/dom/EventNames.h"
     41 #include "core/platform/animation/CSSAnimationDataList.h"
     42 #include "core/platform/animation/TimingFunction.h"
     43 #include "wtf/HashSet.h"
     44 
     45 namespace WebCore {
     46 
     47 void timingFromAnimationData(const CSSAnimationData* animationData, Timing& timing)
     48 {
     49     if (animationData->isDelaySet())
     50         timing.startDelay = animationData->delay();
     51     if (animationData->isDurationSet()) {
     52         timing.iterationDuration = animationData->duration();
     53         timing.hasIterationDuration = true;
     54     }
     55     if (animationData->isIterationCountSet()) {
     56         if (animationData->iterationCount() == CSSAnimationData::IterationCountInfinite)
     57             timing.iterationCount = std::numeric_limits<double>::infinity();
     58         else
     59             timing.iterationCount = animationData->iterationCount();
     60     }
     61     if (animationData->isTimingFunctionSet()) {
     62         if (!animationData->timingFunction()->isLinearTimingFunction())
     63             timing.timingFunction = animationData->timingFunction();
     64     } else {
     65         // CSS default is ease, default in model is linear.
     66         timing.timingFunction = CubicBezierTimingFunction::preset(CubicBezierTimingFunction::Ease);
     67     }
     68     if (animationData->isFillModeSet()) {
     69         switch (animationData->fillMode()) {
     70         case AnimationFillModeForwards:
     71             timing.fillMode = Timing::FillModeForwards;
     72             break;
     73         case AnimationFillModeBackwards:
     74             timing.fillMode = Timing::FillModeBackwards;
     75             break;
     76         case AnimationFillModeBoth:
     77             timing.fillMode = Timing::FillModeBoth;
     78             break;
     79         case AnimationFillModeNone:
     80             timing.fillMode = Timing::FillModeNone;
     81             break;
     82         default:
     83             ASSERT_NOT_REACHED();
     84         }
     85     }
     86     if (animationData->isDirectionSet()) {
     87         switch (animationData->direction()) {
     88         case CSSAnimationData::AnimationDirectionNormal:
     89             timing.direction = Timing::PlaybackDirectionNormal;
     90             break;
     91         case CSSAnimationData::AnimationDirectionAlternate:
     92             timing.direction = Timing::PlaybackDirectionAlternate;
     93             break;
     94         case CSSAnimationData::AnimationDirectionReverse:
     95             timing.direction = Timing::PlaybackDirectionReverse;
     96             break;
     97         case CSSAnimationData::AnimationDirectionAlternateReverse:
     98             timing.direction = Timing::PlaybackDirectionAlternateReverse;
     99             break;
    100         default:
    101             ASSERT_NOT_REACHED();
    102         }
    103     }
    104 }
    105 
    106 bool CSSAnimations::needsUpdate(const Element* element, const RenderStyle* style)
    107 {
    108     ActiveAnimations* activeAnimations = element->activeAnimations();
    109     const CSSAnimationDataList* animations = style->animations();
    110     const CSSAnimations* cssAnimations = activeAnimations ? activeAnimations->cssAnimations() : 0;
    111     EDisplay display = style->display();
    112     return (display != NONE && animations && animations->size()) || (cssAnimations && !cssAnimations->isEmpty());
    113 }
    114 
    115 PassOwnPtr<CSSAnimationUpdate> CSSAnimations::calculateUpdate(const Element* element, EDisplay display, const CSSAnimations* cssAnimations, const CSSAnimationDataList* animationDataList, StyleResolver* resolver)
    116 {
    117     OwnPtr<CSSAnimationUpdate> update;
    118     HashSet<StringImpl*> inactive;
    119     if (cssAnimations)
    120         for (AnimationMap::const_iterator iter = cssAnimations->m_animations.begin(); iter != cssAnimations->m_animations.end(); ++iter)
    121             inactive.add(iter->key);
    122 
    123     RefPtr<MutableStylePropertySet> newStyles;
    124     if (display != NONE) {
    125         for (size_t i = 0; animationDataList && i < animationDataList->size(); ++i) {
    126             const CSSAnimationData* animationData = animationDataList->animation(i);
    127             if (animationData->isNoneAnimation())
    128                 continue;
    129             ASSERT(animationData->isValidAnimation());
    130             AtomicString animationName(animationData->name());
    131 
    132             if (cssAnimations) {
    133                 AnimationMap::const_iterator existing(cssAnimations->m_animations.find(animationName.impl()));
    134                 if (existing != cssAnimations->m_animations.end()) {
    135                     inactive.remove(animationName.impl());
    136                     continue;
    137                 }
    138             }
    139 
    140             // If there's a delay, no styles will apply yet.
    141             if (animationData->isDelaySet() && animationData->delay()) {
    142                 RELEASE_ASSERT_WITH_MESSAGE(animationData->delay() > 0, "Web Animations not yet implemented: Negative delay");
    143                 continue;
    144             }
    145 
    146             const StylePropertySet* keyframeStyles = resolver->firstKeyframeStyles(element, animationName.impl());
    147             if (keyframeStyles) {
    148                 if (!update)
    149                     update = adoptPtr(new CSSAnimationUpdate());
    150                 update->addStyles(keyframeStyles);
    151             }
    152         }
    153     }
    154 
    155     if (!inactive.isEmpty() && !update)
    156         update = adoptPtr(new CSSAnimationUpdate());
    157     for (HashSet<StringImpl*>::const_iterator iter = inactive.begin(); iter != inactive.end(); ++iter)
    158         update->cancel(cssAnimations->m_animations.get(*iter));
    159 
    160     return update.release();
    161 }
    162 
    163 void CSSAnimations::update(Element* element, const RenderStyle* style)
    164 {
    165     const CSSAnimationDataList* animationDataList = style->animations();
    166     HashSet<StringImpl*> inactive;
    167     for (AnimationMap::const_iterator iter = m_animations.begin(); iter != m_animations.end(); ++iter)
    168         inactive.add(iter->key);
    169 
    170     if (style->display() != NONE) {
    171         for (size_t i = 0; animationDataList && i < animationDataList->size(); ++i) {
    172             const CSSAnimationData* animationData = animationDataList->animation(i);
    173             if (animationData->isNoneAnimation())
    174                 continue;
    175             ASSERT(animationData->isValidAnimation());
    176             AtomicString animationName(animationData->name());
    177 
    178             AnimationMap::const_iterator existing(m_animations.find(animationName.impl()));
    179             if (existing != m_animations.end()) {
    180                 bool paused = animationData->playState() == AnimPlayStatePaused;
    181                 existing->value->setPaused(paused);
    182                 inactive.remove(animationName.impl());
    183                 continue;
    184             }
    185 
    186             KeyframeAnimationEffect::KeyframeVector keyframes;
    187             element->document()->styleResolver()->resolveKeyframes(element, style, animationName.impl(), keyframes);
    188             if (!keyframes.isEmpty()) {
    189                 Timing timing;
    190                 timingFromAnimationData(animationData, timing);
    191                 OwnPtr<CSSAnimations::EventDelegate> eventDelegate = adoptPtr(new EventDelegate(element, animationName));
    192                 // FIXME: crbug.com/268791 - Keyframes are already normalized, perhaps there should be a flag on KeyframeAnimationEffect to skip normalization.
    193                 m_animations.set(animationName.impl(), element->document()->timeline()->play(
    194                     Animation::create(element, KeyframeAnimationEffect::create(keyframes), timing, eventDelegate.release()).get()).get());
    195             }
    196         }
    197     }
    198 
    199     for (HashSet<StringImpl*>::const_iterator iter = inactive.begin(); iter != inactive.end(); ++iter)
    200         m_animations.take(*iter)->cancel();
    201 }
    202 
    203 void CSSAnimations::cancel()
    204 {
    205     for (AnimationMap::iterator iter = m_animations.begin(); iter != m_animations.end(); ++iter)
    206         iter->value->cancel();
    207 
    208     m_animations.clear();
    209 }
    210 
    211 void CSSAnimations::EventDelegate::maybeDispatch(Document::ListenerType listenerType, AtomicString& eventName, double elapsedTime)
    212 {
    213     if (m_target->document()->hasListenerType(listenerType))
    214         m_target->document()->timeline()->addEventToDispatch(m_target, AnimationEvent::create(eventName, m_name, elapsedTime));
    215 }
    216 
    217 void CSSAnimations::EventDelegate::onEventCondition(bool wasInPlay, bool isInPlay, double previousIteration, double currentIteration)
    218 {
    219     // Events for a single document are queued and dispatched as a group at
    220     // the end of DocumentTimeline::serviceAnimations.
    221     // FIXME: Events which are queued outside of serviceAnimations should
    222     // trigger a timer to dispatch when control is released.
    223     // FIXME: Receive TimedItem as param in order to produce correct elapsed time value.
    224     double elapsedTime = 0;
    225     if (!wasInPlay && isInPlay) {
    226         maybeDispatch(Document::ANIMATIONSTART_LISTENER, eventNames().webkitAnimationStartEvent, elapsedTime);
    227         return;
    228     }
    229     if (wasInPlay && isInPlay && currentIteration != previousIteration) {
    230         maybeDispatch(Document::ANIMATIONITERATION_LISTENER, eventNames().webkitAnimationIterationEvent, elapsedTime);
    231         return;
    232     }
    233     if (wasInPlay && !isInPlay) {
    234         maybeDispatch(Document::ANIMATIONEND_LISTENER, eventNames().webkitAnimationEndEvent, elapsedTime);
    235         return;
    236     }
    237 }
    238 
    239 } // namespace WebCore
    240