/* -*- 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/. */ #include "ScrollTimeline.h" #include "mozilla/dom/Animation.h" #include "mozilla/dom/ElementInlines.h" #include "mozilla/AnimationTarget.h" #include "mozilla/DisplayPortUtils.h" #include "mozilla/ElementAnimationData.h" #include "mozilla/PresShell.h" #include "nsIFrame.h" #include "nsIScrollableFrame.h" #include "nsLayoutUtils.h" namespace mozilla::dom { // --------------------------------- // Methods of ScrollTimeline // --------------------------------- NS_IMPL_CYCLE_COLLECTION_CLASS(ScrollTimeline) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ScrollTimeline, AnimationTimeline) tmp->Teardown(); NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument) NS_IMPL_CYCLE_COLLECTION_UNLINK(mSource.mElement) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ScrollTimeline, AnimationTimeline) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSource.mElement) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(ScrollTimeline, AnimationTimeline) ScrollTimeline::ScrollTimeline(Document* aDocument, const Scroller& aScroller, StyleScrollAxis aAxis) : AnimationTimeline(aDocument->GetParentObject(), aDocument->GetScopeObject()->GetRTPCallerType()), mDocument(aDocument), mSource(aScroller), mAxis(aAxis) { MOZ_ASSERT(aDocument); RegisterWithScrollSource(); } /* static */ std::pair ScrollTimeline::FindNearestScroller(Element* aSubject, PseudoStyleType aPseudoType) { MOZ_ASSERT(aSubject); Element* subject = AnimationUtils::GetElementForRestyle(aSubject, aPseudoType); Element* curr = subject->GetFlattenedTreeParentElement(); Element* root = subject->OwnerDoc()->GetDocumentElement(); while (curr && curr != root) { const ComputedStyle* style = Servo_Element_GetMaybeOutOfDateStyle(curr); MOZ_ASSERT(style, "The ancestor should be styled."); if (style->StyleDisplay()->IsScrollableOverflow()) { break; } curr = curr->GetFlattenedTreeParentElement(); } // If there is no scroll container, we use root. if (!curr) { return {root, PseudoStyleType::NotPseudo}; } return AnimationUtils::GetElementPseudoPair(curr); } /* static */ already_AddRefed ScrollTimeline::MakeAnonymous( Document* aDocument, const NonOwningAnimationTarget& aTarget, StyleScrollAxis aAxis, StyleScroller aScroller) { MOZ_ASSERT(aTarget); Scroller scroller; switch (aScroller) { case StyleScroller::Root: scroller = Scroller::Root(aTarget.mElement->OwnerDoc()); break; case StyleScroller::Nearest: { auto [element, pseudo] = FindNearestScroller(aTarget.mElement, aTarget.mPseudoType); scroller = Scroller::Nearest(const_cast(element), pseudo); break; } case StyleScroller::SelfElement: scroller = Scroller::Self(aTarget.mElement, aTarget.mPseudoType); break; } // Each use of scroll() corresponds to its own instance of ScrollTimeline in // the Web Animations API, even if multiple elements use scroll() to refer to // the same scroll container with the same arguments. // https://drafts.csswg.org/scroll-animations-1/#scroll-notation return MakeAndAddRef(aDocument, scroller, aAxis); } /* static*/ already_AddRefed ScrollTimeline::MakeNamed( Document* aDocument, Element* aReferenceElement, PseudoStyleType aPseudoType, const StyleScrollTimeline& aStyleTimeline) { MOZ_ASSERT(NS_IsMainThread()); Scroller scroller = Scroller::Named(aReferenceElement, aPseudoType); return MakeAndAddRef(aDocument, std::move(scroller), aStyleTimeline.GetAxis()); } Nullable ScrollTimeline::GetCurrentTimeAsDuration() const { // If no layout box, this timeline is inactive. if (!mSource || !mSource.mElement->GetPrimaryFrame()) { return nullptr; } // if this is not a scroller container, this timeline is inactive. const nsIScrollableFrame* scrollFrame = GetScrollFrame(); if (!scrollFrame) { return nullptr; } const auto orientation = Axis(); // If there is no scrollable overflow, then the ScrollTimeline is inactive. // https://drafts.csswg.org/scroll-animations-1/#scrolltimeline-interface if (!scrollFrame->GetAvailableScrollingDirections().contains(orientation)) { return nullptr; } const bool isHorizontal = orientation == layers::ScrollDirection::eHorizontal; const nsPoint& scrollPosition = scrollFrame->GetScrollPosition(); const Maybe& offsets = ComputeOffsets(scrollFrame, orientation); if (!offsets) { return nullptr; } // Note: For RTL, scrollPosition.x or scrollPosition.y may be negative, // e.g. the range of its value is [0, -range], so we have to use the // absolute value. nscoord position = std::abs(isHorizontal ? scrollPosition.x : scrollPosition.y); double progress = static_cast(position - offsets->mStart) / static_cast(offsets->mEnd - offsets->mStart); return TimeDuration::FromMilliseconds(progress * PROGRESS_TIMELINE_DURATION_MILLISEC); } layers::ScrollDirection ScrollTimeline::Axis() const { MOZ_ASSERT(mSource && mSource.mElement->GetPrimaryFrame()); const WritingMode wm = mSource.mElement->GetPrimaryFrame()->GetWritingMode(); return mAxis == StyleScrollAxis::Horizontal || (!wm.IsVertical() && mAxis == StyleScrollAxis::Inline) || (wm.IsVertical() && mAxis == StyleScrollAxis::Block) ? layers::ScrollDirection::eHorizontal : layers::ScrollDirection::eVertical; } StyleOverflow ScrollTimeline::SourceScrollStyle() const { MOZ_ASSERT(mSource && mSource.mElement->GetPrimaryFrame()); const nsIScrollableFrame* scrollFrame = GetScrollFrame(); MOZ_ASSERT(scrollFrame); const ScrollStyles scrollStyles = scrollFrame->GetScrollStyles(); return Axis() == layers::ScrollDirection::eHorizontal ? scrollStyles.mHorizontal : scrollStyles.mVertical; } bool ScrollTimeline::APZIsActiveForSource() const { MOZ_ASSERT(mSource); return gfxPlatform::AsyncPanZoomEnabled() && !nsLayoutUtils::ShouldDisableApzForElement(mSource.mElement) && DisplayPortUtils::HasNonMinimalNonZeroDisplayPort(mSource.mElement); } bool ScrollTimeline::ScrollingDirectionIsAvailable() const { const nsIScrollableFrame* scrollFrame = GetScrollFrame(); MOZ_ASSERT(scrollFrame); return scrollFrame->GetAvailableScrollingDirections().contains(Axis()); } void ScrollTimeline::ReplacePropertiesWith(const Element* aReferenceElement, PseudoStyleType aPseudoType, const StyleScrollTimeline& aNew) { MOZ_ASSERT(aReferenceElement == mSource.mElement && aPseudoType == mSource.mPseudoType); mAxis = aNew.GetAxis(); for (auto* anim = mAnimationOrder.getFirst(); anim; anim = static_cast*>(anim)->getNext()) { MOZ_ASSERT(anim->GetTimeline() == this); // Set this so we just PostUpdate() for this animation. anim->SetTimeline(this); } } Maybe ScrollTimeline::ComputeOffsets( const nsIScrollableFrame* aScrollFrame, layers::ScrollDirection aOrientation) const { const nsRect& scrollRange = aScrollFrame->GetScrollRange(); nscoord range = aOrientation == layers::ScrollDirection::eHorizontal ? scrollRange.width : scrollRange.height; MOZ_ASSERT(range > 0); return Some(ScrollOffsets{0, range}); } void ScrollTimeline::RegisterWithScrollSource() { if (!mSource) { return; } auto& scheduler = ProgressTimelineScheduler::Ensure(mSource.mElement, mSource.mPseudoType); scheduler.AddTimeline(this); } void ScrollTimeline::UnregisterFromScrollSource() { if (!mSource) { return; } auto* scheduler = ProgressTimelineScheduler::Get(mSource.mElement, mSource.mPseudoType); if (!scheduler) { return; } scheduler->RemoveTimeline(this); if (scheduler->IsEmpty()) { ProgressTimelineScheduler::Destroy(mSource.mElement, mSource.mPseudoType); } } const nsIScrollableFrame* ScrollTimeline::GetScrollFrame() const { if (!mSource) { return nullptr; } switch (mSource.mType) { case Scroller::Type::Root: if (const PresShell* presShell = mSource.mElement->OwnerDoc()->GetPresShell()) { return presShell->GetRootScrollFrameAsScrollable(); } return nullptr; case Scroller::Type::Nearest: case Scroller::Type::Name: case Scroller::Type::Self: return nsLayoutUtils::FindScrollableFrameFor(mSource.mElement); } MOZ_ASSERT_UNREACHABLE("Unsupported scroller type"); return nullptr; } // ------------------------------------ // Methods of ProgressTimelineScheduler // ------------------------------------ /* static */ ProgressTimelineScheduler* ProgressTimelineScheduler::Get( const Element* aElement, PseudoStyleType aPseudoType) { MOZ_ASSERT(aElement); auto* data = aElement->GetAnimationData(); if (!data) { return nullptr; } return data->GetProgressTimelineScheduler(aPseudoType); } /* static */ ProgressTimelineScheduler& ProgressTimelineScheduler::Ensure( Element* aElement, PseudoStyleType aPseudoType) { MOZ_ASSERT(aElement); return aElement->EnsureAnimationData().EnsureProgressTimelineScheduler( *aElement, aPseudoType); } /* static */ void ProgressTimelineScheduler::Destroy(const Element* aElement, PseudoStyleType aPseudoType) { auto* data = aElement->GetAnimationData(); MOZ_ASSERT(data); data->ClearProgressTimelineScheduler(aPseudoType); } } // namespace mozilla::dom