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