1 | /* |
2 | * Copyright (C) Canon Inc. 2016 |
3 | * Copyright (C) 2017 Apple Inc. All rights reserved. |
4 | * |
5 | * Redistribution and use in source and binary forms, with or without |
6 | * modification, are permitted provided that the following conditions |
7 | * are met: |
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 | * |
14 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
15 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
17 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
18 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
19 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
20 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
21 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
22 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
23 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
24 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
25 | */ |
26 | |
27 | #include "config.h" |
28 | #include "AnimationTimeline.h" |
29 | |
30 | #include "Animation.h" |
31 | #include "AnimationEffect.h" |
32 | #include "AnimationList.h" |
33 | #include "CSSAnimation.h" |
34 | #include "CSSPropertyAnimation.h" |
35 | #include "CSSTransition.h" |
36 | #include "DocumentTimeline.h" |
37 | #include "Element.h" |
38 | #include "KeyframeEffect.h" |
39 | #include "RenderStyle.h" |
40 | #include "RenderView.h" |
41 | #include "StylePropertyShorthand.h" |
42 | #include "StyleResolver.h" |
43 | #include "WebAnimationUtilities.h" |
44 | #include <wtf/text/TextStream.h> |
45 | #include <wtf/text/WTFString.h> |
46 | |
47 | namespace WebCore { |
48 | |
49 | AnimationTimeline::AnimationTimeline() |
50 | { |
51 | } |
52 | |
53 | AnimationTimeline::~AnimationTimeline() |
54 | { |
55 | } |
56 | |
57 | void AnimationTimeline::forgetAnimation(WebAnimation* animation) |
58 | { |
59 | m_allAnimations.removeFirst(animation); |
60 | } |
61 | |
62 | void AnimationTimeline::animationTimingDidChange(WebAnimation& animation) |
63 | { |
64 | if (m_animations.add(&animation)) { |
65 | m_allAnimations.append(makeWeakPtr(&animation)); |
66 | auto* timeline = animation.timeline(); |
67 | if (timeline && timeline != this) |
68 | timeline->removeAnimation(animation); |
69 | } |
70 | } |
71 | |
72 | void AnimationTimeline::removeAnimation(WebAnimation& animation) |
73 | { |
74 | ASSERT(!animation.timeline() || animation.timeline() == this); |
75 | m_animations.remove(&animation); |
76 | if (is<KeyframeEffect>(animation.effect())) { |
77 | if (auto* target = downcast<KeyframeEffect>(animation.effect())->target()) |
78 | animationWasRemovedFromElement(animation, *target); |
79 | } |
80 | } |
81 | |
82 | Optional<double> AnimationTimeline::bindingsCurrentTime() |
83 | { |
84 | auto time = currentTime(); |
85 | if (!time) |
86 | return WTF::nullopt; |
87 | return secondsToWebAnimationsAPITime(*time); |
88 | } |
89 | |
90 | void AnimationTimeline::animationWasAddedToElement(WebAnimation& animation, Element& element) |
91 | { |
92 | [&] () -> ElementToAnimationsMap& { |
93 | if (is<CSSTransition>(animation) && downcast<CSSTransition>(animation).owningElement()) |
94 | return m_elementToCSSTransitionsMap; |
95 | if (is<CSSAnimation>(animation) && downcast<CSSAnimation>(animation).owningElement()) |
96 | return m_elementToCSSAnimationsMap; |
97 | return m_elementToAnimationsMap; |
98 | }().ensure(&element, [] { |
99 | return ListHashSet<RefPtr<WebAnimation>> { }; |
100 | }).iterator->value.add(&animation); |
101 | } |
102 | |
103 | static inline bool removeCSSTransitionFromMap(CSSTransition& transition, Element& element, HashMap<Element*, AnimationTimeline::PropertyToTransitionMap>& map) |
104 | { |
105 | auto iterator = map.find(&element); |
106 | if (iterator == map.end()) |
107 | return false; |
108 | |
109 | auto& cssTransitionsByProperty = iterator->value; |
110 | |
111 | auto transitionIterator = cssTransitionsByProperty.find(transition.property()); |
112 | if (transitionIterator == cssTransitionsByProperty.end() || transitionIterator->value != &transition) |
113 | return false; |
114 | |
115 | cssTransitionsByProperty.remove(transitionIterator); |
116 | |
117 | if (cssTransitionsByProperty.isEmpty()) |
118 | map.remove(&element); |
119 | return true; |
120 | } |
121 | |
122 | static inline void removeAnimationFromMapForElement(WebAnimation& animation, AnimationTimeline::ElementToAnimationsMap& map, Element& element) |
123 | { |
124 | auto iterator = map.find(&element); |
125 | if (iterator == map.end()) |
126 | return; |
127 | |
128 | auto& animations = iterator->value; |
129 | animations.remove(&animation); |
130 | if (!animations.size()) |
131 | map.remove(iterator); |
132 | } |
133 | |
134 | void AnimationTimeline::animationWasRemovedFromElement(WebAnimation& animation, Element& element) |
135 | { |
136 | removeAnimationFromMapForElement(animation, m_elementToCSSTransitionsMap, element); |
137 | removeAnimationFromMapForElement(animation, m_elementToCSSAnimationsMap, element); |
138 | removeAnimationFromMapForElement(animation, m_elementToAnimationsMap, element); |
139 | |
140 | // Now, if we're dealing with a declarative animation, we remove it from either the m_elementToCSSAnimationByName |
141 | // or the m_elementToRunningCSSTransitionByCSSPropertyID map, whichever is relevant to this type of animation. |
142 | if (is<DeclarativeAnimation>(animation)) |
143 | removeDeclarativeAnimationFromListsForOwningElement(animation, element); |
144 | } |
145 | |
146 | void AnimationTimeline::removeDeclarativeAnimationFromListsForOwningElement(WebAnimation& animation, Element& element) |
147 | { |
148 | ASSERT(is<DeclarativeAnimation>(animation)); |
149 | |
150 | if (is<CSSAnimation>(animation)) { |
151 | auto iterator = m_elementToCSSAnimationByName.find(&element); |
152 | if (iterator != m_elementToCSSAnimationByName.end()) { |
153 | auto& cssAnimationsByName = iterator->value; |
154 | auto& name = downcast<CSSAnimation>(animation).animationName(); |
155 | cssAnimationsByName.remove(name); |
156 | if (cssAnimationsByName.isEmpty()) |
157 | m_elementToCSSAnimationByName.remove(&element); |
158 | } |
159 | } else if (is<CSSTransition>(animation)) { |
160 | auto& transition = downcast<CSSTransition>(animation); |
161 | if (!removeCSSTransitionFromMap(transition, element, m_elementToRunningCSSTransitionByCSSPropertyID)) |
162 | removeCSSTransitionFromMap(transition, element, m_elementToCompletedCSSTransitionByCSSPropertyID); |
163 | } |
164 | } |
165 | |
166 | Vector<RefPtr<WebAnimation>> AnimationTimeline::animationsForElement(Element& element, Ordering ordering) const |
167 | { |
168 | Vector<RefPtr<WebAnimation>> animations; |
169 | if (m_elementToCSSTransitionsMap.contains(&element)) { |
170 | const auto& cssTransitions = m_elementToCSSTransitionsMap.get(&element); |
171 | if (ordering == Ordering::Sorted) { |
172 | Vector<RefPtr<WebAnimation>> sortedCSSTransitions; |
173 | sortedCSSTransitions.appendRange(cssTransitions.begin(), cssTransitions.end()); |
174 | std::sort(sortedCSSTransitions.begin(), sortedCSSTransitions.end(), [](auto& lhs, auto& rhs) { |
175 | // Sort transitions first by their generation time, and then by transition-property. |
176 | // https://drafts.csswg.org/css-transitions-2/#animation-composite-order |
177 | auto* lhsTransition = downcast<CSSTransition>(lhs.get()); |
178 | auto* rhsTransition = downcast<CSSTransition>(rhs.get()); |
179 | if (lhsTransition->generationTime() != rhsTransition->generationTime()) |
180 | return lhsTransition->generationTime() < rhsTransition->generationTime(); |
181 | return lhsTransition->transitionProperty().utf8() < rhsTransition->transitionProperty().utf8(); |
182 | }); |
183 | animations.appendVector(sortedCSSTransitions); |
184 | } else |
185 | animations.appendRange(cssTransitions.begin(), cssTransitions.end()); |
186 | } |
187 | if (m_elementToCSSAnimationsMap.contains(&element)) { |
188 | const auto& cssAnimations = m_elementToCSSAnimationsMap.get(&element); |
189 | animations.appendRange(cssAnimations.begin(), cssAnimations.end()); |
190 | } |
191 | if (m_elementToAnimationsMap.contains(&element)) { |
192 | const auto& webAnimations = m_elementToAnimationsMap.get(&element); |
193 | animations.appendRange(webAnimations.begin(), webAnimations.end()); |
194 | } |
195 | return animations; |
196 | } |
197 | |
198 | void AnimationTimeline::elementWasRemoved(Element& element) |
199 | { |
200 | for (auto& animation : animationsForElement(element)) |
201 | animation->cancel(WebAnimation::Silently::Yes); |
202 | } |
203 | |
204 | void AnimationTimeline::removeAnimationsForElement(Element& element) |
205 | { |
206 | for (auto& animation : animationsForElement(element)) |
207 | animation->remove(); |
208 | } |
209 | |
210 | void AnimationTimeline::cancelDeclarativeAnimationsForElement(Element& element) |
211 | { |
212 | for (auto& cssTransition : m_elementToCSSTransitionsMap.get(&element)) |
213 | cssTransition->cancel(); |
214 | for (auto& cssAnimation : m_elementToCSSAnimationsMap.get(&element)) |
215 | cssAnimation->cancel(); |
216 | } |
217 | |
218 | static bool shouldConsiderAnimation(Element& element, const Animation& animation) |
219 | { |
220 | if (!animation.isValidAnimation()) |
221 | return false; |
222 | |
223 | static NeverDestroyed<const String> animationNameNone(MAKE_STATIC_STRING_IMPL("none" )); |
224 | |
225 | auto& name = animation.name(); |
226 | if (name == animationNameNone || name.isEmpty()) |
227 | return false; |
228 | |
229 | if (auto* styleScope = Style::Scope::forOrdinal(element, animation.nameStyleScopeOrdinal())) |
230 | return styleScope->resolver().isAnimationNameValid(name); |
231 | |
232 | return false; |
233 | } |
234 | |
235 | void AnimationTimeline::updateCSSAnimationsForElement(Element& element, const RenderStyle* currentStyle, const RenderStyle& afterChangeStyle) |
236 | { |
237 | // In case this element is newly getting a "display: none" we need to cancel all of its animations and disregard new ones. |
238 | if (currentStyle && currentStyle->hasAnimations() && currentStyle->display() != DisplayType::None && afterChangeStyle.display() == DisplayType::None) { |
239 | if (m_elementToCSSAnimationByName.contains(&element)) { |
240 | for (const auto& cssAnimationsByNameMapItem : m_elementToCSSAnimationByName.take(&element)) |
241 | cancelDeclarativeAnimation(*cssAnimationsByNameMapItem.value); |
242 | } |
243 | return; |
244 | } |
245 | |
246 | if (currentStyle && currentStyle->hasAnimations() && afterChangeStyle.hasAnimations() && *(currentStyle->animations()) == *(afterChangeStyle.animations())) |
247 | return; |
248 | |
249 | // First, compile the list of animation names that were applied to this element up to this point. |
250 | HashSet<String> namesOfPreviousAnimations; |
251 | if (currentStyle && currentStyle->hasAnimations()) { |
252 | auto* previousAnimations = currentStyle->animations(); |
253 | for (size_t i = 0; i < previousAnimations->size(); ++i) { |
254 | auto& previousAnimation = previousAnimations->animation(i); |
255 | if (shouldConsiderAnimation(element, previousAnimation)) |
256 | namesOfPreviousAnimations.add(previousAnimation.name()); |
257 | } |
258 | } |
259 | |
260 | // Create or get the CSSAnimations by animation name map for this element. |
261 | auto& cssAnimationsByName = m_elementToCSSAnimationByName.ensure(&element, [] { |
262 | return HashMap<String, RefPtr<CSSAnimation>> { }; |
263 | }).iterator->value; |
264 | |
265 | if (auto* currentAnimations = afterChangeStyle.animations()) { |
266 | for (size_t i = 0; i < currentAnimations->size(); ++i) { |
267 | auto& currentAnimation = currentAnimations->animation(i); |
268 | auto& name = currentAnimation.name(); |
269 | if (namesOfPreviousAnimations.contains(name)) { |
270 | // We've found the name of this animation in our list of previous animations, this means we've already |
271 | // created a CSSAnimation object for it and need to ensure that this CSSAnimation is backed by the current |
272 | // animation object for this animation name. |
273 | if (auto cssAnimation = cssAnimationsByName.get(name)) |
274 | cssAnimation->setBackingAnimation(currentAnimation); |
275 | } else if (shouldConsiderAnimation(element, currentAnimation)) { |
276 | // Otherwise we are dealing with a new animation name and must create a CSSAnimation for it. |
277 | cssAnimationsByName.set(name, CSSAnimation::create(element, currentAnimation, currentStyle, afterChangeStyle)); |
278 | } |
279 | // Remove the name of this animation from our list since it's now known to be current. |
280 | namesOfPreviousAnimations.remove(name); |
281 | } |
282 | } |
283 | |
284 | // The animations names left in namesOfPreviousAnimations are now known to no longer apply so we need to |
285 | // remove the CSSAnimation object created for them. |
286 | for (const auto& nameOfAnimationToRemove : namesOfPreviousAnimations) { |
287 | if (auto animation = cssAnimationsByName.take(nameOfAnimationToRemove)) |
288 | cancelDeclarativeAnimation(*animation); |
289 | } |
290 | } |
291 | |
292 | RefPtr<WebAnimation> AnimationTimeline::cssAnimationForElementAndProperty(Element& element, CSSPropertyID property) |
293 | { |
294 | RefPtr<WebAnimation> matchingAnimation; |
295 | for (const auto& animation : m_elementToCSSAnimationsMap.get(&element)) { |
296 | auto* effect = animation->effect(); |
297 | if (is<KeyframeEffect>(effect) && downcast<KeyframeEffect>(effect)->animatedProperties().contains(property)) |
298 | matchingAnimation = animation; |
299 | } |
300 | return matchingAnimation; |
301 | } |
302 | |
303 | static bool propertyInStyleMatchesValueForTransitionInMap(CSSPropertyID property, const RenderStyle& style, AnimationTimeline::PropertyToTransitionMap& transitions) |
304 | { |
305 | if (auto* transition = transitions.get(property)) { |
306 | if (CSSPropertyAnimation::propertiesEqual(property, &style, &transition->targetStyle())) |
307 | return true; |
308 | } |
309 | return false; |
310 | } |
311 | |
312 | static double transitionCombinedDuration(const Animation* transition) |
313 | { |
314 | return std::max(0.0, transition->duration()) + transition->delay(); |
315 | } |
316 | |
317 | static bool transitionMatchesProperty(const Animation& transition, CSSPropertyID property) |
318 | { |
319 | auto mode = transition.animationMode(); |
320 | if (mode == Animation::AnimateNone || mode == Animation::AnimateUnknownProperty) |
321 | return false; |
322 | if (mode == Animation::AnimateSingleProperty) { |
323 | auto transitionProperty = transition.property(); |
324 | if (transitionProperty != property) { |
325 | auto shorthand = shorthandForProperty(transitionProperty); |
326 | for (size_t i = 0; i < shorthand.length(); ++i) { |
327 | if (shorthand.properties()[i] == property) |
328 | return true; |
329 | } |
330 | return false; |
331 | } |
332 | } |
333 | return true; |
334 | } |
335 | |
336 | AnimationTimeline::PropertyToTransitionMap& AnimationTimeline::ensureRunningTransitionsByProperty(Element& element) |
337 | { |
338 | return m_elementToRunningCSSTransitionByCSSPropertyID.ensure(&element, [] { |
339 | return PropertyToTransitionMap { }; |
340 | }).iterator->value; |
341 | } |
342 | |
343 | void AnimationTimeline::updateCSSTransitionsForElement(Element& element, const RenderStyle& currentStyle, const RenderStyle& afterChangeStyle) |
344 | { |
345 | // In case this element is newly getting a "display: none" we need to cancel all of its transitions and disregard new ones. |
346 | if (currentStyle.hasTransitions() && currentStyle.display() != DisplayType::None && afterChangeStyle.display() == DisplayType::None) { |
347 | if (m_elementToRunningCSSTransitionByCSSPropertyID.contains(&element)) { |
348 | for (const auto& cssTransitionsByCSSPropertyIDMapItem : m_elementToRunningCSSTransitionByCSSPropertyID.take(&element)) |
349 | cancelDeclarativeAnimation(*cssTransitionsByCSSPropertyIDMapItem.value); |
350 | } |
351 | return; |
352 | } |
353 | |
354 | // Section 3 "Starting of transitions" from the CSS Transitions Level 1 specification. |
355 | // https://drafts.csswg.org/css-transitions-1/#starting |
356 | |
357 | auto& runningTransitionsByProperty = ensureRunningTransitionsByProperty(element); |
358 | |
359 | auto& completedTransitionsByProperty = m_elementToCompletedCSSTransitionByCSSPropertyID.ensure(&element, [] { |
360 | return PropertyToTransitionMap { }; |
361 | }).iterator->value; |
362 | |
363 | auto generationTime = MonotonicTime::now(); |
364 | |
365 | auto numberOfProperties = CSSPropertyAnimation::getNumProperties(); |
366 | for (int propertyIndex = 0; propertyIndex < numberOfProperties; ++propertyIndex) { |
367 | Optional<bool> isShorthand; |
368 | auto property = CSSPropertyAnimation::getPropertyAtIndex(propertyIndex, isShorthand); |
369 | if (isShorthand && *isShorthand) |
370 | continue; |
371 | |
372 | const Animation* matchingBackingAnimation = nullptr; |
373 | if (auto* transitions = afterChangeStyle.transitions()) { |
374 | for (size_t i = 0; i < transitions->size(); ++i) { |
375 | auto& backingAnimation = transitions->animation(i); |
376 | if (transitionMatchesProperty(backingAnimation, property)) |
377 | matchingBackingAnimation = &backingAnimation; |
378 | } |
379 | } |
380 | |
381 | // https://drafts.csswg.org/css-transitions-1/#before-change-style |
382 | // Define the before-change style as the computed values of all properties on the element as of the previous style change event, except with |
383 | // any styles derived from declarative animations such as CSS Transitions, CSS Animations, and SMIL Animations updated to the current time. |
384 | auto existingAnimation = cssAnimationForElementAndProperty(element, property); |
385 | const auto& beforeChangeStyle = existingAnimation ? downcast<CSSAnimation>(existingAnimation.get())->unanimatedStyle() : currentStyle; |
386 | |
387 | if (!runningTransitionsByProperty.contains(property) |
388 | && !CSSPropertyAnimation::propertiesEqual(property, &beforeChangeStyle, &afterChangeStyle) |
389 | && CSSPropertyAnimation::canPropertyBeInterpolated(property, &beforeChangeStyle, &afterChangeStyle) |
390 | && !propertyInStyleMatchesValueForTransitionInMap(property, afterChangeStyle, completedTransitionsByProperty) |
391 | && matchingBackingAnimation && transitionCombinedDuration(matchingBackingAnimation) > 0) { |
392 | // 1. If all of the following are true: |
393 | // - the element does not have a running transition for the property, |
394 | // - the before-change style is different from and can be interpolated with the after-change style for that property, |
395 | // - the element does not have a completed transition for the property or the end value of the completed transition is different from the after-change style for the property, |
396 | // - there is a matching transition-property value, and |
397 | // - the combined duration is greater than 0s, |
398 | |
399 | // then implementations must remove the completed transition (if present) from the set of completed transitions |
400 | completedTransitionsByProperty.remove(property); |
401 | |
402 | // and start a transition whose: |
403 | // - start time is the time of the style change event plus the matching transition delay, |
404 | // - end time is the start time plus the matching transition duration, |
405 | // - start value is the value of the transitioning property in the before-change style, |
406 | // - end value is the value of the transitioning property in the after-change style, |
407 | // - reversing-adjusted start value is the same as the start value, and |
408 | // - reversing shortening factor is 1. |
409 | auto delay = Seconds(matchingBackingAnimation->delay()); |
410 | auto duration = Seconds(matchingBackingAnimation->duration()); |
411 | auto& reversingAdjustedStartStyle = beforeChangeStyle; |
412 | auto reversingShorteningFactor = 1; |
413 | runningTransitionsByProperty.set(property, CSSTransition::create(element, property, generationTime, *matchingBackingAnimation, &beforeChangeStyle, afterChangeStyle, delay, duration, reversingAdjustedStartStyle, reversingShorteningFactor)); |
414 | } else if (completedTransitionsByProperty.contains(property) && !propertyInStyleMatchesValueForTransitionInMap(property, afterChangeStyle, completedTransitionsByProperty)) { |
415 | // 2. Otherwise, if the element has a completed transition for the property and the end value of the completed transition is different from |
416 | // the after-change style for the property, then implementations must remove the completed transition from the set of completed transitions. |
417 | completedTransitionsByProperty.remove(property); |
418 | } |
419 | |
420 | bool hasRunningTransition = runningTransitionsByProperty.contains(property); |
421 | if ((hasRunningTransition || completedTransitionsByProperty.contains(property)) && !matchingBackingAnimation) { |
422 | // 3. If the element has a running transition or completed transition for the property, and there is not a matching transition-property |
423 | // value, then implementations must cancel the running transition or remove the completed transition from the set of completed transitions. |
424 | if (hasRunningTransition) |
425 | runningTransitionsByProperty.take(property)->cancel(); |
426 | else |
427 | completedTransitionsByProperty.remove(property); |
428 | } |
429 | |
430 | if (matchingBackingAnimation && runningTransitionsByProperty.contains(property) && !propertyInStyleMatchesValueForTransitionInMap(property, afterChangeStyle, runningTransitionsByProperty)) { |
431 | auto previouslyRunningTransition = runningTransitionsByProperty.take(property); |
432 | auto& previouslyRunningTransitionCurrentStyle = previouslyRunningTransition->currentStyle(); |
433 | // 4. If the element has a running transition for the property, there is a matching transition-property value, and the end value of the running |
434 | // transition is not equal to the value of the property in the after-change style, then: |
435 | if (CSSPropertyAnimation::propertiesEqual(property, &previouslyRunningTransitionCurrentStyle, &afterChangeStyle) || !CSSPropertyAnimation::canPropertyBeInterpolated(property, ¤tStyle, &afterChangeStyle)) { |
436 | // 1. If the current value of the property in the running transition is equal to the value of the property in the after-change style, |
437 | // or if these two values cannot be interpolated, then implementations must cancel the running transition. |
438 | cancelDeclarativeAnimation(*previouslyRunningTransition); |
439 | } else if (transitionCombinedDuration(matchingBackingAnimation) <= 0.0 || !CSSPropertyAnimation::canPropertyBeInterpolated(property, &previouslyRunningTransitionCurrentStyle, &afterChangeStyle)) { |
440 | // 2. Otherwise, if the combined duration is less than or equal to 0s, or if the current value of the property in the running transition |
441 | // cannot be interpolated with the value of the property in the after-change style, then implementations must cancel the running transition. |
442 | cancelDeclarativeAnimation(*previouslyRunningTransition); |
443 | } else if (CSSPropertyAnimation::propertiesEqual(property, &previouslyRunningTransition->reversingAdjustedStartStyle(), &afterChangeStyle)) { |
444 | // 3. Otherwise, if the reversing-adjusted start value of the running transition is the same as the value of the property in the after-change |
445 | // style (see the section on reversing of transitions for why these case exists), implementations must cancel the running transition |
446 | cancelDeclarativeAnimation(*previouslyRunningTransition); |
447 | |
448 | // and start a new transition whose: |
449 | // - reversing-adjusted start value is the end value of the running transition, |
450 | // - reversing shortening factor is the absolute value, clamped to the range [0, 1], of the sum of: |
451 | // 1. the output of the timing function of the old transition at the time of the style change event, times the reversing shortening factor of the old transition |
452 | // 2. 1 minus the reversing shortening factor of the old transition. |
453 | // - start time is the time of the style change event plus: |
454 | // 1. if the matching transition delay is nonnegative, the matching transition delay, or |
455 | // 2. if the matching transition delay is negative, the product of the new transition’s reversing shortening factor and the matching transition delay, |
456 | // - end time is the start time plus the product of the matching transition duration and the new transition’s reversing shortening factor, |
457 | // - start value is the current value of the property in the running transition, |
458 | // - end value is the value of the property in the after-change style |
459 | auto& reversingAdjustedStartStyle = previouslyRunningTransition->targetStyle(); |
460 | double transformedProgress = 1; |
461 | if (auto* effect = previouslyRunningTransition->effect()) { |
462 | if (auto computedTimingProgress = effect->getComputedTiming().progress) |
463 | transformedProgress = *computedTimingProgress; |
464 | } |
465 | auto reversingShorteningFactor = std::max(std::min(((transformedProgress * previouslyRunningTransition->reversingShorteningFactor()) + (1 - previouslyRunningTransition->reversingShorteningFactor())), 1.0), 0.0); |
466 | auto delay = matchingBackingAnimation->delay() < 0 ? Seconds(matchingBackingAnimation->delay()) * reversingShorteningFactor : Seconds(matchingBackingAnimation->delay()); |
467 | auto duration = Seconds(matchingBackingAnimation->duration()) * reversingShorteningFactor; |
468 | |
469 | ensureRunningTransitionsByProperty(element).set(property, CSSTransition::create(element, property, generationTime, *matchingBackingAnimation, &previouslyRunningTransitionCurrentStyle, afterChangeStyle, delay, duration, reversingAdjustedStartStyle, reversingShorteningFactor)); |
470 | } else { |
471 | // 4. Otherwise, implementations must cancel the running transition |
472 | cancelDeclarativeAnimation(*previouslyRunningTransition); |
473 | |
474 | // and start a new transition whose: |
475 | // - start time is the time of the style change event plus the matching transition delay, |
476 | // - end time is the start time plus the matching transition duration, |
477 | // - start value is the current value of the property in the running transition, |
478 | // - end value is the value of the property in the after-change style, |
479 | // - reversing-adjusted start value is the same as the start value, and |
480 | // - reversing shortening factor is 1. |
481 | auto delay = Seconds(matchingBackingAnimation->delay()); |
482 | auto duration = Seconds(matchingBackingAnimation->duration()); |
483 | auto& reversingAdjustedStartStyle = currentStyle; |
484 | auto reversingShorteningFactor = 1; |
485 | ensureRunningTransitionsByProperty(element).set(property, CSSTransition::create(element, property, generationTime, *matchingBackingAnimation, &previouslyRunningTransitionCurrentStyle, afterChangeStyle, delay, duration, reversingAdjustedStartStyle, reversingShorteningFactor)); |
486 | } |
487 | } |
488 | } |
489 | } |
490 | |
491 | void AnimationTimeline::cancelDeclarativeAnimation(DeclarativeAnimation& animation) |
492 | { |
493 | animation.cancelFromStyle(); |
494 | removeAnimation(animation); |
495 | m_allAnimations.removeFirst(&animation); |
496 | } |
497 | |
498 | } // namespace WebCore |
499 | |