Bug 1634943 - Split CSSTransition class into a new file in dom/animation/. r=boris

The motivation here is that we will want to call CSSTransition specific
functions, e.g. updating the start value of a given CSSTransition with
the latest value of the CSSTransition on the compositor, from somewhere
not in layout/style.  Unfotunately nsTransitionManager.h is not exposed
and we will never want to expose it since it's purely for layout/style
stuff.

Differential Revision: https://phabricator.services.mozilla.com/D73571
This commit is contained in:
Hiroyuki Ikezoe 2020-05-05 10:01:33 +00:00
Родитель 6d7f47890b
Коммит eee4796cb1
6 изменённых файлов: 589 добавлений и 576 удалений

Просмотреть файл

@ -0,0 +1,338 @@
/* -*- 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 "CSSTransition.h"
#include "mozilla/AnimationEventDispatcher.h"
#include "mozilla/dom/CSSTransitionBinding.h"
#include "mozilla/dom/KeyframeEffectBinding.h"
#include "mozilla/dom/KeyframeEffect.h"
#include "mozilla/TimeStamp.h"
#include "nsPresContext.h"
namespace mozilla {
namespace dom {
JSObject* CSSTransition::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return dom::CSSTransition_Binding::Wrap(aCx, this, aGivenProto);
}
void CSSTransition::GetTransitionProperty(nsString& aRetVal) const {
MOZ_ASSERT(eCSSProperty_UNKNOWN != mTransitionProperty,
"Transition Property should be initialized");
aRetVal =
NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(mTransitionProperty));
}
AnimationPlayState CSSTransition::PlayStateFromJS() const {
FlushUnanimatedStyle();
return Animation::PlayStateFromJS();
}
bool CSSTransition::PendingFromJS() const {
// Transitions don't become pending again after they start running but, if
// while the transition is still pending, style is updated in such a way
// that the transition will be canceled, we need to report false here.
// Hence we need to flush, but only when we're pending.
if (Pending()) {
FlushUnanimatedStyle();
}
return Animation::PendingFromJS();
}
void CSSTransition::PlayFromJS(ErrorResult& aRv) {
FlushUnanimatedStyle();
Animation::PlayFromJS(aRv);
}
void CSSTransition::UpdateTiming(SeekFlag aSeekFlag,
SyncNotifyFlag aSyncNotifyFlag) {
if (mNeedsNewAnimationIndexWhenRun &&
PlayState() != AnimationPlayState::Idle) {
mAnimationIndex = sNextAnimationIndex++;
mNeedsNewAnimationIndexWhenRun = false;
}
Animation::UpdateTiming(aSeekFlag, aSyncNotifyFlag);
}
void CSSTransition::QueueEvents(const StickyTimeDuration& aActiveTime) {
if (!mOwningElement.IsSet()) {
return;
}
nsPresContext* presContext = mOwningElement.GetPresContext();
if (!presContext) {
return;
}
static constexpr StickyTimeDuration zeroDuration = StickyTimeDuration();
TransitionPhase currentPhase;
StickyTimeDuration intervalStartTime;
StickyTimeDuration intervalEndTime;
if (!mEffect) {
currentPhase = GetAnimationPhaseWithoutEffect<TransitionPhase>(*this);
} else {
ComputedTiming computedTiming = mEffect->GetComputedTiming();
currentPhase = static_cast<TransitionPhase>(computedTiming.mPhase);
intervalStartTime = IntervalStartTime(computedTiming.mActiveDuration);
intervalEndTime = IntervalEndTime(computedTiming.mActiveDuration);
}
if (mPendingState != PendingState::NotPending &&
(mPreviousTransitionPhase == TransitionPhase::Idle ||
mPreviousTransitionPhase == TransitionPhase::Pending)) {
currentPhase = TransitionPhase::Pending;
}
if (currentPhase == mPreviousTransitionPhase) {
return;
}
// 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);
AutoTArray<AnimationEventInfo, 3> events;
auto appendTransitionEvent = [&](EventMessage aMessage,
const StickyTimeDuration& aElapsedTime,
const TimeStamp& aScheduledEventTimeStamp) {
double elapsedTime = aElapsedTime.ToSeconds();
if (aMessage == eTransitionCancel) {
// 0 is an inappropriate value for this callsite. What we need to do is
// use a single random value for all increasing times reportable.
// That is to say, whenever elapsedTime goes negative (because an
// animation restarts, something rewinds the animation, or otherwise)
// a new random value for the mix-in must be generated.
elapsedTime =
nsRFPService::ReduceTimePrecisionAsSecsRFPOnly(elapsedTime, 0);
}
events.AppendElement(AnimationEventInfo(
TransitionProperty(), mOwningElement.Target(), aMessage, elapsedTime,
aScheduledEventTimeStamp, this));
};
// Handle cancel events first
if ((mPreviousTransitionPhase != TransitionPhase::Idle &&
mPreviousTransitionPhase != TransitionPhase::After) &&
currentPhase == TransitionPhase::Idle) {
appendTransitionEvent(eTransitionCancel, aActiveTime,
GetTimelineCurrentTimeAsTimeStamp());
}
// All other events
switch (mPreviousTransitionPhase) {
case TransitionPhase::Idle:
if (currentPhase == TransitionPhase::Pending ||
currentPhase == TransitionPhase::Before) {
appendTransitionEvent(eTransitionRun, intervalStartTime, zeroTimeStamp);
} else if (currentPhase == TransitionPhase::Active) {
appendTransitionEvent(eTransitionRun, intervalStartTime, zeroTimeStamp);
appendTransitionEvent(eTransitionStart, intervalStartTime,
startTimeStamp);
} else if (currentPhase == TransitionPhase::After) {
appendTransitionEvent(eTransitionRun, intervalStartTime, zeroTimeStamp);
appendTransitionEvent(eTransitionStart, intervalStartTime,
startTimeStamp);
appendTransitionEvent(eTransitionEnd, intervalEndTime, endTimeStamp);
}
break;
case TransitionPhase::Pending:
case TransitionPhase::Before:
if (currentPhase == TransitionPhase::Active) {
appendTransitionEvent(eTransitionStart, intervalStartTime,
startTimeStamp);
} else if (currentPhase == TransitionPhase::After) {
appendTransitionEvent(eTransitionStart, intervalStartTime,
startTimeStamp);
appendTransitionEvent(eTransitionEnd, intervalEndTime, endTimeStamp);
}
break;
case TransitionPhase::Active:
if (currentPhase == TransitionPhase::After) {
appendTransitionEvent(eTransitionEnd, intervalEndTime, endTimeStamp);
} else if (currentPhase == TransitionPhase::Before) {
appendTransitionEvent(eTransitionEnd, intervalStartTime,
startTimeStamp);
}
break;
case TransitionPhase::After:
if (currentPhase == TransitionPhase::Active) {
appendTransitionEvent(eTransitionStart, intervalEndTime,
startTimeStamp);
} else if (currentPhase == TransitionPhase::Before) {
appendTransitionEvent(eTransitionStart, intervalEndTime,
startTimeStamp);
appendTransitionEvent(eTransitionEnd, intervalStartTime, endTimeStamp);
}
break;
}
mPreviousTransitionPhase = currentPhase;
if (!events.IsEmpty()) {
presContext->AnimationEventDispatcher()->QueueEvents(std::move(events));
}
}
void CSSTransition::Tick() {
Animation::Tick();
QueueEvents();
}
nsCSSPropertyID CSSTransition::TransitionProperty() const {
MOZ_ASSERT(eCSSProperty_UNKNOWN != mTransitionProperty,
"Transition property should be initialized");
return mTransitionProperty;
}
AnimationValue CSSTransition::ToValue() const {
MOZ_ASSERT(!mTransitionToValue.IsNull(),
"Transition ToValue should be initialized");
return mTransitionToValue;
}
bool CSSTransition::HasLowerCompositeOrderThan(
const CSSTransition& aOther) const {
MOZ_ASSERT(IsTiedToMarkup() && aOther.IsTiedToMarkup(),
"Should only be called for CSS transitions that are sorted "
"as CSS transitions (i.e. tied to CSS markup)");
// 0. Object-equality case
if (&aOther == this) {
return false;
}
// 1. Sort by document order
if (!mOwningElement.Equals(aOther.mOwningElement)) {
return mOwningElement.LessThan(
const_cast<CSSTransition*>(this)->CachedChildIndexRef(),
aOther.mOwningElement,
const_cast<CSSTransition*>(&aOther)->CachedChildIndexRef());
}
// 2. (Same element and pseudo): Sort by transition generation
if (mAnimationIndex != aOther.mAnimationIndex) {
return mAnimationIndex < aOther.mAnimationIndex;
}
// 3. (Same transition generation): Sort by transition property
return nsCSSProps::GetStringValue(TransitionProperty()) <
nsCSSProps::GetStringValue(aOther.TransitionProperty());
}
/* static */
Nullable<TimeDuration> CSSTransition::GetCurrentTimeAt(
const AnimationTimeline& aTimeline, const TimeStamp& aBaseTime,
const TimeDuration& aStartTime, double aPlaybackRate) {
Nullable<TimeDuration> result;
Nullable<TimeDuration> timelineTime = aTimeline.ToTimelineTime(aBaseTime);
if (!timelineTime.IsNull()) {
result.SetValue(
(timelineTime.Value() - aStartTime).MultDouble(aPlaybackRate));
}
return result;
}
double CSSTransition::CurrentValuePortion() const {
if (!GetEffect()) {
return 0.0;
}
// Transitions use a fill mode of 'backwards' so GetComputedTiming will
// never return a null time progress due to being *before* the animation
// interval. However, it might be possible that we're behind on flushing
// causing us to get called *after* the animation interval. So, just in
// case, we override the fill mode to 'both' to ensure the progress
// is never null.
TimingParams timingToUse = GetEffect()->SpecifiedTiming();
timingToUse.SetFill(dom::FillMode::Both);
ComputedTiming computedTiming = GetEffect()->GetComputedTiming(&timingToUse);
if (computedTiming.mProgress.IsNull()) {
return 0.0;
}
// 'transition-timing-function' corresponds to the effect timing while
// the transition keyframes have a linear timing function so we can ignore
// them for the purposes of calculating the value portion.
return computedTiming.mProgress.Value();
}
void CSSTransition::UpdateStartValueFromReplacedTransition() {
MOZ_ASSERT(mEffect && mEffect->AsKeyframeEffect() &&
mEffect->AsKeyframeEffect()->HasAnimationOfPropertySet(
nsCSSPropertyIDSet::CompositorAnimatables()),
"Should be called for compositor-runnable transitions");
MOZ_ASSERT(mTimeline,
"Should have a timeline if we are replacing transition start "
"values");
if (!mReplacedTransition) {
return;
}
ComputedTiming computedTiming = AnimationEffect::GetComputedTimingAt(
CSSTransition::GetCurrentTimeAt(*mTimeline, TimeStamp::Now(),
mReplacedTransition->mStartTime,
mReplacedTransition->mPlaybackRate),
mReplacedTransition->mTiming, mReplacedTransition->mPlaybackRate);
if (!computedTiming.mProgress.IsNull()) {
double valuePosition = ComputedTimingFunction::GetPortion(
mReplacedTransition->mTimingFunction, computedTiming.mProgress.Value(),
computedTiming.mBeforeFlag);
const AnimationValue& replacedFrom = mReplacedTransition->mFromValue;
const AnimationValue& replacedTo = mReplacedTransition->mToValue;
AnimationValue startValue;
startValue.mServo =
Servo_AnimationValues_Interpolate(replacedFrom.mServo,
replacedTo.mServo, valuePosition)
.Consume();
mEffect->AsKeyframeEffect()->ReplaceTransitionStartValue(
std::move(startValue));
}
mReplacedTransition.reset();
}
void CSSTransition::SetEffectFromStyle(dom::AnimationEffect* aEffect) {
Animation::SetEffectNoUpdate(aEffect);
// Initialize transition property and to value.
//
// Typically this should only be called with a KeyframeEffect representing
// a simple transition, but just to be sure we check the effect has the
// expected shape first.
const KeyframeEffect* keyframeEffect = aEffect->AsKeyframeEffect();
if (MOZ_LIKELY(keyframeEffect && keyframeEffect->Properties().Length() == 1 &&
keyframeEffect->Properties()[0].mSegments.Length() == 1)) {
mTransitionProperty = keyframeEffect->Properties()[0].mProperty;
mTransitionToValue = keyframeEffect->Properties()[0].mSegments[0].mToValue;
} else {
MOZ_ASSERT_UNREACHABLE("Transition effect has unexpected shape");
}
}
} // namespace dom
} // namespace mozilla

