Bug 1892041 - Part 1: Update test262 features. r=spidermonkey-reviewers,dminor
[gecko.git] / dom / animation / DocumentTimeline.cpp
blob804ae661600ff1e3990e78c1ecf8ff23bb136acc
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,
21 AnimationTimeline)
22 tmp->UnregisterFromRefreshDriver();
23 if (tmp->isInList()) {
24 tmp->remove();
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
33 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(DocumentTimeline,
34 AnimationTimeline)
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()),
47 mDocument(aDocument),
48 mIsObservingRefreshDriver(false),
49 mOriginTime(aOriginTime) {
50 if (mDocument) {
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");
61 if (isInList()) {
62 remove();
66 JSObject* DocumentTimeline::WrapObject(JSContext* aCx,
67 JS::Handle<JSObject*> aGivenProto) {
68 return DocumentTimeline_Binding::Wrap(aCx, this, aGivenProto);
71 /* static */
72 already_AddRefed<DocumentTimeline> DocumentTimeline::Constructor(
73 const GlobalObject& aGlobal, const DocumentTimelineOptions& aOptions,
74 ErrorResult& aRv) {
75 Document* doc = AnimationUtils::GetCurrentRealmDocument(aGlobal.Context());
76 if (!doc) {
77 aRv.Throw(NS_ERROR_FAILURE);
78 return nullptr;
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");
86 return nullptr;
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()) {
111 return aKnownTime;
113 if (auto* rd = GetRefreshDriver()) {
114 return rd->MostRecentRefresh();
116 return mLastRefreshDriverTime;
117 }();
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
126 // time.
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
129 // time each time.
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()) {
144 return result;
147 nsDOMNavigationTiming* timing = mDocument->GetNavigationTiming();
148 if (MOZ_UNLIKELY(!timing)) {
149 return result;
152 result.SetValue(aTimeStamp - timing->GetNavigationStartTimeStamp() -
153 mOriginTime);
154 return result;
157 void DocumentTimeline::NotifyAnimationUpdated(Animation& aAnimation) {
158 AnimationTimeline::NotifyAnimationUpdated(aAnimation);
160 if (!mIsObservingRefreshDriver && !mAnimationOrder.isEmpty()) {
161 nsRefreshDriver* refreshDriver = GetRefreshDriver();
162 if (refreshDriver) {
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);
179 TickState state;
180 bool ticked = Tick(state);
181 if (!ticked) {
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) {
222 MOZ_RELEASE_ASSERT(
223 !mIsObservingRefreshDriver,
224 "Timeline should not be observing the refresh driver before"
225 " it is created");
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
235 // manually.
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) {
250 return;
253 DisconnectRefreshDriver(aDriver);
256 void DocumentTimeline::RemoveAnimation(Animation* aAnimation) {
257 AnimationTimeline::RemoveAnimation(aAnimation);
259 if (!mIsObservingRefreshDriver || !mAnimationOrder.isEmpty()) {
260 return;
263 UnregisterFromRefreshDriver();
266 void DocumentTimeline::NotifyAnimationContentVisibilityChanged(
267 Animation* aAnimation, bool aIsVisible) {
268 AnimationTimeline::NotifyAnimationContentVisibilityChanged(aAnimation,
269 aIsVisible);
271 if (mIsObservingRefreshDriver && mAnimationOrder.isEmpty()) {
272 UnregisterFromRefreshDriver();
275 if (!mIsObservingRefreshDriver && !mAnimationOrder.isEmpty()) {
276 nsRefreshDriver* refreshDriver = GetRefreshDriver();
277 if (refreshDriver) {
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 {
289 TimeStamp result;
290 nsDOMNavigationTiming* timing = mDocument->GetNavigationTiming();
291 if (MOZ_UNLIKELY(!timing)) {
292 return result;
295 result =
296 timing->GetNavigationStartTimeStamp() + (aTimeDuration + mOriginTime);
297 return result;
300 nsRefreshDriver* DocumentTimeline::GetRefreshDriver() const {
301 nsPresContext* presContext = mDocument->GetPresContext();
302 if (MOZ_UNLIKELY(!presContext)) {
303 return nullptr;
306 return presContext->RefreshDriver();
309 void DocumentTimeline::UnregisterFromRefreshDriver() {
310 if (!mIsObservingRefreshDriver) {
311 return;
314 nsRefreshDriver* refreshDriver = GetRefreshDriver();
315 if (!refreshDriver) {
316 return;
318 DisconnectRefreshDriver(refreshDriver);
321 } // namespace mozilla::dom