Bumping manifests a=b2g-bump
[gecko.git] / dom / animation / AnimationPlayer.cpp
blob0c8a7dff1e365778fa9a92fa98a4c0a730673e85
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
16 namespace mozilla {
17 namespace dom {
19 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(AnimationPlayer, mTimeline,
20 mSource, mReady)
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)
26 NS_INTERFACE_MAP_END
28 JSObject*
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()) {
39 result = mHoldTime;
40 return result;
43 if (!mStartTime.IsNull()) {
44 Nullable<TimeDuration> timelineTime = mTimeline->GetCurrentTime();
45 if (!timelineTime.IsNull()) {
46 result.SetValue(timelineTime.Value() - mStartTime.Value());
49 return result;
52 AnimationPlayState
53 AnimationPlayer::PlayState() const
55 if (mIsPending) {
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;
75 Promise*
76 AnimationPlayer::GetReady(ErrorResult& aRv)
78 // Lazily create the ready promise if it doesn't exist
79 if (!mReady) {
80 nsIGlobalObject* global = mTimeline->GetParentObject();
81 if (global) {
82 mReady = Promise::Create(global, aRv);
83 if (mReady && PlayState() != AnimationPlayState::Pending) {
84 mReady->MaybeResolve(this);
88 if (!mReady) {
89 aRv.Throw(NS_ERROR_FAILURE);
92 return mReady;
95 void
96 AnimationPlayer::Play()
98 DoPlay();
99 PostUpdate();
102 void
103 AnimationPlayer::Pause()
105 DoPause();
106 PostUpdate();
109 Nullable<double>
110 AnimationPlayer::GetStartTimeAsDouble() const
112 return AnimationUtils::TimeDurationToDouble(mStartTime);
115 Nullable<double>
116 AnimationPlayer::GetCurrentTimeAsDouble() const
118 return AnimationUtils::TimeDurationToDouble(GetCurrentTime());
121 void
122 AnimationPlayer::SetSource(Animation* aSource)
124 if (mSource) {
125 mSource->SetParentTime(Nullable<TimeDuration>());
127 mSource = aSource;
128 if (mSource) {
129 mSource->SetParentTime(GetCurrentTime());
133 void
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
139 // resuming.
140 if (mIsPending &&
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();
156 void
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) {
164 return;
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;
172 void
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()) {
189 result = mStartTime;
190 return result;
193 if (mPendingReadyTime.IsNull() || mHoldTime.IsNull()) {
194 return result;
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());
201 return result;
204 void
205 AnimationPlayer::Cancel()
207 if (mIsPending) {
208 CancelPendingPlay();
209 if (mReady) {
210 mReady->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
214 mHoldTime.SetNull();
215 mStartTime.SetNull();
218 bool
219 AnimationPlayer::IsRunning() const
221 if (IsPaused() || !GetSource() || GetSource()->IsFinishedTransition()) {
222 return false;
225 ComputedTiming computedTiming = GetSource()->GetComputedTiming();
226 return computedTiming.mPhase == ComputedTiming::AnimationPhase_Active;
229 bool
230 AnimationPlayer::CanThrottle() const
232 if (!mSource ||
233 mSource->IsFinishedTransition() ||
234 mSource->Properties().IsEmpty()) {
235 return true;
238 if (!mIsRunningOnCompositor) {
239 return false;
242 if (PlayState() != AnimationPlayState::Finished) {
243 // Unfinished animations can be throttled.
244 return true;
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;
254 void
255 AnimationPlayer::ComposeStyle(nsRefPtr<css::AnimValuesStyleRule>& aStyleRule,
256 nsCSSPropertySet& aSetProperties,
257 bool& aNeedsRefreshes)
259 if (!mSource || mSource->IsFinishedTransition()) {
260 return;
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);
274 void
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
286 return;
289 // Clear ready promise. We'll create a new one lazily.
290 mReady = nullptr;
292 mIsPending = true;
294 nsIDocument* doc = GetRenderedDocument();
295 if (!doc) {
296 StartOnNextTick(Nullable<TimeDuration>());
297 return;
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();
308 void
309 AnimationPlayer::DoPause()
311 if (mIsPending) {
312 CancelPendingPlay();
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.
317 if (mReady) {
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();
332 void
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());
344 mHoldTime.SetNull();
345 mIsPending = false;
347 UpdateSourceContent();
349 if (mReady) {
350 mReady->MaybeResolve(this);
354 void
355 AnimationPlayer::UpdateSourceContent()
357 if (mSource) {
358 mSource->SetParentTime(GetCurrentTime());
362 void
363 AnimationPlayer::FlushStyle() const
365 nsIDocument* doc = GetRenderedDocument();
366 if (doc) {
367 doc->FlushPendingNotifications(Flush_Style);
371 void
372 AnimationPlayer::PostUpdate()
374 AnimationPlayerCollection* collection = GetCollection();
375 if (collection) {
376 collection->NotifyPlayerUpdated();
380 void
381 AnimationPlayer::CancelPendingPlay()
383 if (!mIsPending) {
384 return;
387 nsIDocument* doc = GetRenderedDocument();
388 if (doc) {
389 PendingPlayerTracker* tracker = doc->GetPendingPlayerTracker();
390 if (tracker) {
391 tracker->RemovePlayPending(*this);
395 mIsPending = false;
396 mPendingReadyTime.SetNull();
399 bool
400 AnimationPlayer::IsPossiblyOrphanedPendingPlayer() const
402 // Check if we are pending but might never start because we are not being
403 // tracked.
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
417 // next tick.
419 // If we're not pending we're ok.
420 if (!mIsPending) {
421 return false;
424 // If we have a pending ready time then we will be started on the next
425 // tick.
426 if (!mPendingReadyTime.IsNull()) {
427 return false;
430 // If we don't have an active timeline then we shouldn't start until
431 // we do.
432 if (!mTimeline || mTimeline->GetCurrentTime().IsNull()) {
433 return false;
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();
443 return !doc ||
444 !doc->GetPendingPlayerTracker() ||
445 !doc->GetPendingPlayerTracker()->IsWaitingToPlay(*this);
448 StickyTimeDuration
449 AnimationPlayer::SourceContentEnd() const
451 if (!mSource) {
452 return StickyTimeDuration(0);
455 return mSource->Timing().mDelay
456 + mSource->GetComputedTiming().mActiveDuration;
459 nsIDocument*
460 AnimationPlayer::GetRenderedDocument() const
462 if (!mSource) {
463 return nullptr;
466 Element* targetElement;
467 nsCSSPseudoElements::Type pseudoType;
468 mSource->GetTarget(targetElement, pseudoType);
469 if (!targetElement) {
470 return nullptr;
473 return targetElement->GetComposedDoc();
476 nsPresContext*
477 AnimationPlayer::GetPresContext() const
479 nsIDocument* doc = GetRenderedDocument();
480 if (!doc) {
481 return nullptr;
483 nsIPresShell* shell = doc->GetShell();
484 if (!shell) {
485 return nullptr;
487 return shell->GetPresContext();
490 AnimationPlayerCollection*
491 AnimationPlayer::GetCollection() const
493 css::CommonAnimationManager* manager = GetAnimationManager();
494 if (!manager) {
495 return nullptr;
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);
508 } // namespace dom
509 } // namespace mozilla