forked from mirrors/gecko-dev
		
	 e83e1a5e71
			
		
	
	
		e83e1a5e71
		
	
	
	
	
		
			
			This reflects the change made to the Web Animations specification in:9e2053f5531c3415f4cc(I got it wrong the first time. The second commit fixes the first.) And discussed in: https://github.com/w3c/web-animations/issues/196 In summary, we are splitting the "pending" play state out into a separate boolean member so that it is possible to distinguish between "play-pending" and "pause-pending" and because most of the time when you check for animation.playState === 'running' you also really want to include play-pending animations. MozReview-Commit-ID: IJSNoZTKW2I --HG-- extra : rebase_source : 5d17239fd087cfe3cce1c9697eff97d062b6dd4b
		
			
				
	
	
		
			1163 lines
		
	
	
	
		
			41 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1163 lines
		
	
	
	
		
			41 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "nsAnimationManager.h"
 | |
| #include "nsTransitionManager.h"
 | |
| #include "mozilla/dom/CSSAnimationBinding.h"
 | |
| 
 | |
| #include "mozilla/AnimationTarget.h"
 | |
| #include "mozilla/EffectCompositor.h"
 | |
| #include "mozilla/EffectSet.h"
 | |
| #include "mozilla/MemoryReporting.h"
 | |
| #include "mozilla/ServoStyleSet.h"
 | |
| #include "mozilla/StyleAnimationValue.h"
 | |
| #include "mozilla/dom/DocumentTimeline.h"
 | |
| #include "mozilla/dom/KeyframeEffectReadOnly.h"
 | |
| 
 | |
| #include "nsPresContext.h"
 | |
| #include "nsStyleSet.h"
 | |
| #include "nsStyleChangeList.h"
 | |
| #include "nsContentUtils.h"
 | |
| #include "nsCSSRules.h"
 | |
| #include "mozilla/GeckoRestyleManager.h"
 | |
| #include "nsLayoutUtils.h"
 | |
| #include "nsIFrame.h"
 | |
| #include "nsIDocument.h"
 | |
| #include "nsDOMMutationObserver.h"
 | |
| #include "nsIPresShell.h"
 | |
| #include "nsIPresShellInlines.h"
 | |
| #include <algorithm> // std::stable_sort
 | |
| #include <math.h>
 | |
| 
 | |
| using namespace mozilla;
 | |
| using namespace mozilla::css;
 | |
| using mozilla::dom::Animation;
 | |
| using mozilla::dom::AnimationPlayState;
 | |
| using mozilla::dom::KeyframeEffectReadOnly;
 | |
| using mozilla::dom::CSSAnimation;
 | |
| 
 | |
| typedef mozilla::ComputedTiming::AnimationPhase AnimationPhase;
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| struct AnimationEventParams {
 | |
|   EventMessage mMessage;
 | |
|   StickyTimeDuration mElapsedTime;
 | |
|   TimeStamp mTimeStamp;
 | |
| };
 | |
| 
 | |
| } // anonymous namespace
 | |
| 
 | |
| ////////////////////////// CSSAnimation ////////////////////////////
 | |
| 
 | |
| JSObject*
 | |
| CSSAnimation::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 | |
| {
 | |
|   return dom::CSSAnimationBinding::Wrap(aCx, this, aGivenProto);
 | |
| }
 | |
| 
 | |
| mozilla::dom::Promise*
 | |
| CSSAnimation::GetReady(ErrorResult& aRv)
 | |
| {
 | |
|   FlushStyle();
 | |
|   return Animation::GetReady(aRv);
 | |
| }
 | |
| 
 | |
| void
 | |
| CSSAnimation::Play(ErrorResult &aRv, LimitBehavior aLimitBehavior)
 | |
| {
 | |
|   mPauseShouldStick = false;
 | |
|   Animation::Play(aRv, aLimitBehavior);
 | |
| }
 | |
| 
 | |
| void
 | |
| CSSAnimation::Pause(ErrorResult& aRv)
 | |
| {
 | |
|   mPauseShouldStick = true;
 | |
|   Animation::Pause(aRv);
 | |
| }
 | |
| 
 | |
| AnimationPlayState
 | |
| CSSAnimation::PlayStateFromJS() const
 | |
| {
 | |
|   // Flush style to ensure that any properties controlling animation state
 | |
|   // (e.g. animation-play-state) are fully updated.
 | |
|   FlushStyle();
 | |
|   return Animation::PlayStateFromJS();
 | |
| }
 | |
| 
 | |
| bool
 | |
| CSSAnimation::PendingFromJS() const
 | |
| {
 | |
|   // Flush style since, for example, if the animation-play-state was just
 | |
|   // changed its possible we should now be pending.
 | |
|   FlushStyle();
 | |
|   return Animation::PendingFromJS();
 | |
| }
 | |
| 
 | |
| void
 | |
| CSSAnimation::PlayFromJS(ErrorResult& aRv)
 | |
| {
 | |
|   // Note that flushing style below might trigger calls to
 | |
|   // PlayFromStyle()/PauseFromStyle() on this object.
 | |
|   FlushStyle();
 | |
|   Animation::PlayFromJS(aRv);
 | |
| }
 | |
| 
 | |
| void
 | |
| CSSAnimation::PlayFromStyle()
 | |
