зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1864425 - Remove PendingAnimationTracker. r=birtles
Instead of starting transitions and animations as a result of a paint, use the refresh driver tick to do this. This sets the transition-ready time to the current time during the next refresh driver tick that it was started on (see mSawTickWhilePending). This is similar to what's described in the bugs comments, and seems to work nicely in practice. We could easily change that (current time) by a paint-based time if needed (when available), which would be more similar to what we were doing. But I'd rather do the simple thing for now, and land this shortly after the soft freeze is over so that we have time to watch out for regressions. There's one regression on a test that birtles wrote (using an XHR doc and switching the timeline to a rendered doc's timeline). We use the timeline's document rather than the target document to determine whether to trigger animations now. That's one of the cases where we'd keep vsync perma-running without this patch, and Chrome also fails that test. Maybe the test should be removed / the spec should be tweaked to allow this behavior? This causes some progression in some CSS transitions tests too, and I added an extra test for the vsync behavior. Over-all this is much simpler to reason about and I think we should try to do this. Differential Revision: https://phabricator.services.mozilla.com/D193583
This commit is contained in:
Родитель
77f9f27c6d
Коммит
ec7a1d06ff
|
@ -69,16 +69,11 @@ const closeAnimationInspector = async function () {
|
|||
* yet including parts of the Web Animations API.
|
||||
*/
|
||||
const enableAnimationFeatures = function () {
|
||||
return new Promise(resolve => {
|
||||
SpecialPowers.pushPrefEnv(
|
||||
{
|
||||
set: [
|
||||
["dom.animations-api.getAnimations.enabled", true],
|
||||
["dom.animations-api.timelines.enabled", true],
|
||||
],
|
||||
},
|
||||
resolve
|
||||
);
|
||||
return SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["dom.animations-api.getAnimations.enabled", true],
|
||||
["dom.animations-api.timelines.enabled", true],
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -673,7 +668,7 @@ const setStyles = async function (animationInspector, selector, properties) {
|
|||
};
|
||||
|
||||
/**
|
||||
* Wait until curren time of animations will be changed to give currrent time.
|
||||
* Wait until current time of animations will be changed to given current time.
|
||||
*
|
||||
* @param {AnimationInspector} animationInspector
|
||||
* @param {Number} currentTime
|
||||
|
@ -684,9 +679,9 @@ const waitUntilCurrentTimeChangedAt = async function (
|
|||
) {
|
||||
info(`Wait until current time will be change to ${currentTime}`);
|
||||
await waitUntil(() =>
|
||||
animationInspector.state.animations.every(
|
||||
a => a.state.currentTime === currentTime
|
||||
)
|
||||
animationInspector.state.animations.every(a => {
|
||||
return a.state.currentTime === currentTime;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ class TimeScale {
|
|||
let zeroPositionTime = 0;
|
||||
|
||||
// To shift the zero position time is the following two patterns.
|
||||
// * Animation has negative current time which is smaller than negative dleay.
|
||||
// * Animation has negative current time which is smaller than negative delay.
|
||||
// * Animation has negative delay.
|
||||
// Furthermore, we should override the zero position time if we will need to
|
||||
// expand the duration due to this negative current time or negative delay of
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "Animation.h"
|
||||
|
||||
#include "nsIFrame.h"
|
||||
#include "AnimationUtils.h"
|
||||
#include "mozAutoDocUpdate.h"
|
||||
#include "mozilla/dom/AnimationBinding.h"
|
||||
|
@ -27,8 +28,7 @@
|
|||
#include "nsDOMMutationObserver.h" // For nsAutoAnimationMutationBatch
|
||||
#include "nsDOMCSSAttrDeclaration.h" // For nsDOMCSSAttributeDeclaration
|
||||
#include "nsThreadUtils.h" // For nsRunnableMethod and nsRevocableEventPtr
|
||||
#include "nsTransitionManager.h" // For CSSTransition
|
||||
#include "PendingAnimationTracker.h" // For PendingAnimationTracker
|
||||
#include "nsTransitionManager.h" // For CSSTransition
|
||||
#include "ScrollTimelineAnimationTracker.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
@ -138,12 +138,6 @@ already_AddRefed<Animation> Animation::ClonePausedAnimation(
|
|||
|
||||
animation->mPendingState = PendingState::PausePending;
|
||||
|
||||
Document* doc = animation->GetRenderedDocument();
|
||||
MOZ_ASSERT(doc,
|
||||
"Cloning animation should already have the rendered document");
|
||||
PendingAnimationTracker* tracker = doc->GetOrCreatePendingAnimationTracker();
|
||||
tracker->AddPausePending(*animation);
|
||||
|
||||
// We expect our relevance to be the same as the orginal.
|
||||
animation->mIsRelevant = aOther.mIsRelevant;
|
||||
|
||||
|
@ -254,8 +248,6 @@ void Animation::SetEffectNoUpdate(AnimationEffect* aEffect) {
|
|||
if (wasRelevant && mIsRelevant) {
|
||||
MutationObservers::NotifyAnimationChanged(this);
|
||||
}
|
||||
|
||||
ReschedulePendingTasks();
|
||||
}
|
||||
|
||||
MaybeScheduleReplacementCheck();
|
||||
|
@ -347,7 +339,7 @@ void Animation::SetTimelineNoUpdate(AnimationTimeline* aTimeline) {
|
|||
MaybeQueueCancelEvent(activeTime);
|
||||
}
|
||||
|
||||
UpdatePendingAnimationTracker(oldTimeline, aTimeline);
|
||||
UpdateScrollTimelineAnimationTracker(oldTimeline, aTimeline);
|
||||
|
||||
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
|
||||
|
||||
|
@ -932,28 +924,28 @@ void Animation::SetCurrentTimeAsDouble(const Nullable<double>& aCurrentTime,
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void Animation::Tick() {
|
||||
// Finish pending if we have a pending ready time, but only if we also
|
||||
// have an active timeline.
|
||||
if (mPendingState != PendingState::NotPending &&
|
||||
!mPendingReadyTime.IsNull() && mTimeline &&
|
||||
!mTimeline->GetCurrentTimeAsDuration().IsNull()) {
|
||||
// Even though mPendingReadyTime is initialized using TimeStamp::Now()
|
||||
// during the *previous* tick of the refresh driver, it can still be
|
||||
// ahead of the *current* timeline time when we are using the
|
||||
// vsync timer so we need to clamp it to the timeline time.
|
||||
TimeDuration currentTime = mTimeline->GetCurrentTimeAsDuration().Value();
|
||||
if (currentTime < mPendingReadyTime.Value()) {
|
||||
mPendingReadyTime.SetValue(currentTime);
|
||||
void Animation::Tick(AnimationTimeline::TickState& aTickState) {
|
||||
if (Pending()) {
|
||||
// Finish pending if we can, but make sure we've seen one existing tick
|
||||
// at least.
|
||||
if (mSawTickWhilePending) {
|
||||
if (TryTriggerNow() &&
|
||||
StaticPrefs::
|
||||
dom_animations_mainthread_synchronization_with_geometric_animations()) {
|
||||
auto* transition = AsCSSTransition();
|
||||
const bool isTransition = transition && transition->IsTiedToMarkup();
|
||||
auto* array = isTransition ? &aTickState.mStartedTransitions
|
||||
: &aTickState.mStartedAnimations;
|
||||
array->AppendElement(this);
|
||||
bool* startedAnyGeometric =
|
||||
isTransition ? &aTickState.mStartedAnyGeometricTransition
|
||||
: &aTickState.mStartedAnyGeometricAnimation;
|
||||
if (!*startedAnyGeometric) {
|
||||
*startedAnyGeometric = mEffect && mEffect->AffectsGeometry();
|
||||
}
|
||||
}
|
||||
}
|
||||
FinishPendingAt(mPendingReadyTime.Value());
|
||||
mPendingReadyTime.SetNull();
|
||||
}
|
||||
|
||||
if (IsPossiblyOrphanedPendingAnimation()) {
|
||||
MOZ_ASSERT(mTimeline && !mTimeline->GetCurrentTimeAsDuration().IsNull(),
|
||||
"Orphaned pending animations should have an active timeline");
|
||||
FinishPendingAt(mTimeline->GetCurrentTimeAsDuration().Value());
|
||||
mSawTickWhilePending = true;
|
||||
}
|
||||
|
||||
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Sync);
|
||||
|
@ -978,106 +970,22 @@ void Animation::Tick() {
|
|||
}
|
||||
}
|
||||
|
||||
void Animation::TriggerOnNextTick(const Nullable<TimeDuration>& aReadyTime) {
|
||||
// Normally we expect the play state to be pending but it's possible that,
|
||||
// due to the handling of possibly orphaned animations in Tick(), this
|
||||
// animation got started whilst still being in another document's pending
|
||||
// animation map.
|
||||
if (!Pending()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If aReadyTime.IsNull() we'll detect this in Tick() where we check for
|
||||
// orphaned animations and trigger this animation anyway
|
||||
mPendingReadyTime = aReadyTime;
|
||||
}
|
||||
|
||||
void Animation::TriggerNow() {
|
||||
// Normally we expect the play state to be pending but when an animation
|
||||
// is cancelled and its rendered document can't be reached, we can end up
|
||||
// with the animation still in a pending player tracker even after it is
|
||||
// no longer pending.
|
||||
if (!Pending()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we don't have an active timeline we can't trigger the animation.
|
||||
// However, this is a test-only method that we don't expect to be used in
|
||||
// conjunction with animations without an active timeline so generate
|
||||
// a warning if we do find ourselves in that situation.
|
||||
if (!mTimeline || mTimeline->GetCurrentTimeAsDuration().IsNull()) {
|
||||
NS_WARNING("Failed to trigger an animation with an active timeline");
|
||||
return;
|
||||
}
|
||||
|
||||
FinishPendingAt(mTimeline->GetCurrentTimeAsDuration().Value());
|
||||
}
|
||||
|
||||
bool Animation::TryTriggerNowForFiniteTimeline() {
|
||||
// Normally we expect the play state to be pending but when an animation
|
||||
// is cancelled and its rendered document can't be reached, we can end up
|
||||
// with the animation still in a pending player tracker even after it is
|
||||
// no longer pending.
|
||||
bool Animation::TryTriggerNow() {
|
||||
if (!Pending()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mTimeline && !mTimeline->IsMonotonicallyIncreasing());
|
||||
|
||||
// It's possible that the primary frame or the scrollable frame is not ready
|
||||
// when setting up this animation. So we don't finish pending right now. In
|
||||
// this case, the timeline is inactive so it is still pending. The caller
|
||||
// should handle this case by trying this later once the scrollable frame is
|
||||
// ready.
|
||||
const auto currentTime = mTimeline->GetCurrentTimeAsDuration();
|
||||
if (currentTime.IsNull()) {
|
||||
// If we don't have an active timeline we can't trigger the animation.
|
||||
if (NS_WARN_IF(!mTimeline)) {
|
||||
return false;
|
||||
}
|
||||
auto currentTime = mTimeline->GetCurrentTimeAsDuration();
|
||||
if (NS_WARN_IF(currentTime.IsNull())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FinishPendingAt(currentTime.Value());
|
||||
return true;
|
||||
}
|
||||
|
||||
Nullable<TimeDuration> Animation::GetCurrentOrPendingStartTime() const {
|
||||
Nullable<TimeDuration> result;
|
||||
|
||||
// If we have a pending playback rate, work out what start time we will use
|
||||
// when we come to updating that playback rate.
|
||||
//
|
||||
// This logic roughly shadows that in ResumeAt but is just different enough
|
||||
// that it is difficult to extract out the common functionality (and
|
||||
// extracting that functionality out would make it harder to match ResumeAt up
|
||||
// against the spec).
|
||||
if (mPendingPlaybackRate && !mPendingReadyTime.IsNull() &&
|
||||
!mStartTime.IsNull()) {
|
||||
// If we have a hold time, use it as the current time to match.
|
||||
TimeDuration currentTimeToMatch =
|
||||
!mHoldTime.IsNull()
|
||||
? mHoldTime.Value()
|
||||
: CurrentTimeFromTimelineTime(mPendingReadyTime.Value(),
|
||||
mStartTime.Value(), mPlaybackRate);
|
||||
|
||||
result = StartTimeFromTimelineTime(
|
||||
mPendingReadyTime.Value(), currentTimeToMatch, *mPendingPlaybackRate);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!mStartTime.IsNull()) {
|
||||
result = mStartTime;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (mPendingReadyTime.IsNull() || mHoldTime.IsNull()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Calculate the equivalent start time from the pending ready time.
|
||||
result = StartTimeFromTimelineTime(mPendingReadyTime.Value(),
|
||||
mHoldTime.Value(), mPlaybackRate);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
TimeStamp Animation::AnimationTimeToTimeStamp(
|
||||
const StickyTimeDuration& aTime) const {
|
||||
// Initializes to null. Return the same object every time to benefit from
|
||||
|
@ -1169,9 +1077,7 @@ bool Animation::ShouldBeSynchronizedWithMainThread(
|
|||
// to know when you're debugging performance.
|
||||
// Note: |mSyncWithGeometricAnimations| wouldn't be set if the geometric
|
||||
// animations use scroll-timeline.
|
||||
if (StaticPrefs::
|
||||
dom_animations_mainthread_synchronization_with_geometric_animations() &&
|
||||
mSyncWithGeometricAnimations &&
|
||||
if (mSyncWithGeometricAnimations &&
|
||||
keyframeEffect->HasAnimationOfPropertySet(
|
||||
nsCSSPropertyIDSet::TransformLikeProperties())) {
|
||||
aPerformanceWarning =
|
||||
|
@ -1398,10 +1304,9 @@ void Animation::ComposeStyle(StyleAnimationValueMap& aComposeResult,
|
|||
// the main thread. To prevent flicker when this occurs we want to ensure
|
||||
// the timeline time used to calculate the main thread animation values
|
||||
// does not lag far behind the time used on the compositor. Ideally we
|
||||
// would like to use the "animation ready time" calculated at the end of
|
||||
// the layer transaction as the timeline time but it will be too late to
|
||||
// update the style rule at that point so instead we just use the current
|
||||
// wallclock time.
|
||||
// would like to use the "animation ready time", but for now we just use
|
||||
// the current wallclock time. TODO(emilio): After bug 1864425 it seems we
|
||||
// could just use the ready time, or maybe we can remove this?
|
||||
//
|
||||
// (b) For animations that are pausing that we have already taken off the
|
||||
// compositor. In this case we record a pending ready time but we don't
|
||||
|
@ -1422,13 +1327,12 @@ void Animation::ComposeStyle(StyleAnimationValueMap& aComposeResult,
|
|||
// immediately before updating the style rule and then restore it immediately
|
||||
// afterwards. This is purely to prevent visual flicker. Other behavior
|
||||
// such as dispatching events continues to rely on the regular timeline time.
|
||||
bool pending = Pending();
|
||||
const bool pending = Pending();
|
||||
{
|
||||
AutoRestore<Nullable<TimeDuration>> restoreHoldTime(mHoldTime);
|
||||
|
||||
if (pending && mHoldTime.IsNull() && !mStartTime.IsNull()) {
|
||||
Nullable<TimeDuration> timeToUse = mPendingReadyTime;
|
||||
if (timeToUse.IsNull() && mTimeline && mTimeline->TracksWallclockTime()) {
|
||||
Nullable<TimeDuration> timeToUse;
|
||||
if (mTimeline && mTimeline->TracksWallclockTime()) {
|
||||
timeToUse = mTimeline->ToTimelineTime(TimeStamp::Now());
|
||||
}
|
||||
if (!timeToUse.IsNull()) {
|
||||
|
@ -1471,12 +1375,17 @@ void Animation::NotifyEffectTargetUpdated() {
|
|||
MaybeScheduleReplacementCheck();
|
||||
}
|
||||
|
||||
void Animation::NotifyGeometricAnimationsStartingThisFrame() {
|
||||
if (!IsNewlyStarted() || !mEffect) {
|
||||
return;
|
||||
static bool EnsurePaintIsScheduled(Document& aDoc) {
|
||||
PresShell* presShell = aDoc.GetPresShell();
|
||||
if (!presShell) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mSyncWithGeometricAnimations = true;
|
||||
nsIFrame* rootFrame = presShell->GetRootFrame();
|
||||
if (!rootFrame) {
|
||||
return false;
|
||||
}
|
||||
rootFrame->SchedulePaintWithoutInvalidatingObservers();
|
||||
return rootFrame->PresContext()->RefreshDriver()->IsInRefresh();
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/web-animations/#play-an-animation
|
||||
|
@ -1572,23 +1481,18 @@ void Animation::PlayNoUpdate(ErrorResult& aRv, LimitBehavior aLimitBehavior) {
|
|||
// thread for now. We'll set this when we go to set up compositor
|
||||
// animations if it applies.
|
||||
mSyncWithGeometricAnimations = false;
|
||||
|
||||
if (HasFiniteTimeline()) {
|
||||
// Always schedule a task even if we would like to let this animation
|
||||
// immedidately ready, per spec.
|
||||
// https://drafts.csswg.org/web-animations/#playing-an-animation-section
|
||||
if (Document* doc = GetRenderedDocument()) {
|
||||
mSawTickWhilePending = false;
|
||||
if (Document* doc = GetRenderedDocument()) {
|
||||
if (HasFiniteTimeline()) {
|
||||
// Always schedule a task even if we would like to let this animation
|
||||
// immediately ready, per spec.
|
||||
// https://drafts.csswg.org/web-animations/#playing-an-animation-section
|
||||
// If there's no rendered document, we fail to track this animation, so
|
||||
// let the scroll frame to trigger it when ticking.
|
||||
doc->GetOrCreateScrollTimelineAnimationTracker()->AddPending(*this);
|
||||
} // else: we fail to track this animation, so let the scroll frame to
|
||||
// trigger it when ticking.
|
||||
} else {
|
||||
if (Document* doc = GetRenderedDocument()) {
|
||||
PendingAnimationTracker* tracker =
|
||||
doc->GetOrCreatePendingAnimationTracker();
|
||||
tracker->AddPlayPending(*this);
|
||||
} else {
|
||||
TriggerOnNextTick(Nullable<TimeDuration>());
|
||||
}
|
||||
// Make sure to try to schedule a tick.
|
||||
mSawTickWhilePending = EnsurePaintIsScheduled(*doc);
|
||||
}
|
||||
|
||||
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
|
||||
|
@ -1638,23 +1542,14 @@ void Animation::Pause(ErrorResult& aRv) {
|
|||
}
|
||||
|
||||
mPendingState = PendingState::PausePending;
|
||||
mSawTickWhilePending = false;
|
||||
|
||||
if (HasFiniteTimeline()) {
|
||||
// Always schedule a task even if we would like to let this animation
|
||||
// immedidately ready, per spec.
|
||||
// https://drafts.csswg.org/web-animations/#playing-an-animation-section
|
||||
if (Document* doc = GetRenderedDocument()) {
|
||||
// See the relevant PlayPending code for comments.
|
||||
if (Document* doc = GetRenderedDocument()) {
|
||||
if (HasFiniteTimeline()) {
|
||||
doc->GetOrCreateScrollTimelineAnimationTracker()->AddPending(*this);
|
||||
} // else: we fail to track this animation, so let the scroll frame to
|
||||
// trigger it when ticking.
|
||||
} else {
|
||||
if (Document* doc = GetRenderedDocument()) {
|
||||
PendingAnimationTracker* tracker =
|
||||
doc->GetOrCreatePendingAnimationTracker();
|
||||
tracker->AddPausePending(*this);
|
||||
} else {
|
||||
TriggerOnNextTick(Nullable<TimeDuration>());
|
||||
}
|
||||
mSawTickWhilePending = EnsurePaintIsScheduled(*doc);
|
||||
}
|
||||
|
||||
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
|
||||
|
@ -1828,28 +1723,12 @@ void Animation::PostUpdate() {
|
|||
}
|
||||
|
||||
void Animation::CancelPendingTasks() {
|
||||
if (mPendingState == PendingState::NotPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Document* doc = GetRenderedDocument()) {
|
||||
PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker();
|
||||
if (tracker) {
|
||||
if (mPendingState == PendingState::PlayPending) {
|
||||
tracker->RemovePlayPending(*this);
|
||||
} else {
|
||||
tracker->RemovePausePending(*this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mPendingState = PendingState::NotPending;
|
||||
mPendingReadyTime.SetNull();
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/web-animations/#reset-an-animations-pending-tasks
|
||||
void Animation::ResetPendingTasks() {
|
||||
if (mPendingState == PendingState::NotPending) {
|
||||
if (!Pending()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1863,26 +1742,6 @@ void Animation::ResetPendingTasks() {
|
|||
}
|
||||
}
|
||||
|
||||
void Animation::ReschedulePendingTasks() {
|
||||
if (mPendingState == PendingState::NotPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
mPendingReadyTime.SetNull();
|
||||
|
||||
if (Document* doc = GetRenderedDocument()) {
|
||||
PendingAnimationTracker* tracker =
|
||||
doc->GetOrCreatePendingAnimationTracker();
|
||||
if (mPendingState == PendingState::PlayPending &&
|
||||
!tracker->IsWaitingToPlay(*this)) {
|
||||
tracker->AddPlayPending(*this);
|
||||
} else if (mPendingState == PendingState::PausePending &&
|
||||
!tracker->IsWaitingToPause(*this)) {
|
||||
tracker->AddPausePending(*this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/web-animations-2/#at-progress-timeline-boundary
|
||||
/* static*/ Animation::ProgressTimelinePosition
|
||||
Animation::AtProgressTimelineBoundary(
|
||||
|
@ -1930,60 +1789,6 @@ Animation::AtProgressTimelineBoundary(
|
|||
: ProgressTimelinePosition::NotBoundary;
|
||||
}
|
||||
|
||||
bool Animation::IsPossiblyOrphanedPendingAnimation() const {
|
||||
// Check if we are pending but might never start because we are not being
|
||||
// tracked.
|
||||
//
|
||||
// This covers the following cases:
|
||||
//
|
||||
// * We started playing but our effect's target element was orphaned
|
||||
// or bound to a different document.
|
||||
// (note that for the case of our effect changing we should handle
|
||||
// that in SetEffect)
|
||||
// * We started playing but our timeline became inactive.
|
||||
// In this case the pending animation tracker will drop us from its hashmap
|
||||
// when we have been painted.
|
||||
// * When we started playing we couldn't find a
|
||||
// PendingAnimationTracker/ScrollTimelineAnimationTracker to register with
|
||||
// (perhaps the effect had no document) so we may
|
||||
// 1. simply set mPendingState in PlayNoUpdate and relied on this method to
|
||||
// catch us on the next tick, or
|
||||
// 2. rely on the scroll frame to tick this animation and catch us in this
|
||||
// method.
|
||||
|
||||
// If we're not pending we're ok.
|
||||
if (mPendingState == PendingState::NotPending) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we have a pending ready time then we will be started on the next
|
||||
// tick.
|
||||
if (!mPendingReadyTime.IsNull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we don't have an active timeline then we shouldn't start until
|
||||
// we do.
|
||||
if (!mTimeline || mTimeline->GetCurrentTimeAsDuration().IsNull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we have no rendered document, or we're not in our rendered document's
|
||||
// PendingAnimationTracker then there's a good chance no one is tracking us.
|
||||
//
|
||||
// If we're wrong and another document is tracking us then, at worst, we'll
|
||||
// simply start/pause the animation one tick too soon. That's better than
|
||||
// never starting/pausing the animation and is unlikely.
|
||||
Document* doc = GetRenderedDocument();
|
||||
if (!doc) {
|
||||
return true;
|
||||
}
|
||||
|
||||
PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker();
|
||||
return !tracker || (!tracker->IsWaitingToPlay(*this) &&
|
||||
!tracker->IsWaitingToPause(*this));
|
||||
}
|
||||
|
||||
StickyTimeDuration Animation::EffectEnd() const {
|
||||
if (!mEffect) {
|
||||
return StickyTimeDuration(0);
|
||||
|
@ -2004,8 +1809,8 @@ Document* Animation::GetTimelineDocument() const {
|
|||
return mTimeline ? mTimeline->GetDocument() : nullptr;
|
||||
}
|
||||
|
||||
void Animation::UpdatePendingAnimationTracker(AnimationTimeline* aOldTimeline,
|
||||
AnimationTimeline* aNewTimeline) {
|
||||
void Animation::UpdateScrollTimelineAnimationTracker(
|
||||
AnimationTimeline* aOldTimeline, AnimationTimeline* aNewTimeline) {
|
||||
// If we are still in pending, we may have to move this animation into the
|
||||
// correct animation tracker.
|
||||
Document* doc = GetRenderedDocument();
|
||||
|
@ -2021,30 +1826,14 @@ void Animation::UpdatePendingAnimationTracker(AnimationTimeline* aOldTimeline,
|
|||
return;
|
||||
}
|
||||
|
||||
const bool isPlayPending = mPendingState == PendingState::PlayPending;
|
||||
if (toFiniteTimeline) {
|
||||
// From null/document-timeline to scroll-timeline
|
||||
if (auto* tracker = doc->GetPendingAnimationTracker()) {
|
||||
if (isPlayPending) {
|
||||
tracker->RemovePlayPending(*this);
|
||||
} else {
|
||||
tracker->RemovePausePending(*this);
|
||||
}
|
||||
}
|
||||
|
||||
doc->GetOrCreateScrollTimelineAnimationTracker()->AddPending(*this);
|
||||
} else {
|
||||
// From scroll-timeline to null/document-timeline
|
||||
if (auto* tracker = doc->GetScrollTimelineAnimationTracker()) {
|
||||
tracker->RemovePending(*this);
|
||||
}
|
||||
|
||||
auto* tracker = doc->GetOrCreatePendingAnimationTracker();
|
||||
if (isPlayPending) {
|
||||
tracker->AddPlayPending(*this);
|
||||
} else {
|
||||
tracker->AddPausePending(*this);
|
||||
}
|
||||
EnsurePaintIsScheduled(*doc);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -164,7 +164,8 @@ class Animation : public DOMEventTargetHelper,
|
|||
|
||||
bool IsRunningOnCompositor() const;
|
||||
|
||||
virtual void Tick();
|
||||
using TickState = AnimationTimeline::TickState;
|
||||
virtual void Tick(TickState&);
|
||||
bool NeedsTicks() const {
|
||||
return Pending() ||
|
||||
(PlayState() == AnimationPlayState::Running &&
|
||||
|
@ -184,99 +185,14 @@ class Animation : public DOMEventTargetHelper,
|
|||
(mTimeline && !mTimeline->IsMonotonicallyIncreasing() &&
|
||||
PlayState() != AnimationPlayState::Idle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time to use for starting or pausing a pending animation.
|
||||
*
|
||||
* Typically, when an animation is played, it does not start immediately but
|
||||
* is added to a table of pending animations on the document of its effect.
|
||||
* In the meantime it sets its hold time to the time from which playback
|
||||
* should begin.
|
||||
*
|
||||
* When the document finishes painting, any pending animations in its table
|
||||
* are marked as being ready to start by calling TriggerOnNextTick.
|
||||
* The moment when the paint completed is also recorded, converted to a
|
||||
* timeline time, and passed to StartOnTick. This is so that when these
|
||||
* animations do start, they can be timed from the point when painting
|
||||
* completed.
|
||||
*
|
||||
* After calling TriggerOnNextTick, animations remain in the pending state
|
||||
* until the next refresh driver tick. At that time they transition out of
|
||||
* the pending state using the time passed to TriggerOnNextTick as the
|
||||
* effective time at which they resumed.
|
||||
*
|
||||
* This approach means that any setup time required for performing the
|
||||
* initial paint of an animation such as layerization is not deducted from
|
||||
* the running time of the animation. Without this we can easily drop the
|
||||
* first few frames of an animation, or, on slower devices, the whole
|
||||
* animation.
|
||||
*
|
||||
* Furthermore:
|
||||
*
|
||||
* - Starting the animation immediately when painting finishes is problematic
|
||||
* because the start time of the animation will be ahead of its timeline
|
||||
* (since the timeline time is based on the refresh driver time).
|
||||
* That's a problem because the animation is playing but its timing
|
||||
* suggests it starts in the future. We could update the timeline to match
|
||||
* the start time of the animation but then we'd also have to update the
|
||||
* timing and style of all animations connected to that timeline or else be
|
||||
* stuck in an inconsistent state until the next refresh driver tick.
|
||||
*
|
||||
* - If we simply use the refresh driver time on its next tick, the lag
|
||||
* between triggering an animation and its effective start is unacceptably
|
||||
* long.
|
||||
*
|
||||
* For pausing, we apply the same asynchronous approach. This is so that we
|
||||
* synchronize with animations that are running on the compositor. Otherwise
|
||||
* if the main thread lags behind the compositor there will be a noticeable
|
||||
* jump backwards when the main thread takes over. Even though main thread
|
||||
* animations could be paused immediately, we do it asynchronously for
|
||||
* consistency and so that animations paused together end up in step.
|
||||
*
|
||||
* Note that the caller of this method is responsible for removing the
|
||||
* animation from any PendingAnimationTracker it may have been added to.
|
||||
*/
|
||||
void TriggerOnNextTick(const Nullable<TimeDuration>& aReadyTime);
|
||||
/**
|
||||
* For the monotonically increasing timeline, we use this only for testing:
|
||||
* Start or pause a pending animation using the current timeline time. This
|
||||
* is used to support existing tests that expect animations to begin
|
||||
* immediately. Ideally we would rewrite the those tests and get rid of this
|
||||
* method, but there are a lot of them.
|
||||
*
|
||||
* As with TriggerOnNextTick, the caller of this method is responsible for
|
||||
* removing the animation from any PendingAnimationTracker it may have been
|
||||
* added to.
|
||||
*/
|
||||
void TriggerNow();
|
||||
/**
|
||||
* For the non-monotonically increasing timeline (e.g. ScrollTimeline), we try
|
||||
* to trigger it in ScrollTimelineAnimationTracker by this method. This uses
|
||||
* the current scroll position as the ready time. Return true if we don't need
|
||||
* to trigger it or we trigger it successfully.
|
||||
*/
|
||||
bool TryTriggerNowForFiniteTimeline();
|
||||
/**
|
||||
* When TriggerOnNextTick is called, we store the ready time but we don't
|
||||
* apply it until the next tick. In the meantime, GetStartTime() will return
|
||||
* null.
|
||||
*
|
||||
* However, if we build layer animations again before the next tick, we
|
||||
* should initialize them with the start time that GetStartTime() will return
|
||||
* on the next tick.
|
||||
*
|
||||
* If we were to simply set the start time of layer animations to null, their
|
||||
* start time would be updated to the current wallclock time when rendering
|
||||
* finishes, thus making them out of sync with the start time stored here.
|
||||
* This, in turn, will make the animation jump backwards when we build
|
||||
* animations on the next tick and apply the start time stored here.
|
||||
*
|
||||
* This method returns the start time, if resolved. Otherwise, if we have
|
||||
* a pending ready time, it returns the corresponding start time. If neither
|
||||
* of those are available, it returns null.
|
||||
*/
|
||||
Nullable<TimeDuration> GetCurrentOrPendingStartTime() const;
|
||||
|
||||
bool TryTriggerNow();
|
||||
/**
|
||||
* As with the start time, we should use the pending playback rate when
|
||||
* producing layer animations.
|
||||
|
@ -416,21 +332,6 @@ class Animation : public DOMEventTargetHelper,
|
|||
void NotifyEffectTargetUpdated();
|
||||
void NotifyGeometricAnimationsStartingThisFrame();
|
||||
|
||||
/**
|
||||
* Reschedule pending pause or pending play tasks when updating the target
|
||||
* effect.
|
||||
*
|
||||
* If we are pending, we will either be registered in the pending animation
|
||||
* tracker and have a null pending ready time, or, after our effect has been
|
||||
* painted, we will be removed from the tracker and assigned a pending ready
|
||||
* time.
|
||||
*
|
||||
* When the target effect is updated, we'll typically need to repaint so for
|
||||
* the latter case where we already have a pending ready time, clear it and
|
||||
* put ourselves back in the pending animation tracker.
|
||||
*/
|
||||
void ReschedulePendingTasks();
|
||||
|
||||
/**
|
||||
* Used by subclasses to synchronously queue a cancel event in situations
|
||||
* where the Animation may have been cancelled.
|
||||
|
@ -500,6 +401,7 @@ class Animation : public DOMEventTargetHelper,
|
|||
}
|
||||
|
||||
DocGroup* GetDocGroup();
|
||||
void SetSyncWithGeometricAnimations() { mSyncWithGeometricAnimations = true; }
|
||||
|
||||
protected:
|
||||
void SilentlySetCurrentTime(const TimeDuration& aNewCurrentTime);
|
||||
|
@ -519,8 +421,7 @@ class Animation : public DOMEventTargetHelper,
|
|||
}
|
||||
void ApplyPendingPlaybackRate() {
|
||||
if (mPendingPlaybackRate) {
|
||||
mPlaybackRate = *mPendingPlaybackRate;
|
||||
mPendingPlaybackRate.reset();
|
||||
mPlaybackRate = mPendingPlaybackRate.extract();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -561,24 +462,6 @@ class Animation : public DOMEventTargetHelper,
|
|||
* 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.
|
||||
*
|
||||
* We don't, however, want to include animations with a fixed start time such
|
||||
* as animations that are simply having their playbackRate updated or which
|
||||
* are resuming from an aborted pause.
|
||||
*/
|
||||
bool IsNewlyStarted() const {
|
||||
return mPendingState == PendingState::PlayPending &&
|
||||
mPendingReadyTime.IsNull() && mStartTime.IsNull();
|
||||
}
|
||||
bool IsPossiblyOrphanedPendingAnimation() const;
|
||||
StickyTimeDuration EffectEnd() const;
|
||||
|
||||
Nullable<TimeDuration> GetCurrentTimeForHoldTime(
|
||||
|
@ -617,15 +500,14 @@ class Animation : public DOMEventTargetHelper,
|
|||
return mTimeline && !mTimeline->IsMonotonicallyIncreasing();
|
||||
}
|
||||
|
||||
void UpdatePendingAnimationTracker(AnimationTimeline* aOldTimeline,
|
||||
AnimationTimeline* aNewTimeline);
|
||||
void UpdateScrollTimelineAnimationTracker(AnimationTimeline* aOldTimeline,
|
||||
AnimationTimeline* aNewTimeline);
|
||||
|
||||
RefPtr<AnimationTimeline> mTimeline;
|
||||
RefPtr<AnimationEffect> mEffect;
|
||||
// The beginning of the delay period.
|
||||
Nullable<TimeDuration> mStartTime; // Timeline timescale
|
||||
Nullable<TimeDuration> mHoldTime; // Animation timescale
|
||||
Nullable<TimeDuration> mPendingReadyTime; // Timeline timescale
|
||||
Nullable<TimeDuration> mPreviousCurrentTime; // Animation timescale
|
||||
double mPlaybackRate = 1.0;
|
||||
Maybe<double> mPendingPlaybackRate;
|
||||
|
@ -657,11 +539,7 @@ class Animation : public DOMEventTargetHelper,
|
|||
Maybe<uint32_t> mCachedChildIndex;
|
||||
|
||||
// Indicates if the animation is in the pending state (and what state it is
|
||||
// waiting to enter when it finished pending). We use this rather than
|
||||
// checking if this animation is tracked by a PendingAnimationTracker because
|
||||
// the animation will continue to be pending even after it has been removed
|
||||
// from the PendingAnimationTracker while it is waiting for the next tick
|
||||
// (see TriggerOnNextTick for details).
|
||||
// waiting to enter when it finished pending).
|
||||
enum class PendingState : uint8_t { NotPending, PlayPending, PausePending };
|
||||
PendingState mPendingState = PendingState::NotPending;
|
||||
|
||||
|
@ -670,6 +548,11 @@ class Animation : public DOMEventTargetHelper,
|
|||
|
||||
bool mFinishedAtLastComposeStyle = false;
|
||||
bool mWasReplaceableAtLastTick = false;
|
||||
// When we create a new pending animation, this tracks whether we've seen at
|
||||
// least one refresh driver tick. This is used to guarantee that a whole tick
|
||||
// has run before triggering the animation, which guarantees (for most pages)
|
||||
// that we've actually painted.
|
||||
bool mSawTickWhilePending = false;
|
||||
|
||||
bool mHiddenByContentVisibility = false;
|
||||
|
||||
|
@ -698,7 +581,7 @@ class Animation : public DOMEventTargetHelper,
|
|||
RTPCallerType mRTPCallerType;
|
||||
|
||||
private:
|
||||
// The id for this animaiton on the compositor.
|
||||
// The id for this animation on the compositor.
|
||||
uint64_t mIdOnCompositor = 0;
|
||||
bool mIsPartialPrerendered = false;
|
||||
};
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "AnimationTimeline.h"
|
||||
#include "mozilla/AnimationComparator.h"
|
||||
#include "mozilla/dom/Animation.h"
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
@ -38,7 +37,7 @@ AnimationTimeline::AnimationTimeline(nsIGlobalObject* aWindow,
|
|||
|
||||
AnimationTimeline::~AnimationTimeline() { mAnimationOrder.clear(); }
|
||||
|
||||
bool AnimationTimeline::Tick() {
|
||||
bool AnimationTimeline::Tick(TickState& aState) {
|
||||
bool needsTicks = false;
|
||||
|
||||
nsTArray<Animation*> animationsToRemove;
|
||||
|
@ -65,7 +64,7 @@ bool AnimationTimeline::Tick() {
|
|||
// Even if |animation| doesn't need future ticks, we should still
|
||||
// Tick it this time around since it might just need a one-off tick in
|
||||
// order to dispatch events.
|
||||
animation->Tick();
|
||||
animation->Tick(aState);
|
||||
|
||||
if (!animation->NeedsTicks()) {
|
||||
animationsToRemove.AppendElement(animation);
|
||||
|
|
|
@ -10,9 +10,7 @@
|
|||
#include "nsISupports.h"
|
||||
#include "nsWrapperCache.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "js/TypeDecls.h"
|
||||
#include "mozilla/AnimationUtils.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "nsHashKeys.h"
|
||||
#include "nsIGlobalObject.h"
|
||||
#include "nsTHashSet.h"
|
||||
|
@ -28,12 +26,27 @@ class AnimationTimeline : public nsISupports, public nsWrapperCache {
|
|||
explicit AnimationTimeline(nsIGlobalObject* aWindow,
|
||||
RTPCallerType aRTPCallerType);
|
||||
|
||||
// We want to synchronize non-geometric animations that are started at the
|
||||
// same time as geometric ones (e.g., transform animations that are started at
|
||||
// the same time as a width animation).
|
||||
//
|
||||
// We only synchronize animations with animations, and transitions with
|
||||
// transitions.
|
||||
//
|
||||
// TODO: Remove all this once bug 1540906 rides the trains.
|
||||
struct TickState {
|
||||
AutoTArray<Animation*, 8> mStartedAnimations;
|
||||
AutoTArray<Animation*, 8> mStartedTransitions;
|
||||
bool mStartedAnyGeometricTransition = false;
|
||||
bool mStartedAnyGeometricAnimation = false;
|
||||
};
|
||||
|
||||
protected:
|
||||
virtual ~AnimationTimeline();
|
||||
|
||||
// Tick animations and may remove them from the list if we don't need to
|
||||
// tick it. Return true if any animations need to be ticked.
|
||||
bool Tick();
|
||||
// Tick animations and may remove them from the list if we don't need to tick
|
||||
// it. Return true if any animations need to be ticked.
|
||||
bool Tick(TickState&);
|
||||
|
||||
public:
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
|
@ -123,14 +136,14 @@ class AnimationTimeline : public nsISupports, public nsWrapperCache {
|
|||
|
||||
// Animations observing this timeline
|
||||
//
|
||||
// We store them in (a) a hashset for quick lookup, and (b) a LinkedList
|
||||
// to maintain a fixed sampling order. Animations that are hidden by
|
||||
// We store them in (a) a hashset for quick lookup, and (b) a LinkedList to
|
||||
// maintain a fixed sampling order. Animations that are hidden by
|
||||
// `content-visibility` are not sampled and will only be in the hashset.
|
||||
// The LinkedList should always be a subset of the hashset.
|
||||
//
|
||||
// The hashset keeps a strong reference to each animation since
|
||||
// dealing with addref/release with LinkedList is difficult.
|
||||
typedef nsTHashSet<nsRefPtrHashKey<dom::Animation>> AnimationSet;
|
||||
using AnimationSet = nsTHashSet<nsRefPtrHashKey<dom::Animation>>;
|
||||
AnimationSet mAnimations;
|
||||
LinkedList<dom::Animation> mAnimationOrder;
|
||||
|
||||
|
|
|
@ -123,8 +123,8 @@ void CSSAnimation::PauseFromStyle() {
|
|||
}
|
||||
}
|
||||
|
||||
void CSSAnimation::Tick() {
|
||||
Animation::Tick();
|
||||
void CSSAnimation::Tick(TickState& aState) {
|
||||
Animation::Tick(aState);
|
||||
QueueEvents();
|
||||
}
|
||||
|
||||
|
|
|
@ -105,7 +105,7 @@ class CSSAnimation final : public Animation {
|
|||
mOwningElement = OwningElementRef();
|
||||
}
|
||||
|
||||
void Tick() override;
|
||||
void Tick(TickState&) override;
|
||||
void QueueEvents(
|
||||
const StickyTimeDuration& aActiveTime = StickyTimeDuration());
|
||||
|
||||
|
|
|
@ -189,8 +189,8 @@ void CSSTransition::QueueEvents(const StickyTimeDuration& aActiveTime) {
|
|||
}
|
||||
}
|
||||
|
||||
void CSSTransition::Tick() {
|
||||
Animation::Tick();
|
||||
void CSSTransition::Tick(TickState& aState) {
|
||||
Animation::Tick(aState);
|
||||
QueueEvents();
|
||||
}
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ class CSSTransition final : public Animation {
|
|||
|
||||
void SetEffectFromStyle(KeyframeEffect*);
|
||||
|
||||
void Tick() override;
|
||||
void Tick(TickState&) override;
|
||||
|
||||
nsCSSPropertyID TransitionProperty() const;
|
||||
AnimationValue ToValue() const;
|
||||
|
|
|
@ -105,34 +105,35 @@ TimeStamp DocumentTimeline::GetCurrentTimeStamp() const {
|
|||
: mLastRefreshDriverTime;
|
||||
}
|
||||
|
||||
void DocumentTimeline::UpdateLastRefreshDriverTime() {
|
||||
nsRefreshDriver* refreshDriver = GetRefreshDriver();
|
||||
TimeStamp refreshTime =
|
||||
refreshDriver ? refreshDriver->MostRecentRefresh() : TimeStamp();
|
||||
void DocumentTimeline::UpdateLastRefreshDriverTime(TimeStamp aKnownTime) {
|
||||
TimeStamp result = [&] {
|
||||
if (!aKnownTime.IsNull()) {
|
||||
return aKnownTime;
|
||||
}
|
||||
if (auto* rd = GetRefreshDriver()) {
|
||||
return rd->MostRecentRefresh();
|
||||
};
|
||||
return mLastRefreshDriverTime;
|
||||
}();
|
||||
|
||||
// Always return the same object to benefit from return-value optimization.
|
||||
TimeStamp result =
|
||||
!refreshTime.IsNull() ? refreshTime : mLastRefreshDriverTime;
|
||||
|
||||
nsDOMNavigationTiming* timing = mDocument->GetNavigationTiming();
|
||||
// If we don't have a refresh driver and we've never had one use the
|
||||
// timeline's zero time.
|
||||
// In addition, it's possible that our refresh driver's timestamp is behind
|
||||
// from the navigation start time because the refresh driver timestamp is
|
||||
// sent through an IPC call whereas the navigation time is set by calling
|
||||
// TimeStamp::Now() directly. In such cases we also use the timeline's zero
|
||||
// time.
|
||||
if (timing &&
|
||||
(result.IsNull() || result < timing->GetNavigationStartTimeStamp())) {
|
||||
result = timing->GetNavigationStartTimeStamp();
|
||||
// Also, let this time represent the current refresh time. This way
|
||||
// we'll save it as the last refresh time and skip looking up
|
||||
// navigation start time each time.
|
||||
refreshTime = result;
|
||||
if (nsDOMNavigationTiming* timing = mDocument->GetNavigationTiming()) {
|
||||
// If we don't have a refresh driver and we've never had one use the
|
||||
// timeline's zero time.
|
||||
// In addition, it's possible that our refresh driver's timestamp is behind
|
||||
// from the navigation start time because the refresh driver timestamp is
|
||||
// sent through an IPC call whereas the navigation time is set by calling
|
||||
// TimeStamp::Now() directly. In such cases we also use the timeline's zero
|
||||
// time.
|
||||
// Also, let this time represent the current refresh time. This way we'll
|
||||
// save it as the last refresh time and skip looking up navigation start
|
||||
// time each time.
|
||||
if (result.IsNull() || result < timing->GetNavigationStartTimeStamp()) {
|
||||
result = timing->GetNavigationStartTimeStamp();
|
||||
}
|
||||
}
|
||||
|
||||
if (!refreshTime.IsNull()) {
|
||||
mLastRefreshDriverTime = refreshTime;
|
||||
if (!result.IsNull()) {
|
||||
mLastRefreshDriverTime = result;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -175,7 +176,18 @@ void DocumentTimeline::MostRecentRefreshTimeUpdated() {
|
|||
|
||||
nsAutoAnimationMutationBatch mb(mDocument);
|
||||
|
||||
bool ticked = Tick();
|
||||
TickState state;
|
||||
bool ticked = Tick(state);
|
||||
if (state.mStartedAnyGeometricTransition) {
|
||||
for (auto* transition : state.mStartedTransitions) {
|
||||
transition->SetSyncWithGeometricAnimations();
|
||||
}
|
||||
}
|
||||
if (state.mStartedAnyGeometricAnimation) {
|
||||
for (auto* animation : state.mStartedAnimations) {
|
||||
animation->SetSyncWithGeometricAnimations();
|
||||
}
|
||||
}
|
||||
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
|
||||
|
@ -188,7 +200,13 @@ void DocumentTimeline::MostRecentRefreshTimeUpdated() {
|
|||
}
|
||||
}
|
||||
|
||||
void DocumentTimeline::WillRefresh(mozilla::TimeStamp aTime) {
|
||||
void DocumentTimeline::TriggerAllPendingAnimationsNow() {
|
||||
for (Animation* animation : mAnimationOrder) {
|
||||
animation->TryTriggerNow();
|
||||
}
|
||||
}
|
||||
|
||||
void DocumentTimeline::WillRefresh(TimeStamp aTime) {
|
||||
UpdateLastRefreshDriverTime();
|
||||
MostRecentRefreshTimeUpdated();
|
||||
}
|
||||
|
|
|
@ -59,6 +59,8 @@ class DocumentTimeline final : public AnimationTimeline,
|
|||
void NotifyAnimationContentVisibilityChanged(Animation* aAnimation,
|
||||
bool aIsVisible) override;
|
||||
|
||||
void TriggerAllPendingAnimationsNow();
|
||||
|
||||
// nsARefreshObserver methods
|
||||
void WillRefresh(TimeStamp aTime) override;
|
||||
// nsATimerAdjustmentObserver methods
|
||||
|
@ -69,7 +71,7 @@ class DocumentTimeline final : public AnimationTimeline,
|
|||
|
||||
Document* GetDocument() const override { return mDocument; }
|
||||
|
||||
void UpdateLastRefreshDriverTime();
|
||||
void UpdateLastRefreshDriverTime(TimeStamp aKnownTime = {});
|
||||
|
||||
bool IsMonotonicallyIncreasing() const override { return true; }
|
||||
|
||||
|
|
|
@ -6,9 +6,6 @@
|
|||
|
||||
#include "EffectCompositor.h"
|
||||
|
||||
#include <bitset>
|
||||
#include <initializer_list>
|
||||
|
||||
#include "mozilla/dom/Animation.h"
|
||||
#include "mozilla/dom/Element.h"
|
||||
#include "mozilla/dom/KeyframeEffect.h"
|
||||
|
@ -28,14 +25,11 @@
|
|||
#include "mozilla/StaticPrefs_layers.h"
|
||||
#include "mozilla/StyleAnimationValue.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsCSSPseudoElements.h"
|
||||
#include "nsCSSPropertyIDSet.h"
|
||||
#include "nsCSSProps.h"
|
||||
#include "nsDisplayItemTypes.h"
|
||||
#include "nsAtom.h"
|
||||
#include "nsLayoutUtils.h"
|
||||
#include "nsTArray.h"
|
||||
#include "PendingAnimationTracker.h"
|
||||
|
||||
using mozilla::dom::Animation;
|
||||
using mozilla::dom::Element;
|
||||
|
@ -128,19 +122,6 @@ bool FindAnimationsForCompositor(
|
|||
return false;
|
||||
}
|
||||
|
||||
// First check for newly-started transform animations that should be
|
||||
// synchronized with geometric animations. We need to do this before any
|
||||
// other early returns (the one above is ok) since we can only check this
|
||||
// state when the animation is newly-started.
|
||||
if (aPropertySet.Intersects(LayerAnimationInfo::GetCSSPropertiesFor(
|
||||
DisplayItemType::TYPE_TRANSFORM))) {
|
||||
PendingAnimationTracker* tracker =
|
||||
aFrame->PresContext()->Document()->GetPendingAnimationTracker();
|
||||
if (tracker) {
|
||||
tracker->MarkAnimationsThatMightNeedSynchronization();
|
||||
}
|
||||
}
|
||||
|
||||
AnimationPerformanceWarning::Type warning =
|
||||
AnimationPerformanceWarning::Type::None;
|
||||
if (!EffectCompositor::AllowCompositorAnimationsOnFrame(aFrame, warning)) {
|
||||
|
@ -169,8 +150,7 @@ bool FindAnimationsForCompositor(
|
|||
|
||||
bool foundRunningAnimations = false;
|
||||
for (KeyframeEffect* effect : *effects) {
|
||||
AnimationPerformanceWarning::Type effectWarning =
|
||||
AnimationPerformanceWarning::Type::None;
|
||||
auto effectWarning = AnimationPerformanceWarning::Type::None;
|
||||
KeyframeEffect::MatchForCompositor matchResult =
|
||||
effect->IsMatchForCompositor(aPropertySet, aFrame, *effects,
|
||||
effectWarning);
|
||||
|
|
|
@ -939,7 +939,6 @@ void KeyframeEffect::UpdateTarget(Element* aElement,
|
|||
nsAutoAnimationMutationBatch mb(mTarget.mElement->OwnerDoc());
|
||||
if (mAnimation) {
|
||||
MutationObservers::NotifyAnimationAdded(mAnimation);
|
||||
mAnimation->ReschedulePendingTasks();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2037,7 +2036,7 @@ KeyframeEffect::MatchForCompositor KeyframeEffect::IsMatchForCompositor(
|
|||
}
|
||||
|
||||
// We don't yet support off-main-thread background-color animations on
|
||||
// canvas frame or on <html> or <body> which genarate
|
||||
// canvas frame or on <html> or <body> which generate
|
||||
// nsDisplayCanvasBackgroundColor or nsDisplaySolidColor display item.
|
||||
if (aFrame->IsCanvasFrame() ||
|
||||
(aFrame->GetContent() &&
|
||||
|
|
|
@ -1,193 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "PendingAnimationTracker.h"
|
||||
|
||||
#include "mozilla/PresShell.h"
|
||||
#include "mozilla/dom/AnimationEffect.h"
|
||||
#include "mozilla/dom/AnimationTimeline.h"
|
||||
#include "mozilla/dom/Nullable.h"
|
||||
#include "nsIFrame.h"
|
||||
#include "nsTransitionManager.h" // For CSSTransition
|
||||
|
||||
using mozilla::dom::Nullable;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION(PendingAnimationTracker, mPlayPendingSet,
|
||||
mPausePendingSet, mDocument)
|
||||
|
||||
PendingAnimationTracker::PendingAnimationTracker(dom::Document* aDocument)
|
||||
: mDocument(aDocument) {}
|
||||
|
||||
void PendingAnimationTracker::AddPending(dom::Animation& aAnimation,
|
||||
AnimationSet& aSet) {
|
||||
aSet.Insert(&aAnimation);
|
||||
|
||||
// Schedule a paint. Otherwise animations that don't trigger a paint by
|
||||
// themselves (e.g. CSS animations with an empty keyframes rule) won't
|
||||
// start until something else paints.
|
||||
EnsurePaintIsScheduled();
|
||||
}
|
||||
|
||||
void PendingAnimationTracker::RemovePending(dom::Animation& aAnimation,
|
||||
AnimationSet& aSet) {
|
||||
aSet.Remove(&aAnimation);
|
||||
}
|
||||
|
||||
bool PendingAnimationTracker::IsWaiting(const dom::Animation& aAnimation,
|
||||
const AnimationSet& aSet) const {
|
||||
return aSet.Contains(const_cast<dom::Animation*>(&aAnimation));
|
||||
}
|
||||
|
||||
void PendingAnimationTracker::TriggerPendingAnimationsOnNextTick(
|
||||
const TimeStamp& aReadyTime) {
|
||||
auto triggerAnimationsAtReadyTime = [aReadyTime](
|
||||
AnimationSet& aAnimationSet) {
|
||||
for (auto iter = aAnimationSet.begin(), end = aAnimationSet.end();
|
||||
iter != end; ++iter) {
|
||||
dom::Animation* animation = *iter;
|
||||
dom::AnimationTimeline* timeline = animation->GetTimeline();
|
||||
|
||||
// If the animation does not have a timeline, just drop it from the map.
|
||||
// The animation will detect that it is not being tracked and will trigger
|
||||
// itself on the next tick where it has a timeline.
|
||||
if (!timeline) {
|
||||
aAnimationSet.Remove(iter);
|
||||
continue;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(timeline->IsMonotonicallyIncreasing(),
|
||||
"The non-monotonicially-increasing timeline should be in "
|
||||
"ScrollTimelineAnimationTracker");
|
||||
|
||||
// When the timeline's refresh driver is under test control, its values
|
||||
// have no correspondance to wallclock times so we shouldn't try to
|
||||
// convert aReadyTime (which is a wallclock time) to a timeline value.
|
||||
// Instead, the animation will be started/paused when the refresh driver
|
||||
// is next advanced since this will trigger a call to
|
||||
// TriggerPendingAnimationsNow.
|
||||
if (!timeline->TracksWallclockTime()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Nullable<TimeDuration> readyTime = timeline->ToTimelineTime(aReadyTime);
|
||||
animation->TriggerOnNextTick(readyTime);
|
||||
|
||||
aAnimationSet.Remove(iter);
|
||||
}
|
||||
};
|
||||
|
||||
triggerAnimationsAtReadyTime(mPlayPendingSet);
|
||||
triggerAnimationsAtReadyTime(mPausePendingSet);
|
||||
|
||||
mHasPlayPendingGeometricAnimations =
|
||||
mPlayPendingSet.Count() ? CheckState::Indeterminate : CheckState::Absent;
|
||||
}
|
||||
|
||||
void PendingAnimationTracker::TriggerPendingAnimationsNow() {
|
||||
auto triggerAndClearAnimations = [](AnimationSet& aAnimationSet) {
|
||||
for (const auto& animation : aAnimationSet) {
|
||||
animation->TriggerNow();
|
||||
}
|
||||
aAnimationSet.Clear();
|
||||
};
|
||||
|
||||
triggerAndClearAnimations(mPlayPendingSet);
|
||||
triggerAndClearAnimations(mPausePendingSet);
|
||||
|
||||
mHasPlayPendingGeometricAnimations = CheckState::Absent;
|
||||
}
|
||||
|
||||
static bool IsTransition(const dom::Animation& aAnimation) {
|
||||
const dom::CSSTransition* transition = aAnimation.AsCSSTransition();
|
||||
return transition && transition->IsTiedToMarkup();
|
||||
}
|
||||
|
||||
void PendingAnimationTracker::MarkAnimationsThatMightNeedSynchronization() {
|
||||
// We only set mHasPlayPendingGeometricAnimations to "present" in this method
|
||||
// and nowhere else. After setting the state to "present", if there is any
|
||||
// change to the set of play-pending animations we will reset
|
||||
// mHasPlayPendingGeometricAnimations to either "indeterminate" or "absent".
|
||||
//
|
||||
// As a result, if mHasPlayPendingGeometricAnimations is "present", we can
|
||||
// assume that this method has already been called for the current set of
|
||||
// play-pending animations and it is not necessary to run this method again.
|
||||
//
|
||||
// If mHasPlayPendingGeometricAnimations is "absent", then we can also skip
|
||||
// the body of this method since there are no notifications to be sent.
|
||||
//
|
||||
// Therefore, the only case we need to be concerned about is the
|
||||
// "indeterminate" case. For all other cases we can return early.
|
||||
//
|
||||
// Note that *without* this optimization, starting animations would become
|
||||
// O(n^2) in the case where each animation is on a different element and
|
||||
// contains a compositor-animatable property since we would end up iterating
|
||||
// over all animations in the play-pending set for each target element.
|
||||
if (mHasPlayPendingGeometricAnimations != CheckState::Indeterminate) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We only synchronize CSS transitions with other CSS transitions (and we only
|
||||
// synchronize non-transition animations with non-transition animations)
|
||||
// since typically the author will not trigger both CSS animations and
|
||||
// CSS transitions simultaneously and expect them to be synchronized.
|
||||
//
|
||||
// If we try to synchronize CSS transitions with non-transitions then for some
|
||||
// content we will end up degrading performance by forcing animations to run
|
||||
// on the main thread that really don't need to.
|
||||
|
||||
mHasPlayPendingGeometricAnimations = CheckState::Absent;
|
||||
for (const auto& animation : mPlayPendingSet) {
|
||||
if (animation->GetEffect() && animation->GetEffect()->AffectsGeometry()) {
|
||||
mHasPlayPendingGeometricAnimations &= ~CheckState::Absent;
|
||||
mHasPlayPendingGeometricAnimations |= IsTransition(*animation)
|
||||
? CheckState::TransitionsPresent
|
||||
: CheckState::AnimationsPresent;
|
||||
|
||||
// If we have both transitions and animations we don't need to look any
|
||||
// further.
|
||||
if (mHasPlayPendingGeometricAnimations ==
|
||||
(CheckState::TransitionsPresent | CheckState::AnimationsPresent)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mHasPlayPendingGeometricAnimations == CheckState::Absent) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& animation : mPlayPendingSet) {
|
||||
bool isTransition = IsTransition(*animation);
|
||||
if ((isTransition &&
|
||||
mHasPlayPendingGeometricAnimations & CheckState::TransitionsPresent) ||
|
||||
(!isTransition &&
|
||||
mHasPlayPendingGeometricAnimations & CheckState::AnimationsPresent)) {
|
||||
animation->NotifyGeometricAnimationsStartingThisFrame();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PendingAnimationTracker::EnsurePaintIsScheduled() {
|
||||
if (!mDocument) {
|
||||
return;
|
||||
}
|
||||
|
||||
PresShell* presShell = mDocument->GetPresShell();
|
||||
if (!presShell) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsIFrame* rootFrame = presShell->GetRootFrame();
|
||||
if (!rootFrame) {
|
||||
return;
|
||||
}
|
||||
|
||||
rootFrame->SchedulePaintWithoutInvalidatingObservers();
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
|
@ -1,113 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_PendingAnimationTracker_h
|
||||
#define mozilla_PendingAnimationTracker_h
|
||||
|
||||
#include "mozilla/dom/Animation.h"
|
||||
#include "mozilla/TypedEnumBits.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsTHashSet.h"
|
||||
|
||||
class nsIFrame;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace dom {
|
||||
class Document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the pending animations which use document-timeline or null-timeline
|
||||
* while playing or pausing.
|
||||
*/
|
||||
class PendingAnimationTracker final {
|
||||
public:
|
||||
explicit PendingAnimationTracker(dom::Document* aDocument);
|
||||
|
||||
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(PendingAnimationTracker)
|
||||
NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(PendingAnimationTracker)
|
||||
|
||||
void AddPlayPending(dom::Animation& aAnimation) {
|
||||
// We'd like to assert here that IsWaitingToPause(aAnimation) is false but
|
||||
// if |aAnimation| was tracked here as a pause-pending animation when it was
|
||||
// removed from |mDocument|, then re-attached to |mDocument|, and then
|
||||
// played again, we could end up here with IsWaitingToPause returning true.
|
||||
//
|
||||
// However, that should be harmless since all it means is that we'll call
|
||||
// Animation::TriggerOnNextTick or Animation::TriggerNow twice, both of
|
||||
// which will handle the redundant call gracefully.
|
||||
AddPending(aAnimation, mPlayPendingSet);
|
||||
mHasPlayPendingGeometricAnimations = CheckState::Indeterminate;
|
||||
}
|
||||
void RemovePlayPending(dom::Animation& aAnimation) {
|
||||
RemovePending(aAnimation, mPlayPendingSet);
|
||||
mHasPlayPendingGeometricAnimations = CheckState::Indeterminate;
|
||||
}
|
||||
bool IsWaitingToPlay(const dom::Animation& aAnimation) const {
|
||||
return IsWaiting(aAnimation, mPlayPendingSet);
|
||||
}
|
||||
|
||||
void AddPausePending(dom::Animation& aAnimation) {
|
||||
// As with AddPausePending, we'd like to assert that
|
||||
// IsWaitingToPlay(aAnimation) is false but there are some circumstances
|
||||
// where this can be true. Fortunately adding the animation to both pending
|
||||
// sets should be harmless.
|
||||
AddPending(aAnimation, mPausePendingSet);
|
||||
}
|
||||
void RemovePausePending(dom::Animation& aAnimation) {
|
||||
RemovePending(aAnimation, mPausePendingSet);
|
||||
}
|
||||
bool IsWaitingToPause(const dom::Animation& aAnimation) const {
|
||||
return IsWaiting(aAnimation, mPausePendingSet);
|
||||
}
|
||||
|
||||
void TriggerPendingAnimationsOnNextTick(const TimeStamp& aReadyTime);
|
||||
void TriggerPendingAnimationsNow();
|
||||
bool HasPendingAnimations() const {
|
||||
return mPlayPendingSet.Count() > 0 || mPausePendingSet.Count() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks amongst the set of play-pending animations, and, if there are
|
||||
* animations that affect geometric properties, notifies all play-pending
|
||||
* animations so that they can be synchronized, if needed.
|
||||
*/
|
||||
void MarkAnimationsThatMightNeedSynchronization();
|
||||
|
||||
private:
|
||||
~PendingAnimationTracker() = default;
|
||||
|
||||
void EnsurePaintIsScheduled();
|
||||
|
||||
using AnimationSet = nsTHashSet<nsRefPtrHashKey<dom::Animation>>;
|
||||
|
||||
void AddPending(dom::Animation& aAnimation, AnimationSet& aSet);
|
||||
void RemovePending(dom::Animation& aAnimation, AnimationSet& aSet);
|
||||
bool IsWaiting(const dom::Animation& aAnimation,
|
||||
const AnimationSet& aSet) const;
|
||||
|
||||
AnimationSet mPlayPendingSet;
|
||||
AnimationSet mPausePendingSet;
|
||||
RefPtr<dom::Document> mDocument;
|
||||
|
||||
public:
|
||||
enum class CheckState {
|
||||
Indeterminate = 0,
|
||||
Absent = 1 << 0,
|
||||
AnimationsPresent = 1 << 1,
|
||||
TransitionsPresent = 1 << 2,
|
||||
};
|
||||
|
||||
private:
|
||||
CheckState mHasPlayPendingGeometricAnimations = CheckState::Indeterminate;
|
||||
};
|
||||
|
||||
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(PendingAnimationTracker::CheckState)
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_PendingAnimationTracker_h
|
|
@ -165,8 +165,9 @@ class ScrollTimeline : public AnimationTimeline {
|
|||
// FIXME: Bug 1737927: Need to check the animation mutation observers for
|
||||
// animations with scroll timelines.
|
||||
// nsAutoAnimationMutationBatch mb(mDocument);
|
||||
|
||||
Tick();
|
||||
TickState state;
|
||||
Tick(state);
|
||||
// TODO: Do we need to synchronize scroll animations?
|
||||
}
|
||||
|
||||
// If the source of a ScrollTimeline is an element whose principal box does
|
||||
|
|
|
@ -29,7 +29,7 @@ void ScrollTimelineAnimationTracker::TriggerPendingAnimations() {
|
|||
// them immediately until the frames are ready. Using TriggerOnNextTick()
|
||||
// for scroll-driven animations may have issues because we don't tick if
|
||||
// no one does scroll.
|
||||
if (!animation->TryTriggerNowForFiniteTimeline()) {
|
||||
if (!animation->TryTriggerNow()) {
|
||||
// Note: We keep this animation pending even if its timeline is always
|
||||
// inactive. It's pretty hard to tell its future status, for example, it's
|
||||
// possible that the scroll container is in display:none subtree but the
|
||||
|
|
|
@ -37,7 +37,6 @@ EXPORTS.mozilla += [
|
|||
"Keyframe.h",
|
||||
"KeyframeEffectParams.h",
|
||||
"KeyframeUtils.h",
|
||||
"PendingAnimationTracker.h",
|
||||
"PostRestyleMode.h",
|
||||
"PseudoElementHashEntry.h",
|
||||
"ScrollTimelineAnimationTracker.h",
|
||||
|
@ -60,7 +59,6 @@ UNIFIED_SOURCES += [
|
|||
"ElementAnimationData.cpp",
|
||||
"KeyframeEffect.cpp",
|
||||
"KeyframeUtils.cpp",
|
||||
"PendingAnimationTracker.cpp",
|
||||
"ScrollTimeline.cpp",
|
||||
"ScrollTimelineAnimationTracker.cpp",
|
||||
"TimingParams.cpp",
|
||||
|
|
|
@ -44,6 +44,7 @@ SpecialPowers.pushPrefEnv({ "set": [
|
|||
// regardless of platform DPIs.
|
||||
["layout.css.devPixelsPerPx", 1],
|
||||
["layout.animation.prerender.partial", false],
|
||||
["dom.animations.mainthread-synchronization-with-geometric-animations", true],
|
||||
] },
|
||||
start);
|
||||
|
||||
|
@ -381,7 +382,7 @@ function testSetOfGeometricProperties() {
|
|||
{
|
||||
property: 'transform',
|
||||
runningOnCompositor: false,
|
||||
warning: 'CompositorAnimationWarningTransformWithSyncGeometricAnimations'
|
||||
warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
|
||||
}
|
||||
]);
|
||||
}, `${property} is treated as a geometric property`);
|
||||
|
@ -1136,6 +1137,7 @@ function testSynchronizedAnimations() {
|
|||
const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
|
||||
100 * MS_PER_SEC);
|
||||
|
||||
await Promise.all([animA.ready, animB.ready]);
|
||||
await waitForPaints();
|
||||
|
||||
assert_animation_property_state_equals(
|
||||
|
@ -1160,6 +1162,7 @@ function testSynchronizedAnimations() {
|
|||
const animC = elemC.animate({ marginLeft: [ '0px', '100px' ] },
|
||||
100 * MS_PER_SEC);
|
||||
|
||||
await Promise.all([animA.ready, animB.ready, animC.ready]);
|
||||
await waitForPaints();
|
||||
|
||||
assert_animation_property_state_equals(
|
||||
|
@ -1192,6 +1195,7 @@ function testSynchronizedAnimations() {
|
|||
const animC = elemC.animate({ translate: [ '0px', '100px' ] },
|
||||
100 * MS_PER_SEC);
|
||||
|
||||
await Promise.all([animA.ready, animB.ready, animC.ready]);
|
||||
await waitForPaints();
|
||||
|
||||
assert_animation_property_state_equals(
|
||||
|
@ -1252,8 +1256,7 @@ function testSynchronizedAnimations() {
|
|||
assert_animation_property_state_equals(
|
||||
transitionA.effect.getProperties(),
|
||||
[ { property: 'transform',
|
||||
runningOnCompositor: false,
|
||||
warning: 'CompositorAnimationWarningTransformWithSyncGeometricAnimations'
|
||||
runningOnCompositor: true,
|
||||
} ]);
|
||||
}, 'Transitions created before and after a tick are synchronized');
|
||||
|
||||
|
@ -1268,6 +1271,7 @@ function testSynchronizedAnimations() {
|
|||
const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
|
||||
100 * MS_PER_SEC);
|
||||
|
||||
await Promise.all([animA.ready, animB.ready]);
|
||||
await waitForPaints();
|
||||
|
||||
assert_animation_property_state_equals(
|
||||
|
@ -1301,6 +1305,7 @@ function testSynchronizedAnimations() {
|
|||
const cssAnimation = animationElem.getAnimations()[0];
|
||||
|
||||
await Promise.all([cssTransition.ready, cssAnimation.ready]);
|
||||
await waitForPaints();
|
||||
|
||||
assert_animation_property_state_equals(cssAnimation.effect.getProperties(),
|
||||
[{ property: 'transform',
|
||||
|
@ -1313,12 +1318,14 @@ function testSynchronizedAnimations() {
|
|||
|
||||
const animA = elemA.animate({ marginLeft: [ '0px', '100px' ] },
|
||||
100 * MS_PER_SEC);
|
||||
await animA.ready;
|
||||
await waitForPaints();
|
||||
|
||||
let animB = elemB.animate({ transform: [ 'translate(0px)',
|
||||
'translate(100px)' ] },
|
||||
100 * MS_PER_SEC);
|
||||
await animB.ready;
|
||||
await waitForPaints();
|
||||
|
||||
assert_animation_property_state_equals(
|
||||
animB.effect.getProperties(),
|
||||
|
@ -1334,11 +1341,13 @@ function testSynchronizedAnimations() {
|
|||
const animA = elemA.animate({ transform: [ 'translate(0px)',
|
||||
'translate(100px)' ] },
|
||||
100 * MS_PER_SEC);
|
||||
await animA.ready;
|
||||
await waitForPaints();
|
||||
|
||||
let animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
|
||||
100 * MS_PER_SEC);
|
||||
await animB.ready;
|
||||
await waitForPaints();
|
||||
|
||||
assert_animation_property_state_equals(
|
||||
animA.effect.getProperties(),
|
||||
|
@ -1358,6 +1367,7 @@ function testSynchronizedAnimations() {
|
|||
100 * MS_PER_SEC);
|
||||
animB.pause();
|
||||
|
||||
await animA.ready;
|
||||
await waitForPaints();
|
||||
|
||||
assert_animation_property_state_equals(
|
||||
|
@ -1378,6 +1388,7 @@ function testSynchronizedAnimations() {
|
|||
// Seek one of the animations so that their start times will differ
|
||||
animA.currentTime = 5000;
|
||||
|
||||
await Promise.all([animA.ready, animB.ready]);
|
||||
await waitForPaints();
|
||||
|
||||
assert_not_equals(animA.startTime, animB.startTime,
|
||||
|
@ -1401,6 +1412,7 @@ function testSynchronizedAnimations() {
|
|||
const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
|
||||
100 * MS_PER_SEC);
|
||||
|
||||
await Promise.all([animA.ready, animB.ready]);
|
||||
await waitForPaints();
|
||||
|
||||
assert_animation_property_state_equals(
|
||||
|
@ -1411,6 +1423,7 @@ function testSynchronizedAnimations() {
|
|||
animA.pause();
|
||||
animA.play();
|
||||
await animA.ready;
|
||||
await waitForPaints();
|
||||
|
||||
assert_animation_property_state_equals(
|
||||
animA.effect.getProperties(),
|
||||
|
@ -1431,6 +1444,7 @@ function testSynchronizedAnimations() {
|
|||
// Clear target effect
|
||||
animB.effect.target = null;
|
||||
|
||||
await Promise.all([animA.ready, animB.ready]);
|
||||
await waitForPaints();
|
||||
|
||||
assert_animation_property_state_equals(
|
||||
|
|
|
@ -65,8 +65,6 @@ skip-if = ["os == 'win' && bits == 64"] # Bug 1363957
|
|||
|
||||
["mozilla/test_moz_prefixed_properties.html"]
|
||||
|
||||
["mozilla/test_pending_animation_tracker.html"]
|
||||
|
||||
["mozilla/test_restyles.html"]
|
||||
support-files = [
|
||||
"mozilla/file_restyles.html",
|
||||
|
|
|
@ -1,134 +0,0 @@
|
|||
<!doctype html>
|
||||
<head>
|
||||
<meta charset=utf-8>
|
||||
<title>Test animations in PendingAnimationTracker</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<script src="../testcommon.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
promise_test(function waitForLoad() {
|
||||
return new Promise(resolve => {
|
||||
window.addEventListener("load", resolve, { once: true });
|
||||
});
|
||||
});
|
||||
|
||||
promise_test(async t => {
|
||||
// See below, but we should ensure we are in a rAF callback before proceeding
|
||||
// or else we will get inconsistent results.
|
||||
await waitForNextFrame();
|
||||
|
||||
const target = addDiv(t);
|
||||
const anim = target.animate(null, 100 * MS_PER_SEC);
|
||||
assert_true(SpecialPowers.DOMWindowUtils.isAnimationInPendingTracker(anim),
|
||||
'The animation should be tracked by tracker');
|
||||
|
||||
anim.effect = null;
|
||||
await waitForNextFrame();
|
||||
|
||||
assert_false(SpecialPowers.DOMWindowUtils.isAnimationInPendingTracker(anim),
|
||||
'The animation should NOT be tracked by the tracker');
|
||||
}, 'An animation whose effect is made null while pending is subsequently'
|
||||
+ ' removed from the tracker');
|
||||
|
||||
test(t => {
|
||||
const target = addDiv(t);
|
||||
const anim = target.animate(null, 100 * MS_PER_SEC);
|
||||
assert_true(SpecialPowers.DOMWindowUtils.isAnimationInPendingTracker(anim),
|
||||
'The animation should be tracked by tracker');
|
||||
|
||||
const newEffect = new KeyframeEffect(target, null);
|
||||
anim.effect = newEffect;
|
||||
|
||||
assert_true(SpecialPowers.DOMWindowUtils.isAnimationInPendingTracker(anim),
|
||||
'The animation should be still tracked by tracker');
|
||||
}, 'Setting another effect keeps the pending animation in the tracker');
|
||||
|
||||
test(t => {
|
||||
const effect = new KeyframeEffect(null, null);
|
||||
const anim = new Animation(effect);
|
||||
anim.play();
|
||||
assert_false(SpecialPowers.DOMWindowUtils.isAnimationInPendingTracker(anim),
|
||||
'The orphaned animation should NOT be tracked by tracker');
|
||||
|
||||
const target = addDiv(t);
|
||||
const newEffect = new KeyframeEffect(target, null);
|
||||
anim.effect = newEffect;
|
||||
|
||||
assert_true(SpecialPowers.DOMWindowUtils.isAnimationInPendingTracker(anim),
|
||||
'The animation should be now tracked by tracker');
|
||||
}, 'Setting effect having target element starts being tracked by the ' +
|
||||
'tracker');
|
||||
|
||||
test(t => {
|
||||
const target = addDiv(t);
|
||||
const anim = target.animate(null, 100 * MS_PER_SEC);
|
||||
assert_true(SpecialPowers.DOMWindowUtils.isAnimationInPendingTracker(anim),
|
||||
'The animation should be tracked by tracker');
|
||||
|
||||
anim.cancel();
|
||||
|
||||
assert_false(SpecialPowers.DOMWindowUtils.isAnimationInPendingTracker(anim),
|
||||
'The animation should NOT be tracked by the tracker');
|
||||
}, 'Calling cancel() removes the animation from the tracker');
|
||||
|
||||
promise_test(async t => {
|
||||
// Before proceeding this test, make sure following code is _NOT_ processed
|
||||
// between paint and refresh driver's tick. Otherwise, waitForNextFrame below
|
||||
// doesn't ensure that a paint process happens which means that there is
|
||||
// no chance to call TriggerPendingAnimationsOnNextTick to discard the
|
||||
// animation from the pending animation tracker.
|
||||
await waitForNextFrame();
|
||||
|
||||
const target = addDiv(t);
|
||||
const anim = target.animate(null, 100 * MS_PER_SEC);
|
||||
|
||||
assert_true(SpecialPowers.DOMWindowUtils.isAnimationInPendingTracker(anim),
|
||||
'The animation should be tracked by tracker');
|
||||
|
||||
target.remove();
|
||||
|
||||
assert_true(SpecialPowers.DOMWindowUtils.isAnimationInPendingTracker(anim),
|
||||
'The animation is still being tracked by the tracker');
|
||||
|
||||
await waitForNextFrame();
|
||||
assert_false(SpecialPowers.DOMWindowUtils.isAnimationInPendingTracker(anim),
|
||||
'The animation should NOT be tracked by the tracker in the ' +
|
||||
'next frame');
|
||||
}, 'Removing target element from the document removes the animation from ' +
|
||||
'the tracker in the next tick');
|
||||
|
||||
test(t => {
|
||||
const target = addDiv(t);
|
||||
const anotherTarget = addDiv(t);
|
||||
const anim = target.animate(null, 100 * MS_PER_SEC);
|
||||
assert_true(SpecialPowers.DOMWindowUtils.isAnimationInPendingTracker(anim),
|
||||
'The animation should be tracked by tracker');
|
||||
|
||||
anim.effect.target = anotherTarget;
|
||||
|
||||
assert_true(SpecialPowers.DOMWindowUtils.isAnimationInPendingTracker(anim),
|
||||
'The animation should be still tracked by tracker');
|
||||
}, 'Setting another target keeps the pending animation in the tracker');
|
||||
|
||||
test(t => {
|
||||
const effect = new KeyframeEffect(null, null);
|
||||
const anim = new Animation(effect);
|
||||
anim.play();
|
||||
assert_false(SpecialPowers.DOMWindowUtils.isAnimationInPendingTracker(anim),
|
||||
'The orphaned animation should NOT be tracked by tracker');
|
||||
|
||||
const target = addDiv(t);
|
||||
anim.effect.target = target;
|
||||
|
||||
assert_true(SpecialPowers.DOMWindowUtils.isAnimationInPendingTracker(anim),
|
||||
'The animation should be now tracked by tracker');
|
||||
}, 'Setting target element to the orphaned animation starts being tracked ' +
|
||||
'by the tracker');
|
||||
|
||||
</script>
|
||||
</body>
|
|
@ -85,7 +85,6 @@
|
|||
#include "mozilla/NullPrincipal.h"
|
||||
#include "mozilla/OriginAttributes.h"
|
||||
#include "mozilla/OwningNonNull.h"
|
||||
#include "mozilla/PendingAnimationTracker.h"
|
||||
#include "mozilla/PendingFullscreenEvent.h"
|
||||
#include "mozilla/PermissionDelegateHandler.h"
|
||||
#include "mozilla/PermissionManager.h"
|
||||
|
@ -2527,7 +2526,6 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(Document)
|
|||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOriginalDocument)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedEncoder)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentTimeline)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingAnimationTracker)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScrollTimelineAnimationTracker)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTemplateContentsOwner)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildrenCollection)
|
||||
|
@ -2653,7 +2651,6 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Document)
|
|||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOriginalDocument)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedEncoder)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentTimeline)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingAnimationTracker)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mScrollTimelineAnimationTracker)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mTemplateContentsOwner)
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildrenCollection)
|
||||
|
@ -9468,14 +9465,6 @@ SMILAnimationController* Document::GetAnimationController() {
|
|||
return mAnimationController;
|
||||
}
|
||||
|
||||
PendingAnimationTracker* Document::GetOrCreatePendingAnimationTracker() {
|
||||
if (!mPendingAnimationTracker) {
|
||||
mPendingAnimationTracker = new PendingAnimationTracker(this);
|
||||
}
|
||||
|
||||
return mPendingAnimationTracker;
|
||||
}
|
||||
|
||||
ScrollTimelineAnimationTracker*
|
||||
Document::GetOrCreateScrollTimelineAnimationTracker() {
|
||||
if (!mScrollTimelineAnimationTracker) {
|
||||
|
|
|
@ -197,7 +197,6 @@ class FullscreenExit;
|
|||
class FullscreenRequest;
|
||||
class HTMLEditor;
|
||||
struct LangGroupFontPrefs;
|
||||
class PendingAnimationTracker;
|
||||
class PermissionDelegateHandler;
|
||||
class PresShell;
|
||||
class ScrollTimelineAnimationTracker;
|
||||
|
@ -2684,19 +2683,6 @@ class Document : public nsINode,
|
|||
// If HasAnimationController is true, this is guaranteed to return non-null.
|
||||
SMILAnimationController* GetAnimationController();
|
||||
|
||||
// Gets the tracker for animations that are waiting to start.
|
||||
// Returns nullptr if there is no pending animation tracker for this document
|
||||
// which will be the case if there have never been any CSS animations or
|
||||
// transitions on elements in the document.
|
||||
PendingAnimationTracker* GetPendingAnimationTracker() {
|
||||
return mPendingAnimationTracker;
|
||||
}
|
||||
|
||||
// Gets the tracker for animations that are waiting to start and
|
||||
// creates it if it doesn't already exist. As a result, the return value
|
||||
// will never be nullptr.
|
||||
PendingAnimationTracker* GetOrCreatePendingAnimationTracker();
|
||||
|
||||
// Gets the tracker for scroll-driven animations that are waiting to start.
|
||||
// Returns nullptr if there is no scroll-driven animation tracker for this
|
||||
// document which will be the case if there have never been any scroll-driven
|
||||
|
@ -5160,10 +5146,6 @@ class Document : public nsINode,
|
|||
|
||||
RefPtr<dom::ScriptLoader> mScriptLoader;
|
||||
|
||||
// Tracker for animations that are waiting to start.
|
||||
// nullptr until GetOrCreatePendingAnimationTracker is called.
|
||||
RefPtr<PendingAnimationTracker> mPendingAnimationTracker;
|
||||
|
||||
// Tracker for scroll-driven animations that are waiting to start.
|
||||
// nullptr until GetOrCreateScrollTimelineAnimationTracker is called.
|
||||
RefPtr<ScrollTimelineAnimationTracker> mScrollTimelineAnimationTracker;
|
||||
|
|
|
@ -24,12 +24,12 @@
|
|||
#include "mozilla/dom/BindingDeclarations.h"
|
||||
#include "mozilla/dom/BlobBinding.h"
|
||||
#include "mozilla/dom/DocumentInlines.h"
|
||||
#include "mozilla/dom/DocumentTimeline.h"
|
||||
#include "mozilla/dom/DOMCollectedFramesBinding.h"
|
||||
#include "mozilla/dom/Event.h"
|
||||
#include "mozilla/dom/Touch.h"
|
||||
#include "mozilla/dom/UserActivation.h"
|
||||
#include "mozilla/EventStateManager.h"
|
||||
#include "mozilla/PendingAnimationTracker.h"
|
||||
#include "mozilla/ServoStyleSet.h"
|
||||
#include "mozilla/SharedStyleSheetCache.h"
|
||||
#include "mozilla/StaticPrefs_test.h"
|
||||
|
@ -2805,16 +2805,10 @@ nsDOMWindowUtils::AdvanceTimeAndRefresh(int64_t aMilliseconds) {
|
|||
// 'ready' promise before continuing. Then we could remove the special
|
||||
// handling here and the code path followed when testing would more closely
|
||||
// match the code path during regular operation. Filed as bug 1112957.
|
||||
nsCOMPtr<Document> doc = GetDocument();
|
||||
if (doc) {
|
||||
PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker();
|
||||
if (tracker) {
|
||||
tracker->TriggerPendingAnimationsNow();
|
||||
}
|
||||
}
|
||||
|
||||
nsPresContext* presContext = GetPresContext();
|
||||
if (presContext) {
|
||||
presContext->Document()->Timeline()->TriggerAllPendingAnimationsNow();
|
||||
|
||||
RefPtr<nsRefreshDriver> driver = presContext->RefreshDriver();
|
||||
driver->AdvanceTimeAndRefresh(aMilliseconds);
|
||||
|
||||
|
@ -3997,32 +3991,6 @@ nsDOMWindowUtils::GetOMTAStyle(Element* aElement, const nsAString& aProperty,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMWindowUtils::IsAnimationInPendingTracker(dom::Animation* aAnimation,
|
||||
bool* aRetVal) {
|
||||
MOZ_ASSERT(aRetVal);
|
||||
|
||||
if (!aAnimation) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
Document* doc = GetDocument();
|
||||
if (!doc) {
|
||||
*aRetVal = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker();
|
||||
if (!tracker) {
|
||||
*aRetVal = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
*aRetVal = tracker->IsWaitingToPlay(*aAnimation) ||
|
||||
tracker->IsWaitingToPause(*aAnimation);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class HandlingUserInputHelper final : public nsIJSRAIIHelper {
|
||||
|
|
|
@ -1935,12 +1935,6 @@ interface nsIDOMWindowUtils : nsISupports {
|
|||
AString getOMTAStyle(in Element aElement, in AString aProperty,
|
||||
[optional] in AString aPseudoElement);
|
||||
|
||||
/*
|
||||
* Returns true if the given animation is being tracked by the pending
|
||||
* animation tracker for the current document.
|
||||
*/
|
||||
bool isAnimationInPendingTracker(in Animation aAnimation);
|
||||
|
||||
/**
|
||||
* If aHandlingInput is true, this informs the event state manager that
|
||||
* we're handling user input, and provides transient user activation.
|
||||
|
|
|
@ -397,8 +397,7 @@ void AnimationInfo::AddAnimationForProperty(
|
|||
Send aSendFlag) {
|
||||
MOZ_ASSERT(aAnimation->GetEffect(),
|
||||
"Should not be adding an animation without an effect");
|
||||
MOZ_ASSERT(!aAnimation->GetCurrentOrPendingStartTime().IsNull() ||
|
||||
!aAnimation->IsPlaying() ||
|
||||
MOZ_ASSERT(!aAnimation->GetStartTime().IsNull() || !aAnimation->IsPlaying() ||
|
||||
(aAnimation->GetTimeline() &&
|
||||
aAnimation->GetTimeline()->TracksWallclockTime()),
|
||||
"If the animation has an unresolved start time it should either"
|
||||
|
@ -435,8 +434,7 @@ void AnimationInfo::AddAnimationForProperty(
|
|||
? TimeStamp()
|
||||
: aAnimation->GetTimeline()->ToTimeStamp(TimeDuration());
|
||||
|
||||
dom::Nullable<TimeDuration> startTime =
|
||||
aAnimation->GetCurrentOrPendingStartTime();
|
||||
dom::Nullable<TimeDuration> startTime = aAnimation->GetStartTime();
|
||||
if (startTime.IsNull()) {
|
||||
animation->startTime() = Nothing();
|
||||
} else {
|
||||
|
|
|
@ -259,9 +259,6 @@ bool WebRenderLayerManager::EndEmptyTransaction(EndTransactionFlags aFlags) {
|
|||
|
||||
mDisplayItemCache.SkipWaitingForPartialDisplayList();
|
||||
|
||||
// Since we don't do repeat transactions right now, just set the time
|
||||
mAnimationReadyTime = TimeStamp::Now();
|
||||
|
||||
// Don't block on hidden windows on Linux as it may block all rendering.
|
||||
const bool throttle = mWidget->IsMapped();
|
||||
mLatestTransactionId = mTransactionIdAllocator->GetTransactionId(throttle);
|
||||
|
@ -338,9 +335,6 @@ void WebRenderLayerManager::EndTransactionWithoutLayer(
|
|||
|
||||
auto clearTarget = MakeScopeExit([&] { mTarget = nullptr; });
|
||||
|
||||
// Since we don't do repeat transactions right now, just set the time
|
||||
mAnimationReadyTime = TimeStamp::Now();
|
||||
|
||||
WrBridge()->BeginTransaction();
|
||||
|
||||
LayoutDeviceIntSize size = mWidget->GetClientSize();
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
#include "mozilla/dom/SVGSVGElement.h"
|
||||
#include "mozilla/dom/SVGDocument.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
#include "mozilla/PendingAnimationTracker.h"
|
||||
#include "mozilla/PresShell.h"
|
||||
#include "mozilla/ProfilerLabels.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
|
@ -486,11 +485,6 @@ VectorImage::RequestRefresh(const TimeStamp& aTime) {
|
|||
return;
|
||||
}
|
||||
|
||||
PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker();
|
||||
if (tracker && ShouldAnimate()) {
|
||||
tracker->TriggerPendingAnimationsOnNextTick(aTime);
|
||||
}
|
||||
|
||||
EvaluateAnimation();
|
||||
|
||||
mSVGDocumentWrapper->TickRefreshDriver();
|
||||
|
|
|
@ -54,7 +54,6 @@
|
|||
#include "jsapi.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsTextFrame.h"
|
||||
#include "mozilla/PendingAnimationTracker.h"
|
||||
#include "mozilla/PendingFullscreenEvent.h"
|
||||
#include "mozilla/dom/PerformanceMainThread.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
|
@ -2134,16 +2133,6 @@ struct DocumentFrameCallbacks {
|
|||
nsTArray<FrameRequest> mCallbacks;
|
||||
};
|
||||
|
||||
static bool HasPendingAnimations(PresShell* aPresShell) {
|
||||
Document* doc = aPresShell->GetDocument();
|
||||
if (!doc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker();
|
||||
return tracker && tracker->HasPendingAnimations();
|
||||
}
|
||||
|
||||
static void TakeFrameRequestCallbacksFrom(
|
||||
Document* aDocument, nsTArray<DocumentFrameCallbacks>& aTarget) {
|
||||
aTarget.AppendElement(aDocument);
|
||||
|
@ -2502,10 +2491,7 @@ bool nsRefreshDriver::TickObserverArray(uint32_t aIdx, TimeStamp aNowTime) {
|
|||
RefPtr<PresShell> presShell = rawPresShell;
|
||||
presShell->mObservingLayoutFlushes = false;
|
||||
presShell->mWasLastReflowInterrupted = false;
|
||||
const auto flushType = HasPendingAnimations(presShell)
|
||||
? FlushType::Layout
|
||||
: FlushType::InterruptibleLayout;
|
||||
const ChangesToFlush ctf(flushType, false);
|
||||
const ChangesToFlush ctf(FlushType::InterruptibleLayout, false);
|
||||
presShell->FlushPendingNotifications(ctf);
|
||||
if (presShell->FixUpFocus()) {
|
||||
presShell->FlushPendingNotifications(ctf);
|
||||
|
|
|
@ -195,7 +195,6 @@ bool FallbackRenderer::BeginTransaction(const nsCString& aURL) {
|
|||
void FallbackRenderer::EndTransactionWithColor(const nsIntRect& aRect,
|
||||
const gfx::DeviceColor& aColor) {
|
||||
mTarget->GetDrawTarget()->FillRect(Rect(aRect), ColorPattern(aColor));
|
||||
mAnimationReadyTime = TimeStamp::Now();
|
||||
}
|
||||
|
||||
void FallbackRenderer::EndTransactionWithList(nsDisplayListBuilder* aBuilder,
|
||||
|
@ -228,7 +227,6 @@ void FallbackRenderer::EndTransactionWithList(nsDisplayListBuilder* aBuilder,
|
|||
DrawSurfaceOptions(),
|
||||
DrawOptions(1.0f, CompositionOp::OP_SOURCE));
|
||||
}
|
||||
mAnimationReadyTime = TimeStamp::Now();
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -220,8 +220,6 @@ class WindowRenderer : public FrameRecorder {
|
|||
void UpdatePartialPrerenderedAnimations(
|
||||
const nsTArray<uint64_t>& aJankedAnimations);
|
||||
|
||||
const TimeStamp& GetAnimationReadyTime() const { return mAnimationReadyTime; }
|
||||
|
||||
protected:
|
||||
virtual ~WindowRenderer() = default;
|
||||
|
||||
|
@ -231,10 +229,6 @@ class WindowRenderer : public FrameRecorder {
|
|||
// compositor.
|
||||
nsRefPtrHashtable<nsUint64HashKey, dom::Animation>
|
||||
mPartialPrerenderedAnimations;
|
||||
|
||||
// The time when painting most recently finished. This is recorded so that
|
||||
// we can time any play-pending animations from this point.
|
||||
TimeStamp mAnimationReadyTime;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -72,7 +72,6 @@
|
|||
#include "mozilla/HashTable.h"
|
||||
#include "mozilla/LookAndFeel.h"
|
||||
#include "mozilla/OperatorNewExtensions.h"
|
||||
#include "mozilla/PendingAnimationTracker.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/ProfilerLabels.h"
|
||||
#include "mozilla/ProfilerMarkers.h"
|
||||
|
@ -269,11 +268,13 @@ static uint64_t AddAnimationsForWebRender(
|
|||
aManager->CommandBuilder()
|
||||
.CreateOrRecycleWebRenderUserData<WebRenderAnimationData>(aItem);
|
||||
AnimationInfo& animationInfo = animationData->GetAnimationInfo();
|
||||
nsIFrame* frame = aItem->Frame();
|
||||
animationInfo.AddAnimationsForDisplayItem(
|
||||
aItem->Frame(), aDisplayListBuilder, aItem, aItem->GetType(),
|
||||
frame, aDisplayListBuilder, aItem, aItem->GetType(),
|
||||
aManager->LayerManager(), aPosition);
|
||||
animationInfo.StartPendingAnimations(
|
||||
aManager->LayerManager()->GetAnimationReadyTime());
|
||||
frame->PresContext()->RefreshDriver()->MostRecentRefresh(
|
||||
/* aEnsureTimerStarted = */ false));
|
||||
|
||||
// Note that animationsId can be 0 (uninitialized in AnimationInfo) if there
|
||||
// are no active animations.
|
||||
|
@ -2127,26 +2128,6 @@ nsRect nsDisplayList::GetBuildingRect() const {
|
|||
return result;
|
||||
}
|
||||
|
||||
static void TriggerPendingAnimations(Document& aDoc,
|
||||
const TimeStamp& aReadyTime) {
|
||||
MOZ_ASSERT(!aReadyTime.IsNull(),
|
||||
"Animation ready time is not set. Perhaps we're using a layer"
|
||||
" manager that doesn't update it");
|
||||
if (PendingAnimationTracker* tracker = aDoc.GetPendingAnimationTracker()) {
|
||||
PresShell* presShell = aDoc.GetPresShell();
|
||||
// If paint-suppression is in effect then we haven't finished painting
|
||||
// this document yet so we shouldn't start animations
|
||||
if (!presShell || !presShell->IsPaintingSuppressed()) {
|
||||
tracker->TriggerPendingAnimationsOnNextTick(aReadyTime);
|
||||
}
|
||||
}
|
||||
auto recurse = [&aReadyTime](Document& aDoc) {
|
||||
TriggerPendingAnimations(aDoc, aReadyTime);
|
||||
return CallState::Continue;
|
||||
};
|
||||
aDoc.EnumerateSubDocuments(recurse);
|
||||
}
|
||||
|
||||
WindowRenderer* nsDisplayListBuilder::GetWidgetWindowRenderer(nsView** aView) {
|
||||
if (aView) {
|
||||
*aView = RootReferenceFrame()->GetView();
|
||||
|
@ -2312,11 +2293,6 @@ void nsDisplayList::PaintRoot(nsDisplayListBuilder* aBuilder, gfxContext* aCtx,
|
|||
}
|
||||
|
||||
aBuilder->SetIsCompositingCheap(prevIsCompositingCheap);
|
||||
if (document && widgetTransaction) {
|
||||
TriggerPendingAnimations(*document,
|
||||
layerManager->GetAnimationReadyTime());
|
||||
}
|
||||
|
||||
if (presContext->RefreshDriver()->HasScheduleFlush()) {
|
||||
presContext->NotifyInvalidation(layerManager->GetLastTransactionId(),
|
||||
frame->GetRect());
|
||||
|
@ -2341,10 +2317,6 @@ void nsDisplayList::PaintRoot(nsDisplayListBuilder* aBuilder, gfxContext* aCtx,
|
|||
WindowRenderer::END_DEFAULT);
|
||||
|
||||
aBuilder->SetIsCompositingCheap(temp);
|
||||
|
||||
if (document && widgetTransaction) {
|
||||
TriggerPendingAnimations(*document, renderer->GetAnimationReadyTime());
|
||||
}
|
||||
}
|
||||
|
||||
void nsDisplayList::DeleteAll(nsDisplayListBuilder* aBuilder) {
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
[CSSTransition-effect.tentative.html]
|
||||
[After setting a transition's effect to null, a new transition can be started]
|
||||
expected:
|
||||
if (os == "android") and debug and swgl: FAIL
|
||||
if (os == "android") and not debug: FAIL
|
||||
if os == "win": FAIL
|
||||
[FAIL, PASS]
|
||||
|
||||
[After setting a transition's effect to null, it should be possible to interrupt that transition]
|
||||
expected:
|
||||
if (os == "linux") and not fission and debug and not swgl: [PASS, FAIL]
|
||||
if (os == "win") and (processor == "x86"): [PASS, FAIL]
|
||||
if (os == "android") and debug: [PASS, FAIL]
|
||||
if (os == "linux") and fission: [PASS, FAIL]
|
|
@ -1,6 +0,0 @@
|
|||
[transition-events-with-document-change.html]
|
||||
expected:
|
||||
if not fission and (os == "linux") and not debug: [OK, TIMEOUT]
|
||||
[transition events for an element changing document]
|
||||
expected:
|
||||
if not fission and (os == "linux") and not debug: [PASS, TIMEOUT]
|
|
@ -0,0 +1,3 @@
|
|||
[animate-no-browsing-context.html]
|
||||
[Replacing the timeline of an animation targetting an element in a document without a browsing context leaves it in the pending state]
|
||||
expected: FAIL
|
|
@ -0,0 +1,31 @@
|
|||
<!doctype html>
|
||||
<script src=/resources/testharness.js></script>
|
||||
<script src=/resources/testharnessreport.js></script>
|
||||
<title>Test that a pending animation doesn't keep vsync running forever</title>
|
||||
<body>
|
||||
<script>
|
||||
function ensureVsyncDisabled() {
|
||||
let wu = SpecialPowers.wrap(window).windowUtils;
|
||||
return new Promise(resolve => {
|
||||
function check() {
|
||||
if (wu.refreshDriverHasPendingTick) {
|
||||
requestIdleCallback(check, {timeout:300});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
check();
|
||||
})
|
||||
}
|
||||
|
||||
promise_test(async function() {
|
||||
await ensureVsyncDisabled();
|
||||
let doc = new DOMParser().parseFromString("<div>", "text/html");
|
||||
let anim = doc.querySelector("div").animate(null);
|
||||
assert_true(anim.pending, "Animation should be pending");
|
||||
anim.timeline = document.timeline;
|
||||
|
||||
await ensureVsyncDisabled();
|
||||
assert_true(true, "Vsync should be disabled");
|
||||
});
|
||||
</script>
|
|
@ -7,6 +7,8 @@
|
|||
<iframe src="about:blank"></iframe>
|
||||
<script>
|
||||
promise_test(async () => {
|
||||
await new Promise(r => window.addEventListener("load", r));
|
||||
|
||||
const target = document.getElementById("target");
|
||||
target.style.transition = "margin-left 100ms";
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче