1 /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "AnimationPlayer.h"
7 #include "AnimationUtils.h"
8 #include "mozilla/dom/AnimationPlayerBinding.h"
9 #include "AnimationCommon.h" // For AnimationPlayerCollection,
10 // CommonAnimationManager
11 #include "nsIDocument.h" // For nsIDocument
12 #include "nsIPresShell.h" // For nsIPresShell
13 #include "nsLayoutUtils.h" // For PostRestyleEvent (remove after bug 1073336)
14 #include "PendingPlayerTracker.h" // For PendingPlayerTracker
19 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(AnimationPlayer
, mTimeline
,
21 NS_IMPL_CYCLE_COLLECTING_ADDREF(AnimationPlayer
)
22 NS_IMPL_CYCLE_COLLECTING_RELEASE(AnimationPlayer
)
23 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AnimationPlayer
)
24 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
25 NS_INTERFACE_MAP_ENTRY(nsISupports
)
29 AnimationPlayer::WrapObject(JSContext
* aCx
)
31 return dom::AnimationPlayerBinding::Wrap(aCx
, this);
34 Nullable
<TimeDuration
>
35 AnimationPlayer::GetCurrentTime() const
37 Nullable
<TimeDuration
> result
;
38 if (!mHoldTime
.IsNull()) {
43 if (!mStartTime
.IsNull()) {
44 Nullable
<TimeDuration
> timelineTime
= mTimeline
->GetCurrentTime();
45 if (!timelineTime
.IsNull()) {
46 result
.SetValue(timelineTime
.Value() - mStartTime
.Value());
53 AnimationPlayer::PlayState() const
56 return AnimationPlayState::Pending
;
59 Nullable
<TimeDuration
> currentTime
= GetCurrentTime();
60 if (currentTime
.IsNull()) {
61 return AnimationPlayState::Idle
;
64 if (mStartTime
.IsNull()) {
65 return AnimationPlayState::Paused
;
68 if (currentTime
.Value() >= SourceContentEnd()) {
69 return AnimationPlayState::Finished
;
72 return AnimationPlayState::Running
;
76 AnimationPlayer::GetReady(ErrorResult
& aRv
)
78 // Lazily create the ready promise if it doesn't exist
80 nsIGlobalObject
* global
= mTimeline
->GetParentObject();
82 mReady
= Promise::Create(global
, aRv
);
83 if (mReady
&& PlayState() != AnimationPlayState::Pending
) {
84 mReady
->MaybeResolve(this);
89 aRv
.Throw(NS_ERROR_FAILURE
);
96 AnimationPlayer::Play()
103 AnimationPlayer::Pause()
110 AnimationPlayer::GetStartTimeAsDouble() const
112 return AnimationUtils::TimeDurationToDouble(mStartTime
);
116 AnimationPlayer::GetCurrentTimeAsDouble() const
118 return AnimationUtils::TimeDurationToDouble(GetCurrentTime());
122 AnimationPlayer::SetSource(Animation
* aSource
)
125 mSource
->SetParentTime(Nullable
<TimeDuration
>());
129 mSource
->SetParentTime(GetCurrentTime());
134 AnimationPlayer::Tick()
136 // Since we are not guaranteed to get only one call per refresh driver tick,
137 // it's possible that mPendingReadyTime is set to a time in the future.
138 // In that case, we should wait until the next refresh driver tick before
141 !mPendingReadyTime
.IsNull() &&
142 mPendingReadyTime
.Value() <= mTimeline
->GetCurrentTime().Value()) {
143 ResumeAt(mPendingReadyTime
.Value());
144 mPendingReadyTime
.SetNull();
147 if (IsPossiblyOrphanedPendingPlayer()) {
148 MOZ_ASSERT(mTimeline
&& !mTimeline
->GetCurrentTime().IsNull(),
149 "Orphaned pending players should have an active timeline");
150 ResumeAt(mTimeline
->GetCurrentTime().Value());
153 UpdateSourceContent();
157 AnimationPlayer::StartOnNextTick(const Nullable
<TimeDuration
>& aReadyTime
)
159 // Normally we expect the play state to be pending but it's possible that,
160 // due to the handling of possibly orphaned players in Tick() [coming
161 // in a later patch in this series], this player got started whilst still
162 // being in another document's pending player map.
163 if (PlayState() != AnimationPlayState::Pending
) {
167 // If aReadyTime.IsNull() we'll detect this in Tick() where we check for
168 // orphaned players and trigger this animation anyway
169 mPendingReadyTime
= aReadyTime
;
173 AnimationPlayer::StartNow()
175 MOZ_ASSERT(PlayState() == AnimationPlayState::Pending
,
176 "Expected to start a pending player");
177 MOZ_ASSERT(mTimeline
&& !mTimeline
->GetCurrentTime().IsNull(),
178 "Expected an active timeline");
180 ResumeAt(mTimeline
->GetCurrentTime().Value());
183 Nullable
<TimeDuration
>
184 AnimationPlayer::GetCurrentOrPendingStartTime() const
186 Nullable
<TimeDuration
> result
;
188 if (!mStartTime
.IsNull()) {
193 if (mPendingReadyTime
.IsNull() || mHoldTime
.IsNull()) {
197 // Calculate the equivalent start time from the pending ready time.
198 // This is the same as the calculation performed in ResumeAt and will
199 // need to incorporate the playbackRate when implemented (bug 1127380).
200 result
.SetValue(mPendingReadyTime
.Value() - mHoldTime
.Value());
205 AnimationPlayer::Cancel()
210 mReady
->MaybeReject(NS_ERROR_DOM_ABORT_ERR
);
215 mStartTime
.SetNull();
219 AnimationPlayer::IsRunning() const
221 if (IsPaused() || !GetSource() || GetSource()->IsFinishedTransition()) {
225 ComputedTiming computedTiming
= GetSource()->GetComputedTiming();
226 return computedTiming
.mPhase
== ComputedTiming::AnimationPhase_Active
;
230 AnimationPlayer::CanThrottle() const
233 mSource
->IsFinishedTransition() ||
234 mSource
->Properties().IsEmpty()) {
238 if (!mIsRunningOnCompositor
) {
242 if (PlayState() != AnimationPlayState::Finished
) {
243 // Unfinished animations can be throttled.
247 // The animation has finished but, if this is the first sample since
248 // finishing, we need an unthrottled sample so we can apply the correct
249 // end-of-animation behavior on the main thread (either removing the
250 // animation style or applying the fill mode).
251 return mIsPreviousStateFinished
;
255 AnimationPlayer::ComposeStyle(nsRefPtr
<css::AnimValuesStyleRule
>& aStyleRule
,
256 nsCSSPropertySet
& aSetProperties
,
257 bool& aNeedsRefreshes
)
259 if (!mSource
|| mSource
->IsFinishedTransition()) {
263 AnimationPlayState playState
= PlayState();
264 if (playState
== AnimationPlayState::Running
||
265 playState
== AnimationPlayState::Pending
) {
266 aNeedsRefreshes
= true;
269 mSource
->ComposeStyle(aStyleRule
, aSetProperties
);
271 mIsPreviousStateFinished
= (playState
== AnimationPlayState::Finished
);
275 AnimationPlayer::DoPlay()
277 // FIXME: When we implement finishing behavior (bug 1074630) we will
278 // need to pass a flag so that when we start playing due to a change in
279 // animation-play-state we *don't* trigger finishing behavior.
281 Nullable
<TimeDuration
> currentTime
= GetCurrentTime();
282 if (currentTime
.IsNull()) {
283 mHoldTime
.SetValue(TimeDuration(0));
284 } else if (mHoldTime
.IsNull()) {
285 // If the hold time is null, we are already playing normally
289 // Clear ready promise. We'll create a new one lazily.
294 nsIDocument
* doc
= GetRenderedDocument();
296 StartOnNextTick(Nullable
<TimeDuration
>());
300 PendingPlayerTracker
* tracker
= doc
->GetOrCreatePendingPlayerTracker();
301 tracker
->AddPlayPending(*this);
303 // We may have updated the current time when we set the hold time above
304 // so notify source content.
305 UpdateSourceContent();
309 AnimationPlayer::DoPause()
313 // Resolve the ready promise since we currently only use it for
314 // players that are waiting to play. Later (in bug 1109390), we will
315 // use this for players waiting to pause as well and then we won't
316 // want to resolve it just yet.
318 mReady
->MaybeResolve(this);
322 // Mark this as no longer running on the compositor so that next time
323 // we update animations we won't throttle them and will have a chance
324 // to remove the animation from any layer it might be on.
325 mIsRunningOnCompositor
= false;
327 // Bug 1109390 - check for null result here and go to pending state
328 mHoldTime
= GetCurrentTime();
329 mStartTime
.SetNull();
333 AnimationPlayer::ResumeAt(const TimeDuration
& aResumeTime
)
335 // This method is only expected to be called for a player that is
336 // waiting to play. We can easily adapt it to handle other states
337 // but it's currently not necessary.
338 MOZ_ASSERT(PlayState() == AnimationPlayState::Pending
,
339 "Expected to resume a pending player");
340 MOZ_ASSERT(!mHoldTime
.IsNull(),
341 "A player in the pending state should have a resolved hold time");
343 mStartTime
.SetValue(aResumeTime
- mHoldTime
.Value());
347 UpdateSourceContent();
350 mReady
->MaybeResolve(this);
355 AnimationPlayer::UpdateSourceContent()
358 mSource
->SetParentTime(GetCurrentTime());
363 AnimationPlayer::FlushStyle() const
365 nsIDocument
* doc
= GetRenderedDocument();
367 doc
->FlushPendingNotifications(Flush_Style
);
372 AnimationPlayer::PostUpdate()
374 AnimationPlayerCollection
* collection
= GetCollection();
376 collection
->NotifyPlayerUpdated();
381 AnimationPlayer::CancelPendingPlay()
387 nsIDocument
* doc
= GetRenderedDocument();
389 PendingPlayerTracker
* tracker
= doc
->GetPendingPlayerTracker();
391 tracker
->RemovePlayPending(*this);
396 mPendingReadyTime
.SetNull();
400 AnimationPlayer::IsPossiblyOrphanedPendingPlayer() const
402 // Check if we are pending but might never start because we are not being
405 // This covers the following cases:
407 // * We started playing but our source content's target element was orphaned
408 // or bound to a different document.
409 // (note that for the case of our source content changing we should handle
410 // that in SetSource)
411 // * We started playing but our timeline became inactive.
412 // In this case the pending player tracker will drop us from its hashmap
413 // when we have been painted.
414 // * When we started playing we couldn't find a PendingPlayerTracker to
415 // register with (perhaps the source content had no document) so we simply
416 // set mIsPending in DoPlay and relied on this method to catch us on the
419 // If we're not pending we're ok.
424 // If we have a pending ready time then we will be started on the next
426 if (!mPendingReadyTime
.IsNull()) {
430 // If we don't have an active timeline then we shouldn't start until
432 if (!mTimeline
|| mTimeline
->GetCurrentTime().IsNull()) {
436 // If we have no rendered document, or we're not in our rendered document's
437 // PendingPlayerTracker then there's a good chance no one is tracking us.
439 // If we're wrong and another document is tracking us then, at worst, we'll
440 // simply start the animation one tick too soon. That's better than never
441 // starting the animation and is unlikely.
442 nsIDocument
* doc
= GetRenderedDocument();
444 !doc
->GetPendingPlayerTracker() ||
445 !doc
->GetPendingPlayerTracker()->IsWaitingToPlay(*this);
449 AnimationPlayer::SourceContentEnd() const
452 return StickyTimeDuration(0);
455 return mSource
->Timing().mDelay
456 + mSource
->GetComputedTiming().mActiveDuration
;
460 AnimationPlayer::GetRenderedDocument() const
466 Element
* targetElement
;
467 nsCSSPseudoElements::Type pseudoType
;
468 mSource
->GetTarget(targetElement
, pseudoType
);
469 if (!targetElement
) {
473 return targetElement
->GetComposedDoc();
477 AnimationPlayer::GetPresContext() const
479 nsIDocument
* doc
= GetRenderedDocument();
483 nsIPresShell
* shell
= doc
->GetShell();
487 return shell
->GetPresContext();
490 AnimationPlayerCollection
*
491 AnimationPlayer::GetCollection() const
493 css::CommonAnimationManager
* manager
= GetAnimationManager();
497 MOZ_ASSERT(mSource
, "A player with an animation manager must have a source");
499 Element
* targetElement
;
500 nsCSSPseudoElements::Type targetPseudoType
;
501 mSource
->GetTarget(targetElement
, targetPseudoType
);
502 MOZ_ASSERT(targetElement
,
503 "A player with an animation manager must have a target");
505 return manager
->GetAnimationPlayers(targetElement
, targetPseudoType
, false);
509 } // namespace mozilla