ViewTimeline.cpp (8113B)
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 "ViewTimeline.h" 8 9 #include "mozilla/ScrollContainerFrame.h" 10 #include "mozilla/dom/Animation.h" 11 #include "mozilla/dom/ElementInlines.h" 12 #include "nsLayoutUtils.h" 13 14 namespace mozilla::dom { 15 16 NS_IMPL_CYCLE_COLLECTION_INHERITED(ViewTimeline, ScrollTimeline, mSubject) 17 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(ViewTimeline, ScrollTimeline) 18 19 /* static */ 20 already_AddRefed<ViewTimeline> ViewTimeline::MakeNamed( 21 Document* aDocument, Element* aSubject, 22 const PseudoStyleRequest& aPseudoRequest, 23 const StyleViewTimeline& aStyleTimeline) { 24 MOZ_ASSERT(NS_IsMainThread()); 25 26 // 1. Lookup scroller. We have to find the nearest scroller from |aSubject| 27 // and |aPseudoType|. 28 auto [element, pseudo] = FindNearestScroller(aSubject, aPseudoRequest); 29 auto scroller = 30 Scroller::Nearest(const_cast<Element*>(element), pseudo.mType); 31 32 // 2. Create timeline. 33 return MakeAndAddRef<ViewTimeline>( 34 aDocument, scroller, aStyleTimeline.GetAxis(), aSubject, 35 aPseudoRequest.mType, aStyleTimeline.GetInset()); 36 } 37 38 /* static */ 39 already_AddRefed<ViewTimeline> ViewTimeline::MakeAnonymous( 40 Document* aDocument, const NonOwningAnimationTarget& aTarget, 41 StyleScrollAxis aAxis, const StyleViewTimelineInset& aInset) { 42 // view() finds the nearest scroll container from the animation target. 43 auto [element, pseudo] = 44 FindNearestScroller(aTarget.mElement, aTarget.mPseudoRequest); 45 Scroller scroller = 46 Scroller::Nearest(const_cast<Element*>(element), pseudo.mType); 47 return MakeAndAddRef<ViewTimeline>(aDocument, scroller, aAxis, 48 aTarget.mElement, 49 aTarget.mPseudoRequest.mType, aInset); 50 } 51 52 void ViewTimeline::ReplacePropertiesWith( 53 Element* aSubjectElement, const PseudoStyleRequest& aPseudoRequest, 54 const StyleViewTimeline& aNew) { 55 mSubject = aSubjectElement; 56 mSubjectPseudoType = aPseudoRequest.mType; 57 mAxis = aNew.GetAxis(); 58 // FIXME: Bug 1817073. We assume it is a non-animatable value for now. 59 mInset = aNew.GetInset(); 60 61 for (auto* anim = mAnimationOrder.getFirst(); anim; 62 anim = static_cast<LinkedListElement<Animation>*>(anim)->getNext()) { 63 MOZ_ASSERT(anim->GetTimeline() == this); 64 // Set this so we just PostUpdate() for this animation. 65 anim->SetTimeline(this); 66 } 67 } 68 69 Maybe<ScrollTimeline::ScrollOffsets> ViewTimeline::ComputeOffsets( 70 const ScrollContainerFrame* aScrollContainerFrame, 71 layers::ScrollDirection aOrientation) const { 72 MOZ_ASSERT(mSubject); 73 MOZ_ASSERT(aScrollContainerFrame); 74 75 // Note: We may fail to get the pseudo element (or its primary frame) if it is 76 // not generated yet or just get destroyed, while we are sampling this view 77 // timeline. 78 // FIXME: Bug 1954230. It's probably a case we need to discard this timeline. 79 // For now, this is just a hot fix. 80 const Element* subjectElement = 81 mSubject->GetPseudoElement(PseudoStyleRequest(mSubjectPseudoType)); 82 const nsIFrame* subject = 83 subjectElement ? subjectElement->GetPrimaryFrame() : nullptr; 84 if (!subject) { 85 // No principal box of the subject, so we cannot compute the offset. This 86 // may happen when we clear all animation collections during unbinding from 87 // the tree. 88 return Nothing(); 89 } 90 91 // In order to get the distance between the subject and the scrollport 92 // properly, we use the position based on the domain of the scrolled frame, 93 // instead of the scroll container frame. 94 const nsIFrame* scrolledFrame = aScrollContainerFrame->GetScrolledFrame(); 95 MOZ_ASSERT(scrolledFrame); 96 const nsRect subjectRect(subject->GetOffsetTo(scrolledFrame), 97 subject->GetSize()); 98 99 // Use scrollport size (i.e. padding box size - scrollbar size), which is used 100 // for calculating the view progress visibility range. 101 // https://drafts.csswg.org/scroll-animations/#view-progress-visibility-range 102 const nsRect scrollPort = aScrollContainerFrame->GetScrollPortRect(); 103 104 // Adjuct the positions and sizes based on the physical axis. 105 nscoord subjectPosition = subjectRect.y; 106 nscoord subjectSize = subjectRect.height; 107 nscoord scrollPortSize = scrollPort.height; 108 if (aOrientation == layers::ScrollDirection::eHorizontal) { 109 // |subjectPosition| should be the position of the start border edge of the 110 // subject, so for R-L case, we have to use XMost() as the start border 111 // edge of the subject, and compute its position by using the x-most side of 112 // the scrolled frame as the origin on the horizontal axis. 113 subjectPosition = scrolledFrame->GetWritingMode().IsPhysicalRTL() 114 ? scrolledFrame->GetSize().width - subjectRect.XMost() 115 : subjectRect.x; 116 subjectSize = subjectRect.width; 117 scrollPortSize = scrollPort.width; 118 } 119 120 // |sideInsets.mEnd| is used to adjust the start offset, and 121 // |sideInsets.mStart| is used to adjust the end offset. This is because 122 // |sideInsets.mStart| refers to logical start side [1] of the source box 123 // (i.e. the box of the scrollport), where as |startOffset| refers to the 124 // start of the timeline, and similarly for end side/offset. [1] 125 // https://drafts.csswg.org/css-writing-modes-4/#css-start 126 const auto sideInsets = ComputeInsets(aScrollContainerFrame, aOrientation); 127 128 // Basically, we are computing the "cover" timeline range name, which 129 // represents the full range of the view progress timeline. 130 // https://drafts.csswg.org/scroll-animations-1/#valdef-animation-timeline-range-cover 131 132 // Note: `subjectPosition - scrollPortSize` means the distance between the 133 // start border edge of the subject and the end edge of the scrollport. 134 nscoord startOffset = subjectPosition - scrollPortSize + sideInsets.mEnd; 135 // Note: `subjectPosition + subjectSize` means the position of the end border 136 // edge of the subject. When it touches the start edge of the scrollport, it 137 // is 100%. 138 nscoord endOffset = subjectPosition + subjectSize - sideInsets.mStart; 139 return Some(ScrollOffsets{startOffset, endOffset}); 140 } 141 142 ScrollTimeline::ScrollOffsets ViewTimeline::ComputeInsets( 143 const ScrollContainerFrame* aScrollContainerFrame, 144 layers::ScrollDirection aOrientation) const { 145 // If view-timeline-inset is auto, it indicates to use the value of 146 // scroll-padding. We use logical dimension to map that start/end offset to 147 // the corresponding scroll-padding-{inline|block}-{start|end} values. 148 const WritingMode wm = 149 aScrollContainerFrame->GetScrolledFrame()->GetWritingMode(); 150 const auto& scrollPadding = 151 LogicalMargin(wm, aScrollContainerFrame->GetScrollPadding()); 152 const bool isBlockAxis = mAxis == StyleScrollAxis::Block || 153 (mAxis == StyleScrollAxis::X && wm.IsVertical()) || 154 (mAxis == StyleScrollAxis::Y && !wm.IsVertical()); 155 156 // The percentages of view-timelne-inset is relative to the corresponding 157 // dimension of the relevant scrollport. 158 // https://drafts.csswg.org/scroll-animations-1/#view-timeline-inset 159 const nsRect scrollPort = aScrollContainerFrame->GetScrollPortRect(); 160 const nscoord percentageBasis = 161 aOrientation == layers::ScrollDirection::eHorizontal ? scrollPort.width 162 : scrollPort.height; 163 164 nscoord startInset = 165 mInset.start.IsAuto() 166 ? (isBlockAxis ? scrollPadding.BStart(wm) : scrollPadding.IStart(wm)) 167 : mInset.start.AsLengthPercentage().Resolve(percentageBasis); 168 nscoord endInset = 169 mInset.end.IsAuto() 170 ? (isBlockAxis ? scrollPadding.BEnd(wm) : scrollPadding.IEnd(wm)) 171 : mInset.end.AsLengthPercentage().Resolve(percentageBasis); 172 return {startInset, endInset}; 173 } 174 175 } // namespace mozilla::dom