/* -*- 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 friend already_AddRefed 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 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 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 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 aGivenProto) override { // FIXME: Bug 1676794: Implement ScrollTimeline interface. return nullptr; } // AnimationTimeline methods. Nullable GetCurrentTimeAsDuration() const override; bool TracksWallclockTime() const override { return false; } Nullable 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 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; void ReplacePropertiesWith(const Element* aReferenceElement, PseudoStyleType aPseudoType, const StyleScrollTimeline& aNew); protected: virtual ~ScrollTimeline() { Teardown(); } ScrollTimeline() = delete; ScrollTimeline(Document* aDocument, const Scroller& aScroller, StyleScrollAxis aAxis); struct ScrollOffsets { nscoord mStart = 0; nscoord mEnd = 0; }; virtual Maybe 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 FindNearestScroller( Element* aSubject, PseudoStyleType aPseudoType); RefPtr 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 mTimelines; }; } // namespace dom } // namespace mozilla #endif // mozilla_dom_ScrollTimeline_h