LCOV - code coverage report
Current view: top level - layout/style - nsTransitionManager.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 61 360 16.9 %
Date: 2018-08-07 16:35:00 Functions: 0 0 -
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13-14-ga5dd952