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
void AnimationEventDispatcher::Disconnect() {
if (mIsObserving) {
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;
}
ClearEventQueue();
mPresContext = nullptr;
}
void AnimationEventDispatcher::QueueEvent(AnimationEventInfo&& aEvent) {
const bool wasEmpty = mPendingEvents.IsEmpty();
mPendingEvents.AppendElement(std::move(aEvent));
mIsSorted = false;
ScheduleDispatch();
mIsSorted = !wasEmpty;
if (wasEmpty) {
ScheduleDispatch();
}
}
void AnimationEventDispatcher::QueueEvents(
nsTArray<AnimationEventInfo>&& aEvents) {
if (aEvents.IsEmpty()) {
return;
}
const bool wasEmpty = mPendingEvents.IsEmpty();
mPendingEvents.AppendElements(std::move(aEvents));
mIsSorted = false;
ScheduleDispatch();
if (wasEmpty) {
ScheduleDispatch();
}
}
void AnimationEventDispatcher::ScheduleDispatch() {
MOZ_ASSERT(mPresContext, "The pres context should be valid");
if (!mIsObserving) {
mPresContext->RefreshDriver()->ScheduleAnimationEventDispatch(this);
mIsObserving = true;
}
mPresContext->RefreshDriver()->ScheduleRenderingPhase(
RenderingPhase::UpdateAnimationsAndSendEvents);
}
void AnimationEventInfo::MaybeAddMarker() const {

View File

@@ -236,7 +236,7 @@ struct AnimationEventInfo {
class AnimationEventDispatcher final {
public:
explicit AnimationEventDispatcher(nsPresContext* aPresContext)
: mPresContext(aPresContext), mIsSorted(true), mIsObserving(false) {}
: mPresContext(aPresContext), mIsSorted(true) {}
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(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
// called.
void DispatchEvents() {
mIsObserving = false;
if (!mPresContext || mPendingEvents.IsEmpty()) {
return;
}
@@ -288,15 +287,7 @@ class AnimationEventDispatcher final {
}
private:
#ifndef DEBUG
~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
// and composite order.
@@ -324,7 +315,6 @@ class AnimationEventDispatcher final {
using EventArray = nsTArray<AnimationEventInfo>;
EventArray mPendingEvents;
bool mIsSorted;
bool mIsObserving;
};
} // 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
// would have no visible effect.
if (!IsEventHandlingEnabled()) {
if (!IsEventHandlingEnabled() && !IsBeingUsedAsImage()) {
return true;
}
if (!mPresShell || !mPresShell->DidInitialize()) {

View File

@@ -18,6 +18,7 @@
#include "gfxUtils.h"
#include "MobileViewportManager.h"
#include "mozilla/AccessibleCaretEventHub.h"
#include "mozilla/AnimationEventDispatcher.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
@@ -1349,7 +1350,7 @@ void PresShell::Destroy() {
}
if (mPresContext) {
rd->CancelPendingAnimationEvents(mPresContext->AnimationEventDispatcher());
mPresContext->AnimationEventDispatcher()->ClearEventQueue();
}
// 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) ||
!mStyleFlushObservers.IsEmpty() ||
!mEarlyRunners.IsEmpty();
!mStyleFlushObservers.IsEmpty() || !mEarlyRunners.IsEmpty();
}
void nsRefreshDriver::AppendObserverDescriptionsToString(
@@ -2126,7 +2125,7 @@ void nsRefreshDriver::DetermineProximityToViewportAndNotifyResizeObservers() {
Filter);
}
static CallState UpdateAndReduceAnimations(Document& aDocument) {
static void UpdateAndReduceAnimations(Document& aDocument) {
for (DocumentTimeline* tl :
ToTArray<AutoTArray<RefPtr<DocumentTimeline>, 32>>(
aDocument.Timelines())) {
@@ -2138,39 +2137,6 @@ static CallState UpdateAndReduceAnimations(Document& aDocument) {
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(
@@ -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.
RunRenderingPhaseLegacy(RenderingPhase::UpdateAnimationsAndSendEvents,
[&]() MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA {
UpdateAnimationsAndSendEvents();
});
RunRenderingPhase(RenderingPhase::UpdateAnimationsAndSendEvents,
[&](Document& aDoc) MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA {
{
// 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.
RunRenderingPhaseLegacy(
@@ -3078,13 +3061,6 @@ void nsRefreshDriver::CancelPendingFullscreenEvents(Document* aDocument) {
}
}
void nsRefreshDriver::CancelPendingAnimationEvents(
AnimationEventDispatcher* aDispatcher) {
MOZ_ASSERT(aDispatcher);
aDispatcher->ClearEventQueue();
mAnimationEventFlushObservers.RemoveElement(aDispatcher);
}
/* static */
TimeStamp nsRefreshDriver::GetIdleDeadlineHint(TimeStamp aDefault,
IdleCheck aCheckType) {

View File

@@ -192,24 +192,6 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
*/
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
* throttling we apply to visibility updates.
@@ -441,7 +423,6 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
};
using ObserverArray = nsTObserverArray<ObserverData>;
void RunFullscreenSteps();
void UpdateAnimationsAndSendEvents();
MOZ_CAN_RUN_SCRIPT
void RunVideoAndFrameRequestCallbacks(mozilla::TimeStamp aNowTime);
@@ -645,8 +626,6 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
nsTObserverArray<nsAPostRefreshObserver*> mPostRefreshObservers;
nsTArray<mozilla::UniquePtr<mozilla::PendingFullscreenEvent>>
mPendingFullscreenEvents;
AutoTArray<mozilla::AnimationEventDispatcher*, 16>
mAnimationEventFlushObservers;
// nsPresContexts which `NotifyContentfulPaint` have been called,
// 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