1/*
2 * Copyright (C) 2018 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 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "DeclarativeAnimation.h"
28
29#include "Animation.h"
30#include "AnimationEvent.h"
31#include "CSSAnimation.h"
32#include "CSSTransition.h"
33#include "DocumentTimeline.h"
34#include "Element.h"
35#include "EventNames.h"
36#include "KeyframeEffect.h"
37#include "PseudoElement.h"
38#include "TransitionEvent.h"
39#include <wtf/IsoMallocInlines.h>
40
41namespace WebCore {
42
43WTF_MAKE_ISO_ALLOCATED_IMPL(DeclarativeAnimation);
44
45DeclarativeAnimation::DeclarativeAnimation(Element& owningElement, const Animation& backingAnimation)
46 : WebAnimation(owningElement.document())
47 , m_eventQueue(owningElement)
48 , m_owningElement(&owningElement)
49 , m_backingAnimation(const_cast<Animation&>(backingAnimation))
50{
51}
52
53DeclarativeAnimation::~DeclarativeAnimation()
54{
55}
56
57void DeclarativeAnimation::tick()
58{
59 bool wasRelevant = isRelevant();
60
61 WebAnimation::tick();
62 invalidateDOMEvents();
63
64 // If a declarative animation transitions from a non-idle state to an idle state, it means it was
65 // canceled using the Web Animations API and it should be disassociated from its owner element.
66 // From this point on, this animation is like any other animation and should not appear in the
67 // maps containing running CSS Transitions and CSS Animations for a given element.
68 if (wasRelevant && playState() == WebAnimation::PlayState::Idle) {
69 disassociateFromOwningElement();
70 m_eventQueue.close();
71 }
72}
73
74void DeclarativeAnimation::disassociateFromOwningElement()
75{
76 if (!m_owningElement)
77 return;
78
79 if (auto* animationTimeline = timeline())
80 animationTimeline->removeDeclarativeAnimationFromListsForOwningElement(*this, *m_owningElement);
81 m_owningElement = nullptr;
82}
83
84bool DeclarativeAnimation::needsTick() const
85{
86 return WebAnimation::needsTick() || m_eventQueue.hasPendingEvents();
87}
88
89void DeclarativeAnimation::remove()
90{
91 m_eventQueue.close();
92 WebAnimation::remove();
93}
94
95void DeclarativeAnimation::setBackingAnimation(const Animation& backingAnimation)
96{
97 m_backingAnimation = const_cast<Animation&>(backingAnimation);
98 syncPropertiesWithBackingAnimation();
99}
100
101void DeclarativeAnimation::initialize(const RenderStyle* oldStyle, const RenderStyle& newStyle)
102{
103 // We need to suspend invalidation of the animation's keyframe effect during its creation
104 // as it would otherwise trigger invalidation of the document's style and this would be
105 // incorrect since it would happen during style invalidation.
106 suspendEffectInvalidation();
107
108 ASSERT(m_owningElement);
109
110 setEffect(KeyframeEffect::create(*m_owningElement));
111 setTimeline(&m_owningElement->document().timeline());
112 downcast<KeyframeEffect>(effect())->computeDeclarativeAnimationBlendingKeyframes(oldStyle, newStyle);
113 syncPropertiesWithBackingAnimation();
114 if (backingAnimation().playState() == AnimationPlayState::Playing)
115 play();
116 else
117 pause();
118
119 unsuspendEffectInvalidation();
120}
121
122void DeclarativeAnimation::syncPropertiesWithBackingAnimation()
123{
124}
125
126Optional<double> DeclarativeAnimation::startTime() const
127{
128 flushPendingStyleChanges();
129 return WebAnimation::startTime();
130}
131
132void DeclarativeAnimation::setStartTime(Optional<double> startTime)
133{
134 flushPendingStyleChanges();
135 return WebAnimation::setStartTime(startTime);
136}
137
138Optional<double> DeclarativeAnimation::bindingsCurrentTime() const
139{
140 flushPendingStyleChanges();
141 return WebAnimation::bindingsCurrentTime();
142}
143
144ExceptionOr<void> DeclarativeAnimation::setBindingsCurrentTime(Optional<double> currentTime)
145{
146 flushPendingStyleChanges();
147 return WebAnimation::setBindingsCurrentTime(currentTime);
148}
149
150WebAnimation::PlayState DeclarativeAnimation::bindingsPlayState() const
151{
152 flushPendingStyleChanges();
153 return WebAnimation::bindingsPlayState();
154}
155
156bool DeclarativeAnimation::bindingsPending() const
157{
158 flushPendingStyleChanges();
159 return WebAnimation::bindingsPending();
160}
161
162WebAnimation::ReadyPromise& DeclarativeAnimation::bindingsReady()
163{
164 flushPendingStyleChanges();
165 return WebAnimation::bindingsReady();
166}
167
168WebAnimation::FinishedPromise& DeclarativeAnimation::bindingsFinished()
169{
170 flushPendingStyleChanges();
171 return WebAnimation::bindingsFinished();
172}
173
174ExceptionOr<void> DeclarativeAnimation::bindingsPlay()
175{
176 flushPendingStyleChanges();
177 return WebAnimation::bindingsPlay();
178}
179
180ExceptionOr<void> DeclarativeAnimation::bindingsPause()
181{
182 flushPendingStyleChanges();
183 return WebAnimation::bindingsPause();
184}
185
186void DeclarativeAnimation::flushPendingStyleChanges() const
187{
188 if (auto* animationEffect = effect()) {
189 if (is<KeyframeEffect>(animationEffect)) {
190 if (auto* target = downcast<KeyframeEffect>(animationEffect)->target())
191 target->document().updateStyleIfNeeded();
192 }
193 }
194}
195
196void DeclarativeAnimation::setTimeline(RefPtr<AnimationTimeline>&& newTimeline)
197{
198 if (timeline() && !newTimeline)
199 cancel();
200
201 WebAnimation::setTimeline(WTFMove(newTimeline));
202}
203
204void DeclarativeAnimation::cancel()
205{
206 auto cancelationTime = 0_s;
207 if (auto* animationEffect = effect()) {
208 if (auto activeTime = animationEffect->getBasicTiming().activeTime)
209 cancelationTime = *activeTime;
210 }
211
212 WebAnimation::cancel();
213
214 invalidateDOMEvents(cancelationTime);
215}
216
217void DeclarativeAnimation::cancelFromStyle()
218{
219 cancel();
220 disassociateFromOwningElement();
221}
222
223AnimationEffectPhase DeclarativeAnimation::phaseWithoutEffect() const
224{
225 // This shouldn't be called if we actually have an effect.
226 ASSERT(!effect());
227
228 auto animationCurrentTime = currentTime();
229 if (!animationCurrentTime)
230 return AnimationEffectPhase::Idle;
231
232 // Since we don't have an effect, the duration will be zero so the phase is 'before' if the current time is less than zero.
233 return *animationCurrentTime < 0_s ? AnimationEffectPhase::Before : AnimationEffectPhase::After;
234}
235
236void DeclarativeAnimation::invalidateDOMEvents(Seconds elapsedTime)
237{
238 if (!m_owningElement)
239 return;
240
241 auto isPending = pending();
242 if (isPending && m_wasPending)
243 return;
244
245 double iteration = 0;
246 AnimationEffectPhase currentPhase;
247 Seconds intervalStart;
248 Seconds intervalEnd;
249
250 auto* animationEffect = effect();
251 if (animationEffect) {
252 auto timing = animationEffect->getComputedTiming();
253 if (auto computedIteration = timing.currentIteration)
254 iteration = *computedIteration;
255 currentPhase = timing.phase;
256 intervalStart = std::max(0_s, Seconds::fromMilliseconds(std::min(-timing.delay, timing.activeDuration)));
257 intervalEnd = std::max(0_s, Seconds::fromMilliseconds(std::min(timing.endTime - timing.delay, timing.activeDuration)));
258 } else {
259 iteration = 0;
260 currentPhase = phaseWithoutEffect();
261 intervalStart = 0_s;
262 intervalEnd = 0_s;
263 }
264
265 bool wasActive = m_previousPhase == AnimationEffectPhase::Active;
266 bool wasAfter = m_previousPhase == AnimationEffectPhase::After;
267 bool wasBefore = m_previousPhase == AnimationEffectPhase::Before;
268 bool wasIdle = m_previousPhase == AnimationEffectPhase::Idle;
269
270 bool isActive = currentPhase == AnimationEffectPhase::Active;
271 bool isAfter = currentPhase == AnimationEffectPhase::After;
272 bool isBefore = currentPhase == AnimationEffectPhase::Before;
273 bool isIdle = currentPhase == AnimationEffectPhase::Idle;
274
275 if (is<CSSAnimation>(this)) {
276 // https://drafts.csswg.org/css-animations-2/#events
277 if ((wasIdle || wasBefore) && isActive)
278 enqueueDOMEvent(eventNames().animationstartEvent, intervalStart);
279 else if ((wasIdle || wasBefore) && isAfter) {
280 enqueueDOMEvent(eventNames().animationstartEvent, intervalStart);
281 enqueueDOMEvent(eventNames().animationendEvent, intervalEnd);
282 } else if (wasActive && isBefore)
283 enqueueDOMEvent(eventNames().animationendEvent, intervalStart);
284 else if (wasActive && isActive && m_previousIteration != iteration) {
285 auto iterationBoundary = iteration;
286 if (m_previousIteration > iteration)
287 iterationBoundary++;
288 auto elapsedTime = animationEffect ? animationEffect->iterationDuration() * (iterationBoundary - animationEffect->iterationStart()) : 0_s;
289 enqueueDOMEvent(eventNames().animationiterationEvent, elapsedTime);
290 } else if (wasActive && isAfter)
291 enqueueDOMEvent(eventNames().animationendEvent, intervalEnd);
292 else if (wasAfter && isActive)
293 enqueueDOMEvent(eventNames().animationstartEvent, intervalEnd);
294 else if (wasAfter && isBefore) {
295 enqueueDOMEvent(eventNames().animationstartEvent, intervalEnd);
296 enqueueDOMEvent(eventNames().animationendEvent, intervalStart);
297 } else if ((!wasIdle && !wasAfter) && isIdle)
298 enqueueDOMEvent(eventNames().animationcancelEvent, elapsedTime);
299 } else if (is<CSSTransition>(this)) {
300 // https://drafts.csswg.org/css-transitions-2/#transition-events
301 if (wasIdle && (isPending || isBefore))
302 enqueueDOMEvent(eventNames().transitionrunEvent, intervalStart);
303 else if (wasIdle && isActive) {
304 enqueueDOMEvent(eventNames().transitionrunEvent, intervalStart);
305 enqueueDOMEvent(eventNames().transitionstartEvent, intervalStart);
306 } else if (wasIdle && isAfter) {
307 enqueueDOMEvent(eventNames().transitionrunEvent, intervalStart);
308 enqueueDOMEvent(eventNames().transitionstartEvent, intervalStart);
309 enqueueDOMEvent(eventNames().transitionendEvent, intervalEnd);
310 } else if ((m_wasPending || wasBefore) && isActive)
311 enqueueDOMEvent(eventNames().transitionstartEvent, intervalStart);
312 else if ((m_wasPending || wasBefore) && isAfter) {
313 enqueueDOMEvent(eventNames().transitionstartEvent, intervalStart);
314 enqueueDOMEvent(eventNames().transitionendEvent, intervalEnd);
315 } else if (wasActive && isAfter)
316 enqueueDOMEvent(eventNames().transitionendEvent, intervalEnd);
317 else if (wasActive && isBefore)
318 enqueueDOMEvent(eventNames().transitionendEvent, intervalStart);
319 else if (wasAfter && isActive)
320 enqueueDOMEvent(eventNames().transitionstartEvent, intervalEnd);
321 else if (wasAfter && isBefore) {
322 enqueueDOMEvent(eventNames().transitionstartEvent, intervalEnd);
323 enqueueDOMEvent(eventNames().transitionendEvent, intervalStart);
324 } else if ((!wasIdle && !wasAfter) && isIdle)
325 enqueueDOMEvent(eventNames().transitioncancelEvent, elapsedTime);
326 }
327
328 m_wasPending = isPending;
329 m_previousPhase = currentPhase;
330 m_previousIteration = iteration;
331}
332
333void DeclarativeAnimation::enqueueDOMEvent(const AtomicString& eventType, Seconds elapsedTime)
334{
335 ASSERT(m_owningElement);
336 auto time = secondsToWebAnimationsAPITime(elapsedTime) / 1000;
337 if (is<CSSAnimation>(this))
338 m_eventQueue.enqueueEvent(AnimationEvent::create(eventType, downcast<CSSAnimation>(this)->animationName(), time));
339 else if (is<CSSTransition>(this))
340 m_eventQueue.enqueueEvent(TransitionEvent::create(eventType, downcast<CSSTransition>(this)->transitionProperty(), time, PseudoElement::pseudoElementNameForEvents(m_owningElement->pseudoId())));
341}
342
343void DeclarativeAnimation::stop()
344{
345 m_eventQueue.close();
346 WebAnimation::stop();
347}
348
349void DeclarativeAnimation::suspend(ReasonForSuspension reason)
350{
351 m_eventQueue.suspend();
352 WebAnimation::suspend(reason);
353}
354
355void DeclarativeAnimation::resume()
356{
357 m_eventQueue.resume();
358 WebAnimation::resume();
359}
360
361} // namespace WebCore
362