forked from mirrors/gecko-dev
		
	 e83e1a5e71
			
		
	
	
		e83e1a5e71
		
	
	
	
	
		
			
			This reflects the change made to the Web Animations specification in:9e2053f5531c3415f4cc(I got it wrong the first time. The second commit fixes the first.) And discussed in: https://github.com/w3c/web-animations/issues/196 In summary, we are splitting the "pending" play state out into a separate boolean member so that it is possible to distinguish between "play-pending" and "pause-pending" and because most of the time when you check for animation.playState === 'running' you also really want to include play-pending animations. MozReview-Commit-ID: IJSNoZTKW2I --HG-- extra : rebase_source : 5d17239fd087cfe3cce1c9697eff97d062b6dd4b
		
			
				
	
	
		
			1172 lines
		
	
	
	
		
			45 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1172 lines
		
	
	
	
		
			45 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| /* Code to start and animate CSS transitions. */
 | |
| 
 | |
| #include "nsTransitionManager.h"
 | |
| #include "nsAnimationManager.h"
 | |
| #include "mozilla/dom/CSSTransitionBinding.h"
 | |
| 
 | |
| #include "nsIContent.h"
 | |
| #include "nsContentUtils.h"
 | |
| #include "nsStyleContext.h"
 | |
| #include "mozilla/MemoryReporting.h"
 | |
| #include "mozilla/TimeStamp.h"
 | |
| #include "nsRefreshDriver.h"
 | |
| #include "nsRuleProcessorData.h"
 | |
| #include "nsRuleWalker.h"
 | |
| #include "nsCSSPropertyIDSet.h"
 | |
| #include "mozilla/EffectCompositor.h"
 | |
| #include "mozilla/EffectSet.h"
 | |
| #include "mozilla/EventDispatcher.h"
 | |
| #include "mozilla/ServoBindings.h"
 | |
| #include "mozilla/StyleAnimationValue.h"
 | |
| #include "mozilla/dom/DocumentTimeline.h"
 | |
| #include "mozilla/dom/Element.h"
 | |
| #include "nsIFrame.h"
 | |
| #include "Layers.h"
 | |
| #include "FrameLayerBuilder.h"
 | |
| #include "nsCSSProps.h"
 | |
| #include "nsCSSPseudoElements.h"
 | |
| #include "nsDisplayList.h"
 | |
| #include "nsStyleChangeList.h"
 | |
| #include "nsStyleSet.h"
 | |
| #include "mozilla/RestyleManager.h"
 | |
| #include "mozilla/RestyleManagerInlines.h"
 | |
| #include "nsDOMMutationObserver.h"
 | |
| 
 | |
| using mozilla::TimeStamp;
 | |
| using mozilla::TimeDuration;
 | |
| using mozilla::dom::Animation;
 | |
| using mozilla::dom::AnimationPlayState;
 | |
| using mozilla::dom::CSSTransition;
 | |
| using mozilla::dom::KeyframeEffectReadOnly;
 | |
| 
 | |
| using namespace mozilla;
 | |
| using namespace mozilla::css;
 | |
| 
 | |
| namespace {
 | |
| struct TransitionEventParams {
 | |
|   EventMessage mMessage;
 | |
|   StickyTimeDuration mElapsedTime;
 | |
|   TimeStamp mTimeStamp;
 | |
| };
 | |
| } // anonymous namespace
 | |
| 
 | |
| double
 | |
| ElementPropertyTransition::CurrentValuePortion() const
 | |
| {
 | |
|   MOZ_ASSERT(!GetLocalTime().IsNull(),
 | |
|              "Getting the value portion of an animation that's not being "
 | |
|              "sampled");
 | |
| 
 | |
|   // Transitions use a fill mode of 'backwards' so GetComputedTiming will
 | |
|   // never return a null time progress due to being *before* the animation
 | |
|   // interval. However, it might be possible that we're behind on flushing
 | |
|   // causing us to get called *after* the animation interval. So, just in
 | |
|   // case, we override the fill mode to 'both' to ensure the progress
 | |
|   // is never null.
 | |
|   TimingParams timingToUse = SpecifiedTiming();
 | |
|   timingToUse.SetFill(dom::FillMode::Both);
 | |
|   ComputedTiming computedTiming = GetComputedTiming(&timingToUse);
 | |
| 
 | |
|   MOZ_ASSERT(!computedTiming.mProgress.IsNull(),
 | |
|              "Got a null progress for a fill mode of 'both'");
 | |
|   MOZ_ASSERT(mKeyframes.Length() == 2,
 | |
|              "Should have two animation keyframes for a transition");
 | |
|   return ComputedTimingFunction::GetPortion(mKeyframes[0].mTimingFunction,
 | |
|                                             computedTiming.mProgress.Value(),
 | |
|                                             computedTiming.mBeforeFlag);
 | |
| }
 | |
| 
 | |
| void
 | |
| ElementPropertyTransition::UpdateStartValueFromReplacedTransition()
 | |
