1/*
2 * Copyright (C) 2007, 2012 Apple 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
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Apple Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include "config.h"
30#include "KeyframeAnimation.h"
31
32#include "CSSAnimationControllerPrivate.h"
33#include "CSSPropertyAnimation.h"
34#include "CSSPropertyNames.h"
35#include "CompositeAnimation.h"
36#include "EventNames.h"
37#include "GeometryUtilities.h"
38#include "RenderBox.h"
39#include "RenderStyle.h"
40#include "StylePendingResources.h"
41#include "StyleResolver.h"
42#include "StyleScope.h"
43#include "TranslateTransformOperation.h"
44#include "WillChangeData.h"
45
46namespace WebCore {
47
48KeyframeAnimation::KeyframeAnimation(const Animation& animation, Element& element, CompositeAnimation& compositeAnimation, const RenderStyle& unanimatedStyle)
49 : AnimationBase(animation, element, compositeAnimation)
50 , m_keyframes(animation.name())
51 , m_unanimatedStyle(RenderStyle::clonePtr(unanimatedStyle))
52{
53 resolveKeyframeStyles();
54
55 // Update the m_transformFunctionListValid flag based on whether the function lists in the keyframes match.
56 validateTransformFunctionList();
57 checkForMatchingFilterFunctionLists();
58#if ENABLE(FILTERS_LEVEL_2)
59 checkForMatchingBackdropFilterFunctionLists();
60#endif
61 checkForMatchingColorFilterFunctionLists();
62
63 for (auto propertyID : m_keyframes.properties()) {
64 if (WillChangeData::propertyCreatesStackingContext(propertyID))
65 m_triggersStackingContext = true;
66
67 if (CSSPropertyAnimation::animationOfPropertyIsAccelerated(propertyID))
68 m_hasAcceleratedProperty = true;
69
70 if (m_triggersStackingContext && m_hasAcceleratedProperty)
71 break;
72 }
73
74 computeLayoutDependency();
75}
76
77KeyframeAnimation::~KeyframeAnimation()
78{
79 // Make sure to tell the renderer that we are ending. This will make sure any accelerated animations are removed.
80 if (!postActive())
81 endAnimation();
82}
83
84void KeyframeAnimation::computeLayoutDependency()
85{
86 if (!m_keyframes.containsProperty(CSSPropertyTransform))
87 return;
88
89 size_t numKeyframes = m_keyframes.size();
90 for (size_t i = 0; i < numKeyframes; i++) {
91 auto* keyframeStyle = m_keyframes[i].style();
92 if (!keyframeStyle) {
93 ASSERT_NOT_REACHED();
94 continue;
95 }
96 if (keyframeStyle->hasTransform()) {
97 auto& transformOperations = keyframeStyle->transform();
98 for (const auto& operation : transformOperations.operations()) {
99 if (operation->isTranslateTransformOperationType()) {
100 auto translation = downcast<TranslateTransformOperation>(operation.get());
101 if (translation->x().isPercent() || translation->y().isPercent()) {
102 m_dependsOnLayout = true;
103 return;
104 }
105 }
106 }
107 }
108 }
109}
110
111void KeyframeAnimation::fetchIntervalEndpointsForProperty(CSSPropertyID property, const RenderStyle*& fromStyle, const RenderStyle*& toStyle, double& prog) const
112{
113 size_t numKeyframes = m_keyframes.size();
114 if (!numKeyframes)
115 return;
116
117 // Find the first key
118 double elapsedTime = getElapsedTime();
119 if (m_animation->duration() && m_animation->iterationCount() != Animation::IterationCountInfinite)
120 elapsedTime = std::min(elapsedTime, m_animation->duration() * m_animation->iterationCount());
121
122 const double fractionalTime = this->fractionalTime(1, elapsedTime, 0);
123 ASSERT(!m_keyframes[0].key());
124 ASSERT(m_keyframes[m_keyframes.size() - 1].key() == 1);
125
126 int prevIndex = -1;
127 int nextIndex = -1;
128 // FIXME: with a lot of keys, this linear search will be slow. We could binary search.
129 for (size_t i = 0; i < numKeyframes; ++i) {
130 const KeyframeValue& currKeyFrame = m_keyframes[i];
131
132 if (!currKeyFrame.containsProperty(property))
133 continue;
134
135 if (fractionalTime < currKeyFrame.key()) {
136 nextIndex = i;
137 break;
138 }
139 prevIndex = i;
140 }
141
142 if (prevIndex == -1)
143 prevIndex = 0;
144 if (nextIndex == -1)
145 nextIndex = m_keyframes.size() - 1;
146
147 const KeyframeValue& prevKeyframe = m_keyframes[prevIndex];
148 const KeyframeValue& nextKeyframe = m_keyframes[nextIndex];
149
150 fromStyle = prevKeyframe.style();
151 toStyle = nextKeyframe.style();
152
153 double offset = prevKeyframe.key();
154 double scale = 1.0 / (nextIndex == prevIndex ? 1 : (nextKeyframe.key() - prevKeyframe.key()));
155
156 prog = progress(scale, offset, prevKeyframe.timingFunction());
157}
158
159OptionSet<AnimateChange> KeyframeAnimation::animate(CompositeAnimation& compositeAnimation, const RenderStyle& targetStyle, std::unique_ptr<RenderStyle>& animatedStyle)
160{
161 AnimationState oldState = state();
162
163 // Update state and fire the start timeout if needed (FIXME: this function needs a better name).
164 fireAnimationEventsIfNeeded();
165
166 // If we have not yet started, we will not have a valid start time, so just start the animation if needed.
167 if (isNew()) {
168 if (m_animation->playState() == AnimationPlayState::Playing && !compositeAnimation.isSuspended())
169 updateStateMachine(AnimationStateInput::StartAnimation, -1);
170 else if (m_animation->playState() == AnimationPlayState::Paused)
171 updateStateMachine(AnimationStateInput::PlayStatePaused, -1);
172 }
173
174 // If we get this far and the animation is done, it means we are cleaning up a just-finished animation.
175 // If so, we need to send back the targetStyle.
176 if (postActive()) {
177 if (!animatedStyle)
178 animatedStyle = RenderStyle::clonePtr(targetStyle);
179 return { };
180 }
181
182 // If we are waiting for the start timer, we don't want to change the style yet.
183 // Special case 1 - if the delay time is 0, then we do want to set the first frame of the
184 // animation right away. This avoids a flash when the animation starts.
185 // Special case 2 - if there is a backwards fill mode, then we want to continue
186 // through to the style blend so that we get the fromStyle.
187 if (waitingToStart() && m_animation->delay() > 0 && !m_animation->fillsBackwards())
188 return { };
189
190 // If we have no keyframes, don't animate.
191 if (!m_keyframes.size()) {
192 updateStateMachine(AnimationStateInput::EndAnimation, -1);
193 return { };
194 }
195
196 // Run a cycle of animation.
197 // We know we will need a new render style, so make one if needed.
198 if (!animatedStyle)
199 animatedStyle = RenderStyle::clonePtr(targetStyle);
200
201 // FIXME: we need to be more efficient about determining which keyframes we are animating between.
202 // We should cache the last pair or something.
203 for (auto propertyID : m_keyframes.properties()) {
204 // Get the from/to styles and progress between
205 const RenderStyle* fromStyle = nullptr;
206 const RenderStyle* toStyle = nullptr;
207 double progress = 0;
208 fetchIntervalEndpointsForProperty(propertyID, fromStyle, toStyle, progress);
209
210 CSSPropertyAnimation::blendProperties(this, propertyID, animatedStyle.get(), fromStyle, toStyle, progress);
211 }
212
213 OptionSet<AnimateChange> change(AnimateChange::StyleBlended);
214 if (state() != oldState)
215 change.add(AnimateChange::StateChange);
216
217 if ((isPausedState(oldState) || isRunningState(oldState)) != (isPausedState(state()) || isRunningState(state())))
218 change.add(AnimateChange::RunningStateChange);
219
220 return change;
221}
222
223void KeyframeAnimation::getAnimatedStyle(std::unique_ptr<RenderStyle>& animatedStyle)
224{
225 // If we're done, or in the delay phase and we're not backwards filling, tell the caller to use the current style.
226 if (postActive() || (waitingToStart() && m_animation->delay() > 0 && !m_animation->fillsBackwards()))
227 return;
228
229 if (!m_keyframes.size())
230 return;
231
232 if (!animatedStyle)
233 animatedStyle = RenderStyle::clonePtr(renderer()->style());
234
235 for (auto propertyID : m_keyframes.properties()) {
236 // Get the from/to styles and progress between
237 const RenderStyle* fromStyle = nullptr;
238 const RenderStyle* toStyle = nullptr;
239 double progress = 0;
240 fetchIntervalEndpointsForProperty(propertyID, fromStyle, toStyle, progress);
241
242 CSSPropertyAnimation::blendProperties(this, propertyID, animatedStyle.get(), fromStyle, toStyle, progress);
243 }
244}
245
246bool KeyframeAnimation::computeExtentOfTransformAnimation(LayoutRect& bounds) const
247{
248 ASSERT(m_keyframes.containsProperty(CSSPropertyTransform));
249
250 if (!is<RenderBox>(renderer()))
251 return true; // Non-boxes don't get transformed;
252
253 auto& box = downcast<RenderBox>(*renderer());
254 auto rendererBox = snapRectToDevicePixels(box.borderBoxRect(), box.document().deviceScaleFactor());
255
256 auto cumulativeBounds = bounds;
257
258 for (auto& keyframe : m_keyframes.keyframes()) {
259 const RenderStyle* keyframeStyle = keyframe.style();
260
261 if (!keyframe.containsProperty(CSSPropertyTransform)) {
262 // If the first keyframe is missing transform style, use the current style.
263 if (!keyframe.key())
264 keyframeStyle = &box.style();
265 else
266 continue;
267 }
268
269 auto keyframeBounds = bounds;
270
271 bool canCompute;
272 if (transformFunctionListsMatch())
273 canCompute = computeTransformedExtentViaTransformList(rendererBox, *keyframeStyle, keyframeBounds);
274 else
275 canCompute = computeTransformedExtentViaMatrix(rendererBox, *keyframeStyle, keyframeBounds);
276
277 if (!canCompute)
278 return false;
279
280 cumulativeBounds.unite(keyframeBounds);
281 }
282
283 bounds = cumulativeBounds;
284 return true;
285}
286
287bool KeyframeAnimation::hasAnimationForProperty(CSSPropertyID property) const
288{
289 return m_keyframes.containsProperty(property);
290}
291
292bool KeyframeAnimation::startAnimation(double timeOffset)
293{
294 if (auto* renderer = this->renderer())
295 return renderer->startAnimation(timeOffset, m_animation, m_keyframes);
296 return false;
297}
298
299void KeyframeAnimation::pauseAnimation(double timeOffset)
300{
301 if (!element())
302 return;
303
304 if (auto* renderer = this->renderer())
305 renderer->animationPaused(timeOffset, m_keyframes.animationName());
306
307 // Restore the original (unanimated) style
308 if (!paused())
309 setNeedsStyleRecalc(element());
310}
311
312void KeyframeAnimation::endAnimation(bool fillingForwards)
313{
314 if (!element())
315 return;
316
317 if (auto* renderer = this->renderer())
318 renderer->animationFinished(m_keyframes.animationName());
319
320 // Restore the original (unanimated) style
321 if (!fillingForwards && !paused())
322 setNeedsStyleRecalc(element());
323}
324
325bool KeyframeAnimation::shouldSendEventForListener(Document::ListenerType listenerType) const
326{
327 return element()->document().hasListenerType(listenerType);
328}
329
330void KeyframeAnimation::onAnimationStart(double elapsedTime)
331{
332 sendAnimationEvent(eventNames().animationstartEvent, elapsedTime);
333}
334
335void KeyframeAnimation::onAnimationIteration(double elapsedTime)
336{
337 sendAnimationEvent(eventNames().animationiterationEvent, elapsedTime);
338}
339
340void KeyframeAnimation::onAnimationEnd(double elapsedTime)
341{
342 sendAnimationEvent(eventNames().animationendEvent, elapsedTime);
343 endAnimation(m_animation->fillsForwards());
344}
345
346bool KeyframeAnimation::sendAnimationEvent(const AtomString& eventType, double elapsedTime)
347{
348 Document::ListenerType listenerType;
349 if (eventType == eventNames().webkitAnimationIterationEvent || eventType == eventNames().animationiterationEvent)
350 listenerType = Document::ANIMATIONITERATION_LISTENER;
351 else if (eventType == eventNames().webkitAnimationEndEvent || eventType == eventNames().animationendEvent)
352 listenerType = Document::ANIMATIONEND_LISTENER;
353 else {
354 ASSERT(eventType == eventNames().webkitAnimationStartEvent || eventType == eventNames().animationstartEvent);
355 if (m_startEventDispatched)
356 return false;
357 m_startEventDispatched = true;
358 listenerType = Document::ANIMATIONSTART_LISTENER;
359 }
360
361 if (shouldSendEventForListener(listenerType)) {
362 // Dispatch the event
363 auto element = makeRefPtr(this->element());
364
365 ASSERT(!element || element->document().pageCacheState() == Document::NotInPageCache);
366 if (!element)
367 return false;
368
369 // Schedule event handling
370 m_compositeAnimation->animationController().addEventToDispatch(*element, eventType, m_keyframes.animationName(), elapsedTime);
371
372 // Restore the original (unanimated) style
373 if ((eventType == eventNames().webkitAnimationEndEvent || eventType == eventNames().animationendEvent) && element->renderer())
374 setNeedsStyleRecalc(element.get());
375
376 return true; // Did dispatch an event
377 }
378
379 return false; // Did not dispatch an event
380}
381
382void KeyframeAnimation::overrideAnimations()
383{
384 // This will override implicit animations that match the properties in the keyframe animation
385 for (auto propertyID : m_keyframes.properties())
386 compositeAnimation()->overrideImplicitAnimations(propertyID);
387}
388
389void KeyframeAnimation::resumeOverriddenAnimations()
390{
391 // This will resume overridden implicit animations
392 for (auto propertyID : m_keyframes.properties())
393 compositeAnimation()->resumeOverriddenImplicitAnimations(propertyID);
394}
395
396bool KeyframeAnimation::affectsProperty(CSSPropertyID property) const
397{
398 return m_keyframes.containsProperty(property);
399}
400
401void KeyframeAnimation::resolveKeyframeStyles()
402{
403 if (!element())
404 return;
405
406 if (auto* styleScope = Style::Scope::forOrdinal(*element(), m_animation->nameStyleScopeOrdinal()))
407 styleScope->resolver().keyframeStylesForAnimation(*element(), m_unanimatedStyle.get(), m_keyframes);
408
409 // Ensure resource loads for all the frames.
410 for (auto& keyframe : m_keyframes.keyframes()) {
411 if (auto* style = const_cast<RenderStyle*>(keyframe.style()))
412 Style::loadPendingResources(*style, element()->document(), element());
413 }
414}
415
416void KeyframeAnimation::validateTransformFunctionList()
417{
418 m_transformFunctionListsMatch = false;
419
420 if (m_keyframes.size() < 2 || !m_keyframes.containsProperty(CSSPropertyTransform))
421 return;
422
423 // Empty transforms match anything, so find the first non-empty entry as the reference
424 size_t numKeyframes = m_keyframes.size();
425 size_t firstNonEmptyTransformKeyframeIndex = numKeyframes;
426
427 for (size_t i = 0; i < numKeyframes; ++i) {
428 const KeyframeValue& currentKeyframe = m_keyframes[i];
429 if (currentKeyframe.style()->transform().operations().size()) {
430 firstNonEmptyTransformKeyframeIndex = i;
431 break;
432 }
433 }
434
435 if (firstNonEmptyTransformKeyframeIndex == numKeyframes)
436 return;
437
438 const TransformOperations* firstVal = &m_keyframes[firstNonEmptyTransformKeyframeIndex].style()->transform();
439
440 // See if the keyframes are valid
441 for (size_t i = firstNonEmptyTransformKeyframeIndex + 1; i < numKeyframes; ++i) {
442 const KeyframeValue& currentKeyframe = m_keyframes[i];
443 const TransformOperations* val = &currentKeyframe.style()->transform();
444
445 // An emtpy transform list matches anything.
446 if (val->operations().isEmpty())
447 continue;
448
449 if (!firstVal->operationsMatch(*val))
450 return;
451 }
452
453 m_transformFunctionListsMatch = true;
454}
455
456bool KeyframeAnimation::checkForMatchingFilterFunctionLists(CSSPropertyID propertyID, const std::function<const FilterOperations& (const RenderStyle&)>& filtersGetter) const
457{
458 if (m_keyframes.size() < 2 || !m_keyframes.containsProperty(propertyID))
459 return false;
460
461 // Empty filters match anything, so find the first non-empty entry as the reference
462 size_t numKeyframes = m_keyframes.size();
463 size_t firstNonEmptyKeyframeIndex = numKeyframes;
464
465 for (size_t i = 0; i < numKeyframes; ++i) {
466 if (filtersGetter(*m_keyframes[i].style()).operations().size()) {
467 firstNonEmptyKeyframeIndex = i;
468 break;
469 }
470 }
471
472 if (firstNonEmptyKeyframeIndex == numKeyframes)
473 return false;
474
475 auto& firstVal = filtersGetter(*m_keyframes[firstNonEmptyKeyframeIndex].style());
476
477 for (size_t i = firstNonEmptyKeyframeIndex + 1; i < numKeyframes; ++i) {
478 auto& value = filtersGetter(*m_keyframes[i].style());
479
480 // An emtpy filter list matches anything.
481 if (value.operations().isEmpty())
482 continue;
483
484 if (!firstVal.operationsMatch(value))
485 return false;
486 }
487
488 return true;
489}
490
491void KeyframeAnimation::checkForMatchingFilterFunctionLists()
492{
493 m_filterFunctionListsMatch = checkForMatchingFilterFunctionLists(CSSPropertyFilter, [] (const RenderStyle& style) -> const FilterOperations& {
494 return style.filter();
495 });
496}
497
498#if ENABLE(FILTERS_LEVEL_2)
499void KeyframeAnimation::checkForMatchingBackdropFilterFunctionLists()
500{
501 m_backdropFilterFunctionListsMatch = checkForMatchingFilterFunctionLists(CSSPropertyWebkitBackdropFilter, [] (const RenderStyle& style) -> const FilterOperations& {
502 return style.backdropFilter();
503 });
504}
505#endif
506
507void KeyframeAnimation::checkForMatchingColorFilterFunctionLists()
508{
509 m_colorFilterFunctionListsMatch = checkForMatchingFilterFunctionLists(CSSPropertyAppleColorFilter, [] (const RenderStyle& style) -> const FilterOperations& {
510 return style.appleColorFilter();
511 });
512}
513
514Optional<Seconds> KeyframeAnimation::timeToNextService()
515{
516 Optional<Seconds> t = AnimationBase::timeToNextService();
517 if (!t || t.value() != 0_s || preActive())
518 return t;
519
520 // A return value of 0 means we need service. But if we only have accelerated animations we
521 // only need service at the end of the transition.
522 bool acceleratedPropertiesOnly = true;
523
524 for (auto propertyID : m_keyframes.properties()) {
525 if (!CSSPropertyAnimation::animationOfPropertyIsAccelerated(propertyID) || !isAccelerated()) {
526 acceleratedPropertiesOnly = false;
527 break;
528 }
529 }
530
531 if (acceleratedPropertiesOnly) {
532 bool isLooping;
533 getTimeToNextEvent(t.value(), isLooping);
534 }
535
536 return t;
537}
538
539} // namespace WebCore
540