Bug 1472076 - Update animations when the refresh driver's time changed due to the active timer changes. r?birtles
Normally the refresh driver's time changes in nsRefreshDriver::Tick, and then
DocumentTimeline::WillRefresh is called for the change. But nsRefreshDriver
sometimes updates its own time when their timer changes to the active one.
This patch lets DocumentTimeline update animations in response to the refresh
driver's time updates. And thus this patch prevents animation state and
relevant stuff inconsistencies such as an animation has been finished without
proper processes, e.g. without invalidating frame for the animation.
MozReview-Commit-ID: 42p5BWITQN0
--- a/dom/animation/DocumentTimeline.cpp
+++ b/dom/animation/DocumentTimeline.cpp
@@ -136,39 +136,32 @@ DocumentTimeline::NotifyAnimationUpdated
AnimationTimeline::NotifyAnimationUpdated(aAnimation);
if (!mIsObservingRefreshDriver) {
nsRefreshDriver* refreshDriver = GetRefreshDriver();
if (refreshDriver) {
MOZ_ASSERT(isInList(),
"We should not register with the refresh driver if we are not"
" in the document's list of timelines");
- refreshDriver->AddRefreshObserver(this, FlushType::Style);
- mIsObservingRefreshDriver = true;
+
+ ObserveRefreshDriver(refreshDriver);
}
}
}
void
-DocumentTimeline::WillRefresh(mozilla::TimeStamp aTime)
+DocumentTimeline::MostRecentRefreshTimeUpdated()
{
MOZ_ASSERT(mIsObservingRefreshDriver);
MOZ_ASSERT(GetRefreshDriver(),
"Should be able to reach refresh driver from within WillRefresh");
bool needsTicks = false;
nsTArray<Animation*> animationsToRemove(mAnimations.Count());
- // https://drafts.csswg.org/web-animations-1/#update-animations-and-send-events,
- // step2.
- // Note that this should be done before nsAutoAnimationMutationBatch. If
- // PerformMicroTaskCheckpoint was called before nsAutoAnimationMutationBatch
- // is destroyed, some mutation records might not be delivered in this
- // checkpoint.
- nsAutoMicroTask mt;
nsAutoAnimationMutationBatch mb(mDocument);
for (Animation* animation = mAnimationOrder.getFirst(); animation;
animation =
static_cast<LinkedListElement<Animation>*>(animation)->getNext()) {
// Skip any animations that are longer need associated with this timeline.
if (animation->GetTimeline() != this) {
// If animation has some other timeline, it better not be also in the
@@ -201,40 +194,80 @@ DocumentTimeline::WillRefresh(mozilla::T
// of mDocument's PresShell.
MOZ_ASSERT(GetRefreshDriver(),
"Refresh driver should still be valid at end of WillRefresh");
UnregisterFromRefreshDriver();
}
}
void
+DocumentTimeline::WillRefresh(mozilla::TimeStamp aTime)
+{
+ // https://drafts.csswg.org/web-animations-1/#update-animations-and-send-events,
+ // step2.
+ // Note that this should be done before nsAutoAnimationMutationBatch which is
+ // inside MostRecentRefreshTimeUpdated(). If PerformMicroTaskCheckpoint was
+ // called before nsAutoAnimationMutationBatch is destroyed, some mutation
+ // records might not be delivered in this checkpoint.
+ nsAutoMicroTask mt;
+ MostRecentRefreshTimeUpdated();
+}
+
+void
+DocumentTimeline::NotifyTimerAdjusted(TimeStamp aTime)
+{
+ MostRecentRefreshTimeUpdated();
+}
+
+void
+DocumentTimeline::ObserveRefreshDriver(nsRefreshDriver* aDriver)
+{
+ MOZ_ASSERT(!mIsObservingRefreshDriver);
+ // Set the mIsObservingRefreshDriver flag before calling AddRefreshObserver
+ // since it might end up calling NotifyTimerAdjusted which calls
+ // MostRecentRefreshTimeUpdated which has an assertion for
+ // mIsObserveingRefreshDriver check.
+ mIsObservingRefreshDriver = true;
+ aDriver->AddRefreshObserver(this, FlushType::Style);
+ aDriver->AddTimerAdjustmentObserver(this);
+}
+
+void
DocumentTimeline::NotifyRefreshDriverCreated(nsRefreshDriver* aDriver)
{
MOZ_ASSERT(!mIsObservingRefreshDriver,
"Timeline should not be observing the refresh driver before"
" it is created");
if (!mAnimationOrder.isEmpty()) {
MOZ_ASSERT(isInList(),
"We should not register with the refresh driver if we are not"
" in the document's list of timelines");
- aDriver->AddRefreshObserver(this, FlushType::Style);
- mIsObservingRefreshDriver = true;
+ ObserveRefreshDriver(aDriver);
}
}
void
+DocumentTimeline::DisconnectRefreshDriver(nsRefreshDriver* aDriver)
+{
+ MOZ_ASSERT(mIsObservingRefreshDriver);
+
+ aDriver->RemoveRefreshObserver(this, FlushType::Style);
+ aDriver->RemoveTimerAdjustmentObserver(this);
+ mIsObservingRefreshDriver = false;
+}
+
+void
DocumentTimeline::NotifyRefreshDriverDestroying(nsRefreshDriver* aDriver)
{
if (!mIsObservingRefreshDriver) {
return;
}
- aDriver->RemoveRefreshObserver(this, FlushType::Style);
- mIsObservingRefreshDriver = false;
+ DisconnectRefreshDriver(aDriver);
}
void
DocumentTimeline::RemoveAnimation(Animation* aAnimation)
{
AnimationTimeline::RemoveAnimation(aAnimation);
if (mIsObservingRefreshDriver && mAnimations.IsEmpty()) {
@@ -273,15 +306,13 @@ DocumentTimeline::UnregisterFromRefreshD
if (!mIsObservingRefreshDriver) {
return;
}
nsRefreshDriver* refreshDriver = GetRefreshDriver();
if (!refreshDriver) {
return;
}
-
- refreshDriver->RemoveRefreshObserver(this, FlushType::Style);
- mIsObservingRefreshDriver = false;
+ DisconnectRefreshDriver(refreshDriver);
}
} // namespace dom
} // namespace mozilla
--- a/dom/animation/DocumentTimeline.h
+++ b/dom/animation/DocumentTimeline.h
@@ -24,16 +24,17 @@ struct JSContext;
#endif
namespace mozilla {
namespace dom {
class DocumentTimeline final
: public AnimationTimeline
, public nsARefreshObserver
+ , public nsATimerAdjustmentObserver
, public LinkedListElement<DocumentTimeline>
{
public:
DocumentTimeline(nsIDocument* aDocument, const TimeDuration& aOriginTime)
: AnimationTimeline(aDocument->GetParentObject())
, mDocument(aDocument)
, mIsObservingRefreshDriver(false)
, mOriginTime(aOriginTime)
@@ -80,24 +81,29 @@ public:
TimeStamp ToTimeStamp(const TimeDuration& aTimelineTime) const override;
void NotifyAnimationUpdated(Animation& aAnimation) override;
void RemoveAnimation(Animation* aAnimation) override;
// nsARefreshObserver methods
void WillRefresh(TimeStamp aTime) override;
+ // nsATimerAdjustmentObserver methods
+ void NotifyTimerAdjusted(TimeStamp aTime) override;
void NotifyRefreshDriverCreated(nsRefreshDriver* aDriver);
void NotifyRefreshDriverDestroying(nsRefreshDriver* aDriver);
protected:
TimeStamp GetCurrentTimeStamp() const;
nsRefreshDriver* GetRefreshDriver() const;
void UnregisterFromRefreshDriver();
+ void MostRecentRefreshTimeUpdated();
+ void ObserveRefreshDriver(nsRefreshDriver* aDriver);
+ void DisconnectRefreshDriver(nsRefreshDriver* aDriver);
nsCOMPtr<nsIDocument> mDocument;
// The most recently used refresh driver time. This is used in cases where
// we don't have a refresh driver (e.g. because we are in a display:none
// iframe).
mutable TimeStamp mLastRefreshDriverTime;
bool mIsObservingRefreshDriver;