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 if (tmp
->isInList()) {
25 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument
)
26 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
27 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DocumentTimeline
,
29 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument
)
30 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
32 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(DocumentTimeline
,
34 NS_IMPL_CYCLE_COLLECTION_TRACE_END
36 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DocumentTimeline
)
37 NS_INTERFACE_MAP_END_INHERITING(AnimationTimeline
)
39 NS_IMPL_ADDREF_INHERITED(DocumentTimeline
, AnimationTimeline
)
40 NS_IMPL_RELEASE_INHERITED(DocumentTimeline
, AnimationTimeline
)
42 DocumentTimeline::DocumentTimeline(Document
* aDocument
,
43 const TimeDuration
& aOriginTime
)
44 : AnimationTimeline(aDocument
->GetParentObject(),
45 aDocument
->GetScopeObject()->GetRTPCallerType()),
47 mOriginTime(aOriginTime
) {
49 mDocument
->Timelines().insertBack(this);
51 // Ensure mLastRefreshDriverTime is valid.
52 UpdateLastRefreshDriverTime();
55 DocumentTimeline::~DocumentTimeline() {
61 JSObject
* DocumentTimeline::WrapObject(JSContext
* aCx
,
62 JS::Handle
<JSObject
*> aGivenProto
) {
63 return DocumentTimeline_Binding::Wrap(aCx
, this, aGivenProto
);
67 already_AddRefed
<DocumentTimeline
> DocumentTimeline::Constructor(
68 const GlobalObject
& aGlobal
, const DocumentTimelineOptions
& aOptions
,
70 Document
* doc
= AnimationUtils::GetCurrentRealmDocument(aGlobal
.Context());
72 aRv
.Throw(NS_ERROR_FAILURE
);
75 TimeDuration originTime
=
76 TimeDuration::FromMilliseconds(aOptions
.mOriginTime
);
78 if (originTime
== TimeDuration::Forever() ||
79 originTime
== -TimeDuration::Forever()) {
80 aRv
.ThrowTypeError
<dom::MSG_TIME_VALUE_OUT_OF_RANGE
>("Origin time");
83 RefPtr
<DocumentTimeline
> timeline
= new DocumentTimeline(doc
, originTime
);
85 return timeline
.forget();
88 Nullable
<TimeDuration
> DocumentTimeline::GetCurrentTimeAsDuration() const {
89 return ToTimelineTime(GetCurrentTimeStamp());
92 bool DocumentTimeline::TracksWallclockTime() const {
93 nsRefreshDriver
* refreshDriver
= GetRefreshDriver();
94 return !refreshDriver
|| !refreshDriver
->IsTestControllingRefreshesEnabled();
97 TimeStamp
DocumentTimeline::GetCurrentTimeStamp() const {
98 nsRefreshDriver
* refreshDriver
= GetRefreshDriver();
99 return refreshDriver
? refreshDriver
->MostRecentRefresh()
100 : mLastRefreshDriverTime
;
103 void DocumentTimeline::UpdateLastRefreshDriverTime() {
104 TimeStamp result
= [&] {
105 if (auto* rd
= GetRefreshDriver()) {
106 return rd
->MostRecentRefresh();
108 return mLastRefreshDriverTime
;
111 if (nsDOMNavigationTiming
* timing
= mDocument
->GetNavigationTiming()) {
112 // If we don't have a refresh driver and we've never had one use the
113 // timeline's zero time.
114 // In addition, it's possible that our refresh driver's timestamp is behind
115 // from the navigation start time because the refresh driver timestamp is
116 // sent through an IPC call whereas the navigation time is set by calling
117 // TimeStamp::Now() directly. In such cases we also use the timeline's zero
119 // Also, let this time represent the current refresh time. This way we'll
120 // save it as the last refresh time and skip looking up navigation start
122 if (result
.IsNull() || result
< timing
->GetNavigationStartTimeStamp()) {
123 result
= timing
->GetNavigationStartTimeStamp();
127 if (!result
.IsNull()) {
128 mLastRefreshDriverTime
= result
;
132 Nullable
<TimeDuration
> DocumentTimeline::ToTimelineTime(
133 const TimeStamp
& aTimeStamp
) const {
134 Nullable
<TimeDuration
> result
; // Initializes to null
135 if (aTimeStamp
.IsNull()) {
139 nsDOMNavigationTiming
* timing
= mDocument
->GetNavigationTiming();
140 if (MOZ_UNLIKELY(!timing
)) {
144 result
.SetValue(aTimeStamp
- timing
->GetNavigationStartTimeStamp() -
149 void DocumentTimeline::NotifyAnimationUpdated(Animation
& aAnimation
) {
150 AnimationTimeline::NotifyAnimationUpdated(aAnimation
);
152 if (!mAnimationOrder
.isEmpty()) {
153 if (nsRefreshDriver
* refreshDriver
= GetRefreshDriver()) {
154 MOZ_ASSERT(isInList(),
155 "We should not register with the refresh driver if we are not"
156 " in the document's list of timelines");
157 refreshDriver
->EnsureAnimationUpdate();
162 void DocumentTimeline::TriggerAllPendingAnimationsNow() {
163 for (Animation
* animation
:
164 ToTArray
<AutoTArray
<RefPtr
<Animation
>, 32>>(mAnimationOrder
)) {
165 animation
->TryTriggerNow();
169 void DocumentTimeline::WillRefresh() {
170 if (!mDocument
->GetPresShell()) {
171 // If we're not displayed, don't tick animations.
174 UpdateLastRefreshDriverTime();
175 if (mAnimationOrder
.isEmpty()) {
178 nsAutoAnimationMutationBatch
mb(mDocument
);
181 bool ticked
= Tick(state
);
185 // We already assert that GetRefreshDriver() is non-null at the beginning
186 // of this function but we check it again here to be sure that ticking
187 // animations does not have any side effects that cause us to lose the
188 // connection with the refresh driver, such as triggering the destruction
189 // of mDocument's PresShell.
190 if (nsRefreshDriver
* refreshDriver
= GetRefreshDriver()) {
191 refreshDriver
->EnsureAnimationUpdate();
195 void DocumentTimeline::NotifyAnimationContentVisibilityChanged(
196 Animation
* aAnimation
, bool aIsVisible
) {
197 AnimationTimeline::NotifyAnimationContentVisibilityChanged(aAnimation
,
200 if (nsRefreshDriver
* refreshDriver
= GetRefreshDriver()) {
201 MOZ_ASSERT(isInList(),
202 "We should not register with the refresh driver if we are not"
203 " in the document's list of timelines");
204 refreshDriver
->EnsureAnimationUpdate();
208 TimeStamp
DocumentTimeline::ToTimeStamp(
209 const TimeDuration
& aTimeDuration
) const {
211 nsDOMNavigationTiming
* timing
= mDocument
->GetNavigationTiming();
212 if (MOZ_UNLIKELY(!timing
)) {
217 timing
->GetNavigationStartTimeStamp() + (aTimeDuration
+ mOriginTime
);
221 nsRefreshDriver
* DocumentTimeline::GetRefreshDriver() const {
222 nsPresContext
* presContext
= mDocument
->GetPresContext();
223 if (MOZ_UNLIKELY(!presContext
)) {
226 return presContext
->RefreshDriver();
229 } // namespace mozilla::dom