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"
14 #include "nsTransitionManager.h" // For CSSTransition
16 using mozilla::dom::Nullable
;
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
,
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
,
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.
59 aAnimationSet
.Remove(iter
);
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()) {
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
) {
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
153 if (mHasPlayPendingGeometricAnimations
==
154 (CheckState::TransitionsPresent
| CheckState::AnimationsPresent
)) {
160 if (mHasPlayPendingGeometricAnimations
== CheckState::Absent
) {
164 for (const auto& animation
: mPlayPendingSet
) {
165 bool isTransition
= IsTransition(*animation
);
167 mHasPlayPendingGeometricAnimations
& CheckState::TransitionsPresent
) ||
169 mHasPlayPendingGeometricAnimations
& CheckState::AnimationsPresent
)) {
170 animation
->NotifyGeometricAnimationsStartingThisFrame();
175 void PendingAnimationTracker::EnsurePaintIsScheduled() {
180 PresShell
* presShell
= mDocument
->GetPresShell();
185 nsIFrame
* rootFrame
= presShell
->GetRootFrame();
190 rootFrame
->SchedulePaintWithoutInvalidatingObservers();
193 } // namespace mozilla