DocumentTimeline.cpp (7982B)
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 9 #include "AnimationUtils.h" 10 #include "mozilla/dom/DocumentInlines.h" 11 #include "mozilla/dom/DocumentTimelineBinding.h" 12 #include "nsContentUtils.h" 13 #include "nsDOMMutationObserver.h" 14 #include "nsDOMNavigationTiming.h" 15 #include "nsPresContext.h" 16 #include "nsRefreshDriver.h" 17 18 namespace mozilla::dom { 19 20 NS_IMPL_CYCLE_COLLECTION_CLASS(DocumentTimeline) 21 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocumentTimeline, 22 AnimationTimeline) 23 if (tmp->isInList()) { 24 tmp->remove(); 25 } 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 32 33 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(DocumentTimeline, 34 AnimationTimeline) 35 NS_IMPL_CYCLE_COLLECTION_TRACE_END 36 37 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DocumentTimeline) 38 NS_INTERFACE_MAP_END_INHERITING(AnimationTimeline) 39 40 NS_IMPL_ADDREF_INHERITED(DocumentTimeline, AnimationTimeline) 41 NS_IMPL_RELEASE_INHERITED(DocumentTimeline, AnimationTimeline) 42 43 DocumentTimeline::DocumentTimeline(Document* aDocument, 44 const TimeDuration& aOriginTime) 45 : AnimationTimeline(aDocument->GetParentObject(), 46 aDocument->GetScopeObject()->GetRTPCallerType()), 47 mDocument(aDocument), 48 mOriginTime(aOriginTime) { 49 if (mDocument) { 50 mDocument->TimelinesController().AddDocumentTimeline(*this); 51 } 52 // Ensure mLastRefreshDriverTime is valid. 53 UpdateLastRefreshDriverTime(); 54 } 55 56 DocumentTimeline::~DocumentTimeline() { 57 if (isInList()) { 58 remove(); 59 } 60 } 61 62 JSObject* DocumentTimeline::WrapObject(JSContext* aCx, 63 JS::Handle<JSObject*> aGivenProto) { 64 return DocumentTimeline_Binding::Wrap(aCx, this, aGivenProto); 65 } 66 67 /* static */ 68 already_AddRefed<DocumentTimeline> DocumentTimeline::Constructor( 69 const GlobalObject& aGlobal, const DocumentTimelineOptions& aOptions, 70 ErrorResult& aRv) { 71 Document* doc = AnimationUtils::GetCurrentRealmDocument(aGlobal.Context()); 72 if (!doc) { 73 aRv.Throw(NS_ERROR_FAILURE); 74 return nullptr; 75 } 76 TimeDuration originTime = 77 TimeDuration::FromMilliseconds(aOptions.mOriginTime); 78 79 if (originTime == TimeDuration::Forever() || 80 originTime == -TimeDuration::Forever()) { 81 aRv.ThrowTypeError<dom::MSG_TIME_VALUE_OUT_OF_RANGE>("Origin time"); 82 return nullptr; 83 } 84 RefPtr<DocumentTimeline> timeline = new DocumentTimeline(doc, originTime); 85 86 return timeline.forget(); 87 } 88 89 Nullable<TimeDuration> DocumentTimeline::GetCurrentTimeAsDuration() const { 90 return ToTimelineTime(GetCurrentTimeStamp()); 91 } 92 93 bool DocumentTimeline::TracksWallclockTime() const { 94 nsRefreshDriver* refreshDriver = GetRefreshDriver(); 95 return !refreshDriver || !refreshDriver->IsTestControllingRefreshesEnabled(); 96 } 97 98 TimeStamp DocumentTimeline::GetCurrentTimeStamp() const { 99 nsRefreshDriver* refreshDriver = GetRefreshDriver(); 100 TimeStamp result = refreshDriver ? refreshDriver->MostRecentRefresh() 101 : mLastRefreshDriverTime; 102 103 return EnsureValidTimestamp(result); 104 } 105 106 void DocumentTimeline::UpdateLastRefreshDriverTime() { 107 TimeStamp result = [&] { 108 if (auto* rd = GetRefreshDriver()) { 109 return rd->MostRecentRefresh(); 110 }; 111 return mLastRefreshDriverTime; 112 }(); 113 114 result = EnsureValidTimestamp(result); 115 116 if (!result.IsNull()) { 117 mLastRefreshDriverTime = result; 118 } 119 } 120 121 TimeStamp DocumentTimeline::EnsureValidTimestamp( 122 const TimeStamp& aTimestamp) const { 123 if (nsDOMNavigationTiming* timing = mDocument->GetNavigationTiming()) { 124 // If we don't have a refresh driver and we've never had one use the 125 // timeline's zero time. 126 // In addition, it's possible that our refresh driver's timestamp is behind 127 // from the navigation start time because the refresh driver timestamp is 128 // sent through an IPC call whereas the navigation time is set by calling 129 // TimeStamp::Now() directly. In such cases we also use the timeline's zero 130 // time. 131 // Also, let this time represent the current refresh time. This way we'll 132 // save it as the last refresh time and skip looking up navigation start 133 // time each time. 134 if (aTimestamp.IsNull() || 135 aTimestamp < timing->GetNavigationStartTimeStamp()) { 136 return timing->GetNavigationStartTimeStamp(); 137 } 138 } 139 return aTimestamp; 140 } 141 142 Nullable<TimeDuration> DocumentTimeline::ToTimelineTime( 143 const TimeStamp& aTimeStamp) const { 144 Nullable<TimeDuration> result; // Initializes to null 145 if (aTimeStamp.IsNull()) { 146 return result; 147 } 148 149 nsDOMNavigationTiming* timing = mDocument->GetNavigationTiming(); 150 if (MOZ_UNLIKELY(!timing)) { 151 return result; 152 } 153 154 result.SetValue(aTimeStamp - timing->GetNavigationStartTimeStamp() - 155 mOriginTime); 156 return result; 157 } 158 159 void DocumentTimeline::NotifyAnimationUpdated(Animation& aAnimation) { 160 AnimationTimeline::NotifyAnimationUpdated(aAnimation); 161 162 if (!mAnimationOrder.isEmpty()) { 163 if (nsRefreshDriver* refreshDriver = GetRefreshDriver()) { 164 MOZ_ASSERT(isInList(), 165 "We should not register with the refresh driver if we are not" 166 " in the document's list of timelines"); 167 refreshDriver->EnsureAnimationUpdate(); 168 } 169 } 170 } 171 172 void DocumentTimeline::TriggerAllPendingAnimationsNow() { 173 for (Animation* animation : 174 ToTArray<AutoTArray<RefPtr<Animation>, 32>>(mAnimationOrder)) { 175 animation->TryTriggerNow(); 176 } 177 } 178 179 void DocumentTimeline::WillRefresh() { 180 if (!mDocument->GetPresShell()) { 181 // If we're not displayed, don't tick animations. 182 return; 183 } 184 UpdateLastRefreshDriverTime(); 185 if (mAnimationOrder.isEmpty()) { 186 return; 187 } 188 nsAutoAnimationMutationBatch mb(mDocument); 189 190 TickState state; 191 bool ticked = Tick(state); 192 if (!ticked) { 193 return; 194 } 195 // We already assert that GetRefreshDriver() is non-null at the beginning 196 // of this function but we check it again here to be sure that ticking 197 // animations does not have any side effects that cause us to lose the 198 // connection with the refresh driver, such as triggering the destruction 199 // of mDocument's PresShell. 200 if (nsRefreshDriver* refreshDriver = GetRefreshDriver()) { 201 refreshDriver->EnsureAnimationUpdate(); 202 } 203 } 204 205 void DocumentTimeline::NotifyAnimationContentVisibilityChanged( 206 Animation* aAnimation, bool aIsVisible) { 207 AnimationTimeline::NotifyAnimationContentVisibilityChanged(aAnimation, 208 aIsVisible); 209 210 if (nsRefreshDriver* refreshDriver = GetRefreshDriver()) { 211 MOZ_ASSERT(isInList(), 212 "We should not register with the refresh driver if we are not" 213 " in the document's list of timelines"); 214 refreshDriver->EnsureAnimationUpdate(); 215 } 216 } 217 218 TimeStamp DocumentTimeline::ToTimeStamp( 219 const TimeDuration& aTimeDuration) const { 220 TimeStamp result; 221 nsDOMNavigationTiming* timing = mDocument->GetNavigationTiming(); 222 if (MOZ_UNLIKELY(!timing)) { 223 return result; 224 } 225 226 result = 227 timing->GetNavigationStartTimeStamp() + (aTimeDuration + mOriginTime); 228 return result; 229 } 230 231 nsRefreshDriver* DocumentTimeline::GetRefreshDriver() const { 232 nsPresContext* presContext = mDocument->GetPresContext(); 233 if (MOZ_UNLIKELY(!presContext)) { 234 return nullptr; 235 } 236 return presContext->RefreshDriver(); 237 } 238 239 } // namespace mozilla::dom