LCOV - code coverage report
Current view: top level - layout/style - nsTransitionManager.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 273 482 56.6 %
Date: 2017-07-14 16:53:18 Functions: 17 32 53.1 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
       2             : /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
       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 "nsStyleContext.h"
      16             : #include "mozilla/MemoryReporting.h"
      17             : #include "mozilla/TimeStamp.h"
      18             : #include "nsRefreshDriver.h"
      19             : #include "nsRuleProcessorData.h"
      20             : #include "nsRuleWalker.h"
      21             : #include "nsCSSPropertyIDSet.h"
      22             : #include "mozilla/EffectCompositor.h"
      23             : #include "mozilla/EffectSet.h"
      24             : #include "mozilla/EventDispatcher.h"
      25             : #include "mozilla/ServoBindings.h"
      26             : #include "mozilla/StyleAnimationValue.h"
      27             : #include "mozilla/dom/DocumentTimeline.h"
      28             : #include "mozilla/dom/Element.h"
      29             : #include "nsIFrame.h"
      30             : #include "Layers.h"
      31             : #include "FrameLayerBuilder.h"
      32             : #include "nsCSSProps.h"
      33             : #include "nsCSSPseudoElements.h"
      34             : #include "nsDisplayList.h"
      35             : #include "nsStyleChangeList.h"
      36             : #include "nsStyleSet.h"
      37             : #include "mozilla/RestyleManager.h"
      38             : #include "mozilla/RestyleManagerInlines.h"
      39             : #include "nsDOMMutationObserver.h"
      40             : 
      41             : using mozilla::TimeStamp;
      42             : using mozilla::TimeDuration;
      43             : using mozilla::dom::Animation;
      44             : using mozilla::dom::AnimationPlayState;
      45             : using mozilla::dom::CSSTransition;
      46             : using mozilla::dom::KeyframeEffectReadOnly;
      47             : 
      48             : using namespace mozilla;
      49             : using namespace mozilla::css;
      50             : 
      51             : namespace {
      52             : struct TransitionEventParams {
      53             :   EventMessage mMessage;
      54             :   StickyTimeDuration mElapsedTime;
      55             :   TimeStamp mTimeStamp;
      56             : };
      57             : } // anonymous namespace
      58             : 
      59             : double
      60           0 : ElementPropertyTransition::CurrentValuePortion() const
      61             : {
      62           0 :   MOZ_ASSERT(!GetLocalTime().IsNull(),
      63             :              "Getting the value portion of an animation that's not being "
      64             :              "sampled");
      65             : 
      66             :   // Transitions use a fill mode of 'backwards' so GetComputedTiming will
      67             :   // never return a null time progress due to being *before* the animation
      68             :   // interval. However, it might be possible that we're behind on flushing
      69             :   // causing us to get called *after* the animation interval. So, just in
      70             :   // case, we override the fill mode to 'both' to ensure the progress
      71             :   // is never null.
      72           0 :   TimingParams timingToUse = SpecifiedTiming();
      73           0 :   timingToUse.SetFill(dom::FillMode::Both);
      74           0 :   ComputedTiming computedTiming = GetComputedTiming(&timingToUse);
      75             : 
      76           0 :   MOZ_ASSERT(!computedTiming.mProgress.IsNull(),
      77             :              "Got a null progress for a fill mode of 'both'");
      78           0 :   MOZ_ASSERT(mKeyframes.Length() == 2,
      79             :              "Should have two animation keyframes for a transition");
      80           0 :   return ComputedTimingFunction::GetPortion(mKeyframes[0].mTimingFunction,
      81           0 :                                             computedTiming.mProgress.Value(),
      82           0 :                                             computedTiming.mBeforeFlag);
      83             : }
      84             : 
      85             : void
      86           0 : ElementPropertyTransition::UpdateStartValueFromReplacedTransition()
      87             : {
      88           0 :   if (!mReplacedTransition) {
      89           0 :     return;
      90             :   }
      91           0 :   MOZ_ASSERT(nsCSSProps::PropHasFlags(TransitionProperty(),
      92             :                                       CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR),
      93             :              "The transition property should be able to be run on the "
      94             :              "compositor");
      95           0 :   MOZ_ASSERT(mTarget && mTarget->mElement->OwnerDoc(),
      96             :              "We should have a valid document at this moment");
      97             : 
      98           0 :   dom::DocumentTimeline* timeline = mTarget->mElement->OwnerDoc()->Timeline();
      99             :   ComputedTiming computedTiming = GetComputedTimingAt(
     100           0 :     dom::CSSTransition::GetCurrentTimeAt(*timeline,
     101           0 :                                          TimeStamp::Now(),
     102           0 :                                          mReplacedTransition->mStartTime,
     103           0 :                                          mReplacedTransition->mPlaybackRate),
     104           0 :     mReplacedTransition->mTiming,
     105           0 :     mReplacedTransition->mPlaybackRate);
     106             : 
     107           0 :   if (!computedTiming.mProgress.IsNull()) {
     108             :     double valuePosition =
     109           0 :       ComputedTimingFunction::GetPortion(mReplacedTransition->mTimingFunction,
     110           0 :                                          computedTiming.mProgress.Value(),
     111           0 :                                          computedTiming.mBeforeFlag);
     112             : 
     113           0 :     MOZ_ASSERT(mProperties.Length() == 1 &&
     114             :                mProperties[0].mSegments.Length() == 1,
     115             :                "The transition should have one property and one segment");
     116           0 :     MOZ_ASSERT(mKeyframes.Length() == 2,
     117             :                "Transitions should have exactly two animation keyframes");
     118           0 :     MOZ_ASSERT(mKeyframes[0].mPropertyValues.Length() == 1,
     119             :                "Transitions should have exactly one property in their first "
     120             :                "frame");
     121             : 
     122           0 :     const AnimationValue& replacedFrom = mReplacedTransition->mFromValue;
     123           0 :     const AnimationValue& replacedTo = mReplacedTransition->mToValue;
     124           0 :     AnimationValue startValue;
     125           0 :     if (mDocument->IsStyledByServo()) {
     126             :       startValue.mServo =
     127           0 :         Servo_AnimationValues_Interpolate(replacedFrom.mServo,
     128             :                                           replacedTo.mServo,
     129           0 :                                           valuePosition).Consume();
     130           0 :       if (startValue.mServo) {
     131           0 :         mKeyframes[0].mPropertyValues[0].mServoDeclarationBlock =
     132           0 :           Servo_AnimationValue_Uncompute(startValue.mServo).Consume();
     133           0 :         mProperties[0].mSegments[0].mFromValue = Move(startValue);
     134             :       }
     135           0 :     } else if (StyleAnimationValue::Interpolate(mProperties[0].mProperty,
     136             :                                                 replacedFrom.mGecko,
     137             :                                                 replacedTo.mGecko,
     138             :                                                 valuePosition,
     139             :                                                 startValue.mGecko)) {
     140           0 :       nsCSSValue cssValue;
     141             :       DebugOnly<bool> uncomputeResult =
     142           0 :         StyleAnimationValue::UncomputeValue(mProperties[0].mProperty,
     143             :                                             startValue.mGecko,
     144           0 :                                             cssValue);
     145           0 :       MOZ_ASSERT(uncomputeResult, "UncomputeValue should not fail");
     146           0 :       mKeyframes[0].mPropertyValues[0].mValue = cssValue;
     147             : 
     148           0 :       mProperties[0].mSegments[0].mFromValue = Move(startValue);
     149             :     }
     150             :   }
     151             : 
     152           0 :   mReplacedTransition.reset();
     153             : }
     154             : 
     155             : ////////////////////////// CSSTransition ////////////////////////////
     156             : 
     157             : JSObject*
     158           0 : CSSTransition::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
     159             : {
     160           0 :   return dom::CSSTransitionBinding::Wrap(aCx, this, aGivenProto);
     161             : }
     162             : 
     163             : void
     164           0 : CSSTransition::GetTransitionProperty(nsString& aRetVal) const
     165             : {
     166           0 :   MOZ_ASSERT(eCSSProperty_UNKNOWN != mTransitionProperty,
     167             :              "Transition Property should be initialized");
     168             :   aRetVal =
     169           0 :     NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(mTransitionProperty));
     170           0 : }
     171             : 
     172             : AnimationPlayState
     173           0 : CSSTransition::PlayStateFromJS() const
     174             : {
     175           0 :   FlushStyle();
     176           0 :   return Animation::PlayStateFromJS();
     177             : }
     178             : 
     179             : void
     180           0 : CSSTransition::PlayFromJS(ErrorResult& aRv)
     181             : {
     182           0 :   FlushStyle();
     183           0 :   Animation::PlayFromJS(aRv);
     184           0 : }
     185             : 
     186             : void
     187          32 : CSSTransition::UpdateTiming(SeekFlag aSeekFlag, SyncNotifyFlag aSyncNotifyFlag)
     188             : {
     189          32 :   if (mNeedsNewAnimationIndexWhenRun &&
     190           0 :       PlayState() != AnimationPlayState::Idle) {
     191           0 :     mAnimationIndex = sNextAnimationIndex++;
     192           0 :     mNeedsNewAnimationIndexWhenRun = false;
     193             :   }
     194             : 
     195          32 :   Animation::UpdateTiming(aSeekFlag, aSyncNotifyFlag);
     196          32 : }
     197             : 
     198             : void
     199          24 : CSSTransition::QueueEvents(StickyTimeDuration aActiveTime)
     200             : {
     201          24 :   if (!mOwningElement.IsSet()) {
     202           0 :     return;
     203             :   }
     204             : 
     205             :   dom::Element* owningElement;
     206             :   CSSPseudoElementType owningPseudoType;
     207          24 :   mOwningElement.GetElement(owningElement, owningPseudoType);
     208          24 :   MOZ_ASSERT(owningElement, "Owning element should be set");
     209             : 
     210             :   nsPresContext* presContext =
     211          24 :     nsContentUtils::GetContextForContent(owningElement);
     212          24 :   if (!presContext) {
     213           0 :     return;
     214             :   }
     215             : 
     216          24 :   const StickyTimeDuration zeroDuration = StickyTimeDuration();
     217             : 
     218             :   TransitionPhase currentPhase;
     219          24 :   StickyTimeDuration intervalStartTime;
     220          24 :   StickyTimeDuration intervalEndTime;
     221             : 
     222          24 :   if (!mEffect) {
     223           0 :     currentPhase = GetAnimationPhaseWithoutEffect<TransitionPhase>(*this);
     224             :   } else {
     225          48 :     ComputedTiming computedTiming = mEffect->GetComputedTiming();
     226             : 
     227          24 :     currentPhase = static_cast<TransitionPhase>(computedTiming.mPhase);
     228          24 :     intervalStartTime =
     229          48 :       std::max(std::min(StickyTimeDuration(-mEffect->SpecifiedTiming().Delay()),
     230          24 :                         computedTiming.mActiveDuration), zeroDuration);
     231          24 :     intervalEndTime =
     232          48 :       std::max(std::min((EffectEnd() - mEffect->SpecifiedTiming().Delay()),
     233          24 :                         computedTiming.mActiveDuration), zeroDuration);
     234             :   }
     235             : 
     236             :   // TimeStamps to use for ordering the events when they are dispatched. We
     237             :   // use a TimeStamp so we can compare events produced by different elements,
     238             :   // perhaps even with different timelines.
     239             :   // The zero timestamp is for transitionrun events where we ignore the delay
     240             :   // for the purpose of ordering events.
     241          24 :   TimeStamp zeroTimeStamp  = AnimationTimeToTimeStamp(zeroDuration);
     242          24 :   TimeStamp startTimeStamp = ElapsedTimeToTimeStamp(intervalStartTime);
     243          24 :   TimeStamp endTimeStamp   = ElapsedTimeToTimeStamp(intervalEndTime);
     244             : 
     245          42 :   if (mPendingState != PendingState::NotPending &&
     246          34 :       (mPreviousTransitionPhase == TransitionPhase::Idle ||
     247          16 :        mPreviousTransitionPhase == TransitionPhase::Pending))
     248             :   {
     249          18 :     currentPhase = TransitionPhase::Pending;
     250             :   }
     251             : 
     252          48 :   AutoTArray<TransitionEventParams, 3> events;
     253             : 
     254             :   // Handle cancel events first
     255          46 :   if ((mPreviousTransitionPhase != TransitionPhase::Idle &&
     256          44 :        mPreviousTransitionPhase != TransitionPhase::After) &&
     257             :       currentPhase == TransitionPhase::Idle) {
     258           0 :     TimeStamp activeTimeStamp = ElapsedTimeToTimeStamp(aActiveTime);
     259           0 :     events.AppendElement(TransitionEventParams{ eTransitionCancel,
     260             :                                                 aActiveTime,
     261           0 :                                                 activeTimeStamp });
     262             :   }
     263             : 
     264             :   // All other events
     265          24 :   switch (mPreviousTransitionPhase) {
     266             :     case TransitionPhase::Idle:
     267           2 :       if (currentPhase == TransitionPhase::Pending ||
     268             :           currentPhase == TransitionPhase::Before) {
     269           4 :         events.AppendElement(TransitionEventParams{ eTransitionRun,
     270             :                                                     intervalStartTime,
     271           4 :                                                     zeroTimeStamp });
     272           0 :       } else if (currentPhase == TransitionPhase::Active) {
     273           0 :         events.AppendElement(TransitionEventParams{ eTransitionRun,
     274             :                                                     intervalStartTime,
     275           0 :                                                     zeroTimeStamp });
     276           0 :         events.AppendElement(TransitionEventParams{ eTransitionStart,
     277             :                                                     intervalStartTime,
     278           0 :                                                     startTimeStamp });
     279           0 :       } else if (currentPhase == TransitionPhase::After) {
     280           0 :         events.AppendElement(TransitionEventParams{ eTransitionRun,
     281             :                                                     intervalStartTime,
     282           0 :                                                     zeroTimeStamp });
     283           0 :         events.AppendElement(TransitionEventParams{ eTransitionStart,
     284             :                                                     intervalStartTime,
     285           0 :                                                     startTimeStamp });
     286           0 :         events.AppendElement(TransitionEventParams{ eTransitionEnd,
     287             :                                                     intervalEndTime,
     288           0 :                                                     endTimeStamp });
     289             :       }
     290           2 :       break;
     291             : 
     292             :     case TransitionPhase::Pending:
     293             :     case TransitionPhase::Before:
     294          18 :       if (currentPhase == TransitionPhase::Active) {
     295           4 :         events.AppendElement(TransitionEventParams{ eTransitionStart,
     296             :                                                     intervalStartTime,
     297           2 :                                                     startTimeStamp });
     298          16 :       } else if (currentPhase == TransitionPhase::After) {
     299           0 :         events.AppendElement(TransitionEventParams{ eTransitionStart,
     300             :                                                     intervalStartTime,
     301           0 :                                                     startTimeStamp });
     302           0 :         events.AppendElement(TransitionEventParams{ eTransitionEnd,
     303             :                                                     intervalEndTime,
     304           0 :                                                     endTimeStamp });
     305             :       }
     306          18 :       break;
     307             : 
     308             :     case TransitionPhase::Active:
     309           4 :       if (currentPhase == TransitionPhase::After) {
     310           4 :         events.AppendElement(TransitionEventParams{ eTransitionEnd,
     311             :                                                     intervalEndTime,
     312           2 :                                                     endTimeStamp });
     313           2 :       } else if (currentPhase == TransitionPhase::Before) {
     314           0 :         events.AppendElement(TransitionEventParams{ eTransitionEnd,
     315             :                                                     intervalStartTime,
     316           0 :                                                     startTimeStamp });
     317             :       }
     318           4 :       break;
     319             : 
     320             :     case TransitionPhase::After:
     321           0 :       if (currentPhase == TransitionPhase::Active) {
     322           0 :         events.AppendElement(TransitionEventParams{ eTransitionStart,
     323             :                                                     intervalEndTime,
     324           0 :                                                     startTimeStamp });
     325           0 :       } else if (currentPhase == TransitionPhase::Before) {
     326           0 :         events.AppendElement(TransitionEventParams{ eTransitionStart,
     327             :                                                     intervalEndTime,
     328           0 :                                                     startTimeStamp });
     329           0 :         events.AppendElement(TransitionEventParams{ eTransitionEnd,
     330             :                                                     intervalStartTime,
     331           0 :                                                     endTimeStamp });
     332             :       }
     333           0 :       break;
     334             :   }
     335          24 :   mPreviousTransitionPhase = currentPhase;
     336             : 
     337          24 :   nsTransitionManager* manager = presContext->TransitionManager();
     338          30 :   for (const TransitionEventParams& evt : events) {
     339          18 :     manager->QueueEvent(TransitionEventInfo(owningElement, owningPseudoType,
     340           6 :                                             evt.mMessage,
     341             :                                             TransitionProperty(),
     342             :                                             evt.mElapsedTime,
     343             :                                             evt.mTimeStamp,
     344           6 :                                             this));
     345             :   }
     346             : }
     347             : 
     348             : void
     349          24 : CSSTransition::Tick()
     350             : {
     351          24 :   Animation::Tick();
     352          24 :   QueueEvents();
     353          24 : }
     354             : 
     355             : nsCSSPropertyID
     356          34 : CSSTransition::TransitionProperty() const
     357             : {
     358          34 :   MOZ_ASSERT(eCSSProperty_UNKNOWN != mTransitionProperty,
     359             :              "Transition property should be initialized");
     360          34 :   return mTransitionProperty;
     361             : }
     362             : 
     363             : AnimationValue
     364          18 : CSSTransition::ToValue() const
     365             : {
     366          18 :   MOZ_ASSERT(!mTransitionToValue.IsNull(),
     367             :              "Transition ToValue should be initialized");
     368          18 :   return mTransitionToValue;
     369             : }
     370             : 
     371             : bool
     372           3 : CSSTransition::HasLowerCompositeOrderThan(const CSSTransition& aOther) const
     373             : {
     374           3 :   MOZ_ASSERT(IsTiedToMarkup() && aOther.IsTiedToMarkup(),
     375             :              "Should only be called for CSS transitions that are sorted "
     376             :              "as CSS transitions (i.e. tied to CSS markup)");
     377             : 
     378             :   // 0. Object-equality case
     379           3 :   if (&aOther == this) {
     380           0 :     return false;
     381             :   }
     382             : 
     383             :   // 1. Sort by document order
     384           3 :   if (!mOwningElement.Equals(aOther.mOwningElement)) {
     385           3 :     return mOwningElement.LessThan(aOther.mOwningElement);
     386             :   }
     387             : 
     388             :   // 2. (Same element and pseudo): Sort by transition generation
     389           0 :   if (mAnimationIndex != aOther.mAnimationIndex) {
     390           0 :     return mAnimationIndex < aOther.mAnimationIndex;
     391             :   }
     392             : 
     393             :   // 3. (Same transition generation): Sort by transition property
     394           0 :   return nsCSSProps::GetStringValue(TransitionProperty()) <
     395           0 :          nsCSSProps::GetStringValue(aOther.TransitionProperty());
     396             : }
     397             : 
     398             : /* static */ Nullable<TimeDuration>
     399           0 : CSSTransition::GetCurrentTimeAt(const dom::DocumentTimeline& aTimeline,
     400             :                                 const TimeStamp& aBaseTime,
     401             :                                 const TimeDuration& aStartTime,
     402             :                                 double aPlaybackRate)
     403             : {
     404           0 :   Nullable<TimeDuration> result;
     405             : 
     406           0 :   Nullable<TimeDuration> timelineTime = aTimeline.ToTimelineTime(aBaseTime);
     407           0 :   if (!timelineTime.IsNull()) {
     408           0 :     result.SetValue((timelineTime.Value() - aStartTime)
     409           0 :                       .MultDouble(aPlaybackRate));
     410             :   }
     411             : 
     412           0 :   return result;
     413             : }
     414             : 
     415             : void
     416           2 : CSSTransition::SetEffectFromStyle(dom::AnimationEffectReadOnly* aEffect)
     417             : {
     418           2 :   Animation::SetEffectNoUpdate(aEffect);
     419             : 
     420             :   // Initialize transition property.
     421           2 :   ElementPropertyTransition* pt = aEffect ? aEffect->AsTransition() : nullptr;
     422           2 :   if (eCSSProperty_UNKNOWN == mTransitionProperty && pt) {
     423           2 :     mTransitionProperty = pt->TransitionProperty();
     424           2 :     mTransitionToValue = pt->ToValue();
     425             :   }
     426           2 : }
     427             : 
     428             : ////////////////////////// nsTransitionManager ////////////////////////////
     429             : 
     430           0 : NS_IMPL_CYCLE_COLLECTION(nsTransitionManager, mEventDispatcher)
     431             : 
     432           0 : NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsTransitionManager, AddRef)
     433           0 : NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsTransitionManager, Release)
     434             : 
     435             : static inline bool
     436         510 : ExtractNonDiscreteComputedValue(nsCSSPropertyID aProperty,
     437             :                                 nsStyleContext* aStyleContext,
     438             :                                 AnimationValue& aAnimationValue)
     439             : {
     440         528 :   return (nsCSSProps::kAnimTypeTable[aProperty] != eStyleAnimType_Discrete ||
     441        1020 :           aProperty == eCSSProperty_visibility) &&
     442         510 :          StyleAnimationValue::ExtractComputedValue(aProperty, aStyleContext,
     443         510 :                                                    aAnimationValue.mGecko);
     444             : }
     445             : 
     446             : static inline bool
     447           0 : ExtractNonDiscreteComputedValue(nsCSSPropertyID aProperty,
     448             :                                 const ServoComputedValues* aComputedStyle,
     449             :                                 AnimationValue& aAnimationValue)
     450             : {
     451           0 :   if (Servo_Property_IsDiscreteAnimatable(aProperty) &&
     452             :       aProperty != eCSSProperty_visibility) {
     453           0 :     return false;
     454             :   }
     455             : 
     456             :   aAnimationValue.mServo =
     457           0 :     Servo_ComputedValues_ExtractAnimationValue(aComputedStyle,
     458           0 :                                                aProperty).Consume();
     459           0 :   return !!aAnimationValue.mServo;
     460             : }
     461             : 
     462             : void
     463         792 : nsTransitionManager::StyleContextChanged(dom::Element *aElement,
     464             :                                          nsStyleContext *aOldStyleContext,
     465             :                                          RefPtr<nsStyleContext>* aNewStyleContext /* inout */)
     466             : {
     467         792 :   nsStyleContext* newStyleContext = *aNewStyleContext;
     468             : 
     469         792 :   NS_PRECONDITION(aOldStyleContext->GetPseudo() == newStyleContext->GetPseudo(),
     470             :                   "pseudo type mismatch");
     471             : 
     472         792 :   if (mInAnimationOnlyStyleUpdate) {
     473             :     // If we're doing an animation-only style update, return, since the
     474             :     // purpose of an animation-only style update is to update only the
     475             :     // animation styles so that we don't consider style changes
     476             :     // resulting from changes in the animation time for starting a
     477             :     // transition.
     478         724 :     return;
     479             :   }
     480             : 
     481         788 :   if (!mPresContext->IsDynamic()) {
     482             :     // For print or print preview, ignore transitions.
     483           0 :     return;
     484             :   }
     485             : 
     486        1576 :   if (aOldStyleContext->HasPseudoElementData() !=
     487         788 :       newStyleContext->HasPseudoElementData()) {
     488             :     // If the old style context and new style context differ in terms of
     489             :     // whether they're inside ::first-letter, ::first-line, or similar,
     490             :     // bail.  We can't hit this codepath for normal style changes
     491             :     // involving moving frames around the boundaries of these
     492             :     // pseudo-elements since we don't call StyleContextChanged from
     493             :     // ReparentStyleContext.  However, we can hit this codepath during
     494             :     // the handling of transitions that start across reframes.
     495             :     //
     496             :     // While there isn't an easy *perfect* way to handle this case, err
     497             :     // on the side of missing some transitions that we ought to have
     498             :     // rather than having bogus transitions that we shouldn't.
     499             :     //
     500             :     // We could consider changing this handling, although it's worth
     501             :     // thinking about whether the code below could do anything weird in
     502             :     // this case.
     503           0 :     return;
     504             :   }
     505             : 
     506             :   // NOTE: Things in this function (and ConsiderInitiatingTransition)
     507             :   // should never call PeekStyleData because we don't preserve gotten
     508             :   // structs across reframes.
     509             : 
     510             :   // Return sooner (before the startedAny check below) for the most
     511             :   // common case: no transitions specified or running.
     512         788 :   const nsStyleDisplay *disp = newStyleContext->StyleDisplay();
     513         788 :   CSSPseudoElementType pseudoType = newStyleContext->GetPseudoType();
     514         788 :   if (pseudoType != CSSPseudoElementType::NotPseudo) {
     515         102 :     if (pseudoType != CSSPseudoElementType::before &&
     516             :         pseudoType != CSSPseudoElementType::after) {
     517          80 :       return;
     518             :     }
     519             : 
     520          22 :     NS_ASSERTION((pseudoType == CSSPseudoElementType::before &&
     521             :                   aElement->IsGeneratedContentContainerForBefore()) ||
     522             :                  (pseudoType == CSSPseudoElementType::after &&
     523             :                   aElement->IsGeneratedContentContainerForAfter()),
     524             :                  "Unexpected aElement coming through");
     525             : 
     526             :     // Else the element we want to use from now on is the element the
     527             :     // :before or :after is attached to.
     528          22 :     aElement = aElement->GetParent()->AsElement();
     529             :   }
     530             : 
     531             :   CSSTransitionCollection* collection =
     532         708 :     CSSTransitionCollection::GetAnimationCollection(aElement, pseudoType);
     533        2110 :   if (!collection &&
     534        1362 :       disp->mTransitionPropertyCount == 1 &&
     535         654 :       disp->mTransitions[0].GetCombinedDuration() <= 0.0f) {
     536         630 :     return;
     537             :   }
     538             : 
     539          78 :   MOZ_ASSERT(mPresContext->RestyleManager()->IsGecko(),
     540             :              "ServoRestyleManager should not use nsTransitionManager "
     541             :              "for transitions");
     542          92 :   if (collection &&
     543          14 :       collection->mCheckGeneration ==
     544          92 :         mPresContext->RestyleManager()->GetAnimationGeneration()) {
     545             :     // When we start a new transition, we immediately post a restyle.
     546             :     // If the animation generation on the collection is current, that
     547             :     // means *this* is that restyle, since we bump the animation
     548             :     // generation on the restyle manager whenever there's a real style
     549             :     // change (i.e., one where mInAnimationOnlyStyleUpdate isn't true,
     550             :     // which causes us to return above).  Thus we shouldn't do anything.
     551           6 :     return;
     552             :   }
     553         144 :   if (newStyleContext->GetParent() &&
     554          72 :       newStyleContext->GetParent()->HasPseudoElementData()) {
     555             :     // Ignore transitions on things that inherit properties from
     556             :     // pseudo-elements.
     557             :     // FIXME (Bug 522599): Add tests for this.
     558           0 :     return;
     559             :   }
     560             : 
     561          72 :   NS_WARNING_ASSERTION(
     562             :     !mPresContext->EffectCompositor()->HasThrottledStyleUpdates(),
     563             :     "throttled animations not up to date");
     564             : 
     565             :   // Compute what the css-transitions spec calls the "after-change
     566             :   // style", which is the new style without any data from transitions,
     567             :   // but still inheriting from data that contains transitions that are
     568             :   // not stopping or starting right now.
     569         144 :   RefPtr<nsStyleContext> afterChangeStyle;
     570          72 :   if (collection) {
     571           8 :     MOZ_ASSERT(mPresContext->StyleSet()->IsGecko(),
     572             :                "ServoStyleSets should not use nsTransitionManager "
     573             :                "for transitions");
     574           8 :     nsStyleSet* styleSet = mPresContext->StyleSet()->AsGecko();
     575             :     afterChangeStyle =
     576          16 :       styleSet->ResolveStyleByRemovingAnimation(aElement, newStyleContext,
     577           8 :                                                 eRestyle_CSSTransitions);
     578             :   } else {
     579          64 :     afterChangeStyle = newStyleContext;
     580             :   }
     581             : 
     582         144 :   nsAutoAnimationMutationBatch mb(aElement->OwnerDoc());
     583             : 
     584         144 :   DebugOnly<bool> startedAny = false;
     585             :   // We don't have to update transitions if display:none, although we will
     586             :   // cancel them after restyling.
     587          72 :   if (!afterChangeStyle->IsInDisplayNoneSubtree()) {
     588         144 :     startedAny = DoUpdateTransitions(disp,
     589             :                                      aElement,
     590             :                                      afterChangeStyle->GetPseudoType(),
     591             :                                      collection,
     592             :                                      aOldStyleContext,
     593          72 :                                      afterChangeStyle.get());
     594             :   }
     595             : 
     596          72 :   MOZ_ASSERT(!startedAny || collection,
     597             :              "must have element transitions if we started any transitions");
     598             : 
     599             :   EffectCompositor::CascadeLevel cascadeLevel =
     600          72 :     EffectCompositor::CascadeLevel::Transitions;
     601             : 
     602          72 :   if (collection) {
     603          10 :     collection->UpdateCheckGeneration(mPresContext);
     604          10 :     mPresContext->EffectCompositor()->MaybeUpdateAnimationRule(aElement,
     605             :                                                                pseudoType,
     606             :                                                                cascadeLevel,
     607          10 :                                                                newStyleContext);
     608             :   }
     609             : 
     610             :   // We want to replace the new style context with the after-change style.
     611          72 :   *aNewStyleContext = afterChangeStyle;
     612          72 :   if (collection) {
     613             :     // Since we have transition styles, we have to undo this replacement.
     614             :     // The check of collection->mCheckGeneration against the restyle
     615             :     // manager's GetAnimationGeneration() will ensure that we don't go
     616             :     // through the rest of this function again when we do.
     617          10 :     mPresContext->EffectCompositor()->PostRestyleForAnimation(aElement,
     618             :                                                               pseudoType,
     619          10 :                                                               cascadeLevel);
     620             :   }
     621             : }
     622             : 
     623             : bool
     624           0 : nsTransitionManager::UpdateTransitions(
     625             :   dom::Element *aElement,
     626             :   CSSPseudoElementType aPseudoType,
     627             :   const ServoComputedValues* aOldStyle,
     628             :   const ServoComputedValues* aNewStyle)
     629             : {
     630           0 :   if (!mPresContext->IsDynamic()) {
     631             :     // For print or print preview, ignore transitions.
     632           0 :     return false;
     633             :   }
     634             : 
     635             :   CSSTransitionCollection* collection =
     636           0 :     CSSTransitionCollection::GetAnimationCollection(aElement, aPseudoType);
     637           0 :   const nsStyleDisplay *disp = Servo_GetStyleDisplay(aNewStyle);
     638             :   return DoUpdateTransitions(disp,
     639             :                              aElement, aPseudoType,
     640             :                              collection,
     641           0 :                              aOldStyle, aNewStyle);
     642             : }
     643             : 
     644             : template<typename StyleType>
     645             : bool
     646          72 : nsTransitionManager::DoUpdateTransitions(
     647             :   const nsStyleDisplay* aDisp,
     648             :   dom::Element* aElement,
     649             :   CSSPseudoElementType aPseudoType,
     650             :   CSSTransitionCollection*& aElementTransitions,
     651             :   StyleType aOldStyle,
     652             :   StyleType aNewStyle)
     653             : {
     654          72 :   MOZ_ASSERT(aDisp, "Null nsStyleDisplay");
     655          72 :   MOZ_ASSERT(!aElementTransitions ||
     656             :              aElementTransitions->mElement == aElement, "Element mismatch");
     657             : 
     658             :   // Per http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html
     659             :   // I'll consider only the transitions from the number of items in
     660             :   // 'transition-property' on down, and later ones will override earlier
     661             :   // ones (tracked using |whichStarted|).
     662          72 :   bool startedAny = false;
     663          72 :   nsCSSPropertyIDSet whichStarted;
     664         220 :   for (uint32_t i = aDisp->mTransitionPropertyCount; i-- != 0; ) {
     665         148 :     const StyleTransition& t = aDisp->mTransitions[i];
     666             :     // Check the combined duration (combination of delay and duration)
     667             :     // first, since it defaults to zero, which means we can ignore the
     668             :     // transition.
     669         148 :     if (t.GetCombinedDuration() > 0.0f) {
     670             :       // We might have something to transition.  See if any of the
     671             :       // properties in question changed and are animatable.
     672             :       // FIXME: Would be good to find a way to share code between this
     673             :       // interpretation of transition-property and the one below.
     674         148 :       nsCSSPropertyID property = t.GetProperty();
     675         148 :       if (property == eCSSPropertyExtra_no_properties ||
     676         148 :           property == eCSSPropertyExtra_variable ||
     677             :           property == eCSSProperty_UNKNOWN) {
     678             :         // Nothing to do, but need to exclude this from cases below.
     679         148 :       } else if (property == eCSSPropertyExtra_all_properties) {
     680           0 :         for (nsCSSPropertyID p = nsCSSPropertyID(0);
     681           0 :              p < eCSSProperty_COUNT_no_shorthands;
     682             :              p = nsCSSPropertyID(p + 1)) {
     683           0 :           ConsiderInitiatingTransition(p, t, aElement, aPseudoType,
     684             :                                        aElementTransitions,
     685             :                                        aOldStyle, aNewStyle,
     686             :                                        &startedAny, &whichStarted);
     687             :         }
     688         148 :       } else if (nsCSSProps::IsShorthand(property)) {
     689         170 :         CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, property,
     690             :                                              CSSEnabledState::eForAllContent)
     691             :         {
     692         136 :           ConsiderInitiatingTransition(*subprop, t, aElement, aPseudoType,
     693             :                                        aElementTransitions,
     694             :                                        aOldStyle, aNewStyle,
     695             :                                        &startedAny, &whichStarted);
     696             :         }
     697             :       } else {
     698         114 :         ConsiderInitiatingTransition(property, t, aElement, aPseudoType,
     699             :                                      aElementTransitions,
     700             :                                      aOldStyle, aNewStyle,
     701             :                                      &startedAny, &whichStarted);
     702             :       }
     703             :     }
     704             :   }
     705             : 
     706             :   // Stop any transitions for properties that are no longer in
     707             :   // 'transition-property', including finished transitions.
     708             :   // Also stop any transitions (and remove any finished transitions)
     709             :   // for properties that just changed (and are still in the set of
     710             :   // properties to transition), but for which we didn't just start the
     711             :   // transition.  This can happen delay and duration are both zero, or
     712             :   // because the new value is not interpolable.
     713             :   // Note that we also do the latter set of work in
     714             :   // nsTransitionManager::PruneCompletedTransitions.
     715          72 :   if (aElementTransitions) {
     716             :     bool checkProperties =
     717          10 :       aDisp->mTransitions[0].GetProperty() != eCSSPropertyExtra_all_properties;
     718          10 :     nsCSSPropertyIDSet allTransitionProperties;
     719          10 :     if (checkProperties) {
     720          20 :       for (uint32_t i = aDisp->mTransitionPropertyCount; i-- != 0; ) {
     721          10 :         const StyleTransition& t = aDisp->mTransitions[i];
     722             :         // FIXME: Would be good to find a way to share code between this
     723             :         // interpretation of transition-property and the one above.
     724          10 :         nsCSSPropertyID property = t.GetProperty();
     725          10 :         if (property == eCSSPropertyExtra_no_properties ||
     726          10 :             property == eCSSPropertyExtra_variable ||
     727             :             property == eCSSProperty_UNKNOWN) {
     728             :           // Nothing to do, but need to exclude this from cases below.
     729          10 :         } else if (property == eCSSPropertyExtra_all_properties) {
     730           0 :           for (nsCSSPropertyID p = nsCSSPropertyID(0);
     731           0 :                p < eCSSProperty_COUNT_no_shorthands;
     732             :                p = nsCSSPropertyID(p + 1)) {
     733           0 :             allTransitionProperties.AddProperty(p);
     734             :           }
     735          10 :         } else if (nsCSSProps::IsShorthand(property)) {
     736           0 :           CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(
     737             :               subprop, property, CSSEnabledState::eForAllContent) {
     738           0 :             allTransitionProperties.AddProperty(*subprop);
     739             :           }
     740             :         } else {
     741          10 :           allTransitionProperties.AddProperty(property);
     742             :         }
     743             :       }
     744             :     }
     745             : 
     746          10 :     OwningCSSTransitionPtrArray& animations = aElementTransitions->mAnimations;
     747          10 :     size_t i = animations.Length();
     748          10 :     MOZ_ASSERT(i != 0, "empty transitions list?");
     749          20 :     AnimationValue currentValue;
     750           0 :     do {
     751          10 :       --i;
     752          10 :       CSSTransition* anim = animations[i];
     753             :           // properties no longer in 'transition-property'
     754          30 :       if ((checkProperties &&
     755          20 :            !allTransitionProperties.HasProperty(anim->TransitionProperty())) ||
     756             :           // properties whose computed values changed but for which we
     757             :           // did not start a new transition (because delay and
     758             :           // duration are both zero, or because the new value is not
     759             :           // interpolable); a new transition would have anim->ToValue()
     760             :           // matching currentValue
     761          10 :           !ExtractNonDiscreteComputedValue(anim->TransitionProperty(),
     762          30 :                                            aNewStyle, currentValue) ||
     763          40 :           currentValue != anim->ToValue()) {
     764             :         // stop the transition
     765           0 :         if (anim->HasCurrentEffect()) {
     766             :           EffectSet* effectSet =
     767           0 :             EffectSet::GetEffectSet(aElement, aPseudoType);
     768           0 :           if (effectSet) {
     769           0 :             effectSet->UpdateAnimationGeneration(mPresContext);
     770             :           }
     771             :         }
     772           0 :         anim->CancelFromStyle();
     773           0 :         animations.RemoveElementAt(i);
     774             :       }
     775          10 :     } while (i != 0);
     776             : 
     777          10 :     if (animations.IsEmpty()) {
     778           0 :       aElementTransitions->Destroy();
     779           0 :       aElementTransitions = nullptr;
     780             :     }
     781             :   }
     782             : 
     783          72 :   return startedAny;
     784             : }
     785             : 
     786             : static Keyframe&
     787           4 : AppendKeyframe(double aOffset,
     788             :                nsCSSPropertyID aProperty,
     789             :                AnimationValue&& aValue,
     790             :                nsTArray<Keyframe>& aKeyframes)
     791             : {
     792           4 :   Keyframe& frame = *aKeyframes.AppendElement();
     793           4 :   frame.mOffset.emplace(aOffset);
     794             : 
     795           4 :   if (aValue.mServo) {
     796             :     RefPtr<RawServoDeclarationBlock> decl =
     797           0 :       Servo_AnimationValue_Uncompute(aValue.mServo).Consume();
     798           0 :     frame.mPropertyValues.AppendElement(
     799           0 :       Move(PropertyValuePair(aProperty, Move(decl))));
     800             :   } else {
     801           8 :     nsCSSValue propertyValue;
     802             :     DebugOnly<bool> uncomputeResult =
     803           8 :       StyleAnimationValue::UncomputeValue(aProperty, Move(aValue.mGecko),
     804           8 :                                           propertyValue);
     805           4 :     MOZ_ASSERT(uncomputeResult,
     806             :                "Unable to get specified value from computed value");
     807           4 :     frame.mPropertyValues.AppendElement(
     808           8 :       Move(PropertyValuePair(aProperty, Move(propertyValue))));
     809             :   }
     810           4 :   return frame;
     811             : }
     812             : 
     813             : static nsTArray<Keyframe>
     814           2 : GetTransitionKeyframes(nsCSSPropertyID aProperty,
     815             :                        AnimationValue&& aStartValue,
     816             :                        AnimationValue&& aEndValue,
     817             :                        const nsTimingFunction& aTimingFunction)
     818             : {
     819           2 :   nsTArray<Keyframe> keyframes(2);
     820             : 
     821           2 :   Keyframe& fromFrame = AppendKeyframe(0.0, aProperty, Move(aStartValue),
     822           2 :                                        keyframes);
     823           2 :   if (aTimingFunction.mType != nsTimingFunction::Type::Linear) {
     824           2 :     fromFrame.mTimingFunction.emplace();
     825           2 :     fromFrame.mTimingFunction->Init(aTimingFunction);
     826             :   }
     827             : 
     828           2 :   AppendKeyframe(1.0, aProperty, Move(aEndValue), keyframes);
     829             : 
     830           2 :   return keyframes;
     831             : }
     832             : 
     833             : static bool
     834         250 : IsTransitionable(nsCSSPropertyID aProperty, bool aIsServo)
     835             : {
     836         250 :   if (aIsServo) {
     837           0 :     return Servo_Property_IsTransitionable(aProperty);
     838             :   }
     839             : 
     840             :   // FIXME: This should also exclude discretely-animated properties.
     841         250 :   return nsCSSProps::kAnimTypeTable[aProperty] != eStyleAnimType_None;
     842             : }
     843             : 
     844             : template<typename StyleType>
     845             : void
     846         250 : nsTransitionManager::ConsiderInitiatingTransition(
     847             :   nsCSSPropertyID aProperty,
     848             :   const StyleTransition& aTransition,
     849             :   dom::Element* aElement,
     850             :   CSSPseudoElementType aPseudoType,
     851             :   CSSTransitionCollection*& aElementTransitions,
     852             :   StyleType aOldStyle,
     853             :   StyleType aNewStyle,
     854             :   bool* aStartedAny,
     855             :   nsCSSPropertyIDSet* aWhichStarted)
     856             : {
     857             :   // IsShorthand itself will assert if aProperty is not a property.
     858         250 :   MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty),
     859             :              "property out of range");
     860         250 :   NS_ASSERTION(!aElementTransitions ||
     861             :                aElementTransitions->mElement == aElement, "Element mismatch");
     862             : 
     863             :   // Ignore disabled properties. We can arrive here if the transition-property
     864             :   // is 'all' and the disabled property has a default value which derives value
     865             :   // from another property, e.g. color.
     866         250 :   if (!nsCSSProps::IsEnabled(aProperty, CSSEnabledState::eForAllContent)) {
     867         248 :     return;
     868             :   }
     869             : 
     870         250 :   if (aWhichStarted->HasProperty(aProperty)) {
     871             :     // A later item in transition-property already started a
     872             :     // transition for this property, so we ignore this one.
     873             :     // See comment above and
     874             :     // http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html .
     875           0 :     return;
     876             :   }
     877             : 
     878         250 :   if (!IsTransitionable(aProperty, aElement->IsStyledByServo())) {
     879           0 :     return;
     880             :   }
     881             : 
     882         250 :   dom::DocumentTimeline* timeline = aElement->OwnerDoc()->Timeline();
     883             : 
     884         252 :   AnimationValue startValue, endValue;
     885             :   bool haveValues =
     886         500 :     ExtractNonDiscreteComputedValue(aProperty, aOldStyle, startValue) &&
     887         500 :     ExtractNonDiscreteComputedValue(aProperty, aNewStyle, endValue);
     888             : 
     889         250 :   bool haveChange = startValue != endValue;
     890             : 
     891             :   bool shouldAnimate =
     892         250 :     haveValues &&
     893         256 :     haveChange &&
     894         256 :     startValue.IsInterpolableWith(aProperty, endValue);
     895             : 
     896         250 :   bool haveCurrentTransition = false;
     897         250 :   size_t currentIndex = nsTArray<ElementPropertyTransition>::NoIndex;
     898         250 :   const ElementPropertyTransition *oldPT = nullptr;
     899         250 :   if (aElementTransitions) {
     900           8 :     OwningCSSTransitionPtrArray& animations = aElementTransitions->mAnimations;
     901           8 :     for (size_t i = 0, i_end = animations.Length(); i < i_end; ++i) {
     902           8 :       if (animations[i]->TransitionProperty() == aProperty) {
     903           8 :         haveCurrentTransition = true;
     904           8 :         currentIndex = i;
     905          16 :         oldPT = animations[i]->GetEffect()
     906           8 :                 ? animations[i]->GetEffect()->AsTransition()
     907             :                 : nullptr;
     908           8 :         break;
     909             :       }
     910             :     }
     911             :   }
     912             : 
     913             :   // If we got a style change that changed the value to the endpoint
     914             :   // of the currently running transition, we don't want to interrupt
     915             :   // its timing function.
     916             :   // This needs to be before the !shouldAnimate && haveCurrentTransition
     917             :   // case below because we might be close enough to the end of the
     918             :   // transition that the current value rounds to the final value.  In
     919             :   // this case, we'll end up with shouldAnimate as false (because
     920             :   // there's no value change), but we need to return early here rather
     921             :   // than cancel the running transition because shouldAnimate is false!
     922             :   //
     923             :   // Likewise, if we got a style change that changed the value to the
     924             :   // endpoint of our finished transition, we also don't want to start
     925             :   // a new transition for the reasons described in
     926             :   // https://lists.w3.org/Archives/Public/www-style/2015Jan/0444.html .
     927         758 :   if (haveCurrentTransition && haveValues &&
     928         274 :       aElementTransitions->mAnimations[currentIndex]->ToValue() == endValue) {
     929             :     // GetAnimationRule already called RestyleForAnimation.
     930           8 :     return;
     931             :   }
     932             : 
     933         242 :   if (!shouldAnimate) {
     934         240 :     if (haveCurrentTransition) {
     935             :       // We're in the middle of a transition, and just got a non-transition
     936             :       // style change to something that we can't animate.  This might happen
     937             :       // because we got a non-transition style change changing to the current
     938             :       // in-progress value (which is particularly easy to cause when we're
     939             :       // currently in the 'transition-delay').  It also might happen because we
     940             :       // just got a style change to a value that can't be interpolated.
     941             :       OwningCSSTransitionPtrArray& animations =
     942           0 :         aElementTransitions->mAnimations;
     943           0 :       animations[currentIndex]->CancelFromStyle();
     944           0 :       oldPT = nullptr; // Clear pointer so it doesn't dangle
     945           0 :       animations.RemoveElementAt(currentIndex);
     946           0 :       EffectSet* effectSet = EffectSet::GetEffectSet(aElement, aPseudoType);
     947           0 :       if (effectSet) {
     948           0 :         effectSet->UpdateAnimationGeneration(mPresContext);
     949             :       }
     950             : 
     951           0 :       if (animations.IsEmpty()) {
     952           0 :         aElementTransitions->Destroy();
     953             :         // |aElementTransitions| is now a dangling pointer!
     954           0 :         aElementTransitions = nullptr;
     955             :       }
     956             :       // GetAnimationRule already called RestyleForAnimation.
     957             :     }
     958         240 :     return;
     959             :   }
     960             : 
     961           2 :   const nsTimingFunction &tf = aTransition.GetTimingFunction();
     962           2 :   float delay = aTransition.GetDelay();
     963           2 :   float duration = aTransition.GetDuration();
     964           2 :   if (duration < 0.0) {
     965             :     // The spec says a negative duration is treated as zero.
     966           0 :     duration = 0.0;
     967             :   }
     968             : 
     969           4 :   AnimationValue startForReversingTest = startValue;
     970           2 :   double reversePortion = 1.0;
     971             : 
     972             :   // If the new transition reverses an existing one, we'll need to
     973             :   // handle the timing differently.
     974             :   // FIXME: Move mStartForReversingTest, mReversePortion to CSSTransition,
     975             :   //        and set the timing function on transitions as an effect-level
     976             :   //        easing (rather than keyframe-level easing). (Bug 1292001)
     977           2 :   if (haveCurrentTransition &&
     978           0 :       aElementTransitions->mAnimations[currentIndex]->HasCurrentEffect() &&
     979           2 :       oldPT &&
     980           0 :       oldPT->mStartForReversingTest == endValue) {
     981             :     // Compute the appropriate negative transition-delay such that right
     982             :     // now we'd end up at the current position.
     983             :     double valuePortion =
     984           0 :       oldPT->CurrentValuePortion() * oldPT->mReversePortion +
     985           0 :       (1.0 - oldPT->mReversePortion);
     986             :     // A timing function with negative y1 (or y2!) might make
     987             :     // valuePortion negative.  In this case, we still want to apply our
     988             :     // reversing logic based on relative distances, not make duration
     989             :     // negative.
     990           0 :     if (valuePortion < 0.0) {
     991           0 :       valuePortion = -valuePortion;
     992             :     }
     993             :     // A timing function with y2 (or y1!) greater than one might
     994             :     // advance past its terminal value.  It's probably a good idea to
     995             :     // clamp valuePortion to be at most one to preserve the invariant
     996             :     // that a transition will complete within at most its specified
     997             :     // time.
     998           0 :     if (valuePortion > 1.0) {
     999           0 :       valuePortion = 1.0;
    1000             :     }
    1001             : 
    1002             :     // Negative delays are essentially part of the transition
    1003             :     // function, so reduce them along with the duration, but don't
    1004             :     // reduce positive delays.
    1005           0 :     if (delay < 0.0f) {
    1006           0 :       delay *= valuePortion;
    1007             :     }
    1008             : 
    1009           0 :     duration *= valuePortion;
    1010             : 
    1011           0 :     startForReversingTest = oldPT->ToValue();
    1012           0 :     reversePortion = valuePortion;
    1013             :   }
    1014             : 
    1015             :   TimingParams timing =
    1016             :     TimingParamsFromCSSParams(duration, delay,
    1017             :                               1.0 /* iteration count */,
    1018             :                               dom::PlaybackDirection::Normal,
    1019           4 :                               dom::FillMode::Backwards);
    1020             : 
    1021             :   // aElement is non-null here, so we emplace it directly.
    1022           4 :   Maybe<OwningAnimationTarget> target;
    1023           2 :   target.emplace(aElement, aPseudoType);
    1024           2 :   KeyframeEffectParams effectOptions;
    1025             :   RefPtr<ElementPropertyTransition> pt =
    1026           4 :     new ElementPropertyTransition(aElement->OwnerDoc(), target, timing,
    1027             :                                   startForReversingTest, reversePortion,
    1028           6 :                                   effectOptions);
    1029             : 
    1030           2 :   pt->SetKeyframes(GetTransitionKeyframes(aProperty,
    1031           2 :                                           Move(startValue), Move(endValue), tf),
    1032             :                    aNewStyle);
    1033             : 
    1034             :   RefPtr<CSSTransition> animation =
    1035           6 :     new CSSTransition(mPresContext->Document()->GetScopeObject());
    1036           2 :   animation->SetOwningElement(OwningElementRef(*aElement, aPseudoType));
    1037           2 :   animation->SetTimelineNoUpdate(timeline);
    1038           2 :   animation->SetCreationSequence(
    1039           2 :     mPresContext->RestyleManager()->GetAnimationGeneration());
    1040           2 :   animation->SetEffectFromStyle(pt);
    1041           2 :   animation->PlayFromStyle();
    1042             : 
    1043           2 :   if (!aElementTransitions) {
    1044           2 :     bool createdCollection = false;
    1045           2 :     aElementTransitions =
    1046           2 :       CSSTransitionCollection::GetOrCreateAnimationCollection(
    1047             :         aElement, aPseudoType, &createdCollection);
    1048           2 :     if (!aElementTransitions) {
    1049           0 :       MOZ_ASSERT(!createdCollection, "outparam should agree with return value");
    1050           0 :       NS_WARNING("allocating collection failed");
    1051           0 :       return;
    1052             :     }
    1053             : 
    1054           2 :     if (createdCollection) {
    1055           2 :       AddElementCollection(aElementTransitions);
    1056             :     }
    1057             :   }
    1058             : 
    1059           2 :   OwningCSSTransitionPtrArray& animations = aElementTransitions->mAnimations;
    1060             : #ifdef DEBUG
    1061           2 :   for (size_t i = 0, i_end = animations.Length(); i < i_end; ++i) {
    1062           0 :     MOZ_ASSERT(
    1063             :       i == currentIndex || animations[i]->TransitionProperty() != aProperty,
    1064             :       "duplicate transitions for property");
    1065             :   }
    1066             : #endif
    1067           2 :   if (haveCurrentTransition) {
    1068             :     // If this new transition is replacing an existing transition that is running
    1069             :     // on the compositor, we store select parameters from the replaced transition
    1070             :     // so that later, once all scripts have run, we can update the start value
    1071             :     // of the transition using TimeStamp::Now(). This allows us to avoid a
    1072             :     // large jump when starting a new transition when the main thread lags behind
    1073             :     // the compositor.
    1074           0 :     if (oldPT &&
    1075           0 :         oldPT->IsCurrent() &&
    1076           0 :         oldPT->IsRunningOnCompositor() &&
    1077           0 :         !oldPT->GetAnimation()->GetStartTime().IsNull() &&
    1078           0 :         timeline == oldPT->GetAnimation()->GetTimeline()) {
    1079             :       const AnimationPropertySegment& segment =
    1080           0 :         oldPT->Properties()[0].mSegments[0];
    1081           0 :       pt->mReplacedTransition.emplace(
    1082             :         ElementPropertyTransition::ReplacedTransitionProperties({
    1083           0 :           oldPT->GetAnimation()->GetStartTime().Value(),
    1084           0 :           oldPT->GetAnimation()->PlaybackRate(),
    1085             :           oldPT->SpecifiedTiming(),
    1086             :           segment.mTimingFunction,
    1087             :           segment.mFromValue,
    1088             :           segment.mToValue
    1089             :         })
    1090             :       );
    1091             :     }
    1092           0 :     animations[currentIndex]->CancelFromStyle();
    1093           0 :     oldPT = nullptr; // Clear pointer so it doesn't dangle
    1094           0 :     animations[currentIndex] = animation;
    1095             :   } else {
    1096           2 :     if (!animations.AppendElement(animation)) {
    1097           0 :       NS_WARNING("out of memory");
    1098           0 :       return;
    1099             :     }
    1100             :   }
    1101             : 
    1102           2 :   EffectSet* effectSet = EffectSet::GetEffectSet(aElement, aPseudoType);
    1103           2 :   if (effectSet) {
    1104           2 :     effectSet->UpdateAnimationGeneration(mPresContext);
    1105             :   }
    1106             : 
    1107           2 :   *aStartedAny = true;
    1108           2 :   aWhichStarted->AddProperty(aProperty);
    1109             : }
    1110             : 
    1111             : void
    1112          87 : nsTransitionManager::PruneCompletedTransitions(mozilla::dom::Element* aElement,
    1113             :                                                CSSPseudoElementType aPseudoType,
    1114             :                                                nsStyleContext* aNewStyleContext)
    1115             : {
    1116          87 :   MOZ_ASSERT(!aElement->IsGeneratedContentContainerForBefore() &&
    1117             :              !aElement->IsGeneratedContentContainerForAfter());
    1118             : 
    1119             :   CSSTransitionCollection* collection =
    1120          87 :     CSSTransitionCollection::GetAnimationCollection(aElement, aPseudoType);
    1121          87 :   if (!collection) {
    1122          87 :     return;
    1123             :   }
    1124             : 
    1125             :   // Remove any finished transitions whose style doesn't match the new
    1126             :   // style.
    1127             :   // This is similar to some of the work that happens near the end of
    1128             :   // nsTransitionManager::StyleContextChanged.
    1129             :   // FIXME (bug 1158431): Really, we should also cancel running
    1130             :   // transitions whose destination doesn't match as well.
    1131           0 :   OwningCSSTransitionPtrArray& animations = collection->mAnimations;
    1132           0 :   size_t i = animations.Length();
    1133           0 :   MOZ_ASSERT(i != 0, "empty transitions list?");
    1134           0 :   do {
    1135           0 :     --i;
    1136           0 :     CSSTransition* anim = animations[i];
    1137             : 
    1138           0 :     if (anim->HasCurrentEffect()) {
    1139           0 :       continue;
    1140             :     }
    1141             : 
    1142             :     // Since effect is a finished transition, we know it didn't
    1143             :     // influence style.
    1144           0 :     AnimationValue currentValue;
    1145           0 :     if (!ExtractNonDiscreteComputedValue(anim->TransitionProperty(),
    1146           0 :                                          aNewStyleContext, currentValue) ||
    1147           0 :         currentValue != anim->ToValue()) {
    1148           0 :       anim->CancelFromStyle();
    1149           0 :       animations.RemoveElementAt(i);
    1150             :     }
    1151           0 :   } while (i != 0);
    1152             : 
    1153           0 :   if (collection->mAnimations.IsEmpty()) {
    1154           0 :     collection->Destroy();
    1155             :     // |collection| is now a dangling pointer!
    1156           0 :     collection = nullptr;
    1157             :   }
    1158           9 : }

Generated by: LCOV version 1.13