Merge mozilla-central to autoland. a=merge CLOSED TREE
[gecko.git] / dom / animation / CSSAnimation.cpp
blob89bc9d71f626bb0faad3781cf999f0045a4ebdcf
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/. */
7 #include "CSSAnimation.h"
9 #include "mozilla/AnimationEventDispatcher.h"
10 #include "mozilla/dom/CSSAnimationBinding.h"
11 #include "mozilla/dom/KeyframeEffectBinding.h"
12 #include "mozilla/TimeStamp.h"
13 #include "nsPresContext.h"
15 namespace mozilla::dom {
17 using AnimationPhase = ComputedTiming::AnimationPhase;
19 JSObject* CSSAnimation::WrapObject(JSContext* aCx,
20 JS::Handle<JSObject*> aGivenProto) {
21 return dom::CSSAnimation_Binding::Wrap(aCx, this, aGivenProto);
24 void CSSAnimation::SetEffect(AnimationEffect* aEffect) {
25 Animation::SetEffect(aEffect);
27 AddOverriddenProperties(CSSAnimationProperties::Effect);
30 void CSSAnimation::SetStartTimeAsDouble(const Nullable<double>& aStartTime) {
31 // Note that we always compare with the paused state since for the purposes
32 // of determining if play control is being overridden or not, we want to
33 // treat the finished state as running.
34 bool wasPaused = PlayState() == AnimationPlayState::Paused;
36 Animation::SetStartTimeAsDouble(aStartTime);
38 bool isPaused = PlayState() == AnimationPlayState::Paused;
40 if (wasPaused != isPaused) {
41 AddOverriddenProperties(CSSAnimationProperties::PlayState);
45 mozilla::dom::Promise* CSSAnimation::GetReady(ErrorResult& aRv) {
46 FlushUnanimatedStyle();
47 return Animation::GetReady(aRv);
50 void CSSAnimation::Reverse(ErrorResult& aRv) {
51 // As with CSSAnimation::SetStartTimeAsDouble, we're really only interested in
52 // the paused state.
53 bool wasPaused = PlayState() == AnimationPlayState::Paused;
55 Animation::Reverse(aRv);
56 if (aRv.Failed()) {
57 return;
60 bool isPaused = PlayState() == AnimationPlayState::Paused;
62 if (wasPaused != isPaused) {
63 AddOverriddenProperties(CSSAnimationProperties::PlayState);
67 AnimationPlayState CSSAnimation::PlayStateFromJS() const {
68 // Flush style to ensure that any properties controlling animation state
69 // (e.g. animation-play-state) are fully updated.
70 FlushUnanimatedStyle();
71 return Animation::PlayStateFromJS();
74 bool CSSAnimation::PendingFromJS() const {
75 // Flush style since, for example, if the animation-play-state was just
76 // changed its possible we should now be pending.
77 FlushUnanimatedStyle();
78 return Animation::PendingFromJS();
81 void CSSAnimation::PlayFromJS(ErrorResult& aRv) {
82 // Note that flushing style below might trigger calls to
83 // PlayFromStyle()/PauseFromStyle() on this object.
84 FlushUnanimatedStyle();
85 Animation::PlayFromJS(aRv);
86 if (aRv.Failed()) {
87 return;
90 AddOverriddenProperties(CSSAnimationProperties::PlayState);
93 void CSSAnimation::PauseFromJS(ErrorResult& aRv) {
94 Animation::PauseFromJS(aRv);
95 if (aRv.Failed()) {
96 return;
99 AddOverriddenProperties(CSSAnimationProperties::PlayState);
102 void CSSAnimation::PlayFromStyle() {
103 ErrorResult rv;
104 Animation::Play(rv, Animation::LimitBehavior::Continue);
105 // play() should not throw when LimitBehavior is Continue
106 MOZ_ASSERT(!rv.Failed(), "Unexpected exception playing animation");
109 void CSSAnimation::PauseFromStyle() {
110 ErrorResult rv;
111 Animation::Pause(rv);
112 // pause() should only throw when *all* of the following conditions are true:
113 // - we are in the idle state, and
114 // - we have a negative playback rate, and
115 // - we have an infinitely repeating animation
116 // The first two conditions will never happen under regular style processing
117 // but could happen if an author made modifications to the Animation object
118 // and then updated animation-play-state. It's an unusual case and there's
119 // no obvious way to pass on the exception information so we just silently
120 // fail for now.
121 if (rv.Failed()) {
122 NS_WARNING("Unexpected exception pausing animation - silently failing");
126 void CSSAnimation::Tick(TickState& aState) {
127 Animation::Tick(aState);
128 QueueEvents();
131 bool CSSAnimation::HasLowerCompositeOrderThan(
132 const CSSAnimation& aOther) const {
133 MOZ_ASSERT(IsTiedToMarkup() && aOther.IsTiedToMarkup(),
134 "Should only be called for CSS animations that are sorted "
135 "as CSS animations (i.e. tied to CSS markup)");
137 // 0. Object-equality case
138 if (&aOther == this) {
139 return false;
142 // 1. Sort by document order
143 if (!mOwningElement.Equals(aOther.mOwningElement)) {
144 return mOwningElement.LessThan(
145 const_cast<CSSAnimation*>(this)->CachedChildIndexRef(),
146 aOther.mOwningElement,
147 const_cast<CSSAnimation*>(&aOther)->CachedChildIndexRef());
150 // 2. (Same element and pseudo): Sort by position in animation-name
151 return mAnimationIndex < aOther.mAnimationIndex;
154 void CSSAnimation::QueueEvents(const StickyTimeDuration& aActiveTime) {
155 // If the animation is pending, we ignore animation events until we finish
156 // pending.
157 if (mPendingState != PendingState::NotPending) {
158 return;
161 // CSS animations dispatch events at their owning element. This allows
162 // script to repurpose a CSS animation to target a different element,
163 // to use a group effect (which has no obvious "target element"), or
164 // to remove the animation effect altogether whilst still getting
165 // animation events.
167 // It does mean, however, that for a CSS animation that has no owning
168 // element (e.g. it was created using the CSSAnimation constructor or
169 // disassociated from CSS) no events are fired. If it becomes desirable
170 // for these animations to still fire events we should spec the concept
171 // of the "original owning element" or "event target" and allow script
172 // to set it when creating a CSSAnimation object.
173 if (!mOwningElement.IsSet()) {
174 return;
177 nsPresContext* presContext = mOwningElement.GetPresContext();
178 if (!presContext) {
179 return;
182 uint64_t currentIteration = 0;
183 ComputedTiming::AnimationPhase currentPhase;
184 StickyTimeDuration intervalStartTime;
185 StickyTimeDuration intervalEndTime;
186 StickyTimeDuration iterationStartTime;
188 if (!mEffect) {
189 currentPhase =
190 GetAnimationPhaseWithoutEffect<ComputedTiming::AnimationPhase>(*this);
191 if (currentPhase == mPreviousPhase) {
192 return;
194 } else {
195 ComputedTiming computedTiming = mEffect->GetComputedTiming();
196 currentPhase = computedTiming.mPhase;
197 currentIteration = computedTiming.mCurrentIteration;
198 if (currentPhase == mPreviousPhase &&
199 currentIteration == mPreviousIteration) {
200 return;
202 intervalStartTime = IntervalStartTime(computedTiming.mActiveDuration);
203 intervalEndTime = IntervalEndTime(computedTiming.mActiveDuration);
205 uint64_t iterationBoundary = mPreviousIteration > currentIteration
206 ? currentIteration + 1
207 : currentIteration;
208 double multiplier = iterationBoundary - computedTiming.mIterationStart;
209 if (multiplier != 0.0) {
210 iterationStartTime = computedTiming.mDuration.MultDouble(multiplier);
214 TimeStamp startTimeStamp = ElapsedTimeToTimeStamp(intervalStartTime);
215 TimeStamp endTimeStamp = ElapsedTimeToTimeStamp(intervalEndTime);
216 TimeStamp iterationTimeStamp = ElapsedTimeToTimeStamp(iterationStartTime);
218 AutoTArray<AnimationEventInfo, 2> events;
220 auto appendAnimationEvent = [&](EventMessage aMessage,
221 const StickyTimeDuration& aElapsedTime,
222 const TimeStamp& aScheduledEventTimeStamp) {
223 double elapsedTime = aElapsedTime.ToSeconds();
224 if (aMessage == eAnimationCancel) {
225 // 0 is an inappropriate value for this callsite. What we need to do is
226 // use a single random value for all increasing times reportable.
227 // That is to say, whenever elapsedTime goes negative (because an
228 // animation restarts, something rewinds the animation, or otherwise)
229 // a new random value for the mix-in must be generated.
230 elapsedTime = nsRFPService::ReduceTimePrecisionAsSecsRFPOnly(
231 elapsedTime, 0, mRTPCallerType);
233 events.AppendElement(
234 AnimationEventInfo(mAnimationName, mOwningElement.Target(), aMessage,
235 elapsedTime, aScheduledEventTimeStamp, this));
238 // Handle cancel event first
239 if ((mPreviousPhase != AnimationPhase::Idle &&
240 mPreviousPhase != AnimationPhase::After) &&
241 currentPhase == AnimationPhase::Idle) {
242 appendAnimationEvent(eAnimationCancel, aActiveTime,
243 GetTimelineCurrentTimeAsTimeStamp());
246 switch (mPreviousPhase) {
247 case AnimationPhase::Idle:
248 case AnimationPhase::Before:
249 if (currentPhase == AnimationPhase::Active) {
250 appendAnimationEvent(eAnimationStart, intervalStartTime,
251 startTimeStamp);
252 } else if (currentPhase == AnimationPhase::After) {
253 appendAnimationEvent(eAnimationStart, intervalStartTime,
254 startTimeStamp);
255 appendAnimationEvent(eAnimationEnd, intervalEndTime, endTimeStamp);
257 break;
258 case AnimationPhase::Active:
259 if (currentPhase == AnimationPhase::Before) {
260 appendAnimationEvent(eAnimationEnd, intervalStartTime, startTimeStamp);
261 } else if (currentPhase == AnimationPhase::Active) {
262 // The currentIteration must have changed or element we would have
263 // returned early above.
264 MOZ_ASSERT(currentIteration != mPreviousIteration);
265 appendAnimationEvent(eAnimationIteration, iterationStartTime,
266 iterationTimeStamp);
267 } else if (currentPhase == AnimationPhase::After) {
268 appendAnimationEvent(eAnimationEnd, intervalEndTime, endTimeStamp);
270 break;
271 case AnimationPhase::After:
272 if (currentPhase == AnimationPhase::Before) {
273 appendAnimationEvent(eAnimationStart, intervalEndTime, startTimeStamp);
274 appendAnimationEvent(eAnimationEnd, intervalStartTime, endTimeStamp);
275 } else if (currentPhase == AnimationPhase::Active) {
276 appendAnimationEvent(eAnimationStart, intervalEndTime, endTimeStamp);
278 break;
280 mPreviousPhase = currentPhase;
281 mPreviousIteration = currentIteration;
283 if (!events.IsEmpty()) {
284 presContext->AnimationEventDispatcher()->QueueEvents(std::move(events));
288 void CSSAnimation::UpdateTiming(SeekFlag aSeekFlag,
289 SyncNotifyFlag aSyncNotifyFlag) {
290 if (mNeedsNewAnimationIndexWhenRun &&
291 PlayState() != AnimationPlayState::Idle) {
292 mAnimationIndex = sNextAnimationIndex++;
293 mNeedsNewAnimationIndexWhenRun = false;
296 Animation::UpdateTiming(aSeekFlag, aSyncNotifyFlag);
299 /////////////////////// CSSAnimationKeyframeEffect ////////////////////////
301 void CSSAnimationKeyframeEffect::GetTiming(EffectTiming& aRetVal) const {
302 MaybeFlushUnanimatedStyle();
303 KeyframeEffect::GetTiming(aRetVal);
306 void CSSAnimationKeyframeEffect::GetComputedTimingAsDict(
307 ComputedEffectTiming& aRetVal) const {
308 MaybeFlushUnanimatedStyle();
309 KeyframeEffect::GetComputedTimingAsDict(aRetVal);
312 void CSSAnimationKeyframeEffect::UpdateTiming(
313 const OptionalEffectTiming& aTiming, ErrorResult& aRv) {
314 KeyframeEffect::UpdateTiming(aTiming, aRv);
316 if (aRv.Failed()) {
317 return;
320 if (CSSAnimation* cssAnimation = GetOwningCSSAnimation()) {
321 CSSAnimationProperties updatedProperties = CSSAnimationProperties::None;
322 if (aTiming.mDuration.WasPassed()) {
323 updatedProperties |= CSSAnimationProperties::Duration;
325 if (aTiming.mIterations.WasPassed()) {
326 updatedProperties |= CSSAnimationProperties::IterationCount;
328 if (aTiming.mDirection.WasPassed()) {
329 updatedProperties |= CSSAnimationProperties::Direction;
331 if (aTiming.mDelay.WasPassed()) {
332 updatedProperties |= CSSAnimationProperties::Delay;
334 if (aTiming.mFill.WasPassed()) {
335 updatedProperties |= CSSAnimationProperties::FillMode;
338 cssAnimation->AddOverriddenProperties(updatedProperties);
342 void CSSAnimationKeyframeEffect::SetKeyframes(JSContext* aContext,
343 JS::Handle<JSObject*> aKeyframes,
344 ErrorResult& aRv) {
345 KeyframeEffect::SetKeyframes(aContext, aKeyframes, aRv);
347 if (aRv.Failed()) {
348 return;
351 if (CSSAnimation* cssAnimation = GetOwningCSSAnimation()) {
352 cssAnimation->AddOverriddenProperties(CSSAnimationProperties::Keyframes);
356 void CSSAnimationKeyframeEffect::SetComposite(
357 const CompositeOperation& aComposite) {
358 KeyframeEffect::SetComposite(aComposite);
360 if (CSSAnimation* cssAnimation = GetOwningCSSAnimation()) {
361 cssAnimation->AddOverriddenProperties(CSSAnimationProperties::Composition);
365 void CSSAnimationKeyframeEffect::MaybeFlushUnanimatedStyle() const {
366 if (!GetOwningCSSAnimation()) {
367 return;
370 if (dom::Document* doc = GetRenderedDocument()) {
371 doc->FlushPendingNotifications(
372 ChangesToFlush(FlushType::Style, false /* flush animations */));
376 } // namespace mozilla::dom