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/. */
6
7 #include "nsSMILAnimationFunction.h"
8
9 #include "mozilla/dom/SVGAnimationElement.h"
10 #include "mozilla/Move.h"
11 #include "nsISMILAttr.h"
12 #include "nsSMILCSSValueType.h"
13 #include "nsSMILParserUtils.h"
14 #include "nsSMILNullType.h"
15 #include "nsSMILTimedElement.h"
16 #include "nsAttrValueInlines.h"
17 #include "nsGkAtoms.h"
18 #include "nsCOMPtr.h"
19 #include "nsCOMArray.h"
20 #include "nsIContent.h"
21 #include "nsContentUtils.h"
22 #include "nsReadableUtils.h"
23 #include "nsString.h"
24 #include <math.h>
25 #include <algorithm>
26
27 using namespace mozilla::dom;
28
29 //----------------------------------------------------------------------
30 // Static members
31
32 nsAttrValue::EnumTable nsSMILAnimationFunction::sAccumulateTable[] = {
33 {"none", false},
34 {"sum", true},
35 {nullptr, 0}
36 };
37
38 nsAttrValue::EnumTable nsSMILAnimationFunction::sAdditiveTable[] = {
39 {"replace", false},
40 {"sum", true},
41 {nullptr, 0}
42 };
43
44 nsAttrValue::EnumTable nsSMILAnimationFunction::sCalcModeTable[] = {
45 {"linear", CALC_LINEAR},
46 {"discrete", CALC_DISCRETE},
47 {"paced", CALC_PACED},
48 {"spline", CALC_SPLINE},
49 {nullptr, 0}
50 };
51
52 // Any negative number should be fine as a sentinel here,
53 // because valid distances are non-negative.
54 #define COMPUTE_DISTANCE_ERROR (-1)
55
56 //----------------------------------------------------------------------
57 // Constructors etc.
58
nsSMILAnimationFunction()59 nsSMILAnimationFunction::nsSMILAnimationFunction()
60 : mSampleTime(-1),
61 mRepeatIteration(0),
62 mBeginTime(INT64_MIN),
63 mAnimationElement(nullptr),
64 mErrorFlags(0),
65 mIsActive(false),
66 mIsFrozen(false),
67 mLastValue(false),
68 mHasChanged(true),
69 mValueNeedsReparsingEverySample(false),
70 mPrevSampleWasSingleValueAnimation(false),
71 mWasSkippedInPrevSample(false)
72 {
73 }
74
75 void
SetAnimationElement(SVGAnimationElement * aAnimationElement)76 nsSMILAnimationFunction::SetAnimationElement(
77 SVGAnimationElement* aAnimationElement)
78 {
79 mAnimationElement = aAnimationElement;
80 }
81
82 bool
SetAttr(nsIAtom * aAttribute,const nsAString & aValue,nsAttrValue & aResult,nsresult * aParseResult)83 nsSMILAnimationFunction::SetAttr(nsIAtom* aAttribute, const nsAString& aValue,
84 nsAttrValue& aResult, nsresult* aParseResult)
85 {
86 bool foundMatch = true;
87 nsresult parseResult = NS_OK;
88
89 // The attributes 'by', 'from', 'to', and 'values' may be parsed differently
90 // depending on the element & attribute we're animating. So instead of
91 // parsing them now we re-parse them at every sample.
92 if (aAttribute == nsGkAtoms::by ||
93 aAttribute == nsGkAtoms::from ||
94 aAttribute == nsGkAtoms::to ||
95 aAttribute == nsGkAtoms::values) {
96 // We parse to, from, by, values at sample time.
97 // XXX Need to flag which attribute has changed and then when we parse it at
98 // sample time, report any errors and reset the flag
99 mHasChanged = true;
100 aResult.SetTo(aValue);
101 } else if (aAttribute == nsGkAtoms::accumulate) {
102 parseResult = SetAccumulate(aValue, aResult);
103 } else if (aAttribute == nsGkAtoms::additive) {
104 parseResult = SetAdditive(aValue, aResult);
105 } else if (aAttribute == nsGkAtoms::calcMode) {
106 parseResult = SetCalcMode(aValue, aResult);
107 } else if (aAttribute == nsGkAtoms::keyTimes) {
108 parseResult = SetKeyTimes(aValue, aResult);
109 } else if (aAttribute == nsGkAtoms::keySplines) {
110 parseResult = SetKeySplines(aValue, aResult);
111 } else {
112 foundMatch = false;
113 }
114
115 if (foundMatch && aParseResult) {
116 *aParseResult = parseResult;
117 }
118
119 return foundMatch;
120 }
121
122 bool
UnsetAttr(nsIAtom * aAttribute)123 nsSMILAnimationFunction::UnsetAttr(nsIAtom* aAttribute)
124 {
125 bool foundMatch = true;
126
127 if (aAttribute == nsGkAtoms::by ||
128 aAttribute == nsGkAtoms::from ||
129 aAttribute == nsGkAtoms::to ||
130 aAttribute == nsGkAtoms::values) {
131 mHasChanged = true;
132 } else if (aAttribute == nsGkAtoms::accumulate) {
133 UnsetAccumulate();
134 } else if (aAttribute == nsGkAtoms::additive) {
135 UnsetAdditive();
136 } else if (aAttribute == nsGkAtoms::calcMode) {
137 UnsetCalcMode();
138 } else if (aAttribute == nsGkAtoms::keyTimes) {
139 UnsetKeyTimes();
140 } else if (aAttribute == nsGkAtoms::keySplines) {
141 UnsetKeySplines();
142 } else {
143 foundMatch = false;
144 }
145
146 return foundMatch;
147 }
148
149 void
SampleAt(nsSMILTime aSampleTime,const nsSMILTimeValue & aSimpleDuration,uint32_t aRepeatIteration)150 nsSMILAnimationFunction::SampleAt(nsSMILTime aSampleTime,
151 const nsSMILTimeValue& aSimpleDuration,
152 uint32_t aRepeatIteration)
153 {
154 // * Update mHasChanged ("Might this sample be different from prev one?")
155 // Were we previously sampling a fill="freeze" final val? (We're not anymore.)
156 mHasChanged |= mLastValue;
157
158 // Are we sampling at a new point in simple duration? And does that matter?
159 mHasChanged |=
160 (mSampleTime != aSampleTime || mSimpleDuration != aSimpleDuration) &&
161 !IsValueFixedForSimpleDuration();
162
163 // Are we on a new repeat and accumulating across repeats?
164 if (!mErrorFlags) { // (can't call GetAccumulate() if we've had parse errors)
165 mHasChanged |= (mRepeatIteration != aRepeatIteration) && GetAccumulate();
166 }
167
168 mSampleTime = aSampleTime;
169 mSimpleDuration = aSimpleDuration;
170 mRepeatIteration = aRepeatIteration;
171 mLastValue = false;
172 }
173
174 void
SampleLastValue(uint32_t aRepeatIteration)175 nsSMILAnimationFunction::SampleLastValue(uint32_t aRepeatIteration)
176 {
177 if (mHasChanged || !mLastValue || mRepeatIteration != aRepeatIteration) {
178 mHasChanged = true;
179 }
180
181 mRepeatIteration = aRepeatIteration;
182 mLastValue = true;
183 }
184
185 void
Activate(nsSMILTime aBeginTime)186 nsSMILAnimationFunction::Activate(nsSMILTime aBeginTime)
187 {
188 mBeginTime = aBeginTime;
189 mIsActive = true;
190 mIsFrozen = false;
191 mHasChanged = true;
192 }
193
194 void
Inactivate(bool aIsFrozen)195 nsSMILAnimationFunction::Inactivate(bool aIsFrozen)
196 {
197 mIsActive = false;
198 mIsFrozen = aIsFrozen;
199 mHasChanged = true;
200 }
201
202 void
ComposeResult(const nsISMILAttr & aSMILAttr,nsSMILValue & aResult)203 nsSMILAnimationFunction::ComposeResult(const nsISMILAttr& aSMILAttr,
204 nsSMILValue& aResult)
205 {
206 mHasChanged = false;
207 mPrevSampleWasSingleValueAnimation = false;
208 mWasSkippedInPrevSample = false;
209
210 // Skip animations that are inactive or in error
211 if (!IsActiveOrFrozen() || mErrorFlags != 0)
212 return;
213
214 // Get the animation values
215 nsSMILValueArray values;
216 nsresult rv = GetValues(aSMILAttr, values);
217 if (NS_FAILED(rv))
218 return;
219
220 // Check that we have the right number of keySplines and keyTimes
221 CheckValueListDependentAttrs(values.Length());
222 if (mErrorFlags != 0)
223 return;
224
225 // If this interval is active, we must have a non-negative mSampleTime
226 MOZ_ASSERT(mSampleTime >= 0 || !mIsActive,
227 "Negative sample time for active animation");
228 MOZ_ASSERT(mSimpleDuration.IsResolved() || mLastValue,
229 "Unresolved simple duration for active or frozen animation");
230
231 // If we want to add but don't have a base value then just fail outright.
232 // This can happen when we skipped getting the base value because there's an
233 // animation function in the sandwich that should replace it but that function
234 // failed unexpectedly.
235 bool isAdditive = IsAdditive();
236 if (isAdditive && aResult.IsNull())
237 return;
238
239 nsSMILValue result;
240
241 if (values.Length() == 1 && !IsToAnimation()) {
242
243 // Single-valued animation
244 result = values[0];
245 mPrevSampleWasSingleValueAnimation = true;
246
247 } else if (mLastValue) {
248
249 // Sampling last value
250 const nsSMILValue& last = values[values.Length() - 1];
251 result = last;
252
253 // See comment in AccumulateResult: to-animation does not accumulate
254 if (!IsToAnimation() && GetAccumulate() && mRepeatIteration) {
255 // If the target attribute type doesn't support addition Add will
256 // fail leaving result = last
257 result.Add(last, mRepeatIteration);
258 }
259
260 } else {
261
262 // Interpolation
263 if (NS_FAILED(InterpolateResult(values, result, aResult)))
264 return;
265
266 if (NS_FAILED(AccumulateResult(values, result)))
267 return;
268 }
269
270 // If additive animation isn't required or isn't supported, set the value.
271 if (!isAdditive || NS_FAILED(aResult.SandwichAdd(result))) {
272 aResult = Move(result);
273 }
274 }
275
276 int8_t
CompareTo(const nsSMILAnimationFunction * aOther) const277 nsSMILAnimationFunction::CompareTo(const nsSMILAnimationFunction* aOther) const
278 {
279 NS_ENSURE_TRUE(aOther, 0);
280
281 NS_ASSERTION(aOther != this, "Trying to compare to self");
282
283 // Inactive animations sort first
284 if (!IsActiveOrFrozen() && aOther->IsActiveOrFrozen())
285 return -1;
286
287 if (IsActiveOrFrozen() && !aOther->IsActiveOrFrozen())
288 return 1;
289
290 // Sort based on begin time
291 if (mBeginTime != aOther->GetBeginTime())
292 return mBeginTime > aOther->GetBeginTime() ? 1 : -1;
293
294 // Next sort based on syncbase dependencies: the dependent element sorts after
295 // its syncbase
296 const nsSMILTimedElement& thisTimedElement =
297 mAnimationElement->TimedElement();
298 const nsSMILTimedElement& otherTimedElement =
299 aOther->mAnimationElement->TimedElement();
300 if (thisTimedElement.IsTimeDependent(otherTimedElement))
301 return 1;
302 if (otherTimedElement.IsTimeDependent(thisTimedElement))
303 return -1;
304
305 // Animations that appear later in the document sort after those earlier in
306 // the document
307 MOZ_ASSERT(mAnimationElement != aOther->mAnimationElement,
308 "Two animations cannot have the same animation content element!");
309
310 return (nsContentUtils::PositionIsBefore(mAnimationElement, aOther->mAnimationElement))
311 ? -1 : 1;
312 }
313
314 bool
WillReplace() const315 nsSMILAnimationFunction::WillReplace() const
316 {
317 /*
318 * In IsAdditive() we don't consider to-animation to be additive as it is
319 * a special case that is dealt with differently in the compositing method.
320 * Here, however, we return FALSE for to-animation (i.e. it will NOT replace
321 * the underlying value) as it builds on the underlying value.
322 */
323 return !mErrorFlags && !(IsAdditive() || IsToAnimation());
324 }
325
326 bool
HasChanged() const327 nsSMILAnimationFunction::HasChanged() const
328 {
329 return mHasChanged || mValueNeedsReparsingEverySample;
330 }
331
332 bool
UpdateCachedTarget(const nsSMILTargetIdentifier & aNewTarget)333 nsSMILAnimationFunction::UpdateCachedTarget(
334 const nsSMILTargetIdentifier& aNewTarget)
335 {
336 if (!mLastTarget.Equals(aNewTarget)) {
337 mLastTarget = aNewTarget;
338 return true;
339 }
340 return false;
341 }
342
343 //----------------------------------------------------------------------
344 // Implementation helpers
345
346 nsresult
InterpolateResult(const nsSMILValueArray & aValues,nsSMILValue & aResult,nsSMILValue & aBaseValue)347 nsSMILAnimationFunction::InterpolateResult(const nsSMILValueArray& aValues,
348 nsSMILValue& aResult,
349 nsSMILValue& aBaseValue)
350 {
351 // Sanity check animation values
352 if ((!IsToAnimation() && aValues.Length() < 2) ||
353 (IsToAnimation() && aValues.Length() != 1)) {
354 NS_ERROR("Unexpected number of values");
355 return NS_ERROR_FAILURE;
356 }
357
358 if (IsToAnimation() && aBaseValue.IsNull()) {
359 return NS_ERROR_FAILURE;
360 }
361
362 // Get the normalised progress through the simple duration.
363 //
364 // If we have an indefinite simple duration, just set the progress to be
365 // 0 which will give us the expected behaviour of the animation being fixed at
366 // its starting point.
367 double simpleProgress = 0.0;
368
369 if (mSimpleDuration.IsDefinite()) {
370 nsSMILTime dur = mSimpleDuration.GetMillis();
371
372 MOZ_ASSERT(dur >= 0, "Simple duration should not be negative");
373 MOZ_ASSERT(mSampleTime >= 0, "Sample time should not be negative");
374
375 if (mSampleTime >= dur || mSampleTime < 0) {
376 NS_ERROR("Animation sampled outside interval");
377 return NS_ERROR_FAILURE;
378 }
379
380 if (dur > 0) {
381 simpleProgress = (double)mSampleTime / dur;
382 } // else leave simpleProgress at 0.0 (e.g. if mSampleTime == dur == 0)
383 }
384
385 nsresult rv = NS_OK;
386 nsSMILCalcMode calcMode = GetCalcMode();
387
388 // Force discrete calcMode for visibility since StyleAnimationValue will
389 // try to interpolate it using the special clamping behavior defined for
390 // CSS.
391 if (nsSMILCSSValueType::PropertyFromValue(aValues[0])
392 == eCSSProperty_visibility) {
393 calcMode = CALC_DISCRETE;
394 }
395
396 if (calcMode != CALC_DISCRETE) {
397 // Get the normalised progress between adjacent values
398 const nsSMILValue* from = nullptr;
399 const nsSMILValue* to = nullptr;
400 // Init to -1 to make sure that if we ever forget to set this, the
401 // MOZ_ASSERT that tests that intervalProgress is in range will fail.
402 double intervalProgress = -1.f;
403 if (IsToAnimation()) {
404 from = &aBaseValue;
405 to = &aValues[0];
406 if (calcMode == CALC_PACED) {
407 // Note: key[Times/Splines/Points] are ignored for calcMode="paced"
408 intervalProgress = simpleProgress;
409 } else {
410 double scaledSimpleProgress =
411 ScaleSimpleProgress(simpleProgress, calcMode);
412 intervalProgress = ScaleIntervalProgress(scaledSimpleProgress, 0);
413 }
414 } else if (calcMode == CALC_PACED) {
415 rv = ComputePacedPosition(aValues, simpleProgress,
416 intervalProgress, from, to);
417 // Note: If the above call fails, we'll skip the "from->Interpolate"
418 // call below, and we'll drop into the CALC_DISCRETE section
419 // instead. (as the spec says we should, because our failure was
420 // presumably due to the values being non-additive)
421 } else { // calcMode == CALC_LINEAR or calcMode == CALC_SPLINE
422 double scaledSimpleProgress =
423 ScaleSimpleProgress(simpleProgress, calcMode);
424 uint32_t index = (uint32_t)floor(scaledSimpleProgress *
425 (aValues.Length() - 1));
426 from = &aValues[index];
427 to = &aValues[index + 1];
428 intervalProgress =
429 scaledSimpleProgress * (aValues.Length() - 1) - index;
430 intervalProgress = ScaleIntervalProgress(intervalProgress, index);
431 }
432
433 if (NS_SUCCEEDED(rv)) {
434 MOZ_ASSERT(from, "NULL from-value during interpolation");
435 MOZ_ASSERT(to, "NULL to-value during interpolation");
436 MOZ_ASSERT(0.0f <= intervalProgress && intervalProgress < 1.0f,
437 "Interval progress should be in the range [0, 1)");
438 rv = from->Interpolate(*to, intervalProgress, aResult);
439 }
440 }
441
442 // Discrete-CalcMode case
443 // Note: If interpolation failed (isn't supported for this type), the SVG
444 // spec says to force discrete mode.
445 if (calcMode == CALC_DISCRETE || NS_FAILED(rv)) {
446 double scaledSimpleProgress =
447 ScaleSimpleProgress(simpleProgress, CALC_DISCRETE);
448
449 // Floating-point errors can mean that, for example, a sample time of 29s in
450 // a 100s duration animation gives us a simple progress of 0.28999999999
451 // instead of the 0.29 we'd expect. Normally this isn't a noticeable
452 // problem, but when we have sudden jumps in animation values (such as is
453 // the case here with discrete animation) we can get unexpected results.
454 //
455 // To counteract this, before we perform a floor() on the animation
456 // progress, we add a tiny fudge factor to push us into the correct interval
457 // in cases where floating-point errors might cause us to fall short.
458 static const double kFloatingPointFudgeFactor = 1.0e-16;
459 if (scaledSimpleProgress + kFloatingPointFudgeFactor <= 1.0) {
460 scaledSimpleProgress += kFloatingPointFudgeFactor;
461 }
462
463 if (IsToAnimation()) {
464 // We don't follow SMIL 3, 12.6.4, where discrete to animations
465 // are the same as <set> animations. Instead, we treat it as a
466 // discrete animation with two values (the underlying value and
467 // the to="" value), and honor keyTimes="" as well.
468 uint32_t index = (uint32_t)floor(scaledSimpleProgress * 2);
469 aResult = index == 0 ? aBaseValue : aValues[0];
470 } else {
471 uint32_t index = (uint32_t)floor(scaledSimpleProgress * aValues.Length());
472 aResult = aValues[index];
473 }
474 rv = NS_OK;
475 }
476 return rv;
477 }
478
479 nsresult
AccumulateResult(const nsSMILValueArray & aValues,nsSMILValue & aResult)480 nsSMILAnimationFunction::AccumulateResult(const nsSMILValueArray& aValues,
481 nsSMILValue& aResult)
482 {
483 if (!IsToAnimation() && GetAccumulate() && mRepeatIteration) {
484 const nsSMILValue& lastValue = aValues[aValues.Length() - 1];
485
486 // If the target attribute type doesn't support addition, Add will
487 // fail and we leave aResult untouched.
488 aResult.Add(lastValue, mRepeatIteration);
489 }
490
491 return NS_OK;
492 }
493
494 /*
495 * Given the simple progress for a paced animation, this method:
496 * - determines which two elements of the values array we're in between
497 * (returned as aFrom and aTo)
498 * - determines where we are between them
499 * (returned as aIntervalProgress)
500 *
501 * Returns NS_OK, or NS_ERROR_FAILURE if our values don't support distance
502 * computation.
503 */
504 nsresult
ComputePacedPosition(const nsSMILValueArray & aValues,double aSimpleProgress,double & aIntervalProgress,const nsSMILValue * & aFrom,const nsSMILValue * & aTo)505 nsSMILAnimationFunction::ComputePacedPosition(const nsSMILValueArray& aValues,
506 double aSimpleProgress,
507 double& aIntervalProgress,
508 const nsSMILValue*& aFrom,
509 const nsSMILValue*& aTo)
510 {
511 NS_ASSERTION(0.0f <= aSimpleProgress && aSimpleProgress < 1.0f,
512 "aSimpleProgress is out of bounds");
513 NS_ASSERTION(GetCalcMode() == CALC_PACED,
514 "Calling paced-specific function, but not in paced mode");
515 MOZ_ASSERT(aValues.Length() >= 2, "Unexpected number of values");
516
517 // Trivial case: If we have just 2 values, then there's only one interval
518 // for us to traverse, and our progress across that interval is the exact
519 // same as our overall progress.
520 if (aValues.Length() == 2) {
521 aIntervalProgress = aSimpleProgress;
522 aFrom = &aValues[0];
523 aTo = &aValues[1];
524 return NS_OK;
525 }
526
527 double totalDistance = ComputePacedTotalDistance(aValues);
528 if (totalDistance == COMPUTE_DISTANCE_ERROR)
529 return NS_ERROR_FAILURE;
530
531 // If we have 0 total distance, then it's unclear where our "paced" position
532 // should be. We can just fail, which drops us into discrete animation mode.
533 // (That's fine, since our values are apparently indistinguishable anyway.)
534 if (totalDistance == 0.0) {
535 return NS_ERROR_FAILURE;
536 }
537
538 // total distance we should have moved at this point in time.
539 // (called 'remainingDist' due to how it's used in loop below)
540 double remainingDist = aSimpleProgress * totalDistance;
541
542 // Must be satisfied, because totalDistance is a sum of (non-negative)
543 // distances, and aSimpleProgress is non-negative
544 NS_ASSERTION(remainingDist >= 0, "distance values must be non-negative");
545
546 // Find where remainingDist puts us in the list of values
547 // Note: We could optimize this next loop by caching the
548 // interval-distances in an array, but maybe that's excessive.
549 for (uint32_t i = 0; i < aValues.Length() - 1; i++) {
550 // Note: The following assertion is valid because remainingDist should
551 // start out non-negative, and this loop never shaves off more than its
552 // current value.
553 NS_ASSERTION(remainingDist >= 0, "distance values must be non-negative");
554
555 double curIntervalDist;
556
557 #ifdef DEBUG
558 nsresult rv =
559 #endif
560 aValues[i].ComputeDistance(aValues[i+1], curIntervalDist);
561 MOZ_ASSERT(NS_SUCCEEDED(rv),
562 "If we got through ComputePacedTotalDistance, we should "
563 "be able to recompute each sub-distance without errors");
564
565 NS_ASSERTION(curIntervalDist >= 0, "distance values must be non-negative");
566 // Clamp distance value at 0, just in case ComputeDistance is evil.
567 curIntervalDist = std::max(curIntervalDist, 0.0);
568
569 if (remainingDist >= curIntervalDist) {
570 remainingDist -= curIntervalDist;
571 } else {
572 // NOTE: If we get here, then curIntervalDist necessarily is not 0. Why?
573 // Because this clause is only hit when remainingDist < curIntervalDist,
574 // and if curIntervalDist were 0, that would mean remainingDist would
575 // have to be < 0. But that can't happen, because remainingDist (as
576 // a distance) is non-negative by definition.
577 NS_ASSERTION(curIntervalDist != 0,
578 "We should never get here with this set to 0...");
579
580 // We found the right spot -- an interpolated position between
581 // values i and i+1.
582 aFrom = &aValues[i];
583 aTo = &aValues[i+1];
584 aIntervalProgress = remainingDist / curIntervalDist;
585 return NS_OK;
586 }
587 }
588
589 NS_NOTREACHED("shouldn't complete loop & get here -- if we do, "
590 "then aSimpleProgress was probably out of bounds");
591 return NS_ERROR_FAILURE;
592 }
593
594 /*
595 * Computes the total distance to be travelled by a paced animation.
596 *
597 * Returns the total distance, or returns COMPUTE_DISTANCE_ERROR if
598 * our values don't support distance computation.
599 */
600 double
ComputePacedTotalDistance(const nsSMILValueArray & aValues) const601 nsSMILAnimationFunction::ComputePacedTotalDistance(
602 const nsSMILValueArray& aValues) const
603 {
604 NS_ASSERTION(GetCalcMode() == CALC_PACED,
605 "Calling paced-specific function, but not in paced mode");
606
607 double totalDistance = 0.0;
608 for (uint32_t i = 0; i < aValues.Length() - 1; i++) {
609 double tmpDist;
610 nsresult rv = aValues[i].ComputeDistance(aValues[i+1], tmpDist);
611 if (NS_FAILED(rv)) {
612 return COMPUTE_DISTANCE_ERROR;
613 }
614
615 // Clamp distance value to 0, just in case we have an evil ComputeDistance
616 // implementation somewhere
617 MOZ_ASSERT(tmpDist >= 0.0f, "distance values must be non-negative");
618 tmpDist = std::max(tmpDist, 0.0);
619
620 totalDistance += tmpDist;
621 }
622
623 return totalDistance;
624 }
625
626 double
ScaleSimpleProgress(double aProgress,nsSMILCalcMode aCalcMode)627 nsSMILAnimationFunction::ScaleSimpleProgress(double aProgress,
628 nsSMILCalcMode aCalcMode)
629 {
630 if (!HasAttr(nsGkAtoms::keyTimes))
631 return aProgress;
632
633 uint32_t numTimes = mKeyTimes.Length();
634
635 if (numTimes < 2)
636 return aProgress;
637
638 uint32_t i = 0;
639 for (; i < numTimes - 2 && aProgress >= mKeyTimes[i+1]; ++i) { }
640
641 if (aCalcMode == CALC_DISCRETE) {
642 // discrete calcMode behaviour differs in that each keyTime defines the time
643 // from when the corresponding value is set, and therefore the last value
644 // needn't be 1. So check if we're in the last 'interval', that is, the
645 // space between the final value and 1.0.
646 if (aProgress >= mKeyTimes[i+1]) {
647 MOZ_ASSERT(i == numTimes - 2,
648 "aProgress is not in range of the current interval, yet the "
649 "current interval is not the last bounded interval either.");
650 ++i;
651 }
652 return (double)i / numTimes;
653 }
654
655 double& intervalStart = mKeyTimes[i];
656 double& intervalEnd = mKeyTimes[i+1];
657
658 double intervalLength = intervalEnd - intervalStart;
659 if (intervalLength <= 0.0)
660 return intervalStart;
661
662 return (i + (aProgress - intervalStart) / intervalLength) /
663 double(numTimes - 1);
664 }
665
666 double
ScaleIntervalProgress(double aProgress,uint32_t aIntervalIndex)667 nsSMILAnimationFunction::ScaleIntervalProgress(double aProgress,
668 uint32_t aIntervalIndex)
669 {
670 if (GetCalcMode() != CALC_SPLINE)
671 return aProgress;
672
673 if (!HasAttr(nsGkAtoms::keySplines))
674 return aProgress;
675
676 MOZ_ASSERT(aIntervalIndex < mKeySplines.Length(),
677 "Invalid interval index");
678
679 nsSMILKeySpline const &spline = mKeySplines[aIntervalIndex];
680 return spline.GetSplineValue(aProgress);
681 }
682
683 bool
HasAttr(nsIAtom * aAttName) const684 nsSMILAnimationFunction::HasAttr(nsIAtom* aAttName) const
685 {
686 return mAnimationElement->HasAnimAttr(aAttName);
687 }
688
689 const nsAttrValue*
GetAttr(nsIAtom * aAttName) const690 nsSMILAnimationFunction::GetAttr(nsIAtom* aAttName) const
691 {
692 return mAnimationElement->GetAnimAttr(aAttName);
693 }
694
695 bool
GetAttr(nsIAtom * aAttName,nsAString & aResult) const696 nsSMILAnimationFunction::GetAttr(nsIAtom* aAttName, nsAString& aResult) const
697 {
698 return mAnimationElement->GetAnimAttr(aAttName, aResult);
699 }
700
701 /*
702 * A utility function to make querying an attribute that corresponds to an
703 * nsSMILValue a little neater.
704 *
705 * @param aAttName The attribute name (in the global namespace).
706 * @param aSMILAttr The SMIL attribute to perform the parsing.
707 * @param[out] aResult The resulting nsSMILValue.
708 * @param[out] aPreventCachingOfSandwich
709 * If |aResult| contains dependencies on its context that
710 * should prevent the result of the animation sandwich from
711 * being cached and reused in future samples (as reported
712 * by nsISMILAttr::ValueFromString), then this outparam
713 * will be set to true. Otherwise it is left unmodified.
714 *
715 * Returns false if a parse error occurred, otherwise returns true.
716 */
717 bool
ParseAttr(nsIAtom * aAttName,const nsISMILAttr & aSMILAttr,nsSMILValue & aResult,bool & aPreventCachingOfSandwich) const718 nsSMILAnimationFunction::ParseAttr(nsIAtom* aAttName,
719 const nsISMILAttr& aSMILAttr,
720 nsSMILValue& aResult,
721 bool& aPreventCachingOfSandwich) const
722 {
723 nsAutoString attValue;
724 if (GetAttr(aAttName, attValue)) {
725 bool preventCachingOfSandwich = false;
726 nsresult rv = aSMILAttr.ValueFromString(attValue, mAnimationElement,
727 aResult, preventCachingOfSandwich);
728 if (NS_FAILED(rv))
729 return false;
730
731 if (preventCachingOfSandwich) {
732 aPreventCachingOfSandwich = true;
733 }
734 }
735 return true;
736 }
737
738 /*
739 * SMILANIM specifies the following rules for animation function values:
740 *
741 * (1) if values is set, it overrides everything
742 * (2) for from/to/by animation at least to or by must be specified, from on its
743 * own (or nothing) is an error--which we will ignore
744 * (3) if both by and to are specified only to will be used, by will be ignored
745 * (4) if by is specified without from (by animation), forces additive behaviour
746 * (5) if to is specified without from (to animation), special care needs to be
747 * taken when compositing animation as such animations are composited last.
748 *
749 * This helper method applies these rules to fill in the values list and to set
750 * some internal state.
751 */
752 nsresult
GetValues(const nsISMILAttr & aSMILAttr,nsSMILValueArray & aResult)753 nsSMILAnimationFunction::GetValues(const nsISMILAttr& aSMILAttr,
754 nsSMILValueArray& aResult)
755 {
756 if (!mAnimationElement)
757 return NS_ERROR_FAILURE;
758
759 mValueNeedsReparsingEverySample = false;
760 nsSMILValueArray result;
761
762 // If "values" is set, use it
763 if (HasAttr(nsGkAtoms::values)) {
764 nsAutoString attValue;
765 GetAttr(nsGkAtoms::values, attValue);
766 bool preventCachingOfSandwich = false;
767 if (!nsSMILParserUtils::ParseValues(attValue, mAnimationElement,
768 aSMILAttr, result,
769 preventCachingOfSandwich)) {
770 return NS_ERROR_FAILURE;
771 }
772
773 if (preventCachingOfSandwich) {
774 mValueNeedsReparsingEverySample = true;
775 }
776 // Else try to/from/by
777 } else {
778 bool preventCachingOfSandwich = false;
779 bool parseOk = true;
780 nsSMILValue to, from, by;
781 parseOk &= ParseAttr(nsGkAtoms::to, aSMILAttr, to,
782 preventCachingOfSandwich);
783 parseOk &= ParseAttr(nsGkAtoms::from, aSMILAttr, from,
784 preventCachingOfSandwich);
785 parseOk &= ParseAttr(nsGkAtoms::by, aSMILAttr, by,
786 preventCachingOfSandwich);
787
788 if (preventCachingOfSandwich) {
789 mValueNeedsReparsingEverySample = true;
790 }
791
792 if (!parseOk || !result.SetCapacity(2, mozilla::fallible)) {
793 return NS_ERROR_FAILURE;
794 }
795
796 // AppendElement() below must succeed, because SetCapacity() succeeded.
797 if (!to.IsNull()) {
798 if (!from.IsNull()) {
799 MOZ_ALWAYS_TRUE(result.AppendElement(from, mozilla::fallible));
800 MOZ_ALWAYS_TRUE(result.AppendElement(to, mozilla::fallible));
801 } else {
802 MOZ_ALWAYS_TRUE(result.AppendElement(to, mozilla::fallible));
803 }
804 } else if (!by.IsNull()) {
805 nsSMILValue effectiveFrom(by.mType);
806 if (!from.IsNull())
807 effectiveFrom = from;
808 // Set values to 'from; from + by'
809 MOZ_ALWAYS_TRUE(result.AppendElement(effectiveFrom, mozilla::fallible));
810 nsSMILValue effectiveTo(effectiveFrom);
811 if (!effectiveTo.IsNull() && NS_SUCCEEDED(effectiveTo.Add(by))) {
812 MOZ_ALWAYS_TRUE(result.AppendElement(effectiveTo, mozilla::fallible));
813 } else {
814 // Using by-animation with non-additive type or bad base-value
815 return NS_ERROR_FAILURE;
816 }
817 } else {
818 // No values, no to, no by -- call it a day
819 return NS_ERROR_FAILURE;
820 }
821 }
822
823 result.SwapElements(aResult);
824
825 return NS_OK;
826 }
827
828 void
CheckValueListDependentAttrs(uint32_t aNumValues)829 nsSMILAnimationFunction::CheckValueListDependentAttrs(uint32_t aNumValues)
830 {
831 CheckKeyTimes(aNumValues);
832 CheckKeySplines(aNumValues);
833 }
834
835 /**
836 * Performs checks for the keyTimes attribute required by the SMIL spec but
837 * which depend on other attributes and therefore needs to be updated as
838 * dependent attributes are set.
839 */
840 void
CheckKeyTimes(uint32_t aNumValues)841 nsSMILAnimationFunction::CheckKeyTimes(uint32_t aNumValues)
842 {
843 if (!HasAttr(nsGkAtoms::keyTimes))
844 return;
845
846 nsSMILCalcMode calcMode = GetCalcMode();
847
848 // attribute is ignored for calcMode = paced
849 if (calcMode == CALC_PACED) {
850 SetKeyTimesErrorFlag(false);
851 return;
852 }
853
854 uint32_t numKeyTimes = mKeyTimes.Length();
855 if (numKeyTimes < 1) {
856 // keyTimes isn't set or failed preliminary checks
857 SetKeyTimesErrorFlag(true);
858 return;
859 }
860
861 // no. keyTimes == no. values
862 // For to-animation the number of values is considered to be 2.
863 bool matchingNumOfValues =
864 numKeyTimes == (IsToAnimation() ? 2 : aNumValues);
865 if (!matchingNumOfValues) {
866 SetKeyTimesErrorFlag(true);
867 return;
868 }
869
870 // first value must be 0
871 if (mKeyTimes[0] != 0.0) {
872 SetKeyTimesErrorFlag(true);
873 return;
874 }
875
876 // last value must be 1 for linear or spline calcModes
877 if (calcMode != CALC_DISCRETE && numKeyTimes > 1 &&
878 mKeyTimes[numKeyTimes - 1] != 1.0) {
879 SetKeyTimesErrorFlag(true);
880 return;
881 }
882
883 SetKeyTimesErrorFlag(false);
884 }
885
886 void
CheckKeySplines(uint32_t aNumValues)887 nsSMILAnimationFunction::CheckKeySplines(uint32_t aNumValues)
888 {
889 // attribute is ignored if calc mode is not spline
890 if (GetCalcMode() != CALC_SPLINE) {
891 SetKeySplinesErrorFlag(false);
892 return;
893 }
894
895 // calc mode is spline but the attribute is not set
896 if (!HasAttr(nsGkAtoms::keySplines)) {
897 SetKeySplinesErrorFlag(false);
898 return;
899 }
900
901 if (mKeySplines.Length() < 1) {
902 // keyTimes isn't set or failed preliminary checks
903 SetKeySplinesErrorFlag(true);
904 return;
905 }
906
907 // ignore splines if there's only one value
908 if (aNumValues == 1 && !IsToAnimation()) {
909 SetKeySplinesErrorFlag(false);
910 return;
911 }
912
913 // no. keySpline specs == no. values - 1
914 uint32_t splineSpecs = mKeySplines.Length();
915 if ((splineSpecs != aNumValues - 1 && !IsToAnimation()) ||
916 (IsToAnimation() && splineSpecs != 1)) {
917 SetKeySplinesErrorFlag(true);
918 return;
919 }
920
921 SetKeySplinesErrorFlag(false);
922 }
923
924 bool
IsValueFixedForSimpleDuration() const925 nsSMILAnimationFunction::IsValueFixedForSimpleDuration() const
926 {
927 return mSimpleDuration.IsIndefinite() ||
928 (!mHasChanged && mPrevSampleWasSingleValueAnimation);
929 }
930
931 //----------------------------------------------------------------------
932 // Property getters
933
934 bool
GetAccumulate() const935 nsSMILAnimationFunction::GetAccumulate() const
936 {
937 const nsAttrValue* value = GetAttr(nsGkAtoms::accumulate);
938 if (!value)
939 return false;
940
941 return value->GetEnumValue();
942 }
943
944 bool
GetAdditive() const945 nsSMILAnimationFunction::GetAdditive() const
946 {
947 const nsAttrValue* value = GetAttr(nsGkAtoms::additive);
948 if (!value)
949 return false;
950
951 return value->GetEnumValue();
952 }
953
954 nsSMILAnimationFunction::nsSMILCalcMode
GetCalcMode() const955 nsSMILAnimationFunction::GetCalcMode() const
956 {
957 const nsAttrValue* value = GetAttr(nsGkAtoms::calcMode);
958 if (!value)
959 return CALC_LINEAR;
960
961 return nsSMILCalcMode(value->GetEnumValue());
962 }
963
964 //----------------------------------------------------------------------
965 // Property setters / un-setters:
966
967 nsresult
SetAccumulate(const nsAString & aAccumulate,nsAttrValue & aResult)968 nsSMILAnimationFunction::SetAccumulate(const nsAString& aAccumulate,
969 nsAttrValue& aResult)
970 {
971 mHasChanged = true;
972 bool parseResult =
973 aResult.ParseEnumValue(aAccumulate, sAccumulateTable, true);
974 SetAccumulateErrorFlag(!parseResult);
975 return parseResult ? NS_OK : NS_ERROR_FAILURE;
976 }
977
978 void
UnsetAccumulate()979 nsSMILAnimationFunction::UnsetAccumulate()
980 {
981 SetAccumulateErrorFlag(false);
982 mHasChanged = true;
983 }
984
985 nsresult
SetAdditive(const nsAString & aAdditive,nsAttrValue & aResult)986 nsSMILAnimationFunction::SetAdditive(const nsAString& aAdditive,
987 nsAttrValue& aResult)
988 {
989 mHasChanged = true;
990 bool parseResult
991 = aResult.ParseEnumValue(aAdditive, sAdditiveTable, true);
992 SetAdditiveErrorFlag(!parseResult);
993 return parseResult ? NS_OK : NS_ERROR_FAILURE;
994 }
995
996 void
UnsetAdditive()997 nsSMILAnimationFunction::UnsetAdditive()
998 {
999 SetAdditiveErrorFlag(false);
1000 mHasChanged = true;
1001 }
1002
1003 nsresult
SetCalcMode(const nsAString & aCalcMode,nsAttrValue & aResult)1004 nsSMILAnimationFunction::SetCalcMode(const nsAString& aCalcMode,
1005 nsAttrValue& aResult)
1006 {
1007 mHasChanged = true;
1008 bool parseResult
1009 = aResult.ParseEnumValue(aCalcMode, sCalcModeTable, true);
1010 SetCalcModeErrorFlag(!parseResult);
1011 return parseResult ? NS_OK : NS_ERROR_FAILURE;
1012 }
1013
1014 void
UnsetCalcMode()1015 nsSMILAnimationFunction::UnsetCalcMode()
1016 {
1017 SetCalcModeErrorFlag(false);
1018 mHasChanged = true;
1019 }
1020
1021 nsresult
SetKeySplines(const nsAString & aKeySplines,nsAttrValue & aResult)1022 nsSMILAnimationFunction::SetKeySplines(const nsAString& aKeySplines,
1023 nsAttrValue& aResult)
1024 {
1025 mKeySplines.Clear();
1026 aResult.SetTo(aKeySplines);
1027
1028 mHasChanged = true;
1029
1030 if (!nsSMILParserUtils::ParseKeySplines(aKeySplines, mKeySplines)) {
1031 mKeySplines.Clear();
1032 return NS_ERROR_FAILURE;
1033 }
1034
1035 return NS_OK;
1036 }
1037
1038 void
UnsetKeySplines()1039 nsSMILAnimationFunction::UnsetKeySplines()
1040 {
1041 mKeySplines.Clear();
1042 SetKeySplinesErrorFlag(false);
1043 mHasChanged = true;
1044 }
1045
1046 nsresult
SetKeyTimes(const nsAString & aKeyTimes,nsAttrValue & aResult)1047 nsSMILAnimationFunction::SetKeyTimes(const nsAString& aKeyTimes,
1048 nsAttrValue& aResult)
1049 {
1050 mKeyTimes.Clear();
1051 aResult.SetTo(aKeyTimes);
1052
1053 mHasChanged = true;
1054
1055 if (!nsSMILParserUtils::ParseSemicolonDelimitedProgressList(aKeyTimes, true,
1056 mKeyTimes)) {
1057 mKeyTimes.Clear();
1058 return NS_ERROR_FAILURE;
1059 }
1060
1061 return NS_OK;
1062 }
1063
1064 void
UnsetKeyTimes()1065 nsSMILAnimationFunction::UnsetKeyTimes()
1066 {
1067 mKeyTimes.Clear();
1068 SetKeyTimesErrorFlag(false);
1069 mHasChanged = true;
1070 }
1071