forked from mirrors/gecko-dev
		
	 fde06f6d21
			
		
	
	
		fde06f6d21
		
	
	
	
	
		
			
			Also add some missing namespace qualifications
Done with:
./mach static-analysis check --checks="-*,modernize-concat-nested-namespaces" --fix dom/
./mach clang-format -p $(hg status dom/|grep ^M|awk '{print $2}')
Differential Revision: https://phabricator.services.mozilla.com/D95456
		
	
			
		
			
				
	
	
		
			365 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			365 lines
		
	
	
	
		
			13 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 "CSSAnimation.h"
 | |
| 
 | |
| #include "mozilla/AnimationEventDispatcher.h"
 | |
| #include "mozilla/dom/CSSAnimationBinding.h"
 | |
| #include "mozilla/dom/KeyframeEffectBinding.h"
 | |
| #include "mozilla/TimeStamp.h"
 | |
| #include "nsPresContext.h"
 | |
| 
 | |
| namespace mozilla::dom {
 | |
| 
 | |
| using AnimationPhase = ComputedTiming::AnimationPhase;
 | |
| 
 | |
| JSObject* CSSAnimation::WrapObject(JSContext* aCx,
 | |
|                                    JS::Handle<JSObject*> aGivenProto) {
 | |
|   return dom::CSSAnimation_Binding::Wrap(aCx, this, aGivenProto);
 | |
| }
 | |
| 
 | |
| void CSSAnimation::SetEffect(AnimationEffect* aEffect) {
 | |
|   Animation::SetEffect(aEffect);
 | |
| 
 | |
|   AddOverriddenProperties(CSSAnimationProperties::Effect);
 | |
| }
 | |
| 
 | |
| void CSSAnimation::SetStartTimeAsDouble(const Nullable<double>& aStartTime) {
 | |
|   // Note that we always compare with the paused state since for the purposes
 | |
|   // of determining if play control is being overridden or not, we want to
 | |
|   // treat the finished state as running.
 | |
|   bool wasPaused = PlayState() == AnimationPlayState::Paused;
 | |
| 
 | |
|   Animation::SetStartTimeAsDouble(aStartTime);
 | |
| 
 | |
|   bool isPaused = PlayState() == AnimationPlayState::Paused;
 | |
| 
 | |
|   if (wasPaused != isPaused) {
 | |
|     AddOverriddenProperties(CSSAnimationProperties::PlayState);
 | |
|   }
 | |
| }
 | |
| 
 | |
| mozilla::dom::Promise* CSSAnimation::GetReady(ErrorResult& aRv) {
 | |
|   FlushUnanimatedStyle();
 | |
|   return Animation::GetReady(aRv);
 | |
| }
 | |
| 
 | |
| void CSSAnimation::Reverse(ErrorResult& aRv) {
 | |
|   // As with CSSAnimation::SetStartTimeAsDouble, we're really only interested in
 | |
|   // the paused state.
 | |
|   bool wasPaused = PlayState() == AnimationPlayState::Paused;
 | |
| 
 | |
|   Animation::Reverse(aRv);
 | |
|   if (aRv.Failed()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   bool isPaused = PlayState() == AnimationPlayState::Paused;
 | |
| 
 | |
|   if (wasPaused != isPaused) {
 | |
|     AddOverriddenProperties(CSSAnimationProperties::PlayState);
 | |
|   }
 | |
| }
 | |
| 
 | |
| AnimationPlayState CSSAnimation::PlayStateFromJS() const {
 | |
|   // Flush style to ensure that any properties controlling animation state
 | |
|   // (e.g. animation-play-state) are fully updated.
 | |
|   FlushUnanimatedStyle();
 | |
|   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.
 | |
|   FlushUnanimatedStyle();
 | |
|   return Animation::PendingFromJS();
 | |
| }
 | |
| 
 | |
| void CSSAnimation::PlayFromJS(ErrorResult& aRv) {
 | |
|   // Note that flushing style below might trigger calls to
 | |
|   // PlayFromStyle()/PauseFromStyle() on this object.
 | |
|   FlushUnanimatedStyle();
 | |
|   Animation::PlayFromJS(aRv);
 | |
|   if (aRv.Failed()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   AddOverriddenProperties(CSSAnimationProperties::PlayState);
 | |
| }
 | |
| 
 | |
| void CSSAnimation::PauseFromJS(ErrorResult& aRv) {
 | |
|   Animation::PauseFromJS(aRv);
 | |
|   if (aRv.Failed()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   AddOverriddenProperties(CSSAnimationProperties::PlayState);
 | |
| }
 | |
| 
 | |
| void CSSAnimation::PlayFromStyle() {
 | |
|   ErrorResult rv;
 | |
|   Animation::Play(rv, Animation::LimitBehavior::Continue);
 | |
|   // play() should not throw when LimitBehavior is Continue
 | |
|   MOZ_ASSERT(!rv.Failed(), "Unexpected exception playing animation");
 | |
| }
 | |
| 
 | |
| void CSSAnimation::PauseFromStyle() {
 | |
|   ErrorResult rv;
 | |
|   Animation::Pause(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(
 | |
|         const_cast<CSSAnimation*>(this)->CachedChildIndexRef(),
 | |
|         aOther.mOwningElement,
 | |
|         const_cast<CSSAnimation*>(&aOther)->CachedChildIndexRef());
 | |
|   }
 | |
| 
 | |
|   // 2. (Same element and pseudo): Sort by position in animation-name
 | |
|   return mAnimationIndex < aOther.mAnimationIndex;
 | |
| }
 | |
| 
 | |
| void CSSAnimation::QueueEvents(const 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;
 | |
|   }
 | |
| 
 | |
|   nsPresContext* presContext = mOwningElement.GetPresContext();
 | |
|   if (!presContext) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   uint64_t currentIteration = 0;
 | |
|   ComputedTiming::AnimationPhase currentPhase;
 | |
|   StickyTimeDuration intervalStartTime;
 | |
|   StickyTimeDuration intervalEndTime;
 | |
|   StickyTimeDuration iterationStartTime;
 | |
| 
 | |
|   if (!mEffect) {
 | |
|     currentPhase =
 | |
|         GetAnimationPhaseWithoutEffect<ComputedTiming::AnimationPhase>(*this);
 | |
|     if (currentPhase == mPreviousPhase) {
 | |
|       return;
 | |
|     }
 | |
|   } else {
 | |
|     ComputedTiming computedTiming = mEffect->GetComputedTiming();
 | |
|     currentPhase = computedTiming.mPhase;
 | |
|     currentIteration = computedTiming.mCurrentIteration;
 | |
|     if (currentPhase == mPreviousPhase &&
 | |
|         currentIteration == mPreviousIteration) {
 | |
|       return;
 | |
|     }
 | |
|     intervalStartTime = IntervalStartTime(computedTiming.mActiveDuration);
 | |
|     intervalEndTime = IntervalEndTime(computedTiming.mActiveDuration);
 | |
| 
 | |
|     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<AnimationEventInfo, 2> events;
 | |
| 
 | |
|   auto appendAnimationEvent = [&](EventMessage aMessage,
 | |
|                                   const StickyTimeDuration& aElapsedTime,
 | |
|                                   const TimeStamp& aScheduledEventTimeStamp) {
 | |
|     double elapsedTime = aElapsedTime.ToSeconds();
 | |
|     if (aMessage == eAnimationCancel) {
 | |
|       // 0 is an inappropriate value for this callsite. What we need to do is
 | |
|       // use a single random value for all increasing times reportable.
 | |
|       // That is to say, whenever elapsedTime goes negative (because an
 | |
|       // animation restarts, something rewinds the animation, or otherwise)
 | |
|       // a new random value for the mix-in must be generated.
 | |
|       elapsedTime =
 | |
|           nsRFPService::ReduceTimePrecisionAsSecsRFPOnly(elapsedTime, 0);
 | |
|     }
 | |
|     events.AppendElement(
 | |
|         AnimationEventInfo(mAnimationName, mOwningElement.Target(), aMessage,
 | |
|                            elapsedTime, aScheduledEventTimeStamp, this));
 | |
|   };
 | |
| 
 | |
|   // Handle cancel event first
 | |
|   if ((mPreviousPhase != AnimationPhase::Idle &&
 | |
|        mPreviousPhase != AnimationPhase::After) &&
 | |
|       currentPhase == AnimationPhase::Idle) {
 | |
|     appendAnimationEvent(eAnimationCancel, aActiveTime,
 | |
|                          GetTimelineCurrentTimeAsTimeStamp());
 | |
|   }
 | |
| 
 | |
|   switch (mPreviousPhase) {
 | |
|     case AnimationPhase::Idle:
 | |
|     case AnimationPhase::Before:
 | |
|       if (currentPhase == AnimationPhase::Active) {
 | |
|         appendAnimationEvent(eAnimationStart, intervalStartTime,
 | |
|                              startTimeStamp);
 | |
|       } else if (currentPhase == AnimationPhase::After) {
 | |
|         appendAnimationEvent(eAnimationStart, intervalStartTime,
 | |
|                              startTimeStamp);
 | |
|         appendAnimationEvent(eAnimationEnd, intervalEndTime, endTimeStamp);
 | |
|       }
 | |
|       break;
 | |
|     case AnimationPhase::Active:
 | |
|       if (currentPhase == AnimationPhase::Before) {
 | |
|         appendAnimationEvent(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);
 | |
|         appendAnimationEvent(eAnimationIteration, iterationStartTime,
 | |
|                              iterationTimeStamp);
 | |
|       } else if (currentPhase == AnimationPhase::After) {
 | |
|         appendAnimationEvent(eAnimationEnd, intervalEndTime, endTimeStamp);
 | |
|       }
 | |
|       break;
 | |
|     case AnimationPhase::After:
 | |
|       if (currentPhase == AnimationPhase::Before) {
 | |
|         appendAnimationEvent(eAnimationStart, intervalEndTime, startTimeStamp);
 | |
|         appendAnimationEvent(eAnimationEnd, intervalStartTime, endTimeStamp);
 | |
|       } else if (currentPhase == AnimationPhase::Active) {
 | |
|         appendAnimationEvent(eAnimationStart, intervalEndTime, endTimeStamp);
 | |
|       }
 | |
|       break;
 | |
|   }
 | |
|   mPreviousPhase = currentPhase;
 | |
|   mPreviousIteration = currentIteration;
 | |
| 
 | |
|   if (!events.IsEmpty()) {
 | |
|     presContext->AnimationEventDispatcher()->QueueEvents(std::move(events));
 | |
|   }
 | |
| }
 | |
| 
 | |
| void CSSAnimation::UpdateTiming(SeekFlag aSeekFlag,
 | |
|                                 SyncNotifyFlag aSyncNotifyFlag) {
 | |
|   if (mNeedsNewAnimationIndexWhenRun &&
 | |
|       PlayState() != AnimationPlayState::Idle) {
 | |
|     mAnimationIndex = sNextAnimationIndex++;
 | |
|     mNeedsNewAnimationIndexWhenRun = false;
 | |
|   }
 | |
| 
 | |
|   Animation::UpdateTiming(aSeekFlag, aSyncNotifyFlag);
 | |
| }
 | |
| 
 | |
| /////////////////////// CSSAnimationKeyframeEffect ////////////////////////
 | |
| 
 | |
| void CSSAnimationKeyframeEffect::GetTiming(EffectTiming& aRetVal) const {
 | |
|   MaybeFlushUnanimatedStyle();
 | |
|   KeyframeEffect::GetTiming(aRetVal);
 | |
| }
 | |
| 
 | |
| void CSSAnimationKeyframeEffect::GetComputedTimingAsDict(
 | |
|     ComputedEffectTiming& aRetVal) const {
 | |
|   MaybeFlushUnanimatedStyle();
 | |
|   KeyframeEffect::GetComputedTimingAsDict(aRetVal);
 | |
| }
 | |
| 
 | |
| void CSSAnimationKeyframeEffect::UpdateTiming(
 | |
|     const OptionalEffectTiming& aTiming, ErrorResult& aRv) {
 | |
|   KeyframeEffect::UpdateTiming(aTiming, aRv);
 | |
| 
 | |
|   if (aRv.Failed()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (CSSAnimation* cssAnimation = GetOwningCSSAnimation()) {
 | |
|     CSSAnimationProperties updatedProperties = CSSAnimationProperties::None;
 | |
|     if (aTiming.mDuration.WasPassed()) {
 | |
|       updatedProperties |= CSSAnimationProperties::Duration;
 | |
|     }
 | |
|     if (aTiming.mIterations.WasPassed()) {
 | |
|       updatedProperties |= CSSAnimationProperties::IterationCount;
 | |
|     }
 | |
|     if (aTiming.mDirection.WasPassed()) {
 | |
|       updatedProperties |= CSSAnimationProperties::Direction;
 | |
|     }
 | |
|     if (aTiming.mDelay.WasPassed()) {
 | |
|       updatedProperties |= CSSAnimationProperties::Delay;
 | |
|     }
 | |
|     if (aTiming.mFill.WasPassed()) {
 | |
|       updatedProperties |= CSSAnimationProperties::FillMode;
 | |
|     }
 | |
| 
 | |
|     cssAnimation->AddOverriddenProperties(updatedProperties);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void CSSAnimationKeyframeEffect::SetKeyframes(JSContext* aContext,
 | |
|                                               JS::Handle<JSObject*> aKeyframes,
 | |
|                                               ErrorResult& aRv) {
 | |
|   KeyframeEffect::SetKeyframes(aContext, aKeyframes, aRv);
 | |
| 
 | |
|   if (aRv.Failed()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (CSSAnimation* cssAnimation = GetOwningCSSAnimation()) {
 | |
|     cssAnimation->AddOverriddenProperties(CSSAnimationProperties::Keyframes);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void CSSAnimationKeyframeEffect::MaybeFlushUnanimatedStyle() const {
 | |
|   if (!GetOwningCSSAnimation()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (dom::Document* doc = GetRenderedDocument()) {
 | |
|     doc->FlushPendingNotifications(
 | |
|         ChangesToFlush(FlushType::Style, false /* flush animations */));
 | |
|   }
 | |
| }
 | |
| 
 | |
| }  // namespace mozilla::dom
 |