Просмотреть файл

@ -0,0 +1,245 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_CSSTransition_h
#define mozilla_dom_CSSTransition_h
#include "mozilla/dom/Animation.h"
#include "mozilla/StyleAnimationValue.h"
#include "AnimationCommon.h"
class nsIGlobalObject;
namespace mozilla {
namespace dom {
class CSSTransition final : public Animation {
public:
explicit CSSTransition(nsIGlobalObject* aGlobal)
: dom::Animation(aGlobal),
mPreviousTransitionPhase(TransitionPhase::Idle),
mNeedsNewAnimationIndexWhenRun(false),
mTransitionProperty(eCSSProperty_UNKNOWN) {}
JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
CSSTransition* AsCSSTransition() override { return this; }
const CSSTransition* AsCSSTransition() const override { return this; }
// CSSTransition interface
void GetTransitionProperty(nsString& aRetVal) const;
// Animation interface overrides
AnimationPlayState PlayStateFromJS() const override;
bool PendingFromJS() const override;
void PlayFromJS(ErrorResult& aRv) override;
// A variant of Play() that avoids posting style updates since this method
// is expected to be called whilst already updating style.
void PlayFromStyle() {
ErrorResult rv;
PlayNoUpdate(rv, Animation::LimitBehavior::Continue);
// play() should not throw when LimitBehavior is Continue
MOZ_ASSERT(!rv.Failed(), "Unexpected exception playing transition");
}
void CancelFromStyle(PostRestyleMode aPostRestyle) {
// The animation index to use for compositing will be established when
// this transition next transitions out of the idle state but we still
// update it now so that the sort order of this transition remains
// defined until that moment.
//
// See longer explanation in CSSAnimation::CancelFromStyle.
mAnimationIndex = sNextAnimationIndex++;
mNeedsNewAnimationIndexWhenRun = true;
Animation::Cancel(aPostRestyle);
// It is important we do this *after* calling Cancel().
// This is because Cancel() will end up posting a restyle and
// that restyle should target the *transitions* level of the cascade.
// However, once we clear the owning element, CascadeLevel() will begin
// returning CascadeLevel::Animations.
mOwningElement = OwningElementRef();
}
void SetEffectFromStyle(AnimationEffect* aEffect);
void Tick() override;
nsCSSPropertyID TransitionProperty() const;
AnimationValue ToValue() const;
bool HasLowerCompositeOrderThan(const CSSTransition& aOther) const;
EffectCompositor::CascadeLevel CascadeLevel() const override {
return IsTiedToMarkup() ? EffectCompositor::CascadeLevel::Transitions
: EffectCompositor::CascadeLevel::Animations;
}
void SetCreationSequence(uint64_t aIndex) {
MOZ_ASSERT(IsTiedToMarkup());
mAnimationIndex = aIndex;
}
// Sets the owning element which is used for determining the composite
// oder of CSSTransition objects generated from CSS markup.
//
// @see mOwningElement
void SetOwningElement(const OwningElementRef& aElement) {
mOwningElement = aElement;
}
// True for transitions that are generated from CSS markup and continue to
// reflect changes to that markup.
bool IsTiedToMarkup() const { return mOwningElement.IsSet(); }
// Return the animation current time based on a given TimeStamp, a given
// start time and a given playbackRate on a given timeline. This is useful
// when we estimate the current animated value running on the compositor
// because the animation on the compositor may be running ahead while
// main-thread is busy.
static Nullable<TimeDuration> GetCurrentTimeAt(
const AnimationTimeline& aTimeline, const TimeStamp& aBaseTime,
const TimeDuration& aStartTime, double aPlaybackRate);
void MaybeQueueCancelEvent(const StickyTimeDuration& aActiveTime) override {
QueueEvents(aActiveTime);
}
// Compute the portion of the *value* space that we should be through
// at the current time. (The input to the transition timing function
// has time units, the output has value units.)
double CurrentValuePortion() const;
const AnimationValue& StartForReversingTest() const {
return mStartForReversingTest;
}
double ReversePortion() const { return mReversePortion; }
void SetReverseParameters(AnimationValue&& aStartForReversingTest,
double aReversePortion) {
mStartForReversingTest = std::move(aStartForReversingTest);
mReversePortion = aReversePortion;
}
struct ReplacedTransitionProperties {
TimeDuration mStartTime;
double mPlaybackRate;
TimingParams mTiming;
Maybe<ComputedTimingFunction> mTimingFunction;
AnimationValue mFromValue, mToValue;
};
void SetReplacedTransition(
ReplacedTransitionProperties&& aReplacedTransition) {
mReplacedTransition.emplace(std::move(aReplacedTransition));
}
// For a new transition interrupting an existing transition on the
// compositor, update the start value to match the value of the replaced
// transitions at the current time.
void UpdateStartValueFromReplacedTransition();
protected:
virtual ~CSSTransition() {
MOZ_ASSERT(!mOwningElement.IsSet(),
"Owning element should be cleared "
"before a CSS transition is destroyed");
}
// Animation overrides
void UpdateTiming(SeekFlag aSeekFlag,
SyncNotifyFlag aSyncNotifyFlag) override;
void QueueEvents(const StickyTimeDuration& activeTime = StickyTimeDuration());
enum class TransitionPhase;
// 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
// following circumstances:
//
// a) If script removes or replaces the effect of this transition,
// 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;
// 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::Idle),
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
// effect however since it can be replaced using the Web Animations API.
nsCSSPropertyID mTransitionProperty;
AnimationValue mTransitionToValue;
// This is the start value to be used for a check for whether a
// transition is being reversed. Normally the same as
// mEffect->mProperties[0].mSegments[0].mFromValue, except when this
// transition started as the reversal of another in-progress transition
// or when the effect has been mutated using the Web Animations API.
//
// Needed so we can handle two reverses in a row.
AnimationValue mStartForReversingTest;
// Likewise, the portion (in value space) of the "full" reversed
// transition that we're actually covering. For example, if a :hover
// effect has a transition that moves the element 10px to the right
// (by changing 'left' from 0px to 10px), and the mouse moves in to
// the element (starting the transition) but then moves out after the
// transition has advanced 4px, the second transition (from 10px/4px
// to 0px) will have mReversePortion of 0.4. (If the mouse then moves
// in again when the transition is back to 2px, the mReversePortion
// for the third transition (from 0px/2px to 10px) will be 0.8.
double mReversePortion = 1.0;
Maybe<ReplacedTransitionProperties> mReplacedTransition;
};
} // namespace dom
template <>
struct AnimationTypeTraits<dom::CSSTransition> {
static nsAtom* ElementPropertyAtom() {
return nsGkAtoms::transitionsProperty;
}
static nsAtom* BeforePropertyAtom() {
return nsGkAtoms::transitionsOfBeforeProperty;
}
static nsAtom* AfterPropertyAtom() {
return nsGkAtoms::transitionsOfAfterProperty;
}
static nsAtom* MarkerPropertyAtom() {
return nsGkAtoms::transitionsOfMarkerProperty;
}
};
} // namespace mozilla
#endif // mozilla_dom_CSSTransition_h

