Bug 1301305 - Add a member to track if an animation needs to be synchronized with geometric animations or not; r?hiro
Note that in this patch, the mSyncWithGeometricAnimations member is never set
to true since no one calls NotifyGeometricAnimationsStartingThisFrame yet.
MozReview-Commit-ID: GSTQmfkSdoy
--- a/dom/animation/Animation.cpp
+++ b/dom/animation/Animation.cpp
@@ -791,25 +791,34 @@ Animation::ShouldBeSynchronizedWithMainT
return false;
}
// Currently only transform animations need to be synchronized
if (aProperty != eCSSProperty_transform) {
return false;
}
- // Is there anything about our effect itself that means it should be
- // main-thread bound?
KeyframeEffectReadOnly* keyframeEffect = mEffect
? mEffect->AsKeyframeEffect()
: nullptr;
if (!keyframeEffect) {
return false;
}
+ // Are we starting at the same time as other geometric animations?
+ // We check this before calling ShouldBlockAsyncTransformAnimations, partly
+ // because it's cheaper, but also because it's often the most useful thing
+ // to know when you're debugging performance.
+ if (mSyncWithGeometricAnimations &&
+ keyframeEffect->HasAnimationOfProperty(eCSSProperty_transform)) {
+ aPerformanceWarning = AnimationPerformanceWarning::Type::
+ TransformWithSyncGeometricAnimations;
+ return true;
+ }
+
return keyframeEffect->
ShouldBlockAsyncTransformAnimations(aFrame, aPerformanceWarning);
}
void
Animation::UpdateRelevance()
{
bool wasRelevant = mIsRelevant;
@@ -962,16 +971,26 @@ Animation::NotifyEffectTimingUpdated()
{
MOZ_ASSERT(mEffect,
"We should only update timing effect when we have a target "
"effect");
UpdateTiming(Animation::SeekFlag::NoSeek,
Animation::SyncNotifyFlag::Async);
}
+void
+Animation::NotifyGeometricAnimationsStartingThisFrame()
+{
+ if (!IsNewlyStarted() || !mEffect) {
+ return;
+ }
+
+ mSyncWithGeometricAnimations = true;
+}
+
// https://w3c.github.io/web-animations/#play-an-animation
void
Animation::PlayNoUpdate(ErrorResult& aRv, LimitBehavior aLimitBehavior)
{
AutoMutationBatchForAnimation mb(*this);
bool abortedPause = mPendingState == PendingState::PausePending;
@@ -1027,16 +1046,21 @@ Animation::PlayNoUpdate(ErrorResult& aRv
if (!reuseReadyPromise) {
// Clear ready promise. We'll create a new one lazily.
mReady = nullptr;
}
mPendingState = PendingState::PlayPending;
+ // Clear flag that causes us to sync transform animations with the main
+ // thread for now. We'll set this when we go to set up compositor
+ // animations if it applies.
+ mSyncWithGeometricAnimations = false;
+
nsIDocument* doc = GetRenderedDocument();
if (doc) {
PendingAnimationTracker* tracker =
doc->GetOrCreatePendingAnimationTracker();
tracker->AddPlayPending(*this);
} else {
TriggerOnNextTick(Nullable<TimeDuration>());
}
--- a/dom/animation/Animation.h
+++ b/dom/animation/Animation.h
@@ -58,16 +58,17 @@ public:
explicit Animation(nsIGlobalObject* aGlobal)
: DOMEventTargetHelper(aGlobal)
, mPlaybackRate(1.0)
, mPendingState(PendingState::NotPending)
, mAnimationIndex(sNextAnimationIndex++)
, mFinishedAtLastComposeStyle(false)
, mIsRelevant(false)
, mFinishedIsResolved(false)
+ , mSyncWithGeometricAnimations(false)
{
}
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Animation,
DOMEventTargetHelper)
nsIGlobalObject* GetParentObject() const { return GetOwnerGlobal(); }
@@ -315,16 +316,17 @@ public:
* if any.
* Any properties contained in |aPropertiesToSkip| will not be added or
* updated in |aStyleRule|.
*/
void ComposeStyle(RefPtr<AnimValuesStyleRule>& aStyleRule,
const nsCSSPropertyIDSet& aPropertiesToSkip);
void NotifyEffectTimingUpdated();
+ void NotifyGeometricAnimationsStartingThisFrame();
protected:
void SilentlySetCurrentTime(const TimeDuration& aNewCurrentTime);
void SilentlySetPlaybackRate(double aPlaybackRate);
void CancelNoUpdate();
void PlayNoUpdate(ErrorResult& aRv, LimitBehavior aLimitBehavior);
void PauseNoUpdate(ErrorResult& aRv);
void ResumeAt(const TimeDuration& aReadyTime);
@@ -375,16 +377,28 @@ protected:
void CancelPendingTasks();
/**
* Performs the same steps as CancelPendingTasks and also rejects and
* recreates the ready promise if the animation was pending.
*/
void ResetPendingTasks();
+ /**
+ * Returns true if this animation is not only play-pending, but has
+ * yet to be given a pending ready time. This roughly corresponds to
+ * animations that are waiting to be painted (since we set the pending
+ * ready time at the end of painting). Identifying such animations is
+ * useful because in some cases animations that are painted together
+ * may need to be synchronized.
+ */
+ bool IsNewlyStarted() const {
+ return mPendingState == PendingState::PlayPending &&
+ mPendingReadyTime.IsNull();
+ }
bool IsPossiblyOrphanedPendingAnimation() const;
StickyTimeDuration EffectEnd() const;
nsIDocument* GetRenderedDocument() const;
RefPtr<AnimationTimeline> mTimeline;
RefPtr<AnimationEffectReadOnly> mEffect;
// The beginning of the delay period.
@@ -434,15 +448,20 @@ protected:
nsRevocableEventPtr<nsRunnableMethod<Animation>> mFinishNotificationTask;
// True if mFinished is resolved or would be resolved if mFinished has
// yet to be created. This is not set when mFinished is rejected since
// in that case mFinished is immediately reset to represent a new current
// finished promise.
bool mFinishedIsResolved;
+ // True if this animation was triggered at the same time as one or more
+ // geometric animations and hence we should run any transform animations on
+ // the main thread.
+ bool mSyncWithGeometricAnimations;
+
nsString mId;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_Animation_h