tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit d1ffd2aea20bc5548dfc526734aaf2b0fec542d6
parent 10f1e6fd496063286b46bbe33a9ce525f5e2f90c
Author: Boris Chiou <boris.chiou@gmail.com>
Date:   Tue, 16 Dec 2025 07:13:40 +0000

Bug 1817051 - Part 2: Add scroll timeline into AnimationTimelinesController. r=hiro,layout-reviewers

In order to sample scroll timelines together with document timelines, we
add scroll timelines into AnimationTimelinesController as well.

We will sampling for scroll timelines in the following patches.

Differential Revision: https://phabricator.services.mozilla.com/D269664

Diffstat:
Mdom/animation/AnimationEventDispatcher.h | 1+
Mdom/animation/AnimationTimelinesController.cpp | 29++++++++++++++++++++++-------
Mdom/animation/AnimationTimelinesController.h | 14++++++++++----
Mdom/animation/ScrollTimeline.cpp | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mdom/animation/ScrollTimeline.h | 42++++++++++++++++++++++++++++--------------
Mlayout/generic/ScrollContainerFrame.cpp | 6++++++
6 files changed, 166 insertions(+), 27 deletions(-)

diff --git a/dom/animation/AnimationEventDispatcher.h b/dom/animation/AnimationEventDispatcher.h @@ -16,6 +16,7 @@ #include "mozilla/ProfilerMarkers.h" #include "mozilla/Variant.h" #include "mozilla/dom/AnimationPlaybackEvent.h" +#include "mozilla/dom/Document.h" #include "mozilla/dom/KeyframeEffect.h" #include "nsCycleCollectionParticipant.h" #include "nsPresContext.h" diff --git a/dom/animation/AnimationTimelinesController.cpp b/dom/animation/AnimationTimelinesController.cpp @@ -7,32 +7,43 @@ #include "AnimationTimelinesController.h" #include "mozilla/dom/DocumentTimeline.h" +#include "mozilla/dom/ScrollTimeline.h" namespace mozilla::dom { void AnimationTimelinesController::AddDocumentTimeline( DocumentTimeline& aTimeline) { - mTimelines.insertBack(&aTimeline); + mDocumentTimelines.insertBack(&aTimeline); +} + +void AnimationTimelinesController::AddScrollTimeline( + ScrollTimeline& aTimeline) { + mScrollTimelines.insertBack(&aTimeline); } void AnimationTimelinesController::WillRefresh() { for (DocumentTimeline* tl : - ToTArray<AutoTArray<RefPtr<DocumentTimeline>, 32>>(mTimelines)) { + ToTArray<AutoTArray<RefPtr<DocumentTimeline>, 32>>(mDocumentTimelines)) { tl->WillRefresh(); } - // TODO: Add scroll timelines in the following patches. + // TODO: Switch to sample scroll timelines in HTML event loop in the following + // patches. + /*for (ScrollTimeline* tl : + ToTArray<AutoTArray<RefPtr<ScrollTimeline>, 32>>(mScrollTimelines)) { + tl->WillRefresh(); + }*/ } void AnimationTimelinesController::UpdateLastRefreshDriverTime() { - for (DocumentTimeline* timeline : mTimelines) { + for (DocumentTimeline* timeline : mDocumentTimelines) { timeline->UpdateLastRefreshDriverTime(); } // We don't use refresh driver time stamp for scroll timelines. } void AnimationTimelinesController::TriggerAllPendingAnimationsNow() { - for (DocumentTimeline* timeline : mTimelines) { + for (DocumentTimeline* timeline : mDocumentTimelines) { timeline->TriggerAllPendingAnimationsNow(); } @@ -41,11 +52,15 @@ void AnimationTimelinesController::TriggerAllPendingAnimationsNow() { } void AnimationTimelinesController::UpdateHiddenByContentVisibility() { - for (AnimationTimeline* timeline : mTimelines) { + for (AnimationTimeline* timeline : mDocumentTimelines) { timeline->UpdateHiddenByContentVisibility(); } - // TODO: Add scroll timelines in the following patches. + // TODO: Uncomment this once we start to sample scroll timelines in HTML event + // loop in the following patches. + /*for (AnimationTimeline* timeline : mScrollTimelines) { + timeline->UpdateHiddenByContentVisibility(); + }*/ } } // namespace mozilla::dom diff --git a/dom/animation/AnimationTimelinesController.h b/dom/animation/AnimationTimelinesController.h @@ -11,6 +11,7 @@ namespace mozilla::dom { class DocumentTimeline; +class ScrollTimeline; /** * The controller which keeps track of all timelines in a document. So basically @@ -22,10 +23,12 @@ class AnimationTimelinesController final { ~AnimationTimelinesController() { // We expect the timelines should remove themself from the controller // already. - MOZ_ASSERT(mTimelines.isEmpty()); + MOZ_ASSERT(mDocumentTimelines.isEmpty()); + MOZ_ASSERT(mScrollTimelines.isEmpty()); } void AddDocumentTimeline(DocumentTimeline& aTimeline); + void AddScrollTimeline(ScrollTimeline& aTimeline); void WillRefresh(); void UpdateLastRefreshDriverTime(); @@ -33,9 +36,12 @@ class AnimationTimelinesController final { void UpdateHiddenByContentVisibility(); private: - LinkedList<DocumentTimeline> mTimelines; - - // TODO: add scroll timelines (and view timelines) + LinkedList<DocumentTimeline> mDocumentTimelines; + // Note: we use a separate linked list for scroll timelines (and + // view-timelines) because some utility functions don't need to traverse this + // list for now. If all functions have to check both lists, we should merge + // them. + LinkedList<ScrollTimeline> mScrollTimelines; }; } // namespace mozilla::dom diff --git a/dom/animation/ScrollTimeline.cpp b/dom/animation/ScrollTimeline.cpp @@ -12,9 +12,13 @@ #include "mozilla/PresShell.h" #include "mozilla/ScrollContainerFrame.h" #include "mozilla/dom/Animation.h" +#include "mozilla/dom/AnimationTimelinesController.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/DocumentInlines.h" #include "mozilla/dom/ElementInlines.h" #include "nsIFrame.h" #include "nsLayoutUtils.h" +#include "nsRefreshDriver.h" namespace mozilla::dom { @@ -46,10 +50,14 @@ ScrollTimeline::ScrollTimeline(Document* aDocument, const Scroller& aScroller, mSource(aScroller), mAxis(aAxis) { MOZ_ASSERT(aDocument); + RegisterWithScrollSource(); + + mDocument->TimelinesController().AddScrollTimeline(*this); } -/* static */ std::pair<const Element*, PseudoStyleRequest> +/* static */ +std::pair<const Element*, PseudoStyleRequest> ScrollTimeline::FindNearestScroller(Element* aSubject, const PseudoStyleRequest& aPseudoRequest) { MOZ_ASSERT(aSubject); @@ -79,7 +87,13 @@ already_AddRefed<ScrollTimeline> ScrollTimeline::MakeAnonymous( Scroller scroller; switch (aScroller) { case StyleScroller::Root: - scroller = Scroller::Root(aTarget.mElement->OwnerDoc()); + // Specifies to use the document viewport as the scroll container. + // + // We use the owner doc of the animation target. This may be different + // from |mDocument| after we implement ScrollTimeline interface for + // script. + scroller = + Scroller::Root(aTarget.mElement->OwnerDoc()->GetDocumentElement()); break; case StyleScroller::Nearest: { @@ -112,6 +126,7 @@ already_AddRefed<ScrollTimeline> ScrollTimeline::MakeNamed( aStyleTimeline.GetAxis()); } +// TODO: Update this function to use the cached values in the following patches. Nullable<TimeDuration> ScrollTimeline::GetCurrentTimeAsDuration() const { // If no layout box, this timeline is inactive. if (!mSource || !mSource.mElement->GetPrimaryFrame()) { @@ -152,6 +167,26 @@ Nullable<TimeDuration> ScrollTimeline::GetCurrentTimeAsDuration() const { PROGRESS_TIMELINE_DURATION_MILLISEC); } +void ScrollTimeline::WillRefresh() { + UpdateCachedCurrentTime(); + + if (!mDocument->GetPresShell()) { + // If we're not displayed, don't tick animations. + return; + } + + if (mAnimationOrder.isEmpty()) { + return; + } + + // FIXME: Bug 1737927: Need to check the animation mutation observers for + // animations with scroll timelines. + // nsAutoAnimationMutationBatch mb(mDocument); + + TickState dummyState; + Tick(dummyState); +} + layers::ScrollDirection ScrollTimeline::Axis() const { MOZ_ASSERT(mSource && mSource.mElement->GetPrimaryFrame()); @@ -205,6 +240,8 @@ void ScrollTimeline::ReplacePropertiesWith( } } +ScrollTimeline::~ScrollTimeline() { Teardown(); } + Maybe<ScrollTimeline::ScrollOffsets> ScrollTimeline::ComputeOffsets( const ScrollContainerFrame* aScrollContainerFrame, layers::ScrollDirection aOrientation) const { @@ -216,6 +253,42 @@ Maybe<ScrollTimeline::ScrollOffsets> ScrollTimeline::ComputeOffsets( return Some(ScrollOffsets{0, range}); } +void ScrollTimeline::UpdateCachedCurrentTime() { + mCachedCurrentTime.reset(); + + // If no layout box, this timeline is inactive. + if (!mSource || !mSource.mElement->GetPrimaryFrame()) { + return; + } + + // if this is not a scroller container, this timeline is inactive. + const ScrollContainerFrame* scrollContainerFrame = GetScrollContainerFrame(); + if (!scrollContainerFrame) { + return; + } + + 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 (!scrollContainerFrame->GetAvailableScrollingDirections().contains( + orientation)) { + return; + } + + const nsPoint& scrollPosition = scrollContainerFrame->GetScrollPosition(); + const Maybe<ScrollOffsets>& offsets = + ComputeOffsets(scrollContainerFrame, orientation); + if (!offsets) { + return; + } + + mCachedCurrentTime.emplace(CurrentTimeData{ + orientation == layers::ScrollDirection::eHorizontal ? scrollPosition.x + : scrollPosition.y, + offsets.value()}); +} + void ScrollTimeline::RegisterWithScrollSource() { if (!mSource) { return; @@ -275,6 +348,21 @@ const ScrollContainerFrame* ScrollTimeline::GetScrollContainerFrame() const { return nullptr; } +void ScrollTimeline::NotifyAnimationUpdated(Animation& aAnimation) { + AnimationTimeline::NotifyAnimationUpdated(aAnimation); + + // TODO: Switch to sample scroll timelines in HTML event loop in the following + // patches. + /*if (!mAnimationOrder.isEmpty()) { + if (auto* rd = GetRefreshDriver(mDocument)) { + MOZ_ASSERT(isInList(), + "We should not register with the refresh driver if we are not" + " in the document's list of timelines"); + rd->EnsureAnimationUpdate(); + } + }*/ +} + void ScrollTimeline::NotifyAnimationContentVisibilityChanged( Animation* aAnimation, bool aIsVisible) { AnimationTimeline::NotifyAnimationContentVisibilityChanged(aAnimation, @@ -284,6 +372,15 @@ void ScrollTimeline::NotifyAnimationContentVisibilityChanged( } else { RegisterWithScrollSource(); } + + // TODO: Switch to sample scroll timelines in HTML event loop in the following + // patches. + /*if (auto* rd = GetRefreshDriver(mDocument)) { + MOZ_ASSERT(isInList(), + "We should not register with the refresh driver if we are not" + " in the document's list of timelines"); + rd->EnsureAnimationUpdate(); + }*/ } // ------------------------------------ diff --git a/dom/animation/ScrollTimeline.h b/dom/animation/ScrollTimeline.h @@ -8,10 +8,10 @@ #define mozilla_dom_ScrollTimeline_h #include "mozilla/HashTable.h" +#include "mozilla/LinkedList.h" #include "mozilla/ServoStyleConsts.h" #include "mozilla/WritingModes.h" #include "mozilla/dom/AnimationTimeline.h" -#include "mozilla/dom/Document.h" #define PROGRESS_TIMELINE_DURATION_MILLISEC 100000 @@ -20,6 +20,7 @@ class ScrollContainerFrame; class ElementAnimationData; struct NonOwningAnimationTarget; namespace dom { +class Document; class Element; /** @@ -60,7 +61,8 @@ class Element; * ScrollTimelineSet, and iterates the set to schedule the animations * linked to the ScrollTimelines. */ -class ScrollTimeline : public AnimationTimeline { +class ScrollTimeline : public AnimationTimeline, + public LinkedListElement<ScrollTimeline> { template <typename T, typename... Args> friend already_AddRefed<T> mozilla::MakeAndAddRef(Args&&... aArgs); @@ -80,16 +82,8 @@ class ScrollTimeline : public AnimationTimeline { // PseudoStyleRequest. 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 Root(Element* aDocumentElement) { + return {Type::Root, aDocumentElement, PseudoStyleType::NotPseudo}; } static Scroller Nearest(Element* aElement, PseudoStyleType aPseudoType) { @@ -181,6 +175,8 @@ class ScrollTimeline : public AnimationTimeline { return mState; } + void WillRefresh(); + // 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 @@ -208,13 +204,15 @@ class ScrollTimeline : public AnimationTimeline { const PseudoStyleRequest& aPseudoRequest, const StyleScrollTimeline& aNew); + void NotifyAnimationUpdated(Animation& aAnimation) override; + void NotifyAnimationContentVisibilityChanged(Animation* aAnimation, bool aIsVisible) override; void ResetState() { mState = TimelineState::None; } protected: - virtual ~ScrollTimeline() { Teardown(); } + virtual ~ScrollTimeline(); ScrollTimeline() = delete; ScrollTimeline(Document* aDocument, const Scroller& aScroller, StyleScrollAxis aAxis); @@ -227,10 +225,17 @@ class ScrollTimeline : public AnimationTimeline { const ScrollContainerFrame* aScrollFrame, layers::ScrollDirection aOrientation) const; + void UpdateCachedCurrentTime(); + // 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(); } + void Teardown() { + if (isInList()) { + remove(); + } + UnregisterFromScrollSource(); + } // Register this scroll timeline to the element property. void RegisterWithScrollSource(); @@ -255,6 +260,15 @@ class ScrollTimeline : public AnimationTimeline { // because we shouldn't remove this timeline from the scheduler immediately // while scheduling, to avoid any unexpected behaviors. TimelineState mState = TimelineState::None; + + struct CurrentTimeData { + // The position of the scroller, and this may be negative for RTL or + // sideways, e.g. the range of its value could be [0, -range]. The user + // needs to take care of that. + nscoord mPosition; + ScrollOffsets mOffsets; + }; + Maybe<CurrentTimeData> mCachedCurrentTime; }; /** diff --git a/layout/generic/ScrollContainerFrame.cpp b/layout/generic/ScrollContainerFrame.cpp @@ -7867,6 +7867,12 @@ void ScrollContainerFrame::ScheduleScrollAnimations() { const auto [element, request] = AnimationUtils::GetElementPseudoPair(elementOrPseudo); ProgressTimelineScheduler::ScheduleAnimations(element, request); + + // TODO: Switch to sample scroll timelines in HTML event loop in the following + // patches. + // auto* rd = PresContext()->RefreshDriver(); + // MOZ_ASSERT(rd); + // rd->EnsureAnimationUpdate(); } nsSize ScrollContainerFrame::GetSizeForWindowInnerSize() const {