Bumping manifests a=b2g-bump
[gecko.git] / dom / animation / Animation.cpp
blob135a2bdb50bf091b43642507ddcc8ed961dd2948
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"
13 namespace mozilla {
15 void
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);
22 } else {
23 mSteps = aFunction.mSteps;
27 static inline double
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);
35 double
36 ComputedTimingFunction::GetValue(double aPortion) const
38 switch (mType) {
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
47 // really meant it.
48 return 1.0 - StepEnd(mSteps, 1.0 - aPortion);
49 default:
50 NS_ABORT_IF_FALSE(false, "bad type");
51 // fall through
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>();
61 namespace dom {
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)
68 JSObject*
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();
81 void
82 Animation::SetParentTime(Nullable<TimeDuration> aParentTime)
84 mParentTime = aParentTime;
87 ComputedTiming
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()) {
109 return result;
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;
125 return result;
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;
138 return result;
140 // activeTime is zero
141 } else {
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
171 : 0;
172 } else {
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
182 ? 1.0
183 : fmod(aTiming.mIterationCount, 1.0f);
184 } else {
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()
190 ? 0.0
191 : iterationTime / aTiming.mIterationDuration;
194 bool thisIterationReverse = false;
195 switch (aTiming.mDirection) {
196 case NS_STYLE_ANIMATION_DIRECTION_NORMAL:
197 thisIterationReverse = false;
198 break;
199 case NS_STYLE_ANIMATION_DIRECTION_REVERSE:
200 thisIterationReverse = true;
201 break;
202 case NS_STYLE_ANIMATION_DIRECTION_ALTERNATE:
203 thisIterationReverse = (result.mCurrentIteration & 1) == 1;
204 break;
205 case NS_STYLE_ANIMATION_DIRECTION_ALTERNATE_REVERSE:
206 thisIterationReverse = (result.mCurrentIteration & 1) == 0;
207 break;
209 if (thisIterationReverse) {
210 result.mTimeFraction = 1.0 - result.mTimeFraction;
213 return result;
216 StickyTimeDuration
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
222 // active duration.
223 const StickyTimeDuration zeroDuration;
224 return aTiming.mIterationDuration == zeroDuration
225 ? zeroDuration
226 : StickyTimeDuration::Forever();
228 return StickyTimeDuration(
229 aTiming.mIterationDuration.MultDouble(aTiming.mIterationCount));
232 bool
233 Animation::IsCurrent() const
235 if (IsFinishedTransition()) {
236 return false;
239 ComputedTiming computedTiming = GetComputedTiming();
240 return computedTiming.mPhase == ComputedTiming::AnimationPhase_Before ||
241 computedTiming.mPhase == ComputedTiming::AnimationPhase_Active;
244 bool
245 Animation::IsInEffect() const
247 if (IsFinishedTransition()) {
248 return false;
251 ComputedTiming computedTiming = GetComputedTiming();
252 return computedTiming.mTimeFraction != ComputedTiming::kNullTimeFraction;
255 bool
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) {
261 return true;
264 return false;
267 void
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) {
276 return;
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.
297 continue;
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");
310 ++segment;
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) {
318 continue;
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");
326 if (!aStyleRule) {
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);
339 #ifdef DEBUG
340 bool result =
341 #endif
342 StyleAnimationValue::Interpolate(prop.mProperty,
343 segment->mFromValue,
344 segment->mToValue,
345 valuePosition, *val);
346 MOZ_ASSERT(result, "interpolate must succeed now");
350 } // namespace dom
351 } // namespace mozilla