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