зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
6d7f47890b
Коммит
eee4796cb1
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче