Bug 1958970 - Change animation event set-up to match the spec better. r=smaug,firefox-animation-reviewers,boris

Deal with events per-document like the spec. This also ensures that view
transition rendering suppressions and such work for this rendering
phase.

Differential Revision: https://phabricator.services.mozilla.com/D244670
This commit is contained in:
Emilio Cobos Álvarez
2025-04-10 01:13:30 +00:00
parent 4860c3877d
commit da2ca6b75d
7 changed files with 42 additions and 103 deletions

View File

@@ -100,35 +100,36 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AnimationEventDispatcher)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
void AnimationEventDispatcher::Disconnect() { void AnimationEventDispatcher::Disconnect() {
if (mIsObserving) { ClearEventQueue();
MOZ_ASSERT(mPresContext && mPresContext->RefreshDriver(),
"The pres context and the refresh driver should be still "
"alive if we haven't disassociated from the refresh driver");
mPresContext->RefreshDriver()->CancelPendingAnimationEvents(this);
mIsObserving = false;
}
mPresContext = nullptr; mPresContext = nullptr;
} }
void AnimationEventDispatcher::QueueEvent(AnimationEventInfo&& aEvent) { void AnimationEventDispatcher::QueueEvent(AnimationEventInfo&& aEvent) {
const bool wasEmpty = mPendingEvents.IsEmpty();
mPendingEvents.AppendElement(std::move(aEvent)); mPendingEvents.AppendElement(std::move(aEvent));
mIsSorted = false; mIsSorted = !wasEmpty;
ScheduleDispatch(); if (wasEmpty) {
ScheduleDispatch();
}
} }
void AnimationEventDispatcher::QueueEvents( void AnimationEventDispatcher::QueueEvents(
nsTArray<AnimationEventInfo>&& aEvents) { nsTArray<AnimationEventInfo>&& aEvents) {
if (aEvents.IsEmpty()) {
return;
}
const bool wasEmpty = mPendingEvents.IsEmpty();
mPendingEvents.AppendElements(std::move(aEvents)); mPendingEvents.AppendElements(std::move(aEvents));
mIsSorted = false; mIsSorted = false;
ScheduleDispatch(); if (wasEmpty) {
ScheduleDispatch();
}
} }
void AnimationEventDispatcher::ScheduleDispatch() { void AnimationEventDispatcher::ScheduleDispatch() {
MOZ_ASSERT(mPresContext, "The pres context should be valid"); MOZ_ASSERT(mPresContext, "The pres context should be valid");
if (!mIsObserving) { mPresContext->RefreshDriver()->ScheduleRenderingPhase(
mPresContext->RefreshDriver()->ScheduleAnimationEventDispatch(this); RenderingPhase::UpdateAnimationsAndSendEvents);
mIsObserving = true;
}
} }
void AnimationEventInfo::MaybeAddMarker() const { void AnimationEventInfo::MaybeAddMarker() const {

View File

@@ -236,7 +236,7 @@ struct AnimationEventInfo {
class AnimationEventDispatcher final { class AnimationEventDispatcher final {
public: public:
explicit AnimationEventDispatcher(nsPresContext* aPresContext) explicit AnimationEventDispatcher(nsPresContext* aPresContext)
: mPresContext(aPresContext), mIsSorted(true), mIsObserving(false) {} : mPresContext(aPresContext), mIsSorted(true) {}
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AnimationEventDispatcher) NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AnimationEventDispatcher)
NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(AnimationEventDispatcher) NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(AnimationEventDispatcher)
@@ -249,7 +249,6 @@ class AnimationEventDispatcher final {
// This will call SortEvents automatically if it has not already been // This will call SortEvents automatically if it has not already been
// called. // called.
void DispatchEvents() { void DispatchEvents() {
mIsObserving = false;
if (!mPresContext || mPendingEvents.IsEmpty()) { if (!mPresContext || mPendingEvents.IsEmpty()) {
return; return;
} }
@@ -288,15 +287,7 @@ class AnimationEventDispatcher final {
} }
private: private:
#ifndef DEBUG
~AnimationEventDispatcher() = default; ~AnimationEventDispatcher() = default;
#else
~AnimationEventDispatcher() {
MOZ_ASSERT(!mIsObserving,
"AnimationEventDispatcher should have disassociated from "
"nsRefreshDriver");
}
#endif
// Sort all pending CSS animation/transition events by scheduled event time // Sort all pending CSS animation/transition events by scheduled event time
// and composite order. // and composite order.
@@ -324,7 +315,6 @@ class AnimationEventDispatcher final {
using EventArray = nsTArray<AnimationEventInfo>; using EventArray = nsTArray<AnimationEventInfo>;
EventArray mPendingEvents; EventArray mPendingEvents;
bool mIsSorted; bool mIsSorted;
bool mIsObserving;
}; };
} // namespace mozilla } // namespace mozilla

View File

@@ -7416,7 +7416,7 @@ bool Document::IsRenderingSuppressed() const {
} }
// The user agent believes that updating the rendering of doc's node navigable // The user agent believes that updating the rendering of doc's node navigable
// would have no visible effect. // would have no visible effect.
if (!IsEventHandlingEnabled()) { if (!IsEventHandlingEnabled() && !IsBeingUsedAsImage()) {
return true; return true;
} }
if (!mPresShell || !mPresShell->DidInitialize()) { if (!mPresShell || !mPresShell->DidInitialize()) {

View File

@@ -18,6 +18,7 @@
#include "gfxUtils.h" #include "gfxUtils.h"
#include "MobileViewportManager.h" #include "MobileViewportManager.h"
#include "mozilla/AccessibleCaretEventHub.h" #include "mozilla/AccessibleCaretEventHub.h"
#include "mozilla/AnimationEventDispatcher.h"
#include "mozilla/ArrayUtils.h" #include "mozilla/ArrayUtils.h"
#include "mozilla/Assertions.h" #include "mozilla/Assertions.h"
#include "mozilla/Attributes.h" #include "mozilla/Attributes.h"
@@ -1349,7 +1350,7 @@ void PresShell::Destroy() {
} }
if (mPresContext) { if (mPresContext) {
rd->CancelPendingAnimationEvents(mPresContext->AnimationEventDispatcher()); mPresContext->AnimationEventDispatcher()->ClearEventQueue();
} }
// Revoke any pending events. We need to do this and cancel pending reflows // Revoke any pending events. We need to do this and cancel pending reflows

View File

@@ -1860,8 +1860,7 @@ bool nsRefreshDriver::HasObservers() const {
} }
return (mViewManagerFlushIsPending && !mThrottled) || return (mViewManagerFlushIsPending && !mThrottled) ||
!mStyleFlushObservers.IsEmpty() || !mStyleFlushObservers.IsEmpty() || !mEarlyRunners.IsEmpty();
!mEarlyRunners.IsEmpty();
} }
void nsRefreshDriver::AppendObserverDescriptionsToString( void nsRefreshDriver::AppendObserverDescriptionsToString(
@@ -2126,7 +2125,7 @@ void nsRefreshDriver::DetermineProximityToViewportAndNotifyResizeObservers() {
Filter); Filter);
} }
static CallState UpdateAndReduceAnimations(Document& aDocument) { static void UpdateAndReduceAnimations(Document& aDocument) {
for (DocumentTimeline* tl : for (DocumentTimeline* tl :
ToTArray<AutoTArray<RefPtr<DocumentTimeline>, 32>>( ToTArray<AutoTArray<RefPtr<DocumentTimeline>, 32>>(
aDocument.Timelines())) { aDocument.Timelines())) {
@@ -2138,39 +2137,6 @@ static CallState UpdateAndReduceAnimations(Document& aDocument) {
pc->EffectCompositor()->ReduceAnimations(); pc->EffectCompositor()->ReduceAnimations();
} }
} }
aDocument.EnumerateSubDocuments(UpdateAndReduceAnimations);
return CallState::Continue;
}
void nsRefreshDriver::UpdateAnimationsAndSendEvents() {
if (!mPresContext) {
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;
RefPtr doc = mPresContext->Document();
UpdateAndReduceAnimations(*doc);
}
// Hold all AnimationEventDispatcher in mAnimationEventFlushObservers as
// a RefPtr<> array since each AnimationEventDispatcher might be destroyed
// during processing the previous dispatcher.
AutoTArray<RefPtr<AnimationEventDispatcher>, 16> dispatchers;
dispatchers.AppendElements(mAnimationEventFlushObservers);
mAnimationEventFlushObservers.Clear();
for (auto& dispatcher : dispatchers) {
dispatcher->DispatchEvents();
}
} }
void nsRefreshDriver::RunVideoFrameCallbacks( void nsRefreshDriver::RunVideoFrameCallbacks(
@@ -2569,10 +2535,27 @@ void nsRefreshDriver::Tick(VsyncId aId, TimeStamp aNowTime,
}); });
// 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.
RunRenderingPhaseLegacy(RenderingPhase::UpdateAnimationsAndSendEvents, RunRenderingPhase(RenderingPhase::UpdateAnimationsAndSendEvents,
[&]() MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA { [&](Document& aDoc) MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA {
UpdateAnimationsAndSendEvents(); {
}); // 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(aDoc);
}
if (RefPtr pc = aDoc.GetPresContext()) {
RefPtr dispatcher = pc->AnimationEventDispatcher();
dispatcher->DispatchEvents();
}
});
// 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.
RunRenderingPhaseLegacy( RunRenderingPhaseLegacy(
@@ -3078,13 +3061,6 @@ void nsRefreshDriver::CancelPendingFullscreenEvents(Document* aDocument) {
} }
} }
void nsRefreshDriver::CancelPendingAnimationEvents(
AnimationEventDispatcher* aDispatcher) {
MOZ_ASSERT(aDispatcher);
aDispatcher->ClearEventQueue();
mAnimationEventFlushObservers.RemoveElement(aDispatcher);
}
/* static */ /* static */
TimeStamp nsRefreshDriver::GetIdleDeadlineHint(TimeStamp aDefault, TimeStamp nsRefreshDriver::GetIdleDeadlineHint(TimeStamp aDefault,
IdleCheck aCheckType) { IdleCheck aCheckType) {

View File

@@ -192,24 +192,6 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
*/ */
void CancelPendingFullscreenEvents(Document* aDocument); void CancelPendingFullscreenEvents(Document* aDocument);
/**
* Queue new animation events to dispatch in next tick.
*/
void ScheduleAnimationEventDispatch(
mozilla::AnimationEventDispatcher* aDispatcher) {
NS_ASSERTION(!mAnimationEventFlushObservers.Contains(aDispatcher),
"Double-adding animation event flush observer");
mAnimationEventFlushObservers.AppendElement(aDispatcher);
ScheduleRenderingPhase(
mozilla::RenderingPhase::UpdateAnimationsAndSendEvents);
}
/**
* Cancel all pending animation events associated with |aDispatcher|.
*/
void CancelPendingAnimationEvents(
mozilla::AnimationEventDispatcher* aDispatcher);
/** /**
* Schedule a frame visibility update "soon", subject to the heuristics and * Schedule a frame visibility update "soon", subject to the heuristics and
* throttling we apply to visibility updates. * throttling we apply to visibility updates.
@@ -441,7 +423,6 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
}; };
using ObserverArray = nsTObserverArray<ObserverData>; using ObserverArray = nsTObserverArray<ObserverData>;
void RunFullscreenSteps(); void RunFullscreenSteps();
void UpdateAnimationsAndSendEvents();
MOZ_CAN_RUN_SCRIPT MOZ_CAN_RUN_SCRIPT
void RunVideoAndFrameRequestCallbacks(mozilla::TimeStamp aNowTime); void RunVideoAndFrameRequestCallbacks(mozilla::TimeStamp aNowTime);
@@ -645,8 +626,6 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
nsTObserverArray<nsAPostRefreshObserver*> mPostRefreshObservers; nsTObserverArray<nsAPostRefreshObserver*> mPostRefreshObservers;
nsTArray<mozilla::UniquePtr<mozilla::PendingFullscreenEvent>> nsTArray<mozilla::UniquePtr<mozilla::PendingFullscreenEvent>>
mPendingFullscreenEvents; mPendingFullscreenEvents;
AutoTArray<mozilla::AnimationEventDispatcher*, 16>
mAnimationEventFlushObservers;
// nsPresContexts which `NotifyContentfulPaint` have been called, // nsPresContexts which `NotifyContentfulPaint` have been called,
// however the corresponding paint doesn't come from a regular // however the corresponding paint doesn't come from a regular

View File

@@ -1,8 +0,0 @@
[no-css-animation-while-render-blocked.html]
expected:
if (os == "mac") and debug: [CRASH, OK]
[OK, CRASH]
[CSS animation is blocked until prepare callback]
expected:
if (processor == "x86") and (os == "win") and not debug: [PASS, FAIL]
FAIL