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