1 /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
4 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "mozilla/dom/Animation.h"
7 #include "mozilla/dom/AnimationBinding.h"
8 #include "mozilla/dom/AnimationEffect.h"
9 #include "mozilla/FloatingPoint.h"
10 #include "AnimationCommon.h"
11 #include "nsCSSPropertySet.h"
16 ComputedTimingFunction::Init(const nsTimingFunction
&aFunction
)
18 mType
= aFunction
.mType
;
19 if (mType
== nsTimingFunction::Function
) {
20 mTimingFunction
.Init(aFunction
.mFunc
.mX1
, aFunction
.mFunc
.mY1
,
21 aFunction
.mFunc
.mX2
, aFunction
.mFunc
.mY2
);
23 mSteps
= aFunction
.mSteps
;
28 StepEnd(uint32_t aSteps
, double aPortion
)
30 NS_ABORT_IF_FALSE(0.0 <= aPortion
&& aPortion
<= 1.0, "out of range");
31 uint32_t step
= uint32_t(aPortion
* aSteps
); // floor
32 return double(step
) / double(aSteps
);
36 ComputedTimingFunction::GetValue(double aPortion
) const
39 case nsTimingFunction::Function
:
40 return mTimingFunction
.GetSplineValue(aPortion
);
41 case nsTimingFunction::StepStart
:
42 // There are diagrams in the spec that seem to suggest this check
43 // and the bounds point should not be symmetric with StepEnd, but
44 // should actually step up at rather than immediately after the
45 // fraction points. However, we rely on rounding negative values
46 // up to zero, so we can't do that. And it's not clear the spec
48 return 1.0 - StepEnd(mSteps
, 1.0 - aPortion
);
50 NS_ABORT_IF_FALSE(false, "bad type");
52 case nsTimingFunction::StepEnd
:
53 return StepEnd(mSteps
, aPortion
);
57 // In the Web Animations model, the time fraction can be outside the range
58 // [0.0, 1.0] but it shouldn't be Infinity.
59 const double ComputedTiming::kNullTimeFraction
= PositiveInfinity
<double>();
63 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Animation
, mDocument
, mTarget
)
65 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(Animation
, AddRef
)
66 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(Animation
, Release
)
69 Animation::WrapObject(JSContext
* aCx
)
71 return AnimationBinding::Wrap(aCx
, this);
74 already_AddRefed
<AnimationEffect
>
75 Animation::GetEffect()
77 nsRefPtr
<AnimationEffect
> effect
= new AnimationEffect(this);
78 return effect
.forget();
82 Animation::SetParentTime(Nullable
<TimeDuration
> aParentTime
)
84 mParentTime
= aParentTime
;
88 Animation::GetComputedTimingAt(const Nullable
<TimeDuration
>& aLocalTime
,
89 const AnimationTiming
& aTiming
)
91 const TimeDuration zeroDuration
;
93 // Currently we expect negative durations to be picked up during CSS
94 // parsing but when we start receiving timing parameters from other sources
95 // we will need to clamp negative durations here.
96 // For now, if we're hitting this it probably means we're overflowing
97 // integer arithmetic in mozilla::TimeStamp.
98 MOZ_ASSERT(aTiming
.mIterationDuration
>= zeroDuration
,
99 "Expecting iteration duration >= 0");
101 // Always return the same object to benefit from return-value optimization.
102 ComputedTiming result
;
104 result
.mActiveDuration
= ActiveDuration(aTiming
);
106 // The default constructor for ComputedTiming sets all other members to
107 // values consistent with an animation that has not been sampled.
108 if (aLocalTime
.IsNull()) {
111 const TimeDuration
& localTime
= aLocalTime
.Value();
113 // When we finish exactly at the end of an iteration we need to report
114 // the end of the final iteration and not the start of the next iteration
115 // so we set up a flag for that case.
116 bool isEndOfFinalIteration
= false;
118 // Get the normalized time within the active interval.
119 StickyTimeDuration activeTime
;
120 if (localTime
>= aTiming
.mDelay
+ result
.mActiveDuration
) {
121 result
.mPhase
= ComputedTiming::AnimationPhase_After
;
122 if (!aTiming
.FillsForwards()) {
123 // The animation isn't active or filling at this time.
124 result
.mTimeFraction
= ComputedTiming::kNullTimeFraction
;
127 activeTime
= result
.mActiveDuration
;
128 // Note that infinity == floor(infinity) so this will also be true when we
129 // have finished an infinitely repeating animation of zero duration.
130 isEndOfFinalIteration
=
131 aTiming
.mIterationCount
!= 0.0 &&
132 aTiming
.mIterationCount
== floor(aTiming
.mIterationCount
);
133 } else if (localTime
< aTiming
.mDelay
) {
134 result
.mPhase
= ComputedTiming::AnimationPhase_Before
;
135 if (!aTiming
.FillsBackwards()) {
136 // The animation isn't active or filling at this time.
137 result
.mTimeFraction
= ComputedTiming::kNullTimeFraction
;
140 // activeTime is zero
142 MOZ_ASSERT(result
.mActiveDuration
!= zeroDuration
,
143 "How can we be in the middle of a zero-duration interval?");
144 result
.mPhase
= ComputedTiming::AnimationPhase_Active
;
145 activeTime
= localTime
- aTiming
.mDelay
;
148 // Get the position within the current iteration.
149 StickyTimeDuration iterationTime
;
150 if (aTiming
.mIterationDuration
!= zeroDuration
) {
151 iterationTime
= isEndOfFinalIteration
152 ? StickyTimeDuration(aTiming
.mIterationDuration
)
153 : activeTime
% aTiming
.mIterationDuration
;
154 } /* else, iterationTime is zero */
156 // Determine the 0-based index of the current iteration.
157 if (isEndOfFinalIteration
) {
158 result
.mCurrentIteration
=
159 aTiming
.mIterationCount
== NS_IEEEPositiveInfinity()
160 ? UINT64_MAX
// FIXME: When we return this via the API we'll need
161 // to make sure it ends up being infinity.
162 : static_cast<uint64_t>(aTiming
.mIterationCount
) - 1;
163 } else if (activeTime
== zeroDuration
) {
164 // If the active time is zero we're either in the first iteration
165 // (including filling backwards) or we have finished an animation with an
166 // iteration duration of zero that is filling forwards (but we're not at
167 // the exact end of an iteration since we deal with that above).
168 result
.mCurrentIteration
=
169 result
.mPhase
== ComputedTiming::AnimationPhase_After
170 ? static_cast<uint64_t>(aTiming
.mIterationCount
) // floor
173 result
.mCurrentIteration
=
174 static_cast<uint64_t>(activeTime
/ aTiming
.mIterationDuration
); // floor
177 // Normalize the iteration time into a fraction of the iteration duration.
178 if (result
.mPhase
== ComputedTiming::AnimationPhase_Before
) {
179 result
.mTimeFraction
= 0.0;
180 } else if (result
.mPhase
== ComputedTiming::AnimationPhase_After
) {
181 result
.mTimeFraction
= isEndOfFinalIteration
183 : fmod(aTiming
.mIterationCount
, 1.0f
);
185 // We are in the active phase so the iteration duration can't be zero.
186 MOZ_ASSERT(aTiming
.mIterationDuration
!= zeroDuration
,
187 "In the active phase of a zero-duration animation?");
188 result
.mTimeFraction
=
189 aTiming
.mIterationDuration
== TimeDuration::Forever()
191 : iterationTime
/ aTiming
.mIterationDuration
;
194 bool thisIterationReverse
= false;
195 switch (aTiming
.mDirection
) {
196 case NS_STYLE_ANIMATION_DIRECTION_NORMAL
:
197 thisIterationReverse
= false;
199 case NS_STYLE_ANIMATION_DIRECTION_REVERSE
:
200 thisIterationReverse
= true;
202 case NS_STYLE_ANIMATION_DIRECTION_ALTERNATE
:
203 thisIterationReverse
= (result
.mCurrentIteration
& 1) == 1;
205 case NS_STYLE_ANIMATION_DIRECTION_ALTERNATE_REVERSE
:
206 thisIterationReverse
= (result
.mCurrentIteration
& 1) == 0;
209 if (thisIterationReverse
) {
210 result
.mTimeFraction
= 1.0 - result
.mTimeFraction
;
217 Animation::ActiveDuration(const AnimationTiming
& aTiming
)
219 if (aTiming
.mIterationCount
== mozilla::PositiveInfinity
<float>()) {
220 // An animation that repeats forever has an infinite active duration
221 // unless its iteration duration is zero, in which case it has a zero
223 const StickyTimeDuration zeroDuration
;
224 return aTiming
.mIterationDuration
== zeroDuration
226 : StickyTimeDuration::Forever();
228 return StickyTimeDuration(
229 aTiming
.mIterationDuration
.MultDouble(aTiming
.mIterationCount
));
233 Animation::IsCurrent() const
235 if (IsFinishedTransition()) {
239 ComputedTiming computedTiming
= GetComputedTiming();
240 return computedTiming
.mPhase
== ComputedTiming::AnimationPhase_Before
||
241 computedTiming
.mPhase
== ComputedTiming::AnimationPhase_Active
;
245 Animation::IsInEffect() const
247 if (IsFinishedTransition()) {
251 ComputedTiming computedTiming
= GetComputedTiming();
252 return computedTiming
.mTimeFraction
!= ComputedTiming::kNullTimeFraction
;
256 Animation::HasAnimationOfProperty(nsCSSProperty aProperty
) const
258 for (size_t propIdx
= 0, propEnd
= mProperties
.Length();
259 propIdx
!= propEnd
; ++propIdx
) {
260 if (aProperty
== mProperties
[propIdx
].mProperty
) {
268 Animation::ComposeStyle(nsRefPtr
<css::AnimValuesStyleRule
>& aStyleRule
,
269 nsCSSPropertySet
& aSetProperties
)
271 ComputedTiming computedTiming
= GetComputedTiming();
273 // If the time fraction is null, we don't have fill data for the current
274 // time so we shouldn't animate.
275 if (computedTiming
.mTimeFraction
== ComputedTiming::kNullTimeFraction
) {
279 MOZ_ASSERT(0.0 <= computedTiming
.mTimeFraction
&&
280 computedTiming
.mTimeFraction
<= 1.0,
281 "timing fraction should be in [0-1]");
283 for (size_t propIdx
= 0, propEnd
= mProperties
.Length();
284 propIdx
!= propEnd
; ++propIdx
)
286 const AnimationProperty
& prop
= mProperties
[propIdx
];
288 MOZ_ASSERT(prop
.mSegments
[0].mFromKey
== 0.0, "incorrect first from key");
289 MOZ_ASSERT(prop
.mSegments
[prop
.mSegments
.Length() - 1].mToKey
== 1.0,
290 "incorrect last to key");
292 if (aSetProperties
.HasProperty(prop
.mProperty
)) {
293 // Animations are composed by AnimationPlayerCollection by iterating
294 // from the last animation to first. For animations targetting the
295 // same property, the later one wins. So if this property is already set,
296 // we should not override it.
300 aSetProperties
.AddProperty(prop
.mProperty
);
302 MOZ_ASSERT(prop
.mSegments
.Length() > 0,
303 "property should not be in animations if it has no segments");
305 // FIXME: Maybe cache the current segment?
306 const AnimationPropertySegment
*segment
= prop
.mSegments
.Elements(),
307 *segmentEnd
= segment
+ prop
.mSegments
.Length();
308 while (segment
->mToKey
< computedTiming
.mTimeFraction
) {
309 MOZ_ASSERT(segment
->mFromKey
< segment
->mToKey
, "incorrect keys");
311 if (segment
== segmentEnd
) {
312 MOZ_ASSERT_UNREACHABLE("incorrect time fraction");
313 break; // in order to continue in outer loop (just below)
315 MOZ_ASSERT(segment
->mFromKey
== (segment
-1)->mToKey
, "incorrect keys");
317 if (segment
== segmentEnd
) {
320 MOZ_ASSERT(segment
->mFromKey
< segment
->mToKey
, "incorrect keys");
321 MOZ_ASSERT(segment
>= prop
.mSegments
.Elements() &&
322 size_t(segment
- prop
.mSegments
.Elements()) <
323 prop
.mSegments
.Length(),
324 "out of array bounds");
327 // Allocate the style rule now that we know we have animation data.
328 aStyleRule
= new css::AnimValuesStyleRule();
331 double positionInSegment
=
332 (computedTiming
.mTimeFraction
- segment
->mFromKey
) /
333 (segment
->mToKey
- segment
->mFromKey
);
334 double valuePosition
=
335 segment
->mTimingFunction
.GetValue(positionInSegment
);
337 StyleAnimationValue
*val
= aStyleRule
->AddEmptyValue(prop
.mProperty
);
342 StyleAnimationValue::Interpolate(prop
.mProperty
,
345 valuePosition
, *val
);
346 MOZ_ASSERT(result
, "interpolate must succeed now");
351 } // namespace mozilla