moebius#89: DOM - implement animationcancel event

Issue #55
This commit is contained in:
janekptacijarabaci 2018-03-14 11:45:38 +01:00 • committed by Roy Tam
commit 901d2a277f
11 changed files with 99 additions and 73 deletions

View file

@ -787,12 +787,21 @@ Animation::CancelNoUpdate()
mHoldTime.SetNull(); mHoldTime.SetNull();
mStartTime.SetNull(); mStartTime.SetNull();
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
if (mTimeline) { if (mTimeline) {
mTimeline->RemoveAnimation(this); mTimeline->RemoveAnimation(this);
} }
MaybeQueueCancelEvent(activeTime); MaybeQueueCancelEvent(activeTime);
// When an animation is cancelled it no longer needs further ticks from the
// timeline. However, if we queued a cancel event and this was the last
// animation attached to the timeline, the timeline will stop observing the
// refresh driver and there may be no subsequent refresh driver tick for
// dispatching the queued event.
//
// By calling UpdateTiming *after* removing ourselves from our timeline, we
// ensure the timeline will register with the refresh driver for at least one
// more tick.
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
} }
void void

View file

@ -64,12 +64,12 @@ struct ComputedTiming
} }
enum class AnimationPhase { enum class AnimationPhase {
Null, // Not sampled (null sample time) Idle, // Not sampled (null sample time)
Before, // Sampled prior to the start of the active interval Before, // Sampled prior to the start of the active interval
Active, // Sampled within the active interval Active, // Sampled within the active interval
After // Sampled after (or at) the end of the active interval After // Sampled after (or at) the end of the active interval
}; };
AnimationPhase mPhase = AnimationPhase::Null; AnimationPhase mPhase = AnimationPhase::Idle;
ComputedTimingFunction::BeforeFlag mBeforeFlag = ComputedTimingFunction::BeforeFlag mBeforeFlag =
ComputedTimingFunction::BeforeFlag::Unset; ComputedTimingFunction::BeforeFlag::Unset;

View file

@ -694,6 +694,7 @@ GK_ATOM(onadapterremoved, "onadapterremoved")
GK_ATOM(onafterprint, "onafterprint") GK_ATOM(onafterprint, "onafterprint")
GK_ATOM(onafterscriptexecute, "onafterscriptexecute") GK_ATOM(onafterscriptexecute, "onafterscriptexecute")
GK_ATOM(onalerting, "onalerting") GK_ATOM(onalerting, "onalerting")
GK_ATOM(onanimationcancel, "onanimationcancel")
GK_ATOM(onanimationend, "onanimationend") GK_ATOM(onanimationend, "onanimationend")
GK_ATOM(onanimationiteration, "onanimationiteration") GK_ATOM(onanimationiteration, "onanimationiteration")
GK_ATOM(onanimationstart, "onanimationstart") GK_ATOM(onanimationstart, "onanimationstart")

View file

@ -1023,6 +1023,10 @@ EVENT(animationiteration,
eAnimationIteration, eAnimationIteration,
EventNameType_All, EventNameType_All,
eAnimationEventClass) eAnimationEventClass)
EVENT(animationcancel,
eAnimationCancel,
EventNameType_All,
eAnimationEventClass)
// Webkit-prefixed versions of Transition & Animation events, for web compat: // Webkit-prefixed versions of Transition & Animation events, for web compat:
EVENT(webkitAnimationEnd, EVENT(webkitAnimationEnd,

View file

@ -129,6 +129,7 @@ interface GlobalEventHandlers {
attribute EventHandler onmozpointerlockerror; attribute EventHandler onmozpointerlockerror;
// CSS-Animation and CSS-Transition handlers. // CSS-Animation and CSS-Transition handlers.
attribute EventHandler onanimationcancel;
attribute EventHandler onanimationend; attribute EventHandler onanimationend;
attribute EventHandler onanimationiteration; attribute EventHandler onanimationiteration;
attribute EventHandler onanimationstart; attribute EventHandler onanimationstart;

View file

@ -251,6 +251,26 @@ ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
aField.Traverse(&aCallback, aName); aField.Traverse(&aCallback, aName);
} }
// Return the TransitionPhase or AnimationPhase to use when the animation
// doesn't have a target effect.
template <typename PhaseType>
PhaseType GetAnimationPhaseWithoutEffect(const dom::Animation& aAnimation)
{
MOZ_ASSERT(!aAnimation.GetEffect(),
"Should only be called when we do not have an effect");
Nullable<TimeDuration> currentTime = aAnimation.GetCurrentTime();
if (currentTime.IsNull()) {
return PhaseType::Idle;
}
// If we don't have a target effect, the duration will be zero so the phase is
// 'before' if the current time is less than zero.
return currentTime.Value() < TimeDuration()
? PhaseType::Before
: PhaseType::After;
};
} // namespace mozilla } // namespace mozilla
#endif /* !defined(mozilla_css_AnimationCommon_h) */ #endif /* !defined(mozilla_css_AnimationCommon_h) */

