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_RELEASE_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(TimeStamp aKnownTime
) {
109 TimeStamp result
= [&] {
110 if (!aKnownTime
.IsNull()) {
113 if (auto* rd
= GetRefreshDriver()) {
114 return rd
->MostRecentRefresh();
116 return mLastRefreshDriverTime
;
119 if (nsDOMNavigationTiming
* timing
= mDocument
->GetNavigationTiming()) {
120 // If we don't have a refresh driver and we've never had one use the
121 // timeline's zero time.
122 // In addition, it's possible that our refresh driver's timestamp is behind
123 // from the navigation start time because the refresh driver timestamp is
124 // sent through an IPC call whereas the navigation time is set by calling
125 // TimeStamp::Now() directly. In such cases we also use the timeline's zero
127 // Also, let this time represent the current refresh time. This way we'll
128 // save it as the last refresh time and skip looking up navigation start
130 if (result
.IsNull() || result
< timing
->GetNavigationStartTimeStamp()) {
131 result
= timing
->GetNavigationStartTimeStamp();
135 if (!result
.IsNull()) {
136 mLastRefreshDriverTime
= result
;
140 Nullable
<TimeDuration
> DocumentTimeline::ToTimelineTime(
141 const TimeStamp
& aTimeStamp
) const {
142 Nullable
<TimeDuration
> result
; // Initializes to null
143 if (aTimeStamp
.IsNull()) {
147 nsDOMNavigationTiming
* timing
= mDocument
->GetNavigationTiming();
148 if (MOZ_UNLIKELY(!timing
)) {
152 result
.SetValue(aTimeStamp
- timing
->GetNavigationStartTimeStamp() -
157 void DocumentTimeline::NotifyAnimationUpdated(Animation
& aAnimation
) {
158 AnimationTimeline::NotifyAnimationUpdated(aAnimation
);
160 if (!mIsObservingRefreshDriver
&& !mAnimationOrder
.isEmpty()) {
161 nsRefreshDriver
* refreshDriver
= GetRefreshDriver();
163 MOZ_ASSERT(isInList(),
164 "We should not register with the refresh driver if we are not"
165 " in the document's list of timelines");
167 ObserveRefreshDriver(refreshDriver
);
172 void DocumentTimeline::MostRecentRefreshTimeUpdated() {
173 MOZ_ASSERT(mIsObservingRefreshDriver
);
174 MOZ_ASSERT(GetRefreshDriver(),
175 "Should be able to reach refresh driver from within WillRefresh");
177 nsAutoAnimationMutationBatch
mb(mDocument
);
180 bool ticked
= Tick(state
);
182 // We already assert that GetRefreshDriver() is non-null at the beginning
183 // of this function but we check it again here to be sure that ticking
184 // animations does not have any side effects that cause us to lose the
185 // connection with the refresh driver, such as triggering the destruction
186 // of mDocument's PresShell.
187 MOZ_ASSERT(GetRefreshDriver(),
188 "Refresh driver should still be valid at end of WillRefresh");
189 UnregisterFromRefreshDriver();
193 void DocumentTimeline::TriggerAllPendingAnimationsNow() {
194 for (Animation
* animation
: mAnimationOrder
) {
195 animation
->TryTriggerNow();
199 void DocumentTimeline::WillRefresh(TimeStamp aTime
) {
200 UpdateLastRefreshDriverTime();
201 MostRecentRefreshTimeUpdated();
204 void DocumentTimeline::NotifyTimerAdjusted(TimeStamp aTime
) {
205 MostRecentRefreshTimeUpdated();
208 void DocumentTimeline::ObserveRefreshDriver(nsRefreshDriver
* aDriver
) {
209 MOZ_RELEASE_ASSERT(!mIsObservingRefreshDriver
,
210 "shouldn't register as an observer more than once");
211 // Set the mIsObservingRefreshDriver flag before calling AddRefreshObserver
212 // since it might end up calling NotifyTimerAdjusted which calls
213 // MostRecentRefreshTimeUpdated which has an assertion for
214 // mIsObserveingRefreshDriver check.
215 mIsObservingRefreshDriver
= true;
216 aDriver
->AddRefreshObserver(this, FlushType::Style
,
217 "DocumentTimeline animations");
218 aDriver
->AddTimerAdjustmentObserver(this);
221 void DocumentTimeline::NotifyRefreshDriverCreated(nsRefreshDriver
* aDriver
) {
223 !mIsObservingRefreshDriver
,
224 "Timeline should not be observing the refresh driver before"
227 if (!mAnimationOrder
.isEmpty()) {
228 MOZ_ASSERT(isInList(),
229 "We should not register with the refresh driver if we are not"
230 " in the document's list of timelines");
231 ObserveRefreshDriver(aDriver
);
232 // Although we have started observing the refresh driver, it's possible we
233 // could perform a paint before the first refresh driver tick happens. To
234 // ensure we're in a consistent state in that case we run the first tick
236 MostRecentRefreshTimeUpdated();
240 void DocumentTimeline::DisconnectRefreshDriver(nsRefreshDriver
* aDriver
) {
241 MOZ_ASSERT(mIsObservingRefreshDriver
);
243 aDriver
->RemoveRefreshObserver(this, FlushType::Style
);
244 aDriver
->RemoveTimerAdjustmentObserver(this);
245 mIsObservingRefreshDriver
= false;
248 void DocumentTimeline::NotifyRefreshDriverDestroying(nsRefreshDriver
* aDriver
) {
249 if (!mIsObservingRefreshDriver
) {
253 DisconnectRefreshDriver(aDriver
);
256 void DocumentTimeline::RemoveAnimation(Animation
* aAnimation
) {
257 AnimationTimeline::RemoveAnimation(aAnimation
);
259 if (!mIsObservingRefreshDriver
|| !mAnimationOrder
.isEmpty()) {
263 UnregisterFromRefreshDriver();
266 void DocumentTimeline::NotifyAnimationContentVisibilityChanged(
267 Animation
* aAnimation
, bool aIsVisible
) {
268 AnimationTimeline::NotifyAnimationContentVisibilityChanged(aAnimation
,
271 if (mIsObservingRefreshDriver
&& mAnimationOrder
.isEmpty()) {
272 UnregisterFromRefreshDriver();
275 if (!mIsObservingRefreshDriver
&& !mAnimationOrder
.isEmpty()) {
276 nsRefreshDriver
* refreshDriver
= GetRefreshDriver();
278 MOZ_ASSERT(isInList(),
279 "We should not register with the refresh driver if we are not"
280 " in the document's list of timelines");
282 ObserveRefreshDriver(refreshDriver
);
287 TimeStamp
DocumentTimeline::ToTimeStamp(
288 const TimeDuration
& aTimeDuration
) const {
290 nsDOMNavigationTiming
* timing
= mDocument
->GetNavigationTiming();
291 if (MOZ_UNLIKELY(!timing
)) {
296 timing
->GetNavigationStartTimeStamp() + (aTimeDuration
+ mOriginTime
);
300 nsRefreshDriver
* DocumentTimeline::GetRefreshDriver() const {
301 nsPresContext
* presContext
= mDocument
->GetPresContext();
302 if (MOZ_UNLIKELY(!presContext
)) {
306 return presContext
->RefreshDriver();
309 void DocumentTimeline::UnregisterFromRefreshDriver() {
310 if (!mIsObservingRefreshDriver
) {
314 nsRefreshDriver
* refreshDriver
= GetRefreshDriver();
315 if (!refreshDriver
) {
318 DisconnectRefreshDriver(refreshDriver
);
321 } // namespace mozilla::dom