diff options
Diffstat (limited to 'dom/animation/ViewTimeline.cpp')
-rw-r--r-- | dom/animation/ViewTimeline.cpp | 166 |
1 files changed, 166 insertions, 0 deletions
diff --git a/dom/animation/ViewTimeline.cpp b/dom/animation/ViewTimeline.cpp new file mode 100644 index 0000000000..5727c5f08e --- /dev/null +++ b/dom/animation/ViewTimeline.cpp @@ -0,0 +1,166 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ViewTimeline.h" + +#include "mozilla/dom/Animation.h" +#include "mozilla/dom/ElementInlines.h" +#include "nsIScrollableFrame.h" +#include "nsLayoutUtils.h" + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_INHERITED(ViewTimeline, ScrollTimeline, mSubject) +NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(ViewTimeline, ScrollTimeline) + +/* static */ +already_AddRefed<ViewTimeline> ViewTimeline::MakeNamed( + Document* aDocument, Element* aSubject, PseudoStyleType aPseudoType, + const StyleViewTimeline& aStyleTimeline) { + MOZ_ASSERT(NS_IsMainThread()); + + // 1. Lookup scroller. We have to find the nearest scroller from |aSubject| + // and |aPseudoType|. + auto [element, pseudo] = FindNearestScroller(aSubject, aPseudoType); + auto scroller = Scroller::Nearest(const_cast<Element*>(element), pseudo); + + // 2. Create timeline. + return MakeAndAddRef<ViewTimeline>(aDocument, scroller, + aStyleTimeline.GetAxis(), aSubject, + aPseudoType, aStyleTimeline.GetInset()); +} + +/* static */ +already_AddRefed<ViewTimeline> ViewTimeline::MakeAnonymous( + Document* aDocument, const NonOwningAnimationTarget& aTarget, + StyleScrollAxis aAxis, const StyleViewTimelineInset& aInset) { + // view() finds the nearest scroll container from the animation target. + auto [element, pseudo] = + FindNearestScroller(aTarget.mElement, aTarget.mPseudoType); + Scroller scroller = Scroller::Nearest(const_cast<Element*>(element), pseudo); + return MakeAndAddRef<ViewTimeline>(aDocument, scroller, aAxis, + aTarget.mElement, aTarget.mPseudoType, + aInset); +} + +void ViewTimeline::ReplacePropertiesWith(Element* aSubjectElement, + PseudoStyleType aPseudoType, + const StyleViewTimeline& aNew) { + mSubject = aSubjectElement; + mSubjectPseudoType = aPseudoType; + mAxis = aNew.GetAxis(); + // FIXME: Bug 1817073. We assume it is a non-animatable value for now. + mInset = aNew.GetInset(); + + for (auto* anim = mAnimationOrder.getFirst(); anim; + anim = static_cast<LinkedListElement<Animation>*>(anim)->getNext()) { + MOZ_ASSERT(anim->GetTimeline() == this); + // Set this so we just PostUpdate() for this animation. + anim->SetTimeline(this); + } +} + +Maybe<ScrollTimeline::ScrollOffsets> ViewTimeline::ComputeOffsets( + const nsIScrollableFrame* aScrollFrame, + layers::ScrollDirection aOrientation) const { + MOZ_ASSERT(mSubject); + MOZ_ASSERT(aScrollFrame); + + const Element* subjectElement = + AnimationUtils::GetElementForRestyle(mSubject, mSubjectPseudoType); + const nsIFrame* subject = subjectElement->GetPrimaryFrame(); + if (!subject) { + // No principal box of the subject, so we cannot compute the offset. This + // may happen when we clear all animation collections during unbinding from + // the tree. + return Nothing(); + } + + // In order to get the distance between the subject and the scrollport + // properly, we use the position based on the domain of the scrolled frame, + // instead of the scrollable frame. + const nsIFrame* scrolledFrame = aScrollFrame->GetScrolledFrame(); + MOZ_ASSERT(scrolledFrame); + const nsRect subjectRect(subject->GetOffsetTo(scrolledFrame), + subject->GetSize()); + + // Use scrollport size (i.e. padding box size - scrollbar size), which is used + // for calculating the view progress visibility range. + // https://drafts.csswg.org/scroll-animations/#view-progress-visibility-range + const nsRect scrollPort = aScrollFrame->GetScrollPortRect(); + + // Adjuct the positions and sizes based on the physical axis. + nscoord subjectPosition = subjectRect.y; + nscoord subjectSize = subjectRect.height; + nscoord scrollPortSize = scrollPort.height; + if (aOrientation == layers::ScrollDirection::eHorizontal) { + // |subjectPosition| should be the position of the start border edge of the + // subject, so for R-L case, we have to use XMost() as the start border + // edge of the subject, and compute its position by using the x-most side of + // the scrolled frame as the origin on the horizontal axis. + subjectPosition = scrolledFrame->GetWritingMode().IsPhysicalRTL() + ? scrolledFrame->GetSize().width - subjectRect.XMost() + : subjectRect.x; + subjectSize = subjectRect.width; + scrollPortSize = scrollPort.width; + } + + // |sideInsets.mEnd| is used to adjust the start offset, and + // |sideInsets.mStart| is used to adjust the end offset. This is because + // |sideInsets.mStart| refers to logical start side [1] of the source box + // (i.e. the box of the scrollport), where as |startOffset| refers to the + // start of the timeline, and similarly for end side/offset. [1] + // https://drafts.csswg.org/css-writing-modes-4/#css-start + const auto sideInsets = ComputeInsets(aScrollFrame, aOrientation); + + // Basically, we are computing the "cover" timeline range name, which + // represents the full range of the view progress timeline. + // https://drafts.csswg.org/scroll-animations-1/#valdef-animation-timeline-range-cover + + // Note: `subjectPosition - scrollPortSize` means the distance between the + // start border edge of the subject and the end edge of the scrollport. + nscoord startOffset = subjectPosition - scrollPortSize + sideInsets.mEnd; + // Note: `subjectPosition + subjectSize` means the position of the end border + // edge of the subject. When it touches the start edge of the scrollport, it + // is 100%. + nscoord endOffset = subjectPosition + subjectSize - sideInsets.mStart; + return Some(ScrollOffsets{startOffset, endOffset}); +} + +ScrollTimeline::ScrollOffsets ViewTimeline::ComputeInsets( + const nsIScrollableFrame* aScrollFrame, + layers::ScrollDirection aOrientation) const { + // If view-timeline-inset is auto, it indicates to use the value of + // scroll-padding. We use logical dimension to map that start/end offset to + // the corresponding scroll-padding-{inline|block}-{start|end} values. + const WritingMode wm = aScrollFrame->GetScrolledFrame()->GetWritingMode(); + const auto& scrollPadding = + LogicalMargin(wm, aScrollFrame->GetScrollPadding()); + const bool isBlockAxis = + mAxis == StyleScrollAxis::Block || + (mAxis == StyleScrollAxis::Horizontal && wm.IsVertical()) || + (mAxis == StyleScrollAxis::Vertical && !wm.IsVertical()); + + // The percentages of view-timelne-inset is relative to the corresponding + // dimension of the relevant scrollport. + // https://drafts.csswg.org/scroll-animations-1/#view-timeline-inset + const nsRect scrollPort = aScrollFrame->GetScrollPortRect(); + const nscoord percentageBasis = + aOrientation == layers::ScrollDirection::eHorizontal ? scrollPort.width + : scrollPort.height; + + nscoord startInset = + mInset.start.IsAuto() + ? (isBlockAxis ? scrollPadding.BStart(wm) : scrollPadding.IStart(wm)) + : mInset.start.AsLengthPercentage().Resolve(percentageBasis); + nscoord endInset = + mInset.end.IsAuto() + ? (isBlockAxis ? scrollPadding.BEnd(wm) : scrollPadding.IEnd(wm)) + : mInset.end.AsLengthPercentage().Resolve(percentageBasis); + return {startInset, endInset}; +} + +} // namespace mozilla::dom |