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