diff options
Diffstat (limited to '')
-rw-r--r-- | dom/animation/ScrollTimeline.h | 285 |
1 files changed, 285 insertions, 0 deletions
diff --git a/dom/animation/ScrollTimeline.h b/dom/animation/ScrollTimeline.h new file mode 100644 index 0000000000..58ab9586d9 --- /dev/null +++ b/dom/animation/ScrollTimeline.h @@ -0,0 +1,285 @@ +/* -*- 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 { +class ElementAnimationData; +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 : public AnimationTimeline { + template <typename T, typename... Args> + friend already_AddRefed<T> mozilla::MakeAndAddRef(Args&&... aArgs); + + public: + struct Scroller { + // FIXME: Bug 1765211. Perhaps we only need root and a specific element. + // This depends on how we fix this bug. + enum class Type : uint8_t { + Root, + Nearest, + Name, + Self, + }; + Type mType = Type::Root; + RefPtr<Element> mElement; + PseudoStyleType mPseudoType; + + // 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(), + PseudoStyleType::NotPseudo}; + } + + static Scroller Nearest(Element* aElement, PseudoStyleType aPseudoType) { + return {Type::Nearest, aElement, aPseudoType}; + } + + static Scroller Named(Element* aElement, PseudoStyleType aPseudoType) { + return {Type::Name, aElement, aPseudoType}; + } + + static Scroller Self(Element* aElement, PseudoStyleType aPseudoType) { + return {Type::Self, aElement, aPseudoType}; + } + + explicit operator bool() const { return mElement; } + bool operator==(const Scroller& aOther) const { + return mType == aOther.mType && mElement == aOther.mElement && + mPseudoType == aOther.mPseudoType; + } + }; + + static already_AddRefed<ScrollTimeline> MakeAnonymous( + Document* aDocument, const NonOwningAnimationTarget& aTarget, + StyleScrollAxis aAxis, StyleScroller aScroller); + + // Note: |aReferfenceElement| is used as the scroller which specifies + // scroll-timeline-name property. + static already_AddRefed<ScrollTimeline> MakeNamed( + Document* aDocument, Element* aReferenceElement, + PseudoStyleType aPseudoType, const StyleScrollTimeline& aStyleTimeline); + + bool operator==(const ScrollTimeline& aOther) const { + return mDocument == aOther.mDocument && mSource == aOther.mSource && + mAxis == aOther.mAxis; + } + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_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; } + bool IsViewTimeline() const override { return false; } + + 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); + TickState state; + Tick(state); + // TODO: Do we need to synchronize scroll animations? + } + + // 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; + + void ReplacePropertiesWith(const Element* aReferenceElement, + PseudoStyleType aPseudoType, + const StyleScrollTimeline& aNew); + + void NotifyAnimationContentVisibilityChanged(Animation* aAnimation, + bool aIsVisible) override; + + protected: + virtual ~ScrollTimeline() { Teardown(); } + ScrollTimeline() = delete; + ScrollTimeline(Document* aDocument, const Scroller& aScroller, + StyleScrollAxis aAxis); + + struct ScrollOffsets { + nscoord mStart = 0; + nscoord mEnd = 0; + }; + virtual Maybe<ScrollOffsets> ComputeOffsets( + const nsIScrollableFrame* aScrollFrame, + layers::ScrollDirection aOrientation) const; + + // 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(); } + + // Register this scroll timeline to the element property. + void RegisterWithScrollSource(); + + // Unregister this scroll timeline from the element property. + void UnregisterFromScrollSource(); + + const nsIScrollableFrame* GetScrollFrame() const; + + static std::pair<const Element*, PseudoStyleType> FindNearestScroller( + Element* aSubject, PseudoStyleType aPseudoType); + + 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 the scheduling + * of scroll driven animations. This is used for all kinds of progress + * timelines, i.e. anonymous/named scroll timelines and anonymous/named view + * timelines. And this object is owned by the scroll source (See + * ElementAnimationData and nsGfxScrollFrame for the usage). + */ +class ProgressTimelineScheduler { + public: + ProgressTimelineScheduler() { MOZ_COUNT_CTOR(ProgressTimelineScheduler); } + ~ProgressTimelineScheduler() { MOZ_COUNT_DTOR(ProgressTimelineScheduler); } + + static ProgressTimelineScheduler* Get(const Element* aElement, + PseudoStyleType aPseudoType); + static ProgressTimelineScheduler& Ensure(Element* aElement, + PseudoStyleType aPseudoType); + static void Destroy(const Element* aElement, PseudoStyleType aPseudoType); + + void AddTimeline(ScrollTimeline* aScrollTimeline) { + Unused << mTimelines.put(aScrollTimeline); + } + void RemoveTimeline(ScrollTimeline* aScrollTimeline) { + mTimelines.remove(aScrollTimeline); + } + + bool IsEmpty() const { return mTimelines.empty(); } + + void ScheduleAnimations() const { + for (auto iter = mTimelines.iter(); !iter.done(); iter.next()) { + iter.get()->ScheduleAnimations(); + } + } + + private: + // We let Animations own its scroll timeline or view timeline if it is + // anonymous. For named progress timelines, they are created and destroyed by + // TimelineCollection. + HashSet<ScrollTimeline*> mTimelines; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_ScrollTimeline_h |