Bug 1301305 - Extend PendingAnimationTracker to mark play-pending animations if there are geometric animations starting at the same time; r?hiro
The approach here is to lazily check if we have such animations. This allows
animations to be modified after being added to the pending animation tracker
(but not after HasPlayPendingGeometricAnimations is called since we cache the
result at that point) and avoids poor performance when calling
RemovePlayPending.
MozReview-Commit-ID: LRLpCRnzvw
--- a/dom/animation/PendingAnimationTracker.cpp
+++ b/dom/animation/PendingAnimationTracker.cpp
@@ -80,30 +80,85 @@ PendingAnimationTracker::TriggerPendingA
animation->TriggerOnNextTick(readyTime);
iter.Remove();
}
};
triggerAnimationsAtReadyTime(mPlayPendingSet);
triggerAnimationsAtReadyTime(mPausePendingSet);
+
+ mHasPlayPendingGeometricAnimations = mPlayPendingSet.Count()
+ ? CheckState::Indeterminate
+ : CheckState::Absent;
}
void
PendingAnimationTracker::TriggerPendingAnimationsNow()
{
auto triggerAndClearAnimations = [](AnimationSet& aAnimationSet) {
for (auto iter = aAnimationSet.Iter(); !iter.Done(); iter.Next()) {
iter.Get()->GetKey()->TriggerNow();
}
aAnimationSet.Clear();
};
triggerAndClearAnimations(mPlayPendingSet);
triggerAndClearAnimations(mPausePendingSet);
+
+ mHasPlayPendingGeometricAnimations = CheckState::Absent;
+}
+
+void
+PendingAnimationTracker::MarkAnimationsThatMightNeedSynchronization()
+{
+ // We only ever set mHasPlayPendingGeometricAnimations to 'present' in
+ // HasPlayPendingGeometricAnimations(). So, if it is 'present' already,
+ // (i.e. before calling HasPlayPendingGeometricAnimations()) we can assume
+ // that this method has already been called for the current set of
+ // play-pending animations and it is not necessary to run again.
+ //
+ // We can't make the same assumption about 'absent', but if this method
+ // was already called and the result was 'absent', then this method is
+ // a no-op anyway so it's ok to run again.
+ //
+ // Note that *without* this optimization, starting animations would become
+ // O(n^2) in that case where each animation is on a different element and
+ // contains a compositor-animatable property since we would end up iterating
+ // over all animations in the play-pending set for each target element.
+ if (mHasPlayPendingGeometricAnimations == CheckState::Present) {
+ return;
+ }
+
+ if (!HasPlayPendingGeometricAnimations()) {
+ return;
+ }
+
+ for (auto iter = mPlayPendingSet.Iter(); !iter.Done(); iter.Next()) {
+ iter.Get()->GetKey()->NotifyGeometricAnimationsStartingThisFrame();
+ }
+}
+
+bool
+PendingAnimationTracker::HasPlayPendingGeometricAnimations()
+{
+ if (mHasPlayPendingGeometricAnimations != CheckState::Indeterminate) {
+ return mHasPlayPendingGeometricAnimations == CheckState::Present;
+ }
+
+ mHasPlayPendingGeometricAnimations = CheckState::Absent;
+ for (auto iter = mPlayPendingSet.ConstIter(); !iter.Done(); iter.Next()) {
+ auto animation = iter.Get()->GetKey();
+ if (animation->GetEffect() && animation->GetEffect()->AffectsGeometry()) {
+ mHasPlayPendingGeometricAnimations = CheckState::Present;
+ break;
+ }
+ }
+
+ return mHasPlayPendingGeometricAnimations == CheckState::Present;
}
void
PendingAnimationTracker::EnsurePaintIsScheduled()
{
if (!mDocument) {
return;
}
--- a/dom/animation/PendingAnimationTracker.h
+++ b/dom/animation/PendingAnimationTracker.h
@@ -26,20 +26,22 @@ public:
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(PendingAnimationTracker)
NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(PendingAnimationTracker)
void AddPlayPending(dom::Animation& aAnimation)
{
MOZ_ASSERT(!IsWaitingToPause(aAnimation),
"Animation is already waiting to pause");
AddPending(aAnimation, mPlayPendingSet);
+ mHasPlayPendingGeometricAnimations = CheckState::Indeterminate;
}
void RemovePlayPending(dom::Animation& aAnimation)
{
RemovePending(aAnimation, mPlayPendingSet);
+ mHasPlayPendingGeometricAnimations = CheckState::Indeterminate;
}
bool IsWaitingToPlay(const dom::Animation& aAnimation) const
{
return IsWaiting(aAnimation, mPlayPendingSet);
}
void AddPausePending(dom::Animation& aAnimation)
{
@@ -57,28 +59,43 @@ public:
}
void TriggerPendingAnimationsOnNextTick(const TimeStamp& aReadyTime);
void TriggerPendingAnimationsNow();
bool HasPendingAnimations() const {
return mPlayPendingSet.Count() > 0 || mPausePendingSet.Count() > 0;
}
+ /**
+ * Looks amongst the set of play-pending animations, and, if there are
+ * animations that affect geometric properties, notifies all play-pending
+ * animations so that they can be synchronized, if needed.
+ */
+ void MarkAnimationsThatMightNeedSynchronization();
+
private:
~PendingAnimationTracker() { }
+ bool HasPlayPendingGeometricAnimations();
void EnsurePaintIsScheduled();
typedef nsTHashtable<nsRefPtrHashKey<dom::Animation>> AnimationSet;
void AddPending(dom::Animation& aAnimation, AnimationSet& aSet);
void RemovePending(dom::Animation& aAnimation, AnimationSet& aSet);
bool IsWaiting(const dom::Animation& aAnimation,
const AnimationSet& aSet) const;
AnimationSet mPlayPendingSet;
AnimationSet mPausePendingSet;
nsCOMPtr<nsIDocument> mDocument;
+
+ enum class CheckState {
+ Indeterminate,
+ Absent,
+ Present
+ };
+ CheckState mHasPlayPendingGeometricAnimations = CheckState::Indeterminate;
};
} // namespace mozilla
#endif // mozilla_dom_PendingAnimationTracker_h