View file

@ -158,12 +158,8 @@ CSSAnimation::HasLowerCompositeOrderThan(const CSSAnimation& aOther) const
} }
void void
CSSAnimation::QueueEvents() CSSAnimation::QueueEvents(StickyTimeDuration aActiveTime)
{ {
if (!mEffect) {
return;
}
// If the animation is pending, we ignore animation events until we finish // If the animation is pending, we ignore animation events until we finish
// pending. // pending.
if (mPendingState != PendingState::NotPending) { if (mPendingState != PendingState::NotPending) {
@ -198,39 +194,60 @@ CSSAnimation::QueueEvents()
} }
nsAnimationManager* manager = presContext->AnimationManager(); nsAnimationManager* manager = presContext->AnimationManager();
ComputedTiming computedTiming = mEffect->GetComputedTiming();
ComputedTiming::AnimationPhase currentPhase = computedTiming.mPhase;
uint64_t currentIteration = computedTiming.mCurrentIteration;
if (currentPhase == mPreviousPhase &&
currentIteration == mPreviousIteration) {
return;
}
const StickyTimeDuration zeroDuration; const StickyTimeDuration zeroDuration;
StickyTimeDuration intervalStartTime = uint64_t currentIteration = 0;
std::max(std::min(StickyTimeDuration(-mEffect->SpecifiedTiming().mDelay), ComputedTiming::AnimationPhase currentPhase;
computedTiming.mActiveDuration), StickyTimeDuration intervalStartTime;
zeroDuration); StickyTimeDuration intervalEndTime;
StickyTimeDuration intervalEndTime = StickyTimeDuration iterationStartTime;
std::max(std::min((EffectEnd() - mEffect->SpecifiedTiming().mDelay),
computedTiming.mActiveDuration),
zeroDuration);
uint64_t iterationBoundary = mPreviousIteration > currentIteration if (!mEffect) {
? currentIteration + 1 currentPhase = GetAnimationPhaseWithoutEffect
: currentIteration; <ComputedTiming::AnimationPhase>(*this);
StickyTimeDuration iterationStartTime = } else {
computedTiming.mDuration.MultDouble( ComputedTiming computedTiming = mEffect->GetComputedTiming();
(iterationBoundary - computedTiming.mIterationStart)); currentPhase = computedTiming.mPhase;
currentIteration = computedTiming.mCurrentIteration;
if (currentPhase == mPreviousPhase &&
currentIteration == mPreviousIteration) {
return;
}
intervalStartTime =
std::max(std::min(StickyTimeDuration(-mEffect->SpecifiedTiming().mDelay),
computedTiming.mActiveDuration),
zeroDuration);
intervalEndTime =
std::max(std::min((EffectEnd() - mEffect->SpecifiedTiming().mDelay),
computedTiming.mActiveDuration),
zeroDuration);
uint64_t iterationBoundary = mPreviousIteration > currentIteration
? currentIteration + 1
: currentIteration;
iterationStartTime =
computedTiming.mDuration.MultDouble(
(iterationBoundary - computedTiming.mIterationStart));
}
TimeStamp startTimeStamp = ElapsedTimeToTimeStamp(intervalStartTime); TimeStamp startTimeStamp = ElapsedTimeToTimeStamp(intervalStartTime);
TimeStamp endTimeStamp = ElapsedTimeToTimeStamp(intervalEndTime); TimeStamp endTimeStamp = ElapsedTimeToTimeStamp(intervalEndTime);
TimeStamp iterationTimeStamp = ElapsedTimeToTimeStamp(iterationStartTime); TimeStamp iterationTimeStamp = ElapsedTimeToTimeStamp(iterationStartTime);
AutoTArray<AnimationEventParams, 2> events; AutoTArray<AnimationEventParams, 2> events;
// Handle cancel event first
if ((mPreviousPhase != AnimationPhase::Idle &&
mPreviousPhase != AnimationPhase::After) &&
currentPhase == AnimationPhase::Idle) {
TimeStamp activeTimeStamp = ElapsedTimeToTimeStamp(aActiveTime);
events.AppendElement(AnimationEventParams{ eAnimationCancel,
aActiveTime,
activeTimeStamp });
}
switch (mPreviousPhase) { switch (mPreviousPhase) {
case AnimationPhase::Null: case AnimationPhase::Idle:
case AnimationPhase::Before: case AnimationPhase::Before:
if (currentPhase == AnimationPhase::Active) { if (currentPhase == AnimationPhase::Active) {
events.AppendElement(AnimationEventParams{ eAnimationStart, events.AppendElement(AnimationEventParams{ eAnimationStart,

View file

@ -76,7 +76,7 @@ public:
, mIsStylePaused(false) , mIsStylePaused(false)
, mPauseShouldStick(false) , mPauseShouldStick(false)
, mNeedsNewAnimationIndexWhenRun(false) , mNeedsNewAnimationIndexWhenRun(false)
, mPreviousPhase(ComputedTiming::AnimationPhase::Null) , mPreviousPhase(ComputedTiming::AnimationPhase::Idle)
, mPreviousIteration(0) , mPreviousIteration(0)
{ {
// We might need to drop this assertion once we add a script-accessible // We might need to drop this assertion once we add a script-accessible
@ -110,8 +110,6 @@ public:
void PauseFromStyle(); void PauseFromStyle();
void CancelFromStyle() override void CancelFromStyle() override
{ {
mOwningElement = OwningElementRef();
// When an animation is disassociated with style it enters an odd state // When an animation is disassociated with style it enters an odd state
// where its composite order is undefined until it first transitions // where its composite order is undefined until it first transitions
// out of the idle state. // out of the idle state.
@ -126,10 +124,15 @@ public:
mNeedsNewAnimationIndexWhenRun = true; mNeedsNewAnimationIndexWhenRun = true;
Animation::CancelFromStyle(); Animation::CancelFromStyle();
// We need to do this *after* calling CancelFromStyle() since
// CancelFromStyle might synchronously trigger a cancel event for which
// we need an owning element to target the event at.
mOwningElement = OwningElementRef();
} }
void Tick() override; void Tick() override;
void QueueEvents(); void QueueEvents(StickyTimeDuration aActiveTime = StickyTimeDuration());
bool IsStylePaused() const { return mIsStylePaused; } bool IsStylePaused() const { return mIsStylePaused; }
@ -158,6 +161,10 @@ public:
// reflect changes to that markup. // reflect changes to that markup.
bool IsTiedToMarkup() const { return mOwningElement.IsSet(); } bool IsTiedToMarkup() const { return mOwningElement.IsSet(); }
void MaybeQueueCancelEvent(StickyTimeDuration aActiveTime) override {
QueueEvents(aActiveTime);
}
protected: protected:
virtual ~CSSAnimation() virtual ~CSSAnimation()
{ {

View file

@ -46,8 +46,6 @@ using mozilla::dom::KeyframeEffectReadOnly;
using namespace mozilla; using namespace mozilla;
using namespace mozilla::css; using namespace mozilla::css;
typedef mozilla::ComputedTiming::AnimationPhase AnimationPhase;
namespace { namespace {
struct TransitionEventParams { struct TransitionEventParams {
EventMessage mMessage; EventMessage mMessage;
@ -203,7 +201,7 @@ CSSTransition::QueueEvents(StickyTimeDuration aActiveTime)
StickyTimeDuration intervalEndTime; StickyTimeDuration intervalEndTime;
if (!mEffect) { if (!mEffect) {
currentPhase = GetTransitionPhaseWithoutEffect(); currentPhase = GetAnimationPhaseWithoutEffect<TransitionPhase>(*this);
intervalStartTime = zeroDuration; intervalStartTime = zeroDuration;
intervalEndTime = zeroDuration; intervalEndTime = zeroDuration;
} else { } else {
@ -329,23 +327,6 @@ CSSTransition::QueueEvents(StickyTimeDuration aActiveTime)
} }
} }
CSSTransition::TransitionPhase
CSSTransition::GetTransitionPhaseWithoutEffect() const
{
MOZ_ASSERT(!mEffect, "Should only be called when we do not have an effect");
Nullable<TimeDuration> currentTime = GetCurrentTime();
if (currentTime.IsNull()) {
return TransitionPhase::Idle;
}
// If we don't have a target effect, the duration will be zero so the phase is
// 'before' if the current time is less than zero.
return currentTime.Value() < TimeDuration()
? TransitionPhase::Before
: TransitionPhase::After;
}
void void
CSSTransition::Tick() CSSTransition::Tick()
{ {

View file

@ -162,18 +162,6 @@ public:
Animation::CancelFromStyle(); Animation::CancelFromStyle();
// The above call to Animation::CancelFromStyle may cause a transitioncancel
// event to be queued. However, it will also remove the transition from its
// timeline. If this transition was the last animation attached to
// the timeline, the timeline will stop observing the refresh driver and
// there may be no subsequent tick fro dispatching animation events.
//
// To ensure the cancel event is dispatched we tell the timeline it needs to
// observe the refresh driver for at least one more tick.
if (mTimeline) {
mTimeline->NotifyAnimationUpdated(*this);
}
// It is important we do this *after* calling CancelFromStyle(). // It is important we do this *after* calling CancelFromStyle().
// This is because CancelFromStyle() will end up posting a restyle and // This is because CancelFromStyle() will end up posting a restyle and
// that restyle should target the *transitions* level of the cascade. // that restyle should target the *transitions* level of the cascade.
@ -245,9 +233,6 @@ protected:
enum class TransitionPhase; enum class TransitionPhase;
// Return the TransitionPhase to use when the transition doesn't have a target
// effect.
TransitionPhase GetTransitionPhaseWithoutEffect() const;
// The (pseudo-)element whose computed transition-property refers to this // The (pseudo-)element whose computed transition-property refers to this
// transition (if any). // transition (if any).
@ -272,7 +257,7 @@ protected:
// to be queued on this tick. // to be queued on this tick.
// See: https://drafts.csswg.org/css-transitions-2/#transition-phase // See: https://drafts.csswg.org/css-transitions-2/#transition-phase
enum class TransitionPhase { enum class TransitionPhase {
Idle = static_cast<int>(ComputedTiming::AnimationPhase::Null), Idle = static_cast<int>(ComputedTiming::AnimationPhase::Idle),
Before = static_cast<int>(ComputedTiming::AnimationPhase::Before), Before = static_cast<int>(ComputedTiming::AnimationPhase::Before),
Active = static_cast<int>(ComputedTiming::AnimationPhase::Active), Active = static_cast<int>(ComputedTiming::AnimationPhase::Active),
After = static_cast<int>(ComputedTiming::AnimationPhase::After), After = static_cast<int>(ComputedTiming::AnimationPhase::After),

View file

@ -345,6 +345,7 @@ NS_EVENT_MESSAGE(eTransitionCancel)
NS_EVENT_MESSAGE(eAnimationStart) NS_EVENT_MESSAGE(eAnimationStart)
NS_EVENT_MESSAGE(eAnimationEnd) NS_EVENT_MESSAGE(eAnimationEnd)
NS_EVENT_MESSAGE(eAnimationIteration) NS_EVENT_MESSAGE(eAnimationIteration)
NS_EVENT_MESSAGE(eAnimationCancel)
// Webkit-prefixed versions of Transition & Animation events, for web compat: // Webkit-prefixed versions of Transition & Animation events, for web compat:
NS_EVENT_MESSAGE(eWebkitTransitionEnd) NS_EVENT_MESSAGE(eWebkitTransitionEnd)