/* -*- 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/TimingParams.h" #include "mozilla/WritingModes.h" 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-linked 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 for , we can use // StyleScroller here. // https://drafts.csswg.org/scroll-animations-1/rewrite#typedef-scroller enum class Type : uint8_t { Root, Nearest, Name, }; Type mType = Type::Root; RefPtr 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 FromRule( const RawServoScrollTimelineRule& aRule, Document* aDocument, const NonOwningAnimationTarget& aTarget); static already_AddRefed FromAnonymousScroll( Document* aDocument, const NonOwningAnimationTarget& aTarget, StyleScrollAxis aAxis, StyleScroller aScroller); static already_AddRefed 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 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; } 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; static constexpr const TimingParams& GetTiming() { return sTiming; } protected: virtual ~ScrollTimeline() { Teardown(); } private: ScrollTimeline() = delete; ScrollTimeline(Document* aDocument, const Scroller& aScroller, StyleScrollAxis aAxis); static already_AddRefed 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 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; // Note: it's unfortunate TimingParams cannot be a const variable because // we have to use StickyTimingDuration::FromMilliseconds() in its // constructor. static TimingParams sTiming; }; /** * 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; using NonOwningScrollTimelineMap = HashMap>; ~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