Просмотреть файл

@ -15,6 +15,7 @@ EXPORTS.mozilla.dom += [
'AnimationEffect.h',
'AnimationTimeline.h',
'CSSPseudoElement.h',
'CSSTransition.h',
'DocumentTimeline.h',
'KeyframeEffect.h',
]
@ -48,6 +49,7 @@ UNIFIED_SOURCES += [
'AnimationUtils.cpp',
'ComputedTimingFunction.cpp',
'CSSPseudoElement.cpp',
'CSSTransition.cpp',
'DocumentTimeline.cpp',
'EffectCompositor.cpp',
'EffectSet.cpp',

Просмотреть файл

@ -7,9 +7,9 @@
#include "mozilla/AnimationCollection.h"
#include "mozilla/RestyleManager.h"
#include "nsAnimationManager.h" // For dom::CSSAnimation
#include "nsDOMMutationObserver.h" // For nsAutoAnimationMutationBatch
#include "nsTransitionManager.h" // For dom::CSSTransition
#include "nsAnimationManager.h" // For dom::CSSAnimation
#include "nsDOMMutationObserver.h" // For nsAutoAnimationMutationBatch
#include "mozilla/dom/CSSTransition.h" // For dom::CSSTransition
namespace mozilla {

Просмотреть файл

@ -8,18 +8,12 @@
#include "nsTransitionManager.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/KeyframeEffectBinding.h"
#include "nsAnimationManager.h"
#include "mozilla/dom/CSSTransitionBinding.h"
#include "nsIContent.h"
#include "mozilla/ComputedStyle.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/TimeStamp.h"
#include "nsRefreshDriver.h"
#include "nsCSSPropertyIDSet.h"
#include "mozilla/AnimationEventDispatcher.h"
#include "mozilla/EffectCompositor.h"
#include "mozilla/EffectSet.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/ServoBindings.h"
@ -35,342 +29,15 @@
#include "nsRFPService.h"
#include "nsStyleChangeList.h"
#include "mozilla/RestyleManager.h"
#include "nsDOMMutationObserver.h"
using mozilla::TimeDuration;
using mozilla::TimeStamp;
using mozilla::dom::Animation;
using mozilla::dom::AnimationPlayState;
using mozilla::dom::CSSTransition;
using mozilla::dom::DocumentTimeline;
using mozilla::dom::KeyframeEffect;
using mozilla::dom::Nullable;
using namespace mozilla;
using namespace mozilla::css;
////////////////////////// CSSTransition ////////////////////////////
JSObject* CSSTransition::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return dom::CSSTransition_Binding::Wrap(aCx, this, aGivenProto);
}
void CSSTransition::GetTransitionProperty(nsString& aRetVal) const {
MOZ_ASSERT(eCSSProperty_UNKNOWN != mTransitionProperty,
"Transition Property should be initialized");
aRetVal =
NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(mTransitionProperty));
}
AnimationPlayState CSSTransition::PlayStateFromJS() const {
FlushUnanimatedStyle();
return Animation::PlayStateFromJS();
}
bool CSSTransition::PendingFromJS() const {
// Transitions don't become pending again after they start running but, if
// while the transition is still pending, style is updated in such a way
// that the transition will be canceled, we need to report false here.
// Hence we need to flush, but only when we're pending.
if (Pending()) {
FlushUnanimatedStyle();
}
return Animation::PendingFromJS();
}
void CSSTransition::PlayFromJS(ErrorResult& aRv) {
FlushUnanimatedStyle();
Animation::PlayFromJS(aRv);
}
void CSSTransition::UpdateTiming(SeekFlag aSeekFlag,
SyncNotifyFlag aSyncNotifyFlag) {
if (mNeedsNewAnimationIndexWhenRun &&
PlayState() != AnimationPlayState::Idle) {
mAnimationIndex = sNextAnimationIndex++;
mNeedsNewAnimationIndexWhenRun = false;
}
Animation::UpdateTiming(aSeekFlag, aSyncNotifyFlag);
}
void CSSTransition::QueueEvents(const StickyTimeDuration& aActiveTime) {
if (!mOwningElement.IsSet()) {
return;
}
nsPresContext* presContext = mOwningElement.GetPresContext();
if (!presContext) {
return;
}
static constexpr StickyTimeDuration zeroDuration = StickyTimeDuration();
TransitionPhase currentPhase;
StickyTimeDuration intervalStartTime;
StickyTimeDuration intervalEndTime;
if (!mEffect) {
currentPhase = GetAnimationPhaseWithoutEffect<TransitionPhase>(*this);
} else {
ComputedTiming computedTiming = mEffect->GetComputedTiming();
currentPhase = static_cast<TransitionPhase>(computedTiming.mPhase);
intervalStartTime = IntervalStartTime(computedTiming.mActiveDuration);
intervalEndTime = IntervalEndTime(computedTiming.mActiveDuration);
}
if (mPendingState != PendingState::NotPending &&
(mPreviousTransitionPhase == TransitionPhase::Idle ||
mPreviousTransitionPhase == TransitionPhase::Pending)) {
currentPhase = TransitionPhase::Pending;
}
if (currentPhase == mPreviousTransitionPhase) {
return;
}
// 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);
AutoTArray<AnimationEventInfo, 3> events;
auto appendTransitionEvent = [&](EventMessage aMessage,
const StickyTimeDuration& aElapsedTime,
const TimeStamp& aScheduledEventTimeStamp) {
double elapsedTime = aElapsedTime.ToSeconds();
if (aMessage == eTransitionCancel) {
// 0 is an inappropriate value for this callsite. What we need to do is
// use a single random value for all increasing times reportable.
// That is to say, whenever elapsedTime goes negative (because an
// animation restarts, something rewinds the animation, or otherwise)
// a new random value for the mix-in must be generated.
elapsedTime =
nsRFPService::ReduceTimePrecisionAsSecsRFPOnly(elapsedTime, 0);
}
events.AppendElement(AnimationEventInfo(
TransitionProperty(), mOwningElement.Target(), aMessage, elapsedTime,
aScheduledEventTimeStamp, this));
};
// Handle cancel events first
if ((mPreviousTransitionPhase != TransitionPhase::Idle &&
mPreviousTransitionPhase != TransitionPhase::After) &&
currentPhase == TransitionPhase::Idle) {
appendTransitionEvent(eTransitionCancel, aActiveTime,
GetTimelineCurrentTimeAsTimeStamp());
}
// All other events
switch (mPreviousTransitionPhase) {
case TransitionPhase::Idle:
if (currentPhase == TransitionPhase::Pending ||
currentPhase == TransitionPhase::Before) {
appendTransitionEvent(eTransitionRun, intervalStartTime, zeroTimeStamp);
} else if (currentPhase == TransitionPhase::Active) {
appendTransitionEvent(eTransitionRun, intervalStartTime, zeroTimeStamp);
appendTransitionEvent(eTransitionStart, intervalStartTime,
startTimeStamp);
} else if (currentPhase == TransitionPhase::After) {
appendTransitionEvent(eTransitionRun, intervalStartTime, zeroTimeStamp);
appendTransitionEvent(eTransitionStart, intervalStartTime,
startTimeStamp);
appendTransitionEvent(eTransitionEnd, intervalEndTime, endTimeStamp);
}
break;
case TransitionPhase::Pending:
case TransitionPhase::Before:
if (currentPhase == TransitionPhase::Active) {
appendTransitionEvent(eTransitionStart, intervalStartTime,
startTimeStamp);
} else if (currentPhase == TransitionPhase::After) {
appendTransitionEvent(eTransitionStart, intervalStartTime,
startTimeStamp);
appendTransitionEvent(eTransitionEnd, intervalEndTime, endTimeStamp);
}
break;
case TransitionPhase::Active:
if (currentPhase == TransitionPhase::After) {
appendTransitionEvent(eTransitionEnd, intervalEndTime, endTimeStamp);
} else if (currentPhase == TransitionPhase::Before) {
appendTransitionEvent(eTransitionEnd, intervalStartTime,
startTimeStamp);
}
break;
case TransitionPhase::After:
if (currentPhase == TransitionPhase::Active) {
appendTransitionEvent(eTransitionStart, intervalEndTime,
startTimeStamp);
} else if (currentPhase == TransitionPhase::Before) {
appendTransitionEvent(eTransitionStart, intervalEndTime,
startTimeStamp);
appendTransitionEvent(eTransitionEnd, intervalStartTime, endTimeStamp);
}
break;
}
mPreviousTransitionPhase = currentPhase;
if (!events.IsEmpty()) {
presContext->AnimationEventDispatcher()->QueueEvents(std::move(events));
}
}
void CSSTransition::Tick() {
Animation::Tick();
QueueEvents();
}
nsCSSPropertyID CSSTransition::TransitionProperty() const {
MOZ_ASSERT(eCSSProperty_UNKNOWN != mTransitionProperty,
"Transition property should be initialized");
return mTransitionProperty;
}
AnimationValue CSSTransition::ToValue() const {
MOZ_ASSERT(!mTransitionToValue.IsNull(),
"Transition ToValue should be initialized");
return mTransitionToValue;
}
bool CSSTransition::HasLowerCompositeOrderThan(
const CSSTransition& aOther) const {
MOZ_ASSERT(IsTiedToMarkup() && aOther.IsTiedToMarkup(),
"Should only be called for CSS transitions that are sorted "
"as CSS transitions (i.e. tied to CSS markup)");
// 0. Object-equality case
if (&aOther == this) {
return false;
}
// 1. Sort by document order
if (!mOwningElement.Equals(aOther.mOwningElement)) {
return mOwningElement.LessThan(
const_cast<CSSTransition*>(this)->CachedChildIndexRef(),
aOther.mOwningElement,
const_cast<CSSTransition*>(&aOther)->CachedChildIndexRef());
}
// 2. (Same element and pseudo): Sort by transition generation
if (mAnimationIndex != aOther.mAnimationIndex) {
return mAnimationIndex < aOther.mAnimationIndex;
}
// 3. (Same transition generation): Sort by transition property
return nsCSSProps::GetStringValue(TransitionProperty()) <
nsCSSProps::GetStringValue(aOther.TransitionProperty());
}
/* static */
Nullable<TimeDuration> CSSTransition::GetCurrentTimeAt(
const AnimationTimeline& aTimeline, const TimeStamp& aBaseTime,
const TimeDuration& aStartTime, double aPlaybackRate) {
Nullable<TimeDuration> result;
Nullable<TimeDuration> timelineTime = aTimeline.ToTimelineTime(aBaseTime);
if (!timelineTime.IsNull()) {
result.SetValue(
(timelineTime.Value() - aStartTime).MultDouble(aPlaybackRate));
}
return result;
}
double CSSTransition::CurrentValuePortion() const {
if (!GetEffect()) {
return 0.0;
}
// Transitions use a fill mode of 'backwards' so GetComputedTiming will
// never return a null time progress due to being *before* the animation
// interval. However, it might be possible that we're behind on flushing
// causing us to get called *after* the animation interval. So, just in
// case, we override the fill mode to 'both' to ensure the progress
// is never null.
TimingParams timingToUse = GetEffect()->SpecifiedTiming();
timingToUse.SetFill(dom::FillMode::Both);
ComputedTiming computedTiming = GetEffect()->GetComputedTiming(&timingToUse);
if (computedTiming.mProgress.IsNull()) {
return 0.0;
}
// 'transition-timing-function' corresponds to the effect timing while
// the transition keyframes have a linear timing function so we can ignore
// them for the purposes of calculating the value portion.
return computedTiming.mProgress.Value();
}
void CSSTransition::UpdateStartValueFromReplacedTransition() {
MOZ_ASSERT(mEffect && mEffect->AsKeyframeEffect() &&
mEffect->AsKeyframeEffect()->HasAnimationOfPropertySet(
nsCSSPropertyIDSet::CompositorAnimatables()),
"Should be called for compositor-runnable transitions");
MOZ_ASSERT(mTimeline,
"Should have a timeline if we are replacing transition start "
"values");
if (!mReplacedTransition) {
return;
}
ComputedTiming computedTiming = AnimationEffect::GetComputedTimingAt(
CSSTransition::GetCurrentTimeAt(*mTimeline, TimeStamp::Now(),
mReplacedTransition->mStartTime,
mReplacedTransition->mPlaybackRate),
mReplacedTransition->mTiming, mReplacedTransition->mPlaybackRate);
if (!computedTiming.mProgress.IsNull()) {
double valuePosition = ComputedTimingFunction::GetPortion(
mReplacedTransition->mTimingFunction, computedTiming.mProgress.Value(),
computedTiming.mBeforeFlag);
const AnimationValue& replacedFrom = mReplacedTransition->mFromValue;
const AnimationValue& replacedTo = mReplacedTransition->mToValue;
AnimationValue startValue;
startValue.mServo =
Servo_AnimationValues_Interpolate(replacedFrom.mServo,
replacedTo.mServo, valuePosition)
.Consume();
mEffect->AsKeyframeEffect()->ReplaceTransitionStartValue(
std::move(startValue));
}
mReplacedTransition.reset();
}
void CSSTransition::SetEffectFromStyle(dom::AnimationEffect* aEffect) {
Animation::SetEffectNoUpdate(aEffect);
// Initialize transition property and to value.
//
// Typically this should only be called with a KeyframeEffect representing
// a simple transition, but just to be sure we check the effect has the
// expected shape first.
const KeyframeEffect* keyframeEffect = aEffect->AsKeyframeEffect();
if (MOZ_LIKELY(keyframeEffect && keyframeEffect->Properties().Length() == 1 &&
keyframeEffect->Properties()[0].mSegments.Length() == 1)) {
mTransitionProperty = keyframeEffect->Properties()[0].mProperty;
mTransitionToValue = keyframeEffect->Properties()[0].mSegments[0].mToValue;
} else {
MOZ_ASSERT_UNREACHABLE("Transition effect has unexpected shape");
}
}
////////////////////////// nsTransitionManager ////////////////////////////
static inline bool ExtractNonDiscreteComputedValue(
nsCSSPropertyID aProperty, const ComputedStyle& aComputedStyle,
AnimationValue& aAnimationValue) {

Просмотреть файл

@ -9,255 +9,16 @@
#ifndef nsTransitionManager_h_
#define nsTransitionManager_h_
#include "mozilla/ComputedTiming.h"
#include "mozilla/EffectCompositor.h" // For EffectCompositor::CascadeLevel
#include "mozilla/dom/Animation.h"
#include "mozilla/dom/KeyframeEffect.h"
#include "mozilla/dom/CSSTransition.h"
#include "AnimationCommon.h"
#include "nsISupportsImpl.h"
class nsIGlobalObject;
class nsPresContext;
class nsCSSPropertyIDSet;
namespace mozilla {
class ComputedStyle;
enum class PseudoStyleType : uint8_t;
struct Keyframe;
struct StyleTransition;
} // namespace mozilla
/*****************************************************************************
* Per-Element data *
*****************************************************************************/
namespace mozilla {
namespace dom {
class CSSTransition final : public Animation {
public:
explicit CSSTransition(nsIGlobalObject* aGlobal)
: dom::Animation(aGlobal),
mPreviousTransitionPhase(TransitionPhase::Idle),
mNeedsNewAnimationIndexWhenRun(false),
mTransitionProperty(eCSSProperty_UNKNOWN) {}
JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
CSSTransition* AsCSSTransition() override { return this; }
const CSSTransition* AsCSSTransition() const override { return this; }
// CSSTransition interface
void GetTransitionProperty(nsString& aRetVal) const;
// Animation interface overrides
AnimationPlayState PlayStateFromJS() const override;
bool PendingFromJS() const override;
void PlayFromJS(ErrorResult& aRv) override;
// A variant of Play() that avoids posting style updates since this method
// is expected to be called whilst already updating style.
void PlayFromStyle() {
ErrorResult rv;
PlayNoUpdate(rv, Animation::LimitBehavior::Continue);
// play() should not throw when LimitBehavior is Continue
MOZ_ASSERT(!rv.Failed(), "Unexpected exception playing transition");
}
void CancelFromStyle(PostRestyleMode aPostRestyle) {
// The animation index to use for compositing will be established when
// this transition next transitions out of the idle state but we still
// update it now so that the sort order of this transition remains
// defined until that moment.
//
// See longer explanation in CSSAnimation::CancelFromStyle.
mAnimationIndex = sNextAnimationIndex++;
mNeedsNewAnimationIndexWhenRun = true;
Animation::Cancel(aPostRestyle);
// It is important we do this *after* calling Cancel().
// This is because Cancel() will end up posting a restyle and
// that restyle should target the *transitions* level of the cascade.
// However, once we clear the owning element, CascadeLevel() will begin
// returning CascadeLevel::Animations.
mOwningElement = OwningElementRef();
}
void SetEffectFromStyle(AnimationEffect* aEffect);
void Tick() override;
nsCSSPropertyID TransitionProperty() const;
AnimationValue ToValue() const;
bool HasLowerCompositeOrderThan(const CSSTransition& aOther) const;
EffectCompositor::CascadeLevel CascadeLevel() const override {
return IsTiedToMarkup() ? EffectCompositor::CascadeLevel::Transitions
: EffectCompositor::CascadeLevel::Animations;
}
void SetCreationSequence(uint64_t aIndex) {
MOZ_ASSERT(IsTiedToMarkup());
mAnimationIndex = aIndex;
}
// Sets the owning element which is used for determining the composite
// oder of CSSTransition objects generated from CSS markup.
//
// @see mOwningElement
void SetOwningElement(const OwningElementRef& aElement) {
mOwningElement = aElement;
}
// True for transitions that are generated from CSS markup and continue to
// reflect changes to that markup.
bool IsTiedToMarkup() const { return mOwningElement.IsSet(); }
// Return the animation current time based on a given TimeStamp, a given
// start time and a given playbackRate on a given timeline. This is useful
// when we estimate the current animated value running on the compositor
// because the animation on the compositor may be running ahead while
// main-thread is busy.
static Nullable<TimeDuration> GetCurrentTimeAt(
const AnimationTimeline& aTimeline, const TimeStamp& aBaseTime,
const TimeDuration& aStartTime, double aPlaybackRate);
void MaybeQueueCancelEvent(const StickyTimeDuration& aActiveTime) override {
QueueEvents(aActiveTime);
}
// Compute the portion of the *value* space that we should be through
// at the current time. (The input to the transition timing function
// has time units, the output has value units.)
double CurrentValuePortion() const;
const AnimationValue& StartForReversingTest() const {
return mStartForReversingTest;
}
double ReversePortion() const { return mReversePortion; }
void SetReverseParameters(AnimationValue&& aStartForReversingTest,
double aReversePortion) {
mStartForReversingTest = std::move(aStartForReversingTest);
mReversePortion = aReversePortion;
}
struct ReplacedTransitionProperties {
TimeDuration mStartTime;
double mPlaybackRate;
TimingParams mTiming;
Maybe<ComputedTimingFunction> mTimingFunction;
AnimationValue mFromValue, mToValue;
};
void SetReplacedTransition(
ReplacedTransitionProperties&& aReplacedTransition) {
mReplacedTransition.emplace(std::move(aReplacedTransition));
}
// For a new transition interrupting an existing transition on the
// compositor, update the start value to match the value of the replaced
// transitions at the current time.
void UpdateStartValueFromReplacedTransition();
protected:
virtual ~CSSTransition() {
MOZ_ASSERT(!mOwningElement.IsSet(),
"Owning element should be cleared "
"before a CSS transition is destroyed");
}
// Animation overrides
void UpdateTiming(SeekFlag aSeekFlag,
SyncNotifyFlag aSyncNotifyFlag) override;
void QueueEvents(const StickyTimeDuration& activeTime = StickyTimeDuration());
enum class TransitionPhase;
// 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
// following circumstances:
//
// a) If script removes or replaces the effect of this transition,
// 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;
// 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::Idle),
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
// effect however since it can be replaced using the Web Animations API.
nsCSSPropertyID mTransitionProperty;
AnimationValue mTransitionToValue;
// This is the start value to be used for a check for whether a
// transition is being reversed. Normally the same as
// mEffect->mProperties[0].mSegments[0].mFromValue, except when this
// transition started as the reversal of another in-progress transition
// or when the effect has been mutated using the Web Animations API.
//
// Needed so we can handle two reverses in a row.
AnimationValue mStartForReversingTest;
// Likewise, the portion (in value space) of the "full" reversed
// transition that we're actually covering. For example, if a :hover
// effect has a transition that moves the element 10px to the right
// (by changing 'left' from 0px to 10px), and the mouse moves in to
// the element (starting the transition) but then moves out after the
// transition has advanced 4px, the second transition (from 10px/4px
// to 0px) will have mReversePortion of 0.4. (If the mouse then moves
// in again when the transition is back to 2px, the mReversePortion
// for the third transition (from 0px/2px to 10px) will be 0.8.
double mReversePortion = 1.0;
Maybe<ReplacedTransitionProperties> mReplacedTransition;
};
} // namespace dom
template <>
struct AnimationTypeTraits<dom::CSSTransition> {
static nsAtom* ElementPropertyAtom() {
return nsGkAtoms::transitionsProperty;
}
static nsAtom* BeforePropertyAtom() {
return nsGkAtoms::transitionsOfBeforeProperty;
}
static nsAtom* AfterPropertyAtom() {
return nsGkAtoms::transitionsOfAfterProperty;
}
static nsAtom* MarkerPropertyAtom() {
return nsGkAtoms::transitionsOfMarkerProperty;
}
};
} // namespace mozilla
class nsTransitionManager final