Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : /* Code to start and animate CSS transitions. */
8 :
9 : #include "nsTransitionManager.h"
10 : #include "nsAnimationManager.h"
11 : #include "mozilla/dom/CSSTransitionBinding.h"
12 :
13 : #include "nsIContent.h"
14 : #include "nsContentUtils.h"
15 : #include "mozilla/ComputedStyle.h"
16 : #include "mozilla/MemoryReporting.h"
17 : #include "mozilla/TimeStamp.h"
18 : #include "nsRefreshDriver.h"
19 : #include "nsCSSPropertyIDSet.h"
20 : #include "mozilla/AnimationEventDispatcher.h"
21 : #include "mozilla/EffectCompositor.h"
22 : #include "mozilla/EffectSet.h"
23 : #include "mozilla/EventDispatcher.h"
24 : #include "mozilla/ServoBindings.h"
25 : #include "mozilla/StyleAnimationValue.h"
26 : #include "mozilla/dom/DocumentTimeline.h"
27 : #include "mozilla/dom/Element.h"
28 : #include "nsIFrame.h"
29 : #include "Layers.h"
30 : #include "FrameLayerBuilder.h"
31 : #include "nsCSSProps.h"
32 : #include "nsCSSPseudoElements.h"
33 : #include "nsDisplayList.h"
34 : #include "nsRFPService.h"
35 : #include "nsStyleChangeList.h"
36 : #include "mozilla/RestyleManager.h"
37 : #include "nsDOMMutationObserver.h"
38 :
39 : using mozilla::TimeStamp;
40 : using mozilla::TimeDuration;
41 : using mozilla::dom::Animation;
42 : using mozilla::dom::AnimationPlayState;
43 : using mozilla::dom::CSSTransition;
44 :
45 : using namespace mozilla;
46 : using namespace mozilla::css;
47 :
48 : double
49 0 : ElementPropertyTransition::CurrentValuePortion() const
50 : {
51 0 : MOZ_ASSERT(!GetLocalTime().IsNull(),
52 : "Getting the value portion of an animation that's not being "
53 : "sampled");
54 :
55 : // Transitions use a fill mode of 'backwards' so GetComputedTiming will
56 : // never return a null time progress due to being *before* the animation
57 : // interval. However, it might be possible that we're behind on flushing
58 : // causing us to get called *after* the animation interval. So, just in
59 : // case, we override the fill mode to 'both' to ensure the progress
60 : // is never null.
61 0 : TimingParams timingToUse = SpecifiedTiming();
62 0 : timingToUse.SetFill(dom::FillMode::Both);
63 0 : ComputedTiming computedTiming = GetComputedTiming(&timingToUse);
64 :
65 0 : MOZ_ASSERT(!computedTiming.mProgress.IsNull(),
66 : "Got a null progress for a fill mode of 'both'");
67 0 : MOZ_ASSERT(mKeyframes.Length() == 2,
68 : "Should have two animation keyframes for a transition");
69 0 : return ComputedTimingFunction::GetPortion(mKeyframes[0].mTimingFunction,
70 0 : computedTiming.mProgress.Value(),
71 0 : computedTiming.mBeforeFlag);
72 : }
73 :
74 : void
75 0 : ElementPropertyTransition::UpdateStartValueFromReplacedTransition()
76 : {
77 0 : if (!mReplacedTransition) {
78 0 : return;
79 : }
80 0 : MOZ_ASSERT(nsCSSProps::PropHasFlags(TransitionProperty(),
81 : CSSPropFlags::CanAnimateOnCompositor),
82 : "The transition property should be able to be run on the "
83 : "compositor");
84 0 : MOZ_ASSERT(mTarget && mTarget->mElement->OwnerDoc(),
85 : "We should have a valid document at this moment");
86 :
87 0 : dom::DocumentTimeline* timeline = mTarget->mElement->OwnerDoc()->Timeline();
88 : ComputedTiming computedTiming = GetComputedTimingAt(
89 0 : dom::CSSTransition::GetCurrentTimeAt(*timeline,
90 0 : TimeStamp::Now(),
91 0 : mReplacedTransition->mStartTime,
92 0 : mReplacedTransition->mPlaybackRate),
93 0 : mReplacedTransition->mTiming,
94 0 : mReplacedTransition->mPlaybackRate);
95 :
96 0 : if (!computedTiming.mProgress.IsNull()) {
97 : double valuePosition =
98 0 : ComputedTimingFunction::GetPortion(mReplacedTransition->mTimingFunction,
99 0 : computedTiming.mProgress.Value(),
100 0 : computedTiming.mBeforeFlag);
101 :
102 0 : MOZ_ASSERT(mProperties.Length() == 1 &&
103 : mProperties[0].mSegments.Length() == 1,
104 : "The transition should have one property and one segment");
105 0 : MOZ_ASSERT(mKeyframes.Length() == 2,
106 : "Transitions should have exactly two animation keyframes");
107 0 : MOZ_ASSERT(mKeyframes[0].mPropertyValues.Length() == 1,
108 : "Transitions should have exactly one property in their first "
109 : "frame");
110 :
111 0 : const AnimationValue& replacedFrom = mReplacedTransition->mFromValue;
112 0 : const AnimationValue& replacedTo = mReplacedTransition->mToValue;
113 0 : AnimationValue startValue;
114 : startValue.mServo =
115 0 : Servo_AnimationValues_Interpolate(replacedFrom.mServo,
116 : replacedTo.mServo,
117 0 : valuePosition).Consume();
118 0 : if (startValue.mServo) {
119 0 : mKeyframes[0].mPropertyValues[0].mServoDeclarationBlock =
120 0 : Servo_AnimationValue_Uncompute(startValue.mServo).Consume();
121 0 : mProperties[0].mSegments[0].mFromValue = std::move(startValue);
122 : }
123 : }
124 :
125 0 : mReplacedTransition.reset();
126 : }
127 :
128 : ////////////////////////// CSSTransition ////////////////////////////
129 :
130 : JSObject*
131 0 : CSSTransition::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
132 : {
133 0 : return dom::CSSTransitionBinding::Wrap(aCx, this, aGivenProto);
134 : }
135 :
136 : void
137 0 : CSSTransition::GetTransitionProperty(nsString& aRetVal) const
138 : {
139 0 : MOZ_ASSERT(eCSSProperty_UNKNOWN != mTransitionProperty,
140 : "Transition Property should be initialized");
141 : aRetVal =
142 0 : NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(mTransitionProperty));
143 0 : }
144 :
145 : AnimationPlayState
146 0 : CSSTransition::PlayStateFromJS() const
147 : {
148 0 : FlushUnanimatedStyle();
149 0 : return Animation::PlayStateFromJS();
150 : }
151 :
152 : bool
153 0 : CSSTransition::PendingFromJS() const
154 : {
155 : // Transitions don't become pending again after they start running but, if
156 : // while the transition is still pending, style is updated in such a way
157 : // that the transition will be canceled, we need to report false here.
158 : // Hence we need to flush, but only when we're pending.
159 0 : if (Pending()) {
160 0 : FlushUnanimatedStyle();
161 : }
162 0 : return Animation::PendingFromJS();
163 : }
164 :
165 : void
166 0 : CSSTransition::PlayFromJS(ErrorResult& aRv)
167 : {
168 0 : FlushUnanimatedStyle();
169 0 : Animation::PlayFromJS(aRv);
170 0 : }
171 :
172 : void
173 0 : CSSTransition::UpdateTiming(SeekFlag aSeekFlag, SyncNotifyFlag aSyncNotifyFlag)
174 : {
175 0 : if (mNeedsNewAnimationIndexWhenRun &&
176 0 : PlayState() != AnimationPlayState::Idle) {
177 0 : mAnimationIndex = sNextAnimationIndex++;
178 0 : mNeedsNewAnimationIndexWhenRun = false;
179 : }
180 :
181 0 : Animation::UpdateTiming(aSeekFlag, aSyncNotifyFlag);
182 0 : }
183 :
184 : void
185 0 : CSSTransition::QueueEvents(const StickyTimeDuration& aActiveTime)
186 : {
187 0 : if (!mOwningElement.IsSet()) {
188 0 : return;
189 : }
190 :
191 0 : nsPresContext* presContext = mOwningElement.GetPresContext();
192 0 : if (!presContext) {
193 : return;
194 : }
195 :
196 : static constexpr StickyTimeDuration zeroDuration = StickyTimeDuration();
197 :
198 : TransitionPhase currentPhase;
199 0 : StickyTimeDuration intervalStartTime;
200 0 : StickyTimeDuration intervalEndTime;
201 :
202 0 : if (!mEffect) {
203 0 : currentPhase = GetAnimationPhaseWithoutEffect<TransitionPhase>(*this);
204 : } else {
205 0 : ComputedTiming computedTiming = mEffect->GetComputedTiming();
206 :
207 0 : currentPhase = static_cast<TransitionPhase>(computedTiming.mPhase);
208 0 : intervalStartTime =
209 0 : std::max(std::min(StickyTimeDuration(-mEffect->SpecifiedTiming().Delay()),
210 0 : computedTiming.mActiveDuration), zeroDuration);
211 0 : intervalEndTime =
212 0 : std::max(std::min((EffectEnd() - mEffect->SpecifiedTiming().Delay()),
213 0 : computedTiming.mActiveDuration), zeroDuration);
214 : }
215 :
216 : // TimeStamps to use for ordering the events when they are dispatched. We
217 : // use a TimeStamp so we can compare events produced by different elements,
218 : // perhaps even with different timelines.
219 : // The zero timestamp is for transitionrun events where we ignore the delay
220 : // for the purpose of ordering events.
221 0 : TimeStamp zeroTimeStamp = AnimationTimeToTimeStamp(zeroDuration);
222 0 : TimeStamp startTimeStamp = ElapsedTimeToTimeStamp(intervalStartTime);
223 0 : TimeStamp endTimeStamp = ElapsedTimeToTimeStamp(intervalEndTime);
224 :
225 0 : if (mPendingState != PendingState::NotPending &&
226 0 : (mPreviousTransitionPhase == TransitionPhase::Idle ||
227 : mPreviousTransitionPhase == TransitionPhase::Pending))
228 : {
229 0 : currentPhase = TransitionPhase::Pending;
230 : }
231 :
232 0 : AutoTArray<AnimationEventInfo, 3> events;
233 :
234 : auto appendTransitionEvent = [&](EventMessage aMessage,
235 : const StickyTimeDuration& aElapsedTime,
236 0 : const TimeStamp& aTimeStamp) {
237 0 : double elapsedTime = aElapsedTime.ToSeconds();
238 0 : if (aMessage == eTransitionCancel) {
239 : // 0 is an inappropriate value for this callsite. What we need to do is
240 : // use a single random value for all increasing times reportable.
241 : // That is to say, whenever elapsedTime goes negative (because an
242 : // animation restarts, something rewinds the animation, or otherwise)
243 : // a new random value for the mix-in must be generated.
244 0 : elapsedTime = nsRFPService::ReduceTimePrecisionAsSecs(elapsedTime, 0, TimerPrecisionType::RFPOnly);
245 : }
246 0 : events.AppendElement(AnimationEventInfo(TransitionProperty(),
247 : mOwningElement.Target(),
248 : aMessage,
249 : elapsedTime,
250 : aTimeStamp,
251 0 : this));
252 0 : };
253 :
254 : // Handle cancel events first
255 0 : if ((mPreviousTransitionPhase != TransitionPhase::Idle &&
256 0 : mPreviousTransitionPhase != TransitionPhase::After) &&
257 : currentPhase == TransitionPhase::Idle) {
258 0 : TimeStamp activeTimeStamp = ElapsedTimeToTimeStamp(aActiveTime);
259 0 : appendTransitionEvent(eTransitionCancel, aActiveTime, activeTimeStamp);
260 : }
261 :
262 : // All other events
263 0 : switch (mPreviousTransitionPhase) {
264 : case TransitionPhase::Idle:
265 0 : if (currentPhase == TransitionPhase::Pending ||
266 0 : currentPhase == TransitionPhase::Before) {
267 0 : appendTransitionEvent(eTransitionRun, intervalStartTime, zeroTimeStamp);
268 0 : } else if (currentPhase == TransitionPhase::Active) {
269 0 : appendTransitionEvent(eTransitionRun, intervalStartTime, zeroTimeStamp);
270 : appendTransitionEvent(eTransitionStart,
271 : intervalStartTime,
272 0 : startTimeStamp);
273 0 : } else if (currentPhase == TransitionPhase::After) {
274 0 : appendTransitionEvent(eTransitionRun, intervalStartTime, zeroTimeStamp);
275 : appendTransitionEvent(eTransitionStart,
276 : intervalStartTime,
277 0 : startTimeStamp);
278 0 : appendTransitionEvent(eTransitionEnd, intervalEndTime, endTimeStamp);
279 : }
280 : break;
281 :
282 : case TransitionPhase::Pending:
283 : case TransitionPhase::Before:
284 0 : if (currentPhase == TransitionPhase::Active) {
285 : appendTransitionEvent(eTransitionStart,
286 : intervalStartTime,
287 0 : startTimeStamp);
288 0 : } else if (currentPhase == TransitionPhase::After) {
289 : appendTransitionEvent(eTransitionStart,
290 : intervalStartTime,
291 0 : startTimeStamp);
292 0 : appendTransitionEvent(eTransitionEnd, intervalEndTime, endTimeStamp);
293 : }
294 : break;
295 :
296 : case TransitionPhase::Active:
297 0 : if (currentPhase == TransitionPhase::After) {
298 0 : appendTransitionEvent(eTransitionEnd, intervalEndTime, endTimeStamp);
299 0 : } else if (currentPhase == TransitionPhase::Before) {
300 : appendTransitionEvent(eTransitionEnd,
301 : intervalStartTime,
302 0 : startTimeStamp);
303 : }
304 : break;
305 :
306 : case TransitionPhase::After:
307 0 : if (currentPhase == TransitionPhase::Active) {
308 : appendTransitionEvent(eTransitionStart,
309 : intervalEndTime,
310 0 : startTimeStamp);
311 0 : } else if (currentPhase == TransitionPhase::Before) {
312 : appendTransitionEvent(eTransitionStart,
313 : intervalEndTime,
314 0 : startTimeStamp);
315 : appendTransitionEvent(eTransitionEnd,
316 : intervalStartTime,
317 0 : endTimeStamp);
318 : }
319 : break;
320 : }
321 0 : mPreviousTransitionPhase = currentPhase;
322 :
323 0 : if (!events.IsEmpty()) {
324 0 : presContext->AnimationEventDispatcher()->QueueEvents(std::move(events));
325 : }
326 : }
327 :
328 : void
329 0 : CSSTransition::Tick()
330 : {
331 0 : Animation::Tick();
332 0 : QueueEvents();
333 0 : }
334 :
335 : nsCSSPropertyID
336 0 : CSSTransition::TransitionProperty() const
337 : {
338 0 : MOZ_ASSERT(eCSSProperty_UNKNOWN != mTransitionProperty,
339 : "Transition property should be initialized");
340 0 : return mTransitionProperty;
341 : }
342 :
343 : AnimationValue
344 0 : CSSTransition::ToValue() const
345 : {
346 0 : MOZ_ASSERT(!mTransitionToValue.IsNull(),
347 : "Transition ToValue should be initialized");
348 0 : return mTransitionToValue;
349 : }
350 :
351 : bool
352 0 : CSSTransition::HasLowerCompositeOrderThan(const CSSTransition& aOther) const
353 : {
354 0 : MOZ_ASSERT(IsTiedToMarkup() && aOther.IsTiedToMarkup(),
355 : "Should only be called for CSS transitions that are sorted "
356 : "as CSS transitions (i.e. tied to CSS markup)");
357 :
358 : // 0. Object-equality case
359 0 : if (&aOther == this) {
360 : return false;
361 : }
362 :
363 : // 1. Sort by document order
364 0 : if (!mOwningElement.Equals(aOther.mOwningElement)) {
365 0 : return mOwningElement.LessThan(aOther.mOwningElement);
366 : }
367 :
368 : // 2. (Same element and pseudo): Sort by transition generation
369 0 : if (mAnimationIndex != aOther.mAnimationIndex) {
370 0 : return mAnimationIndex < aOther.mAnimationIndex;
371 : }
372 :
373 : // 3. (Same transition generation): Sort by transition property
374 0 : return nsCSSProps::GetStringValue(TransitionProperty()) <
375 0 : nsCSSProps::GetStringValue(aOther.TransitionProperty());
376 : }
377 :
378 : /* static */ Nullable<TimeDuration>
379 0 : CSSTransition::GetCurrentTimeAt(const dom::DocumentTimeline& aTimeline,
380 : const TimeStamp& aBaseTime,
381 : const TimeDuration& aStartTime,
382 : double aPlaybackRate)
383 : {
384 0 : Nullable<TimeDuration> result;
385 :
386 0 : Nullable<TimeDuration> timelineTime = aTimeline.ToTimelineTime(aBaseTime);
387 0 : if (!timelineTime.IsNull()) {
388 0 : result.SetValue((timelineTime.Value() - aStartTime)
389 0 : .MultDouble(aPlaybackRate));
390 : }
391 :
392 0 : return result;
393 : }
394 :
395 : void
396 0 : CSSTransition::SetEffectFromStyle(dom::AnimationEffect* aEffect)
397 : {
398 0 : Animation::SetEffectNoUpdate(aEffect);
399 :
400 : // Initialize transition property.
401 0 : ElementPropertyTransition* pt = aEffect ? aEffect->AsTransition() : nullptr;
402 0 : if (eCSSProperty_UNKNOWN == mTransitionProperty && pt) {
403 0 : mTransitionProperty = pt->TransitionProperty();
404 0 : mTransitionToValue = pt->ToValue();
405 : }
406 0 : }
407 :
408 : ////////////////////////// nsTransitionManager ////////////////////////////
409 :
410 :
411 : static inline bool
412 0 : ExtractNonDiscreteComputedValue(nsCSSPropertyID aProperty,
413 : const ComputedStyle& aComputedStyle,
414 : AnimationValue& aAnimationValue)
415 : {
416 0 : if (Servo_Property_IsDiscreteAnimatable(aProperty) &&
417 : aProperty != eCSSProperty_visibility) {
418 : return false;
419 : }
420 :
421 : aAnimationValue.mServo =
422 0 : Servo_ComputedValues_ExtractAnimationValue(&aComputedStyle,
423 0 : aProperty).Consume();
424 0 : return !!aAnimationValue.mServo;
425 : }
426 :
427 :
428 : bool
429 0 : nsTransitionManager::UpdateTransitions(
430 : dom::Element *aElement,
431 : CSSPseudoElementType aPseudoType,
432 : const ComputedStyle& aOldStyle,
433 : const ComputedStyle& aNewStyle)
434 : {
435 0 : if (!mPresContext->IsDynamic()) {
436 : // For print or print preview, ignore transitions.
437 : return false;
438 : }
439 :
440 : CSSTransitionCollection* collection =
441 0 : CSSTransitionCollection::GetAnimationCollection(aElement, aPseudoType);
442 : const nsStyleDisplay* disp =
443 0 : aNewStyle.ComputedData()->GetStyleDisplay();
444 : return DoUpdateTransitions(*disp,
445 : aElement, aPseudoType,
446 : collection,
447 0 : aOldStyle, aNewStyle);
448 : }
449 :
450 : bool
451 0 : nsTransitionManager::DoUpdateTransitions(
452 : const nsStyleDisplay& aDisp,
453 : dom::Element* aElement,
454 : CSSPseudoElementType aPseudoType,
455 : CSSTransitionCollection*& aElementTransitions,
456 : const ComputedStyle& aOldStyle,
457 : const ComputedStyle& aNewStyle)
458 : {
459 0 : MOZ_ASSERT(!aElementTransitions ||
460 : aElementTransitions->mElement == aElement, "Element mismatch");
461 :
462 : // Per http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html
463 : // I'll consider only the transitions from the number of items in
464 : // 'transition-property' on down, and later ones will override earlier
465 : // ones (tracked using |propertiesChecked|).
466 0 : bool startedAny = false;
467 0 : nsCSSPropertyIDSet propertiesChecked;
468 0 : for (uint32_t i = aDisp.mTransitionPropertyCount; i--; ) {
469 : // We're not going to look at any further transitions, so we can just avoid
470 : // looking at this if we know it will not start any transitions.
471 0 : if (i == 0 && aDisp.GetTransitionCombinedDuration(i) <= 0.0f) {
472 : continue;
473 : }
474 :
475 0 : nsCSSPropertyID property = aDisp.GetTransitionProperty(i);
476 0 : if (property == eCSSPropertyExtra_no_properties ||
477 0 : property == eCSSPropertyExtra_variable ||
478 : property == eCSSProperty_UNKNOWN) {
479 : // Nothing to do.
480 : continue;
481 : }
482 : // We might have something to transition. See if any of the
483 : // properties in question changed and are animatable.
484 : // FIXME: Would be good to find a way to share code between this
485 : // interpretation of transition-property and the one below.
486 : // FIXME(emilio): This should probably just use the "all" shorthand id, and
487 : // we should probably remove eCSSPropertyExtra_all_properties.
488 0 : if (property == eCSSPropertyExtra_all_properties) {
489 0 : for (nsCSSPropertyID p = nsCSSPropertyID(0);
490 0 : p < eCSSProperty_COUNT_no_shorthands;
491 0 : p = nsCSSPropertyID(p + 1)) {
492 0 : if (!nsCSSProps::IsEnabled(p, CSSEnabledState::eForAllContent)) {
493 : continue;
494 : }
495 0 : startedAny |=
496 0 : ConsiderInitiatingTransition(p, aDisp, i, aElement, aPseudoType,
497 : aElementTransitions,
498 : aOldStyle, aNewStyle,
499 : propertiesChecked);
500 : }
501 2 : } else if (nsCSSProps::IsShorthand(property)) {
502 0 : CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, property,
503 : CSSEnabledState::eForAllContent)
504 : {
505 0 : startedAny |=
506 0 : ConsiderInitiatingTransition(*subprop, aDisp, i, aElement, aPseudoType,
507 : aElementTransitions,
508 : aOldStyle, aNewStyle,
509 : propertiesChecked);
510 : }
511 : } else {
512 2 : startedAny |=
513 2 : ConsiderInitiatingTransition(property, aDisp, i, aElement, aPseudoType,
514 : aElementTransitions,
515 : aOldStyle, aNewStyle,
516 : propertiesChecked);
517 : }
518 : }
519 :
520 : // Stop any transitions for properties that are no longer in
521 : // 'transition-property', including finished transitions.
522 : // Also stop any transitions (and remove any finished transitions)
523 : // for properties that just changed (and are still in the set of
524 : // properties to transition), but for which we didn't just start the
525 : // transition. This can happen delay and duration are both zero, or
526 : // because the new value is not interpolable.
527 : // Note that we also do the latter set of work in
528 : // nsTransitionManager::PruneCompletedTransitions.
529 0 : if (aElementTransitions) {
530 : bool checkProperties =
531 0 : aDisp.GetTransitionProperty(0) != eCSSPropertyExtra_all_properties;
532 2 : nsCSSPropertyIDSet allTransitionProperties;
533 2 : if (checkProperties) {
534 0 : for (uint32_t i = aDisp.mTransitionPropertyCount; i-- != 0; ) {
535 : // FIXME: Would be good to find a way to share code between this
536 : // interpretation of transition-property and the one above.
537 2 : nsCSSPropertyID property = aDisp.GetTransitionProperty(i);
538 4 : if (property == eCSSPropertyExtra_no_properties ||
539 0 : property == eCSSPropertyExtra_variable ||
540 : property == eCSSProperty_UNKNOWN) {
541 : // Nothing to do, but need to exclude this from cases below.
542 1 : } else if (property == eCSSPropertyExtra_all_properties) {
543 0 : for (nsCSSPropertyID p = nsCSSPropertyID(0);
544 0 : p < eCSSProperty_COUNT_no_shorthands;
545 0 : p = nsCSSPropertyID(p + 1)) {
546 0 : allTransitionProperties.AddProperty(p);
547 : }
548 0 : } else if (nsCSSProps::IsShorthand(property)) {
549 0 : CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(
550 : subprop, property, CSSEnabledState::eForAllContent) {
551 0 : allTransitionProperties.AddProperty(*subprop);
552 : }
553 : } else {
554 2 : allTransitionProperties.AddProperty(property);
555 : }
556 : }
557 : }
558 :
559 0 : OwningCSSTransitionPtrArray& animations = aElementTransitions->mAnimations;
560 4 : size_t i = animations.Length();
561 0 : MOZ_ASSERT(i != 0, "empty transitions list?");
562 0 : AnimationValue currentValue;
563 : do {
564 0 : --i;
565 0 : CSSTransition* anim = animations[i];
566 : // properties no longer in 'transition-property'
567 4 : if ((checkProperties &&
568 4 : !allTransitionProperties.HasProperty(anim->TransitionProperty())) ||
569 : // properties whose computed values changed but for which we
570 : // did not start a new transition (because delay and
571 : // duration are both zero, or because the new value is not
572 : // interpolable); a new transition would have anim->ToValue()
573 : // matching currentValue
574 2 : !ExtractNonDiscreteComputedValue(anim->TransitionProperty(),
575 0 : aNewStyle, currentValue) ||
576 6 : currentValue != anim->ToValue()) {
577 : // stop the transition
578 0 : if (anim->HasCurrentEffect()) {
579 : EffectSet* effectSet =
580 0 : EffectSet::GetEffectSet(aElement, aPseudoType);
581 0 : if (effectSet) {
582 0 : effectSet->UpdateAnimationGeneration(mPresContext);
583 : }
584 : }
585 0 : anim->CancelFromStyle();
586 0 : animations.RemoveElementAt(i);
587 : }
588 0 : } while (i != 0);
589 :
590 4 : if (animations.IsEmpty()) {
591 0 : aElementTransitions->Destroy();
592 0 : aElementTransitions = nullptr;
593 : }
594 : }
595 :
596 2 : return startedAny;
597 : }
598 :
599 : static Keyframe&
600 4 : AppendKeyframe(double aOffset,
601 : nsCSSPropertyID aProperty,
602 : AnimationValue&& aValue,
603 : nsTArray<Keyframe>& aKeyframes)
604 : {
605 0 : Keyframe& frame = *aKeyframes.AppendElement();
606 4 : frame.mOffset.emplace(aOffset);
607 :
608 0 : if (aValue.mServo) {
609 : RefPtr<RawServoDeclarationBlock> decl =
610 16 : Servo_AnimationValue_Uncompute(aValue.mServo).Consume();
611 1 : frame.mPropertyValues.AppendElement(
612 12 : PropertyValuePair(aProperty, std::move(decl)));
613 : } else {
614 0 : MOZ_CRASH("old style system disabled");
615 : }
616 4 : return frame;
617 : }
618 :
619 : static nsTArray<Keyframe>
620 2 : GetTransitionKeyframes(nsCSSPropertyID aProperty,
621 : AnimationValue&& aStartValue,
622 : AnimationValue&& aEndValue,
623 : const nsTimingFunction& aTimingFunction)
624 : {
625 0 : nsTArray<Keyframe> keyframes(2);
626 :
627 0 : Keyframe& fromFrame = AppendKeyframe(0.0, aProperty, std::move(aStartValue),
628 0 : keyframes);
629 2 : if (aTimingFunction.mType != nsTimingFunction::Type::Linear) {
630 2 : fromFrame.mTimingFunction.emplace();
631 0 : fromFrame.mTimingFunction->Init(aTimingFunction);
632 : }
633 :
634 2 : AppendKeyframe(1.0, aProperty, std::move(aEndValue), keyframes);
635 :
636 2 : return keyframes;
637 : }
638 :
639 : static bool
640 : IsTransitionable(nsCSSPropertyID aProperty)
641 : {
642 2 : return Servo_Property_IsTransitionable(aProperty);
643 : }
644 :
645 : bool
646 2 : nsTransitionManager::ConsiderInitiatingTransition(
647 : nsCSSPropertyID aProperty,
648 : const nsStyleDisplay& aStyleDisplay,
649 : uint32_t transitionIdx,
650 : dom::Element* aElement,
651 : CSSPseudoElementType aPseudoType,
652 : CSSTransitionCollection*& aElementTransitions,
653 : const ComputedStyle& aOldStyle,
654 : const ComputedStyle& aNewStyle,
655 : nsCSSPropertyIDSet& aPropertiesChecked)
656 : {
657 : // IsShorthand itself will assert if aProperty is not a property.
658 2 : MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty),
659 : "property out of range");
660 2 : NS_ASSERTION(!aElementTransitions ||
661 : aElementTransitions->mElement == aElement, "Element mismatch");
662 :
663 : // A later item in transition-property already specified a transition for this
664 : // property, so we ignore this one.
665 : // See http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html .
666 2 : if (aPropertiesChecked.HasProperty(aProperty)) {
667 : return false;
668 : }
669 :
670 2 : aPropertiesChecked.AddProperty(aProperty);
671 :
672 0 : if (!IsTransitionable(aProperty)) {
673 : return false;
674 : }
675 :
676 0 : float delay = aStyleDisplay.GetTransitionDelay(transitionIdx);
677 :
678 : // The spec says a negative duration is treated as zero.
679 : float duration =
680 0 : std::max(aStyleDisplay.GetTransitionDuration(transitionIdx), 0.0f);
681 :
682 : // If the combined duration of this transition is 0 or less don't start a
683 : // transition.
684 0 : if (delay + duration <= 0.0f) {
685 : return false;
686 : }
687 :
688 0 : dom::DocumentTimeline* timeline = aElement->OwnerDoc()->Timeline();
689 :
690 6 : AnimationValue startValue, endValue;
691 : bool haveValues =
692 0 : ExtractNonDiscreteComputedValue(aProperty, aOldStyle, startValue) &&
693 4 : ExtractNonDiscreteComputedValue(aProperty, aNewStyle, endValue);
694 :
695 2 : bool haveChange = startValue != endValue;
696 :
697 : bool shouldAnimate =
698 2 : haveValues &&
699 0 : haveChange &&
700 4 : startValue.IsInterpolableWith(aProperty, endValue);
701 :
702 0 : bool haveCurrentTransition = false;
703 0 : size_t currentIndex = nsTArray<ElementPropertyTransition>::NoIndex;
704 0 : const ElementPropertyTransition *oldPT = nullptr;
705 2 : if (aElementTransitions) {
706 0 : OwningCSSTransitionPtrArray& animations = aElementTransitions->mAnimations;
707 0 : for (size_t i = 0, i_end = animations.Length(); i < i_end; ++i) {
708 0 : if (animations[i]->TransitionProperty() == aProperty) {
709 0 : haveCurrentTransition = true;
710 0 : currentIndex = i;
711 0 : oldPT = animations[i]->GetEffect()
712 0 : ? animations[i]->GetEffect()->AsTransition()
713 : : nullptr;
714 : break;
715 : }
716 : }
717 : }
718 :
719 : // If we got a style change that changed the value to the endpoint
720 : // of the currently running transition, we don't want to interrupt
721 : // its timing function.
722 : // This needs to be before the !shouldAnimate && haveCurrentTransition
723 : // case below because we might be close enough to the end of the
724 : // transition that the current value rounds to the final value. In
725 : // this case, we'll end up with shouldAnimate as false (because
726 : // there's no value change), but we need to return early here rather
727 : // than cancel the running transition because shouldAnimate is false!
728 : //
729 : // Likewise, if we got a style change that changed the value to the
730 : // endpoint of our finished transition, we also don't want to start
731 : // a new transition for the reasons described in
732 : // https://lists.w3.org/Archives/Public/www-style/2015Jan/0444.html .
733 6 : if (haveCurrentTransition && haveValues &&
734 2 : aElementTransitions->mAnimations[currentIndex]->ToValue() == endValue) {
735 : // GetAnimationRule already called RestyleForAnimation.
736 : return false;
737 : }
738 :
739 2 : if (!shouldAnimate) {
740 0 : if (haveCurrentTransition) {
741 : // We're in the middle of a transition, and just got a non-transition
742 : // style change to something that we can't animate. This might happen
743 : // because we got a non-transition style change changing to the current
744 : // in-progress value (which is particularly easy to cause when we're
745 : // currently in the 'transition-delay'). It also might happen because we
746 : // just got a style change to a value that can't be interpolated.
747 : OwningCSSTransitionPtrArray& animations =
748 0 : aElementTransitions->mAnimations;
749 0 : animations[currentIndex]->CancelFromStyle();
750 0 : oldPT = nullptr; // Clear pointer so it doesn't dangle
751 0 : animations.RemoveElementAt(currentIndex);
752 0 : EffectSet* effectSet = EffectSet::GetEffectSet(aElement, aPseudoType);
753 0 : if (effectSet) {
754 0 : effectSet->UpdateAnimationGeneration(mPresContext);
755 : }
756 :
757 0 : if (animations.IsEmpty()) {
758 0 : aElementTransitions->Destroy();
759 : // |aElementTransitions| is now a dangling pointer!
760 0 : aElementTransitions = nullptr;
761 : }
762 : // GetAnimationRule already called RestyleForAnimation.
763 : }
764 : return false;
765 : }
766 :
767 : const nsTimingFunction &tf =
768 2 : aStyleDisplay.GetTransitionTimingFunction(transitionIdx);
769 :
770 2 : AnimationValue startForReversingTest = startValue;
771 2 : double reversePortion = 1.0;
772 :
773 : // If the new transition reverses an existing one, we'll need to
774 : // handle the timing differently.
775 : // FIXME: Move mStartForReversingTest, mReversePortion to CSSTransition,
776 : // and set the timing function on transitions as an effect-level
777 : // easing (rather than keyframe-level easing). (Bug 1292001)
778 2 : if (haveCurrentTransition &&
779 0 : aElementTransitions->mAnimations[currentIndex]->HasCurrentEffect() &&
780 2 : oldPT &&
781 0 : oldPT->mStartForReversingTest == endValue) {
782 : // Compute the appropriate negative transition-delay such that right
783 : // now we'd end up at the current position.
784 : double valuePortion =
785 0 : oldPT->CurrentValuePortion() * oldPT->mReversePortion +
786 0 : (1.0 - oldPT->mReversePortion);
787 : // A timing function with negative y1 (or y2!) might make
788 : // valuePortion negative. In this case, we still want to apply our
789 : // reversing logic based on relative distances, not make duration
790 : // negative.
791 0 : if (valuePortion < 0.0) {
792 0 : valuePortion = -valuePortion;
793 : }
794 : // A timing function with y2 (or y1!) greater than one might
795 : // advance past its terminal value. It's probably a good idea to
796 : // clamp valuePortion to be at most one to preserve the invariant
797 : // that a transition will complete within at most its specified
798 : // time.
799 0 : if (valuePortion > 1.0) {
800 0 : valuePortion = 1.0;
801 : }
802 :
803 : // Negative delays are essentially part of the transition
804 : // function, so reduce them along with the duration, but don't
805 : // reduce positive delays.
806 0 : if (delay < 0.0f) {
807 0 : delay *= valuePortion;
808 : }
809 :
810 0 : duration *= valuePortion;
811 :
812 0 : startForReversingTest = oldPT->ToValue();
813 0 : reversePortion = valuePortion;
814 : }
815 :
816 : TimingParams timing =
817 : TimingParamsFromCSSParams(duration, delay,
818 : 1.0 /* iteration count */,
819 : dom::PlaybackDirection::Normal,
820 4 : dom::FillMode::Backwards);
821 :
822 : // aElement is non-null here, so we emplace it directly.
823 4 : Maybe<OwningAnimationTarget> target;
824 0 : target.emplace(aElement, aPseudoType);
825 2 : KeyframeEffectParams effectOptions;
826 : RefPtr<ElementPropertyTransition> pt =
827 0 : new ElementPropertyTransition(aElement->OwnerDoc(), target, timing,
828 : startForReversingTest, reversePortion,
829 0 : effectOptions);
830 :
831 0 : pt->SetKeyframes(GetTransitionKeyframes(aProperty,
832 2 : std::move(startValue), std::move(endValue), tf),
833 0 : &aNewStyle);
834 :
835 : RefPtr<CSSTransition> animation =
836 0 : new CSSTransition(mPresContext->Document()->GetScopeObject());
837 0 : animation->SetOwningElement(OwningElementRef(*aElement, aPseudoType));
838 2 : animation->SetTimelineNoUpdate(timeline);
839 2 : animation->SetCreationSequence(
840 0 : mPresContext->RestyleManager()->GetAnimationGeneration());
841 0 : animation->SetEffectFromStyle(pt);
842 0 : animation->PlayFromStyle();
843 :
844 0 : if (!aElementTransitions) {
845 0 : bool createdCollection = false;
846 0 : aElementTransitions =
847 2 : CSSTransitionCollection::GetOrCreateAnimationCollection(
848 : aElement, aPseudoType, &createdCollection);
849 0 : if (!aElementTransitions) {
850 0 : MOZ_ASSERT(!createdCollection, "outparam should agree with return value");
851 0 : NS_WARNING("allocating collection failed");
852 0 : return false;
853 : }
854 :
855 1 : if (createdCollection) {
856 1 : AddElementCollection(aElementTransitions);
857 : }
858 : }
859 :
860 0 : OwningCSSTransitionPtrArray& animations = aElementTransitions->mAnimations;
861 : #ifdef DEBUG
862 4 : for (size_t i = 0, i_end = animations.Length(); i < i_end; ++i) {
863 0 : MOZ_ASSERT(
864 : i == currentIndex || animations[i]->TransitionProperty() != aProperty,
865 : "duplicate transitions for property");
866 : }
867 : #endif
868 2 : if (haveCurrentTransition) {
869 : // If this new transition is replacing an existing transition that is running
870 : // on the compositor, we store select parameters from the replaced transition
871 : // so that later, once all scripts have run, we can update the start value
872 : // of the transition using TimeStamp::Now(). This allows us to avoid a
873 : // large jump when starting a new transition when the main thread lags behind
874 : // the compositor.
875 0 : if (oldPT &&
876 0 : oldPT->IsCurrent() &&
877 0 : oldPT->IsRunningOnCompositor() &&
878 0 : !oldPT->GetAnimation()->GetStartTime().IsNull() &&
879 0 : timeline == oldPT->GetAnimation()->GetTimeline()) {
880 : const AnimationPropertySegment& segment =
881 0 : oldPT->Properties()[0].mSegments[0];
882 0 : pt->mReplacedTransition.emplace(
883 0 : ElementPropertyTransition::ReplacedTransitionProperties({
884 0 : oldPT->GetAnimation()->GetStartTime().Value(),
885 0 : oldPT->GetAnimation()->PlaybackRate(),
886 : oldPT->SpecifiedTiming(),
887 : segment.mTimingFunction,
888 : segment.mFromValue,
889 : segment.mToValue
890 : })
891 0 : );
892 : }
893 0 : animations[currentIndex]->CancelFromStyle();
894 0 : oldPT = nullptr; // Clear pointer so it doesn't dangle
895 0 : animations[currentIndex] = animation;
896 : } else {
897 0 : if (!animations.AppendElement(animation)) {
898 0 : NS_WARNING("out of memory");
899 0 : return false;
900 : }
901 : }
902 :
903 1 : EffectSet* effectSet = EffectSet::GetEffectSet(aElement, aPseudoType);
904 2 : if (effectSet) {
905 2 : effectSet->UpdateAnimationGeneration(mPresContext);
906 : }
907 :
908 : return true;
909 0 : }
910 :
|