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