Bug 1287983 part 2 - Add transitionstart/transitionrun event handling. r?birtles
The specification of CSS-Transition event is as follow:
https://drafts.csswg.org/css-transitions-2/#transition-events
MozReview-Commit-ID: CWgsRRLhnXp
--- a/layout/style/nsTransitionManager.cpp
+++ b/layout/style/nsTransitionManager.cpp
@@ -41,16 +41,26 @@ using mozilla::TimeDuration;
using mozilla::dom::Animation;
using mozilla::dom::AnimationPlayState;
using mozilla::dom::CSSTransition;
using mozilla::dom::KeyframeEffectReadOnly;
using namespace mozilla;
using namespace mozilla::css;
+typedef mozilla::ComputedTiming::AnimationPhase AnimationPhase;
+
+namespace {
+struct TransitionEventParams {
+ EventMessage mMessage;
+ StickyTimeDuration mElapsedTime;
+ TimeStamp mTimeStamp;
+};
+} // anonymous namespace
+
double
ElementPropertyTransition::CurrentValuePortion() const
{
MOZ_ASSERT(!GetLocalTime().IsNull(),
"Getting the value portion of an animation that's not being "
"sampled");
// Transitions use a fill mode of 'backwards' so GetComputedTiming will
@@ -167,42 +177,149 @@ CSSTransition::UpdateTiming(SeekFlag aSe
}
Animation::UpdateTiming(aSeekFlag, aSyncNotifyFlag);
}
void
CSSTransition::QueueEvents()
{
- AnimationPlayState playState = PlayState();
- bool newlyFinished = !mWasFinishedOnLastTick &&
- playState == AnimationPlayState::Finished;
- mWasFinishedOnLastTick = playState == AnimationPlayState::Finished;
-
- if (!newlyFinished || !mEffect || !mOwningElement.IsSet()) {
+ if (!mEffect ||
+ !mOwningElement.IsSet()) {
return;
}
dom::Element* owningElement;
CSSPseudoElementType owningPseudoType;
mOwningElement.GetElement(owningElement, owningPseudoType);
MOZ_ASSERT(owningElement, "Owning element should be set");
nsPresContext* presContext = mOwningElement.GetRenderedPresContext();
if (!presContext) {
return;
}
+ ComputedTiming computedTiming = mEffect->GetComputedTiming();
+ const StickyTimeDuration zeroDuration;
+ StickyTimeDuration intervalStartTime =
+ std::max(std::min(StickyTimeDuration(-mEffect->SpecifiedTiming().mDelay),
+ computedTiming.mActiveDuration), zeroDuration);
+ StickyTimeDuration intervalEndTime =
+ std::max(std::min((EffectEnd() - mEffect->SpecifiedTiming().mDelay),
+ computedTiming.mActiveDuration), zeroDuration);
+
+ // TimeStamps to use for ordering the events when they are dispatched. We
+ // use a TimeStamp so we can compare events produced by different elements,
+ // perhaps even with different timelines.
+ // The zero timestamp is for transitionrun events where we ignore the delay
+ // for the purpose of ordering events.
+ TimeStamp zeroTimeStamp = AnimationTimeToTimeStamp(zeroDuration);
+ TimeStamp startTimeStamp = ElapsedTimeToTimeStamp(intervalStartTime);
+ TimeStamp endTimeStamp = ElapsedTimeToTimeStamp(intervalEndTime);
+
+ TransitionPhase currentPhase;
+ if (mPendingState != PendingState::NotPending &&
+ (mPreviousTransitionPhase == TransitionPhase::Idle ||
+ mPreviousTransitionPhase == TransitionPhase::Pending))
+ {
+ currentPhase = TransitionPhase::Pending;
+ } else {
+ currentPhase = static_cast<TransitionPhase>(computedTiming.mPhase);
+ }
+
+ AutoTArray<TransitionEventParams, 3> events;
+ switch (mPreviousTransitionPhase) {
+ case TransitionPhase::Idle:
+ if (currentPhase == TransitionPhase::Pending ||
+ currentPhase == TransitionPhase::Before) {
+ events.AppendElement(TransitionEventParams{ eTransitionRun,
+ intervalStartTime,
+ zeroTimeStamp });
+ } else if (currentPhase == TransitionPhase::Active) {
+ events.AppendElement(TransitionEventParams{ eTransitionRun,
+ intervalStartTime,
+ zeroTimeStamp });
+ events.AppendElement(TransitionEventParams{ eTransitionStart,
+ intervalStartTime,
+ startTimeStamp });
+ } else if (currentPhase == TransitionPhase::After) {
+ events.AppendElement(TransitionEventParams{ eTransitionRun,
+ intervalStartTime,
+ zeroTimeStamp });
+ events.AppendElement(TransitionEventParams{ eTransitionStart,
+ intervalStartTime,
+ startTimeStamp });
+ events.AppendElement(TransitionEventParams{ eTransitionEnd,
+ intervalEndTime,
+ endTimeStamp });
+ }
+ break;
+
+ case TransitionPhase::Pending:
+ case TransitionPhase::Before:
+ if (currentPhase == TransitionPhase::Active) {
+ events.AppendElement(TransitionEventParams{ eTransitionStart,
+ intervalStartTime,
+ startTimeStamp });
+ } else if (currentPhase == TransitionPhase::After) {
+ events.AppendElement(TransitionEventParams{ eTransitionStart,
+ intervalStartTime,
+ startTimeStamp });
+ events.AppendElement(TransitionEventParams{ eTransitionEnd,
+ intervalEndTime,
+ endTimeStamp });
+ }
+ break;
+
+ case TransitionPhase::Active:
+ if (currentPhase == TransitionPhase::After) {
+ events.AppendElement(TransitionEventParams{ eTransitionEnd,
+ intervalEndTime,
+ endTimeStamp });
+ } else if (currentPhase == TransitionPhase::Before) {
+ events.AppendElement(TransitionEventParams{ eTransitionEnd,
+ intervalStartTime,
+ startTimeStamp });
+ }
+ break;
+
+ case TransitionPhase::After:
+ if (currentPhase == TransitionPhase::Active) {
+ events.AppendElement(TransitionEventParams{ eTransitionStart,
+ intervalEndTime,
+ startTimeStamp });
+ } else if (currentPhase == TransitionPhase::Before) {
+ events.AppendElement(TransitionEventParams{ eTransitionStart,
+ intervalEndTime,
+ startTimeStamp });
+ events.AppendElement(TransitionEventParams{ eTransitionEnd,
+ intervalStartTime,
+ endTimeStamp });
+ }
+ break;
+ }
+ mPreviousTransitionPhase = currentPhase;
+
nsTransitionManager* manager = presContext->TransitionManager();
- manager->QueueEvent(TransitionEventInfo(owningElement, owningPseudoType,
- TransitionProperty(),
- mEffect->GetComputedTiming()
- .mDuration,
- AnimationTimeToTimeStamp(EffectEnd()),
- this));
+ for (const TransitionEventParams& evt : events) {
+ manager->QueueEvent(TransitionEventInfo(owningElement, owningPseudoType,
+ evt.mMessage,
+ TransitionProperty(),
+ evt.mElapsedTime,
+ evt.mTimeStamp,
+ this));
+ }
+}
+
+TimeStamp
+CSSTransition::ElapsedTimeToTimeStamp(
+ const StickyTimeDuration& aElapsedTime) const
+{
+ return AnimationTimeToTimeStamp(aElapsedTime +
+ mEffect->SpecifiedTiming().mDelay);
}
void
CSSTransition::Tick()
{
Animation::Tick();
QueueEvents();
}
--- a/layout/style/nsTransitionManager.h
+++ b/layout/style/nsTransitionManager.h
@@ -3,16 +3,17 @@
* 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/. */
/* Code to start and animate CSS transitions. */
#ifndef nsTransitionManager_h_
#define nsTransitionManager_h_
+#include "mozilla/ComputedTiming.h"
#include "mozilla/ContentEvents.h"
#include "mozilla/EffectCompositor.h" // For EffectCompositor::CascadeLevel
#include "mozilla/MemoryReporting.h"
#include "mozilla/dom/Animation.h"
#include "mozilla/dom/KeyframeEffectReadOnly.h"
#include "AnimationCommon.h"
#include "nsCSSProps.h"
@@ -114,17 +115,17 @@ struct ElementPropertyTransition : publi
namespace dom {
class CSSTransition final : public Animation
{
public:
explicit CSSTransition(nsIGlobalObject* aGlobal)
: dom::Animation(aGlobal)
- , mWasFinishedOnLastTick(false)
+ , mPreviousTransitionPhase(TransitionPhase::Idle)
, mNeedsNewAnimationIndexWhenRun(false)
, mTransitionProperty(eCSSProperty_UNKNOWN)
{
}
JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
@@ -221,16 +222,20 @@ protected:
}
// Animation overrides
void UpdateTiming(SeekFlag aSeekFlag,
SyncNotifyFlag aSyncNotifyFlag) override;
void QueueEvents();
+ // Converts an TransitionEvent's elapsedTime value to an equivalent TimeStamp
+ // that can be used to sort events by when they occurred.
+ TimeStamp ElapsedTimeToTimeStamp(const StickyTimeDuration& aElapsedTime) const;
+
// The (pseudo-)element whose computed transition-property refers to this
// transition (if any).
//
// This is used for determining the relative composite order of transitions
// generated from CSS markup.
//
// Typically this will be the same as the target element of the keyframe
// effect associated with this transition. However, it can differ in the
@@ -240,17 +245,27 @@ protected:
// b) If this transition is cancelled (e.g. by updating the
// transition-property or removing the owning element from the document),
// c) If this object is generated from script using the CSSTransition
// constructor.
//
// For (b) and (c) the owning element will return !IsSet().
OwningElementRef mOwningElement;
- bool mWasFinishedOnLastTick;
+ // The 'transition phase' used to determine which transition events need
+ // to be queued on this tick.
+ // See: https://drafts.csswg.org/css-transitions-2/#transition-phase
+ enum class TransitionPhase {
+ Idle = static_cast<int>(ComputedTiming::AnimationPhase::Null),
+ Before = static_cast<int>(ComputedTiming::AnimationPhase::Before),
+ Active = static_cast<int>(ComputedTiming::AnimationPhase::Active),
+ After = static_cast<int>(ComputedTiming::AnimationPhase::After),
+ Pending
+ };
+ TransitionPhase mPreviousTransitionPhase;
// When true, indicates that when this transition next leaves the idle state,
// its animation index should be updated.
bool mNeedsNewAnimationIndexWhenRun;
// Store the transition property and to-value here since we need that
// information in order to determine if there is an existing transition
// for a given style change. We can't store that information on the
@@ -282,39 +297,40 @@ struct AnimationTypeTraits<dom::CSSTrans
struct TransitionEventInfo {
RefPtr<dom::Element> mElement;
RefPtr<dom::Animation> mAnimation;
InternalTransitionEvent mEvent;
TimeStamp mTimeStamp;
TransitionEventInfo(dom::Element* aElement,
CSSPseudoElementType aPseudoType,
+ EventMessage aMessage,
nsCSSPropertyID aProperty,
StickyTimeDuration aDuration,
const TimeStamp& aTimeStamp,
dom::Animation* aAnimation)
: mElement(aElement)
, mAnimation(aAnimation)
- , mEvent(true, eTransitionEnd)
+ , mEvent(true, aMessage)
, mTimeStamp(aTimeStamp)
{
// XXX Looks like nobody initialize WidgetEvent::time
mEvent.mPropertyName =
NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(aProperty));
mEvent.mElapsedTime = aDuration.ToSeconds();
mEvent.mPseudoElement =
AnimationCollection<dom::CSSTransition>::PseudoTypeAsString(aPseudoType);
}
// InternalTransitionEvent doesn't support copy-construction, so we need
// to ourselves in order to work with nsTArray
TransitionEventInfo(const TransitionEventInfo& aOther)
: mElement(aOther.mElement)
, mAnimation(aOther.mAnimation)
- , mEvent(true, eTransitionEnd)
+ , mEvent(aOther.mEvent)
, mTimeStamp(aOther.mTimeStamp)
{
mEvent.AssignTransitionEventData(aOther.mEvent, false);
}
};
} // namespace mozilla