Bug 1896762 - Make animation timing match the spec. r=smaug,firefox-animation-reviewers,boris

Differential Revision: https://phabricator.services.mozilla.com/D210658
This commit is contained in:
Emilio Cobos Álvarez
2024-05-17 16:33:06 +00:00
parent 1adba79fde
commit fc159ff851
9 changed files with 100 additions and 235 deletions

View File

@@ -19,7 +19,6 @@ namespace mozilla::dom {
NS_IMPL_CYCLE_COLLECTION_CLASS(DocumentTimeline) NS_IMPL_CYCLE_COLLECTION_CLASS(DocumentTimeline)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocumentTimeline, NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocumentTimeline,
AnimationTimeline) AnimationTimeline)
tmp->UnregisterFromRefreshDriver();
if (tmp->isInList()) { if (tmp->isInList()) {
tmp->remove(); tmp->remove();
} }
@@ -45,7 +44,6 @@ DocumentTimeline::DocumentTimeline(Document* aDocument,
: AnimationTimeline(aDocument->GetParentObject(), : AnimationTimeline(aDocument->GetParentObject(),
aDocument->GetScopeObject()->GetRTPCallerType()), aDocument->GetScopeObject()->GetRTPCallerType()),
mDocument(aDocument), mDocument(aDocument),
mIsObservingRefreshDriver(false),
mOriginTime(aOriginTime) { mOriginTime(aOriginTime) {
if (mDocument) { if (mDocument) {
mDocument->Timelines().insertBack(this); mDocument->Timelines().insertBack(this);
@@ -55,9 +53,6 @@ DocumentTimeline::DocumentTimeline(Document* aDocument,
} }
DocumentTimeline::~DocumentTimeline() { DocumentTimeline::~DocumentTimeline() {
MOZ_RELEASE_ASSERT(!mIsObservingRefreshDriver,
"Timeline should have disassociated"
" from the refresh driver before being destroyed");
if (isInList()) { if (isInList()) {
remove(); remove();
} }
@@ -105,11 +100,8 @@ TimeStamp DocumentTimeline::GetCurrentTimeStamp() const {
: mLastRefreshDriverTime; : mLastRefreshDriverTime;
} }
void DocumentTimeline::UpdateLastRefreshDriverTime(TimeStamp aKnownTime) { void DocumentTimeline::UpdateLastRefreshDriverTime() {
TimeStamp result = [&] { TimeStamp result = [&] {
if (!aKnownTime.IsNull()) {
return aKnownTime;
}
if (auto* rd = GetRefreshDriver()) { if (auto* rd = GetRefreshDriver()) {
return rd->MostRecentRefresh(); return rd->MostRecentRefresh();
}; };
@@ -157,110 +149,53 @@ Nullable<TimeDuration> DocumentTimeline::ToTimelineTime(
void DocumentTimeline::NotifyAnimationUpdated(Animation& aAnimation) { void DocumentTimeline::NotifyAnimationUpdated(Animation& aAnimation) {
AnimationTimeline::NotifyAnimationUpdated(aAnimation); AnimationTimeline::NotifyAnimationUpdated(aAnimation);
if (!mIsObservingRefreshDriver && !mAnimationOrder.isEmpty()) { if (!mAnimationOrder.isEmpty()) {
nsRefreshDriver* refreshDriver = GetRefreshDriver(); if (nsRefreshDriver* refreshDriver = GetRefreshDriver()) {
if (refreshDriver) {
MOZ_ASSERT(isInList(), MOZ_ASSERT(isInList(),
"We should not register with the refresh driver if we are not" "We should not register with the refresh driver if we are not"
" in the document's list of timelines"); " in the document's list of timelines");
refreshDriver->EnsureAnimationUpdate();
ObserveRefreshDriver(refreshDriver);
} }
} }
} }
void DocumentTimeline::MostRecentRefreshTimeUpdated() {
MOZ_ASSERT(mIsObservingRefreshDriver);
MOZ_ASSERT(GetRefreshDriver(),
"Should be able to reach refresh driver from within WillRefresh");
nsAutoAnimationMutationBatch mb(mDocument);
TickState state;
bool ticked = Tick(state);
if (!ticked) {
// We already assert that GetRefreshDriver() is non-null at the beginning
// of this function but we check it again here to be sure that ticking
// animations does not have any side effects that cause us to lose the
// connection with the refresh driver, such as triggering the destruction
// of mDocument's PresShell.
MOZ_ASSERT(GetRefreshDriver(),
"Refresh driver should still be valid at end of WillRefresh");
UnregisterFromRefreshDriver();
}
}
void DocumentTimeline::TriggerAllPendingAnimationsNow() { void DocumentTimeline::TriggerAllPendingAnimationsNow() {
for (Animation* animation : mAnimationOrder) { for (Animation* animation : mAnimationOrder) {
animation->TryTriggerNow(); animation->TryTriggerNow();
} }
} }
void DocumentTimeline::WillRefresh(TimeStamp aTime) { void DocumentTimeline::WillRefresh() {
UpdateLastRefreshDriverTime(); if (!mDocument->GetPresShell()) {
MostRecentRefreshTimeUpdated(); // If we're not displayed, don't tick animations.
}
void DocumentTimeline::NotifyTimerAdjusted(TimeStamp aTime) {
MostRecentRefreshTimeUpdated();
}
void DocumentTimeline::ObserveRefreshDriver(nsRefreshDriver* aDriver) {
MOZ_RELEASE_ASSERT(!mIsObservingRefreshDriver,
"shouldn't register as an observer more than once");
// 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,
"DocumentTimeline animations");
aDriver->AddTimerAdjustmentObserver(this);
}
void DocumentTimeline::NotifyRefreshDriverCreated(nsRefreshDriver* aDriver) {
MOZ_RELEASE_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");
ObserveRefreshDriver(aDriver);
// Although we have started observing the refresh driver, it's possible we
// could perform a paint before the first refresh driver tick happens. To
// ensure we're in a consistent state in that case we run the first tick
// manually.
MostRecentRefreshTimeUpdated();
}
}
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; return;
} }
UpdateLastRefreshDriverTime();
if (mAnimationOrder.isEmpty()) {
return;
}
nsAutoAnimationMutationBatch mb(mDocument);
DisconnectRefreshDriver(aDriver); TickState state;
bool ticked = Tick(state);
if (!ticked) {
return;
}
// We already assert that GetRefreshDriver() is non-null at the beginning
// of this function but we check it again here to be sure that ticking
// animations does not have any side effects that cause us to lose the
// connection with the refresh driver, such as triggering the destruction
// of mDocument's PresShell.
if (nsRefreshDriver* refreshDriver = GetRefreshDriver()) {
refreshDriver->EnsureAnimationUpdate();
} else {
MOZ_ASSERT_UNREACHABLE(
"Refresh driver should still be valid at end of WillRefresh");
}
} }
void DocumentTimeline::RemoveAnimation(Animation* aAnimation) { void DocumentTimeline::RemoveAnimation(Animation* aAnimation) {
AnimationTimeline::RemoveAnimation(aAnimation); AnimationTimeline::RemoveAnimation(aAnimation);
if (!mIsObservingRefreshDriver || !mAnimationOrder.isEmpty()) {
return;
}
UnregisterFromRefreshDriver();
} }
void DocumentTimeline::NotifyAnimationContentVisibilityChanged( void DocumentTimeline::NotifyAnimationContentVisibilityChanged(
@@ -268,19 +203,11 @@ void DocumentTimeline::NotifyAnimationContentVisibilityChanged(
AnimationTimeline::NotifyAnimationContentVisibilityChanged(aAnimation, AnimationTimeline::NotifyAnimationContentVisibilityChanged(aAnimation,
aIsVisible); aIsVisible);
if (mIsObservingRefreshDriver && mAnimationOrder.isEmpty()) { if (nsRefreshDriver* refreshDriver = GetRefreshDriver()) {
UnregisterFromRefreshDriver(); MOZ_ASSERT(isInList(),
} "We should not register with the refresh driver if we are not"
" in the document's list of timelines");
if (!mIsObservingRefreshDriver && !mAnimationOrder.isEmpty()) { refreshDriver->EnsureAnimationUpdate();
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");
ObserveRefreshDriver(refreshDriver);
}
} }
} }
@@ -302,20 +229,7 @@ nsRefreshDriver* DocumentTimeline::GetRefreshDriver() const {
if (MOZ_UNLIKELY(!presContext)) { if (MOZ_UNLIKELY(!presContext)) {
return nullptr; return nullptr;
} }
return presContext->RefreshDriver(); return presContext->RefreshDriver();
} }
void DocumentTimeline::UnregisterFromRefreshDriver() {
if (!mIsObservingRefreshDriver) {
return;
}
nsRefreshDriver* refreshDriver = GetRefreshDriver();
if (!refreshDriver) {
return;
}
DisconnectRefreshDriver(refreshDriver);
}
} // namespace mozilla::dom } // namespace mozilla::dom

View File

@@ -14,15 +14,12 @@
#include "AnimationTimeline.h" #include "AnimationTimeline.h"
#include "nsDOMNavigationTiming.h" // for DOMHighResTimeStamp #include "nsDOMNavigationTiming.h" // for DOMHighResTimeStamp
#include "nsRefreshDriver.h" #include "nsRefreshDriver.h"
#include "nsRefreshObservers.h"
struct JSContext; struct JSContext;
namespace mozilla::dom { namespace mozilla::dom {
class DocumentTimeline final : public AnimationTimeline, class DocumentTimeline final : public AnimationTimeline,
public nsARefreshObserver,
public nsATimerAdjustmentObserver,
public LinkedListElement<DocumentTimeline> { public LinkedListElement<DocumentTimeline> {
public: public:
DocumentTimeline(Document* aDocument, const TimeDuration& aOriginTime); DocumentTimeline(Document* aDocument, const TimeDuration& aOriginTime);
@@ -35,8 +32,7 @@ class DocumentTimeline final : public AnimationTimeline,
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(DocumentTimeline, NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(DocumentTimeline,
AnimationTimeline) AnimationTimeline)
virtual JSObject* WrapObject(JSContext* aCx, JSObject* WrapObject(JSContext*, JS::Handle<JSObject*> aGivenProto) override;
JS::Handle<JSObject*> aGivenProto) override;
static already_AddRefed<DocumentTimeline> Constructor( static already_AddRefed<DocumentTimeline> Constructor(
const GlobalObject& aGlobal, const DocumentTimelineOptions& aOptions, const GlobalObject& aGlobal, const DocumentTimelineOptions& aOptions,
@@ -46,7 +42,7 @@ class DocumentTimeline final : public AnimationTimeline,
// This is deliberately _not_ called GetCurrentTime since that would clash // This is deliberately _not_ called GetCurrentTime since that would clash
// with a macro defined in winbase.h // with a macro defined in winbase.h
virtual Nullable<TimeDuration> GetCurrentTimeAsDuration() const override; Nullable<TimeDuration> GetCurrentTimeAsDuration() const override;
bool TracksWallclockTime() const override; bool TracksWallclockTime() const override;
Nullable<TimeDuration> ToTimelineTime( Nullable<TimeDuration> ToTimelineTime(
@@ -61,27 +57,17 @@ class DocumentTimeline final : public AnimationTimeline,
void TriggerAllPendingAnimationsNow(); void TriggerAllPendingAnimationsNow();
// nsARefreshObserver methods void WillRefresh();
void WillRefresh(TimeStamp aTime) override;
// nsATimerAdjustmentObserver methods
void NotifyTimerAdjusted(TimeStamp aTime) override;
void NotifyRefreshDriverCreated(nsRefreshDriver* aDriver);
void NotifyRefreshDriverDestroying(nsRefreshDriver* aDriver);
Document* GetDocument() const override { return mDocument; } Document* GetDocument() const override { return mDocument; }
void UpdateLastRefreshDriverTime(TimeStamp aKnownTime = {}); void UpdateLastRefreshDriverTime();
bool IsMonotonicallyIncreasing() const override { return true; } bool IsMonotonicallyIncreasing() const override { return true; }
protected: protected:
TimeStamp GetCurrentTimeStamp() const; TimeStamp GetCurrentTimeStamp() const;
nsRefreshDriver* GetRefreshDriver() const; nsRefreshDriver* GetRefreshDriver() const;
void UnregisterFromRefreshDriver();
void MostRecentRefreshTimeUpdated();
void ObserveRefreshDriver(nsRefreshDriver* aDriver);
void DisconnectRefreshDriver(nsRefreshDriver* aDriver);
RefPtr<Document> mDocument; RefPtr<Document> mDocument;
@@ -89,8 +75,6 @@ class DocumentTimeline final : public AnimationTimeline,
// we don't have a refresh driver (e.g. because we are in a display:none // we don't have a refresh driver (e.g. because we are in a display:none
// iframe). // iframe).
TimeStamp mLastRefreshDriverTime; TimeStamp mLastRefreshDriverTime;
bool mIsObservingRefreshDriver;
TimeDuration mOriginTime; TimeDuration mOriginTime;
}; };

View File

@@ -88,11 +88,12 @@ promise_test(async t => {
div.style.animationPlayState = 'paused'; div.style.animationPlayState = 'paused';
await animation.ready; await animation.ready;
await waitForPaints();
assert_animation_is_not_running_on_compositor(animation, assert_animation_is_not_running_on_compositor(animation,
'Animation reports that it is NOT running on the compositor' 'Animation reports that it is NOT running on the compositor'
+ ' when paused'); + ' when paused');
}, ''); }, 'Basic test');
promise_test(async t => { promise_test(async t => {
var div = addDiv(t, { style: 'animation: z-index 100s' }); var div = addDiv(t, { style: 'animation: z-index 100s' });
@@ -127,6 +128,8 @@ promise_test(async t => {
animation.pause(); animation.pause();
await animation.ready; await animation.ready;
await waitForPaints();
assert_animation_is_not_running_on_compositor(animation, assert_animation_is_not_running_on_compositor(animation,
'Animation reports that it is NOT running on the compositor' 'Animation reports that it is NOT running on the compositor'
+ ' when animation.pause() is called'); + ' when animation.pause() is called');

View File

@@ -994,8 +994,8 @@ void PresShell::Init(nsPresContext* aPresContext, nsViewManager* aViewManager) {
animCtrl->NotifyRefreshDriverCreated(GetPresContext()->RefreshDriver()); animCtrl->NotifyRefreshDriverCreated(GetPresContext()->RefreshDriver());
} }
for (DocumentTimeline* timeline : mDocument->Timelines()) { for (DocumentTimeline* timelines : mDocument->Timelines()) {
timeline->NotifyRefreshDriverCreated(GetPresContext()->RefreshDriver()); timelines->UpdateLastRefreshDriverTime();
} }
// Get our activeness from the docShell. // Get our activeness from the docShell.
@@ -1338,9 +1338,6 @@ void PresShell::Destroy() {
if (mDocument->HasAnimationController()) { if (mDocument->HasAnimationController()) {
mDocument->GetAnimationController()->NotifyRefreshDriverDestroying(rd); mDocument->GetAnimationController()->NotifyRefreshDriverDestroying(rd);
} }
for (DocumentTimeline* timeline : mDocument->Timelines()) {
timeline->NotifyRefreshDriverDestroying(rd);
}
} }
if (mPresContext) { if (mPresContext) {

View File

@@ -50,6 +50,7 @@
#include "nsComponentManagerUtils.h" #include "nsComponentManagerUtils.h"
#include "mozilla/Logging.h" #include "mozilla/Logging.h"
#include "mozilla/dom/Document.h" #include "mozilla/dom/Document.h"
#include "mozilla/dom/DocumentTimeline.h"
#include "mozilla/dom/DocumentInlines.h" #include "mozilla/dom/DocumentInlines.h"
#include "nsIXULRuntime.h" #include "nsIXULRuntime.h"
#include "jsapi.h" #include "jsapi.h"
@@ -1364,6 +1365,7 @@ nsRefreshDriver::nsRefreshDriver(nsPresContext* aPresContext)
mNotifyDOMContentFlushed(false), mNotifyDOMContentFlushed(false),
mNeedToUpdateIntersectionObservations(false), mNeedToUpdateIntersectionObservations(false),
mNeedToUpdateResizeObservers(false), mNeedToUpdateResizeObservers(false),
mNeedToUpdateAnimations(false),
mMightNeedMediaQueryListenerUpdate(false), mMightNeedMediaQueryListenerUpdate(false),
mNeedToUpdateContentRelevancy(false), mNeedToUpdateContentRelevancy(false),
mInNormalTick(false), mInNormalTick(false),
@@ -1489,18 +1491,6 @@ bool nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver* aObserver,
return true; return true;
} }
void nsRefreshDriver::AddTimerAdjustmentObserver(
nsATimerAdjustmentObserver* aObserver) {
MOZ_ASSERT(!mTimerAdjustmentObservers.Contains(aObserver));
mTimerAdjustmentObservers.AppendElement(aObserver);
}
void nsRefreshDriver::RemoveTimerAdjustmentObserver(
nsATimerAdjustmentObserver* aObserver) {
MOZ_ASSERT(mTimerAdjustmentObservers.Contains(aObserver));
mTimerAdjustmentObservers.RemoveElement(aObserver);
}
void nsRefreshDriver::PostVisualViewportResizeEvent( void nsRefreshDriver::PostVisualViewportResizeEvent(
VVPResizeEvent* aResizeEvent) { VVPResizeEvent* aResizeEvent) {
mVisualViewportResizeEvents.AppendElement(aResizeEvent); mVisualViewportResizeEvents.AppendElement(aResizeEvent);
@@ -1885,11 +1875,6 @@ void nsRefreshDriver::EnsureTimerStarted(EnsureTimerStartedFlags aFlags) {
if (mMostRecentRefresh != mActiveTimer->MostRecentRefresh()) { if (mMostRecentRefresh != mActiveTimer->MostRecentRefresh()) {
mMostRecentRefresh = mActiveTimer->MostRecentRefresh(); mMostRecentRefresh = mActiveTimer->MostRecentRefresh();
for (nsATimerAdjustmentObserver* obs :
mTimerAdjustmentObservers.EndLimitedRange()) {
obs->NotifyTimerAdjusted(mMostRecentRefresh);
}
} }
} }
@@ -1919,7 +1904,6 @@ uint32_t nsRefreshDriver::ObserverCount() const {
sum += mThrottledFrameRequestCallbackDocs.Length(); sum += mThrottledFrameRequestCallbackDocs.Length();
sum += mViewManagerFlushIsPending; sum += mViewManagerFlushIsPending;
sum += mEarlyRunners.Length(); sum += mEarlyRunners.Length();
sum += mTimerAdjustmentObservers.Length();
sum += mAutoFocusFlushDocuments.Length(); sum += mAutoFocusFlushDocuments.Length();
return sum; return sum;
} }
@@ -2011,6 +1995,9 @@ auto nsRefreshDriver::GetReasonsToTick() const -> TickReasons {
if (mNeedToUpdateResizeObservers) { if (mNeedToUpdateResizeObservers) {
reasons |= TickReasons::eNeedsToNotifyResizeObservers; reasons |= TickReasons::eNeedsToNotifyResizeObservers;
} }
if (mNeedToUpdateAnimations) {
reasons |= TickReasons::eNeedsToUpdateAnimations;
}
if (mNeedToUpdateIntersectionObservations) { if (mNeedToUpdateIntersectionObservations) {
reasons |= TickReasons::eNeedsToUpdateIntersectionObservations; reasons |= TickReasons::eNeedsToUpdateIntersectionObservations;
} }
@@ -2054,6 +2041,9 @@ void nsRefreshDriver::AppendTickReasonsToString(TickReasons aReasons,
if (aReasons & TickReasons::eNeedsToNotifyResizeObservers) { if (aReasons & TickReasons::eNeedsToNotifyResizeObservers) {
aStr.AppendLiteral(" NeedsToNotifyResizeObservers"); aStr.AppendLiteral(" NeedsToNotifyResizeObservers");
} }
if (aReasons & TickReasons::eNeedsToUpdateAnimations) {
aStr.AppendLiteral(" NeedsToUpdateAnimations");
}
if (aReasons & TickReasons::eNeedsToUpdateIntersectionObservations) { if (aReasons & TickReasons::eNeedsToUpdateIntersectionObservations) {
aStr.AppendLiteral(" NeedsToUpdateIntersectionObservations"); aStr.AppendLiteral(" NeedsToUpdateIntersectionObservations");
} }
@@ -2344,11 +2334,41 @@ void nsRefreshDriver::DetermineProximityToViewportAndNotifyResizeObservers() {
} }
} }
void nsRefreshDriver::DispatchAnimationEvents() { static CallState UpdateAndReduceAnimations(Document& aDocument) {
for (DocumentTimeline* timeline : aDocument.Timelines()) {
timeline->WillRefresh();
}
if (nsPresContext* pc = aDocument.GetPresContext()) {
if (pc->EffectCompositor()->NeedsReducing()) {
pc->EffectCompositor()->ReduceAnimations();
}
}
aDocument.EnumerateSubDocuments(UpdateAndReduceAnimations);
return CallState::Continue;
}
void nsRefreshDriver::UpdateAnimationsAndSendEvents() {
// TODO(emilio): Can we early-return here if mNeedToUpdateAnimations is
// already false?
mNeedToUpdateAnimations = false;
if (!mPresContext) { if (!mPresContext) {
return; return;
} }
{
// Animation updates may queue Promise resolution microtasks. We shouldn't
// run these, however, until we have fully updated the animation state. As
// per the "update animations and send events" procedure[1], we should
// remove replaced animations and then run these microtasks before
// dispatching the corresponding animation events.
//
// [1]:
// https://drafts.csswg.org/web-animations-1/#update-animations-and-send-events
nsAutoMicroTask mt;
UpdateAndReduceAnimations(*mPresContext->Document());
}
// Hold all AnimationEventDispatcher in mAnimationEventFlushObservers as // Hold all AnimationEventDispatcher in mAnimationEventFlushObservers as
// a RefPtr<> array since each AnimationEventDispatcher might be destroyed // a RefPtr<> array since each AnimationEventDispatcher might be destroyed
// during processing the previous dispatcher. // during processing the previous dispatcher.
@@ -2495,16 +2515,6 @@ void nsRefreshDriver::CancelIdleTask(Task* aTask) {
} }
} }
static CallState ReduceAnimations(Document& aDocument) {
if (nsPresContext* pc = aDocument.GetPresContext()) {
if (pc->EffectCompositor()->NeedsReducing()) {
pc->EffectCompositor()->ReduceAnimations();
}
}
aDocument.EnumerateSubDocuments(ReduceAnimations);
return CallState::Continue;
}
bool nsRefreshDriver::TickObserverArray(uint32_t aIdx, TimeStamp aNowTime) { bool nsRefreshDriver::TickObserverArray(uint32_t aIdx, TimeStamp aNowTime) {
MOZ_ASSERT(aIdx < ArrayLength(mObservers)); MOZ_ASSERT(aIdx < ArrayLength(mObservers));
for (RefPtr<nsARefreshObserver> obs : mObservers[aIdx].EndLimitedRange()) { for (RefPtr<nsARefreshObserver> obs : mObservers[aIdx].EndLimitedRange()) {
@@ -2667,28 +2677,6 @@ void nsRefreshDriver::Tick(VsyncId aId, TimeStamp aNowTime,
return StopTimer(); return StopTimer();
} }
// Any animation timelines updated above (animation timelines are style flush
// observers) may cause animations to queue Promise resolution microtasks. We
// shouldn't run these, however, until we have fully updated the animation
// state.
//
// As per the "update animations and send events" procedure[1], we should
// remove replaced animations and then run these microtasks before
// dispatching the corresponding animation events.
//
// FIXME(emilio, bug 1896762): This comment doesn't make much sense to me.
// We're running micro-tasks in the block below, but we don't "Update
// animations and send events" until we hit DispatchAnimationEvents() below.
// We should probably refactor the setup so that animation timelines are
// ticked as part of step 11 below or so, and do this only then.
//
// [1]:
// https://drafts.csswg.org/web-animations-1/#update-animations-and-send-events
{
nsAutoMicroTask mt;
ReduceAnimations(*mPresContext->Document());
}
// Check if running the microtask checkpoint above caused the pres context to // Check if running the microtask checkpoint above caused the pres context to
// be destroyed. // be destroyed.
if (!mPresContext || !mPresContext->GetPresShell()) { if (!mPresContext || !mPresContext->GetPresShell()) {
@@ -2713,7 +2701,7 @@ void nsRefreshDriver::Tick(VsyncId aId, TimeStamp aNowTime,
EvaluateMediaQueriesAndReportChanges(); EvaluateMediaQueriesAndReportChanges();
// Step 11. For each doc of docs, update animations and send events for doc. // Step 11. For each doc of docs, update animations and send events for doc.
DispatchAnimationEvents(); UpdateAnimationsAndSendEvents();
// Step 12. For each doc of docs, run the fullscreen steps for doc. // Step 12. For each doc of docs, run the fullscreen steps for doc.
RunFullscreenSteps(); RunFullscreenSteps();

View File

@@ -103,12 +103,6 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
const char* aObserverDescription); const char* aObserverDescription);
bool RemoveRefreshObserver(nsARefreshObserver* aObserver, bool RemoveRefreshObserver(nsARefreshObserver* aObserver,
mozilla::FlushType aFlushType); mozilla::FlushType aFlushType);
/**
* Add / remove an observer wants to know the time when the refresh driver
* updated the most recent refresh time due to its active timer changes.
*/
void AddTimerAdjustmentObserver(nsATimerAdjustmentObserver* aObserver);
void RemoveTimerAdjustmentObserver(nsATimerAdjustmentObserver* aObserver);
void PostVisualViewportResizeEvent(VVPResizeEvent* aResizeEvent); void PostVisualViewportResizeEvent(VVPResizeEvent* aResizeEvent);
void DispatchVisualViewportResizeEvents(); void DispatchVisualViewportResizeEvents();
@@ -417,6 +411,11 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
mNeedToUpdateResizeObservers = true; mNeedToUpdateResizeObservers = true;
} }
void EnsureAnimationUpdate() {
EnsureTimerStarted();
mNeedToUpdateAnimations = true;
}
void ScheduleMediaQueryListenerUpdate() { void ScheduleMediaQueryListenerUpdate() {
EnsureTimerStarted(); EnsureTimerStarted();
mMightNeedMediaQueryListenerUpdate = true; mMightNeedMediaQueryListenerUpdate = true;
@@ -446,6 +445,7 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
eHasPendingMediaQueryListeners = 1 << 7, eHasPendingMediaQueryListeners = 1 << 7,
eNeedsToNotifyResizeObservers = 1 << 8, eNeedsToNotifyResizeObservers = 1 << 8,
eRootNeedsMoreTicksForUserInput = 1 << 9, eRootNeedsMoreTicksForUserInput = 1 << 9,
eNeedsToUpdateAnimations = 1 << 10,
}; };
void AddForceNotifyContentfulPaintPresContext(nsPresContext* aPresContext); void AddForceNotifyContentfulPaintPresContext(nsPresContext* aPresContext);
@@ -487,7 +487,7 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
MOZ_CAN_RUN_SCRIPT MOZ_CAN_RUN_SCRIPT
void FlushAutoFocusDocuments(); void FlushAutoFocusDocuments();
void RunFullscreenSteps(); void RunFullscreenSteps();
void DispatchAnimationEvents(); void UpdateAnimationsAndSendEvents();
MOZ_CAN_RUN_SCRIPT MOZ_CAN_RUN_SCRIPT
void RunFrameRequestCallbacks(mozilla::TimeStamp aNowTime); void RunFrameRequestCallbacks(mozilla::TimeStamp aNowTime);
void UpdateIntersectionObservations(mozilla::TimeStamp aNowTime); void UpdateIntersectionObservations(mozilla::TimeStamp aNowTime);
@@ -643,6 +643,9 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
// all our documents. // all our documents.
bool mNeedToUpdateResizeObservers : 1; bool mNeedToUpdateResizeObservers : 1;
// True if we need to update animations.
bool mNeedToUpdateAnimations : 1;
// True if we might need to report media query changes in any of our // True if we might need to report media query changes in any of our
// documents. // documents.
bool mMightNeedMediaQueryListenerUpdate : 1; bool mMightNeedMediaQueryListenerUpdate : 1;
@@ -673,12 +676,6 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
// separate arrays for each flush type we support // separate arrays for each flush type we support
ObserverArray mObservers[3]; ObserverArray mObservers[3];
// These observers should NOT be included in HasObservers() since that method
// is used to determine whether or not to stop the timer, or restore it when
// thawing the refresh driver. On the other hand these observers are intended
// to be called when the timer is re-started and should not influence its
// starting or stopping.
nsTObserverArray<nsATimerAdjustmentObserver*> mTimerAdjustmentObservers;
nsTArray<mozilla::layers::CompositionPayload> mCompositionPayloads; nsTArray<mozilla::layers::CompositionPayload> mCompositionPayloads;
RequestTable mRequests; RequestTable mRequests;
ImageStartTable mStartTable; ImageStartTable mStartTable;

View File

@@ -60,17 +60,6 @@ class nsARefreshObserver {
#endif // DEBUG #endif // DEBUG
}; };
/**
* An abstract base class to be implemented by callers wanting to be notified
* when the observing refresh driver updated mMostRecentRefresh due to active
* timer changes. Callers must ensure an observer is removed before it is
* destroyed.
*/
class nsATimerAdjustmentObserver {
public:
virtual void NotifyTimerAdjusted(mozilla::TimeStamp aTime) = 0;
};
/** /**
* An abstract base class to be implemented by callers wanting to be notified * An abstract base class to be implemented by callers wanting to be notified
* that a refresh has occurred. Callers must ensure an observer is removed * that a refresh has occurred. Callers must ensure an observer is removed

View File

@@ -20,6 +20,7 @@
#include "mozilla/dom/ShadowRoot.h" #include "mozilla/dom/ShadowRoot.h"
#include "mozilla/dom/CustomEvent.h" #include "mozilla/dom/CustomEvent.h"
#include "mozilla/dom/ContentChild.h" #include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/DocumentTimeline.h"
#include "mozilla/dom/HTMLCanvasElement.h" #include "mozilla/dom/HTMLCanvasElement.h"
#include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/ScriptSettings.h"
#include "mozilla/IntegerRange.h" #include "mozilla/IntegerRange.h"
@@ -1414,6 +1415,10 @@ nsresult nsPrintJob::ReflowPrintObject(const UniquePtr<nsPrintObject>& aPO) {
aPO->mPresContext->SetPageSize(pageSize); aPO->mPresContext->SetPageSize(pageSize);
} }
} }
// Make sure animations are active.
for (DocumentTimeline* tl : aPO->mDocument->Timelines()) {
tl->TriggerAllPendingAnimationsNow();
}
// Process the reflow event Initialize posted // Process the reflow event Initialize posted
presShell->FlushPendingNotifications(FlushType::Layout); presShell->FlushPendingNotifications(FlushType::Layout);

View File

@@ -1,16 +1,4 @@
[css-transition-cross-document.html] [css-transition-cross-document.html]
expected: expected: [TIMEOUT, OK]
if (os == "linux") and debug and fission: [OK, TIMEOUT]
if (os == "linux") and debug and not fission: [OK, TIMEOUT]
if (os == "win") and not debug: TIMEOUT
if (os == "linux") and not debug: TIMEOUT
if os == "android": OK
[TIMEOUT, OK]
[Moving a transition across documents should reset its state] [Moving a transition across documents should reset its state]
expected: expected: [TIMEOUT, FAIL]
if (os == "linux") and debug and fission: [FAIL, TIMEOUT]
if (os == "linux") and debug and not fission: [FAIL, TIMEOUT]
if (os == "win") and not debug: TIMEOUT
if (os == "linux") and not debug: TIMEOUT
if os == "android": FAIL
[TIMEOUT, FAIL]