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 "DocumentTimeline.h"
8 #include "mozilla/dom/DocumentInlines.h"
9 #include "mozilla/dom/DocumentTimelineBinding.h"
10 #include "AnimationUtils.h"
11 #include "nsContentUtils.h"
12 #include "nsDOMMutationObserver.h"
13 #include "nsDOMNavigationTiming.h"
14 #include "nsPresContext.h"
15 #include "nsRefreshDriver.h"
16 
17 namespace mozilla::dom {
18 
19 NS_IMPL_CYCLE_COLLECTION_CLASS(DocumentTimeline)
20 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocumentTimeline,
21                                                 AnimationTimeline)
22   tmp->UnregisterFromRefreshDriver();
23   if (tmp->isInList()) {
24     tmp->remove();
25   }
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)26   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
27 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
28 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DocumentTimeline,
29                                                   AnimationTimeline)
30   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
31 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
32 
33 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(DocumentTimeline,
34                                                AnimationTimeline)
35 NS_IMPL_CYCLE_COLLECTION_TRACE_END
36 
37 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DocumentTimeline)
38 NS_INTERFACE_MAP_END_INHERITING(AnimationTimeline)
39 
40 NS_IMPL_ADDREF_INHERITED(DocumentTimeline, AnimationTimeline)
41 NS_IMPL_RELEASE_INHERITED(DocumentTimeline, AnimationTimeline)
42 
43 DocumentTimeline::DocumentTimeline(Document* aDocument,
44                                    const TimeDuration& aOriginTime)
45     : AnimationTimeline(aDocument->GetParentObject()),
46       mDocument(aDocument),
47       mIsObservingRefreshDriver(false),
48       mOriginTime(aOriginTime) {
49   if (mDocument) {
50     mDocument->Timelines().insertBack(this);
51   }
52 }
53 
~DocumentTimeline()54 DocumentTimeline::~DocumentTimeline() {
55   MOZ_ASSERT(!mIsObservingRefreshDriver,
56              "Timeline should have disassociated"
57              " from the refresh driver before being destroyed");
58   if (isInList()) {
59     remove();
60   }
61 }
62 
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)63 JSObject* DocumentTimeline::WrapObject(JSContext* aCx,
64                                        JS::Handle<JSObject*> aGivenProto) {
65   return DocumentTimeline_Binding::Wrap(aCx, this, aGivenProto);
66 }
67 
68 /* static */
Constructor(const GlobalObject & aGlobal,const DocumentTimelineOptions & aOptions,ErrorResult & aRv)69 already_AddRefed<DocumentTimeline> DocumentTimeline::Constructor(
70     const GlobalObject& aGlobal, const DocumentTimelineOptions& aOptions,
71     ErrorResult& aRv) {
72   Document* doc = AnimationUtils::GetCurrentRealmDocument(aGlobal.Context());
73   if (!doc) {
74     aRv.Throw(NS_ERROR_FAILURE);
75     return nullptr;
76   }
77   TimeDuration originTime =
78       TimeDuration::FromMilliseconds(aOptions.mOriginTime);
79 
80   if (originTime == TimeDuration::Forever() ||
81       originTime == -TimeDuration::Forever()) {
82     aRv.ThrowTypeError<dom::MSG_TIME_VALUE_OUT_OF_RANGE>("Origin time");
83     return nullptr;
84   }
85   RefPtr<DocumentTimeline> timeline = new DocumentTimeline(doc, originTime);
86 
87   return timeline.forget();
88 }
89 
GetCurrentTimeAsDuration() const90 Nullable<TimeDuration> DocumentTimeline::GetCurrentTimeAsDuration() const {
91   return ToTimelineTime(GetCurrentTimeStamp());
92 }
93 
TracksWallclockTime() const94 bool DocumentTimeline::TracksWallclockTime() const {
95   nsRefreshDriver* refreshDriver = GetRefreshDriver();
96   return !refreshDriver || !refreshDriver->IsTestControllingRefreshesEnabled();
97 }
98 
GetCurrentTimeStamp() const99 TimeStamp DocumentTimeline::GetCurrentTimeStamp() const {
100   nsRefreshDriver* refreshDriver = GetRefreshDriver();
101   TimeStamp refreshTime =
102       refreshDriver ? refreshDriver->MostRecentRefresh() : TimeStamp();
103 
104   // Always return the same object to benefit from return-value optimization.
105   TimeStamp result =
106       !refreshTime.IsNull() ? refreshTime : mLastRefreshDriverTime;
107 
108   nsDOMNavigationTiming* timing = mDocument->GetNavigationTiming();
109   // If we don't have a refresh driver and we've never had one use the
110   // timeline's zero time.
111   // In addition, it's possible that our refresh driver's timestamp is behind
112   // from the navigation start time because the refresh driver timestamp is
113   // sent through an IPC call whereas the navigation time is set by calling
114   // TimeStamp::Now() directly. In such cases we also use the timeline's zero
115   // time.
116   if (timing &&
117       (result.IsNull() || result < timing->GetNavigationStartTimeStamp())) {
118     result = timing->GetNavigationStartTimeStamp();
119     // Also, let this time represent the current refresh time. This way
120     // we'll save it as the last refresh time and skip looking up
121     // navigation start time each time.
122     refreshTime = result;
123   }
124 
125   if (!refreshTime.IsNull()) {
126     mLastRefreshDriverTime = refreshTime;
127   }
128 
129   return result;
130 }
131 
ToTimelineTime(const TimeStamp & aTimeStamp) const132 Nullable<TimeDuration> DocumentTimeline::ToTimelineTime(
133     const TimeStamp& aTimeStamp) const {
134   Nullable<TimeDuration> result;  // Initializes to null
135   if (aTimeStamp.IsNull()) {
136     return result;
137   }
138 
139   nsDOMNavigationTiming* timing = mDocument->GetNavigationTiming();
140   if (MOZ_UNLIKELY(!timing)) {
141     return result;
142   }
143 
144   result.SetValue(aTimeStamp - timing->GetNavigationStartTimeStamp() -
145                   mOriginTime);
146   return result;
147 }
148 
NotifyAnimationUpdated(Animation & aAnimation)149 void DocumentTimeline::NotifyAnimationUpdated(Animation& aAnimation) {
150   AnimationTimeline::NotifyAnimationUpdated(aAnimation);
151 
152   if (!mIsObservingRefreshDriver) {
153     nsRefreshDriver* refreshDriver = GetRefreshDriver();
154     if (refreshDriver) {
155       MOZ_ASSERT(isInList(),
156                  "We should not register with the refresh driver if we are not"
157                  " in the document's list of timelines");
158 
159       ObserveRefreshDriver(refreshDriver);
160     }
161   }
162 }
163 
MostRecentRefreshTimeUpdated()164 void DocumentTimeline::MostRecentRefreshTimeUpdated() {
165   MOZ_ASSERT(mIsObservingRefreshDriver);
166   MOZ_ASSERT(GetRefreshDriver(),
167              "Should be able to reach refresh driver from within WillRefresh");
168 
169   bool needsTicks = false;
170   nsTArray<Animation*> animationsToRemove(mAnimations.Count());
171 
172   nsAutoAnimationMutationBatch mb(mDocument);
173 
174   for (Animation* animation = mAnimationOrder.getFirst(); animation;
175        animation =
176            static_cast<LinkedListElement<Animation>*>(animation)->getNext()) {
177     // Skip any animations that are longer need associated with this timeline.
178     if (animation->GetTimeline() != this) {
179       // If animation has some other timeline, it better not be also in the
180       // animation list of this timeline object!
181       MOZ_ASSERT(!animation->GetTimeline());
182       animationsToRemove.AppendElement(animation);
183       continue;
184     }
185 
186     needsTicks |= animation->NeedsTicks();
187     // Even if |animation| doesn't need future ticks, we should still
188     // Tick it this time around since it might just need a one-off tick in
189     // order to dispatch events.
190     animation->Tick();
191 
192     if (!animation->NeedsTicks()) {
193       animationsToRemove.AppendElement(animation);
194     }
195   }
196 
197   for (Animation* animation : animationsToRemove) {
198     RemoveAnimation(animation);
199   }
200 
201   if (!needsTicks) {
202     // We already assert that GetRefreshDriver() is non-null at the beginning
203     // of this function but we check it again here to be sure that ticking
204     // animations does not have any side effects that cause us to lose the
205     // connection with the refresh driver, such as triggering the destruction
206     // of mDocument's PresShell.
207     MOZ_ASSERT(GetRefreshDriver(),
208                "Refresh driver should still be valid at end of WillRefresh");
209     UnregisterFromRefreshDriver();
210   }
211 }
212 
WillRefresh(mozilla::TimeStamp aTime)213 void DocumentTimeline::WillRefresh(mozilla::TimeStamp aTime) {
214   MostRecentRefreshTimeUpdated();
215 }
216 
NotifyTimerAdjusted(TimeStamp aTime)217 void DocumentTimeline::NotifyTimerAdjusted(TimeStamp aTime) {
218   MostRecentRefreshTimeUpdated();
219 }
220 
ObserveRefreshDriver(nsRefreshDriver * aDriver)221 void DocumentTimeline::ObserveRefreshDriver(nsRefreshDriver* aDriver) {
222   MOZ_ASSERT(!mIsObservingRefreshDriver);
223   // Set the mIsObservingRefreshDriver flag before calling AddRefreshObserver
224   // since it might end up calling NotifyTimerAdjusted which calls
225   // MostRecentRefreshTimeUpdated which has an assertion for
226   // mIsObserveingRefreshDriver check.
227   mIsObservingRefreshDriver = true;
228   aDriver->AddRefreshObserver(this, FlushType::Style,
229                               "DocumentTimeline animations");
230   aDriver->AddTimerAdjustmentObserver(this);
231 }
232 
NotifyRefreshDriverCreated(nsRefreshDriver * aDriver)233 void DocumentTimeline::NotifyRefreshDriverCreated(nsRefreshDriver* aDriver) {
234   MOZ_ASSERT(!mIsObservingRefreshDriver,
235              "Timeline should not be observing the refresh driver before"
236              " it is created");
237 
238   if (!mAnimationOrder.isEmpty()) {
239     MOZ_ASSERT(isInList(),
240                "We should not register with the refresh driver if we are not"
241                " in the document's list of timelines");
242     ObserveRefreshDriver(aDriver);
243     // Although we have started observing the refresh driver, it's possible we
244     // could perform a paint before the first refresh driver tick happens.  To
245     // ensure we're in a consistent state in that case we run the first tick
246     // manually.
247     MostRecentRefreshTimeUpdated();
248   }
249 }
250 
DisconnectRefreshDriver(nsRefreshDriver * aDriver)251 void DocumentTimeline::DisconnectRefreshDriver(nsRefreshDriver* aDriver) {
252   MOZ_ASSERT(mIsObservingRefreshDriver);
253 
254   aDriver->RemoveRefreshObserver(this, FlushType::Style);
255   aDriver->RemoveTimerAdjustmentObserver(this);
256   mIsObservingRefreshDriver = false;
257 }
258 
NotifyRefreshDriverDestroying(nsRefreshDriver * aDriver)259 void DocumentTimeline::NotifyRefreshDriverDestroying(nsRefreshDriver* aDriver) {
260   if (!mIsObservingRefreshDriver) {
261     return;
262   }
263 
264   DisconnectRefreshDriver(aDriver);
265 }
266 
RemoveAnimation(Animation * aAnimation)267 void DocumentTimeline::RemoveAnimation(Animation* aAnimation) {
268   AnimationTimeline::RemoveAnimation(aAnimation);
269 
270   if (mIsObservingRefreshDriver && mAnimations.IsEmpty()) {
271     UnregisterFromRefreshDriver();
272   }
273 }
274 
ToTimeStamp(const TimeDuration & aTimeDuration) const275 TimeStamp DocumentTimeline::ToTimeStamp(
276     const TimeDuration& aTimeDuration) const {
277   TimeStamp result;
278   nsDOMNavigationTiming* timing = mDocument->GetNavigationTiming();
279   if (MOZ_UNLIKELY(!timing)) {
280     return result;
281   }
282 
283   result =
284       timing->GetNavigationStartTimeStamp() + (aTimeDuration + mOriginTime);
285   return result;
286 }
287 
GetRefreshDriver() const288 nsRefreshDriver* DocumentTimeline::GetRefreshDriver() const {
289   nsPresContext* presContext = mDocument->GetPresContext();
290   if (MOZ_UNLIKELY(!presContext)) {
291     return nullptr;
292   }
293 
294   return presContext->RefreshDriver();
295 }
296 
UnregisterFromRefreshDriver()297 void DocumentTimeline::UnregisterFromRefreshDriver() {
298   if (!mIsObservingRefreshDriver) {
299     return;
300   }
301 
302   nsRefreshDriver* refreshDriver = GetRefreshDriver();
303   if (!refreshDriver) {
304     return;
305   }
306   DisconnectRefreshDriver(refreshDriver);
307 }
308 
309 }  // namespace mozilla::dom
310