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/. */
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"
17 namespace mozilla::dom
{
19 NS_IMPL_CYCLE_COLLECTION_CLASS(DocumentTimeline
)
20 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocumentTimeline
,
22 tmp
->UnregisterFromRefreshDriver();
23 if (tmp
->isInList()) {
26 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument
)
27 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
28 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DocumentTimeline
,
30 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument
)
31 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
33 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(DocumentTimeline
,
35 NS_IMPL_CYCLE_COLLECTION_TRACE_END
37 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DocumentTimeline
)
38 NS_INTERFACE_MAP_END_INHERITING(AnimationTimeline
)
40 NS_IMPL_ADDREF_INHERITED(DocumentTimeline
, AnimationTimeline
)
41 NS_IMPL_RELEASE_INHERITED(DocumentTimeline
, AnimationTimeline
)
43 DocumentTimeline::DocumentTimeline(Document
* aDocument
,
44 const TimeDuration
& aOriginTime
)
45 : AnimationTimeline(aDocument
->GetParentObject(),
46 aDocument
->GetScopeObject()->GetRTPCallerType()),
48 mIsObservingRefreshDriver(false),
49 mOriginTime(aOriginTime
) {
51 mDocument
->Timelines().insertBack(this);
53 // Ensure mLastRefreshDriverTime is valid.
54 UpdateLastRefreshDriverTime();
57 DocumentTimeline::~DocumentTimeline() {
58 MOZ_ASSERT(!mIsObservingRefreshDriver
,
59 "Timeline should have disassociated"
60 " from the refresh driver before being destroyed");
66 JSObject
* DocumentTimeline::WrapObject(JSContext
* aCx
,
67 JS::Handle
<JSObject
*> aGivenProto
) {
68 return DocumentTimeline_Binding::Wrap(aCx
, this, aGivenProto
);
72 already_AddRefed
<DocumentTimeline
> DocumentTimeline::Constructor(
73 const GlobalObject
& aGlobal
, const DocumentTimelineOptions
& aOptions
,
75 Document
* doc
= AnimationUtils::GetCurrentRealmDocument(aGlobal
.Context());
77 aRv
.Throw(NS_ERROR_FAILURE
);
80 TimeDuration originTime
=
81 TimeDuration::FromMilliseconds(aOptions
.mOriginTime
);
83 if (originTime
== TimeDuration::Forever() ||
84 originTime
== -TimeDuration::Forever()) {
85 aRv
.ThrowTypeError
<dom::MSG_TIME_VALUE_OUT_OF_RANGE
>("Origin time");
88 RefPtr
<DocumentTimeline
> timeline
= new DocumentTimeline(doc
, originTime
);
90 return timeline
.forget();
93 Nullable
<TimeDuration
> DocumentTimeline::GetCurrentTimeAsDuration() const {
94 return ToTimelineTime(GetCurrentTimeStamp());
97 bool DocumentTimeline::TracksWallclockTime() const {
98 nsRefreshDriver
* refreshDriver
= GetRefreshDriver();
99 return !refreshDriver
|| !refreshDriver
->IsTestControllingRefreshesEnabled();
102 TimeStamp
DocumentTimeline::GetCurrentTimeStamp() const {
103 nsRefreshDriver
* refreshDriver
= GetRefreshDriver();
104 return refreshDriver
? refreshDriver
->MostRecentRefresh()
105 : mLastRefreshDriverTime
;
108 void DocumentTimeline::UpdateLastRefreshDriverTime() {
109 nsRefreshDriver
* refreshDriver
= GetRefreshDriver();
110 TimeStamp refreshTime
=
111 refreshDriver
? refreshDriver
->MostRecentRefresh() : TimeStamp();
113 // Always return the same object to benefit from return-value optimization.
115 !refreshTime
.IsNull() ? refreshTime
: mLastRefreshDriverTime
;
117 nsDOMNavigationTiming
* timing
= mDocument
->GetNavigationTiming();
118 // If we don't have a refresh driver and we've never had one use the
119 // timeline's zero time.
120 // In addition, it's possible that our refresh driver's timestamp is behind
121 // from the navigation start time because the refresh driver timestamp is
122 // sent through an IPC call whereas the navigation time is set by calling
123 // TimeStamp::Now() directly. In such cases we also use the timeline's zero
126 (result
.IsNull() || result
< timing
->GetNavigationStartTimeStamp())) {
127 result
= timing
->GetNavigationStartTimeStamp();
128 // Also, let this time represent the current refresh time. This way
129 // we'll save it as the last refresh time and skip looking up
130 // navigation start time each time.
131 refreshTime
= result
;
134 if (!refreshTime
.IsNull()) {
135 mLastRefreshDriverTime
= refreshTime
;
139 Nullable
<TimeDuration
> DocumentTimeline::ToTimelineTime(
140 const TimeStamp
& aTimeStamp
) const {
141 Nullable
<TimeDuration
> result
; // Initializes to null
142 if (aTimeStamp
.IsNull()) {
146 nsDOMNavigationTiming
* timing
= mDocument
->GetNavigationTiming();
147 if (MOZ_UNLIKELY(!timing
)) {
151 result
.SetValue(aTimeStamp
- timing
->GetNavigationStartTimeStamp() -
156 void DocumentTimeline::NotifyAnimationUpdated(Animation
& aAnimation
) {
157 AnimationTimeline::NotifyAnimationUpdated(aAnimation
);
159 if (!mIsObservingRefreshDriver
&& !mAnimationOrder
.isEmpty()) {
160 nsRefreshDriver
* refreshDriver
= GetRefreshDriver();
162 MOZ_ASSERT(isInList(),
163 "We should not register with the refresh driver if we are not"
164 " in the document's list of timelines");
166 ObserveRefreshDriver(refreshDriver
);
171 void DocumentTimeline::MostRecentRefreshTimeUpdated() {
172 MOZ_ASSERT(mIsObservingRefreshDriver
);
173 MOZ_ASSERT(GetRefreshDriver(),
174 "Should be able to reach refresh driver from within WillRefresh");
176 nsAutoAnimationMutationBatch
mb(mDocument
);
178 bool ticked
= Tick();
180 // We already assert that GetRefreshDriver() is non-null at the beginning
181 // of this function but we check it again here to be sure that ticking
182 // animations does not have any side effects that cause us to lose the
183 // connection with the refresh driver, such as triggering the destruction
184 // of mDocument's PresShell.
185 MOZ_ASSERT(GetRefreshDriver(),
186 "Refresh driver should still be valid at end of WillRefresh");
187 UnregisterFromRefreshDriver();
191 void DocumentTimeline::WillRefresh(mozilla::TimeStamp aTime
) {
192 UpdateLastRefreshDriverTime();
193 MostRecentRefreshTimeUpdated();
196 void DocumentTimeline::NotifyTimerAdjusted(TimeStamp aTime
) {
197 MostRecentRefreshTimeUpdated();
200 void DocumentTimeline::ObserveRefreshDriver(nsRefreshDriver
* aDriver
) {
201 MOZ_ASSERT(!mIsObservingRefreshDriver
);
202 // Set the mIsObservingRefreshDriver flag before calling AddRefreshObserver
203 // since it might end up calling NotifyTimerAdjusted which calls
204 // MostRecentRefreshTimeUpdated which has an assertion for
205 // mIsObserveingRefreshDriver check.
206 mIsObservingRefreshDriver
= true;
207 aDriver
->AddRefreshObserver(this, FlushType::Style
,
208 "DocumentTimeline animations");
209 aDriver
->AddTimerAdjustmentObserver(this);
212 void DocumentTimeline::NotifyRefreshDriverCreated(nsRefreshDriver
* aDriver
) {
213 MOZ_ASSERT(!mIsObservingRefreshDriver
,
214 "Timeline should not be observing the refresh driver before"
217 if (!mAnimationOrder
.isEmpty()) {
218 MOZ_ASSERT(isInList(),
219 "We should not register with the refresh driver if we are not"
220 " in the document's list of timelines");
221 ObserveRefreshDriver(aDriver
);
222 // Although we have started observing the refresh driver, it's possible we
223 // could perform a paint before the first refresh driver tick happens. To
224 // ensure we're in a consistent state in that case we run the first tick
226 MostRecentRefreshTimeUpdated();
230 void DocumentTimeline::DisconnectRefreshDriver(nsRefreshDriver
* aDriver
) {
231 MOZ_ASSERT(mIsObservingRefreshDriver
);
233 aDriver
->RemoveRefreshObserver(this, FlushType::Style
);
234 aDriver
->RemoveTimerAdjustmentObserver(this);
235 mIsObservingRefreshDriver
= false;
238 void DocumentTimeline::NotifyRefreshDriverDestroying(nsRefreshDriver
* aDriver
) {
239 if (!mIsObservingRefreshDriver
) {
243 DisconnectRefreshDriver(aDriver
);
246 void DocumentTimeline::RemoveAnimation(Animation
* aAnimation
) {
247 AnimationTimeline::RemoveAnimation(aAnimation
);
249 if (!mIsObservingRefreshDriver
|| !mAnimationOrder
.isEmpty()) {
253 UnregisterFromRefreshDriver();
256 void DocumentTimeline::NotifyAnimationContentVisibilityChanged(
257 Animation
* aAnimation
, bool aIsVisible
) {
258 AnimationTimeline::NotifyAnimationContentVisibilityChanged(aAnimation
,
261 if (mIsObservingRefreshDriver
&& mAnimationOrder
.isEmpty()) {
262 UnregisterFromRefreshDriver();
265 if (!mIsObservingRefreshDriver
&& !mAnimationOrder
.isEmpty()) {
266 nsRefreshDriver
* refreshDriver
= GetRefreshDriver();
268 MOZ_ASSERT(isInList(),
269 "We should not register with the refresh driver if we are not"
270 " in the document's list of timelines");
272 ObserveRefreshDriver(refreshDriver
);
277 TimeStamp
DocumentTimeline::ToTimeStamp(
278 const TimeDuration
& aTimeDuration
) const {
280 nsDOMNavigationTiming
* timing
= mDocument
->GetNavigationTiming();
281 if (MOZ_UNLIKELY(!timing
)) {
286 timing
->GetNavigationStartTimeStamp() + (aTimeDuration
+ mOriginTime
);
290 nsRefreshDriver
* DocumentTimeline::GetRefreshDriver() const {
291 nsPresContext
* presContext
= mDocument
->GetPresContext();
292 if (MOZ_UNLIKELY(!presContext
)) {
296 return presContext
->RefreshDriver();
299 void DocumentTimeline::UnregisterFromRefreshDriver() {
300 if (!mIsObservingRefreshDriver
) {
304 nsRefreshDriver
* refreshDriver
= GetRefreshDriver();
305 if (!refreshDriver
) {
308 DisconnectRefreshDriver(refreshDriver
);
311 } // namespace mozilla::dom