summaryrefslogtreecommitdiffstats
path: root/dom/animation/ViewTimeline.cpp
blob: 5727c5f08e71ba4730c7c57cb2b1af32ff018895 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
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