Bug 1812499 [wpt PR 38184] - Simplify handling of name-to-subdir mapping in canvas...
[gecko.git] / dom / animation / CSSTransition.cpp
blob38f9c9386abb5be7149f538469d01ddef3622fde
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "CSSTransition.h"
9 #include "mozilla/AnimationEventDispatcher.h"
10 #include "mozilla/dom/CSSTransitionBinding.h"
11 #include "mozilla/dom/KeyframeEffectBinding.h"
12 #include "mozilla/dom/KeyframeEffect.h"
13 #include "mozilla/TimeStamp.h"
14 #include "nsPresContext.h"
16 namespace mozilla::dom {
18 JSObject* CSSTransition::WrapObject(JSContext* aCx,
19 JS::Handle<JSObject*> aGivenProto) {
20 return dom::CSSTransition_Binding::Wrap(aCx, this, aGivenProto);
23 void CSSTransition::GetTransitionProperty(nsString& aRetVal) const {
24 MOZ_ASSERT(eCSSProperty_UNKNOWN != mTransitionProperty,
25 "Transition Property should be initialized");
26 aRetVal =
27 NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(mTransitionProperty));
30 AnimationPlayState CSSTransition::PlayStateFromJS() const {
31 FlushUnanimatedStyle();
32 return Animation::PlayStateFromJS();
35 bool CSSTransition::PendingFromJS() const {
36 // Transitions don't become pending again after they start running but, if
37 // while the transition is still pending, style is updated in such a way
38 // that the transition will be canceled, we need to report false here.
39 // Hence we need to flush, but only when we're pending.
40 if (Pending()) {
41 FlushUnanimatedStyle();
43 return Animation::PendingFromJS();
46 void CSSTransition::PlayFromJS(ErrorResult& aRv) {
47 FlushUnanimatedStyle();
48 Animation::PlayFromJS(aRv);
51 void CSSTransition::UpdateTiming(SeekFlag aSeekFlag,
52 SyncNotifyFlag aSyncNotifyFlag) {
53 if (mNeedsNewAnimationIndexWhenRun &&
54 PlayState() != AnimationPlayState::Idle) {
55 mAnimationIndex = sNextAnimationIndex++;
56 mNeedsNewAnimationIndexWhenRun = false;
59 Animation::UpdateTiming(aSeekFlag, aSyncNotifyFlag);
62 void CSSTransition::QueueEvents(const StickyTimeDuration& aActiveTime) {
63 if (!mOwningElement.IsSet()) {
64 return;
67 nsPresContext* presContext = mOwningElement.GetPresContext();
68 if (!presContext) {
69 return;
72 static constexpr StickyTimeDuration zeroDuration = StickyTimeDuration();
74 TransitionPhase currentPhase;
75 StickyTimeDuration intervalStartTime;
76 StickyTimeDuration intervalEndTime;
78 if (!mEffect) {
79 currentPhase = GetAnimationPhaseWithoutEffect<TransitionPhase>(*this);
80 } else {
81 ComputedTiming computedTiming = mEffect->GetComputedTiming();
83 currentPhase = static_cast<TransitionPhase>(computedTiming.mPhase);
84 intervalStartTime = IntervalStartTime(computedTiming.mActiveDuration);
85 intervalEndTime = IntervalEndTime(computedTiming.mActiveDuration);
88 if (mPendingState != PendingState::NotPending &&
89 (mPreviousTransitionPhase == TransitionPhase::Idle ||
90 mPreviousTransitionPhase == TransitionPhase::Pending)) {
91 currentPhase = TransitionPhase::Pending;
94 if (currentPhase == mPreviousTransitionPhase) {
95 return;
98 // TimeStamps to use for ordering the events when they are dispatched. We
99 // use a TimeStamp so we can compare events produced by different elements,
100 // perhaps even with different timelines.
101 // The zero timestamp is for transitionrun events where we ignore the delay
102 // for the purpose of ordering events.
103 TimeStamp zeroTimeStamp = AnimationTimeToTimeStamp(zeroDuration);
104 TimeStamp startTimeStamp = ElapsedTimeToTimeStamp(intervalStartTime);
105 TimeStamp endTimeStamp = ElapsedTimeToTimeStamp(intervalEndTime);
107 AutoTArray<AnimationEventInfo, 3> events;
109 auto appendTransitionEvent = [&](EventMessage aMessage,
110 const StickyTimeDuration& aElapsedTime,
111 const TimeStamp& aScheduledEventTimeStamp) {
112 double elapsedTime = aElapsedTime.ToSeconds();
113 if (aMessage == eTransitionCancel) {
114 // 0 is an inappropriate value for this callsite. What we need to do is
115 // use a single random value for all increasing times reportable.
116 // That is to say, whenever elapsedTime goes negative (because an
117 // animation restarts, something rewinds the animation, or otherwise)
118 // a new random value for the mix-in must be generated.
119 elapsedTime = nsRFPService::ReduceTimePrecisionAsSecsRFPOnly(
120 elapsedTime, 0, mRTPCallerType);
122 events.AppendElement(AnimationEventInfo(
123 TransitionProperty(), mOwningElement.Target(), aMessage, elapsedTime,
124 aScheduledEventTimeStamp, this));
127 // Handle cancel events first
128 if ((mPreviousTransitionPhase != TransitionPhase::Idle &&
129 mPreviousTransitionPhase != TransitionPhase::After) &&
130 currentPhase == TransitionPhase::Idle) {
131 appendTransitionEvent(eTransitionCancel, aActiveTime,
132 GetTimelineCurrentTimeAsTimeStamp());
135 // All other events
136 switch (mPreviousTransitionPhase) {
137 case TransitionPhase::Idle:
138 if (currentPhase == TransitionPhase::Pending ||
139 currentPhase == TransitionPhase::Before) {
140 appendTransitionEvent(eTransitionRun, intervalStartTime, zeroTimeStamp);
141 } else if (currentPhase == TransitionPhase::Active) {
142 appendTransitionEvent(eTransitionRun, intervalStartTime, zeroTimeStamp);
143 appendTransitionEvent(eTransitionStart, intervalStartTime,
144 startTimeStamp);
145 } else if (currentPhase == TransitionPhase::After) {
146 appendTransitionEvent(eTransitionRun, intervalStartTime, zeroTimeStamp);
147 appendTransitionEvent(eTransitionStart, intervalStartTime,
148 startTimeStamp);
149 appendTransitionEvent(eTransitionEnd, intervalEndTime, endTimeStamp);
151 break;
153 case TransitionPhase::Pending:
154 case TransitionPhase::Before:
155 if (currentPhase == TransitionPhase::Active) {
156 appendTransitionEvent(eTransitionStart, intervalStartTime,
157 startTimeStamp);
158 } else if (currentPhase == TransitionPhase::After) {
159 appendTransitionEvent(eTransitionStart, intervalStartTime,
160 startTimeStamp);
161 appendTransitionEvent(eTransitionEnd, intervalEndTime, endTimeStamp);
163 break;
165 case TransitionPhase::Active:
166 if (currentPhase == TransitionPhase::After) {
167 appendTransitionEvent(eTransitionEnd, intervalEndTime, endTimeStamp);
168 } else if (currentPhase == TransitionPhase::Before) {
169 appendTransitionEvent(eTransitionEnd, intervalStartTime,
170 startTimeStamp);
172 break;
174 case TransitionPhase::After:
175 if (currentPhase == TransitionPhase::Active) {
176 appendTransitionEvent(eTransitionStart, intervalEndTime,
177 startTimeStamp);
178 } else if (currentPhase == TransitionPhase::Before) {
179 appendTransitionEvent(eTransitionStart, intervalEndTime,
180 startTimeStamp);
181 appendTransitionEvent(eTransitionEnd, intervalStartTime, endTimeStamp);
183 break;
185 mPreviousTransitionPhase = currentPhase;
187 if (!events.IsEmpty()) {
188 presContext->AnimationEventDispatcher()->QueueEvents(std::move(events));
192 void CSSTransition::Tick() {
193 Animation::Tick();
194 QueueEvents();
197 nsCSSPropertyID CSSTransition::TransitionProperty() const {
198 MOZ_ASSERT(eCSSProperty_UNKNOWN != mTransitionProperty,
199 "Transition property should be initialized");
200 return mTransitionProperty;
203 AnimationValue CSSTransition::ToValue() const {
204 MOZ_ASSERT(!mTransitionToValue.IsNull(),
205 "Transition ToValue should be initialized");
206 return mTransitionToValue;
209 bool CSSTransition::HasLowerCompositeOrderThan(
210 const CSSTransition& aOther) const {
211 MOZ_ASSERT(IsTiedToMarkup() && aOther.IsTiedToMarkup(),
212 "Should only be called for CSS transitions that are sorted "
213 "as CSS transitions (i.e. tied to CSS markup)");
215 // 0. Object-equality case
216 if (&aOther == this) {
217 return false;
220 // 1. Sort by document order
221 if (!mOwningElement.Equals(aOther.mOwningElement)) {
222 return mOwningElement.LessThan(
223 const_cast<CSSTransition*>(this)->CachedChildIndexRef(),
224 aOther.mOwningElement,
225 const_cast<CSSTransition*>(&aOther)->CachedChildIndexRef());
228 // 2. (Same element and pseudo): Sort by transition generation
229 if (mAnimationIndex != aOther.mAnimationIndex) {
230 return mAnimationIndex < aOther.mAnimationIndex;
233 // 3. (Same transition generation): Sort by transition property
234 return nsCSSProps::GetStringValue(TransitionProperty()) <
235 nsCSSProps::GetStringValue(aOther.TransitionProperty());
238 /* static */
239 Nullable<TimeDuration> CSSTransition::GetCurrentTimeAt(
240 const AnimationTimeline& aTimeline, const TimeStamp& aBaseTime,
241 const TimeDuration& aStartTime, double aPlaybackRate) {
242 Nullable<TimeDuration> result;
244 Nullable<TimeDuration> timelineTime = aTimeline.ToTimelineTime(aBaseTime);
245 if (!timelineTime.IsNull()) {
246 result.SetValue(
247 (timelineTime.Value() - aStartTime).MultDouble(aPlaybackRate));
250 return result;
253 double CSSTransition::CurrentValuePortion() const {
254 if (!GetEffect()) {
255 return 0.0;
258 // Transitions use a fill mode of 'backwards' so GetComputedTiming will
259 // never return a null time progress due to being *before* the animation
260 // interval. However, it might be possible that we're behind on flushing
261 // causing us to get called *after* the animation interval. So, just in
262 // case, we override the fill mode to 'both' to ensure the progress
263 // is never null.
264 TimingParams timingToUse = GetEffect()->SpecifiedTiming();
265 timingToUse.SetFill(dom::FillMode::Both);
266 ComputedTiming computedTiming = GetEffect()->GetComputedTiming(&timingToUse);
268 if (computedTiming.mProgress.IsNull()) {
269 return 0.0;
272 // 'transition-timing-function' corresponds to the effect timing while
273 // the transition keyframes have a linear timing function so we can ignore
274 // them for the purposes of calculating the value portion.
275 return computedTiming.mProgress.Value();
278 void CSSTransition::UpdateStartValueFromReplacedTransition() {
279 MOZ_ASSERT(mEffect && mEffect->AsKeyframeEffect() &&
280 mEffect->AsKeyframeEffect()->HasAnimationOfPropertySet(
281 nsCSSPropertyIDSet::CompositorAnimatables()),
282 "Should be called for compositor-runnable transitions");
284 if (!mReplacedTransition) {
285 return;
288 // We don't set |mReplacedTransition| if the timeline of this transition is
289 // different from the document timeline. The timeline of Animation may be
290 // null via script, so if it's null, it must be different from the document
291 // timeline (because document timeline is readonly so we cannot change it by
292 // script). Therefore, we check this assertion if mReplacedTransition is
293 // valid.
294 MOZ_ASSERT(mTimeline,
295 "Should have a timeline if we are replacing transition start "
296 "values");
298 ComputedTiming computedTiming = AnimationEffect::GetComputedTimingAt(
299 CSSTransition::GetCurrentTimeAt(*mTimeline, TimeStamp::Now(),
300 mReplacedTransition->mStartTime,
301 mReplacedTransition->mPlaybackRate),
302 mReplacedTransition->mTiming, mReplacedTransition->mPlaybackRate,
303 Animation::ProgressTimelinePosition::NotBoundary);
305 if (!computedTiming.mProgress.IsNull()) {
306 double valuePosition = StyleComputedTimingFunction::GetPortion(
307 mReplacedTransition->mTimingFunction, computedTiming.mProgress.Value(),
308 computedTiming.mBeforeFlag);
310 const AnimationValue& replacedFrom = mReplacedTransition->mFromValue;
311 const AnimationValue& replacedTo = mReplacedTransition->mToValue;
312 AnimationValue startValue;
313 startValue.mServo =
314 Servo_AnimationValues_Interpolate(replacedFrom.mServo,
315 replacedTo.mServo, valuePosition)
316 .Consume();
318 mEffect->AsKeyframeEffect()->ReplaceTransitionStartValue(
319 std::move(startValue));
322 mReplacedTransition.reset();
325 void CSSTransition::SetEffectFromStyle(KeyframeEffect* aEffect) {
326 MOZ_ASSERT(aEffect->IsValidTransition());
328 Animation::SetEffectNoUpdate(aEffect);
329 mTransitionProperty = aEffect->Properties()[0].mProperty;
330 mTransitionToValue = aEffect->Properties()[0].mSegments[0].mToValue;
333 } // namespace mozilla::dom