Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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,
#include "ViewTimeline.h"
#include "mozilla/dom/Animation.h"
#include "mozilla/dom/ElementInlines.h"
#include "mozilla/ScrollContainerFrame.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,
const PseudoStyleRequest& aPseudoRequest,
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, aPseudoRequest);
auto scroller =
Scroller::Nearest(const_cast<Element*>(element), pseudo.mType);
// 2. Create timeline.
return MakeAndAddRef<ViewTimeline>(
aDocument, scroller, aStyleTimeline.GetAxis(), aSubject,
aPseudoRequest.mType, 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.mPseudoRequest);
Scroller scroller =
Scroller::Nearest(const_cast<Element*>(element), pseudo.mType);
return MakeAndAddRef<ViewTimeline>(aDocument, scroller, aAxis,
aTarget.mElement,
aTarget.mPseudoRequest.mType, aInset);
}
void ViewTimeline::ReplacePropertiesWith(
Element* aSubjectElement, const PseudoStyleRequest& aPseudoRequest,
const StyleViewTimeline& aNew) {
mSubject = aSubjectElement;
mSubjectPseudoType = aPseudoRequest.mType;
mAxis = aNew.GetAxis();
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 ScrollContainerFrame* aScrollContainerFrame,
layers::ScrollDirection aOrientation) const {
MOZ_ASSERT(mSubject);
MOZ_ASSERT(aScrollContainerFrame);
const Element* subjectElement =
mSubject->GetPseudoElement(PseudoStyleRequest(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 scroll container frame.
const nsIFrame* scrolledFrame = aScrollContainerFrame->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.
const nsRect scrollPort = aScrollContainerFrame->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]
const auto sideInsets = ComputeInsets(aScrollContainerFrame, aOrientation);
// Basically, we are computing the "cover" timeline range name, which
// represents the full range of the view progress timeline.
// 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 ScrollContainerFrame* aScrollContainerFrame,
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 =
aScrollContainerFrame->GetScrolledFrame()->GetWritingMode();
const auto& scrollPadding =
LogicalMargin(wm, aScrollContainerFrame->GetScrollPadding());
const bool isBlockAxis = mAxis == StyleScrollAxis::Block ||
(mAxis == StyleScrollAxis::X && wm.IsVertical()) ||
(mAxis == StyleScrollAxis::Y && !wm.IsVertical());
// The percentages of view-timelne-inset is relative to the corresponding
// dimension of the relevant scrollport.
const nsRect scrollPort = aScrollContainerFrame->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