| {
 | |
|   if (!mReplacedTransition) {
 | |
|     return;
 | |
|   }
 | |
|   MOZ_ASSERT(nsCSSProps::PropHasFlags(TransitionProperty(),
 | |
|                                       CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR),
 | |
|              "The transition property should be able to be run on the "
 | |
|              "compositor");
 | |
|   MOZ_ASSERT(mTarget && mTarget->mElement->OwnerDoc(),
 | |
|              "We should have a valid document at this moment");
 | |
| 
 | |
|   dom::DocumentTimeline* timeline = mTarget->mElement->OwnerDoc()->Timeline();
 | |
|   ComputedTiming computedTiming = GetComputedTimingAt(
 | |
|     dom::CSSTransition::GetCurrentTimeAt(*timeline,
 | |
|                                          TimeStamp::Now(),
 | |
|                                          mReplacedTransition->mStartTime,
 | |
|                                          mReplacedTransition->mPlaybackRate),
 | |
|     mReplacedTransition->mTiming,
 | |
|     mReplacedTransition->mPlaybackRate);
 | |
| 
 | |
|   if (!computedTiming.mProgress.IsNull()) {
 | |
|     double valuePosition =
 | |
|       ComputedTimingFunction::GetPortion(mReplacedTransition->mTimingFunction,
 | |
|                                          computedTiming.mProgress.Value(),
 | |
|                                          computedTiming.mBeforeFlag);
 | |
| 
 | |
|     MOZ_ASSERT(mProperties.Length() == 1 &&
 | |
|                mProperties[0].mSegments.Length() == 1,
 | |
|                "The transition should have one property and one segment");
 | |
|     MOZ_ASSERT(mKeyframes.Length() == 2,
 | |
|                "Transitions should have exactly two animation keyframes");
 | |
|     MOZ_ASSERT(mKeyframes[0].mPropertyValues.Length() == 1,
 | |
|                "Transitions should have exactly one property in their first "
 | |
|                "frame");
 | |
| 
 | |
|     const AnimationValue& replacedFrom = mReplacedTransition->mFromValue;
 | |
|     const AnimationValue& replacedTo = mReplacedTransition->mToValue;
 | |
|     AnimationValue startValue;
 | |
|     if (mDocument->IsStyledByServo()) {
 | |
|       startValue.mServo =
 | |
|         Servo_AnimationValues_Interpolate(replacedFrom.mServo,
 | |
|                                           replacedTo.mServo,
 | |
|                                           valuePosition).Consume();
 | |
|       if (startValue.mServo) {
 | |
|         mKeyframes[0].mPropertyValues[0].mServoDeclarationBlock =
 | |
|           Servo_AnimationValue_Uncompute(startValue.mServo).Consume();
 | |
|         mProperties[0].mSegments[0].mFromValue = Move(startValue);
 | |
|       }
 | |
|     } else if (StyleAnimationValue::Interpolate(mProperties[0].mProperty,
 | |
|                                                 replacedFrom.mGecko,
 | |
|                                                 replacedTo.mGecko,
 | |
|                                                 valuePosition,
 | |
|                                                 startValue.mGecko)) {
 | |
|       nsCSSValue cssValue;
 | |
|       DebugOnly<bool> uncomputeResult =
 | |
|         StyleAnimationValue::UncomputeValue(mProperties[0].mProperty,
 | |
|                                             startValue.mGecko,
 | |
|                                             cssValue);
 | |
|       MOZ_ASSERT(uncomputeResult, "UncomputeValue should not fail");
 | |
|       mKeyframes[0].mPropertyValues[0].mValue = cssValue;
 | |
| 
 | |
|       mProperties[0].mSegments[0].mFromValue = Move(startValue);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mReplacedTransition.reset();
 | |
| }
 | |
| 
 | |
| ////////////////////////// CSSTransition ////////////////////////////
 | |
| 
 | |
| JSObject*
 | |
| CSSTransition::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 | |
| {
 | |
|   return dom::CSSTransitionBinding::Wrap(aCx, this, aGivenProto);
 | |
| }
 | |
| 
 | |
| void
 | |
| CSSTransition::GetTransitionProperty(nsString& aRetVal) const
 | |
| {
 | |
|   MOZ_ASSERT(eCSSProperty_UNKNOWN != mTransitionProperty,
 | |
|              "Transition Property should be initialized");
 | |
|   aRetVal =
 | |
|     NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(mTransitionProperty));
 | |
| }
 | |
| 
 | |
| AnimationPlayState
 | |
| CSSTransition::PlayStateFromJS() const
 | |
| {
 | |
|   FlushStyle();
 | |
|   return Animation::PlayStateFromJS();
 | |
| }
 | |
| 
 | |
| bool
 | |
| CSSTransition::PendingFromJS() const
 | |
| {
 | |
|   // Transitions don't become pending again after they start running but, if
 | |
|   // while the transition is still pending, style is updated in such a way
 | |
|   // that the transition will be canceled, we need to report false here.
 | |
|   // Hence we need to flush, but only when we're pending.
 | |
|   if (Pending()) {
 | |
|     FlushStyle();
 | |
|   }
 | |
|   return Animation::PendingFromJS();
 | |
| }
 | |
| 
 | |
| void
 | |
| CSSTransition::PlayFromJS(ErrorResult& aRv)
 | |
| {
 | |
|   FlushStyle();
 | |
|   Animation::PlayFromJS(aRv);
 | |
| }
 | |
| 
 | |
| void
 | |
| CSSTransition::UpdateTiming(SeekFlag aSeekFlag, SyncNotifyFlag aSyncNotifyFlag)
 | |
| {
 | |
|   if (mNeedsNewAnimationIndexWhenRun &&
 | |
|       PlayState() != AnimationPlayState::Idle) {
 | |
|     mAnimationIndex = sNextAnimationIndex++;
 | |
|     mNeedsNewAnimationIndexWhenRun = false;
 | |
|   }
 | |
| 
 | |
|   Animation::UpdateTiming(aSeekFlag, aSyncNotifyFlag);
 | |
| }
 | |
| 
 | |
| void
 | |
| CSSTransition::QueueEvents(StickyTimeDuration aActiveTime)
 | |
| {
 | |
|   if (!mOwningElement.IsSet()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   dom::Element* owningElement;
 | |
|   CSSPseudoElementType owningPseudoType;
 | |
|   mOwningElement.GetElement(owningElement, owningPseudoType);
 | |
|   MOZ_ASSERT(owningElement, "Owning element should be set");
 | |
| 
 | |
|   nsPresContext* presContext =
 | |
|     nsContentUtils::GetContextForContent(owningElement);
 | |
|   if (!presContext) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   const StickyTimeDuration zeroDuration = StickyTimeDuration();
 | |
| 
 | |
|   TransitionPhase currentPhase;
 | |
|   StickyTimeDuration intervalStartTime;
 | |
|   StickyTimeDuration intervalEndTime;
 | |
| 
 | |
|   if (!mEffect) {
 | |
|     currentPhase = GetAnimationPhaseWithoutEffect<TransitionPhase>(*this);
 | |
|   } else {
 | |
|     ComputedTiming computedTiming = mEffect->GetComputedTiming();
 | |
| 
 | |
|     currentPhase = static_cast<TransitionPhase>(computedTiming.mPhase);
 | |
|     intervalStartTime =
 | |
|       std::max(std::min(StickyTimeDuration(-mEffect->SpecifiedTiming().Delay()),
 | |
|                         computedTiming.mActiveDuration), zeroDuration);
 | |
|     intervalEndTime =
 | |
|       std::max(std::min((EffectEnd() - mEffect->SpecifiedTiming().Delay()),
 | |
|                         computedTiming.mActiveDuration), zeroDuration);
 | |
|   }
 | |
| 
 | |
|   // TimeStamps to use for ordering the events when they are dispatched. We
 | |
|   // use a TimeStamp so we can compare events produced by different elements,
 | |
|   // perhaps even with different timelines.
 | |
|   // The zero timestamp is for transitionrun events where we ignore the delay
 | |
|   // for the purpose of ordering events.
 | |
|   TimeStamp zeroTimeStamp  = AnimationTimeToTimeStamp(zeroDuration);
 | |
|   TimeStamp startTimeStamp = ElapsedTimeToTimeStamp(intervalStartTime);
 | |
|   TimeStamp endTimeStamp   = ElapsedTimeToTimeStamp(intervalEndTime);
 | |
| 
 | |
|   if (mPendingState != PendingState::NotPending &&
 | |
|       (mPreviousTransitionPhase == TransitionPhase::Idle ||
 | |
|        mPreviousTransitionPhase == TransitionPhase::Pending))
 | |
|   {
 | |
|     currentPhase = TransitionPhase::Pending;
 | |
|   }
 | |
| 
 | |
|   AutoTArray<TransitionEventParams, 3> events;
 | |
| 
 | |
|   // Handle cancel events first
 | |
|   if ((mPreviousTransitionPhase != TransitionPhase::Idle &&
 | |
|        mPreviousTransitionPhase != TransitionPhase::After) &&
 | |
|       currentPhase == TransitionPhase::Idle) {
 | |
|     TimeStamp activeTimeStamp = ElapsedTimeToTimeStamp(aActiveTime);
 | |
|     events.AppendElement(TransitionEventParams{ eTransitionCancel,
 | |
|                                                 aActiveTime,
 | |
|                                                 activeTimeStamp });
 | |
|   }
 | |
| 
 | |
|   // All other events
 | |
|   switch (mPreviousTransitionPhase) {
 | |
|     case TransitionPhase::Idle:
 | |
|       if (currentPhase == TransitionPhase::Pending ||
 | |
|           currentPhase == TransitionPhase::Before) {
 | |
|         events.AppendElement(TransitionEventParams{ eTransitionRun,
 | |
|                                                     intervalStartTime,
 | |
|                                                     zeroTimeStamp });
 | |
|       } else if (currentPhase == TransitionPhase::Active) {
 | |
|         events.AppendElement(TransitionEventParams{ eTransitionRun,
 | |
|                                                     intervalStartTime,
 | |
|                                                     zeroTimeStamp });
 | |
|         events.AppendElement(TransitionEventParams{ eTransitionStart,
 | |
|                                                     intervalStartTime,
 | |
|                                                     startTimeStamp });
 | |
|       } else if (currentPhase == TransitionPhase::After) {
 | |
|         events.AppendElement(TransitionEventParams{ eTransitionRun,
 | |
|                                                     intervalStartTime,
 | |
|                                                     zeroTimeStamp });
 | |
|         events.AppendElement(TransitionEventParams{ eTransitionStart,
 | |
|                                                     intervalStartTime,
 | |
|                                                     startTimeStamp });
 | |
|         events.AppendElement(TransitionEventParams{ eTransitionEnd,
 | |
|                                                     intervalEndTime,
 | |
|                                                     endTimeStamp });
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     case TransitionPhase::Pending:
 | |
|     case TransitionPhase::Before:
 | |
|       if (currentPhase == TransitionPhase::Active) {
 | |
|         events.AppendElement(TransitionEventParams{ eTransitionStart,
 | |
|                                                     intervalStartTime,
 | |
|                                                     startTimeStamp });
 | |
|       } else if (currentPhase == TransitionPhase::After) {
 | |
|         events.AppendElement(TransitionEventParams{ eTransitionStart,
 | |
|                                                     intervalStartTime,
 | |
|                                                     startTimeStamp });
 | |
|         events.AppendElement(TransitionEventParams{ eTransitionEnd,
 | |
|                                                     intervalEndTime,
 | |
|                                                     endTimeStamp });
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     case TransitionPhase::Active:
 | |
|       if (currentPhase == TransitionPhase::After) {
 | |
|         events.AppendElement(TransitionEventParams{ eTransitionEnd,
 | |
|                                                     intervalEndTime,
 | |
|                                                     endTimeStamp });
 | |
|       } else if (currentPhase == TransitionPhase::Before) {
 | |
|         events.AppendElement(TransitionEventParams{ eTransitionEnd,
 | |
|                                                     intervalStartTime,
 | |
|                                                     startTimeStamp });
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     case TransitionPhase::After:
 | |
|       if (currentPhase == TransitionPhase::Active) {
 | |
|         events.AppendElement(TransitionEventParams{ eTransitionStart,
 | |
|                                                     intervalEndTime,
 | |
|                                                     startTimeStamp });
 | |
|       } else if (currentPhase == TransitionPhase::Before) {
 | |
|         events.AppendElement(TransitionEventParams{ eTransitionStart,
 | |
|                                                     intervalEndTime,
 | |
|                                                     startTimeStamp });
 | |
|         events.AppendElement(TransitionEventParams{ eTransitionEnd,
 | |
|                                                     intervalStartTime,
 | |
|                                                     endTimeStamp });
 | |
|       }
 | |
|       break;
 | |
|   }
 | |
|   mPreviousTransitionPhase = currentPhase;
 | |
| 
 | |
|   nsTransitionManager* manager = presContext->TransitionManager();
 | |
|   for (const TransitionEventParams& evt : events) {
 | |
|     manager->QueueEvent(TransitionEventInfo(owningElement, owningPseudoType,
 | |
|                                             evt.mMessage,
 | |
|                                             TransitionProperty(),
 | |
|                                             evt.mElapsedTime,
 | |
|                                             evt.mTimeStamp,
 | |
|                                             this));
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| CSSTransition::Tick()
 | |
| {
 | |
|   Animation::Tick();
 | |
|   QueueEvents();
 | |
| }
 | |
| 
 | |
| nsCSSPropertyID
 | |
| CSSTransition::TransitionProperty() const
 | |
| {
 | |
|   MOZ_ASSERT(eCSSProperty_UNKNOWN != mTransitionProperty,
 | |
|              "Transition property should be initialized");
 | |
|   return mTransitionProperty;
 | |
| }
 | |
| 
 | |
| AnimationValue
 | |
| CSSTransition::ToValue() const
 | |
| {
 | |
|   MOZ_ASSERT(!mTransitionToValue.IsNull(),
 | |
|              "Transition ToValue should be initialized");
 | |
|   return mTransitionToValue;
 | |
| }
 | |
| 
 | |
| bool
 | |
| CSSTransition::HasLowerCompositeOrderThan(const CSSTransition& aOther) const
 | |
| {
 | |
|   MOZ_ASSERT(IsTiedToMarkup() && aOther.IsTiedToMarkup(),
 | |
|              "Should only be called for CSS transitions that are sorted "
 | |
|              "as CSS transitions (i.e. tied to CSS markup)");
 | |
| 
 | |
|   // 0. Object-equality case
 | |
|   if (&aOther == this) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // 1. Sort by document order
 | |
|   if (!mOwningElement.Equals(aOther.mOwningElement)) {
 | |
|     return mOwningElement.LessThan(aOther.mOwningElement);
 | |
|   }
 | |
| 
 | |
|   // 2. (Same element and pseudo): Sort by transition generation
 | |
|   if (mAnimationIndex != aOther.mAnimationIndex) {
 | |
|     return mAnimationIndex < aOther.mAnimationIndex;
 | |
|   }
 | |
| 
 | |
|   // 3. (Same transition generation): Sort by transition property
 | |
|   return nsCSSProps::GetStringValue(TransitionProperty()) <
 | |
|          nsCSSProps::GetStringValue(aOther.TransitionProperty());
 | |
| }
 | |
| 
 | |
| /* static */ Nullable<TimeDuration>
 | |
| CSSTransition::GetCurrentTimeAt(const dom::DocumentTimeline& aTimeline,
 | |
|                                 const TimeStamp& aBaseTime,
 | |
|                                 const TimeDuration& aStartTime,
 | |
|                                 double aPlaybackRate)
 | |
| {
 | |
|   Nullable<TimeDuration> result;
 | |
| 
 | |
|   Nullable<TimeDuration> timelineTime = aTimeline.ToTimelineTime(aBaseTime);
 | |
|   if (!timelineTime.IsNull()) {
 | |
|     result.SetValue((timelineTime.Value() - aStartTime)
 | |
|                       .MultDouble(aPlaybackRate));
 | |
|   }
 | |
| 
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| void
 | |
| CSSTransition::SetEffectFromStyle(dom::AnimationEffectReadOnly* aEffect)
 | |
| {
 | |
|   Animation::SetEffectNoUpdate(aEffect);
 | |
| 
 | |
|   // Initialize transition property.
 | |
|   ElementPropertyTransition* pt = aEffect ? aEffect->AsTransition() : nullptr;
 | |
|   if (eCSSProperty_UNKNOWN == mTransitionProperty && pt) {
 | |
|     mTransitionProperty = pt->TransitionProperty();
 | |
|     mTransitionToValue = pt->ToValue();
 | |
|   }
 | |
| }
 | |
| 
 | |
| ////////////////////////// nsTransitionManager ////////////////////////////
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION(nsTransitionManager, mEventDispatcher)
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsTransitionManager, AddRef)
 | |
| NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsTransitionManager, Release)
 | |
| 
 | |
| static inline bool
 | |
| ExtractNonDiscreteComputedValue(nsCSSPropertyID aProperty,
 | |
|                                 GeckoStyleContext* aStyleContext,
 | |
|                                 AnimationValue& aAnimationValue)
 | |
| {
 | |
|   return (nsCSSProps::kAnimTypeTable[aProperty] != eStyleAnimType_Discrete ||
 | |
|           aProperty == eCSSProperty_visibility) &&
 | |
|          StyleAnimationValue::ExtractComputedValue(aProperty, aStyleContext,
 | |
|                                                    aAnimationValue.mGecko);
 | |
| }
 | |
| 
 | |
| static inline bool
 | |
| ExtractNonDiscreteComputedValue(nsCSSPropertyID aProperty,
 | |
|                                 const ServoStyleContext* aComputedStyle,
 | |
|                                 AnimationValue& aAnimationValue)
 | |
| {
 | |
|   if (Servo_Property_IsDiscreteAnimatable(aProperty) &&
 | |
|       aProperty != eCSSProperty_visibility) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   aAnimationValue.mServo =
 | |
|     Servo_ComputedValues_ExtractAnimationValue(aComputedStyle,
 | |
|                                                aProperty).Consume();
 | |
|   return !!aAnimationValue.mServo;
 | |
| }
 | |
| 
 | |
| void
 | |
| nsTransitionManager::StyleContextChanged(dom::Element *aElement,
 | |
|                                          GeckoStyleContext* aOldStyleContext,
 | |
|                                          RefPtr<GeckoStyleContext>* aNewStyleContext /* inout */)
 | |
| {
 | |
|   GeckoStyleContext* newStyleContext = *aNewStyleContext;
 | |
| 
 | |
|   NS_PRECONDITION(aOldStyleContext->GetPseudo() == newStyleContext->GetPseudo(),
 | |
|                   "pseudo type mismatch");
 | |
| 
 | |
|   if (mInAnimationOnlyStyleUpdate) {
 | |
|     // If we're doing an animation-only style update, return, since the
 | |
|     // purpose of an animation-only style update is to update only the
 | |
|     // animation styles so that we don't consider style changes
 | |
|     // resulting from changes in the animation time for starting a
 | |
|     // transition.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!mPresContext->IsDynamic()) {
 | |
|     // For print or print preview, ignore transitions.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (aOldStyleContext->HasPseudoElementData() !=
 | |
|       newStyleContext->HasPseudoElementData()) {
 | |
|     // If the old style context and new style context differ in terms of
 | |
|     // whether they're inside ::first-letter, ::first-line, or similar,
 | |
|     // bail.  We can't hit this codepath for normal style changes
 | |
|     // involving moving frames around the boundaries of these
 | |
|     // pseudo-elements since we don't call StyleContextChanged from
 | |
|     // ReparentStyleContext.  However, we can hit this codepath during
 | |
|     // the handling of transitions that start across reframes.
 | |
|     //
 | |
|     // While there isn't an easy *perfect* way to handle this case, err
 | |
|     // on the side of missing some transitions that we ought to have
 | |
|     // rather than having bogus transitions that we shouldn't.
 | |
|     //
 | |
|     // We could consider changing this handling, although it's worth
 | |
|     // thinking about whether the code below could do anything weird in
 | |
|     // this case.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // NOTE: Things in this function (and ConsiderInitiatingTransition)
 | |
|   // should never call PeekStyleData because we don't preserve gotten
 | |
|   // structs across reframes.
 | |
| 
 | |
|   // Return sooner (before the startedAny check below) for the most
 | |
|   // common case: no transitions specified or running.
 | |
|   const nsStyleDisplay *disp = newStyleContext->StyleDisplay();
 | |
|   CSSPseudoElementType pseudoType = newStyleContext->GetPseudoType();
 | |
|   if (pseudoType != CSSPseudoElementType::NotPseudo) {
 | |
|     if (pseudoType != CSSPseudoElementType::before &&
 | |
|         pseudoType != CSSPseudoElementType::after) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     NS_ASSERTION((pseudoType == CSSPseudoElementType::before &&
 | |
|                   aElement->IsGeneratedContentContainerForBefore()) ||
 | |
|                  (pseudoType == CSSPseudoElementType::after &&
 | |
|                   aElement->IsGeneratedContentContainerForAfter()),
 | |
|                  "Unexpected aElement coming through");
 | |
| 
 | |
|     // Else the element we want to use from now on is the element the
 | |
|     // :before or :after is attached to.
 | |
|     aElement = aElement->GetParent()->AsElement();
 | |
|   }
 | |
| 
 | |
|   CSSTransitionCollection* collection =
 | |
|     CSSTransitionCollection::GetAnimationCollection(aElement, pseudoType);
 | |
|   if (!collection &&
 | |
|       disp->mTransitionPropertyCount == 1 &&
 | |
|       disp->mTransitions[0].GetCombinedDuration() <= 0.0f) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(mPresContext->RestyleManager()->IsGecko(),
 | |
|              "ServoRestyleManager should not use nsTransitionManager "
 | |
|              "for transitions");
 | |
|   if (collection &&
 | |
|       collection->mCheckGeneration ==
 | |
|         mPresContext->RestyleManager()->GetAnimationGeneration()) {
 | |
|     // When we start a new transition, we immediately post a restyle.
 | |
|     // If the animation generation on the collection is current, that
 | |
|     // means *this* is that restyle, since we bump the animation
 | |
|     // generation on the restyle manager whenever there's a real style
 | |
|     // change (i.e., one where mInAnimationOnlyStyleUpdate isn't true,
 | |
|     // which causes us to return above).  Thus we shouldn't do anything.
 | |
|     return;
 | |
|   }
 | |
|   if (newStyleContext->GetParent() &&
 | |
|       newStyleContext->GetParent()->HasPseudoElementData()) {
 | |
|     // Ignore transitions on things that inherit properties from
 | |
|     // pseudo-elements.
 | |
|     // FIXME (Bug 522599): Add tests for this.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   NS_WARNING_ASSERTION(
 | |
|     !mPresContext->EffectCompositor()->HasThrottledStyleUpdates(),
 | |
|     "throttled animations not up to date");
 | |
| 
 | |
|   // Compute what the css-transitions spec calls the "after-change
 | |
|   // style", which is the new style without any data from transitions,
 | |
|   // but still inheriting from data that contains transitions that are
 | |
|   // not stopping or starting right now.
 | |
|   RefPtr<GeckoStyleContext> afterChangeStyle;
 | |
|   if (collection) {
 | |
|     MOZ_ASSERT(mPresContext->StyleSet()->IsGecko(),
 | |
|                "ServoStyleSets should not use nsTransitionManager "
 | |
|                "for transitions");
 | |
|     nsStyleSet* styleSet = mPresContext->StyleSet()->AsGecko();
 | |
|     afterChangeStyle =
 | |
|       styleSet->ResolveStyleByRemovingAnimation(aElement, newStyleContext,
 | |
|                                                 eRestyle_CSSTransitions);
 | |
|   } else {
 | |
|     afterChangeStyle = newStyleContext;
 | |
|   }
 | |
| 
 | |
|   nsAutoAnimationMutationBatch mb(aElement->OwnerDoc());
 | |
| 
 | |
|   DebugOnly<bool> startedAny = false;
 | |
|   // We don't have to update transitions if display:none, although we will
 | |
|   // cancel them after restyling.
 | |
|   if (!afterChangeStyle->IsInDisplayNoneSubtree()) {
 | |
|     startedAny = DoUpdateTransitions(disp,
 | |
|                                      aElement,
 | |
|                                      afterChangeStyle->GetPseudoType(),
 | |
|                                      collection,
 | |
|                                      aOldStyleContext->AsGecko(),
 | |
|                                      afterChangeStyle->AsGecko());
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(!startedAny || collection,
 | |
|              "must have element transitions if we started any transitions");
 | |
| 
 | |
|   EffectCompositor::CascadeLevel cascadeLevel =
 | |
|     EffectCompositor::CascadeLevel::Transitions;
 | |
| 
 | |
|   if (collection) {
 | |
|     collection->UpdateCheckGeneration(mPresContext);
 | |
|     mPresContext->EffectCompositor()->MaybeUpdateAnimationRule(aElement,
 | |
|                                                                pseudoType,
 | |
|                                                                cascadeLevel,
 | |
|                                                                newStyleContext);
 | |
|   }
 | |
| 
 | |
|   // We want to replace the new style context with the after-change style.
 | |
|   *aNewStyleContext = afterChangeStyle;
 | |
|   if (collection) {
 | |
|     // Since we have transition styles, we have to undo this replacement.
 | |
|     // The check of collection->mCheckGeneration against the restyle
 | |
|     // manager's GetAnimationGeneration() will ensure that we don't go
 | |
|     // through the rest of this function again when we do.
 | |
|     mPresContext->EffectCompositor()->PostRestyleForAnimation(aElement,
 | |
|                                                               pseudoType,
 | |
|                                                               cascadeLevel);
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool
 | |
| nsTransitionManager::UpdateTransitions(
 | |
|   dom::Element *aElement,
 | |
|   CSSPseudoElementType aPseudoType,
 | |
|   const ServoStyleContext* aOldStyle,
 | |
|   const ServoStyleContext* aNewStyle)
 | |
| {
 | |
|   if (!mPresContext->IsDynamic()) {
 | |
|     // For print or print preview, ignore transitions.
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   CSSTransitionCollection* collection =
 | |
|     CSSTransitionCollection::GetAnimationCollection(aElement, aPseudoType);
 | |
|   const nsStyleDisplay *disp =
 | |
|       aNewStyle->ComputedData()->GetStyleDisplay();
 | |
|   return DoUpdateTransitions(disp,
 | |
|                              aElement, aPseudoType,
 | |
|                              collection,
 | |
|                              aOldStyle, aNewStyle);
 | |
| }
 | |
| 
 | |
| template<typename StyleType>
 | |
| bool
 | |
| nsTransitionManager::DoUpdateTransitions(
 | |
|   const nsStyleDisplay* aDisp,
 | |
|   dom::Element* aElement,
 | |
|   CSSPseudoElementType aPseudoType,
 | |
|   CSSTransitionCollection*& aElementTransitions,
 | |
|   StyleType aOldStyle,
 | |
|   StyleType aNewStyle)
 | |
| {
 | |
|   MOZ_ASSERT(aDisp, "Null nsStyleDisplay");
 | |
|   MOZ_ASSERT(!aElementTransitions ||
 | |
|              aElementTransitions->mElement == aElement, "Element mismatch");
 | |
| 
 | |
|   // Per http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html
 | |
|   // I'll consider only the transitions from the number of items in
 | |
|   // 'transition-property' on down, and later ones will override earlier
 | |
|   // ones (tracked using |whichStarted|).
 | |
|   bool startedAny = false;
 | |
|   nsCSSPropertyIDSet whichStarted;
 | |
|   for (uint32_t i = aDisp->mTransitionPropertyCount; i-- != 0; ) {
 | |
|     const StyleTransition& t = aDisp->mTransitions[i];
 | |
|     // Check the combined duration (combination of delay and duration)
 | |
|     // first, since it defaults to zero, which means we can ignore the
 | |
|     // transition.
 | |
|     if (t.GetCombinedDuration() > 0.0f) {
 | |
|       // We might have something to transition.  See if any of the
 | |
|       // properties in question changed and are animatable.
 | |
|       // FIXME: Would be good to find a way to share code between this
 | |
|       // interpretation of transition-property and the one below.
 | |
|       nsCSSPropertyID property = t.GetProperty();
 | |
|       if (property == eCSSPropertyExtra_no_properties ||
 | |
|           property == eCSSPropertyExtra_variable ||
 | |
|           property == eCSSProperty_UNKNOWN) {
 | |
|         // Nothing to do, but need to exclude this from cases below.
 | |
|       } else if (property == eCSSPropertyExtra_all_properties) {
 | |
|         for (nsCSSPropertyID p = nsCSSPropertyID(0);
 | |
|              p < eCSSProperty_COUNT_no_shorthands;
 | |
|              p = nsCSSPropertyID(p + 1)) {
 | |
|           ConsiderInitiatingTransition(p, t, aElement, aPseudoType,
 | |
|                                        aElementTransitions,
 | |
|                                        aOldStyle, aNewStyle,
 | |
|                                        &startedAny, &whichStarted);
 | |
|         }
 | |
|       } else if (nsCSSProps::IsShorthand(property)) {
 | |
|         CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, property,
 | |
|                                              CSSEnabledState::eForAllContent)
 | |
|         {
 | |
|           ConsiderInitiatingTransition(*subprop, t, aElement, aPseudoType,
 | |
|                                        aElementTransitions,
 | |
|                                        aOldStyle, aNewStyle,
 | |
|                                        &startedAny, &whichStarted);
 | |
|         }
 | |
|       } else {
 | |
|         ConsiderInitiatingTransition(property, t, aElement, aPseudoType,
 | |
|                                      aElementTransitions,
 | |
|                                      aOldStyle, aNewStyle,
 | |
|                                      &startedAny, &whichStarted);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Stop any transitions for properties that are no longer in
 | |
|   // 'transition-property', including finished transitions.
 | |
|   // Also stop any transitions (and remove any finished transitions)
 | |
|   // for properties that just changed (and are still in the set of
 | |
|   // properties to transition), but for which we didn't just start the
 | |
|   // transition.  This can happen delay and duration are both zero, or
 | |
|   // because the new value is not interpolable.
 | |
|   // Note that we also do the latter set of work in
 | |
|   // nsTransitionManager::PruneCompletedTransitions.
 | |
|   if (aElementTransitions) {
 | |
|     bool checkProperties =
 | |
|       aDisp->mTransitions[0].GetProperty() != eCSSPropertyExtra_all_properties;
 | |
|     nsCSSPropertyIDSet allTransitionProperties;
 | |
|     if (checkProperties) {
 | |
|       for (uint32_t i = aDisp->mTransitionPropertyCount; i-- != 0; ) {
 | |
|         const StyleTransition& t = aDisp->mTransitions[i];
 | |
|         // FIXME: Would be good to find a way to share code between this
 | |
|         // interpretation of transition-property and the one above.
 | |
|         nsCSSPropertyID property = t.GetProperty();
 | |
|         if (property == eCSSPropertyExtra_no_properties ||
 | |
|             property == eCSSPropertyExtra_variable ||
 | |
|             property == eCSSProperty_UNKNOWN) {
 | |
|           // Nothing to do, but need to exclude this from cases below.
 | |
|         } else if (property == eCSSPropertyExtra_all_properties) {
 | |
|           for (nsCSSPropertyID p = nsCSSPropertyID(0);
 | |
|                p < eCSSProperty_COUNT_no_shorthands;
 | |
|                p = nsCSSPropertyID(p + 1)) {
 | |
|             allTransitionProperties.AddProperty(p);
 | |
|           }
 | |
|         } else if (nsCSSProps::IsShorthand(property)) {
 | |
|           CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(
 | |
|               subprop, property, CSSEnabledState::eForAllContent) {
 | |
|             allTransitionProperties.AddProperty(*subprop);
 | |
|           }
 | |
|         } else {
 | |
|           allTransitionProperties.AddProperty(property);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     OwningCSSTransitionPtrArray& animations = aElementTransitions->mAnimations;
 | |
|     size_t i = animations.Length();
 | |
|     MOZ_ASSERT(i != 0, "empty transitions list?");
 | |
|     AnimationValue currentValue;
 | |
|     do {
 | |
|       --i;
 | |
|       CSSTransition* anim = animations[i];
 | |
|           // properties no longer in 'transition-property'
 | |
|       if ((checkProperties &&
 | |
|            !allTransitionProperties.HasProperty(anim->TransitionProperty())) ||
 | |
|           // properties whose computed values changed but for which we
 | |
|           // did not start a new transition (because delay and
 | |
|           // duration are both zero, or because the new value is not
 | |
|           // interpolable); a new transition would have anim->ToValue()
 | |
|           // matching currentValue
 | |
|           !ExtractNonDiscreteComputedValue(anim->TransitionProperty(),
 | |
|                                            aNewStyle, currentValue) ||
 | |
|           currentValue != anim->ToValue()) {
 | |
|         // stop the transition
 | |
|         if (anim->HasCurrentEffect()) {
 | |
|           EffectSet* effectSet =
 | |
|             EffectSet::GetEffectSet(aElement, aPseudoType);
 | |
|           if (effectSet) {
 | |
|             effectSet->UpdateAnimationGeneration(mPresContext);
 | |
|           }
 | |
|         }
 | |
|         anim->CancelFromStyle();
 | |
|         animations.RemoveElementAt(i);
 | |
|       }
 | |
|     } while (i != 0);
 | |
| 
 | |
|     if (animations.IsEmpty()) {
 | |
|       aElementTransitions->Destroy();
 | |
|       aElementTransitions = nullptr;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return startedAny;
 | |
| }
 | |
| 
 | |
| static Keyframe&
 | |
| AppendKeyframe(double aOffset,
 | |
|                nsCSSPropertyID aProperty,
 | |
|                AnimationValue&& aValue,
 | |
|                nsTArray<Keyframe>& aKeyframes)
 | |
| {
 | |
|   Keyframe& frame = *aKeyframes.AppendElement();
 | |
|   frame.mOffset.emplace(aOffset);
 | |
| 
 | |
|   if (aValue.mServo) {
 | |
|     RefPtr<RawServoDeclarationBlock> decl =
 | |
|       Servo_AnimationValue_Uncompute(aValue.mServo).Consume();
 | |
|     frame.mPropertyValues.AppendElement(
 | |
|       Move(PropertyValuePair(aProperty, Move(decl))));
 | |
|   } else {
 | |
|     nsCSSValue propertyValue;
 | |
|     DebugOnly<bool> uncomputeResult =
 | |
|       StyleAnimationValue::UncomputeValue(aProperty, Move(aValue.mGecko),
 | |
|                                           propertyValue);
 | |
|     MOZ_ASSERT(uncomputeResult,
 | |
|                "Unable to get specified value from computed value");
 | |
|     frame.mPropertyValues.AppendElement(
 | |
|       Move(PropertyValuePair(aProperty, Move(propertyValue))));
 | |
|   }
 | |
|   return frame;
 | |
| }
 | |
| 
 | |
| static nsTArray<Keyframe>
 | |
| GetTransitionKeyframes(nsCSSPropertyID aProperty,
 | |
|                        AnimationValue&& aStartValue,
 | |
|                        AnimationValue&& aEndValue,
 | |
|                        const nsTimingFunction& aTimingFunction)
 | |
| {
 | |
|   nsTArray<Keyframe> keyframes(2);
 | |
| 
 | |
|   Keyframe& fromFrame = AppendKeyframe(0.0, aProperty, Move(aStartValue),
 | |
|                                        keyframes);
 | |
|   if (aTimingFunction.mType != nsTimingFunction::Type::Linear) {
 | |
|     fromFrame.mTimingFunction.emplace();
 | |
|     fromFrame.mTimingFunction->Init(aTimingFunction);
 | |
|   }
 | |
| 
 | |
|   AppendKeyframe(1.0, aProperty, Move(aEndValue), keyframes);
 | |
| 
 | |
|   return keyframes;
 | |
| }
 | |
| 
 | |
| static bool
 | |
| IsTransitionable(nsCSSPropertyID aProperty, bool aIsServo)
 | |
| {
 | |
|   if (aIsServo) {
 | |
|     return Servo_Property_IsTransitionable(aProperty);
 | |
|   }
 | |
| 
 | |
|   // FIXME: This should also exclude discretely-animated properties.
 | |
|   return nsCSSProps::kAnimTypeTable[aProperty] != eStyleAnimType_None;
 | |
| }
 | |
| 
 | |
| template<typename StyleType>
 | |
| void
 | |
| nsTransitionManager::ConsiderInitiatingTransition(
 | |
|   nsCSSPropertyID aProperty,
 | |
|   const StyleTransition& aTransition,
 | |
|   dom::Element* aElement,
 | |
|   CSSPseudoElementType aPseudoType,
 | |
|   CSSTransitionCollection*& aElementTransitions,
 | |
|   StyleType aOldStyle,
 | |
|   StyleType aNewStyle,
 | |
|   bool* aStartedAny,
 | |
|   nsCSSPropertyIDSet* aWhichStarted)
 | |
| {
 | |
|   // IsShorthand itself will assert if aProperty is not a property.
 | |
|   MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty),
 | |
|              "property out of range");
 | |
|   NS_ASSERTION(!aElementTransitions ||
 | |
|                aElementTransitions->mElement == aElement, "Element mismatch");
 | |
| 
 | |
|   // Ignore disabled properties. We can arrive here if the transition-property
 | |
|   // is 'all' and the disabled property has a default value which derives value
 | |
|   // from another property, e.g. color.
 | |
|   if (!nsCSSProps::IsEnabled(aProperty, CSSEnabledState::eForAllContent)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (aWhichStarted->HasProperty(aProperty)) {
 | |
|     // A later item in transition-property already started a
 | |
|     // transition for this property, so we ignore this one.
 | |
|     // See comment above and
 | |
|     // http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html .
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!IsTransitionable(aProperty, aElement->IsStyledByServo())) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   dom::DocumentTimeline* timeline = aElement->OwnerDoc()->Timeline();
 | |
| 
 | |
|   AnimationValue startValue, endValue;
 | |
|   bool haveValues =
 | |
|     ExtractNonDiscreteComputedValue(aProperty, aOldStyle, startValue) &&
 | |
|     ExtractNonDiscreteComputedValue(aProperty, aNewStyle, endValue);
 | |
| 
 | |
|   bool haveChange = startValue != endValue;
 | |
| 
 | |
|   bool shouldAnimate =
 | |
|     haveValues &&
 | |
|     haveChange &&
 | |
|     startValue.IsInterpolableWith(aProperty, endValue);
 | |
| 
 | |
|   bool haveCurrentTransition = false;
 | |
|   size_t currentIndex = nsTArray<ElementPropertyTransition>::NoIndex;
 | |
|   const ElementPropertyTransition *oldPT = nullptr;
 | |
|   if (aElementTransitions) {
 | |
|     OwningCSSTransitionPtrArray& animations = aElementTransitions->mAnimations;
 | |
|     for (size_t i = 0, i_end = animations.Length(); i < i_end; ++i) {
 | |
|       if (animations[i]->TransitionProperty() == aProperty) {
 | |
|         haveCurrentTransition = true;
 | |
|         currentIndex = i;
 | |
|         oldPT = animations[i]->GetEffect()
 | |
|                 ? animations[i]->GetEffect()->AsTransition()
 | |
|                 : nullptr;
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // If we got a style change that changed the value to the endpoint
 | |
|   // of the currently running transition, we don't want to interrupt
 | |
|   // its timing function.
 | |
|   // This needs to be before the !shouldAnimate && haveCurrentTransition
 | |
|   // case below because we might be close enough to the end of the
 | |
|   // transition that the current value rounds to the final value.  In
 | |
|   // this case, we'll end up with shouldAnimate as false (because
 | |
|   // there's no value change), but we need to return early here rather
 | |
|   // than cancel the running transition because shouldAnimate is false!
 | |
|   //
 | |
|   // Likewise, if we got a style change that changed the value to the
 | |
|   // endpoint of our finished transition, we also don't want to start
 | |
|   // a new transition for the reasons described in
 | |
|   // https://lists.w3.org/Archives/Public/www-style/2015Jan/0444.html .
 | |
|   if (haveCurrentTransition && haveValues &&
 | |
|       aElementTransitions->mAnimations[currentIndex]->ToValue() == endValue) {
 | |
|     // GetAnimationRule already called RestyleForAnimation.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!shouldAnimate) {
 | |
|     if (haveCurrentTransition) {
 | |
|       // We're in the middle of a transition, and just got a non-transition
 | |
|       // style change to something that we can't animate.  This might happen
 | |
|       // because we got a non-transition style change changing to the current
 | |
|       // in-progress value (which is particularly easy to cause when we're
 | |
|       // currently in the 'transition-delay').  It also might happen because we
 | |
|       // just got a style change to a value that can't be interpolated.
 | |
|       OwningCSSTransitionPtrArray& animations =
 | |
|         aElementTransitions->mAnimations;
 | |
|       animations[currentIndex]->CancelFromStyle();
 | |
|       oldPT = nullptr; // Clear pointer so it doesn't dangle
 | |
|       animations.RemoveElementAt(currentIndex);
 | |
|       EffectSet* effectSet = EffectSet::GetEffectSet(aElement, aPseudoType);
 | |
|       if (effectSet) {
 | |
|         effectSet->UpdateAnimationGeneration(mPresContext);
 | |
|       }
 | |
| 
 | |
|       if (animations.IsEmpty()) {
 | |
|         aElementTransitions->Destroy();
 | |
|         // |aElementTransitions| is now a dangling pointer!
 | |
|         aElementTransitions = nullptr;
 | |
|       }
 | |
|       // GetAnimationRule already called RestyleForAnimation.
 | |
|     }
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   const nsTimingFunction &tf = aTransition.GetTimingFunction();
 | |
|   float delay = aTransition.GetDelay();
 | |
|   float duration = aTransition.GetDuration();
 | |
|   if (duration < 0.0) {
 | |
|     // The spec says a negative duration is treated as zero.
 | |
|     duration = 0.0;
 | |
|   }
 | |
| 
 | |
|   AnimationValue startForReversingTest = startValue;
 | |
|   double reversePortion = 1.0;
 | |
| 
 | |
|   // If the new transition reverses an existing one, we'll need to
 | |
|   // handle the timing differently.
 | |
|   // FIXME: Move mStartForReversingTest, mReversePortion to CSSTransition,
 | |
|   //        and set the timing function on transitions as an effect-level
 | |
|   //        easing (rather than keyframe-level easing). (Bug 1292001)
 | |
|   if (haveCurrentTransition &&
 | |
|       aElementTransitions->mAnimations[currentIndex]->HasCurrentEffect() &&
 | |
|       oldPT &&
 | |
|       oldPT->mStartForReversingTest == endValue) {
 | |
|     // Compute the appropriate negative transition-delay such that right
 | |
|     // now we'd end up at the current position.
 | |
|     double valuePortion =
 | |
|       oldPT->CurrentValuePortion() * oldPT->mReversePortion +
 | |
|       (1.0 - oldPT->mReversePortion);
 | |
|     // A timing function with negative y1 (or y2!) might make
 | |
|     // valuePortion negative.  In this case, we still want to apply our
 | |
|     // reversing logic based on relative distances, not make duration
 | |
|     // negative.
 | |
|     if (valuePortion < 0.0) {
 | |
|       valuePortion = -valuePortion;
 | |
|     }
 | |
|     // A timing function with y2 (or y1!) greater than one might
 | |
|     // advance past its terminal value.  It's probably a good idea to
 | |
|     // clamp valuePortion to be at most one to preserve the invariant
 | |
|     // that a transition will complete within at most its specified
 | |
|     // time.
 | |
|     if (valuePortion > 1.0) {
 | |
|       valuePortion = 1.0;
 | |
|     }
 | |
| 
 | |
|     // Negative delays are essentially part of the transition
 | |
|     // function, so reduce them along with the duration, but don't
 | |
|     // reduce positive delays.
 | |
|     if (delay < 0.0f) {
 | |
|       delay *= valuePortion;
 | |
|     }
 | |
| 
 | |
|     duration *= valuePortion;
 | |
| 
 | |
|     startForReversingTest = oldPT->ToValue();
 | |
|     reversePortion = valuePortion;
 | |
|   }
 | |
| 
 | |
|   TimingParams timing =
 | |
|     TimingParamsFromCSSParams(duration, delay,
 | |
|                               1.0 /* iteration count */,
 | |
|                               dom::PlaybackDirection::Normal,
 | |
|                               dom::FillMode::Backwards);
 | |
| 
 | |
|   // aElement is non-null here, so we emplace it directly.
 | |
|   Maybe<OwningAnimationTarget> target;
 | |
|   target.emplace(aElement, aPseudoType);
 | |
|   KeyframeEffectParams effectOptions;
 | |
|   RefPtr<ElementPropertyTransition> pt =
 | |
|     new ElementPropertyTransition(aElement->OwnerDoc(), target, timing,
 | |
|                                   startForReversingTest, reversePortion,
 | |
|                                   effectOptions);
 | |
| 
 | |
|   pt->SetKeyframes(GetTransitionKeyframes(aProperty,
 | |
|                                           Move(startValue), Move(endValue), tf),
 | |
|                    aNewStyle);
 | |
| 
 | |
|   RefPtr<CSSTransition> animation =
 | |
|     new CSSTransition(mPresContext->Document()->GetScopeObject());
 | |
|   animation->SetOwningElement(OwningElementRef(*aElement, aPseudoType));
 | |
|   animation->SetTimelineNoUpdate(timeline);
 | |
|   animation->SetCreationSequence(
 | |
|     mPresContext->RestyleManager()->GetAnimationGeneration());
 | |
|   animation->SetEffectFromStyle(pt);
 | |
|   animation->PlayFromStyle();
 | |
| 
 | |
|   if (!aElementTransitions) {
 | |
|     bool createdCollection = false;
 | |
|     aElementTransitions =
 | |
|       CSSTransitionCollection::GetOrCreateAnimationCollection(
 | |
|         aElement, aPseudoType, &createdCollection);
 | |
|     if (!aElementTransitions) {
 | |
|       MOZ_ASSERT(!createdCollection, "outparam should agree with return value");
 | |
|       NS_WARNING("allocating collection failed");
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (createdCollection) {
 | |
|       AddElementCollection(aElementTransitions);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   OwningCSSTransitionPtrArray& animations = aElementTransitions->mAnimations;
 | |
| #ifdef DEBUG
 | |
|   for (size_t i = 0, i_end = animations.Length(); i < i_end; ++i) {
 | |
|     MOZ_ASSERT(
 | |
|       i == currentIndex || animations[i]->TransitionProperty() != aProperty,
 | |
|       "duplicate transitions for property");
 | |
|   }
 | |
| #endif
 | |
|   if (haveCurrentTransition) {
 | |
|     // If this new transition is replacing an existing transition that is running
 | |
|     // on the compositor, we store select parameters from the replaced transition
 | |
|     // so that later, once all scripts have run, we can update the start value
 | |
|     // of the transition using TimeStamp::Now(). This allows us to avoid a
 | |
|     // large jump when starting a new transition when the main thread lags behind
 | |
|     // the compositor.
 | |
|     if (oldPT &&
 | |
|         oldPT->IsCurrent() &&
 | |
|         oldPT->IsRunningOnCompositor() &&
 | |
|         !oldPT->GetAnimation()->GetStartTime().IsNull() &&
 | |
|         timeline == oldPT->GetAnimation()->GetTimeline()) {
 | |
|       const AnimationPropertySegment& segment =
 | |
|         oldPT->Properties()[0].mSegments[0];
 | |
|       pt->mReplacedTransition.emplace(
 | |
|         ElementPropertyTransition::ReplacedTransitionProperties({
 | |
|           oldPT->GetAnimation()->GetStartTime().Value(),
 | |
|           oldPT->GetAnimation()->PlaybackRate(),
 | |
|           oldPT->SpecifiedTiming(),
 | |
|           segment.mTimingFunction,
 | |
|           segment.mFromValue,
 | |
|           segment.mToValue
 | |
|         })
 | |
|       );
 | |
|     }
 | |
|     animations[currentIndex]->CancelFromStyle();
 | |
|     oldPT = nullptr; // Clear pointer so it doesn't dangle
 | |
|     animations[currentIndex] = animation;
 | |
|   } else {
 | |
|     if (!animations.AppendElement(animation)) {
 | |
|       NS_WARNING("out of memory");
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   EffectSet* effectSet = EffectSet::GetEffectSet(aElement, aPseudoType);
 | |
|   if (effectSet) {
 | |
|     effectSet->UpdateAnimationGeneration(mPresContext);
 | |
|   }
 | |
| 
 | |
|   *aStartedAny = true;
 | |
|   aWhichStarted->AddProperty(aProperty);
 | |
| }
 | |
| 
 | |
| void
 | |
| nsTransitionManager::PruneCompletedTransitions(mozilla::dom::Element* aElement,
 | |
|                                                CSSPseudoElementType aPseudoType,
 | |
|                                                GeckoStyleContext* aNewStyleContext)
 | |
| {
 | |
|   MOZ_ASSERT(!aElement->IsGeneratedContentContainerForBefore() &&
 | |
|              !aElement->IsGeneratedContentContainerForAfter());
 | |
| 
 | |
|   CSSTransitionCollection* collection =
 | |
|     CSSTransitionCollection::GetAnimationCollection(aElement, aPseudoType);
 | |
|   if (!collection) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Remove any finished transitions whose style doesn't match the new
 | |
|   // style.
 | |
|   // This is similar to some of the work that happens near the end of
 | |
|   // nsTransitionManager::StyleContextChanged.
 | |
|   // FIXME (bug 1158431): Really, we should also cancel running
 | |
|   // transitions whose destination doesn't match as well.
 | |
|   OwningCSSTransitionPtrArray& animations = collection->mAnimations;
 | |
|   size_t i = animations.Length();
 | |
|   MOZ_ASSERT(i != 0, "empty transitions list?");
 | |
|   do {
 | |
|     --i;
 | |
|     CSSTransition* anim = animations[i];
 | |
| 
 | |
|     if (anim->HasCurrentEffect()) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     // Since effect is a finished transition, we know it didn't
 | |
|     // influence style.
 | |
|     AnimationValue currentValue;
 | |
|     if (!ExtractNonDiscreteComputedValue(anim->TransitionProperty(),
 | |
|                                          aNewStyleContext, currentValue) ||
 | |
|         currentValue != anim->ToValue()) {
 | |
|       anim->CancelFromStyle();
 | |
|       animations.RemoveElementAt(i);
 | |
|     }
 | |
|   } while (i != 0);
 | |
| 
 | |
|   if (collection->mAnimations.IsEmpty()) {
 | |
|     collection->Destroy();
 | |
|     // |collection| is now a dangling pointer!
 | |
|     collection = nullptr;
 | |
|   }
 | |
| }
 |