| {
 | |
|   mIsStylePaused = false;
 | |
|   if (!mPauseShouldStick) {
 | |
|     ErrorResult rv;
 | |
|     PlayNoUpdate(rv, Animation::LimitBehavior::Continue);
 | |
|     // play() should not throw when LimitBehavior is Continue
 | |
|     MOZ_ASSERT(!rv.Failed(), "Unexpected exception playing animation");
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| CSSAnimation::PauseFromStyle()
 | |
| {
 | |
|   // Check if the pause state is being overridden
 | |
|   if (mIsStylePaused) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mIsStylePaused = true;
 | |
|   ErrorResult rv;
 | |
|   PauseNoUpdate(rv);
 | |
|   // pause() should only throw when *all* of the following conditions are true:
 | |
|   // - we are in the idle state, and
 | |
|   // - we have a negative playback rate, and
 | |
|   // - we have an infinitely repeating animation
 | |
|   // The first two conditions will never happen under regular style processing
 | |
|   // but could happen if an author made modifications to the Animation object
 | |
|   // and then updated animation-play-state. It's an unusual case and there's
 | |
|   // no obvious way to pass on the exception information so we just silently
 | |
|   // fail for now.
 | |
|   if (rv.Failed()) {
 | |
|     NS_WARNING("Unexpected exception pausing animation - silently failing");
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| CSSAnimation::Tick()
 | |
| {
 | |
|   Animation::Tick();
 | |
|   QueueEvents();
 | |
| }
 | |
| 
 | |
| bool
 | |
| CSSAnimation::HasLowerCompositeOrderThan(const CSSAnimation& aOther) const
 | |
| {
 | |
|   MOZ_ASSERT(IsTiedToMarkup() && aOther.IsTiedToMarkup(),
 | |
|              "Should only be called for CSS animations that are sorted "
 | |
|              "as CSS animations (i.e. tied to CSS markup)");
 | |
| 
 | |
|   // 0. Object-equality case
 | |
|   if (&aOther == this) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // 1. Sort by document order
 | |
|   if (!mOwningElement.Equals(aOther.mOwningElement)) {
 | |
|     return mOwningElement.LessThan(aOther.mOwningElement);
 | |
|   }
 | |
| 
 | |
|   // 2. (Same element and pseudo): Sort by position in animation-name
 | |
|   return mAnimationIndex < aOther.mAnimationIndex;
 | |
| }
 | |
| 
 | |
| void
 | |
| CSSAnimation::QueueEvents(StickyTimeDuration aActiveTime)
 | |
| {
 | |
|   // If the animation is pending, we ignore animation events until we finish
 | |
|   // pending.
 | |
|   if (mPendingState != PendingState::NotPending) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // CSS animations dispatch events at their owning element. This allows
 | |
|   // script to repurpose a CSS animation to target a different element,
 | |
|   // to use a group effect (which has no obvious "target element"), or
 | |
|   // to remove the animation effect altogether whilst still getting
 | |
|   // animation events.
 | |
|   //
 | |
|   // It does mean, however, that for a CSS animation that has no owning
 | |
|   // element (e.g. it was created using the CSSAnimation constructor or
 | |
|   // disassociated from CSS) no events are fired. If it becomes desirable
 | |
|   // for these animations to still fire events we should spec the concept
 | |
|   // of the "original owning element" or "event target" and allow script
 | |
|   // to set it when creating a CSSAnimation object.
 | |
|   if (!mOwningElement.IsSet()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   dom::Element* owningElement;
 | |
|   CSSPseudoElementType owningPseudoType;
 | |
|   mOwningElement.GetElement(owningElement, owningPseudoType);
 | |
|   MOZ_ASSERT(owningElement, "Owning element should be set");
 | |
| 
 | |
|   // Get the nsAnimationManager so we can queue events on it
 | |
|   nsPresContext* presContext =
 | |
|     nsContentUtils::GetContextForContent(owningElement);
 | |
|   if (!presContext) {
 | |
|     return;
 | |
|   }
 | |
|   nsAnimationManager* manager = presContext->AnimationManager();
 | |
| 
 | |
|   const StickyTimeDuration zeroDuration;
 | |
|   uint64_t currentIteration = 0;
 | |
|   ComputedTiming::AnimationPhase currentPhase;
 | |
|   StickyTimeDuration intervalStartTime;
 | |
|   StickyTimeDuration intervalEndTime;
 | |
|   StickyTimeDuration iterationStartTime;
 | |
| 
 | |
|   if (!mEffect) {
 | |
|     currentPhase = GetAnimationPhaseWithoutEffect
 | |
|       <ComputedTiming::AnimationPhase>(*this);
 | |
|   } else {
 | |
|     ComputedTiming computedTiming = mEffect->GetComputedTiming();
 | |
|     currentPhase = computedTiming.mPhase;
 | |
|     currentIteration = computedTiming.mCurrentIteration;
 | |
|     if (currentPhase == mPreviousPhase &&
 | |
|         currentIteration == mPreviousIteration) {
 | |
|       return;
 | |
|     }
 | |
|     intervalStartTime =
 | |
|       std::max(std::min(StickyTimeDuration(-mEffect->SpecifiedTiming().Delay()),
 | |
|                         computedTiming.mActiveDuration),
 | |
|                zeroDuration);
 | |
|     intervalEndTime =
 | |
|       std::max(std::min((EffectEnd() - mEffect->SpecifiedTiming().Delay()),
 | |
|                         computedTiming.mActiveDuration),
 | |
|                zeroDuration);
 | |
| 
 | |
|     uint64_t iterationBoundary = mPreviousIteration > currentIteration
 | |
|                                  ? currentIteration + 1
 | |
|                                  : currentIteration;
 | |
|     iterationStartTime  =
 | |
|       computedTiming.mDuration.MultDouble(
 | |
|         (iterationBoundary - computedTiming.mIterationStart));
 | |
|   }
 | |
| 
 | |
|   TimeStamp startTimeStamp     = ElapsedTimeToTimeStamp(intervalStartTime);
 | |
|   TimeStamp endTimeStamp       = ElapsedTimeToTimeStamp(intervalEndTime);
 | |
|   TimeStamp iterationTimeStamp = ElapsedTimeToTimeStamp(iterationStartTime);
 | |
| 
 | |
|   AutoTArray<AnimationEventParams, 2> events;
 | |
| 
 | |
|   // Handle cancel event first
 | |
|   if ((mPreviousPhase != AnimationPhase::Idle &&
 | |
|        mPreviousPhase != AnimationPhase::After) &&
 | |
|       currentPhase == AnimationPhase::Idle) {
 | |
|     TimeStamp activeTimeStamp = ElapsedTimeToTimeStamp(aActiveTime);
 | |
|     events.AppendElement(AnimationEventParams{ eAnimationCancel,
 | |
|                                                aActiveTime,
 | |
|                                                activeTimeStamp });
 | |
|   }
 | |
| 
 | |
|   switch (mPreviousPhase) {
 | |
|     case AnimationPhase::Idle:
 | |
|     case AnimationPhase::Before:
 | |
|       if (currentPhase == AnimationPhase::Active) {
 | |
|         events.AppendElement(AnimationEventParams{ eAnimationStart,
 | |
|                                                    intervalStartTime,
 | |
|                                                    startTimeStamp });
 | |
|       } else if (currentPhase == AnimationPhase::After) {
 | |
|         events.AppendElement(AnimationEventParams{ eAnimationStart,
 | |
|                                                    intervalStartTime,
 | |
|                                                    startTimeStamp });
 | |
|         events.AppendElement(AnimationEventParams{ eAnimationEnd,
 | |
|                                                    intervalEndTime,
 | |
|                                                    endTimeStamp });
 | |
|       }
 | |
|       break;
 | |
|     case AnimationPhase::Active:
 | |
|       if (currentPhase == AnimationPhase::Before) {
 | |
|         events.AppendElement(AnimationEventParams{ eAnimationEnd,
 | |
|                                                    intervalStartTime,
 | |
|                                                    startTimeStamp });
 | |
|       } else if (currentPhase == AnimationPhase::Active) {
 | |
|         // The currentIteration must have changed or element we would have
 | |
|         // returned early above.
 | |
|         MOZ_ASSERT(currentIteration != mPreviousIteration);
 | |
|         events.AppendElement(AnimationEventParams{ eAnimationIteration,
 | |
|                                                    iterationStartTime,
 | |
|                                                    iterationTimeStamp });
 | |
|       } else if (currentPhase == AnimationPhase::After) {
 | |
|         events.AppendElement(AnimationEventParams{ eAnimationEnd,
 | |
|                                                    intervalEndTime,
 | |
|                                                    endTimeStamp });
 | |
|       }
 | |
|       break;
 | |
|     case AnimationPhase::After:
 | |
|       if (currentPhase == AnimationPhase::Before) {
 | |
|         events.AppendElement(AnimationEventParams{ eAnimationStart,
 | |
|                                                    intervalEndTime,
 | |
|                                                    startTimeStamp});
 | |
|         events.AppendElement(AnimationEventParams{ eAnimationEnd,
 | |
|                                                    intervalStartTime,
 | |
|                                                    endTimeStamp });
 | |
|       } else if (currentPhase == AnimationPhase::Active) {
 | |
|         events.AppendElement(AnimationEventParams{ eAnimationStart,
 | |
|                                                    intervalEndTime,
 | |
|                                                    endTimeStamp });
 | |
|       }
 | |
|       break;
 | |
|   }
 | |
|   mPreviousPhase = currentPhase;
 | |
|   mPreviousIteration = currentIteration;
 | |
| 
 | |
|   for (const AnimationEventParams& event : events){
 | |
|     manager->QueueEvent(
 | |
|                AnimationEventInfo(owningElement, owningPseudoType,
 | |
|                                   event.mMessage, mAnimationName,
 | |
|                                   event.mElapsedTime, event.mTimeStamp,
 | |
|                                   this));
 | |
|   }
 | |
| }
 | |
| 
 | |
| void
 | |
| CSSAnimation::UpdateTiming(SeekFlag aSeekFlag, SyncNotifyFlag aSyncNotifyFlag)
 | |
| {
 | |
|   if (mNeedsNewAnimationIndexWhenRun &&
 | |
|       PlayState() != AnimationPlayState::Idle) {
 | |
|     mAnimationIndex = sNextAnimationIndex++;
 | |
|     mNeedsNewAnimationIndexWhenRun = false;
 | |
|   }
 | |
| 
 | |
|   Animation::UpdateTiming(aSeekFlag, aSyncNotifyFlag);
 | |
| }
 | |
| 
 | |
| ////////////////////////// nsAnimationManager ////////////////////////////
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION(nsAnimationManager, mEventDispatcher)
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsAnimationManager, AddRef)
 | |
| NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsAnimationManager, Release)
 | |
| 
 | |
| // Find the matching animation by |aName| in the old list
 | |
| // of animations and remove the matched animation from the list.
 | |
| static already_AddRefed<CSSAnimation>
 | |
| PopExistingAnimation(const nsAtom* aName,
 | |
|                      nsAnimationManager::CSSAnimationCollection* aCollection)
 | |
| {
 | |
|   if (!aCollection) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   // Animations are stored in reverse order to how they appear in the
 | |
|   // animation-name property. However, we want to match animations beginning
 | |
|   // from the end of the animation-name list, so we iterate *forwards*
 | |
|   // through the collection.
 | |
|   for (size_t idx = 0, length = aCollection->mAnimations.Length();
 | |
|        idx != length; ++ idx) {
 | |
|     CSSAnimation* cssAnim = aCollection->mAnimations[idx];
 | |
|     if (cssAnim->AnimationName() == aName) {
 | |
|       RefPtr<CSSAnimation> match = cssAnim;
 | |
|       aCollection->mAnimations.RemoveElementAt(idx);
 | |
|       return match.forget();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| class ResolvedStyleCache {
 | |
| public:
 | |
|   ResolvedStyleCache() : mCache() {}
 | |
|   GeckoStyleContext* Get(nsPresContext* aPresContext,
 | |
|                          GeckoStyleContext* aParentStyleContext,
 | |
|                          Declaration* aKeyframeDeclaration);
 | |
| 
 | |
| private:
 | |
|   nsRefPtrHashtable<nsPtrHashKey<Declaration>, GeckoStyleContext> mCache;
 | |
| };
 | |
| 
 | |
| GeckoStyleContext*
 | |
| ResolvedStyleCache::Get(nsPresContext* aPresContext,
 | |
|                         GeckoStyleContext* aParentStyleContext,
 | |
|                         Declaration* aKeyframeDeclaration)
 | |
| {
 | |
|   // FIXME (spec):  The css3-animations spec isn't very clear about how
 | |
|   // properties are resolved when they have values that depend on other
 | |
|   // properties (e.g., values in 'em').  I presume that they're resolved
 | |
|   // relative to the other styles of the element.  The question is
 | |
|   // whether they are resolved relative to other animations:  I assume
 | |
|   // that they're not, since that would prevent us from caching a lot of
 | |
|   // data that we'd really like to cache (in particular, the
 | |
|   // StyleAnimationValue values in AnimationPropertySegment).
 | |
|   GeckoStyleContext* result = mCache.GetWeak(aKeyframeDeclaration);
 | |
|   if (!result) {
 | |
|     aKeyframeDeclaration->SetImmutable();
 | |
|     // The spec says that !important declarations should just be ignored
 | |
|     MOZ_ASSERT(!aKeyframeDeclaration->HasImportantData(),
 | |
|                "Keyframe rule has !important data");
 | |
| 
 | |
|     nsCOMArray<nsIStyleRule> rules;
 | |
|     rules.AppendObject(aKeyframeDeclaration);
 | |
|     MOZ_ASSERT(aPresContext->StyleSet()->IsGecko(),
 | |
|                "ServoStyleSet should not use nsAnimationManager for "
 | |
|                "animations");
 | |
|     RefPtr<GeckoStyleContext> resultStrong =
 | |
|       aPresContext->StyleSet()->AsGecko()->
 | |
|         ResolveStyleByAddingRules(aParentStyleContext, rules);
 | |
|     mCache.Put(aKeyframeDeclaration, resultStrong);
 | |
|     result = resultStrong;
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| class MOZ_STACK_CLASS ServoCSSAnimationBuilder final {
 | |
| public:
 | |
|   explicit ServoCSSAnimationBuilder(const ServoStyleContext* aStyleContext)
 | |
|     : mStyleContext(aStyleContext)
 | |
|   {
 | |
|     MOZ_ASSERT(aStyleContext);
 | |
|   }
 | |
| 
 | |
|   bool BuildKeyframes(nsPresContext* aPresContext,
 | |
|                       const StyleAnimation& aSrc,
 | |
|                       nsTArray<Keyframe>& aKeyframes)
 | |
|   {
 | |
|     ServoStyleSet* styleSet = aPresContext->StyleSet()->AsServo();
 | |
|     MOZ_ASSERT(styleSet);
 | |
|     const nsTimingFunction& timingFunction = aSrc.GetTimingFunction();
 | |
|     return styleSet->GetKeyframesForName(aSrc.GetName(),
 | |
|                                          timingFunction,
 | |
|                                          aKeyframes);
 | |
|   }
 | |
|   void SetKeyframes(KeyframeEffectReadOnly& aEffect,
 | |
|                     nsTArray<Keyframe>&& aKeyframes)
 | |
|   {
 | |
|     aEffect.SetKeyframes(Move(aKeyframes), mStyleContext);
 | |
|   }
 | |
| 
 | |
|   // Currently all the animation building code in this file is based on
 | |
|   // assumption that creating and removing animations should *not* trigger
 | |
|   // additional restyles since those changes will be handled within the same
 | |
|   // restyle.
 | |
|   //
 | |
|   // While that is true for the Gecko style backend, it is not true for the
 | |
|   // Servo style backend where we want restyles to be triggered so that we
 | |
|   // perform a second animation restyle where we will incorporate the changes
 | |
|   // arising from creating and removing animations.
 | |
|   //
 | |
|   // Fortunately, our attempts to avoid posting extra restyles as part of the
 | |
|   // processing here are imperfect and most of the time we happen to post
 | |
|   // them anyway. Occasionally, however, we don't. For example, we don't post
 | |
|   // a restyle when we create a new animation whose an animation index matches
 | |
|   // the default value it was given already (which is typically only true when
 | |
|   // the CSSAnimation we create is the first Animation created in a particular
 | |
|   // content process).
 | |
|   //
 | |
|   // As a result, when we are using the Servo backend, whenever we have an added
 | |
|   // or removed animation we need to explicitly trigger a restyle.
 | |
|   //
 | |
|   // This code should eventually disappear along with the Gecko style backend
 | |
|   // and we should simply call Play() / Pause() / Cancel() etc. which will
 | |
|   // post the required restyles.
 | |
|   void NotifyNewOrRemovedAnimation(const Animation& aAnimation)
 | |
|   {
 | |
|     AnimationEffectReadOnly* effect = aAnimation.GetEffect();
 | |
|     if (!effect) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     KeyframeEffectReadOnly* keyframeEffect = effect->AsKeyframeEffect();
 | |
|     if (!keyframeEffect) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     keyframeEffect->RequestRestyle(EffectCompositor::RestyleType::Standard);
 | |
|   }
 | |
| 
 | |
| private:
 | |
|   const ServoStyleContext* mStyleContext;
 | |
| };
 | |
| 
 | |
| class MOZ_STACK_CLASS GeckoCSSAnimationBuilder final {
 | |
| public:
 | |
|   GeckoCSSAnimationBuilder(GeckoStyleContext* aStyleContext,
 | |
|                            const NonOwningAnimationTarget& aTarget)
 | |
|     : mStyleContext(aStyleContext)
 | |
|     , mTarget(aTarget)
 | |
|   {
 | |
|     MOZ_ASSERT(aStyleContext);
 | |
|     MOZ_ASSERT(aTarget.mElement);
 | |
|   }
 | |
| 
 | |
|   bool BuildKeyframes(nsPresContext* aPresContext,
 | |
|                       const StyleAnimation& aSrc,
 | |
|                       nsTArray<Keyframe>& aKeyframs);
 | |
|   void SetKeyframes(KeyframeEffectReadOnly& aEffect,
 | |
|                     nsTArray<Keyframe>&& aKeyframes)
 | |
|   {
 | |
|     aEffect.SetKeyframes(Move(aKeyframes), mStyleContext);
 | |
|   }
 | |
| 
 | |
|   void NotifyNewOrRemovedAnimation(const Animation&) {}
 | |
| 
 | |
| private:
 | |
|   nsTArray<Keyframe> BuildAnimationFrames(nsPresContext* aPresContext,
 | |
|                                           const StyleAnimation& aSrc,
 | |
|                                           const nsCSSKeyframesRule* aRule);
 | |
|   Maybe<ComputedTimingFunction> GetKeyframeTimingFunction(
 | |
|     nsPresContext* aPresContext,
 | |
|     nsCSSKeyframeRule* aKeyframeRule,
 | |
|     const Maybe<ComputedTimingFunction>& aInheritedTimingFunction);
 | |
|   nsTArray<PropertyValuePair> GetKeyframePropertyValues(
 | |
|     nsPresContext* aPresContext,
 | |
|     nsCSSKeyframeRule* aKeyframeRule,
 | |
|     nsCSSPropertyIDSet& aAnimatedProperties);
 | |
|   void FillInMissingKeyframeValues(
 | |
|     nsCSSPropertyIDSet aAnimatedProperties,
 | |
|     nsCSSPropertyIDSet aPropertiesSetAtStart,
 | |
|     nsCSSPropertyIDSet aPropertiesSetAtEnd,
 | |
|     const Maybe<ComputedTimingFunction>& aInheritedTimingFunction,
 | |
|     nsTArray<Keyframe>& aKeyframes);
 | |
| 
 | |
|   RefPtr<GeckoStyleContext> mStyleContext;
 | |
|   NonOwningAnimationTarget mTarget;
 | |
| 
 | |
|   ResolvedStyleCache mResolvedStyles;
 | |
| };
 | |
| 
 | |
| static Maybe<ComputedTimingFunction>
 | |
| ConvertTimingFunction(const nsTimingFunction& aTimingFunction);
 | |
| 
 | |
| template<class BuilderType>
 | |
| static void
 | |
| UpdateOldAnimationPropertiesWithNew(
 | |
|     CSSAnimation& aOld,
 | |
|     TimingParams& aNewTiming,
 | |
|     nsTArray<Keyframe>&& aNewKeyframes,
 | |
|     bool aNewIsStylePaused,
 | |
|     BuilderType& aBuilder)
 | |
| {
 | |
|   bool animationChanged = false;
 | |
| 
 | |
|   // Update the old from the new so we can keep the original object
 | |
|   // identity (and any expando properties attached to it).
 | |
|   if (aOld.GetEffect()) {
 | |
|     dom::AnimationEffectReadOnly* oldEffect = aOld.GetEffect();
 | |
|     animationChanged = oldEffect->SpecifiedTiming() != aNewTiming;
 | |
|     oldEffect->SetSpecifiedTiming(aNewTiming);
 | |
| 
 | |
|     KeyframeEffectReadOnly* oldKeyframeEffect = oldEffect->AsKeyframeEffect();
 | |
|     if (oldKeyframeEffect) {
 | |
|       aBuilder.SetKeyframes(*oldKeyframeEffect, Move(aNewKeyframes));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Handle changes in play state. If the animation is idle, however,
 | |
|   // changes to animation-play-state should *not* restart it.
 | |
|   if (aOld.PlayState() != AnimationPlayState::Idle) {
 | |
|     // CSSAnimation takes care of override behavior so that,
 | |
|     // for example, if the author has called pause(), that will
 | |
|     // override the animation-play-state.
 | |
|     // (We should check aNew->IsStylePaused() but that requires
 | |
|     //  downcasting to CSSAnimation and we happen to know that
 | |
|     //  aNew will only ever be paused by calling PauseFromStyle
 | |
|     //  making IsPausedOrPausing synonymous in this case.)
 | |
|     if (!aOld.IsStylePaused() && aNewIsStylePaused) {
 | |
|       aOld.PauseFromStyle();
 | |
|       animationChanged = true;
 | |
|     } else if (aOld.IsStylePaused() && !aNewIsStylePaused) {
 | |
|       aOld.PlayFromStyle();
 | |
|       animationChanged = true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Updating the effect timing above might already have caused the
 | |
|   // animation to become irrelevant so only add a changed record if
 | |
|   // the animation is still relevant.
 | |
|   if (animationChanged && aOld.IsRelevant()) {
 | |
|     nsNodeUtils::AnimationChanged(&aOld);
 | |
|   }
 | |
| }
 | |
| 
 | |
| // Returns a new animation set up with given StyleAnimation.
 | |
| // Or returns an existing animation matching StyleAnimation's name updated
 | |
| // with the new StyleAnimation.
 | |
| template<class BuilderType>
 | |
| static already_AddRefed<CSSAnimation>
 | |
| BuildAnimation(nsPresContext* aPresContext,
 | |
|                const NonOwningAnimationTarget& aTarget,
 | |
|                const StyleAnimation& aSrc,
 | |
|                BuilderType& aBuilder,
 | |
|                nsAnimationManager::CSSAnimationCollection* aCollection)
 | |
| {
 | |
|   MOZ_ASSERT(aPresContext);
 | |
| 
 | |
|   nsTArray<Keyframe> keyframes;
 | |
|   if (!aBuilder.BuildKeyframes(aPresContext, aSrc, keyframes)) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   TimingParams timing = TimingParamsFromCSSParams(aSrc.GetDuration(),
 | |
|                                                   aSrc.GetDelay(),
 | |
|                                                   aSrc.GetIterationCount(),
 | |
|                                                   aSrc.GetDirection(),
 | |
|                                                   aSrc.GetFillMode());
 | |
| 
 | |
|   bool isStylePaused =
 | |
|     aSrc.GetPlayState() == NS_STYLE_ANIMATION_PLAY_STATE_PAUSED;
 | |
| 
 | |
|   // Find the matching animation with animation name in the old list
 | |
|   // of animations and remove the matched animation from the list.
 | |
|   RefPtr<CSSAnimation> oldAnim =
 | |
|     PopExistingAnimation(aSrc.GetName(), aCollection);
 | |
| 
 | |
|   if (oldAnim) {
 | |
|     // Copy over the start times and (if still paused) pause starts
 | |
|     // for each animation (matching on name only) that was also in the
 | |
|     // old list of animations.
 | |
|     // This means that we honor dynamic changes, which isn't what the
 | |
|     // spec says to do, but WebKit seems to honor at least some of
 | |
|     // them.  See
 | |
|     // http://lists.w3.org/Archives/Public/www-style/2011Apr/0079.html
 | |
|     // In order to honor what the spec said, we'd copy more data over.
 | |
|     UpdateOldAnimationPropertiesWithNew(*oldAnim,
 | |
|                                         timing,
 | |
|                                         Move(keyframes),
 | |
|                                         isStylePaused,
 | |
|                                         aBuilder);
 | |
|     return oldAnim.forget();
 | |
|   }
 | |
| 
 | |
|   // mTarget is non-null here, so we emplace it directly.
 | |
|   Maybe<OwningAnimationTarget> target;
 | |
|   target.emplace(aTarget.mElement, aTarget.mPseudoType);
 | |
|   KeyframeEffectParams effectOptions;
 | |
|   RefPtr<KeyframeEffectReadOnly> effect =
 | |
|     new KeyframeEffectReadOnly(aPresContext->Document(), target, timing,
 | |
|                                effectOptions);
 | |
| 
 | |
|   aBuilder.SetKeyframes(*effect, Move(keyframes));
 | |
| 
 | |
|   RefPtr<CSSAnimation> animation =
 | |
|     new CSSAnimation(aPresContext->Document()->GetScopeObject(),
 | |
|                      aSrc.GetName());
 | |
|   animation->SetOwningElement(
 | |
|     OwningElementRef(*aTarget.mElement, aTarget.mPseudoType));
 | |
| 
 | |
|   animation->SetTimelineNoUpdate(aTarget.mElement->OwnerDoc()->Timeline());
 | |
|   animation->SetEffectNoUpdate(effect);
 | |
| 
 | |
|   if (isStylePaused) {
 | |
|     animation->PauseFromStyle();
 | |
|   } else {
 | |
|     animation->PlayFromStyle();
 | |
|   }
 | |
| 
 | |
|   aBuilder.NotifyNewOrRemovedAnimation(*animation);
 | |
| 
 | |
|   return animation.forget();
 | |
| }
 | |
| 
 | |
| bool
 | |
| GeckoCSSAnimationBuilder::BuildKeyframes(nsPresContext* aPresContext,
 | |
|                                          const StyleAnimation& aSrc,
 | |
|                                          nsTArray<Keyframe>& aKeyframes)
 | |
| {
 | |
|   MOZ_ASSERT(aPresContext);
 | |
|   MOZ_ASSERT(aPresContext->StyleSet()->IsGecko());
 | |
| 
 | |
|   nsCSSKeyframesRule* rule =
 | |
|     aPresContext->StyleSet()->AsGecko()->KeyframesRuleForName(aSrc.GetName());
 | |
|   if (!rule) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   aKeyframes = BuildAnimationFrames(aPresContext, aSrc, rule);
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| nsTArray<Keyframe>
 | |
| GeckoCSSAnimationBuilder::BuildAnimationFrames(nsPresContext* aPresContext,
 | |
|                                                const StyleAnimation& aSrc,
 | |
|                                                const nsCSSKeyframesRule* aRule)
 | |
| {
 | |
|   // Ideally we'd like to build up a set of Keyframe objects that more-or-less
 | |
|   // reflect the keyframes as-specified in the @keyframes rule(s) so that
 | |
|   // authors get something intuitive when they call anim.effect.getKeyframes().
 | |
|   //
 | |
|   // That, however, proves to be difficult because the way CSS declarations are
 | |
|   // processed differs from how we are able to represent keyframes as
 | |
|   // JavaScript objects in the Web Animations API.
 | |
|   //
 | |
|   // For example,
 | |
|   //
 | |
|   //   { margin: 10px; margin-left: 20px }
 | |
|   //
 | |
|   // could be represented as:
 | |
|   //
 | |
|   //   { margin: '10px', marginLeft: '20px' }
 | |
|   //
 | |
|   // BUT:
 | |
|   //
 | |
|   //   { margin-left: 20px; margin: 10px }
 | |
|   //
 | |
|   // would be represented as:
 | |
|   //
 | |
|   //   { margin: '10px' }
 | |
|   //
 | |
|   // Likewise,
 | |
|   //
 | |
|   //   { margin-left: 20px; margin-left: 30px }
 | |
|   //
 | |
|   // would be represented as:
 | |
|   //
 | |
|   //   { marginLeft: '30px' }
 | |
|   //
 | |
|   // As such, the mapping between source @keyframes and the Keyframe objects
 | |
|   // becomes obscured. The deviation is even more significant when we consider
 | |
|   // cascading between @keyframes rules and variable references in shorthand
 | |
|   // properties.
 | |
|   //
 | |
|   // We could, perhaps, produce a mapping that makes sense most of the time
 | |
|   // but it would be complex and need to be specified and implemented
 | |
|   // interoperably. Instead, for now, for CSS Animations (and CSS Transitions,
 | |
|   // for that matter) we resolve values on @keyframes down to computed values
 | |
|   // (thereby expanding shorthands and variable references) and then pick up the
 | |
|   // last value for each longhand property at each offset.
 | |
| 
 | |
|   // FIXME: There is a pending spec change to make multiple @keyframes
 | |
|   // rules with the same name cascade but we don't support that yet.
 | |
| 
 | |
|   Maybe<ComputedTimingFunction> inheritedTimingFunction =
 | |
|     ConvertTimingFunction(aSrc.GetTimingFunction());
 | |
| 
 | |
|   // First, make up Keyframe objects for each rule
 | |
|   nsTArray<Keyframe> keyframes;
 | |
|   nsCSSPropertyIDSet animatedProperties;
 | |
| 
 | |
|   for (auto ruleIdx = 0, ruleEnd = aRule->StyleRuleCount();
 | |
|        ruleIdx != ruleEnd; ++ruleIdx) {
 | |
|     css::Rule* cssRule = aRule->GetStyleRuleAt(ruleIdx);
 | |
|     MOZ_ASSERT(cssRule, "must have rule");
 | |
|     MOZ_ASSERT(cssRule->GetType() == css::Rule::KEYFRAME_RULE,
 | |
|                "must be keyframe rule");
 | |
|     nsCSSKeyframeRule* keyframeRule = static_cast<nsCSSKeyframeRule*>(cssRule);
 | |
| 
 | |
|     const nsTArray<float>& keys = keyframeRule->GetKeys();
 | |
|     for (float key : keys) {
 | |
|       if (key < 0.0f || key > 1.0f) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       Keyframe keyframe;
 | |
|       keyframe.mOffset.emplace(key);
 | |
|       keyframe.mTimingFunction =
 | |
|         GetKeyframeTimingFunction(aPresContext, keyframeRule,
 | |
|                                   inheritedTimingFunction);
 | |
|       keyframe.mPropertyValues =
 | |
|         GetKeyframePropertyValues(aPresContext, keyframeRule,
 | |
|                                   animatedProperties);
 | |
| 
 | |
|       keyframes.AppendElement(Move(keyframe));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Next, stable sort by offset
 | |
|   std::stable_sort(keyframes.begin(), keyframes.end(),
 | |
|                    [](const Keyframe& a, const Keyframe& b)
 | |
|                    {
 | |
|                      return a.mOffset < b.mOffset;
 | |
|                    });
 | |
| 
 | |
|   // Then walk backwards through the keyframes and drop overridden properties.
 | |
|   nsCSSPropertyIDSet propertiesSetAtCurrentOffset;
 | |
|   nsCSSPropertyIDSet propertiesSetAtStart;
 | |
|   nsCSSPropertyIDSet propertiesSetAtEnd;
 | |
|   double currentOffset = -1.0;
 | |
|   for (size_t keyframeIdx = keyframes.Length();
 | |
|        keyframeIdx > 0;
 | |
|        --keyframeIdx) {
 | |
|     Keyframe& keyframe = keyframes[keyframeIdx - 1];
 | |
|     MOZ_ASSERT(keyframe.mOffset, "Should have filled in the offset");
 | |
| 
 | |
|     if (keyframe.mOffset.value() != currentOffset) {
 | |
|       propertiesSetAtCurrentOffset.Empty();
 | |
|       currentOffset = keyframe.mOffset.value();
 | |
|     }
 | |
| 
 | |
|     // Get the set of properties from this keyframe that have not
 | |
|     // already been set at this offset.
 | |
|     nsTArray<PropertyValuePair> uniquePropertyValues;
 | |
|     uniquePropertyValues.SetCapacity(keyframe.mPropertyValues.Length());
 | |
|     for (const PropertyValuePair& pair : keyframe.mPropertyValues) {
 | |
|       if (!propertiesSetAtCurrentOffset.HasProperty(pair.mProperty)) {
 | |
|         uniquePropertyValues.AppendElement(pair);
 | |
|         propertiesSetAtCurrentOffset.AddProperty(pair.mProperty);
 | |
| 
 | |
|         if (currentOffset == 0.0) {
 | |
|           propertiesSetAtStart.AddProperty(pair.mProperty);
 | |
|         } else if (currentOffset == 1.0) {
 | |
|           propertiesSetAtEnd.AddProperty(pair.mProperty);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // If we have a keyframe at the same offset with the same timing
 | |
|     // function we should merge our (unique) values into it.
 | |
|     // Otherwise, we should update the existing keyframe with only the
 | |
|     // unique properties.
 | |
|     //
 | |
|     // Bug 1293490: We should also match composite modes here.
 | |
|     Keyframe* existingKeyframe = nullptr;
 | |
|     // Don't bother searching for an existing keyframe if we don't
 | |
|     // have anything to contribute to it.
 | |
|     if (!uniquePropertyValues.IsEmpty()) {
 | |
|       for (size_t i = keyframeIdx; i < keyframes.Length(); i++) {
 | |
|         Keyframe& kf = keyframes[i];
 | |
|         if (kf.mOffset.value() != currentOffset) {
 | |
|           break;
 | |
|         }
 | |
|         if (kf.mTimingFunction == keyframe.mTimingFunction) {
 | |
|           existingKeyframe = &kf;
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (existingKeyframe) {
 | |
|       existingKeyframe->
 | |
|         mPropertyValues.AppendElements(Move(uniquePropertyValues));
 | |
|       keyframe.mPropertyValues.Clear();
 | |
|     } else {
 | |
|       keyframe.mPropertyValues.SwapElements(uniquePropertyValues);
 | |
|     }
 | |
| 
 | |
|     // Check for a now-empty keyframe
 | |
|     if (keyframe.mPropertyValues.IsEmpty()) {
 | |
|       keyframes.RemoveElementAt(keyframeIdx - 1);
 | |
|       // existingKeyframe might dangle now
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Finally, we need to look for any animated properties that have an
 | |
|   // implicit 'to' or 'from' value and fill in the appropriate keyframe
 | |
|   // with the current computed style.
 | |
|   FillInMissingKeyframeValues(animatedProperties, propertiesSetAtStart,
 | |
|                               propertiesSetAtEnd, inheritedTimingFunction,
 | |
|                               keyframes);
 | |
| 
 | |
|   return keyframes;
 | |
| }
 | |
| 
 | |
| Maybe<ComputedTimingFunction>
 | |
| GeckoCSSAnimationBuilder::GetKeyframeTimingFunction(
 | |
|     nsPresContext* aPresContext,
 | |
|     nsCSSKeyframeRule* aKeyframeRule,
 | |
|     const Maybe<ComputedTimingFunction>& aInheritedTimingFunction)
 | |
| {
 | |
|   Maybe<ComputedTimingFunction> result;
 | |
| 
 | |
|   if (aKeyframeRule->Declaration() &&
 | |
|       aKeyframeRule->Declaration()->HasProperty(
 | |
|         eCSSProperty_animation_timing_function)) {
 | |
|     RefPtr<nsStyleContext> keyframeRuleContext =
 | |
|       mResolvedStyles.Get(aPresContext, mStyleContext,
 | |
|                           aKeyframeRule->Declaration());
 | |
|     const nsTimingFunction& tf = keyframeRuleContext->StyleDisplay()->
 | |
|       mAnimations[0].GetTimingFunction();
 | |
|     result = ConvertTimingFunction(tf);
 | |
|   } else {
 | |
|     result = aInheritedTimingFunction;
 | |
|   }
 | |
| 
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| static Maybe<ComputedTimingFunction>
 | |
| ConvertTimingFunction(const nsTimingFunction& aTimingFunction)
 | |
| {
 | |
|   Maybe<ComputedTimingFunction> result;
 | |
| 
 | |
|   if (aTimingFunction.mType != nsTimingFunction::Type::Linear) {
 | |
|     result.emplace();
 | |
|     result->Init(aTimingFunction);
 | |
|   }
 | |
| 
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| nsTArray<PropertyValuePair>
 | |
| GeckoCSSAnimationBuilder::GetKeyframePropertyValues(
 | |
|     nsPresContext* aPresContext,
 | |
|     nsCSSKeyframeRule* aKeyframeRule,
 | |
|     nsCSSPropertyIDSet& aAnimatedProperties)
 | |
| {
 | |
|   nsTArray<PropertyValuePair> result;
 | |
|   RefPtr<GeckoStyleContext> styleContext =
 | |
|     mResolvedStyles.Get(aPresContext, mStyleContext,
 | |
|                         aKeyframeRule->Declaration());
 | |
| 
 | |
|   for (nsCSSPropertyID prop = nsCSSPropertyID(0);
 | |
|        prop < eCSSProperty_COUNT_no_shorthands;
 | |
|        prop = nsCSSPropertyID(prop + 1)) {
 | |
|     if (nsCSSProps::kAnimTypeTable[prop] == eStyleAnimType_None ||
 | |
|         !aKeyframeRule->Declaration()->HasNonImportantValueFor(prop)) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     StyleAnimationValue computedValue;
 | |
|     if (!StyleAnimationValue::ExtractComputedValue(prop, styleContext,
 | |
|                                                    computedValue)) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     nsCSSValue propertyValue;
 | |
|     DebugOnly<bool> uncomputeResult =
 | |
|       StyleAnimationValue::UncomputeValue(prop, Move(computedValue),
 | |
|                                           propertyValue);
 | |
|     MOZ_ASSERT(uncomputeResult,
 | |
|                "Unable to get specified value from computed value");
 | |
|     MOZ_ASSERT(propertyValue.GetUnit() != eCSSUnit_Null,
 | |
|                "Not expecting to read invalid properties");
 | |
| 
 | |
|     result.AppendElement(Move(PropertyValuePair(prop, Move(propertyValue))));
 | |
|     aAnimatedProperties.AddProperty(prop);
 | |
|   }
 | |
| 
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| void
 | |
| GeckoCSSAnimationBuilder::FillInMissingKeyframeValues(
 | |
|     nsCSSPropertyIDSet aAnimatedProperties,
 | |
|     nsCSSPropertyIDSet aPropertiesSetAtStart,
 | |
|     nsCSSPropertyIDSet aPropertiesSetAtEnd,
 | |
|     const Maybe<ComputedTimingFunction>& aInheritedTimingFunction,
 | |
|     nsTArray<Keyframe>& aKeyframes)
 | |
| {
 | |
|   static const size_t kNotSet = static_cast<size_t>(-1);
 | |
| 
 | |
|   // Find/create the keyframe to add start values to
 | |
|   size_t startKeyframeIndex = kNotSet;
 | |
|   if (!aAnimatedProperties.Equals(aPropertiesSetAtStart) &&
 | |
|       !nsAnimationManager::FindMatchingKeyframe(aKeyframes,
 | |
|                                                 0.0,
 | |
|                                                 aInheritedTimingFunction,
 | |
|                                                 startKeyframeIndex)) {
 | |
|     Keyframe newKeyframe;
 | |
|     newKeyframe.mOffset.emplace(0.0);
 | |
|     newKeyframe.mTimingFunction = aInheritedTimingFunction;
 | |
|     aKeyframes.InsertElementAt(startKeyframeIndex, Move(newKeyframe));
 | |
|   }
 | |
| 
 | |
|   // Find/create the keyframe to add end values to
 | |
|   size_t endKeyframeIndex = kNotSet;
 | |
|   if (!aAnimatedProperties.Equals(aPropertiesSetAtEnd)) {
 | |
|     if (!nsAnimationManager::FindMatchingKeyframe(Reversed(aKeyframes),
 | |
|                                                   1.0,
 | |
|                                                   aInheritedTimingFunction,
 | |
|                                                   endKeyframeIndex)) {
 | |
|       Keyframe newKeyframe;
 | |
|       newKeyframe.mOffset.emplace(1.0);
 | |
|       newKeyframe.mTimingFunction = aInheritedTimingFunction;
 | |
|       aKeyframes.AppendElement(Move(newKeyframe));
 | |
|       endKeyframeIndex = aKeyframes.Length() - 1;
 | |
|     } else {
 | |
|       // endKeyframeIndex is currently a count from the end of the array
 | |
|       // so we need to reverse it.
 | |
|       endKeyframeIndex = aKeyframes.Length() - 1 - endKeyframeIndex;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (startKeyframeIndex == kNotSet && endKeyframeIndex == kNotSet) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Now that we have finished manipulating aKeyframes, it is safe to
 | |
|   // take pointers to its elements.
 | |
|   Keyframe* startKeyframe = startKeyframeIndex == kNotSet
 | |
|                             ? nullptr : &aKeyframes[startKeyframeIndex];
 | |
|   Keyframe* endKeyframe   = endKeyframeIndex == kNotSet
 | |
|                             ? nullptr : &aKeyframes[endKeyframeIndex];
 | |
| 
 | |
|   // Iterate through all properties and fill-in missing values
 | |
|   for (nsCSSPropertyID prop = nsCSSPropertyID(0);
 | |
|        prop < eCSSProperty_COUNT_no_shorthands;
 | |
|        prop = nsCSSPropertyID(prop + 1)) {
 | |
|     if (!aAnimatedProperties.HasProperty(prop)) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (startKeyframe && !aPropertiesSetAtStart.HasProperty(prop)) {
 | |
|       // An uninitialized nsCSSValue represents the underlying value.
 | |
|       PropertyValuePair propertyValue(prop, Move(nsCSSValue()));
 | |
|       startKeyframe->mPropertyValues.AppendElement(Move(propertyValue));
 | |
|     }
 | |
|     if (endKeyframe && !aPropertiesSetAtEnd.HasProperty(prop)) {
 | |
|       // An uninitialized nsCSSValue represents the underlying value.
 | |
|       PropertyValuePair propertyValue(prop, Move(nsCSSValue()));
 | |
|       endKeyframe->mPropertyValues.AppendElement(Move(propertyValue));
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| template<class BuilderType>
 | |
| static nsAnimationManager::OwningCSSAnimationPtrArray
 | |
| BuildAnimations(nsPresContext* aPresContext,
 | |
|                 const NonOwningAnimationTarget& aTarget,
 | |
|                 const nsStyleAutoArray<StyleAnimation>& aStyleAnimations,
 | |
|                 uint32_t aStyleAnimationNameCount,
 | |
|                 BuilderType& aBuilder,
 | |
|                 nsAnimationManager::CSSAnimationCollection* aCollection)
 | |
| {
 | |
|   nsAnimationManager::OwningCSSAnimationPtrArray result;
 | |
| 
 | |
|   for (size_t animIdx = aStyleAnimationNameCount; animIdx-- != 0;) {
 | |
|     const StyleAnimation& src = aStyleAnimations[animIdx];
 | |
| 
 | |
|     // CSS Animations whose animation-name does not match a @keyframes rule do
 | |
|     // not generate animation events. This includes when the animation-name is
 | |
|     // "none" which is represented by an empty name in the StyleAnimation.
 | |
|     // Since such animations neither affect style nor dispatch events, we do
 | |
|     // not generate a corresponding CSSAnimation for them.
 | |
|     if (src.GetName() == nsGkAtoms::_empty) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     RefPtr<CSSAnimation> dest = BuildAnimation(aPresContext,
 | |
|                                                aTarget,
 | |
|                                                src,
 | |
|                                                aBuilder,
 | |
|                                                aCollection);
 | |
|     if (!dest) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     dest->SetAnimationIndex(static_cast<uint64_t>(animIdx));
 | |
|     result.AppendElement(dest);
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| void
 | |
| nsAnimationManager::UpdateAnimations(GeckoStyleContext* aStyleContext,
 | |
|                                      mozilla::dom::Element* aElement)
 | |
| {
 | |
|   MOZ_ASSERT(mPresContext->IsDynamic(),
 | |
|              "Should not update animations for print or print preview");
 | |
|   MOZ_ASSERT(aElement->IsInComposedDoc(),
 | |
|              "Should not update animations that are not attached to the "
 | |
|              "document tree");
 | |
| 
 | |
|   if (aStyleContext->IsInDisplayNoneSubtree()) {
 | |
|     StopAnimationsForElement(aElement, aStyleContext->GetPseudoType());
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   NonOwningAnimationTarget target(aElement, aStyleContext->GetPseudoType());
 | |
|   GeckoCSSAnimationBuilder builder(aStyleContext, target);
 | |
| 
 | |
|   const nsStyleDisplay* disp = aStyleContext->StyleDisplay();
 | |
|   DoUpdateAnimations(target, *disp, builder);
 | |
| }
 | |
| 
 | |
| void
 | |
| nsAnimationManager::UpdateAnimations(
 | |
|   dom::Element* aElement,
 | |
|   CSSPseudoElementType aPseudoType,
 | |
|   const ServoStyleContext* aStyleContext)
 | |
| {
 | |
|   MOZ_ASSERT(mPresContext->IsDynamic(),
 | |
|              "Should not update animations for print or print preview");
 | |
|   MOZ_ASSERT(aElement->IsInComposedDoc(),
 | |
|              "Should not update animations that are not attached to the "
 | |
|              "document tree");
 | |
| 
 | |
|   const nsStyleDisplay* disp = aStyleContext
 | |
|     ? aStyleContext->ComputedData()->GetStyleDisplay()
 | |
|     : nullptr;
 | |
| 
 | |
|   if (!disp || disp->mDisplay == StyleDisplay::None) {
 | |
|     // If we are in a display:none subtree we will have no computed values.
 | |
|     // However, if we are on the root of display:none subtree, the computed
 | |
|     // values might not have been cleared yet.
 | |
|     // In either case, since CSS animations should not run in display:none
 | |
|     // subtrees we should stop (actually, destroy) any animations on this
 | |
|     // element here.
 | |
|     StopAnimationsForElement(aElement, aPseudoType);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   NonOwningAnimationTarget target(aElement, aPseudoType);
 | |
|   ServoCSSAnimationBuilder builder(aStyleContext);
 | |
| 
 | |
|   DoUpdateAnimations(target, *disp, builder);
 | |
| }
 | |
| 
 | |
| template<class BuilderType>
 | |
| void
 | |
| nsAnimationManager::DoUpdateAnimations(
 | |
|   const NonOwningAnimationTarget& aTarget,
 | |
|   const nsStyleDisplay& aStyleDisplay,
 | |
|   BuilderType& aBuilder)
 | |
| {
 | |
|   // Everything that causes our animation data to change triggers a
 | |
|   // style change, which in turn triggers a non-animation restyle.
 | |
|   // Likewise, when we initially construct frames, we're not in a
 | |
|   // style change, but also not in an animation restyle.
 | |
| 
 | |
|   CSSAnimationCollection* collection =
 | |
|     CSSAnimationCollection::GetAnimationCollection(aTarget.mElement,
 | |
|                                                    aTarget.mPseudoType);
 | |
|   if (!collection &&
 | |
|       aStyleDisplay.mAnimationNameCount == 1 &&
 | |
|       aStyleDisplay.mAnimations[0].GetName() == nsGkAtoms::_empty) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsAutoAnimationMutationBatch mb(aTarget.mElement->OwnerDoc());
 | |
| 
 | |
|   // Build the updated animations list, extracting matching animations from
 | |
|   // the existing collection as we go.
 | |
|   OwningCSSAnimationPtrArray newAnimations;
 | |
|   newAnimations = BuildAnimations(mPresContext,
 | |
|                                   aTarget,
 | |
|                                   aStyleDisplay.mAnimations,
 | |
|                                   aStyleDisplay.mAnimationNameCount,
 | |
|                                   aBuilder,
 | |
|                                   collection);
 | |
| 
 | |
|   if (newAnimations.IsEmpty()) {
 | |
|     if (collection) {
 | |
|       collection->Destroy();
 | |
|     }
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!collection) {
 | |
|     bool createdCollection = false;
 | |
|     collection =
 | |
|       CSSAnimationCollection::GetOrCreateAnimationCollection(
 | |
|         aTarget.mElement, aTarget.mPseudoType, &createdCollection);
 | |
|     if (!collection) {
 | |
|       MOZ_ASSERT(!createdCollection, "outparam should agree with return value");
 | |
|       NS_WARNING("allocating collection failed");
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (createdCollection) {
 | |
|       AddElementCollection(collection);
 | |
|     }
 | |
|   }
 | |
|   collection->mAnimations.SwapElements(newAnimations);
 | |
| 
 | |
|   // Cancel removed animations
 | |
|   for (size_t newAnimIdx = newAnimations.Length(); newAnimIdx-- != 0; ) {
 | |
|     aBuilder.NotifyNewOrRemovedAnimation(*newAnimations[newAnimIdx]);
 | |
|     newAnimations[newAnimIdx]->CancelFromStyle();
 | |
|   }
 | |
| }
 |