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
;
21 struct NonOwningAnimationTarget
;
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.
48 * | Call Animation::Tick() if there are any scroll updates.
54 * | Try schedule the scroll-linked animations, if there are any scroll
55 * | offsets changed or the scroll range changed [1].
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
{
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
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
,
109 JSObject
* WrapObject(JSContext
* aCx
,
110 JS::Handle
<JSObject
*> aGivenProto
) override
{
111 // FIXME: Bug 1676794: Implement ScrollTimeline interface.
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
124 TimeStamp
ToTimeStamp(const TimeDuration
& aTimelineTime
) const override
{
125 // It's unclear to us what should we do for this function now, so 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);
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 {
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
; }
168 virtual ~ScrollTimeline() { Teardown(); }
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
191 StyleScrollAxis mAxis
;
193 // Note: it's unfortunate TimingParams cannot be a const variable because
194 // we have to use StickyTimingDuration::FromMilliseconds() in its
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.
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
{
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
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();
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
;
265 } // namespace mozilla
267 #endif // mozilla_dom_ScrollTimeline_h