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 file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "PendingAnimationTracker.h"
9 #include "mozilla/PresShell.h"
10 #include "mozilla/dom/AnimationEffect.h"
11 #include "mozilla/dom/AnimationTimeline.h"
12 #include "mozilla/dom/Nullable.h"
14 #include "nsTransitionManager.h" // For CSSTransition
16 using mozilla::dom::Nullable
;
20 NS_IMPL_CYCLE_COLLECTION(PendingAnimationTracker
, mPlayPendingSet
,
21 mPausePendingSet
, mDocument
)
23 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(PendingAnimationTracker
, AddRef
)
24 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(PendingAnimationTracker
, Release
)
26 PendingAnimationTracker::PendingAnimationTracker(dom::Document
* aDocument
)
27 : mDocument(aDocument
) {}
29 void PendingAnimationTracker::AddPending(dom::Animation
& aAnimation
,
31 aSet
.Insert(&aAnimation
);
33 // Schedule a paint. Otherwise animations that don't trigger a paint by
34 // themselves (e.g. CSS animations with an empty keyframes rule) won't
35 // start until something else paints.
36 EnsurePaintIsScheduled();
39 void PendingAnimationTracker::RemovePending(dom::Animation
& aAnimation
,
41 aSet
.Remove(&aAnimation
);
44 bool PendingAnimationTracker::IsWaiting(const dom::Animation
& aAnimation
,
45 const AnimationSet
& aSet
) const {
46 return aSet
.Contains(const_cast<dom::Animation
*>(&aAnimation
));
49 void PendingAnimationTracker::TriggerPendingAnimationsOnNextTick(
50 const TimeStamp
& aReadyTime
) {
51 auto triggerAnimationsAtReadyTime
= [aReadyTime
](
52 AnimationSet
& aAnimationSet
) {
53 for (auto iter
= aAnimationSet
.begin(), end
= aAnimationSet
.end();
54 iter
!= end
; ++iter
) {
55 dom::Animation
* animation
= *iter
;
56 dom::AnimationTimeline
* timeline
= animation
->GetTimeline();
58 // If the animation does not have a timeline, just drop it from the map.
59 // The animation will detect that it is not being tracked and will trigger
60 // itself on the next tick where it has a timeline.
62 aAnimationSet
.Remove(iter
);
66 // When the timeline's refresh driver is under test control, its values
67 // have no correspondance to wallclock times so we shouldn't try to
68 // convert aReadyTime (which is a wallclock time) to a timeline value.
69 // Instead, the animation will be started/paused when the refresh driver
70 // is next advanced since this will trigger a call to
71 // TriggerPendingAnimationsNow.
72 if (!timeline
->TracksWallclockTime()) {
76 Nullable
<TimeDuration
> readyTime
= timeline
->ToTimelineTime(aReadyTime
);
77 animation
->TriggerOnNextTick(readyTime
);
79 aAnimationSet
.Remove(iter
);
83 triggerAnimationsAtReadyTime(mPlayPendingSet
);
84 triggerAnimationsAtReadyTime(mPausePendingSet
);
86 mHasPlayPendingGeometricAnimations
=
87 mPlayPendingSet
.Count() ? CheckState::Indeterminate
: CheckState::Absent
;
90 void PendingAnimationTracker::TriggerPendingAnimationsNow() {
91 auto triggerAndClearAnimations
= [](AnimationSet
& aAnimationSet
) {
92 for (const auto& animation
: aAnimationSet
) {
93 animation
->TriggerNow();
95 aAnimationSet
.Clear();
98 triggerAndClearAnimations(mPlayPendingSet
);
99 triggerAndClearAnimations(mPausePendingSet
);
101 mHasPlayPendingGeometricAnimations
= CheckState::Absent
;
104 static bool IsTransition(const dom::Animation
& aAnimation
) {
105 const dom::CSSTransition
* transition
= aAnimation
.AsCSSTransition();
106 return transition
&& transition
->IsTiedToMarkup();
109 void PendingAnimationTracker::MarkAnimationsThatMightNeedSynchronization() {
110 // We only set mHasPlayPendingGeometricAnimations to "present" in this method
111 // and nowhere else. After setting the state to "present", if there is any
112 // change to the set of play-pending animations we will reset
113 // mHasPlayPendingGeometricAnimations to either "indeterminate" or "absent".
115 // As a result, if mHasPlayPendingGeometricAnimations is "present", we can
116 // assume that this method has already been called for the current set of
117 // play-pending animations and it is not necessary to run this method again.
119 // If mHasPlayPendingGeometricAnimations is "absent", then we can also skip
120 // the body of this method since there are no notifications to be sent.
122 // Therefore, the only case we need to be concerned about is the
123 // "indeterminate" case. For all other cases we can return early.
125 // Note that *without* this optimization, starting animations would become
126 // O(n^2) in the case where each animation is on a different element and
127 // contains a compositor-animatable property since we would end up iterating
128 // over all animations in the play-pending set for each target element.
129 if (mHasPlayPendingGeometricAnimations
!= CheckState::Indeterminate
) {
133 // We only synchronize CSS transitions with other CSS transitions (and we only
134 // synchronize non-transition animations with non-transition animations)
135 // since typically the author will not trigger both CSS animations and
136 // CSS transitions simultaneously and expect them to be synchronized.
138 // If we try to synchronize CSS transitions with non-transitions then for some
139 // content we will end up degrading performance by forcing animations to run
140 // on the main thread that really don't need to.
142 mHasPlayPendingGeometricAnimations
= CheckState::Absent
;
143 for (const auto& animation
: mPlayPendingSet
) {
144 if (animation
->GetEffect() && animation
->GetEffect()->AffectsGeometry()) {
145 mHasPlayPendingGeometricAnimations
&= ~CheckState::Absent
;
146 mHasPlayPendingGeometricAnimations
|= IsTransition(*animation
)
147 ? CheckState::TransitionsPresent
148 : CheckState::AnimationsPresent
;
150 // If we have both transitions and animations we don't need to look any
152 if (mHasPlayPendingGeometricAnimations
==
153 (CheckState::TransitionsPresent
| CheckState::AnimationsPresent
)) {
159 if (mHasPlayPendingGeometricAnimations
== CheckState::Absent
) {
163 for (const auto& animation
: mPlayPendingSet
) {
164 bool isTransition
= IsTransition(*animation
);
166 mHasPlayPendingGeometricAnimations
& CheckState::TransitionsPresent
) ||
168 mHasPlayPendingGeometricAnimations
& CheckState::AnimationsPresent
)) {
169 animation
->NotifyGeometricAnimationsStartingThisFrame();
174 void PendingAnimationTracker::EnsurePaintIsScheduled() {
179 PresShell
* presShell
= mDocument
->GetPresShell();
184 nsIFrame
* rootFrame
= presShell
->GetRootFrame();
189 rootFrame
->SchedulePaintWithoutInvalidatingObservers();
192 } // namespace mozilla