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,
* You can obtain one at */
#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)
/* static */
already_AddRefed<ViewTimeline> ViewTimeline::MakeNamed(
Document* aDocument, Element* aSubject, PseudoStyleType aPseudoType,
const StyleViewTimeline& aStyleTimeline) {
// 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,
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.
Maybe<ScrollTimeline::ScrollOffsets> ViewTimeline::ComputeOffsets(
const ScrollContainerFrame* aScrollContainerFrame,
layers::ScrollDirection aOrientation) const {
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 scroll container frame.
const nsIFrame* scrolledFrame = aScrollContainerFrame->GetScrolledFrame();
const nsRect subjectRect(subject->GetOffsetTo(scrolledFrame),
// 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 =
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 =
? (isBlockAxis ? scrollPadding.BStart(wm) : scrollPadding.IStart(wm))
: mInset.start.AsLengthPercentage().Resolve(percentageBasis);
nscoord endInset =
? (isBlockAxis ? scrollPadding.BEnd(wm) : scrollPadding.IEnd(wm))
: mInset.end.AsLengthPercentage().Resolve(percentageBasis);
return {startInset, endInset};
} // namespace mozilla::dom