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/. */
6
7 #include "PendingAnimationTracker.h"
8
9 #include "mozilla/dom/AnimationTimeline.h"
10 #include "nsIFrame.h"
11 #include "nsIPresShell.h"
12
13 using namespace mozilla;
14
15 namespace mozilla {
16
NS_IMPL_CYCLE_COLLECTION(PendingAnimationTracker,mPlayPendingSet,mPausePendingSet,mDocument)17 NS_IMPL_CYCLE_COLLECTION(PendingAnimationTracker, mPlayPendingSet,
18 mPausePendingSet, mDocument)
19
20 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(PendingAnimationTracker, AddRef)
21 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(PendingAnimationTracker, Release)
22
23 void PendingAnimationTracker::AddPending(dom::Animation& aAnimation,
24 AnimationSet& aSet) {
25 aSet.PutEntry(&aAnimation);
26
27 // Schedule a paint. Otherwise animations that don't trigger a paint by
28 // themselves (e.g. CSS animations with an empty keyframes rule) won't
29 // start until something else paints.
30 EnsurePaintIsScheduled();
31 }
32
RemovePending(dom::Animation & aAnimation,AnimationSet & aSet)33 void PendingAnimationTracker::RemovePending(dom::Animation& aAnimation,
34 AnimationSet& aSet) {
35 aSet.RemoveEntry(&aAnimation);
36 }
37
IsWaiting(const dom::Animation & aAnimation,const AnimationSet & aSet) const38 bool PendingAnimationTracker::IsWaiting(const dom::Animation& aAnimation,
39 const AnimationSet& aSet) const {
40 return aSet.Contains(const_cast<dom::Animation*>(&aAnimation));
41 }
42
TriggerPendingAnimationsOnNextTick(const TimeStamp & aReadyTime)43 void PendingAnimationTracker::TriggerPendingAnimationsOnNextTick(
44 const TimeStamp& aReadyTime) {
45 auto triggerAnimationsAtReadyTime = [aReadyTime](
46 AnimationSet& aAnimationSet) {
47 for (auto iter = aAnimationSet.Iter(); !iter.Done(); iter.Next()) {
48 dom::Animation* animation = iter.Get()->GetKey();
49 dom::AnimationTimeline* timeline = animation->GetTimeline();
50
51 // If the animation does not have a timeline, just drop it from the map.
52 // The animation will detect that it is not being tracked and will trigger
53 // itself on the next tick where it has a timeline.
54 if (!timeline) {
55 iter.Remove();
56 continue;
57 }
58
59 // When the timeline's refresh driver is under test control, its values
60 // have no correspondance to wallclock times so we shouldn't try to
61 // convert aReadyTime (which is a wallclock time) to a timeline value.
62 // Instead, the animation will be started/paused when the refresh driver
63 // is next advanced since this will trigger a call to
64 // TriggerPendingAnimationsNow.
65 if (!timeline->TracksWallclockTime()) {
66 continue;
67 }
68
69 Nullable<TimeDuration> readyTime = timeline->ToTimelineTime(aReadyTime);
70 animation->TriggerOnNextTick(readyTime);
71
72 iter.Remove();
73 }
74 };
75
76 triggerAnimationsAtReadyTime(mPlayPendingSet);
77 triggerAnimationsAtReadyTime(mPausePendingSet);
78
79 mHasPlayPendingGeometricAnimations =
80 mPlayPendingSet.Count() ? CheckState::Indeterminate : CheckState::Absent;
81 }
82
TriggerPendingAnimationsNow()83 void PendingAnimationTracker::TriggerPendingAnimationsNow() {
84 auto triggerAndClearAnimations = [](AnimationSet& aAnimationSet) {
85 for (auto iter = aAnimationSet.Iter(); !iter.Done(); iter.Next()) {
86 iter.Get()->GetKey()->TriggerNow();
87 }
88 aAnimationSet.Clear();
89 };
90
91 triggerAndClearAnimations(mPlayPendingSet);
92 triggerAndClearAnimations(mPausePendingSet);
93
94 mHasPlayPendingGeometricAnimations = CheckState::Absent;
95 }
96
MarkAnimationsThatMightNeedSynchronization()97 void PendingAnimationTracker::MarkAnimationsThatMightNeedSynchronization() {
98 // We only ever set mHasPlayPendingGeometricAnimations to 'present' in
99 // HasPlayPendingGeometricAnimations(). So, if it is 'present' already,
100 // (i.e. before calling HasPlayPendingGeometricAnimations()) we can assume
101 // that this method has already been called for the current set of
102 // play-pending animations and it is not necessary to run again.
103 //
104 // We can't make the same assumption about 'absent', but if this method
105 // was already called and the result was 'absent', then this method is
106 // a no-op anyway so it's ok to run again.
107 //
108 // Note that *without* this optimization, starting animations would become
109 // O(n^2) in that case where each animation is on a different element and
110 // contains a compositor-animatable property since we would end up iterating
111 // over all animations in the play-pending set for each target element.
112 if (mHasPlayPendingGeometricAnimations == CheckState::Present) {
113 return;
114 }
115
116 if (!HasPlayPendingGeometricAnimations()) {
117 return;
118 }
119
120 for (auto iter = mPlayPendingSet.Iter(); !iter.Done(); iter.Next()) {
121 iter.Get()->GetKey()->NotifyGeometricAnimationsStartingThisFrame();
122 }
123 }
124
HasPlayPendingGeometricAnimations()125 bool PendingAnimationTracker::HasPlayPendingGeometricAnimations() {
126 if (mHasPlayPendingGeometricAnimations != CheckState::Indeterminate) {
127 return mHasPlayPendingGeometricAnimations == CheckState::Present;
128 }
129
130 mHasPlayPendingGeometricAnimations = CheckState::Absent;
131 for (auto iter = mPlayPendingSet.ConstIter(); !iter.Done(); iter.Next()) {
132 auto animation = iter.Get()->GetKey();
133 if (animation->GetEffect() && animation->GetEffect()->AffectsGeometry()) {
134 mHasPlayPendingGeometricAnimations = CheckState::Present;
135 break;
136 }
137 }
138
139 return mHasPlayPendingGeometricAnimations == CheckState::Present;
140 }
141
EnsurePaintIsScheduled()142 void PendingAnimationTracker::EnsurePaintIsScheduled() {
143 if (!mDocument) {
144 return;
145 }
146
147 nsIPresShell* presShell = mDocument->GetShell();
148 if (!presShell) {
149 return;
150 }
151
152 nsIFrame* rootFrame = presShell->GetRootFrame();
153 if (!rootFrame) {
154 return;
155 }
156
157 rootFrame->SchedulePaintWithoutInvalidatingObservers();
158 }
159
160 } // namespace mozilla
161