ScrollTimeline.cpp (11524B)
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 "ScrollTimeline.h" 8 9 #include "mozilla/AnimationTarget.h" 10 #include "mozilla/DisplayPortUtils.h" 11 #include "mozilla/ElementAnimationData.h" 12 #include "mozilla/PresShell.h" 13 #include "mozilla/ScrollContainerFrame.h" 14 #include "mozilla/dom/Animation.h" 15 #include "mozilla/dom/AnimationTimelinesController.h" 16 #include "mozilla/dom/Document.h" 17 #include "mozilla/dom/DocumentInlines.h" 18 #include "mozilla/dom/ElementInlines.h" 19 #include "nsIFrame.h" 20 #include "nsLayoutUtils.h" 21 #include "nsRefreshDriver.h" 22 23 namespace mozilla::dom { 24 25 // --------------------------------- 26 // Methods of ScrollTimeline 27 // --------------------------------- 28 29 NS_IMPL_CYCLE_COLLECTION_CLASS(ScrollTimeline) 30 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ScrollTimeline, 31 AnimationTimeline) 32 tmp->Teardown(); 33 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument) 34 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSource.mElement) 35 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 36 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ScrollTimeline, 37 AnimationTimeline) 38 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument) 39 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSource.mElement) 40 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 41 42 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(ScrollTimeline, 43 AnimationTimeline) 44 45 ScrollTimeline::ScrollTimeline(Document* aDocument, const Scroller& aScroller, 46 StyleScrollAxis aAxis) 47 : AnimationTimeline(aDocument->GetParentObject(), 48 aDocument->GetScopeObject()->GetRTPCallerType()), 49 mDocument(aDocument), 50 mSource(aScroller), 51 mAxis(aAxis) { 52 MOZ_ASSERT(aDocument); 53 54 mDocument->TimelinesController().AddScrollTimeline(*this); 55 } 56 57 /* static */ 58 std::pair<const Element*, PseudoStyleRequest> 59 ScrollTimeline::FindNearestScroller(Element* aSubject, 60 const PseudoStyleRequest& aPseudoRequest) { 61 MOZ_ASSERT(aSubject); 62 Element* subject = aSubject->GetPseudoElement(aPseudoRequest); 63 Element* curr = subject->GetFlattenedTreeParentElement(); 64 Element* root = subject->OwnerDoc()->GetDocumentElement(); 65 while (curr && curr != root) { 66 const ComputedStyle* style = Servo_Element_GetMaybeOutOfDateStyle(curr); 67 MOZ_ASSERT(style, "The ancestor should be styled."); 68 if (style->StyleDisplay()->IsScrollableOverflow()) { 69 break; 70 } 71 curr = curr->GetFlattenedTreeParentElement(); 72 } 73 // If there is no scroll container, we use root. 74 if (!curr) { 75 return {root, PseudoStyleRequest::NotPseudo()}; 76 } 77 return AnimationUtils::GetElementPseudoPair(curr); 78 } 79 80 /* static */ 81 already_AddRefed<ScrollTimeline> ScrollTimeline::MakeAnonymous( 82 Document* aDocument, const NonOwningAnimationTarget& aTarget, 83 StyleScrollAxis aAxis, StyleScroller aScroller) { 84 MOZ_ASSERT(aTarget); 85 Scroller scroller; 86 switch (aScroller) { 87 case StyleScroller::Root: 88 // Specifies to use the document viewport as the scroll container. 89 // 90 // We use the owner doc of the animation target. This may be different 91 // from |mDocument| after we implement ScrollTimeline interface for 92 // script. 93 scroller = 94 Scroller::Root(aTarget.mElement->OwnerDoc()->GetDocumentElement()); 95 break; 96 97 case StyleScroller::Nearest: { 98 auto [element, pseudo] = 99 FindNearestScroller(aTarget.mElement, aTarget.mPseudoRequest); 100 scroller = Scroller::Nearest(const_cast<Element*>(element), pseudo.mType); 101 break; 102 } 103 case StyleScroller::SelfElement: 104 scroller = Scroller::Self(aTarget.mElement, aTarget.mPseudoRequest.mType); 105 break; 106 } 107 108 // Each use of scroll() corresponds to its own instance of ScrollTimeline in 109 // the Web Animations API, even if multiple elements use scroll() to refer to 110 // the same scroll container with the same arguments. 111 // https://drafts.csswg.org/scroll-animations-1/#scroll-notation 112 return MakeAndAddRef<ScrollTimeline>(aDocument, scroller, aAxis); 113 } 114 115 /* static*/ 116 already_AddRefed<ScrollTimeline> ScrollTimeline::MakeNamed( 117 Document* aDocument, Element* aReferenceElement, 118 const PseudoStyleRequest& aPseudoRequest, 119 const StyleScrollTimeline& aStyleTimeline) { 120 MOZ_ASSERT(NS_IsMainThread()); 121 122 Scroller scroller = Scroller::Named(aReferenceElement, aPseudoRequest.mType); 123 return MakeAndAddRef<ScrollTimeline>(aDocument, std::move(scroller), 124 aStyleTimeline.GetAxis()); 125 } 126 127 Nullable<TimeDuration> ScrollTimeline::GetCurrentTimeAsDuration() const { 128 if (!mCachedCurrentTime) { 129 return nullptr; 130 } 131 132 const CurrentTimeData& data = mCachedCurrentTime.ref(); 133 // FIXME: Scroll offsets on the RTL container is complicated specifically on 134 // mobile, see https://github.com/w3c/csswg-drafts/issues/12893. For now, we 135 // use the absoluate value to make things simple. 136 double progress = 137 static_cast<double>(std::abs(data.mPosition) - data.mOffsets.mStart) / 138 static_cast<double>(data.mOffsets.mEnd - data.mOffsets.mStart); 139 return TimeDuration::FromMilliseconds(progress * 140 PROGRESS_TIMELINE_DURATION_MILLISEC); 141 } 142 143 void ScrollTimeline::WillRefresh() { 144 UpdateCachedCurrentTime(); 145 146 if (!mDocument->GetPresShell()) { 147 // If we're not displayed, don't tick animations. 148 return; 149 } 150 151 if (mAnimationOrder.isEmpty()) { 152 return; 153 } 154 155 // FIXME: Bug 1737927: Need to check the animation mutation observers for 156 // animations with scroll timelines. 157 // nsAutoAnimationMutationBatch mb(mDocument); 158 159 TickState dummyState; 160 Tick(dummyState); 161 } 162 163 layers::ScrollDirection ScrollTimeline::Axis() const { 164 MOZ_ASSERT(mSource && mSource.mElement->GetPrimaryFrame()); 165 166 const WritingMode wm = mSource.mElement->GetPrimaryFrame()->GetWritingMode(); 167 return mAxis == StyleScrollAxis::X || 168 (!wm.IsVertical() && mAxis == StyleScrollAxis::Inline) || 169 (wm.IsVertical() && mAxis == StyleScrollAxis::Block) 170 ? layers::ScrollDirection::eHorizontal 171 : layers::ScrollDirection::eVertical; 172 } 173 174 StyleOverflow ScrollTimeline::SourceScrollStyle() const { 175 MOZ_ASSERT(mSource && mSource.mElement->GetPrimaryFrame()); 176 177 const ScrollContainerFrame* scrollContainerFrame = GetScrollContainerFrame(); 178 MOZ_ASSERT(scrollContainerFrame); 179 180 const ScrollStyles scrollStyles = scrollContainerFrame->GetScrollStyles(); 181 182 return Axis() == layers::ScrollDirection::eHorizontal 183 ? scrollStyles.mHorizontal 184 : scrollStyles.mVertical; 185 } 186 187 bool ScrollTimeline::APZIsActiveForSource() const { 188 MOZ_ASSERT(mSource); 189 return gfxPlatform::AsyncPanZoomEnabled() && 190 !nsLayoutUtils::ShouldDisableApzForElement(mSource.mElement) && 191 DisplayPortUtils::HasNonMinimalNonZeroDisplayPort(mSource.mElement); 192 } 193 194 bool ScrollTimeline::ScrollingDirectionIsAvailable() const { 195 const ScrollContainerFrame* scrollContainerFrame = GetScrollContainerFrame(); 196 MOZ_ASSERT(scrollContainerFrame); 197 return scrollContainerFrame->GetAvailableScrollingDirections().contains( 198 Axis()); 199 } 200 201 void ScrollTimeline::ReplacePropertiesWith( 202 const Element* aReferenceElement, const PseudoStyleRequest& aPseudoRequest, 203 const StyleScrollTimeline& aNew) { 204 MOZ_ASSERT(aReferenceElement == mSource.mElement && 205 aPseudoRequest.mType == mSource.mPseudoType); 206 mAxis = aNew.GetAxis(); 207 208 for (auto* anim = mAnimationOrder.getFirst(); anim; 209 anim = static_cast<LinkedListElement<Animation>*>(anim)->getNext()) { 210 MOZ_ASSERT(anim->GetTimeline() == this); 211 // Set this so we just PostUpdate() for this animation. 212 anim->SetTimeline(this); 213 } 214 } 215 216 ScrollTimeline::~ScrollTimeline() { Teardown(); } 217 218 Maybe<ScrollTimeline::ScrollOffsets> ScrollTimeline::ComputeOffsets( 219 const ScrollContainerFrame* aScrollContainerFrame, 220 layers::ScrollDirection aOrientation) const { 221 const nsRect& scrollRange = aScrollContainerFrame->GetScrollRange(); 222 nscoord range = aOrientation == layers::ScrollDirection::eHorizontal 223 ? scrollRange.width 224 : scrollRange.height; 225 MOZ_ASSERT(range > 0); 226 return Some(ScrollOffsets{0, range}); 227 } 228 229 void ScrollTimeline::UpdateCachedCurrentTime() { 230 mCachedCurrentTime.reset(); 231 232 // If no layout box, this timeline is inactive. 233 if (!mSource || !mSource.mElement->GetPrimaryFrame()) { 234 return; 235 } 236 237 // if this is not a scroller container, this timeline is inactive. 238 const ScrollContainerFrame* scrollContainerFrame = GetScrollContainerFrame(); 239 if (!scrollContainerFrame) { 240 return; 241 } 242 243 const auto orientation = Axis(); 244 245 // If there is no scrollable overflow, then the ScrollTimeline is inactive. 246 // https://drafts.csswg.org/scroll-animations-1/#scrolltimeline-interface 247 if (!scrollContainerFrame->GetAvailableScrollingDirections().contains( 248 orientation)) { 249 return; 250 } 251 252 const nsPoint& scrollPosition = scrollContainerFrame->GetScrollPosition(); 253 const Maybe<ScrollOffsets>& offsets = 254 ComputeOffsets(scrollContainerFrame, orientation); 255 if (!offsets) { 256 return; 257 } 258 259 mCachedCurrentTime.emplace(CurrentTimeData{ 260 orientation == layers::ScrollDirection::eHorizontal ? scrollPosition.x 261 : scrollPosition.y, 262 offsets.value()}); 263 } 264 265 const ScrollContainerFrame* ScrollTimeline::GetScrollContainerFrame() const { 266 if (!mSource) { 267 return nullptr; 268 } 269 270 switch (mSource.mType) { 271 case Scroller::Type::Root: 272 if (const PresShell* presShell = 273 mSource.mElement->OwnerDoc()->GetPresShell()) { 274 return presShell->GetRootScrollContainerFrame(); 275 } 276 return nullptr; 277 case Scroller::Type::Nearest: 278 case Scroller::Type::Name: 279 case Scroller::Type::Self: 280 return nsLayoutUtils::FindScrollContainerFrameFor(mSource.mElement); 281 } 282 283 MOZ_ASSERT_UNREACHABLE("Unsupported scroller type"); 284 return nullptr; 285 } 286 287 static nsRefreshDriver* GetRefreshDriver(Document* aDocument) { 288 nsPresContext* presContext = aDocument->GetPresContext(); 289 if (MOZ_UNLIKELY(!presContext)) { 290 return nullptr; 291 } 292 return presContext->RefreshDriver(); 293 } 294 295 void ScrollTimeline::NotifyAnimationUpdated(Animation& aAnimation) { 296 AnimationTimeline::NotifyAnimationUpdated(aAnimation); 297 298 if (!mAnimationOrder.isEmpty()) { 299 if (auto* rd = GetRefreshDriver(mDocument)) { 300 MOZ_ASSERT(isInList(), 301 "We should not register with the refresh driver if we are not" 302 " in the document's list of timelines"); 303 rd->EnsureAnimationUpdate(); 304 } 305 } 306 } 307 308 void ScrollTimeline::NotifyAnimationContentVisibilityChanged( 309 Animation* aAnimation, bool aIsVisible) { 310 AnimationTimeline::NotifyAnimationContentVisibilityChanged(aAnimation, 311 aIsVisible); 312 if (auto* rd = GetRefreshDriver(mDocument)) { 313 MOZ_ASSERT(isInList(), 314 "We should not register with the refresh driver if we are not" 315 " in the document's list of timelines"); 316 rd->EnsureAnimationUpdate(); 317 } 318 } 319 320 } // namespace mozilla::dom