summaryrefslogtreecommitdiffstats
path: root/dom/animation/ScrollTimeline.h
blob: c1fa72f5b6ed5f2cba60242a409a328e1b7fa42f (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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
/* -*- 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/. */

#ifndef mozilla_dom_ScrollTimeline_h
#define mozilla_dom_ScrollTimeline_h

#include "mozilla/dom/AnimationTimeline.h"
#include "mozilla/dom/Document.h"
#include "mozilla/HashTable.h"
#include "mozilla/PairHash.h"
#include "mozilla/ServoStyleConsts.h"
#include "mozilla/WritingModes.h"

#define PROGRESS_TIMELINE_DURATION_MILLISEC 100000

class nsIScrollableFrame;

namespace mozilla {

struct NonOwningAnimationTarget;

namespace dom {

class Element;

/**
 * Implementation notes
 * --------------------
 *
 * ScrollTimelines do not observe refreshes the way DocumentTimelines do.
 * This is because the refresh driver keeps ticking while it has registered
 * refresh observers. For a DocumentTimeline, it's appropriate to keep the
 * refresh driver ticking as long as there are active animations, since the
 * animations need to be sampled on every frame. Scroll-linked animations,
 * however, only need to be sampled when scrolling has occurred, so keeping
 * the refresh driver ticking is wasteful.
 *
 * As a result, we schedule an animation restyle when
 * 1) there are any scroll offsets updated (from APZ or script), via
 *    nsIScrollableFrame, or
 * 2) there are any possible scroll range updated during the frame reflow.
 *
 * -------------
 * | Animation |
 * -------------
 *   ^
 *   | Call Animation::Tick() if there are any scroll updates.
 *   |
 * ------------------
 * | ScrollTimeline |
 * ------------------
 *   ^
 *   | Try schedule the scroll-driven animations, if there are any scroll
 *   | offsets changed or the scroll range changed [1].
 *   |
 * ----------------------
 * | nsIScrollableFrame |
 * ----------------------
 *
 * [1] nsIScrollableFrame uses its associated dom::Element to lookup the
 *     ScrollTimelineSet, and iterates the set to schedule the animations
 *     linked to the ScrollTimelines.
 */
class ScrollTimeline final : public AnimationTimeline {
 public:
  struct Scroller {
    // FIXME: Once we support <custom-ident> for <scroller>, we can use
    // StyleScroller here.
    // https://drafts.csswg.org/scroll-animations-1/#typedef-scroller
    enum class Type : uint8_t {
      Root,
      Nearest,
      Name,
    };
    Type mType = Type::Root;
    RefPtr<Element> mElement;

    // We use the owner doc of the animation target. This may be different from
    // |mDocument| after we implement ScrollTimeline interface for script.
    static Scroller Root(const Document* aOwnerDoc) {
      // For auto, we use scrolling element as the default scroller.
      // However, it's mutable, and we would like to keep things simple, so
      // we always register the ScrollTimeline to the document element (i.e.
      // root element) because the content of the root scroll frame is the root
      // element.
      return {Type::Root, aOwnerDoc->GetDocumentElement()};
    }

    static Scroller Nearest(Element* aElement) {
      return {Type::Nearest, aElement};
    }

    static Scroller Named(Element* aElement) { return {Type::Name, aElement}; }

    explicit operator bool() const { return mElement; }
    bool operator==(const Scroller& aOther) const {
      return mType == aOther.mType && mElement == aOther.mElement;
    }
  };

  static already_AddRefed<ScrollTimeline> FromAnonymousScroll(
      Document* aDocument, const NonOwningAnimationTarget& aTarget,
      StyleScrollAxis aAxis, StyleScroller aScroller);

  static already_AddRefed<ScrollTimeline> FromNamedScroll(
      Document* aDocument, const NonOwningAnimationTarget& aTarget,
      const nsAtom* aName);

  bool operator==(const ScrollTimeline& aOther) const {
    return mDocument == aOther.mDocument && mSource == aOther.mSource &&
           mAxis == aOther.mAxis;
  }

  NS_DECL_ISUPPORTS_INHERITED
  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(ScrollTimeline,
                                                         AnimationTimeline)

  JSObject* WrapObject(JSContext* aCx,
                       JS::Handle<JSObject*> aGivenProto) override {
    // FIXME: Bug 1676794: Implement ScrollTimeline interface.
    return nullptr;
  }

  // AnimationTimeline methods.
  Nullable<TimeDuration> GetCurrentTimeAsDuration() const override;
  bool TracksWallclockTime() const override { return false; }
  Nullable<TimeDuration> ToTimelineTime(
      const TimeStamp& aTimeStamp) const override {
    // It's unclear to us what should we do for this function now, so return
    // nullptr.
    return nullptr;
  }
  TimeStamp ToTimeStamp(const TimeDuration& aTimelineTime) const override {
    // It's unclear to us what should we do for this function now, so return
    // zero time.
    return {};
  }
  Document* GetDocument() const override { return mDocument; }
  bool IsMonotonicallyIncreasing() const override { return false; }
  bool IsScrollTimeline() const override { return true; }
  const ScrollTimeline* AsScrollTimeline() const override { return this; }
  Nullable<TimeDuration> TimelineDuration() const override {
    // We are using this magic number for progress-based timeline duration
    // because we don't support percentage for duration.
    return TimeDuration::FromMilliseconds(PROGRESS_TIMELINE_DURATION_MILLISEC);
  }

  void ScheduleAnimations() {
    // FIXME: Bug 1737927: Need to check the animation mutation observers for
    // animations with scroll timelines.
    // nsAutoAnimationMutationBatch mb(mDocument);

    Tick();
  }

  // If the source of a ScrollTimeline is an element whose principal box does
  // not exist or is not a scroll container, then its phase is the timeline
  // inactive phase. It is otherwise in the active phase. This returns true if
  // the timeline is in active phase.
  // https://drafts.csswg.org/web-animations-1/#inactive-timeline
  // Note: This function is called only for compositor animations, so we must
  // have the primary frame (principal box) for the source element if it exists.
  bool IsActive() const { return GetScrollFrame(); }

  Element* SourceElement() const {
    MOZ_ASSERT(mSource);
    return mSource.mElement;
  }

  // A helper to get the physical orientation of this scroll-timeline.
  layers::ScrollDirection Axis() const;

  StyleOverflow SourceScrollStyle() const;

  bool APZIsActiveForSource() const;

  bool ScrollingDirectionIsAvailable() const;

 protected:
  virtual ~ScrollTimeline() { Teardown(); }

 private:
  ScrollTimeline() = delete;
  ScrollTimeline(Document* aDocument, const Scroller& aScroller,
                 StyleScrollAxis aAxis);

  static already_AddRefed<ScrollTimeline> GetOrCreateScrollTimeline(
      Document* aDocument, const Scroller& aScroller,
      const StyleScrollAxis& aAxis);

  // Note: This function is required to be idempotent, as it can be called from
  // both cycleCollection::Unlink() and ~ScrollTimeline(). When modifying this
  // function, be sure to preserve this property.
  void Teardown() { UnregisterFromScrollSource(); }

  // Unregister this scroll timeline to the element property.
  void UnregisterFromScrollSource();

  const nsIScrollableFrame* GetScrollFrame() const;

  RefPtr<Document> mDocument;

  // FIXME: Bug 1765211: We may have to update the source element once the
  // overflow property of the scroll-container is updated when we are using
  // nearest scroller.
  Scroller mSource;
  StyleScrollAxis mAxis;
};

/**
 * A wrapper around a hashset of ScrollTimeline objects to handle
 * storing the set as a property of an element (i.e. source).
 * This makes use easier to look up a ScrollTimeline from the element.
 *
 * Note:
 * 1. "source" is the element which the ScrollTimeline hooks.
 *    Each ScrollTimeline hooks an dom::Element, and a dom::Element may be
 *    registered by multiple ScrollTimelines.
 * 2. Element holds the ScrollTimelineSet as an element property. Also, the
 *    owner document of this Element keeps a linked list of ScrollTimelines
 *    (instead of ScrollTimelineSet).
 */
class ScrollTimelineSet {
 public:
  // In order to reuse the ScrollTimeline with the same scroller and the same
  // axis, we define a special key for it.
  using Key = std::pair<ScrollTimeline::Scroller::Type, StyleScrollAxis>;
  using NonOwningScrollTimelineMap =
      HashMap<Key, ScrollTimeline*,
              PairHasher<ScrollTimeline::Scroller::Type, StyleScrollAxis>>;

  ~ScrollTimelineSet() = default;

  static ScrollTimelineSet* GetScrollTimelineSet(Element* aElement);
  static ScrollTimelineSet* GetOrCreateScrollTimelineSet(Element* aElement);
  static void DestroyScrollTimelineSet(Element* aElement);

  NonOwningScrollTimelineMap::AddPtr LookupForAdd(Key aKey) {
    return mScrollTimelines.lookupForAdd(aKey);
  }
  void Add(NonOwningScrollTimelineMap::AddPtr& aPtr, Key aKey,
           ScrollTimeline* aScrollTimeline) {
    Unused << mScrollTimelines.add(aPtr, aKey, aScrollTimeline);
  }
  void Remove(const Key aKey) { mScrollTimelines.remove(aKey); }

  bool IsEmpty() const { return mScrollTimelines.empty(); }

  void ScheduleAnimations() const {
    for (auto iter = mScrollTimelines.iter(); !iter.done(); iter.next()) {
      iter.get().value()->ScheduleAnimations();
    }
  }

 private:
  ScrollTimelineSet() = default;

  // ScrollTimelineSet doesn't own ScrollTimeline. We let Animations own its
  // scroll timeline. (Note: one ScrollTimeline could be owned by multiple
  // associated Animations.)
  // The ScrollTimeline is generated only by CSS, so if all the associated
  // Animations are gone, we don't need the ScrollTimeline anymore, so
  // ScrollTimelineSet doesn't have to keep it for the source element.
  // We rely on ScrollTimeline::Teardown() to remove the unused ScrollTimeline
  // from this hash map.
  // FIXME: Bug 1676794: We may have to update here if it's possible to create
  // ScrollTimeline via script.
  NonOwningScrollTimelineMap mScrollTimelines;
};

}  // namespace dom
}  // namespace mozilla

#endif  // mozilla_dom_ScrollTimeline_h