gecko-dev/dom/animation/Animation.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1789 строки
58 KiB
C++
Исходник Обычный вид История

/* -*- 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 "Animation.h"
#include "AnimationUtils.h"
#include "mozAutoDocUpdate.h"
#include "mozilla/dom/AnimationBinding.h"
#include "mozilla/dom/AnimationPlaybackEvent.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/DocumentInlines.h"
#include "mozilla/dom/DocumentTimeline.h"
#include "mozilla/AnimationEventDispatcher.h"
#include "mozilla/AnimationTarget.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/DeclarationBlock.h"
#include "mozilla/Maybe.h" // For Maybe
#include "mozilla/TypeTraits.h" // For std::forward<>
#include "nsAnimationManager.h" // For CSSAnimation
#include "nsComputedDOMStyle.h"
#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
namespace mozilla {
namespace dom {
Bug 1171817 part 5 - Add a sequence number member to Animations; r=dbaron Web Animations defines Animations as having a globally unique sequence number for the purpose of prioritization: http://w3c.github.io/web-animations/#animation-sequence-number As of the writing of this patch, the spec says the sequence number is updated when the Animation is created. This is problematic and I have proposed that actually this should be updated from each transition from idle: https://lists.w3.org/Archives/Public/public-fx/2015AprJun/0054.html This doesn't seem to have met any opposition so I will update the spec to reflect this soon. This patch implements the behavior of updating the sequence number on each transition from idle. To make sure we perform this on each change to timing this patch removes a couple of instances of early returns to ensure that UpdateTiming is called. The current maximum sequence number is simply a class static and we make no attempt to deal with wraparound. This is because we only update this number when an animation transitions from idle which only happens when an animation is created or script calls cancel() followed by play() on the animation. Supposing that across all content this happenned an unlikely 1 billion times a second we still wouldn't exhaust the range of the unsigned 64-bit int for about 585 years. We'd like to make kUnsequenced be zero and make the static represent the current maximum. This would probably be easier to understand and recognize in a debugger. However, later in this patch series we will make CSS animations and CSS transitions override this sequencing behavior. If we define kUnsequenced to be zero and they accidentally assign zero as an actual sequence number then they'll run into trouble. To avoid that we set kUnsequenced to UINT64_MAX. --HG-- extra : commitid : DMw8uKjg4Hz extra : rebase_source : 9e98b3346f0297efce3ecfa0b2dd8a9c13075dca
2015-06-09 05:13:53 +03:00
// Static members
uint64_t Animation::sNextAnimationIndex = 0;
Bug 1171817 part 5 - Add a sequence number member to Animations; r=dbaron Web Animations defines Animations as having a globally unique sequence number for the purpose of prioritization: http://w3c.github.io/web-animations/#animation-sequence-number As of the writing of this patch, the spec says the sequence number is updated when the Animation is created. This is problematic and I have proposed that actually this should be updated from each transition from idle: https://lists.w3.org/Archives/Public/public-fx/2015AprJun/0054.html This doesn't seem to have met any opposition so I will update the spec to reflect this soon. This patch implements the behavior of updating the sequence number on each transition from idle. To make sure we perform this on each change to timing this patch removes a couple of instances of early returns to ensure that UpdateTiming is called. The current maximum sequence number is simply a class static and we make no attempt to deal with wraparound. This is because we only update this number when an animation transitions from idle which only happens when an animation is created or script calls cancel() followed by play() on the animation. Supposing that across all content this happenned an unlikely 1 billion times a second we still wouldn't exhaust the range of the unsigned 64-bit int for about 585 years. We'd like to make kUnsequenced be zero and make the static represent the current maximum. This would probably be easier to understand and recognize in a debugger. However, later in this patch series we will make CSS animations and CSS transitions override this sequencing behavior. If we define kUnsequenced to be zero and they accidentally assign zero as an actual sequence number then they'll run into trouble. To avoid that we set kUnsequenced to UINT64_MAX. --HG-- extra : commitid : DMw8uKjg4Hz extra : rebase_source : 9e98b3346f0297efce3ecfa0b2dd8a9c13075dca
2015-06-09 05:13:53 +03:00
NS_IMPL_CYCLE_COLLECTION_INHERITED(Animation, DOMEventTargetHelper, mTimeline,
mEffect, mReady, mFinished)
NS_IMPL_ADDREF_INHERITED(Animation, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(Animation, DOMEventTargetHelper)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Animation)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
JSObject* Animation::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return dom::Animation_Binding::Wrap(aCx, this, aGivenProto);
}
// ---------------------------------------------------------------------------
//
// Utility methods
//
// ---------------------------------------------------------------------------
namespace {
// A wrapper around nsAutoAnimationMutationBatch that looks up the
// appropriate document from the supplied animation.
class MOZ_RAII AutoMutationBatchForAnimation {
public:
explicit AutoMutationBatchForAnimation(
const Animation& aAnimation MOZ_GUARD_OBJECT_NOTIFIER_PARAM) {
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
Maybe<NonOwningAnimationTarget> target =
nsNodeUtils::GetTargetForAnimation(&aAnimation);
if (!target) {
return;
}
// For mutation observers, we use the OwnerDoc.
mAutoBatch.emplace(target->mElement->OwnerDoc());
}
private:
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
Maybe<nsAutoAnimationMutationBatch> mAutoBatch;
};
} // namespace
// ---------------------------------------------------------------------------
//
// Animation interface:
//
// ---------------------------------------------------------------------------
/* static */
already_AddRefed<Animation> Animation::Constructor(
const GlobalObject& aGlobal, AnimationEffect* aEffect,
const Optional<AnimationTimeline*>& aTimeline, ErrorResult& aRv) {
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
RefPtr<Animation> animation = new Animation(global);
AnimationTimeline* timeline;
if (aTimeline.WasPassed()) {
timeline = aTimeline.Value();
} else {
Document* document =
AnimationUtils::GetCurrentRealmDocument(aGlobal.Context());
if (!document) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
timeline = document->Timeline();
}
animation->SetTimelineNoUpdate(timeline);
animation->SetEffectNoUpdate(aEffect);
return animation.forget();
}
void Animation::SetId(const nsAString& aId) {
if (mId == aId) {
return;
}
mId = aId;
nsNodeUtils::AnimationChanged(this);
}
void Animation::SetEffect(AnimationEffect* aEffect) {
SetEffectNoUpdate(aEffect);
PostUpdate();
}
// https://drafts.csswg.org/web-animations/#setting-the-target-effect
void Animation::SetEffectNoUpdate(AnimationEffect* aEffect) {
Bug 1207245 - part 6 - rename nsRefPtr<T> to RefPtr<T>; r=ehsan; a=Tomcat The bulk of this commit was generated with a script, executed at the top level of a typical source code checkout. The only non-machine-generated part was modifying MFBT's moz.build to reflect the new naming. CLOSED TREE makes big refactorings like this a piece of cake. # The main substitution. find . -name '*.cpp' -o -name '*.cc' -o -name '*.h' -o -name '*.mm' -o -name '*.idl'| \ xargs perl -p -i -e ' s/nsRefPtr\.h/RefPtr\.h/g; # handle includes s/nsRefPtr ?</RefPtr</g; # handle declarations and variables ' # Handle a special friend declaration in gfx/layers/AtomicRefCountedWithFinalize.h. perl -p -i -e 's/::nsRefPtr;/::RefPtr;/' gfx/layers/AtomicRefCountedWithFinalize.h # Handle nsRefPtr.h itself, a couple places that define constructors # from nsRefPtr, and code generators specially. We do this here, rather # than indiscriminantly s/nsRefPtr/RefPtr/, because that would rename # things like nsRefPtrHashtable. perl -p -i -e 's/nsRefPtr/RefPtr/g' \ mfbt/nsRefPtr.h \ xpcom/glue/nsCOMPtr.h \ xpcom/base/OwningNonNull.h \ ipc/ipdl/ipdl/lower.py \ ipc/ipdl/ipdl/builtin.py \ dom/bindings/Codegen.py \ python/lldbutils/lldbutils/utils.py # In our indiscriminate substitution above, we renamed # nsRefPtrGetterAddRefs, the class behind getter_AddRefs. Fix that up. find . -name '*.cpp' -o -name '*.h' -o -name '*.idl' | \ xargs perl -p -i -e 's/nsRefPtrGetterAddRefs/RefPtrGetterAddRefs/g' if [ -d .git ]; then git mv mfbt/nsRefPtr.h mfbt/RefPtr.h else hg mv mfbt/nsRefPtr.h mfbt/RefPtr.h fi --HG-- rename : mfbt/nsRefPtr.h => mfbt/RefPtr.h
2015-10-18 08:24:48 +03:00
RefPtr<Animation> kungFuDeathGrip(this);
if (mEffect == aEffect) {
return;
}
AutoMutationBatchForAnimation mb(*this);
bool wasRelevant = mIsRelevant;
if (mEffect) {
// We need to notify observers now because once we set mEffect to null
// we won't be able to find the target element to notify.
if (mIsRelevant) {
nsNodeUtils::AnimationRemoved(this);
}
// Break links with the old effect and then drop it.
RefPtr<AnimationEffect> oldEffect = mEffect;
mEffect = nullptr;
oldEffect->SetAnimation(nullptr);
// The following will not do any notification because mEffect is null.
UpdateRelevance();
}
if (aEffect) {
// Break links from the new effect to its previous animation, if any.
RefPtr<AnimationEffect> newEffect = aEffect;
Animation* prevAnim = aEffect->GetAnimation();
if (prevAnim) {
prevAnim->SetEffect(nullptr);
}
// Create links with the new effect. SetAnimation(this) will also update
// mIsRelevant of this animation, and then notify mutation observer if
// needed by calling Animation::UpdateRelevance(), so we don't need to
// call it again.
mEffect = newEffect;
mEffect->SetAnimation(this);
// Notify possible add or change.
// If the target is different, the change notification will be ignored by
// AutoMutationBatchForAnimation.
if (wasRelevant && mIsRelevant) {
nsNodeUtils::AnimationChanged(this);
}
ReschedulePendingTasks();
}
Bug 1188251 part 8 - Remove call to Animation::Tick from CheckAnimationRule; r=dholbert We want to move the newly-introduced RequestRestyle call from FlushAnimations to Animation::Tick. However, nsAnimationManager::CheckAnimationRule calls Animation::Tick so this would cause us to start posting animation restyles within a restyle. Typically, Animations have an effect (currently there is only one type of effect: KeyframeEffectReadOnly) and when there is any change in timing they pass it down to their effect. However, the Animation is dependent on the duration of the effect for determining if it is "finished" or not. As a result, when an effect's timing changes, the owning Animation needs to know. (The way this *should* work is that effects should tell their animation or trigger some chain of events that causes animation's to update themselves. However, the current implementation of effects is fairly primitive and does not do this or even have a reference to the owning Animation. When we implement the script API for updating the timing properties of effects we will have to fix this but for now it is up to code in layout/style to update the Animation when it touches the corresponding effect's timing.) nsAnimationManager::CheckAnimationRule currently does this by calling Animation::Tick() which ensures the Animation's finished state is updated accordingly. Ultimately we want to ensure that Animation::Tick is called exactly once per frame (and at the appropriate point in that frame) so we'd like to remove this call from CheckAnimationRule. This patch achieves that by: * Making Animation::SetEffect update the animation's timing - this is necessary for animations that are created by CheckAnimationRule and will be necessary when once we make Animation.effect writeable from script anyway. * Calling Animation::SetEffect even for the case when we are updating the existing effect. Another side-effect of calling Animation::Tick within nsAnimationManager::CheckAnimationRule is that CSSAnimation::Tick queues events. There are some tests (e.g. layout/style/test/test_animations.html) that assume that animationstart events are dispatched immediately when new animations are created. That will change with bug 1134163 but for now we should maintain this existing behavior since changing this might introduce compatibility issues that are best dealt with as a separate bug rather than blocking this refactoring. To that end, this patch also explicitly queues animationstart events for newly-created animations.
2015-08-17 07:59:45 +03:00
MaybeScheduleReplacementCheck();
Bug 1188251 part 8 - Remove call to Animation::Tick from CheckAnimationRule; r=dholbert We want to move the newly-introduced RequestRestyle call from FlushAnimations to Animation::Tick. However, nsAnimationManager::CheckAnimationRule calls Animation::Tick so this would cause us to start posting animation restyles within a restyle. Typically, Animations have an effect (currently there is only one type of effect: KeyframeEffectReadOnly) and when there is any change in timing they pass it down to their effect. However, the Animation is dependent on the duration of the effect for determining if it is "finished" or not. As a result, when an effect's timing changes, the owning Animation needs to know. (The way this *should* work is that effects should tell their animation or trigger some chain of events that causes animation's to update themselves. However, the current implementation of effects is fairly primitive and does not do this or even have a reference to the owning Animation. When we implement the script API for updating the timing properties of effects we will have to fix this but for now it is up to code in layout/style to update the Animation when it touches the corresponding effect's timing.) nsAnimationManager::CheckAnimationRule currently does this by calling Animation::Tick() which ensures the Animation's finished state is updated accordingly. Ultimately we want to ensure that Animation::Tick is called exactly once per frame (and at the appropriate point in that frame) so we'd like to remove this call from CheckAnimationRule. This patch achieves that by: * Making Animation::SetEffect update the animation's timing - this is necessary for animations that are created by CheckAnimationRule and will be necessary when once we make Animation.effect writeable from script anyway. * Calling Animation::SetEffect even for the case when we are updating the existing effect. Another side-effect of calling Animation::Tick within nsAnimationManager::CheckAnimationRule is that CSSAnimation::Tick queues events. There are some tests (e.g. layout/style/test/test_animations.html) that assume that animationstart events are dispatched immediately when new animations are created. That will change with bug 1134163 but for now we should maintain this existing behavior since changing this might introduce compatibility issues that are best dealt with as a separate bug rather than blocking this refactoring. To that end, this patch also explicitly queues animationstart events for newly-created animations.
2015-08-17 07:59:45 +03:00
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
}
void Animation::SetTimeline(AnimationTimeline* aTimeline) {
SetTimelineNoUpdate(aTimeline);
PostUpdate();
}
// https://drafts.csswg.org/web-animations/#setting-the-timeline
void Animation::SetTimelineNoUpdate(AnimationTimeline* aTimeline) {
if (mTimeline == aTimeline) {
return;
}
StickyTimeDuration activeTime =
mEffect ? mEffect->GetComputedTiming().mActiveTime : StickyTimeDuration();
RefPtr<AnimationTimeline> oldTimeline = mTimeline;
if (oldTimeline) {
oldTimeline->RemoveAnimation(this);
}
mTimeline = aTimeline;
if (!mStartTime.IsNull()) {
mHoldTime.SetNull();
}
if (!aTimeline) {
MaybeQueueCancelEvent(activeTime);
}
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
}
// https://drafts.csswg.org/web-animations/#set-the-animation-start-time
void Animation::SetStartTime(const Nullable<TimeDuration>& aNewStartTime) {
// Return early if the start time will not change. However, if we
// are pending, then setting the start time to any value
// including the current value has the effect of aborting
// pending tasks so we should not return early in that case.
if (!Pending() && aNewStartTime == mStartTime) {
return;
}
AutoMutationBatchForAnimation mb(*this);
Nullable<TimeDuration> timelineTime;
if (mTimeline) {
// The spec says to check if the timeline is active (has a resolved time)
// before using it here, but we don't need to since it's harmless to set
// the already null time to null.
timelineTime = mTimeline->GetCurrentTimeAsDuration();
}
if (timelineTime.IsNull() && !aNewStartTime.IsNull()) {
mHoldTime.SetNull();
}
Nullable<TimeDuration> previousCurrentTime = GetCurrentTimeAsDuration();
ApplyPendingPlaybackRate();
mStartTime = aNewStartTime;
if (!aNewStartTime.IsNull()) {
if (mPlaybackRate != 0.0) {
mHoldTime.SetNull();
}
} else {
mHoldTime = previousCurrentTime;
}
CancelPendingTasks();
if (mReady) {
// We may have already resolved mReady, but in that case calling
// MaybeResolve is a no-op, so that's okay.
mReady->MaybeResolve(this);
}
UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Async);
if (IsRelevant()) {
nsNodeUtils::AnimationChanged(this);
}
PostUpdate();
}
// https://drafts.csswg.org/web-animations/#current-time
Nullable<TimeDuration> Animation::GetCurrentTimeForHoldTime(
const Nullable<TimeDuration>& aHoldTime) const {
Nullable<TimeDuration> result;
if (!aHoldTime.IsNull()) {
result = aHoldTime;
return result;
}
if (mTimeline && !mStartTime.IsNull()) {
Nullable<TimeDuration> timelineTime = mTimeline->GetCurrentTimeAsDuration();
if (!timelineTime.IsNull()) {
result = CurrentTimeFromTimelineTime(timelineTime.Value(),
mStartTime.Value(), mPlaybackRate);
}
}
return result;
}
// https://drafts.csswg.org/web-animations/#set-the-current-time
void Animation::SetCurrentTime(const TimeDuration& aSeekTime) {
// Return early if the current time has not changed. However, if we
// are pause-pending, then setting the current time to any value
// including the current value has the effect of aborting the
// pause so we should not return early in that case.
if (mPendingState != PendingState::PausePending &&
Nullable<TimeDuration>(aSeekTime) == GetCurrentTimeAsDuration()) {
return;
}
AutoMutationBatchForAnimation mb(*this);
SilentlySetCurrentTime(aSeekTime);
if (mPendingState == PendingState::PausePending) {
// Finish the pause operation
mHoldTime.SetValue(aSeekTime);
ApplyPendingPlaybackRate();
mStartTime.SetNull();
if (mReady) {
mReady->MaybeResolve(this);
}
CancelPendingTasks();
}
UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Async);
if (IsRelevant()) {
nsNodeUtils::AnimationChanged(this);
}
PostUpdate();
}
// https://drafts.csswg.org/web-animations/#set-the-playback-rate
void Animation::SetPlaybackRate(double aPlaybackRate) {
mPendingPlaybackRate.reset();
if (aPlaybackRate == mPlaybackRate) {
return;
}
AutoMutationBatchForAnimation mb(*this);
Nullable<TimeDuration> previousTime = GetCurrentTimeAsDuration();
mPlaybackRate = aPlaybackRate;
if (!previousTime.IsNull()) {
SetCurrentTime(previousTime.Value());
}
// In the case where GetCurrentTimeAsDuration() returns the same result before
// and after updating mPlaybackRate, SetCurrentTime will return early since,
// as far as it can tell, nothing has changed.
// As a result, we need to perform the following updates here:
// - update timing (since, if the sign of the playback rate has changed, our
// finished state may have changed),
// - dispatch a change notification for the changed playback rate, and
// - update the playback rate on animations on layers.
UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Async);
if (IsRelevant()) {
nsNodeUtils::AnimationChanged(this);
}
PostUpdate();
}
// https://drafts.csswg.org/web-animations/#seamlessly-update-the-playback-rate
void Animation::UpdatePlaybackRate(double aPlaybackRate) {
if (mPendingPlaybackRate && mPendingPlaybackRate.value() == aPlaybackRate) {
return;
}
// Calculate the play state using the existing playback rate since below we
// want to know if the animation is _currently_ finished or not, not whether
// it _will_ be finished.
AnimationPlayState playState = PlayState();
mPendingPlaybackRate = Some(aPlaybackRate);
// If we already have a pending task, there is nothing more to do since the
// playback rate will be applied then.
if (Pending()) {
return;
}
AutoMutationBatchForAnimation mb(*this);
if (playState == AnimationPlayState::Idle ||
playState == AnimationPlayState::Paused) {
// We are either idle or paused. In either case we can apply the pending
// playback rate immediately.
ApplyPendingPlaybackRate();
// We don't need to update timing or post an update here because:
//
// * the current time hasn't changed -- it's either unresolved or fixed
// with a hold time -- so the output won't have changed
// * the finished state won't have changed even if the sign of the
// playback rate changed since we're not finished (we're paused or idle)
// * the playback rate on layers doesn't need to be updated since we're not
// moving. Once we get a start time etc. we'll update the playback rate
// then.
//
// All we need to do is update observers so that, e.g. DevTools, report the
// right information.
if (IsRelevant()) {
nsNodeUtils::AnimationChanged(this);
}
} else if (playState == AnimationPlayState::Finished) {
MOZ_ASSERT(mTimeline && !mTimeline->GetCurrentTimeAsDuration().IsNull(),
"If we have no active timeline, we should be idle or paused");
if (aPlaybackRate != 0) {
// The unconstrained current time can only be unresolved if either we
// don't have an active timeline (and we already asserted that is not
// true) or we have an unresolved start time (in which case we should be
// paused).
MOZ_ASSERT(!GetUnconstrainedCurrentTime().IsNull(),
"Unconstrained current time should be resolved");
TimeDuration unconstrainedCurrentTime =
GetUnconstrainedCurrentTime().Value();
TimeDuration timelineTime = mTimeline->GetCurrentTimeAsDuration().Value();
mStartTime = StartTimeFromTimelineTime(
timelineTime, unconstrainedCurrentTime, aPlaybackRate);
} else {
mStartTime = mTimeline->GetCurrentTimeAsDuration();
}
ApplyPendingPlaybackRate();
// Even though we preserve the current time, we might now leave the finished
// state (e.g. if the playback rate changes sign) so we need to update
// timing.
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
if (IsRelevant()) {
nsNodeUtils::AnimationChanged(this);
}
PostUpdate();
} else {
ErrorResult rv;
Play(rv, LimitBehavior::Continue);
MOZ_ASSERT(!rv.Failed(),
"We should only fail to play when using auto-rewind behavior");
}
}
// https://drafts.csswg.org/web-animations/#play-state
AnimationPlayState Animation::PlayState() const {
Nullable<TimeDuration> currentTime = GetCurrentTimeAsDuration();
if (currentTime.IsNull() && !Pending()) {
return AnimationPlayState::Idle;
}
if (mPendingState == PendingState::PausePending ||
(mStartTime.IsNull() && !Pending())) {
return AnimationPlayState::Paused;
}
double playbackRate = CurrentOrPendingPlaybackRate();
if (!currentTime.IsNull() &&
((playbackRate > 0.0 && currentTime.Value() >= EffectEnd()) ||
(playbackRate < 0.0 && currentTime.Value() <= TimeDuration()))) {
return AnimationPlayState::Finished;
}
return AnimationPlayState::Running;
}
Promise* Animation::GetReady(ErrorResult& aRv) {
nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
if (!mReady && global) {
mReady = Promise::Create(global, aRv); // Lazily create on demand
}
if (!mReady) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
if (!Pending()) {
mReady->MaybeResolve(this);
}
return mReady;
}
Promise* Animation::GetFinished(ErrorResult& aRv) {
nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
if (!mFinished && global) {
mFinished = Promise::Create(global, aRv); // Lazily create on demand
}
if (!mFinished) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
if (mFinishedIsResolved) {
MaybeResolveFinishedPromise();
}
return mFinished;
}
// https://drafts.csswg.org/web-animations/#cancel-an-animation
void Animation::Cancel(PostRestyleMode aPostRestyle) {
bool newlyIdle = false;
if (PlayState() != AnimationPlayState::Idle) {
newlyIdle = true;
ResetPendingTasks();
if (mFinished) {
mFinished->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
}
ResetFinishedPromise();
QueuePlaybackEvent(NS_LITERAL_STRING("cancel"),
GetTimelineCurrentTimeAsTimeStamp());
}
StickyTimeDuration activeTime =
mEffect ? mEffect->GetComputedTiming().mActiveTime : StickyTimeDuration();
mHoldTime.SetNull();
mStartTime.SetNull();
// Allow our effect to remove itself from the its target element's EffectSet.
UpdateEffect(aPostRestyle);
if (mTimeline) {
mTimeline->RemoveAnimation(this);
}
MaybeQueueCancelEvent(activeTime);
if (newlyIdle && aPostRestyle == PostRestyleMode::IfNeeded) {
PostUpdate();
}
}
// https://drafts.csswg.org/web-animations/#finish-an-animation
void Animation::Finish(ErrorResult& aRv) {
double effectivePlaybackRate = CurrentOrPendingPlaybackRate();
if (effectivePlaybackRate == 0 ||
(effectivePlaybackRate > 0 && EffectEnd() == TimeDuration::Forever())) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
AutoMutationBatchForAnimation mb(*this);
ApplyPendingPlaybackRate();
// Seek to the end
TimeDuration limit =
mPlaybackRate > 0 ? TimeDuration(EffectEnd()) : TimeDuration(0);
bool didChange = GetCurrentTimeAsDuration() != Nullable<TimeDuration>(limit);
SilentlySetCurrentTime(limit);
// If we are paused or play-pending we need to fill in the start time in
// order to transition to the finished state.
//
// We only do this, however, if we have an active timeline. If we have an
// inactive timeline we can't transition into the finished state just like
// we can't transition to the running state (this finished state is really
// a substate of the running state).
if (mStartTime.IsNull() && mTimeline &&
!mTimeline->GetCurrentTimeAsDuration().IsNull()) {
mStartTime = StartTimeFromTimelineTime(
mTimeline->GetCurrentTimeAsDuration().Value(), limit, mPlaybackRate);
didChange = true;
}
// If we just resolved the start time for a pause or play-pending
// animation, we need to clear the task. We don't do this as a branch of
// the above however since we can have a play-pending animation with a
// resolved start time if we aborted a pause operation.
if (!mStartTime.IsNull() && (mPendingState == PendingState::PlayPending ||
mPendingState == PendingState::PausePending)) {
if (mPendingState == PendingState::PausePending) {
mHoldTime.SetNull();
}
CancelPendingTasks();
didChange = true;
if (mReady) {
mReady->MaybeResolve(this);
}
}
UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Sync);
if (didChange && IsRelevant()) {
nsNodeUtils::AnimationChanged(this);
}
PostUpdate();
}
void Animation::Play(ErrorResult& aRv, LimitBehavior aLimitBehavior) {
PlayNoUpdate(aRv, aLimitBehavior);
PostUpdate();
}
// https://drafts.csswg.org/web-animations/#reverse-an-animation
void Animation::Reverse(ErrorResult& aRv) {
if (!mTimeline || mTimeline->GetCurrentTimeAsDuration().IsNull()) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
double effectivePlaybackRate = CurrentOrPendingPlaybackRate();
if (effectivePlaybackRate == 0.0) {
return;
}
Maybe<double> originalPendingPlaybackRate = mPendingPlaybackRate;
mPendingPlaybackRate = Some(-effectivePlaybackRate);
Play(aRv, LimitBehavior::AutoRewind);
// If Play() threw, restore state and don't report anything to mutation
// observers.
if (aRv.Failed()) {
mPendingPlaybackRate = originalPendingPlaybackRate;
}
// Play(), above, unconditionally calls PostUpdate so we don't need to do
// it here.
}
void Animation::Persist() {
if (mReplaceState == AnimationReplaceState::Persisted) {
return;
}
bool wasRemoved = mReplaceState == AnimationReplaceState::Removed;
mReplaceState = AnimationReplaceState::Persisted;
// If the animation is not (yet) removed, there should be no side effects of
// persisting it.
if (wasRemoved) {
UpdateEffect(PostRestyleMode::IfNeeded);
PostUpdate();
}
}
// https://drafts.csswg.org/web-animations/#dom-animation-commitstyles
void Animation::CommitStyles(ErrorResult& aRv) {
if (!mEffect) {
return;
}
// Take an owning reference to the keyframe effect. This will ensure that
// this Animation and the target element remain alive after flushing style.
RefPtr<KeyframeEffect> keyframeEffect = mEffect->AsKeyframeEffect();
if (!keyframeEffect) {
return;
}
Maybe<NonOwningAnimationTarget> target = keyframeEffect->GetTarget();
if (!target) {
return;
}
if (target->mPseudoType != PseudoStyleType::NotPseudo) {
aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
return;
}
// Check it is an element with a style attribute
nsCOMPtr<nsStyledElement> styledElement = do_QueryInterface(target->mElement);
if (!styledElement) {
aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
return;
}
// Flush style before checking if the target element is rendered since the
// result could depend on pending style changes.
if (Document* doc = target->mElement->GetComposedDoc()) {
doc->FlushPendingNotifications(FlushType::Style);
}
if (!target->mElement->IsRendered()) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
nsPresContext* presContext =
nsContentUtils::GetContextForContent(target->mElement);
if (!presContext) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
// Get the computed animation values
UniquePtr<RawServoAnimationValueMap> animationValues =
Servo_AnimationValueMap_Create().Consume();
if (!presContext->EffectCompositor()->ComposeServoAnimationRuleForEffect(
*keyframeEffect, CascadeLevel(), animationValues.get())) {
NS_WARNING("Failed to compose animation style to commit");
return;
}
// Calling SetCSSDeclaration will trigger attribute setting code.
// Start the update now so that the old rule doesn't get used
// between when we mutate the declaration and when we set the new
// rule.
mozAutoDocUpdate autoUpdate(target->mElement->OwnerDoc(), true);
// Get the inline style to append to
RefPtr<DeclarationBlock> declarationBlock;
if (auto* existing = target->mElement->GetInlineStyleDeclaration()) {
declarationBlock = existing->EnsureMutable();
} else {
declarationBlock = new DeclarationBlock();
declarationBlock->SetDirty();
}
// Prepare the callback
MutationClosureData closureData;
closureData.mClosure = nsDOMCSSAttributeDeclaration::MutationClosureFunction;
closureData.mElement = target->mElement;
DeclarationBlockMutationClosure beforeChangeClosure = {
nsDOMCSSAttributeDeclaration::MutationClosureFunction,
&closureData,
};
// Set the animated styles
bool changed = false;
nsCSSPropertyIDSet properties = keyframeEffect->GetPropertySet();
for (nsCSSPropertyID property : properties) {
RefPtr<RawServoAnimationValue> computedValue =
Servo_AnimationValueMap_GetValue(animationValues.get(), property)
.Consume();
if (computedValue) {
changed |= Servo_DeclarationBlock_SetPropertyToAnimationValue(
declarationBlock->Raw(), computedValue, beforeChangeClosure);
}
}
if (!changed) {
return;
}
// Update inline style declaration
target->mElement->SetInlineStyleDeclaration(*declarationBlock, closureData);
}
// ---------------------------------------------------------------------------
//
// JS wrappers for Animation interface:
//
// ---------------------------------------------------------------------------
Nullable<double> Animation::GetStartTimeAsDouble() const {
return AnimationUtils::TimeDurationToDouble(mStartTime);
}
void Animation::SetStartTimeAsDouble(const Nullable<double>& aStartTime) {
return SetStartTime(AnimationUtils::DoubleToTimeDuration(aStartTime));
}
Nullable<double> Animation::GetCurrentTimeAsDouble() const {
return AnimationUtils::TimeDurationToDouble(GetCurrentTimeAsDuration());
}
void Animation::SetCurrentTimeAsDouble(const Nullable<double>& aCurrentTime,
ErrorResult& aRv) {
if (aCurrentTime.IsNull()) {
if (!GetCurrentTimeAsDuration().IsNull()) {
aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
}
return;
}
return SetCurrentTime(TimeDuration::FromMilliseconds(aCurrentTime.Value()));
}
// ---------------------------------------------------------------------------
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);
}
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());
}
Bug 1253476 - Make Animation::Tick do finish actions synchronously; r=hiro Animation::UpdateTiming takes a SyncNotifyFlag parameter. This is passed to UpdateFinishedState where it determines how we handle finish actions. If it is async we queue a microtask where we re-evaluate if the animation is finished or not before queuing events / resolving promises. That allows code like the following to _not_ trigger finish events: ``` const animation = elem.animate({...}, 1000); animation.currentTime += 1000; animation.effect.updateTiming({ duration: 2000 }); ``` (Since the check that the animation is finished will run in a microtask _after_ the call to updateTiming.) When the flag is "sync" we still don't _actually_ run the finish actions entirely synchronously: the finished promise is resolved synchronously, but resolving a promise actually queues a microtask for each callback. Likewise, the finish event is queued synchronously, but not dispatched. Since there should be no opportunity for script to run between when we call Animation::Tick and when we run the next microtask checkpoint (currently at the end of DocumentTimeline::WillRefresh but that will change slightly in the next patch in this series) there is no need to introduce the extra "async" microtask for re-evaluating an animation's finished state. Instead it should be possible to use the "sync" finishing behavior. Such a change should be unobservable to Web content but will reduce indirection somewhat. Differential Revision: https://phabricator.services.mozilla.com/D30318 --HG-- extra : moz-landing-system : lando
2019-05-20 08:20:17 +03:00
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Sync);
// Check for changes to whether or not this animation is replaceable.
bool isReplaceable = IsReplaceable();
if (isReplaceable && !mWasReplaceableAtLastTick) {
ScheduleReplacementCheck();
}
mWasReplaceableAtLastTick = isReplaceable;
if (!mEffect) {
return;
}
// Update layers if we are newly finished.
KeyframeEffect* keyframeEffect = mEffect->AsKeyframeEffect();
if (keyframeEffect && !keyframeEffect->Properties().IsEmpty() &&
!mFinishedAtLastComposeStyle &&
PlayState() == AnimationPlayState::Finished) {
PostUpdate();
}
}
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());
}
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
// return-value-optimization.
TimeStamp result;
// We *don't* check for mTimeline->TracksWallclockTime() here because that
// method only tells us if the timeline times can be converted to
// TimeStamps that can be compared to TimeStamp::Now() or not, *not*
// whether the timelines can be converted to TimeStamp values at all.
//
// Furthermore, we want to be able to use this method when the refresh driver
// is under test control (in which case TracksWallclockTime() will return
// false).
//
// Once we introduce timelines that are not time-based we will need to
// differentiate between them here and determine how to sort their events.
if (!mTimeline) {
return result;
}
// Check the time is convertible to a timestamp
if (aTime == TimeDuration::Forever() || mPlaybackRate == 0.0 ||
mStartTime.IsNull()) {
return result;
}
// Invert the standard relation:
// current time = (timeline time - start time) * playback rate
TimeDuration timelineTime =
TimeDuration(aTime).MultDouble(1.0 / mPlaybackRate) + mStartTime.Value();
result = mTimeline->ToTimeStamp(timelineTime);
return result;
}
TimeStamp Animation::ElapsedTimeToTimeStamp(
const StickyTimeDuration& aElapsedTime) const {
TimeDuration delay =
mEffect ? mEffect->SpecifiedTiming().Delay() : TimeDuration();
return AnimationTimeToTimeStamp(aElapsedTime + delay);
}
// https://drafts.csswg.org/web-animations/#silently-set-the-current-time
void Animation::SilentlySetCurrentTime(const TimeDuration& aSeekTime) {
if (!mHoldTime.IsNull() || mStartTime.IsNull() || !mTimeline ||
mTimeline->GetCurrentTimeAsDuration().IsNull() || mPlaybackRate == 0.0) {
mHoldTime.SetValue(aSeekTime);
if (!mTimeline || mTimeline->GetCurrentTimeAsDuration().IsNull()) {
mStartTime.SetNull();
}
} else {
mStartTime =
StartTimeFromTimelineTime(mTimeline->GetCurrentTimeAsDuration().Value(),
aSeekTime, mPlaybackRate);
}
mPreviousCurrentTime.SetNull();
}
bool Animation::ShouldBeSynchronizedWithMainThread(
const nsCSSPropertyIDSet& aPropertySet, const nsIFrame* aFrame,
AnimationPerformanceWarning::Type& aPerformanceWarning) const {
// Only synchronize playing animations
if (!IsPlaying()) {
return false;
}
// Currently only transform animations need to be synchronized
if (!aPropertySet.Intersects(nsCSSPropertyIDSet::TransformLikeProperties())) {
return false;
}
KeyframeEffect* keyframeEffect =
mEffect ? mEffect->AsKeyframeEffect() : nullptr;
if (!keyframeEffect) {
return false;
}
// Are we starting at the same time as other geometric animations?
// We check this before calling ShouldBlockAsyncTransformAnimations, partly
// because it's cheaper, but also because it's often the most useful thing
// to know when you're debugging performance.
if (mSyncWithGeometricAnimations &&
keyframeEffect->HasAnimationOfPropertySet(
nsCSSPropertyIDSet::TransformLikeProperties())) {
aPerformanceWarning =
AnimationPerformanceWarning::Type::TransformWithSyncGeometricAnimations;
return true;
}
return keyframeEffect->ShouldBlockAsyncTransformAnimations(
aFrame, aPropertySet, aPerformanceWarning);
}
void Animation::UpdateRelevance() {
bool wasRelevant = mIsRelevant;
mIsRelevant = mReplaceState != AnimationReplaceState::Removed &&
(HasCurrentEffect() || IsInEffect());
// Notify animation observers.
if (wasRelevant && !mIsRelevant) {
nsNodeUtils::AnimationRemoved(this);
} else if (!wasRelevant && mIsRelevant) {
nsNodeUtils::AnimationAdded(this);
}
}
template <class T>
bool IsMarkupAnimation(T* aAnimation) {
return aAnimation && aAnimation->IsTiedToMarkup();
}
// https://drafts.csswg.org/web-animations/#replaceable-animation
bool Animation::IsReplaceable() const {
// We never replace CSS animations or CSS transitions since they are managed
// by CSS.
if (IsMarkupAnimation(AsCSSAnimation()) ||
IsMarkupAnimation(AsCSSTransition())) {
return false;
}
// Only finished animations can be replaced.
if (PlayState() != AnimationPlayState::Finished) {
return false;
}
// Already removed animations cannot be replaced.
if (ReplaceState() == AnimationReplaceState::Removed) {
return false;
}
// We can only replace an animation if we know that, uninterfered, it would
// never start playing again. That excludes any animations on timelines that
// aren't monotonically increasing.
//
// If we don't have any timeline at all, then we can't be in the finished
// state (since we need both a resolved start time and current time for that)
// and will have already returned false above.
//
// (However, if it ever does become possible to be finished without a timeline
// then we will want to return false here since it probably suggests an
// animation being driven directly by script, in which case we can't assume
// anything about how they will behave.)
if (!GetTimeline() || !GetTimeline()->TracksWallclockTime()) {
return false;
}
// If the animation doesn't have an effect then we can't determine if it is
// filling or not so just leave it alone.
if (!GetEffect()) {
return false;
}
// At the time of writing we only know about KeyframeEffects. If we introduce
// other types of effects we will need to decide if they are replaceable or
// not.
MOZ_ASSERT(GetEffect()->AsKeyframeEffect(),
"Effect should be a keyframe effect");
// We only replace animations that are filling.
if (GetEffect()->GetComputedTiming().mProgress.IsNull()) {
return false;
}
// We should only replace animations with a target element (since otherwise
// what other effects would we consider when determining if they are covered
// or not?).
if (!GetEffect()->AsKeyframeEffect()->GetTarget()) {
return false;
}
return true;
}
bool Animation::IsRemovable() const {
return ReplaceState() == AnimationReplaceState::Active && IsReplaceable();
}
void Animation::ScheduleReplacementCheck() {
MOZ_ASSERT(
IsReplaceable(),
"Should only schedule a replacement check for a replaceable animation");
// If IsReplaceable() is true, the following should also hold
MOZ_ASSERT(GetEffect());
MOZ_ASSERT(GetEffect()->AsKeyframeEffect());
MOZ_ASSERT(GetEffect()->AsKeyframeEffect()->GetTarget());
Maybe<NonOwningAnimationTarget> target =
GetEffect()->AsKeyframeEffect()->GetTarget();
nsPresContext* presContext =
nsContentUtils::GetContextForContent(target->mElement);
if (presContext) {
presContext->EffectCompositor()->NoteElementForReducing(*target);
}
}
void Animation::MaybeScheduleReplacementCheck() {
if (!IsReplaceable()) {
return;
}
ScheduleReplacementCheck();
}
void Animation::Remove() {
MOZ_ASSERT(IsRemovable(),
"Should not be trying to remove an effect that is not removable");
mReplaceState = AnimationReplaceState::Removed;
UpdateEffect(PostRestyleMode::IfNeeded);
PostUpdate();
QueuePlaybackEvent(NS_LITERAL_STRING("remove"),
GetTimelineCurrentTimeAsTimeStamp());
}
bool Animation::HasLowerCompositeOrderThan(const Animation& aOther) const {
// 0. Object-equality case
if (&aOther == this) {
return false;
}
// 1. CSS Transitions sort lowest
{
auto asCSSTransitionForSorting =
[](const Animation& anim) -> const CSSTransition* {
const CSSTransition* transition = anim.AsCSSTransition();
return transition && transition->IsTiedToMarkup() ? transition : nullptr;
};
auto thisTransition = asCSSTransitionForSorting(*this);
auto otherTransition = asCSSTransitionForSorting(aOther);
if (thisTransition && otherTransition) {
return thisTransition->HasLowerCompositeOrderThan(*otherTransition);
}
if (thisTransition || otherTransition) {
// Cancelled transitions no longer have an owning element. To be strictly
// correct we should store a strong reference to the owning element
// so that if we arrive here while sorting cancel events, we can sort
// them in the correct order.
//
// However, given that cancel events are almost always queued
// synchronously in some deterministic manner, we can be fairly sure
// that cancel events will be dispatched in a deterministic order
// (which is our only hard requirement until specs say otherwise).
// Furthermore, we only reach here when we have events with equal
// timestamps so this is an edge case we can probably ignore for now.
return thisTransition;
}
}
// 2. CSS Animations sort next
{
auto asCSSAnimationForSorting =
[](const Animation& anim) -> const CSSAnimation* {
const CSSAnimation* animation = anim.AsCSSAnimation();
return animation && animation->IsTiedToMarkup() ? animation : nullptr;
};
auto thisAnimation = asCSSAnimationForSorting(*this);
auto otherAnimation = asCSSAnimationForSorting(aOther);
if (thisAnimation && otherAnimation) {
return thisAnimation->HasLowerCompositeOrderThan(*otherAnimation);
}
if (thisAnimation || otherAnimation) {
return thisAnimation;
}
}
// Subclasses of Animation repurpose mAnimationIndex to implement their
// own brand of composite ordering. However, by this point we should have
// handled any such custom composite ordering so we should now have unique
// animation indices.
MOZ_ASSERT(mAnimationIndex != aOther.mAnimationIndex,
"Animation indices should be unique");
// 3. Finally, generic animations sort by their position in the global
// animation array.
return mAnimationIndex < aOther.mAnimationIndex;
}
void Animation::WillComposeStyle() {
mFinishedAtLastComposeStyle = (PlayState() == AnimationPlayState::Finished);
MOZ_ASSERT(mEffect);
KeyframeEffect* keyframeEffect = mEffect->AsKeyframeEffect();
if (keyframeEffect) {
keyframeEffect->WillComposeStyle();
}
}
void Animation::ComposeStyle(RawServoAnimationValueMap& aComposeResult,
const nsCSSPropertyIDSet& aPropertiesToSkip) {
if (!mEffect) {
return;
}
// In order to prevent flicker, there are a few cases where we want to use
// a different time for rendering that would otherwise be returned by
// GetCurrentTimeAsDuration. These are:
//
// (a) For animations that are pausing but which are still running on the
// compositor. In this case we send a layer transaction that removes the
// animation but which also contains the animation values calculated on
// 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.
//
// (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
// apply it until the next tick. However, while waiting for the next tick,
// we should still use the pending ready time as the timeline time. If we
// use the regular timeline time the animation may appear jump backwards
// if the main thread's timeline time lags behind the compositor.
//
// (c) For animations that are play-pending due to an aborted pause operation
// (i.e. a pause operation that was interrupted before we entered the
// paused state). When we cancel a pending pause we might momentarily take
// the animation off the compositor, only to re-add it moments later. In
// that case the compositor might have been ahead of the main thread so we
// should use the current wallclock time to ensure the animation doesn't
// temporarily jump backwards.
//
// To address each of these cases we temporarily tweak the hold time
// 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();
{
AutoRestore<Nullable<TimeDuration>> restoreHoldTime(mHoldTime);
if (pending && mHoldTime.IsNull() && !mStartTime.IsNull()) {
Nullable<TimeDuration> timeToUse = mPendingReadyTime;
if (timeToUse.IsNull() && mTimeline && mTimeline->TracksWallclockTime()) {
timeToUse = mTimeline->ToTimelineTime(TimeStamp::Now());
}
if (!timeToUse.IsNull()) {
mHoldTime = CurrentTimeFromTimelineTime(
timeToUse.Value(), mStartTime.Value(), mPlaybackRate);
}
}
KeyframeEffect* keyframeEffect = mEffect->AsKeyframeEffect();
if (keyframeEffect) {
keyframeEffect->ComposeStyle(aComposeResult, aPropertiesToSkip);
}
}
MOZ_ASSERT(
pending == Pending(),
"Pending state should not change during the course of compositing");
}
void Animation::NotifyEffectTimingUpdated() {
MOZ_ASSERT(mEffect,
"We should only update effect timing when we have a target "
"effect");
UpdateTiming(Animation::SeekFlag::NoSeek, Animation::SyncNotifyFlag::Async);
}
void Animation::NotifyEffectPropertiesUpdated() {
MOZ_ASSERT(mEffect,
"We should only update effect properties when we have a target "
"effect");
MaybeScheduleReplacementCheck();
}
void Animation::NotifyEffectTargetUpdated() {
MOZ_ASSERT(mEffect,
"We should only update the effect target when we have a target "
"effect");
MaybeScheduleReplacementCheck();
}
void Animation::NotifyGeometricAnimationsStartingThisFrame() {
if (!IsNewlyStarted() || !mEffect) {
return;
}
mSyncWithGeometricAnimations = true;
}
// https://drafts.csswg.org/web-animations/#play-an-animation
void Animation::PlayNoUpdate(ErrorResult& aRv, LimitBehavior aLimitBehavior) {
AutoMutationBatchForAnimation mb(*this);
bool abortedPause = mPendingState == PendingState::PausePending;
double effectivePlaybackRate = CurrentOrPendingPlaybackRate();
Nullable<TimeDuration> currentTime = GetCurrentTimeAsDuration();
if (effectivePlaybackRate > 0.0 &&
(currentTime.IsNull() || (aLimitBehavior == LimitBehavior::AutoRewind &&
(currentTime.Value() < TimeDuration() ||
currentTime.Value() >= EffectEnd())))) {
mHoldTime.SetValue(TimeDuration(0));
} else if (effectivePlaybackRate < 0.0 &&
(currentTime.IsNull() ||
(aLimitBehavior == LimitBehavior::AutoRewind &&
(currentTime.Value() <= TimeDuration() ||
currentTime.Value() > EffectEnd())))) {
if (EffectEnd() == TimeDuration::Forever()) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
mHoldTime.SetValue(TimeDuration(EffectEnd()));
} else if (effectivePlaybackRate == 0.0 && currentTime.IsNull()) {
mHoldTime.SetValue(TimeDuration(0));
}
bool reuseReadyPromise = false;
if (mPendingState != PendingState::NotPending) {
CancelPendingTasks();
reuseReadyPromise = true;
}
// If the hold time is null then we're already playing normally and,
// typically, we can bail out here.
//
// However, there are two cases where we can't do that:
//
// (a) If we just aborted a pause. In this case, for consistency, we need to
// go through the motions of doing an asynchronous start.
//
// (b) If we have timing changes (specifically a change to the playbackRate)
// that should be applied asynchronously.
//
if (mHoldTime.IsNull() && !abortedPause && !mPendingPlaybackRate) {
return;
}
// Clear the start time until we resolve a new one. We do this except
// for the case where we are aborting a pause and don't have a hold time.
//
// If we're aborting a pause and *do* have a hold time (e.g. because
// the animation is finished or we just applied the auto-rewind behavior
// above) we should respect it by clearing the start time. If we *don't*
// have a hold time we should keep the current start time so that the
// the animation continues moving uninterrupted by the aborted pause.
//
// (If we're not aborting a pause, mHoldTime must be resolved by now
// or else we would have returned above.)
if (!mHoldTime.IsNull()) {
mStartTime.SetNull();
}
if (!reuseReadyPromise) {
// Clear ready promise. We'll create a new one lazily.
mReady = nullptr;
}
mPendingState = PendingState::PlayPending;
// Clear flag that causes us to sync transform animations with the main
// thread for now. We'll set this when we go to set up compositor
// animations if it applies.
mSyncWithGeometricAnimations = false;
if (Document* doc = GetRenderedDocument()) {
Bug 1171817 part 5 - Add a sequence number member to Animations; r=dbaron Web Animations defines Animations as having a globally unique sequence number for the purpose of prioritization: http://w3c.github.io/web-animations/#animation-sequence-number As of the writing of this patch, the spec says the sequence number is updated when the Animation is created. This is problematic and I have proposed that actually this should be updated from each transition from idle: https://lists.w3.org/Archives/Public/public-fx/2015AprJun/0054.html This doesn't seem to have met any opposition so I will update the spec to reflect this soon. This patch implements the behavior of updating the sequence number on each transition from idle. To make sure we perform this on each change to timing this patch removes a couple of instances of early returns to ensure that UpdateTiming is called. The current maximum sequence number is simply a class static and we make no attempt to deal with wraparound. This is because we only update this number when an animation transitions from idle which only happens when an animation is created or script calls cancel() followed by play() on the animation. Supposing that across all content this happenned an unlikely 1 billion times a second we still wouldn't exhaust the range of the unsigned 64-bit int for about 585 years. We'd like to make kUnsequenced be zero and make the static represent the current maximum. This would probably be easier to understand and recognize in a debugger. However, later in this patch series we will make CSS animations and CSS transitions override this sequencing behavior. If we define kUnsequenced to be zero and they accidentally assign zero as an actual sequence number then they'll run into trouble. To avoid that we set kUnsequenced to UINT64_MAX. --HG-- extra : commitid : DMw8uKjg4Hz extra : rebase_source : 9e98b3346f0297efce3ecfa0b2dd8a9c13075dca
2015-06-09 05:13:53 +03:00
PendingAnimationTracker* tracker =
doc->GetOrCreatePendingAnimationTracker();
tracker->AddPlayPending(*this);
} else {
TriggerOnNextTick(Nullable<TimeDuration>());
}
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
if (IsRelevant()) {
nsNodeUtils::AnimationChanged(this);
}
}
// https://drafts.csswg.org/web-animations/#pause-an-animation
void Animation::Pause(ErrorResult& aRv) {
if (IsPausedOrPausing()) {
return;
}
AutoMutationBatchForAnimation mb(*this);
// If we are transitioning from idle, fill in the current time
if (GetCurrentTimeAsDuration().IsNull()) {
if (mPlaybackRate >= 0.0) {
mHoldTime.SetValue(TimeDuration(0));
} else {
if (EffectEnd() == TimeDuration::Forever()) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
mHoldTime.SetValue(TimeDuration(EffectEnd()));
}
}
bool reuseReadyPromise = false;
if (mPendingState == PendingState::PlayPending) {
CancelPendingTasks();
reuseReadyPromise = true;
}
if (!reuseReadyPromise) {
// Clear ready promise. We'll create a new one lazily.
mReady = nullptr;
}
mPendingState = PendingState::PausePending;
if (Document* doc = GetRenderedDocument()) {
Bug 1171817 part 5 - Add a sequence number member to Animations; r=dbaron Web Animations defines Animations as having a globally unique sequence number for the purpose of prioritization: http://w3c.github.io/web-animations/#animation-sequence-number As of the writing of this patch, the spec says the sequence number is updated when the Animation is created. This is problematic and I have proposed that actually this should be updated from each transition from idle: https://lists.w3.org/Archives/Public/public-fx/2015AprJun/0054.html This doesn't seem to have met any opposition so I will update the spec to reflect this soon. This patch implements the behavior of updating the sequence number on each transition from idle. To make sure we perform this on each change to timing this patch removes a couple of instances of early returns to ensure that UpdateTiming is called. The current maximum sequence number is simply a class static and we make no attempt to deal with wraparound. This is because we only update this number when an animation transitions from idle which only happens when an animation is created or script calls cancel() followed by play() on the animation. Supposing that across all content this happenned an unlikely 1 billion times a second we still wouldn't exhaust the range of the unsigned 64-bit int for about 585 years. We'd like to make kUnsequenced be zero and make the static represent the current maximum. This would probably be easier to understand and recognize in a debugger. However, later in this patch series we will make CSS animations and CSS transitions override this sequencing behavior. If we define kUnsequenced to be zero and they accidentally assign zero as an actual sequence number then they'll run into trouble. To avoid that we set kUnsequenced to UINT64_MAX. --HG-- extra : commitid : DMw8uKjg4Hz extra : rebase_source : 9e98b3346f0297efce3ecfa0b2dd8a9c13075dca
2015-06-09 05:13:53 +03:00
PendingAnimationTracker* tracker =
doc->GetOrCreatePendingAnimationTracker();
tracker->AddPausePending(*this);
} else {
TriggerOnNextTick(Nullable<TimeDuration>());
}
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
if (IsRelevant()) {
nsNodeUtils::AnimationChanged(this);
}
PostUpdate();
}
// https://drafts.csswg.org/web-animations/#play-an-animation
void Animation::ResumeAt(const TimeDuration& aReadyTime) {
// This method is only expected to be called for an animation that is
// waiting to play. We can easily adapt it to handle other states
// but it's currently not necessary.
MOZ_ASSERT(mPendingState == PendingState::PlayPending,
"Expected to resume a play-pending animation");
MOZ_ASSERT(!mHoldTime.IsNull() || !mStartTime.IsNull(),
"An animation in the play-pending state should have either a"
" resolved hold time or resolved start time");
AutoMutationBatchForAnimation mb(*this);
bool hadPendingPlaybackRate = mPendingPlaybackRate.isSome();
if (!mHoldTime.IsNull()) {
// The hold time is set, so we don't need any special handling to preserve
// the current time.
ApplyPendingPlaybackRate();
mStartTime =
StartTimeFromTimelineTime(aReadyTime, mHoldTime.Value(), mPlaybackRate);
if (mPlaybackRate != 0) {
mHoldTime.SetNull();
}
} else if (!mStartTime.IsNull() && mPendingPlaybackRate) {
// Apply any pending playback rate, preserving the current time.
TimeDuration currentTimeToMatch = CurrentTimeFromTimelineTime(
aReadyTime, mStartTime.Value(), mPlaybackRate);
ApplyPendingPlaybackRate();
mStartTime = StartTimeFromTimelineTime(aReadyTime, currentTimeToMatch,
mPlaybackRate);
if (mPlaybackRate == 0) {
mHoldTime.SetValue(currentTimeToMatch);
}
}
mPendingState = PendingState::NotPending;
Bug 1253476 - Make Animation::Tick do finish actions synchronously; r=hiro Animation::UpdateTiming takes a SyncNotifyFlag parameter. This is passed to UpdateFinishedState where it determines how we handle finish actions. If it is async we queue a microtask where we re-evaluate if the animation is finished or not before queuing events / resolving promises. That allows code like the following to _not_ trigger finish events: ``` const animation = elem.animate({...}, 1000); animation.currentTime += 1000; animation.effect.updateTiming({ duration: 2000 }); ``` (Since the check that the animation is finished will run in a microtask _after_ the call to updateTiming.) When the flag is "sync" we still don't _actually_ run the finish actions entirely synchronously: the finished promise is resolved synchronously, but resolving a promise actually queues a microtask for each callback. Likewise, the finish event is queued synchronously, but not dispatched. Since there should be no opportunity for script to run between when we call Animation::Tick and when we run the next microtask checkpoint (currently at the end of DocumentTimeline::WillRefresh but that will change slightly in the next patch in this series) there is no need to introduce the extra "async" microtask for re-evaluating an animation's finished state. Instead it should be possible to use the "sync" finishing behavior. Such a change should be unobservable to Web content but will reduce indirection somewhat. Differential Revision: https://phabricator.services.mozilla.com/D30318 --HG-- extra : moz-landing-system : lando
2019-05-20 08:20:17 +03:00
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Sync);
// If we had a pending playback rate, we will have now applied it so we need
// to notify observers.
if (hadPendingPlaybackRate && IsRelevant()) {
nsNodeUtils::AnimationChanged(this);
}
if (mReady) {
mReady->MaybeResolve(this);
}
}
void Animation::PauseAt(const TimeDuration& aReadyTime) {
MOZ_ASSERT(mPendingState == PendingState::PausePending,
"Expected to pause a pause-pending animation");
if (!mStartTime.IsNull() && mHoldTime.IsNull()) {
mHoldTime = CurrentTimeFromTimelineTime(aReadyTime, mStartTime.Value(),
mPlaybackRate);
}
ApplyPendingPlaybackRate();
mStartTime.SetNull();
mPendingState = PendingState::NotPending;
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
if (mReady) {
mReady->MaybeResolve(this);
}
}
void Animation::UpdateTiming(SeekFlag aSeekFlag,
SyncNotifyFlag aSyncNotifyFlag) {
// We call UpdateFinishedState before UpdateEffect because the former
// can change the current time, which is used by the latter.
UpdateFinishedState(aSeekFlag, aSyncNotifyFlag);
UpdateEffect(PostRestyleMode::IfNeeded);
if (mTimeline) {
Bug 1195180 part 6 - Lazily remove animations from timelines; r=heycam Now that DocumentTimeline observes the refresh driver we can use regular ticks to remove unnecessary animations. We do this because in a subsequent patch, in order to provide deterministic enumeration order when ticking animations, we will store animations in an array. Removing an arbitrary element from an nsTArray is O(n) since we have to search for the array index first, or O(log n) if we keep the array sorted. If we destroy a subtree containing n animations, the operation effectively becomes O(n^2), or, if we keep the array sorted, O(n log n). By destroying during a tick when we are already iterating over the array, however, we will be able to do this much more efficiently. Whether an animation is newly associated with a timeline, or is disassociated from a timeline, or if it merely has its timing updated, the behavior implemented in this patch is to simply make sure we are observing the refresh driver and deal with the animation on the next tick. It might seem that we could be a lot more clever about this and, for example, if an animation reports NeedsTicks() == false, not start observing the refresh driver. There are various edge cases however that need to be taken into account. For example, if a CSS animation is finished (IsRelevant() == false so that animation will have been removed from the timeline), and paused (NeedsTicks() == false), and we seek it back to the point where it is relevant again, we actually need to observe the refresh driver so that it can dispatch an animationstart event on the next tick. A test case in a subsequent patch tests this specific situation. We could possibly add logic to detect if we need to fire events on the next tick but the complexity does not seem warranted given that even if we unnecessarily start observing the refresh driver, we will stop watching it on the next tick. This patch removes some rather lengthy comments from AnimationTiming::UpdateTiming. This is, in part, because of the behavior described above that makes these comments no longer relevant. Other parts are removed because the Web Animations specification has been updated such that a timeline becoming inactive now pauses the animation[1] so that the issue regarding detecting timelines becoming active/inactive no longer applies since animations attached to an inactive timeline remain "relevant". [1] https://w3c.github.io/web-animations/#responding-to-a-newly-inactive-timeline
2015-09-28 06:38:41 +03:00
mTimeline->NotifyAnimationUpdated(*this);
}
}
// https://drafts.csswg.org/web-animations/#update-an-animations-finished-state
void Animation::UpdateFinishedState(SeekFlag aSeekFlag,
SyncNotifyFlag aSyncNotifyFlag) {
Nullable<TimeDuration> currentTime = GetCurrentTimeAsDuration();
TimeDuration effectEnd = TimeDuration(EffectEnd());
if (!mStartTime.IsNull() && mPendingState == PendingState::NotPending) {
if (mPlaybackRate > 0.0 && !currentTime.IsNull() &&
currentTime.Value() >= effectEnd) {
if (aSeekFlag == SeekFlag::DidSeek) {
mHoldTime = currentTime;
} else if (!mPreviousCurrentTime.IsNull()) {
mHoldTime.SetValue(std::max(mPreviousCurrentTime.Value(), effectEnd));
} else {
mHoldTime.SetValue(effectEnd);
}
} else if (mPlaybackRate < 0.0 && !currentTime.IsNull() &&
currentTime.Value() <= TimeDuration()) {
if (aSeekFlag == SeekFlag::DidSeek) {
mHoldTime = currentTime;
} else if (!mPreviousCurrentTime.IsNull()) {
mHoldTime.SetValue(
std::min(mPreviousCurrentTime.Value(), TimeDuration(0)));
} else {
mHoldTime.SetValue(0);
}
} else if (mPlaybackRate != 0.0 && !currentTime.IsNull() && mTimeline &&
!mTimeline->GetCurrentTimeAsDuration().IsNull()) {
if (aSeekFlag == SeekFlag::DidSeek && !mHoldTime.IsNull()) {
mStartTime = StartTimeFromTimelineTime(
mTimeline->GetCurrentTimeAsDuration().Value(), mHoldTime.Value(),
mPlaybackRate);
}
mHoldTime.SetNull();
}
}
bool currentFinishedState = PlayState() == AnimationPlayState::Finished;
if (currentFinishedState && !mFinishedIsResolved) {
DoFinishNotification(aSyncNotifyFlag);
} else if (!currentFinishedState && mFinishedIsResolved) {
ResetFinishedPromise();
}
// We must recalculate the current time to take account of any mHoldTime
// changes the code above made.
mPreviousCurrentTime = GetCurrentTimeAsDuration();
}
void Animation::UpdateEffect(PostRestyleMode aPostRestyle) {
if (mEffect) {
UpdateRelevance();
KeyframeEffect* keyframeEffect = mEffect->AsKeyframeEffect();
if (keyframeEffect) {
keyframeEffect->NotifyAnimationTimingUpdated(aPostRestyle);
}
}
}
void Animation::FlushUnanimatedStyle() const {
if (Document* doc = GetRenderedDocument()) {
doc->FlushPendingNotifications(
ChangesToFlush(FlushType::Style, false /* flush animations */));
}
}
void Animation::PostUpdate() {
if (!mEffect) {
return;
}
KeyframeEffect* keyframeEffect = mEffect->AsKeyframeEffect();
if (!keyframeEffect) {
return;
}
keyframeEffect->RequestRestyle(EffectCompositor::RestyleType::Layer);
}
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) {
return;
}
CancelPendingTasks();
ApplyPendingPlaybackRate();
if (mReady) {
mReady->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
mReady = nullptr;
}
}
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);
}
}
}
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 to
// register with (perhaps the effect had no document) so we simply
// set mPendingState in PlayNoUpdate and relied on this method to catch us
// on the next tick.
// 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);
}
return mEffect->SpecifiedTiming().EndTime();
}
Document* Animation::GetRenderedDocument() const {
if (!mEffect || !mEffect->AsKeyframeEffect()) {
return nullptr;
}
return mEffect->AsKeyframeEffect()->GetRenderedDocument();
}
Document* Animation::GetTimelineDocument() const {
return mTimeline ? mTimeline->GetDocument() : nullptr;
}
class AsyncFinishNotification : public MicroTaskRunnable {
public:
explicit AsyncFinishNotification(Animation* aAnimation)
: MicroTaskRunnable(), mAnimation(aAnimation) {}
virtual void Run(AutoSlowOperation& aAso) override {
mAnimation->DoFinishNotificationImmediately(this);
mAnimation = nullptr;
}
virtual bool Suppressed() override {
nsIGlobalObject* global = mAnimation->GetOwnerGlobal();
return global && global->IsInSyncOperation();
}
private:
RefPtr<Animation> mAnimation;
};
void Animation::DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag) {
CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
if (aSyncNotifyFlag == SyncNotifyFlag::Sync) {
DoFinishNotificationImmediately();
} else if (!mFinishNotificationTask) {
RefPtr<MicroTaskRunnable> runnable = new AsyncFinishNotification(this);
context->DispatchToMicroTask(do_AddRef(runnable));
mFinishNotificationTask = runnable.forget();
}
}
void Animation::ResetFinishedPromise() {
mFinishedIsResolved = false;
mFinished = nullptr;
}
void Animation::MaybeResolveFinishedPromise() {
if (mFinished) {
mFinished->MaybeResolve(this);
}
mFinishedIsResolved = true;
}
void Animation::DoFinishNotificationImmediately(MicroTaskRunnable* aAsync) {
if (aAsync && aAsync != mFinishNotificationTask) {
return;
}
mFinishNotificationTask = nullptr;
if (PlayState() != AnimationPlayState::Finished) {
return;
}
MaybeResolveFinishedPromise();
QueuePlaybackEvent(NS_LITERAL_STRING("finish"),
AnimationTimeToTimeStamp(EffectEnd()));
}
void Animation::QueuePlaybackEvent(const nsAString& aName,
TimeStamp&& aScheduledEventTime) {
// Use document for timing.
// https://drafts.csswg.org/web-animations-1/#document-for-timing
Document* doc = GetTimelineDocument();
if (!doc) {
return;
}
nsPresContext* presContext = doc->GetPresContext();
if (!presContext) {
return;
}
AnimationPlaybackEventInit init;
if (aName.EqualsLiteral("finish") || aName.EqualsLiteral("remove")) {
init.mCurrentTime = GetCurrentTimeAsDouble();
}
if (mTimeline) {
init.mTimelineTime = mTimeline->GetCurrentTimeAsDouble();
}
Bug 1207245 - part 6 - rename nsRefPtr<T> to RefPtr<T>; r=ehsan; a=Tomcat The bulk of this commit was generated with a script, executed at the top level of a typical source code checkout. The only non-machine-generated part was modifying MFBT's moz.build to reflect the new naming. CLOSED TREE makes big refactorings like this a piece of cake. # The main substitution. find . -name '*.cpp' -o -name '*.cc' -o -name '*.h' -o -name '*.mm' -o -name '*.idl'| \ xargs perl -p -i -e ' s/nsRefPtr\.h/RefPtr\.h/g; # handle includes s/nsRefPtr ?</RefPtr</g; # handle declarations and variables ' # Handle a special friend declaration in gfx/layers/AtomicRefCountedWithFinalize.h. perl -p -i -e 's/::nsRefPtr;/::RefPtr;/' gfx/layers/AtomicRefCountedWithFinalize.h # Handle nsRefPtr.h itself, a couple places that define constructors # from nsRefPtr, and code generators specially. We do this here, rather # than indiscriminantly s/nsRefPtr/RefPtr/, because that would rename # things like nsRefPtrHashtable. perl -p -i -e 's/nsRefPtr/RefPtr/g' \ mfbt/nsRefPtr.h \ xpcom/glue/nsCOMPtr.h \ xpcom/base/OwningNonNull.h \ ipc/ipdl/ipdl/lower.py \ ipc/ipdl/ipdl/builtin.py \ dom/bindings/Codegen.py \ python/lldbutils/lldbutils/utils.py # In our indiscriminate substitution above, we renamed # nsRefPtrGetterAddRefs, the class behind getter_AddRefs. Fix that up. find . -name '*.cpp' -o -name '*.h' -o -name '*.idl' | \ xargs perl -p -i -e 's/nsRefPtrGetterAddRefs/RefPtrGetterAddRefs/g' if [ -d .git ]; then git mv mfbt/nsRefPtr.h mfbt/RefPtr.h else hg mv mfbt/nsRefPtr.h mfbt/RefPtr.h fi --HG-- rename : mfbt/nsRefPtr.h => mfbt/RefPtr.h
2015-10-18 08:24:48 +03:00
RefPtr<AnimationPlaybackEvent> event =
AnimationPlaybackEvent::Constructor(this, aName, init);
event->SetTrusted(true);
presContext->AnimationEventDispatcher()->QueueEvent(AnimationEventInfo(
aName, std::move(event), std::move(aScheduledEventTime), this));
}
bool Animation::IsRunningOnCompositor() const {
return mEffect && mEffect->AsKeyframeEffect() &&
mEffect->AsKeyframeEffect()->IsRunningOnCompositor();
}
} // namespace dom
} // namespace mozilla