1/*
2 * Copyright (C) 2007 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 "ImplicitAnimation.h"
31
32#include "CSSAnimationControllerPrivate.h"
33#include "CSSPropertyAnimation.h"
34#include "CompositeAnimation.h"
35#if PLATFORM(IOS_FAMILY)
36#include "ContentChangeObserver.h"
37#endif
38#include "EventNames.h"
39#include "GeometryUtilities.h"
40#include "KeyframeAnimation.h"
41#include "RenderBox.h"
42#include "StylePendingResources.h"
43
44namespace WebCore {
45
46ImplicitAnimation::ImplicitAnimation(const Animation& transition, CSSPropertyID animatingProperty, Element& element, CompositeAnimation& compositeAnimation, const RenderStyle& fromStyle)
47 : AnimationBase(transition, element, compositeAnimation)
48 , m_fromStyle(RenderStyle::clonePtr(fromStyle))
49 , m_transitionProperty(transition.property())
50 , m_animatingProperty(animatingProperty)
51{
52#if PLATFORM(IOS_FAMILY)
53 element.document().contentChangeObserver().didAddTransition(element, transition);
54#endif
55 ASSERT(animatingProperty != CSSPropertyInvalid);
56}
57
58ImplicitAnimation::~ImplicitAnimation()
59{
60#if PLATFORM(IOS_FAMILY)
61 if (auto* element = this->element())
62 element->document().contentChangeObserver().didRemoveTransition(*element, m_animatingProperty);
63#endif
64 // // Make sure to tell the renderer that we are ending. This will make sure any accelerated animations are removed.
65 if (!postActive())
66 endAnimation();
67}
68
69bool ImplicitAnimation::shouldSendEventForListener(Document::ListenerType inListenerType) const
70{
71 return element()->document().hasListenerType(inListenerType);
72}
73
74OptionSet<AnimateChange> ImplicitAnimation::animate(CompositeAnimation& compositeAnimation, const RenderStyle& targetStyle, std::unique_ptr<RenderStyle>& animatedStyle)
75{
76 // If we get this far and the animation is done, it means we are cleaning up a just finished animation.
77 // So just return. Everything is already all cleaned up.
78 if (postActive())
79 return { };
80
81 AnimationState oldState = state();
82
83 // Reset to start the transition if we are new
84 if (isNew())
85 reset(targetStyle, compositeAnimation);
86
87 // Run a cycle of animation.
88 // We know we will need a new render style, so make one if needed
89 if (!animatedStyle)
90 animatedStyle = RenderStyle::clonePtr(targetStyle);
91
92 CSSPropertyAnimation::blendProperties(this, m_animatingProperty, animatedStyle.get(), m_fromStyle.get(), m_toStyle.get(), progress());
93 // FIXME: we also need to detect cases where we have to software animate for other reasons,
94 // such as a child using inheriting the transform. https://bugs.webkit.org/show_bug.cgi?id=23902
95
96 // Fire the start timeout if needed
97 fireAnimationEventsIfNeeded();
98
99 OptionSet<AnimateChange> change(AnimateChange::StyleBlended);
100 if (state() != oldState)
101 change.add(AnimateChange::StateChange);
102
103 if ((isPausedState(oldState) || isRunningState(oldState)) != (isPausedState(state()) || isRunningState(state())))
104 change.add(AnimateChange::RunningStateChange);
105
106 return change;
107}
108
109void ImplicitAnimation::getAnimatedStyle(std::unique_ptr<RenderStyle>& animatedStyle)
110{
111 if (!animatedStyle)
112 animatedStyle = RenderStyle::clonePtr(*m_toStyle);
113
114 CSSPropertyAnimation::blendProperties(this, m_animatingProperty, animatedStyle.get(), m_fromStyle.get(), m_toStyle.get(), progress());
115}
116
117bool ImplicitAnimation::computeExtentOfTransformAnimation(LayoutRect& bounds) const
118{
119 ASSERT(hasStyle());
120
121 if (!is<RenderBox>(renderer()))
122 return true; // Non-boxes don't get transformed;
123
124 ASSERT(m_animatingProperty == CSSPropertyTransform);
125
126 RenderBox& box = downcast<RenderBox>(*renderer());
127 FloatRect rendererBox = snapRectToDevicePixels(box.borderBoxRect(), box.document().deviceScaleFactor());
128
129 LayoutRect startBounds = bounds;
130 LayoutRect endBounds = bounds;
131
132 if (transformFunctionListsMatch()) {
133 if (!computeTransformedExtentViaTransformList(rendererBox, *m_fromStyle, startBounds))
134 return false;
135
136 if (!computeTransformedExtentViaTransformList(rendererBox, *m_toStyle, endBounds))
137 return false;
138 } else {
139 if (!computeTransformedExtentViaMatrix(rendererBox, *m_fromStyle, startBounds))
140 return false;
141
142 if (!computeTransformedExtentViaMatrix(rendererBox, *m_toStyle, endBounds))
143 return false;
144 }
145
146 bounds = unionRect(startBounds, endBounds);
147 return true;
148}
149
150bool ImplicitAnimation::affectsAcceleratedProperty() const
151{
152 return CSSPropertyAnimation::animationOfPropertyIsAccelerated(m_animatingProperty);
153}
154
155bool ImplicitAnimation::startAnimation(double timeOffset)
156{
157 if (auto* renderer = this->renderer())
158 return renderer->startTransition(timeOffset, m_animatingProperty, m_fromStyle.get(), m_toStyle.get());
159 return false;
160}
161
162void ImplicitAnimation::pauseAnimation(double timeOffset)
163{
164 if (auto* renderer = this->renderer())
165 renderer->transitionPaused(timeOffset, m_animatingProperty);
166 // Restore the original (unanimated) style
167 if (!paused())
168 setNeedsStyleRecalc(element());
169}
170
171void ImplicitAnimation::clear()
172{
173#if PLATFORM(IOS_FAMILY)
174 if (auto* element = this->element())
175 element->document().contentChangeObserver().didRemoveTransition(*element, m_animatingProperty);
176#endif
177 AnimationBase::clear();
178}
179
180void ImplicitAnimation::endAnimation(bool)
181{
182 if (auto* renderer = this->renderer())
183 renderer->transitionFinished(m_animatingProperty);
184}
185
186void ImplicitAnimation::onAnimationEnd(double elapsedTime)
187{
188#if PLATFORM(IOS_FAMILY)
189 if (auto* element = this->element())
190 element->document().contentChangeObserver().didFinishTransition(*element, m_animatingProperty);
191#endif
192 // If we have a keyframe animation on this property, this transition is being overridden. The keyframe
193 // animation keeps an unanimated style in case a transition starts while the keyframe animation is
194 // running. But now that the transition has completed, we need to update this style with its new
195 // destination. If we didn't, the next time through we would think a transition had started
196 // (comparing the old unanimated style with the new final style of the transition).
197 if (auto* animation = m_compositeAnimation->animationForProperty(m_animatingProperty))
198 animation->setUnanimatedStyle(RenderStyle::clonePtr(*m_toStyle));
199
200 sendTransitionEvent(eventNames().transitionendEvent, elapsedTime);
201 endAnimation();
202}
203
204bool ImplicitAnimation::sendTransitionEvent(const AtomString& eventType, double elapsedTime)
205{
206 if (eventType == eventNames().transitionendEvent) {
207 Document::ListenerType listenerType = Document::TRANSITIONEND_LISTENER;
208
209 if (shouldSendEventForListener(listenerType)) {
210 String propertyName = getPropertyNameString(m_animatingProperty);
211
212 // Dispatch the event
213 auto element = makeRefPtr(this->element());
214
215 ASSERT(!element || element->document().pageCacheState() == Document::NotInPageCache);
216 if (!element)
217 return false;
218
219 // Schedule event handling
220 m_compositeAnimation->animationController().addEventToDispatch(*element, eventType, propertyName, elapsedTime);
221
222 // Restore the original (unanimated) style
223 if (eventType == eventNames().transitionendEvent && element->renderer())
224 setNeedsStyleRecalc(element.get());
225
226 return true; // Did dispatch an event
227 }
228 }
229
230 return false; // Didn't dispatch an event
231}
232
233void ImplicitAnimation::reset(const RenderStyle& to, CompositeAnimation& compositeAnimation)
234{
235 ASSERT(m_fromStyle);
236
237 m_toStyle = RenderStyle::clonePtr(to);
238
239 if (element())
240 Style::loadPendingResources(*m_toStyle, element()->document(), element());
241
242 // Restart the transition.
243 if (m_fromStyle && m_toStyle && !compositeAnimation.isSuspended())
244 updateStateMachine(AnimationStateInput::RestartAnimation, -1);
245
246 // Set the transform animation list.
247 validateTransformFunctionList();
248 checkForMatchingFilterFunctionLists();
249#if ENABLE(FILTERS_LEVEL_2)
250 checkForMatchingBackdropFilterFunctionLists();
251#endif
252 checkForMatchingColorFilterFunctionLists();
253}
254
255void ImplicitAnimation::setOverridden(bool b)
256{
257 if (b == m_overridden)
258 return;
259
260 m_overridden = b;
261 updateStateMachine(m_overridden ? AnimationStateInput::PauseOverride : AnimationStateInput::ResumeOverride, -1);
262}
263
264bool ImplicitAnimation::affectsProperty(CSSPropertyID property) const
265{
266 return (m_animatingProperty == property);
267}
268
269bool ImplicitAnimation::isTargetPropertyEqual(CSSPropertyID prop, const RenderStyle* targetStyle)
270{
271 // We can get here for a transition that has not started yet. This would make m_toStyle unset and null.
272 // So we check that here (see <https://bugs.webkit.org/show_bug.cgi?id=26706>)
273 if (!m_toStyle)
274 return false;
275 return CSSPropertyAnimation::propertiesEqual(prop, m_toStyle.get(), targetStyle);
276}
277
278void ImplicitAnimation::blendPropertyValueInStyle(CSSPropertyID prop, RenderStyle* currentStyle)
279{
280 // We should never add a transition with a 0 duration and delay. But if we ever did
281 // it would have a null toStyle. So just in case, let's check that here. (See
282 // <https://bugs.webkit.org/show_bug.cgi?id=24787>
283 if (!m_toStyle)
284 return;
285
286 CSSPropertyAnimation::blendProperties(this, prop, currentStyle, m_fromStyle.get(), m_toStyle.get(), progress());
287}
288
289void ImplicitAnimation::validateTransformFunctionList()
290{
291 m_transformFunctionListsMatch = false;
292
293 if (!m_fromStyle || !m_toStyle)
294 return;
295
296 const TransformOperations* val = &m_fromStyle->transform();
297 const TransformOperations* toVal = &m_toStyle->transform();
298
299 if (val->operations().isEmpty())
300 val = toVal;
301
302 if (val->operations().isEmpty())
303 return;
304
305 // An empty transform list matches anything.
306 if (val != toVal && !toVal->operations().isEmpty() && !val->operationsMatch(*toVal))
307 return;
308
309 // Transform lists match.
310 m_transformFunctionListsMatch = true;
311}
312
313static bool filterOperationsMatch(const FilterOperations* fromOperations, const FilterOperations& toOperations)
314{
315 if (fromOperations->operations().isEmpty())
316 fromOperations = &toOperations;
317
318 if (fromOperations->operations().isEmpty())
319 return false;
320
321 if (fromOperations != &toOperations && !toOperations.operations().isEmpty() && !fromOperations->operationsMatch(toOperations))
322 return false;
323
324 return true;
325}
326
327void ImplicitAnimation::checkForMatchingFilterFunctionLists()
328{
329 m_filterFunctionListsMatch = false;
330
331 if (!m_fromStyle || !m_toStyle)
332 return;
333
334 m_filterFunctionListsMatch = filterOperationsMatch(&m_fromStyle->filter(), m_toStyle->filter());
335}
336
337#if ENABLE(FILTERS_LEVEL_2)
338void ImplicitAnimation::checkForMatchingBackdropFilterFunctionLists()
339{
340 m_backdropFilterFunctionListsMatch = false;
341
342 if (!m_fromStyle || !m_toStyle)
343 return;
344
345 m_backdropFilterFunctionListsMatch = filterOperationsMatch(&m_fromStyle->backdropFilter(), m_toStyle->backdropFilter());
346}
347#endif
348
349void ImplicitAnimation::checkForMatchingColorFilterFunctionLists()
350{
351 m_filterFunctionListsMatch = false;
352
353 if (!m_fromStyle || !m_toStyle)
354 return;
355
356 m_colorFilterFunctionListsMatch = filterOperationsMatch(&m_fromStyle->appleColorFilter(), m_toStyle->appleColorFilter());
357}
358
359Optional<Seconds> ImplicitAnimation::timeToNextService()
360{
361 Optional<Seconds> t = AnimationBase::timeToNextService();
362 if (!t || t.value() != 0_s || preActive())
363 return t;
364
365 // A return value of 0 means we need service. But if this is an accelerated animation we
366 // only need service at the end of the transition.
367 if (CSSPropertyAnimation::animationOfPropertyIsAccelerated(m_animatingProperty) && isAccelerated()) {
368 bool isLooping;
369 getTimeToNextEvent(t.value(), isLooping);
370 }
371 return t;
372}
373
374} // namespace WebCore
375