Upstream version 11.40.277.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / animation / css / CSSAnimations.cpp
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/StylePropertyShorthand.h"
35 #include "core/animation/ActiveAnimations.h"
36 #include "core/animation/AnimationTimeline.h"
37 #include "core/animation/CompositorAnimations.h"
38 #include "core/animation/KeyframeEffectModel.h"
39 #include "core/animation/LegacyStyleInterpolation.h"
40 #include "core/animation/css/CSSAnimatableValueFactory.h"
41 #include "core/animation/css/CSSPropertyEquality.h"
42 #include "core/css/CSSKeyframeRule.h"
43 #include "core/css/CSSPropertyMetadata.h"
44 #include "core/css/resolver/CSSToStyleMap.h"
45 #include "core/css/resolver/StyleResolver.h"
46 #include "core/dom/Element.h"
47 #include "core/dom/PseudoElement.h"
48 #include "core/dom/StyleEngine.h"
49 #include "core/events/TransitionEvent.h"
50 #include "core/events/WebKitAnimationEvent.h"
51 #include "core/frame/UseCounter.h"
52 #include "core/rendering/RenderLayer.h"
53 #include "core/rendering/RenderObject.h"
54 #include "core/rendering/style/KeyframeList.h"
55 #include "platform/animation/TimingFunction.h"
56 #include "public/platform/Platform.h"
57 #include "wtf/BitArray.h"
58 #include "wtf/HashSet.h"
59
60 namespace blink {
61
62 namespace {
63
64 CSSPropertyID propertyForAnimation(CSSPropertyID property)
65 {
66     switch (property) {
67     case CSSPropertyWebkitPerspective:
68         return CSSPropertyPerspective;
69     case CSSPropertyWebkitTransform:
70         return CSSPropertyTransform;
71     case CSSPropertyWebkitPerspectiveOriginX:
72     case CSSPropertyWebkitPerspectiveOriginY:
73     case CSSPropertyWebkitPerspectiveOrigin:
74         return CSSPropertyPerspectiveOrigin;
75     case CSSPropertyWebkitTransformOriginX:
76     case CSSPropertyWebkitTransformOriginY:
77     case CSSPropertyWebkitTransformOriginZ:
78     case CSSPropertyWebkitTransformOrigin:
79         return CSSPropertyTransformOrigin;
80     default:
81         break;
82     }
83     return property;
84 }
85
86 static void resolveKeyframes(StyleResolver* resolver, const Element* animatingElement, Element& element, const RenderStyle& style, RenderStyle* parentStyle, const AtomicString& name, TimingFunction* defaultTimingFunction,
87     AnimatableValueKeyframeVector& keyframes)
88 {
89     // When the animating element is null, use its parent for scoping purposes.
90     const Element* elementForScoping = animatingElement ? animatingElement : &element;
91     const StyleRuleKeyframes* keyframesRule = CSSAnimations::matchScopedKeyframesRule(resolver, elementForScoping, name.impl());
92     if (!keyframesRule)
93         return;
94
95     const WillBeHeapVector<RefPtrWillBeMember<StyleKeyframe>>& styleKeyframes = keyframesRule->keyframes();
96     if (styleKeyframes.isEmpty())
97         return;
98
99     // Construct and populate the style for each keyframe
100     PropertySet specifiedPropertiesForUseCounter;
101     for (size_t i = 0; i < styleKeyframes.size(); ++i) {
102         const StyleKeyframe* styleKeyframe = styleKeyframes[i].get();
103         RefPtr<RenderStyle> keyframeStyle = resolver->styleForKeyframe(element, style, parentStyle, styleKeyframe, name);
104         RefPtrWillBeRawPtr<AnimatableValueKeyframe> keyframe = AnimatableValueKeyframe::create();
105         const Vector<double>& offsets = styleKeyframe->keys();
106         ASSERT(!offsets.isEmpty());
107         keyframe->setOffset(offsets[0]);
108         keyframe->setEasing(defaultTimingFunction);
109         const StylePropertySet& properties = styleKeyframe->properties();
110         for (unsigned j = 0; j < properties.propertyCount(); j++) {
111             specifiedPropertiesForUseCounter.add(properties.propertyAt(j).id());
112             CSSPropertyID property = propertyForAnimation(properties.propertyAt(j).id());
113             if (property == CSSPropertyWebkitAnimationTimingFunction || property == CSSPropertyAnimationTimingFunction) {
114                 CSSValue* value = properties.propertyAt(j).value();
115                 RefPtr<TimingFunction> timingFunction;
116                 if (value->isInheritedValue() && parentStyle->animations())
117                     timingFunction = parentStyle->animations()->timingFunctionList()[0];
118                 else if (value->isInheritedValue() || value->isInitialValue())
119                     timingFunction = CSSTimingData::initialTimingFunction();
120                 else
121                     timingFunction = CSSToStyleMap::mapAnimationTimingFunction(toCSSValueList(value)->item(0));
122                 keyframe->setEasing(timingFunction.release());
123             } else if (CSSPropertyMetadata::isAnimatableProperty(property)) {
124                 keyframe->setPropertyValue(property, CSSAnimatableValueFactory::create(property, *keyframeStyle).get());
125             }
126         }
127         keyframes.append(keyframe);
128         // The last keyframe specified at a given offset is used.
129         for (size_t j = 1; j < offsets.size(); ++j) {
130             keyframes.append(toAnimatableValueKeyframe(keyframe->cloneWithOffset(offsets[j]).get()));
131         }
132     }
133     ASSERT(!keyframes.isEmpty());
134
135     for (CSSPropertyID property : specifiedPropertiesForUseCounter) {
136         ASSERT(property != CSSPropertyInvalid);
137         blink::Platform::current()->histogramSparse("WebCore.Animation.CSSProperties", UseCounter::mapCSSPropertyIdToCSSSampleIdForHistogram(property));
138     }
139
140     // Remove duplicate keyframes. In CSS the last keyframe at a given offset takes priority.
141     std::stable_sort(keyframes.begin(), keyframes.end(), Keyframe::compareOffsets);
142     size_t targetIndex = 0;
143     for (size_t i = 1; i < keyframes.size(); i++) {
144         if (keyframes[i]->offset() != keyframes[targetIndex]->offset())
145             targetIndex++;
146         if (targetIndex != i)
147             keyframes[targetIndex] = keyframes[i];
148     }
149     keyframes.shrink(targetIndex + 1);
150
151     // Add 0% and 100% keyframes if absent.
152     RefPtrWillBeRawPtr<AnimatableValueKeyframe> startKeyframe = keyframes[0];
153     if (startKeyframe->offset()) {
154         startKeyframe = AnimatableValueKeyframe::create();
155         startKeyframe->setOffset(0);
156         startKeyframe->setEasing(defaultTimingFunction);
157         keyframes.prepend(startKeyframe);
158     }
159     RefPtrWillBeRawPtr<AnimatableValueKeyframe> endKeyframe = keyframes[keyframes.size() - 1];
160     if (endKeyframe->offset() != 1) {
161         endKeyframe = AnimatableValueKeyframe::create();
162         endKeyframe->setOffset(1);
163         endKeyframe->setEasing(defaultTimingFunction);
164         keyframes.append(endKeyframe);
165     }
166     ASSERT(keyframes.size() >= 2);
167     ASSERT(!keyframes.first()->offset());
168     ASSERT(keyframes.last()->offset() == 1);
169
170     // Snapshot current property values for 0% and 100% if missing.
171     PropertySet allProperties;
172     for (const auto& keyframe : keyframes) {
173         for (CSSPropertyID property : keyframe->properties())
174             allProperties.add(property);
175     }
176     const PropertySet& startKeyframeProperties = startKeyframe->properties();
177     const PropertySet& endKeyframeProperties = endKeyframe->properties();
178     bool missingStartValues = startKeyframeProperties.size() < allProperties.size();
179     bool missingEndValues = endKeyframeProperties.size() < allProperties.size();
180     if (missingStartValues || missingEndValues) {
181         for (CSSPropertyID property : allProperties) {
182             bool startNeedsValue = missingStartValues && !startKeyframeProperties.contains(property);
183             bool endNeedsValue = missingEndValues && !endKeyframeProperties.contains(property);
184             if (!startNeedsValue && !endNeedsValue)
185                 continue;
186             RefPtrWillBeRawPtr<AnimatableValue> snapshotValue = CSSAnimatableValueFactory::create(property, style);
187             if (startNeedsValue)
188                 startKeyframe->setPropertyValue(property, snapshotValue.get());
189             if (endNeedsValue)
190                 endKeyframe->setPropertyValue(property, snapshotValue.get());
191         }
192     }
193     ASSERT(startKeyframe->properties().size() == allProperties.size());
194     ASSERT(endKeyframe->properties().size() == allProperties.size());
195 }
196
197 } // namespace
198
199 const StyleRuleKeyframes* CSSAnimations::matchScopedKeyframesRule(StyleResolver* resolver, const Element* element, const StringImpl* animationName)
200 {
201     // FIXME: This is all implementation detail of style resolver, CSSAnimations shouldn't be reaching into any of it.
202     if (element->document().styleEngine()->hasOnlyScopedResolverForDocument())
203         return element->document().scopedStyleResolver()->keyframeStylesForAnimation(animationName);
204
205     WillBeHeapVector<RawPtrWillBeMember<ScopedStyleResolver>, 8> stack;
206     resolver->styleTreeResolveScopedKeyframesRules(element, stack);
207     if (stack.isEmpty())
208         return nullptr;
209
210     for (size_t i = 0; i < stack.size(); ++i) {
211         if (const StyleRuleKeyframes* keyframesRule = stack.at(i)->keyframeStylesForAnimation(animationName))
212             return keyframesRule;
213     }
214     return nullptr;
215 }
216
217 CSSAnimations::CSSAnimations()
218 {
219 }
220
221 const AtomicString CSSAnimations::getAnimationNameForInspector(const AnimationPlayer& player)
222 {
223     for (const auto& it : m_animations) {
224         if (it.value->sequenceNumber() == player.sequenceNumber())
225             return it.key;
226     }
227     return nullAtom;
228 }
229
230 PassOwnPtrWillBeRawPtr<CSSAnimationUpdate> CSSAnimations::calculateUpdate(const Element* animatingElement, Element& element, const RenderStyle& style, RenderStyle* parentStyle, StyleResolver* resolver)
231 {
232     OwnPtrWillBeRawPtr<CSSAnimationUpdate> update = adoptPtrWillBeNoop(new CSSAnimationUpdate());
233     calculateAnimationUpdate(update.get(), animatingElement, element, style, parentStyle, resolver);
234     calculateAnimationActiveInterpolations(update.get(), animatingElement, element.document().timeline().currentTimeInternal());
235     calculateTransitionUpdate(update.get(), animatingElement, style);
236     calculateTransitionActiveInterpolations(update.get(), animatingElement, element.document().timeline().currentTimeInternal());
237     return update->isEmpty() ? nullptr : update.release();
238 }
239
240 void CSSAnimations::calculateAnimationUpdate(CSSAnimationUpdate* update, const Element* animatingElement, Element& element, const RenderStyle& style, RenderStyle* parentStyle, StyleResolver* resolver)
241 {
242     const ActiveAnimations* activeAnimations = animatingElement ? animatingElement->activeAnimations() : nullptr;
243
244 #if !ENABLE(ASSERT)
245     // If we're in an animation style change, no animations can have started, been cancelled or changed play state.
246     // When ASSERT is enabled, we verify this optimization.
247     if (activeAnimations && activeAnimations->isAnimationStyleChange())
248         return;
249 #endif
250
251     const CSSAnimationData* animationData = style.animations();
252     const CSSAnimations* cssAnimations = activeAnimations ? &activeAnimations->cssAnimations() : nullptr;
253
254     HashSet<AtomicString> inactive;
255     if (cssAnimations) {
256         for (const auto& entry : cssAnimations->m_animations)
257             inactive.add(entry.key);
258     }
259
260     if (style.display() != NONE) {
261         for (size_t i = 0; animationData && i < animationData->nameList().size(); ++i) {
262             AtomicString animationName(animationData->nameList()[i]);
263             if (animationName == CSSAnimationData::initialName())
264                 continue;
265
266             bool isPaused = CSSTimingData::getRepeated(animationData->playStateList(), i) == AnimPlayStatePaused;
267
268             // Keyframes and animation properties are snapshotted when the
269             // animation starts, so we don't need to track changes to these,
270             // with the exception of play-state.
271             if (cssAnimations) {
272                 AnimationMap::const_iterator existing(cssAnimations->m_animations.find(animationName));
273                 if (existing != cssAnimations->m_animations.end()) {
274                     inactive.remove(animationName);
275                     AnimationPlayer* player = existing->value.get();
276                     if (isPaused != player->paused()) {
277                         ASSERT(!activeAnimations || !activeAnimations->isAnimationStyleChange());
278                         update->toggleAnimationPaused(animationName);
279                     }
280                     continue;
281                 }
282             }
283
284             Timing timing = animationData->convertToTiming(i);
285             RefPtr<TimingFunction> keyframeTimingFunction = timing.timingFunction;
286             timing.timingFunction = Timing::defaults().timingFunction;
287             AnimatableValueKeyframeVector resolvedKeyframes;
288             resolveKeyframes(resolver, animatingElement, element, style, parentStyle, animationName, keyframeTimingFunction.get(), resolvedKeyframes);
289             if (!resolvedKeyframes.isEmpty()) {
290                 ASSERT(!activeAnimations || !activeAnimations->isAnimationStyleChange());
291                 update->startAnimation(animationName, InertAnimation::create(AnimatableValueKeyframeEffectModel::create(resolvedKeyframes), timing, isPaused));
292             }
293         }
294     }
295
296     ASSERT(inactive.isEmpty() || cssAnimations);
297     for (const AtomicString& animationName : inactive) {
298         ASSERT(!activeAnimations || !activeAnimations->isAnimationStyleChange());
299         update->cancelAnimation(animationName, *cssAnimations->m_animations.get(animationName));
300     }
301 }
302
303 void CSSAnimations::maybeApplyPendingUpdate(Element* element)
304 {
305     if (!m_pendingUpdate) {
306         m_previousActiveInterpolationsForAnimations.clear();
307         return;
308     }
309
310     OwnPtrWillBeRawPtr<CSSAnimationUpdate> update = m_pendingUpdate.release();
311
312     m_previousActiveInterpolationsForAnimations.swap(update->activeInterpolationsForAnimations());
313
314     // FIXME: cancelling, pausing, unpausing animations all query compositingState, which is not necessarily up to date here
315     // since we call this from recalc style.
316     // https://code.google.com/p/chromium/issues/detail?id=339847
317     DisableCompositingQueryAsserts disabler;
318
319     for (const AtomicString& animationName : update->cancelledAnimationNames()) {
320         RefPtrWillBeRawPtr<AnimationPlayer> player = m_animations.take(animationName);
321         player->cancel();
322         player->update(TimingUpdateOnDemand);
323     }
324
325     for (const AtomicString& animationName : update->animationsWithPauseToggled()) {
326         AnimationPlayer* player = m_animations.get(animationName);
327         if (player->paused())
328             player->unpause();
329         else
330             player->pause();
331         if (player->outdated())
332             player->update(TimingUpdateOnDemand);
333     }
334
335     for (const auto& entry : update->newAnimations()) {
336         const InertAnimation* inertAnimation = entry.animation.get();
337         OwnPtrWillBeRawPtr<AnimationEventDelegate> eventDelegate = adoptPtrWillBeNoop(new AnimationEventDelegate(element, entry.name));
338         RefPtrWillBeRawPtr<Animation> animation = Animation::create(element, inertAnimation->effect(), inertAnimation->specifiedTiming(), Animation::DefaultPriority, eventDelegate.release());
339         animation->setName(inertAnimation->name());
340         RefPtrWillBeRawPtr<AnimationPlayer> player = element->document().timeline().createAnimationPlayer(animation.get());
341         if (inertAnimation->paused())
342             player->pause();
343         player->update(TimingUpdateOnDemand);
344         m_animations.set(entry.name, player.get());
345     }
346
347     // Transitions that are run on the compositor only update main-thread state
348     // lazily. However, we need the new state to know what the from state shoud
349     // be when transitions are retargeted. Instead of triggering complete style
350     // recalculation, we find these cases by searching for new transitions that
351     // have matching cancelled animation property IDs on the compositor.
352     WillBeHeapHashMap<CSSPropertyID, std::pair<RefPtrWillBeMember<Animation>, double>> retargetedCompositorTransitions;
353     for (CSSPropertyID id : update->cancelledTransitions()) {
354         ASSERT(m_transitions.contains(id));
355
356         RefPtrWillBeRawPtr<AnimationPlayer> player = m_transitions.take(id).player;
357         Animation* animation = toAnimation(player->source());
358         if (animation->hasActiveAnimationsOnCompositor(id) && update->newTransitions().find(id) != update->newTransitions().end())
359             retargetedCompositorTransitions.add(id, std::pair<RefPtrWillBeMember<Animation>, double>(animation, player->startTimeInternal()));
360         player->cancel();
361         player->update(TimingUpdateOnDemand);
362     }
363
364     for (const auto& entry : update->newTransitions()) {
365         const CSSAnimationUpdate::NewTransition& newTransition = entry.value;
366
367         RunningTransition runningTransition;
368         runningTransition.from = newTransition.from;
369         runningTransition.to = newTransition.to;
370
371         CSSPropertyID id = newTransition.id;
372         InertAnimation* inertAnimation = newTransition.animation.get();
373         OwnPtrWillBeRawPtr<TransitionEventDelegate> eventDelegate = adoptPtrWillBeNoop(new TransitionEventDelegate(element, newTransition.eventId));
374
375         RefPtrWillBeRawPtr<AnimationEffect> effect = inertAnimation->effect();
376
377         if (retargetedCompositorTransitions.contains(id)) {
378             const std::pair<RefPtrWillBeMember<Animation>, double>& oldTransition = retargetedCompositorTransitions.get(id);
379             RefPtrWillBeRawPtr<Animation> oldAnimation = oldTransition.first;
380             double oldStartTime = oldTransition.second;
381             double inheritedTime = isNull(oldStartTime) ? 0 : element->document().timeline().currentTimeInternal() - oldStartTime;
382
383             AnimatableValueKeyframeEffectModel* oldEffect = toAnimatableValueKeyframeEffectModel(inertAnimation->effect());
384             const KeyframeVector& frames = oldEffect->getFrames();
385
386             AnimatableValueKeyframeVector newFrames;
387             newFrames.append(toAnimatableValueKeyframe(frames[0]->clone().get()));
388             newFrames.append(toAnimatableValueKeyframe(frames[1]->clone().get()));
389
390             newFrames[0]->clearPropertyValue(id);
391             RefPtrWillBeRawPtr<InertAnimation> inertAnimationForSampling = InertAnimation::create(oldAnimation->effect(), oldAnimation->specifiedTiming(), false);
392             OwnPtrWillBeRawPtr<WillBeHeapVector<RefPtrWillBeMember<Interpolation>>> sample = inertAnimationForSampling->sample(inheritedTime);
393             ASSERT(sample->size() == 1);
394             newFrames[0]->setPropertyValue(id, toLegacyStyleInterpolation(sample->at(0).get())->currentValue());
395
396             effect = AnimatableValueKeyframeEffectModel::create(newFrames);
397         }
398
399         RefPtrWillBeRawPtr<Animation> transition = Animation::create(element, effect, inertAnimation->specifiedTiming(), Animation::TransitionPriority, eventDelegate.release());
400         transition->setName(inertAnimation->name());
401         RefPtrWillBeRawPtr<AnimationPlayer> player = element->document().timeline().createAnimationPlayer(transition.get());
402         player->update(TimingUpdateOnDemand);
403         runningTransition.player = player;
404         m_transitions.set(id, runningTransition);
405         ASSERT(id != CSSPropertyInvalid);
406         blink::Platform::current()->histogramSparse("WebCore.Animation.CSSProperties", UseCounter::mapCSSPropertyIdToCSSSampleIdForHistogram(id));
407     }
408 }
409
410 void CSSAnimations::calculateTransitionUpdateForProperty(CSSPropertyID id, CSSPropertyID eventId, const CSSTransitionData& transitionData, size_t transitionIndex, const RenderStyle& oldStyle, const RenderStyle& style, const TransitionMap* activeTransitions, CSSAnimationUpdate* update, const Element* element)
411 {
412     RefPtrWillBeRawPtr<AnimatableValue> to = nullptr;
413     if (activeTransitions) {
414         TransitionMap::const_iterator activeTransitionIter = activeTransitions->find(id);
415         if (activeTransitionIter != activeTransitions->end()) {
416             to = CSSAnimatableValueFactory::create(id, style);
417             const AnimatableValue* activeTo = activeTransitionIter->value.to;
418             if (to->equals(activeTo))
419                 return;
420             update->cancelTransition(id);
421             ASSERT(!element->activeAnimations() || !element->activeAnimations()->isAnimationStyleChange());
422         }
423     }
424
425     if (CSSPropertyEquality::propertiesEqual(id, oldStyle, style))
426         return;
427     if (!to)
428         to = CSSAnimatableValueFactory::create(id, style);
429
430     RefPtrWillBeRawPtr<AnimatableValue> from = CSSAnimatableValueFactory::create(id, oldStyle);
431     // If we have multiple transitions on the same property, we will use the
432     // last one since we iterate over them in order.
433     if (AnimatableValue::usesDefaultInterpolation(to.get(), from.get()))
434         return;
435
436     Timing timing = transitionData.convertToTiming(transitionIndex);
437     if (timing.startDelay + timing.iterationDuration <= 0)
438         return;
439
440     AnimatableValueKeyframeVector keyframes;
441
442     RefPtrWillBeRawPtr<AnimatableValueKeyframe> startKeyframe = AnimatableValueKeyframe::create();
443     startKeyframe->setPropertyValue(id, from.get());
444     startKeyframe->setOffset(0);
445     startKeyframe->setEasing(timing.timingFunction.release());
446     timing.timingFunction = LinearTimingFunction::shared();
447     keyframes.append(startKeyframe);
448
449     RefPtrWillBeRawPtr<AnimatableValueKeyframe> endKeyframe = AnimatableValueKeyframe::create();
450     endKeyframe->setPropertyValue(id, to.get());
451     endKeyframe->setOffset(1);
452     keyframes.append(endKeyframe);
453
454     RefPtrWillBeRawPtr<AnimatableValueKeyframeEffectModel> effect = AnimatableValueKeyframeEffectModel::create(keyframes);
455     update->startTransition(id, eventId, from.get(), to.get(), InertAnimation::create(effect, timing, false));
456     ASSERT(!element->activeAnimations() || !element->activeAnimations()->isAnimationStyleChange());
457 }
458
459 void CSSAnimations::calculateTransitionUpdate(CSSAnimationUpdate* update, const Element* animatingElement, const RenderStyle& style)
460 {
461     if (!animatingElement)
462         return;
463
464     ActiveAnimations* activeAnimations = animatingElement->activeAnimations();
465     const TransitionMap* activeTransitions = activeAnimations ? &activeAnimations->cssAnimations().m_transitions : nullptr;
466     const CSSTransitionData* transitionData = style.transitions();
467
468 #if ENABLE(ASSERT)
469     // In debug builds we verify that it would have been safe to avoid populating and testing listedProperties if the style recalc is due to animation.
470     const bool animationStyleRecalc = false;
471 #else
472     // In release builds we avoid the cost of checking for new and interrupted transitions if the style recalc is due to animation.
473     const bool animationStyleRecalc = activeAnimations && activeAnimations->isAnimationStyleChange();
474 #endif
475
476     BitArray<numCSSProperties> listedProperties;
477     bool anyTransitionHadTransitionAll = false;
478     const RenderObject* renderer = animatingElement->renderer();
479     if (!animationStyleRecalc && style.display() != NONE && renderer && renderer->style() && transitionData) {
480         const RenderStyle& oldStyle = *renderer->style();
481
482         for (size_t i = 0; i < transitionData->propertyList().size(); ++i) {
483             const CSSTransitionData::TransitionProperty& transitionProperty = transitionData->propertyList()[i];
484             CSSTransitionData::TransitionPropertyType mode = transitionProperty.propertyType;
485             CSSPropertyID property = transitionProperty.propertyId;
486             if (mode == CSSTransitionData::TransitionNone || mode == CSSTransitionData::TransitionUnknown)
487                 continue;
488
489             bool animateAll = mode == CSSTransitionData::TransitionAll;
490             ASSERT(animateAll || mode == CSSTransitionData::TransitionSingleProperty);
491             if (animateAll)
492                 anyTransitionHadTransitionAll = true;
493             const StylePropertyShorthand& propertyList = animateAll ? CSSAnimations::animatableProperties() : shorthandForProperty(property);
494             // If not a shorthand we only execute one iteration of this loop, and refer to the property directly.
495             for (unsigned j = 0; !j || j < propertyList.length(); ++j) {
496                 CSSPropertyID id = propertyList.length() ? propertyList.properties()[j] : property;
497                 CSSPropertyID eventId = id;
498
499                 if (!animateAll) {
500                     id = propertyForAnimation(id);
501                     if (CSSPropertyMetadata::isAnimatableProperty(id))
502                         listedProperties.set(id);
503                     else
504                         continue;
505                 }
506
507                 // FIXME: We should transition if an !important property changes even when an animation is running,
508                 // but this is a bit hard to do with the current applyMatchedProperties system.
509                 if (!update->activeInterpolationsForAnimations().contains(id)
510                     && (!activeAnimations || !activeAnimations->cssAnimations().m_previousActiveInterpolationsForAnimations.contains(id))) {
511                     calculateTransitionUpdateForProperty(id, eventId, *transitionData, i, oldStyle, style, activeTransitions, update, animatingElement);
512                 }
513             }
514         }
515     }
516
517     if (activeTransitions) {
518         for (const auto& entry : *activeTransitions) {
519             const AnimationPlayer& player = *entry.value.player;
520             CSSPropertyID id = entry.key;
521             if (player.finishedInternal() || (!anyTransitionHadTransitionAll && !animationStyleRecalc && !listedProperties.get(id))) {
522                 // TODO: Figure out why this fails on Chrome OS login page. crbug.com/365507
523                 // ASSERT(player.finishedInternal() || !(activeAnimations && activeAnimations->isAnimationStyleChange()));
524                 update->cancelTransition(id);
525             }
526         }
527     }
528 }
529
530 void CSSAnimations::cancel()
531 {
532     for (const auto& entry : m_animations) {
533         entry.value->cancel();
534         entry.value->update(TimingUpdateOnDemand);
535     }
536
537     for (const auto& entry : m_transitions) {
538         entry.value.player->cancel();
539         entry.value.player->update(TimingUpdateOnDemand);
540     }
541
542     m_animations.clear();
543     m_transitions.clear();
544     m_pendingUpdate = nullptr;
545 }
546
547 void CSSAnimations::calculateAnimationActiveInterpolations(CSSAnimationUpdate* update, const Element* animatingElement, double timelineCurrentTime)
548 {
549     ActiveAnimations* activeAnimations = animatingElement ? animatingElement->activeAnimations() : nullptr;
550     AnimationStack* animationStack = activeAnimations ? &activeAnimations->defaultStack() : nullptr;
551
552     if (update->newAnimations().isEmpty() && update->cancelledAnimationAnimationPlayers().isEmpty()) {
553         WillBeHeapHashMap<CSSPropertyID, RefPtrWillBeMember<Interpolation>> activeInterpolationsForAnimations(AnimationStack::activeInterpolations(animationStack, 0, 0, Animation::DefaultPriority, timelineCurrentTime));
554         update->adoptActiveInterpolationsForAnimations(activeInterpolationsForAnimations);
555         return;
556     }
557
558     WillBeHeapVector<RawPtrWillBeMember<InertAnimation>> newAnimations;
559     for (size_t i = 0; i < update->newAnimations().size(); ++i) {
560         newAnimations.append(update->newAnimations()[i].animation.get());
561     }
562     WillBeHeapHashMap<CSSPropertyID, RefPtrWillBeMember<Interpolation>> activeInterpolationsForAnimations(AnimationStack::activeInterpolations(animationStack, &newAnimations, &update->cancelledAnimationAnimationPlayers(), Animation::DefaultPriority, timelineCurrentTime));
563     update->adoptActiveInterpolationsForAnimations(activeInterpolationsForAnimations);
564 }
565
566 void CSSAnimations::calculateTransitionActiveInterpolations(CSSAnimationUpdate* update, const Element* animatingElement, double timelineCurrentTime)
567 {
568     ActiveAnimations* activeAnimations = animatingElement ? animatingElement->activeAnimations() : nullptr;
569     AnimationStack* animationStack = activeAnimations ? &activeAnimations->defaultStack() : nullptr;
570
571     WillBeHeapHashMap<CSSPropertyID, RefPtrWillBeMember<Interpolation>> activeInterpolationsForTransitions;
572     if (update->newTransitions().isEmpty() && update->cancelledTransitions().isEmpty()) {
573         activeInterpolationsForTransitions = AnimationStack::activeInterpolations(animationStack, 0, 0, Animation::TransitionPriority, timelineCurrentTime);
574     } else {
575         WillBeHeapVector<RawPtrWillBeMember<InertAnimation>> newTransitions;
576         for (const auto& entry : update->newTransitions())
577             newTransitions.append(entry.value.animation.get());
578
579         WillBeHeapHashSet<RawPtrWillBeMember<const AnimationPlayer>> cancelledAnimationPlayers;
580         if (!update->cancelledTransitions().isEmpty()) {
581             ASSERT(activeAnimations);
582             const TransitionMap& transitionMap = activeAnimations->cssAnimations().m_transitions;
583             for (CSSPropertyID id : update->cancelledTransitions()) {
584                 ASSERT(transitionMap.contains(id));
585                 cancelledAnimationPlayers.add(transitionMap.get(id).player.get());
586             }
587         }
588
589         activeInterpolationsForTransitions = AnimationStack::activeInterpolations(animationStack, &newTransitions, &cancelledAnimationPlayers, Animation::TransitionPriority, timelineCurrentTime);
590     }
591
592     // Properties being animated by animations don't get values from transitions applied.
593     if (!update->activeInterpolationsForAnimations().isEmpty() && !activeInterpolationsForTransitions.isEmpty()) {
594         for (const auto& entry : update->activeInterpolationsForAnimations())
595             activeInterpolationsForTransitions.remove(entry.key);
596     }
597     update->adoptActiveInterpolationsForTransitions(activeInterpolationsForTransitions);
598 }
599
600 void CSSAnimations::AnimationEventDelegate::maybeDispatch(Document::ListenerType listenerType, const AtomicString& eventName, double elapsedTime)
601 {
602     if (m_target->document().hasListenerType(listenerType)) {
603         RefPtrWillBeRawPtr<WebKitAnimationEvent> event = WebKitAnimationEvent::create(eventName, m_name, elapsedTime);
604         event->setTarget(m_target);
605         m_target->document().enqueueAnimationFrameEvent(event);
606     }
607 }
608
609 void CSSAnimations::AnimationEventDelegate::onEventCondition(const AnimationNode* animationNode)
610 {
611     const AnimationNode::Phase currentPhase = animationNode->phase();
612     const double currentIteration = animationNode->currentIteration();
613
614     if (m_previousPhase != currentPhase
615         && (currentPhase == AnimationNode::PhaseActive || currentPhase == AnimationNode::PhaseAfter)
616         && (m_previousPhase == AnimationNode::PhaseNone || m_previousPhase == AnimationNode::PhaseBefore)) {
617         // The spec states that the elapsed time should be
618         // 'delay < 0 ? -delay : 0', but we always use 0 to match the existing
619         // implementation. See crbug.com/279611
620         maybeDispatch(Document::ANIMATIONSTART_LISTENER, EventTypeNames::animationstart, 0);
621     }
622
623     if (currentPhase == AnimationNode::PhaseActive && m_previousPhase == currentPhase && m_previousIteration != currentIteration) {
624         // We fire only a single event for all iterations thast terminate
625         // between a single pair of samples. See http://crbug.com/275263. For
626         // compatibility with the existing implementation, this event uses
627         // the elapsedTime for the first iteration in question.
628         ASSERT(!std::isnan(animationNode->specifiedTiming().iterationDuration));
629         const double elapsedTime = animationNode->specifiedTiming().iterationDuration * (m_previousIteration + 1);
630         maybeDispatch(Document::ANIMATIONITERATION_LISTENER, EventTypeNames::animationiteration, elapsedTime);
631     }
632
633     if (currentPhase == AnimationNode::PhaseAfter && m_previousPhase != AnimationNode::PhaseAfter)
634         maybeDispatch(Document::ANIMATIONEND_LISTENER, EventTypeNames::animationend, animationNode->activeDurationInternal());
635
636     m_previousPhase = currentPhase;
637     m_previousIteration = currentIteration;
638 }
639
640 void CSSAnimations::AnimationEventDelegate::trace(Visitor* visitor)
641 {
642     visitor->trace(m_target);
643     AnimationNode::EventDelegate::trace(visitor);
644 }
645
646 void CSSAnimations::TransitionEventDelegate::onEventCondition(const AnimationNode* animationNode)
647 {
648     const AnimationNode::Phase currentPhase = animationNode->phase();
649     if (currentPhase == AnimationNode::PhaseAfter && currentPhase != m_previousPhase && m_target->document().hasListenerType(Document::TRANSITIONEND_LISTENER)) {
650         String propertyName = getPropertyNameString(m_property);
651         const Timing& timing = animationNode->specifiedTiming();
652         double elapsedTime = timing.iterationDuration;
653         const AtomicString& eventType = EventTypeNames::transitionend;
654         String pseudoElement = PseudoElement::pseudoElementNameForEvents(m_target->pseudoId());
655         RefPtrWillBeRawPtr<TransitionEvent> event = TransitionEvent::create(eventType, propertyName, elapsedTime, pseudoElement);
656         event->setTarget(m_target);
657         m_target->document().enqueueAnimationFrameEvent(event);
658     }
659
660     m_previousPhase = currentPhase;
661 }
662
663 void CSSAnimations::TransitionEventDelegate::trace(Visitor* visitor)
664 {
665     visitor->trace(m_target);
666     AnimationNode::EventDelegate::trace(visitor);
667 }
668
669 const StylePropertyShorthand& CSSAnimations::animatableProperties()
670 {
671     DEFINE_STATIC_LOCAL(Vector<CSSPropertyID>, properties, ());
672     DEFINE_STATIC_LOCAL(StylePropertyShorthand, propertyShorthand, ());
673     if (properties.isEmpty()) {
674         for (int i = firstCSSProperty; i < lastCSSProperty; ++i) {
675             CSSPropertyID id = convertToCSSPropertyID(i);
676             if (CSSPropertyMetadata::isAnimatableProperty(id))
677                 properties.append(id);
678         }
679         propertyShorthand = StylePropertyShorthand(CSSPropertyInvalid, properties.begin(), properties.size());
680     }
681     return propertyShorthand;
682 }
683
684 // Animation properties are not allowed to be affected by Web Animations.
685 // http://dev.w3.org/fxtf/web-animations/#not-animatable
686 bool CSSAnimations::isAllowedAnimation(CSSPropertyID property)
687 {
688     switch (property) {
689     case CSSPropertyAnimation:
690     case CSSPropertyAnimationDelay:
691     case CSSPropertyAnimationDirection:
692     case CSSPropertyAnimationDuration:
693     case CSSPropertyAnimationFillMode:
694     case CSSPropertyAnimationIterationCount:
695     case CSSPropertyAnimationName:
696     case CSSPropertyAnimationPlayState:
697     case CSSPropertyAnimationTimingFunction:
698     case CSSPropertyDisplay:
699     case CSSPropertyTransition:
700     case CSSPropertyTransitionDelay:
701     case CSSPropertyTransitionDuration:
702     case CSSPropertyTransitionProperty:
703     case CSSPropertyTransitionTimingFunction:
704     case CSSPropertyWebkitAnimation:
705     case CSSPropertyWebkitAnimationDelay:
706     case CSSPropertyWebkitAnimationDirection:
707     case CSSPropertyWebkitAnimationDuration:
708     case CSSPropertyWebkitAnimationFillMode:
709     case CSSPropertyWebkitAnimationIterationCount:
710     case CSSPropertyWebkitAnimationName:
711     case CSSPropertyWebkitAnimationPlayState:
712     case CSSPropertyWebkitAnimationTimingFunction:
713     case CSSPropertyWebkitTransition:
714     case CSSPropertyWebkitTransitionDelay:
715     case CSSPropertyWebkitTransitionDuration:
716     case CSSPropertyWebkitTransitionProperty:
717     case CSSPropertyWebkitTransitionTimingFunction:
718         return false;
719     default:
720         return true;
721     }
722 }
723
724 void CSSAnimations::trace(Visitor* visitor)
725 {
726 #if ENABLE(OILPAN)
727     visitor->trace(m_transitions);
728     visitor->trace(m_pendingUpdate);
729     visitor->trace(m_animations);
730     visitor->trace(m_previousActiveInterpolationsForAnimations);
731 #endif
732 }
733
734 void CSSAnimationUpdate::trace(Visitor* visitor)
735 {
736 #if ENABLE(OILPAN)
737     visitor->trace(m_newTransitions);
738     visitor->trace(m_activeInterpolationsForAnimations);
739     visitor->trace(m_activeInterpolationsForTransitions);
740     visitor->trace(m_newAnimations);
741     visitor->trace(m_cancelledAnimationPlayers);
742 #endif
743 }
744
745 } // namespace blink