Bug 1850713: remove duplicated setting of early hint preloader id in `ScriptLoader...
[gecko.git] / dom / animation / PendingAnimationTracker.cpp
blob9993f0888a773bb87717acc78147e105421c715b
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "PendingAnimationTracker.h"
9 #include "mozilla/PresShell.h"
10 #include "mozilla/dom/AnimationEffect.h"
11 #include "mozilla/dom/AnimationTimeline.h"
12 #include "mozilla/dom/Nullable.h"
13 #include "nsIFrame.h"
14 #include "nsTransitionManager.h" // For CSSTransition
16 using mozilla::dom::Nullable;
18 namespace mozilla {
20 NS_IMPL_CYCLE_COLLECTION(PendingAnimationTracker, mPlayPendingSet,
21 mPausePendingSet, mDocument)
23 PendingAnimationTracker::PendingAnimationTracker(dom::Document* aDocument)
24 : mDocument(aDocument) {}
26 void PendingAnimationTracker::AddPending(dom::Animation& aAnimation,
27 AnimationSet& aSet) {
28 aSet.Insert(&aAnimation);
30 // Schedule a paint. Otherwise animations that don't trigger a paint by
31 // themselves (e.g. CSS animations with an empty keyframes rule) won't
32 // start until something else paints.
33 EnsurePaintIsScheduled();
36 void PendingAnimationTracker::RemovePending(dom::Animation& aAnimation,
37 AnimationSet& aSet) {
38 aSet.Remove(&aAnimation);
41 bool PendingAnimationTracker::IsWaiting(const dom::Animation& aAnimation,
42 const AnimationSet& aSet) const {
43 return aSet.Contains(const_cast<dom::Animation*>(&aAnimation));
46 void PendingAnimationTracker::TriggerPendingAnimationsOnNextTick(
47 const TimeStamp& aReadyTime) {
48 auto triggerAnimationsAtReadyTime = [aReadyTime](
49 AnimationSet& aAnimationSet) {
50 for (auto iter = aAnimationSet.begin(), end = aAnimationSet.end();
51 iter != end; ++iter) {
52 dom::Animation* animation = *iter;
53 dom::AnimationTimeline* timeline = animation->GetTimeline();
55 // If the animation does not have a timeline, just drop it from the map.
56 // The animation will detect that it is not being tracked and will trigger
57 // itself on the next tick where it has a timeline.
58 if (!timeline) {
59 aAnimationSet.Remove(iter);
60 continue;
63 MOZ_ASSERT(timeline->IsMonotonicallyIncreasing(),
64 "The non-monotonicially-increasing timeline should be in "
65 "ScrollTimelineAnimationTracker");
67 // When the timeline's refresh driver is under test control, its values
68 // have no correspondance to wallclock times so we shouldn't try to
69 // convert aReadyTime (which is a wallclock time) to a timeline value.
70 // Instead, the animation will be started/paused when the refresh driver
71 // is next advanced since this will trigger a call to
72 // TriggerPendingAnimationsNow.
73 if (!timeline->TracksWallclockTime()) {
74 continue;
77 Nullable<TimeDuration> readyTime = timeline->ToTimelineTime(aReadyTime);
78 animation->TriggerOnNextTick(readyTime);
80 aAnimationSet.Remove(iter);
84 triggerAnimationsAtReadyTime(mPlayPendingSet);
85 triggerAnimationsAtReadyTime(mPausePendingSet);
87 mHasPlayPendingGeometricAnimations =
88 mPlayPendingSet.Count() ? CheckState::Indeterminate : CheckState::Absent;
91 void PendingAnimationTracker::TriggerPendingAnimationsNow() {
92 auto triggerAndClearAnimations = [](AnimationSet& aAnimationSet) {
93 for (const auto& animation : aAnimationSet) {
94 animation->TriggerNow();
96 aAnimationSet.Clear();
99 triggerAndClearAnimations(mPlayPendingSet);
100 triggerAndClearAnimations(mPausePendingSet);
102 mHasPlayPendingGeometricAnimations = CheckState::Absent;
105 static bool IsTransition(const dom::Animation& aAnimation) {
106 const dom::CSSTransition* transition = aAnimation.AsCSSTransition();
107 return transition && transition->IsTiedToMarkup();
110 void PendingAnimationTracker::MarkAnimationsThatMightNeedSynchronization() {
111 // We only set mHasPlayPendingGeometricAnimations to "present" in this method
112 // and nowhere else. After setting the state to "present", if there is any
113 // change to the set of play-pending animations we will reset
114 // mHasPlayPendingGeometricAnimations to either "indeterminate" or "absent".
116 // As a result, if mHasPlayPendingGeometricAnimations is "present", we can
117 // assume that this method has already been called for the current set of
118 // play-pending animations and it is not necessary to run this method again.
120 // If mHasPlayPendingGeometricAnimations is "absent", then we can also skip
121 // the body of this method since there are no notifications to be sent.
123 // Therefore, the only case we need to be concerned about is the
124 // "indeterminate" case. For all other cases we can return early.
126 // Note that *without* this optimization, starting animations would become
127 // O(n^2) in the case where each animation is on a different element and
128 // contains a compositor-animatable property since we would end up iterating
129 // over all animations in the play-pending set for each target element.
130 if (mHasPlayPendingGeometricAnimations != CheckState::Indeterminate) {
131 return;
134 // We only synchronize CSS transitions with other CSS transitions (and we only
135 // synchronize non-transition animations with non-transition animations)
136 // since typically the author will not trigger both CSS animations and
137 // CSS transitions simultaneously and expect them to be synchronized.
139 // If we try to synchronize CSS transitions with non-transitions then for some
140 // content we will end up degrading performance by forcing animations to run
141 // on the main thread that really don't need to.
143 mHasPlayPendingGeometricAnimations = CheckState::Absent;
144 for (const auto& animation : mPlayPendingSet) {
145 if (animation->GetEffect() && animation->GetEffect()->AffectsGeometry()) {
146 mHasPlayPendingGeometricAnimations &= ~CheckState::Absent;
147 mHasPlayPendingGeometricAnimations |= IsTransition(*animation)
148 ? CheckState::TransitionsPresent
149 : CheckState::AnimationsPresent;
151 // If we have both transitions and animations we don't need to look any
152 // further.
153 if (mHasPlayPendingGeometricAnimations ==
154 (CheckState::TransitionsPresent | CheckState::AnimationsPresent)) {
155 break;
160 if (mHasPlayPendingGeometricAnimations == CheckState::Absent) {
161 return;
164 for (const auto& animation : mPlayPendingSet) {
165 bool isTransition = IsTransition(*animation);
166 if ((isTransition &&
167 mHasPlayPendingGeometricAnimations & CheckState::TransitionsPresent) ||
168 (!isTransition &&
169 mHasPlayPendingGeometricAnimations & CheckState::AnimationsPresent)) {
170 animation->NotifyGeometricAnimationsStartingThisFrame();
175 void PendingAnimationTracker::EnsurePaintIsScheduled() {
176 if (!mDocument) {
177 return;
180 PresShell* presShell = mDocument->GetPresShell();
181 if (!presShell) {
182 return;
185 nsIFrame* rootFrame = presShell->GetRootFrame();
186 if (!rootFrame) {
187 return;
190 rootFrame->SchedulePaintWithoutInvalidatingObservers();
193 } // namespace mozilla