Bug 1769628 [wpt PR 34081] - Update wpt metadata, a=testonly
[gecko.git] / dom / animation / ScrollTimeline.h
blob141335e40a6ab5eed86c05ffbeed0bac66053b75
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #ifndef mozilla_dom_ScrollTimeline_h
8 #define mozilla_dom_ScrollTimeline_h
10 #include "mozilla/dom/AnimationTimeline.h"
11 #include "mozilla/dom/Document.h"
12 #include "mozilla/HashTable.h"
13 #include "mozilla/ServoStyleConsts.h"
14 #include "mozilla/TimingParams.h"
15 #include "mozilla/WritingModes.h"
17 class nsIScrollableFrame;
19 namespace mozilla {
21 struct NonOwningAnimationTarget;
23 namespace dom {
25 class Element;
27 /**
28 * Implementation notes
29 * --------------------
31 * ScrollTimelines do not observe refreshes the way DocumentTimelines do.
32 * This is because the refresh driver keeps ticking while it has registered
33 * refresh observers. For a DocumentTimeline, it's appropriate to keep the
34 * refresh driver ticking as long as there are active animations, since the
35 * animations need to be sampled on every frame. Scroll-linked animations,
36 * however, only need to be sampled when scrolling has occurred, so keeping
37 * the refresh driver ticking is wasteful.
39 * As a result, we schedule an animation restyle when
40 * 1) there are any scroll offsets updated (from APZ or script), via
41 * nsIScrollableFrame, or
42 * 2) there are any possible scroll range updated during the frame reflow.
44 * -------------
45 * | Animation |
46 * -------------
47 * ^
48 * | Call Animation::Tick() if there are any scroll updates.
49 * |
50 * ------------------
51 * | ScrollTimeline |
52 * ------------------
53 * ^
54 * | Try schedule the scroll-linked animations, if there are any scroll
55 * | offsets changed or the scroll range changed [1].
56 * |
57 * ----------------------
58 * | nsIScrollableFrame |
59 * ----------------------
61 * [1] nsIScrollableFrame uses its associated dom::Element to lookup the
62 * ScrollTimelineSet, and iterates the set to schedule the animations
63 * linked to the ScrollTimelines.
65 class ScrollTimeline final : public AnimationTimeline {
66 public:
67 struct Scroller {
68 StyleScroller mType = StyleScroller::Root;
69 RefPtr<Element> mElement;
71 // We use the owner doc of the animation target. This may be different from
72 // |mDocument| after we implement ScrollTimeline interface for script.
73 static Scroller Root(const Document* aOwnerDoc) {
74 // For auto, we use scrolling element as the default scroller.
75 // However, it's mutable, and we would like to keep things simple, so
76 // we always register the ScrollTimeline to the document element (i.e.
77 // root element) because the content of the root scroll frame is the root
78 // element.
79 return {StyleScroller::Root, aOwnerDoc->GetDocumentElement()};
82 static Scroller Nearest(Element* aElement) {
83 return {StyleScroller::Nearest, aElement};
86 explicit operator bool() const { return mElement; }
87 bool operator==(const Scroller& aOther) const {
88 return mType == aOther.mType && mElement == aOther.mElement;
92 static already_AddRefed<ScrollTimeline> FromRule(
93 const RawServoScrollTimelineRule& aRule, Document* aDocument,
94 const NonOwningAnimationTarget& aTarget);
96 static already_AddRefed<ScrollTimeline> FromAnonymousScroll(
97 Document* aDocument, const NonOwningAnimationTarget& aTarget,
98 StyleScrollAxis aAxis, StyleScroller aScroller);
100 bool operator==(const ScrollTimeline& aOther) const {
101 return mDocument == aOther.mDocument && mSource == aOther.mSource &&
102 mAxis == aOther.mAxis;
105 NS_DECL_ISUPPORTS_INHERITED
106 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(ScrollTimeline,
107 AnimationTimeline)
109 JSObject* WrapObject(JSContext* aCx,
110 JS::Handle<JSObject*> aGivenProto) override {
111 // FIXME: Bug 1676794: Implement ScrollTimeline interface.
112 return nullptr;
115 // AnimationTimeline methods.
116 Nullable<TimeDuration> GetCurrentTimeAsDuration() const override;
117 bool TracksWallclockTime() const override { return false; }
118 Nullable<TimeDuration> ToTimelineTime(
119 const TimeStamp& aTimeStamp) const override {
120 // It's unclear to us what should we do for this function now, so return
121 // nullptr.
122 return nullptr;
124 TimeStamp ToTimeStamp(const TimeDuration& aTimelineTime) const override {
125 // It's unclear to us what should we do for this function now, so return
126 // zero time.
127 return {};
129 Document* GetDocument() const override { return mDocument; }
130 bool IsMonotonicallyIncreasing() const override { return false; }
131 bool IsScrollTimeline() const override { return true; }
132 const ScrollTimeline* AsScrollTimeline() const override { return this; }
134 void ScheduleAnimations() {
135 // FIXME: Bug 1737927: Need to check the animation mutation observers for
136 // animations with scroll timelines.
137 // nsAutoAnimationMutationBatch mb(mDocument);
139 Tick();
142 // If the source of a ScrollTimeline is an element whose principal box does
143 // not exist or is not a scroll container, then its phase is the timeline
144 // inactive phase. It is otherwise in the active phase. This returns true if
145 // the timeline is in active phase.
146 // https://drafts.csswg.org/web-animations-1/#inactive-timeline
147 // Note: This function is called only for compositor animations, so we must
148 // have the primary frame (principal box) for the source element if it exists.
149 bool IsActive() const { return GetScrollFrame(); }
151 Element* SourceElement() const {
152 MOZ_ASSERT(mSource);
153 return mSource.mElement;
156 // A helper to get the physical orientation of this scroll-timeline.
157 layers::ScrollDirection Axis() const;
159 StyleOverflow SourceScrollStyle() const;
161 bool APZIsActiveForSource() const;
163 bool ScrollingDirectionIsAvailable() const;
165 static constexpr const TimingParams& GetTiming() { return sTiming; }
167 protected:
168 virtual ~ScrollTimeline() { Teardown(); }
170 private:
171 ScrollTimeline() = delete;
172 ScrollTimeline(Document* aDocument, const Scroller& aScroller,
173 StyleScrollAxis aAxis);
175 // Note: This function is required to be idempotent, as it can be called from
176 // both cycleCollection::Unlink() and ~ScrollTimeline(). When modifying this
177 // function, be sure to preserve this property.
178 void Teardown() { UnregisterFromScrollSource(); }
180 // Unregister this scroll timeline to the element property.
181 void UnregisterFromScrollSource();
183 const nsIScrollableFrame* GetScrollFrame() const;
185 RefPtr<Document> mDocument;
187 // FIXME: Bug 1765211: We may have to update the source element once the
188 // overflow property of the scroll-container is updated when we are using
189 // nearest scroller.
190 Scroller mSource;
191 StyleScrollAxis mAxis;
193 // Note: it's unfortunate TimingParams cannot be a const variable because
194 // we have to use StickyTimingDuration::FromMilliseconds() in its
195 // constructor.
196 static TimingParams sTiming;
201 * A wrapper around a hashset of ScrollTimeline objects to handle
202 * storing the set as a property of an element (i.e. source).
203 * This makes use easier to look up a ScrollTimeline from the element.
205 * Note:
206 * 1. "source" is the element which the ScrollTimeline hooks.
207 * Each ScrollTimeline hooks an dom::Element, and a dom::Element may be
208 * registered by multiple ScrollTimelines.
209 * 2. Element holds the ScrollTimelineSet as an element property. Also, the
210 * owner document of this Element keeps a linked list of ScrollTimelines
211 * (instead of ScrollTimelineSet).
213 class ScrollTimelineSet {
214 public:
215 // Use StyleScrollAxis as the key, so we reuse the ScrollTimeline with the
216 // same source and the same direction.
217 // Note: the drawback of using the direction as the key is that we have to
218 // update this once we support more descriptors. This implementation assumes
219 // scroll-offsets will be obsolute. However, I'm pretty sure @scroll-timeline
220 // will be obsolute, based on the spec issue. We may have to do a lot of
221 // updates after the spec updates, so this tentative implmentation should be
222 // enough for now.
223 using NonOwningScrollTimelineMap = HashMap<StyleScrollAxis, ScrollTimeline*>;
225 ~ScrollTimelineSet() = default;
227 static ScrollTimelineSet* GetScrollTimelineSet(Element* aElement);
228 static ScrollTimelineSet* GetOrCreateScrollTimelineSet(Element* aElement);
229 static void DestroyScrollTimelineSet(Element* aElement);
231 NonOwningScrollTimelineMap::AddPtr LookupForAdd(StyleScrollAxis aKey) {
232 return mScrollTimelines.lookupForAdd(aKey);
234 void Add(NonOwningScrollTimelineMap::AddPtr& aPtr, StyleScrollAxis aKey,
235 ScrollTimeline* aScrollTimeline) {
236 Unused << mScrollTimelines.add(aPtr, aKey, aScrollTimeline);
238 void Remove(StyleScrollAxis aKey) { mScrollTimelines.remove(aKey); }
240 bool IsEmpty() const { return mScrollTimelines.empty(); }
242 void ScheduleAnimations() const {
243 for (auto iter = mScrollTimelines.iter(); !iter.done(); iter.next()) {
244 iter.get().value()->ScheduleAnimations();
248 private:
249 ScrollTimelineSet() = default;
251 // ScrollTimelineSet doesn't own ScrollTimeline. We let Animations own its
252 // scroll timeline. (Note: one ScrollTimeline could be owned by multiple
253 // associated Animations.)
254 // The ScrollTimeline is generated only by CSS, so if all the associated
255 // Animations are gone, we don't need the ScrollTimeline anymore, so
256 // ScrollTimelineSet doesn't have to keep it for the source element.
257 // We rely on ScrollTimeline::Teardown() to remove the unused ScrollTimeline
258 // from this hash map.
259 // FIXME: Bug 1676794: We may have to update here if it's possible to create
260 // ScrollTimeline via script.
261 NonOwningScrollTimelineMap mScrollTimelines;
264 } // namespace dom
265 } // namespace mozilla
267 #endif // mozilla_dom_ScrollTimeline_h