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 "SMILAnimationFunction.h"
14 #include "mozilla/DebugOnly.h"
15 #include "mozilla/SMILAttr.h"
16 #include "mozilla/SMILCSSValueType.h"
17 #include "mozilla/SMILNullType.h"
18 #include "mozilla/SMILParserUtils.h"
19 #include "mozilla/SMILTimedElement.h"
20 #include "mozilla/dom/SVGAnimationElement.h"
21 #include "nsAttrValueInlines.h"
22 #include "nsCOMArray.h"
24 #include "nsContentUtils.h"
25 #include "nsGkAtoms.h"
26 #include "nsIContent.h"
27 #include "nsReadableUtils.h"
30 using namespace mozilla::dom
;
34 //----------------------------------------------------------------------
37 nsAttrValue::EnumTable
SMILAnimationFunction::sAccumulateTable
[] = {
38 {"none", false}, {"sum", true}, {nullptr, 0}};
40 nsAttrValue::EnumTable
SMILAnimationFunction::sAdditiveTable
[] = {
41 {"replace", false}, {"sum", true}, {nullptr, 0}};
43 nsAttrValue::EnumTable
SMILAnimationFunction::sCalcModeTable
[] = {
44 {"linear", CALC_LINEAR
},
45 {"discrete", CALC_DISCRETE
},
46 {"paced", CALC_PACED
},
47 {"spline", CALC_SPLINE
},
50 // Any negative number should be fine as a sentinel here,
51 // because valid distances are non-negative.
52 #define COMPUTE_DISTANCE_ERROR (-1)
54 //----------------------------------------------------------------------
57 SMILAnimationFunction::SMILAnimationFunction()
60 mBeginTime(INT64_MIN
),
61 mAnimationElement(nullptr),
67 mValueNeedsReparsingEverySample(false),
68 mPrevSampleWasSingleValueAnimation(false),
69 mWasSkippedInPrevSample(false) {}
71 void SMILAnimationFunction::SetAnimationElement(
72 SVGAnimationElement
* aAnimationElement
) {
73 mAnimationElement
= aAnimationElement
;
76 bool SMILAnimationFunction::SetAttr(nsAtom
* aAttribute
, const nsAString
& aValue
,
78 nsresult
* aParseResult
) {
79 // Some elements such as set and discard don't support all possible attributes
80 if (IsDisallowedAttribute(aAttribute
)) {
81 aResult
.SetTo(aValue
);
83 *aParseResult
= NS_OK
;
88 bool foundMatch
= true;
89 nsresult parseResult
= NS_OK
;
91 // The attributes 'by', 'from', 'to', and 'values' may be parsed differently
92 // depending on the element & attribute we're animating. So instead of
93 // parsing them now we re-parse them at every sample.
94 if (aAttribute
== nsGkAtoms::by
|| aAttribute
== nsGkAtoms::from
||
95 aAttribute
== nsGkAtoms::to
|| 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
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
);
115 if (foundMatch
&& aParseResult
) {
116 *aParseResult
= parseResult
;
122 bool SMILAnimationFunction::UnsetAttr(nsAtom
* aAttribute
) {
123 if (IsDisallowedAttribute(aAttribute
)) {
127 bool foundMatch
= true;
129 if (aAttribute
== nsGkAtoms::by
|| aAttribute
== nsGkAtoms::from
||
130 aAttribute
== nsGkAtoms::to
|| aAttribute
== nsGkAtoms::values
) {
132 } else if (aAttribute
== nsGkAtoms::accumulate
) {
134 } else if (aAttribute
== nsGkAtoms::additive
) {
136 } else if (aAttribute
== nsGkAtoms::calcMode
) {
138 } else if (aAttribute
== nsGkAtoms::keyTimes
) {
140 } else if (aAttribute
== nsGkAtoms::keySplines
) {
149 void SMILAnimationFunction::SampleAt(SMILTime aSampleTime
,
150 const SMILTimeValue
& aSimpleDuration
,
151 uint32_t aRepeatIteration
) {
152 // * Update mHasChanged ("Might this sample be different from prev one?")
153 // Were we previously sampling a fill="freeze" final val? (We're not anymore.)
154 mHasChanged
|= mLastValue
;
156 // Are we sampling at a new point in simple duration? And does that matter?
158 (mSampleTime
!= aSampleTime
|| mSimpleDuration
!= aSimpleDuration
) &&
159 !IsValueFixedForSimpleDuration();
161 // Are we on a new repeat and accumulating across repeats?
162 if (!mErrorFlags
) { // (can't call GetAccumulate() if we've had parse errors)
163 mHasChanged
|= (mRepeatIteration
!= aRepeatIteration
) && GetAccumulate();
166 mSampleTime
= aSampleTime
;
167 mSimpleDuration
= aSimpleDuration
;
168 mRepeatIteration
= aRepeatIteration
;
172 void SMILAnimationFunction::SampleLastValue(uint32_t aRepeatIteration
) {
173 if (!mLastValue
|| mRepeatIteration
!= aRepeatIteration
) {
177 mRepeatIteration
= aRepeatIteration
;
181 void SMILAnimationFunction::Activate(SMILTime aBeginTime
) {
182 mBeginTime
= aBeginTime
;
188 void SMILAnimationFunction::Inactivate(bool aIsFrozen
) {
190 mIsFrozen
= aIsFrozen
;
194 void SMILAnimationFunction::ComposeResult(const SMILAttr
& aSMILAttr
,
195 SMILValue
& aResult
) {
197 mPrevSampleWasSingleValueAnimation
= false;
198 mWasSkippedInPrevSample
= false;
200 // Skip animations that are inactive or in error
201 if (!IsActiveOrFrozen() || mErrorFlags
!= 0) return;
203 // Get the animation values
204 SMILValueArray values
;
205 nsresult rv
= GetValues(aSMILAttr
, values
);
206 if (NS_FAILED(rv
)) return;
208 // Check that we have the right number of keySplines and keyTimes
209 CheckValueListDependentAttrs(values
.Length());
210 if (mErrorFlags
!= 0) return;
212 // If this interval is active, we must have a non-negative mSampleTime
213 MOZ_ASSERT(mSampleTime
>= 0 || !mIsActive
,
214 "Negative sample time for active animation");
215 MOZ_ASSERT(mSimpleDuration
.IsResolved() || mLastValue
,
216 "Unresolved simple duration for active or frozen animation");
218 // If we want to add but don't have a base value then just fail outright.
219 // This can happen when we skipped getting the base value because there's an
220 // animation function in the sandwich that should replace it but that function
221 // failed unexpectedly.
222 bool isAdditive
= IsAdditive();
223 if (isAdditive
&& aResult
.IsNull()) return;
227 if (values
.Length() == 1 && !IsToAnimation()) {
228 // Single-valued animation
230 mPrevSampleWasSingleValueAnimation
= true;
232 } else if (mLastValue
) {
233 // Sampling last value
234 const SMILValue
& last
= values
.LastElement();
237 // See comment in AccumulateResult: to-animation does not accumulate
238 if (!IsToAnimation() && GetAccumulate() && mRepeatIteration
) {
239 // If the target attribute type doesn't support addition Add will
240 // fail leaving result = last
241 result
.Add(last
, mRepeatIteration
);
246 if (NS_FAILED(InterpolateResult(values
, result
, aResult
))) return;
248 if (NS_FAILED(AccumulateResult(values
, result
))) return;
251 // If additive animation isn't required or isn't supported, set the value.
252 if (!isAdditive
|| NS_FAILED(aResult
.SandwichAdd(result
))) {
253 aResult
= std::move(result
);
257 int8_t SMILAnimationFunction::CompareTo(
258 const SMILAnimationFunction
* aOther
) const {
259 NS_ENSURE_TRUE(aOther
, 0);
261 NS_ASSERTION(aOther
!= this, "Trying to compare to self");
263 // Inactive animations sort first
264 if (!IsActiveOrFrozen() && aOther
->IsActiveOrFrozen()) return -1;
266 if (IsActiveOrFrozen() && !aOther
->IsActiveOrFrozen()) return 1;
268 // Sort based on begin time
269 if (mBeginTime
!= aOther
->GetBeginTime())
270 return mBeginTime
> aOther
->GetBeginTime() ? 1 : -1;
272 // Next sort based on syncbase dependencies: the dependent element sorts after
274 const SMILTimedElement
& thisTimedElement
= mAnimationElement
->TimedElement();
275 const SMILTimedElement
& otherTimedElement
=
276 aOther
->mAnimationElement
->TimedElement();
277 if (thisTimedElement
.IsTimeDependent(otherTimedElement
)) return 1;
278 if (otherTimedElement
.IsTimeDependent(thisTimedElement
)) return -1;
280 // Animations that appear later in the document sort after those earlier in
282 MOZ_ASSERT(mAnimationElement
!= aOther
->mAnimationElement
,
283 "Two animations cannot have the same animation content element!");
285 return (nsContentUtils::PositionIsBefore(mAnimationElement
,
286 aOther
->mAnimationElement
))
291 bool SMILAnimationFunction::WillReplace() const {
293 * In IsAdditive() we don't consider to-animation to be additive as it is
294 * a special case that is dealt with differently in the compositing method.
295 * Here, however, we return FALSE for to-animation (i.e. it will NOT replace
296 * the underlying value) as it builds on the underlying value.
298 return !mErrorFlags
&& !(IsAdditive() || IsToAnimation());
301 bool SMILAnimationFunction::HasChanged() const {
302 return mHasChanged
|| mValueNeedsReparsingEverySample
;
305 bool SMILAnimationFunction::UpdateCachedTarget(
306 const SMILTargetIdentifier
& aNewTarget
) {
307 if (!mLastTarget
.Equals(aNewTarget
)) {
308 mLastTarget
= aNewTarget
;
314 //----------------------------------------------------------------------
315 // Implementation helpers
317 nsresult
SMILAnimationFunction::InterpolateResult(const SMILValueArray
& aValues
,
319 SMILValue
& aBaseValue
) {
320 // Sanity check animation values
321 if ((!IsToAnimation() && aValues
.Length() < 2) ||
322 (IsToAnimation() && aValues
.Length() != 1)) {
323 NS_ERROR("Unexpected number of values");
324 return NS_ERROR_FAILURE
;
327 if (IsToAnimation() && aBaseValue
.IsNull()) {
328 return NS_ERROR_FAILURE
;
331 // Get the normalised progress through the simple duration.
333 // If we have an indefinite simple duration, just set the progress to be
334 // 0 which will give us the expected behaviour of the animation being fixed at
335 // its starting point.
336 double simpleProgress
= 0.0;
338 if (mSimpleDuration
.IsDefinite()) {
339 SMILTime dur
= mSimpleDuration
.GetMillis();
341 MOZ_ASSERT(dur
>= 0, "Simple duration should not be negative");
342 MOZ_ASSERT(mSampleTime
>= 0, "Sample time should not be negative");
344 if (mSampleTime
>= dur
|| mSampleTime
< 0) {
345 NS_ERROR("Animation sampled outside interval");
346 return NS_ERROR_FAILURE
;
350 simpleProgress
= (double)mSampleTime
/ dur
;
351 } // else leave simpleProgress at 0.0 (e.g. if mSampleTime == dur == 0)
355 SMILCalcMode calcMode
= GetCalcMode();
357 // Force discrete calcMode for visibility since StyleAnimationValue will
358 // try to interpolate it using the special clamping behavior defined for
360 if (SMILCSSValueType::PropertyFromValue(aValues
[0]) ==
361 eCSSProperty_visibility
) {
362 calcMode
= CALC_DISCRETE
;
365 if (calcMode
!= CALC_DISCRETE
) {
366 // Get the normalised progress between adjacent values
367 const SMILValue
* from
= nullptr;
368 const SMILValue
* to
= nullptr;
369 // Init to -1 to make sure that if we ever forget to set this, the
370 // MOZ_ASSERT that tests that intervalProgress is in range will fail.
371 double intervalProgress
= -1.f
;
372 if (IsToAnimation()) {
375 if (calcMode
== CALC_PACED
) {
376 // Note: key[Times/Splines/Points] are ignored for calcMode="paced"
377 intervalProgress
= simpleProgress
;
379 double scaledSimpleProgress
=
380 ScaleSimpleProgress(simpleProgress
, calcMode
);
381 intervalProgress
= ScaleIntervalProgress(scaledSimpleProgress
, 0);
383 } else if (calcMode
== CALC_PACED
) {
384 rv
= ComputePacedPosition(aValues
, simpleProgress
, intervalProgress
, from
,
386 // Note: If the above call fails, we'll skip the "from->Interpolate"
387 // call below, and we'll drop into the CALC_DISCRETE section
388 // instead. (as the spec says we should, because our failure was
389 // presumably due to the values being non-additive)
390 } else { // calcMode == CALC_LINEAR or calcMode == CALC_SPLINE
391 double scaledSimpleProgress
=
392 ScaleSimpleProgress(simpleProgress
, calcMode
);
394 (uint32_t)floor(scaledSimpleProgress
* (aValues
.Length() - 1));
395 from
= &aValues
[index
];
396 to
= &aValues
[index
+ 1];
397 intervalProgress
= scaledSimpleProgress
* (aValues
.Length() - 1) - index
;
398 intervalProgress
= ScaleIntervalProgress(intervalProgress
, index
);
401 if (NS_SUCCEEDED(rv
)) {
402 MOZ_ASSERT(from
, "NULL from-value during interpolation");
403 MOZ_ASSERT(to
, "NULL to-value during interpolation");
404 MOZ_ASSERT(0.0f
<= intervalProgress
&& intervalProgress
< 1.0f
,
405 "Interval progress should be in the range [0, 1)");
406 rv
= from
->Interpolate(*to
, intervalProgress
, aResult
);
410 // Discrete-CalcMode case
411 // Note: If interpolation failed (isn't supported for this type), the SVG
412 // spec says to force discrete mode.
413 if (calcMode
== CALC_DISCRETE
|| NS_FAILED(rv
)) {
414 double scaledSimpleProgress
=
415 ScaleSimpleProgress(simpleProgress
, CALC_DISCRETE
);
417 // Floating-point errors can mean that, for example, a sample time of 29s in
418 // a 100s duration animation gives us a simple progress of 0.28999999999
419 // instead of the 0.29 we'd expect. Normally this isn't a noticeable
420 // problem, but when we have sudden jumps in animation values (such as is
421 // the case here with discrete animation) we can get unexpected results.
423 // To counteract this, before we perform a floor() on the animation
424 // progress, we add a tiny fudge factor to push us into the correct interval
425 // in cases where floating-point errors might cause us to fall short.
426 static const double kFloatingPointFudgeFactor
= 1.0e-16;
427 if (scaledSimpleProgress
+ kFloatingPointFudgeFactor
<= 1.0) {
428 scaledSimpleProgress
+= kFloatingPointFudgeFactor
;
431 if (IsToAnimation()) {
432 // We don't follow SMIL 3, 12.6.4, where discrete to animations
433 // are the same as <set> animations. Instead, we treat it as a
434 // discrete animation with two values (the underlying value and
435 // the to="" value), and honor keyTimes="" as well.
436 uint32_t index
= (uint32_t)floor(scaledSimpleProgress
* 2);
437 aResult
= index
== 0 ? aBaseValue
: aValues
[0];
439 uint32_t index
= (uint32_t)floor(scaledSimpleProgress
* aValues
.Length());
440 aResult
= aValues
[index
];
442 // For animation of CSS properties, normally when interpolating we perform
443 // a zero-value fixup which means that empty values (values with type
444 // SMILCSSValueType but a null pointer value) are converted into
445 // a suitable zero value based on whatever they're being interpolated
446 // with. For discrete animation, however, since we don't interpolate,
447 // that never happens. In some rare cases, such as discrete non-additive
448 // by-animation, we can arrive here with |aResult| being such an empty
449 // value so we need to manually perform the fixup.
451 // We could define a generic method for this on SMILValue but its faster
452 // and simpler to just special case SMILCSSValueType.
453 if (aResult
.mType
== &SMILCSSValueType::sSingleton
) {
454 // We have currently only ever encountered this case for the first
455 // value of a by-animation (which has two values) and since we have no
456 // way of testing other cases we just skip them (but assert if we
457 // ever do encounter them so that we can add code to handle them).
458 if (index
+ 1 >= aValues
.Length()) {
459 MOZ_ASSERT(aResult
.mU
.mPtr
, "The last value should not be empty");
461 // Base the type of the zero value on the next element in the series.
462 SMILCSSValueType::FinalizeValue(aResult
, aValues
[index
+ 1]);
471 nsresult
SMILAnimationFunction::AccumulateResult(const SMILValueArray
& aValues
,
472 SMILValue
& aResult
) {
473 if (!IsToAnimation() && GetAccumulate() && mRepeatIteration
) {
474 // If the target attribute type doesn't support addition, Add will
475 // fail and we leave aResult untouched.
476 aResult
.Add(aValues
.LastElement(), mRepeatIteration
);
483 * Given the simple progress for a paced animation, this method:
484 * - determines which two elements of the values array we're in between
485 * (returned as aFrom and aTo)
486 * - determines where we are between them
487 * (returned as aIntervalProgress)
489 * Returns NS_OK, or NS_ERROR_FAILURE if our values don't support distance
492 nsresult
SMILAnimationFunction::ComputePacedPosition(
493 const SMILValueArray
& aValues
, double aSimpleProgress
,
494 double& aIntervalProgress
, const SMILValue
*& aFrom
, const SMILValue
*& aTo
) {
495 NS_ASSERTION(0.0f
<= aSimpleProgress
&& aSimpleProgress
< 1.0f
,
496 "aSimpleProgress is out of bounds");
497 NS_ASSERTION(GetCalcMode() == CALC_PACED
,
498 "Calling paced-specific function, but not in paced mode");
499 MOZ_ASSERT(aValues
.Length() >= 2, "Unexpected number of values");
501 // Trivial case: If we have just 2 values, then there's only one interval
502 // for us to traverse, and our progress across that interval is the exact
503 // same as our overall progress.
504 if (aValues
.Length() == 2) {
505 aIntervalProgress
= aSimpleProgress
;
511 double totalDistance
= ComputePacedTotalDistance(aValues
);
512 if (totalDistance
== COMPUTE_DISTANCE_ERROR
) return NS_ERROR_FAILURE
;
514 // If we have 0 total distance, then it's unclear where our "paced" position
515 // should be. We can just fail, which drops us into discrete animation mode.
516 // (That's fine, since our values are apparently indistinguishable anyway.)
517 if (totalDistance
== 0.0) {
518 return NS_ERROR_FAILURE
;
521 // total distance we should have moved at this point in time.
522 // (called 'remainingDist' due to how it's used in loop below)
523 double remainingDist
= aSimpleProgress
* totalDistance
;
525 // Must be satisfied, because totalDistance is a sum of (non-negative)
526 // distances, and aSimpleProgress is non-negative
527 NS_ASSERTION(remainingDist
>= 0, "distance values must be non-negative");
529 // Find where remainingDist puts us in the list of values
530 // Note: We could optimize this next loop by caching the
531 // interval-distances in an array, but maybe that's excessive.
532 for (uint32_t i
= 0; i
< aValues
.Length() - 1; i
++) {
533 // Note: The following assertion is valid because remainingDist should
534 // start out non-negative, and this loop never shaves off more than its
536 NS_ASSERTION(remainingDist
>= 0, "distance values must be non-negative");
538 double curIntervalDist
;
540 DebugOnly
<nsresult
> rv
=
541 aValues
[i
].ComputeDistance(aValues
[i
+ 1], curIntervalDist
);
542 MOZ_ASSERT(NS_SUCCEEDED(rv
),
543 "If we got through ComputePacedTotalDistance, we should "
544 "be able to recompute each sub-distance without errors");
546 NS_ASSERTION(curIntervalDist
>= 0, "distance values must be non-negative");
547 // Clamp distance value at 0, just in case ComputeDistance is evil.
548 curIntervalDist
= std::max(curIntervalDist
, 0.0);
550 if (remainingDist
>= curIntervalDist
) {
551 remainingDist
-= curIntervalDist
;
553 // NOTE: If we get here, then curIntervalDist necessarily is not 0. Why?
554 // Because this clause is only hit when remainingDist < curIntervalDist,
555 // and if curIntervalDist were 0, that would mean remainingDist would
556 // have to be < 0. But that can't happen, because remainingDist (as
557 // a distance) is non-negative by definition.
558 NS_ASSERTION(curIntervalDist
!= 0,
559 "We should never get here with this set to 0...");
561 // We found the right spot -- an interpolated position between
564 aTo
= &aValues
[i
+ 1];
565 aIntervalProgress
= remainingDist
/ curIntervalDist
;
570 MOZ_ASSERT_UNREACHABLE(
571 "shouldn't complete loop & get here -- if we do, "
572 "then aSimpleProgress was probably out of bounds");
573 return NS_ERROR_FAILURE
;
577 * Computes the total distance to be travelled by a paced animation.
579 * Returns the total distance, or returns COMPUTE_DISTANCE_ERROR if
580 * our values don't support distance computation.
582 double SMILAnimationFunction::ComputePacedTotalDistance(
583 const SMILValueArray
& aValues
) const {
584 NS_ASSERTION(GetCalcMode() == CALC_PACED
,
585 "Calling paced-specific function, but not in paced mode");
587 double totalDistance
= 0.0;
588 for (uint32_t i
= 0; i
< aValues
.Length() - 1; i
++) {
590 nsresult rv
= aValues
[i
].ComputeDistance(aValues
[i
+ 1], tmpDist
);
592 return COMPUTE_DISTANCE_ERROR
;
595 // Clamp distance value to 0, just in case we have an evil ComputeDistance
596 // implementation somewhere
597 MOZ_ASSERT(tmpDist
>= 0.0f
, "distance values must be non-negative");
598 tmpDist
= std::max(tmpDist
, 0.0);
600 totalDistance
+= tmpDist
;
603 return totalDistance
;
606 double SMILAnimationFunction::ScaleSimpleProgress(double aProgress
,
607 SMILCalcMode aCalcMode
) {
608 if (!HasAttr(nsGkAtoms::keyTimes
)) return aProgress
;
610 uint32_t numTimes
= mKeyTimes
.Length();
612 if (numTimes
< 2) return aProgress
;
615 for (; i
< numTimes
- 2 && aProgress
>= mKeyTimes
[i
+ 1]; ++i
) {
618 if (aCalcMode
== CALC_DISCRETE
) {
619 // discrete calcMode behaviour differs in that each keyTime defines the time
620 // from when the corresponding value is set, and therefore the last value
621 // needn't be 1. So check if we're in the last 'interval', that is, the
622 // space between the final value and 1.0.
623 if (aProgress
>= mKeyTimes
[i
+ 1]) {
624 MOZ_ASSERT(i
== numTimes
- 2,
625 "aProgress is not in range of the current interval, yet the "
626 "current interval is not the last bounded interval either.");
629 return (double)i
/ numTimes
;
632 double& intervalStart
= mKeyTimes
[i
];
633 double& intervalEnd
= mKeyTimes
[i
+ 1];
635 double intervalLength
= intervalEnd
- intervalStart
;
636 if (intervalLength
<= 0.0) return intervalStart
;
638 return (i
+ (aProgress
- intervalStart
) / intervalLength
) /
639 double(numTimes
- 1);
642 double SMILAnimationFunction::ScaleIntervalProgress(double aProgress
,
643 uint32_t aIntervalIndex
) {
644 if (GetCalcMode() != CALC_SPLINE
) return aProgress
;
646 if (!HasAttr(nsGkAtoms::keySplines
)) return aProgress
;
648 MOZ_ASSERT(aIntervalIndex
< mKeySplines
.Length(), "Invalid interval index");
650 SMILKeySpline
const& spline
= mKeySplines
[aIntervalIndex
];
651 return spline
.GetSplineValue(aProgress
);
654 bool SMILAnimationFunction::HasAttr(nsAtom
* aAttName
) const {
655 if (IsDisallowedAttribute(aAttName
)) {
658 return mAnimationElement
->HasAttr(aAttName
);
661 const nsAttrValue
* SMILAnimationFunction::GetAttr(nsAtom
* aAttName
) const {
662 if (IsDisallowedAttribute(aAttName
)) {
665 return mAnimationElement
->GetParsedAttr(aAttName
);
668 bool SMILAnimationFunction::GetAttr(nsAtom
* aAttName
,
669 nsAString
& aResult
) const {
670 if (IsDisallowedAttribute(aAttName
)) {
673 return mAnimationElement
->GetAttr(aAttName
, aResult
);
677 * A utility function to make querying an attribute that corresponds to an
678 * SMILValue a little neater.
680 * @param aAttName The attribute name (in the global namespace).
681 * @param aSMILAttr The SMIL attribute to perform the parsing.
682 * @param[out] aResult The resulting SMILValue.
683 * @param[out] aPreventCachingOfSandwich
684 * If |aResult| contains dependencies on its context that
685 * should prevent the result of the animation sandwich from
686 * being cached and reused in future samples (as reported
687 * by SMILAttr::ValueFromString), then this outparam
688 * will be set to true. Otherwise it is left unmodified.
690 * Returns false if a parse error occurred, otherwise returns true.
692 bool SMILAnimationFunction::ParseAttr(nsAtom
* aAttName
,
693 const SMILAttr
& aSMILAttr
,
695 bool& aPreventCachingOfSandwich
) const {
696 nsAutoString attValue
;
697 if (GetAttr(aAttName
, attValue
)) {
698 nsresult rv
= aSMILAttr
.ValueFromString(attValue
, mAnimationElement
,
699 aResult
, aPreventCachingOfSandwich
);
700 if (NS_FAILED(rv
)) return false;
706 * SMILANIM specifies the following rules for animation function values:
708 * (1) if values is set, it overrides everything
709 * (2) for from/to/by animation at least to or by must be specified, from on its
710 * own (or nothing) is an error--which we will ignore
711 * (3) if both by and to are specified only to will be used, by will be ignored
712 * (4) if by is specified without from (by animation), forces additive behaviour
713 * (5) if to is specified without from (to animation), special care needs to be
714 * taken when compositing animation as such animations are composited last.
716 * This helper method applies these rules to fill in the values list and to set
717 * some internal state.
719 nsresult
SMILAnimationFunction::GetValues(const SMILAttr
& aSMILAttr
,
720 SMILValueArray
& aResult
) {
721 if (!mAnimationElement
) return NS_ERROR_FAILURE
;
723 mValueNeedsReparsingEverySample
= false;
724 SMILValueArray result
;
726 // If "values" is set, use it
727 if (HasAttr(nsGkAtoms::values
)) {
728 nsAutoString attValue
;
729 GetAttr(nsGkAtoms::values
, attValue
);
730 bool preventCachingOfSandwich
= false;
731 if (!SMILParserUtils::ParseValues(attValue
, mAnimationElement
, aSMILAttr
,
732 result
, preventCachingOfSandwich
)) {
733 return NS_ERROR_FAILURE
;
736 if (preventCachingOfSandwich
) {
737 mValueNeedsReparsingEverySample
= true;
739 // Else try to/from/by
741 bool preventCachingOfSandwich
= false;
743 SMILValue to
, from
, by
;
745 ParseAttr(nsGkAtoms::to
, aSMILAttr
, to
, preventCachingOfSandwich
);
747 ParseAttr(nsGkAtoms::from
, aSMILAttr
, from
, preventCachingOfSandwich
);
749 ParseAttr(nsGkAtoms::by
, aSMILAttr
, by
, preventCachingOfSandwich
);
751 if (preventCachingOfSandwich
) {
752 mValueNeedsReparsingEverySample
= true;
755 if (!parseOk
|| !result
.SetCapacity(2, fallible
)) {
756 return NS_ERROR_FAILURE
;
759 // AppendElement() below must succeed, because SetCapacity() succeeded.
761 if (!from
.IsNull()) {
762 MOZ_ALWAYS_TRUE(result
.AppendElement(from
, fallible
));
763 MOZ_ALWAYS_TRUE(result
.AppendElement(to
, fallible
));
765 MOZ_ALWAYS_TRUE(result
.AppendElement(to
, fallible
));
767 } else if (!by
.IsNull()) {
768 SMILValue
effectiveFrom(by
.mType
);
769 if (!from
.IsNull()) effectiveFrom
= from
;
770 // Set values to 'from; from + by'
771 MOZ_ALWAYS_TRUE(result
.AppendElement(effectiveFrom
, fallible
));
772 SMILValue
effectiveTo(effectiveFrom
);
773 if (!effectiveTo
.IsNull() && NS_SUCCEEDED(effectiveTo
.Add(by
))) {
774 MOZ_ALWAYS_TRUE(result
.AppendElement(effectiveTo
, fallible
));
776 // Using by-animation with non-additive type or bad base-value
777 return NS_ERROR_FAILURE
;
780 // No values, no to, no by -- call it a day
781 return NS_ERROR_FAILURE
;
785 aResult
= std::move(result
);
790 void SMILAnimationFunction::CheckValueListDependentAttrs(uint32_t aNumValues
) {
791 CheckKeyTimes(aNumValues
);
792 CheckKeySplines(aNumValues
);
796 * Performs checks for the keyTimes attribute required by the SMIL spec but
797 * which depend on other attributes and therefore needs to be updated as
798 * dependent attributes are set.
800 void SMILAnimationFunction::CheckKeyTimes(uint32_t aNumValues
) {
801 if (!HasAttr(nsGkAtoms::keyTimes
)) return;
803 SMILCalcMode calcMode
= GetCalcMode();
805 // attribute is ignored for calcMode = paced
806 if (calcMode
== CALC_PACED
) {
807 SetKeyTimesErrorFlag(false);
811 uint32_t numKeyTimes
= mKeyTimes
.Length();
812 if (numKeyTimes
< 1) {
813 // keyTimes isn't set or failed preliminary checks
814 SetKeyTimesErrorFlag(true);
818 // no. keyTimes == no. values
819 // For to-animation the number of values is considered to be 2.
820 bool matchingNumOfValues
= numKeyTimes
== (IsToAnimation() ? 2 : aNumValues
);
821 if (!matchingNumOfValues
) {
822 SetKeyTimesErrorFlag(true);
826 // first value must be 0
827 if (mKeyTimes
[0] != 0.0) {
828 SetKeyTimesErrorFlag(true);
832 // last value must be 1 for linear or spline calcModes
833 if (calcMode
!= CALC_DISCRETE
&& numKeyTimes
> 1 &&
834 mKeyTimes
.LastElement() != 1.0) {
835 SetKeyTimesErrorFlag(true);
839 SetKeyTimesErrorFlag(false);
842 void SMILAnimationFunction::CheckKeySplines(uint32_t aNumValues
) {
843 // attribute is ignored if calc mode is not spline
844 if (GetCalcMode() != CALC_SPLINE
) {
845 SetKeySplinesErrorFlag(false);
849 // calc mode is spline but the attribute is not set
850 if (!HasAttr(nsGkAtoms::keySplines
)) {
851 SetKeySplinesErrorFlag(false);
855 if (mKeySplines
.Length() < 1) {
856 // keyTimes isn't set or failed preliminary checks
857 SetKeySplinesErrorFlag(true);
861 // ignore splines if there's only one value
862 if (aNumValues
== 1 && !IsToAnimation()) {
863 SetKeySplinesErrorFlag(false);
867 // no. keySpline specs == no. values - 1
868 uint32_t splineSpecs
= mKeySplines
.Length();
869 if ((splineSpecs
!= aNumValues
- 1 && !IsToAnimation()) ||
870 (IsToAnimation() && splineSpecs
!= 1)) {
871 SetKeySplinesErrorFlag(true);
875 SetKeySplinesErrorFlag(false);
878 bool SMILAnimationFunction::IsValueFixedForSimpleDuration() const {
879 return mSimpleDuration
.IsIndefinite() ||
880 (!mHasChanged
&& mPrevSampleWasSingleValueAnimation
);
883 //----------------------------------------------------------------------
886 bool SMILAnimationFunction::GetAccumulate() const {
887 const nsAttrValue
* value
= GetAttr(nsGkAtoms::accumulate
);
888 if (!value
) return false;
890 return value
->GetEnumValue();
893 bool SMILAnimationFunction::GetAdditive() const {
894 const nsAttrValue
* value
= GetAttr(nsGkAtoms::additive
);
895 if (!value
) return false;
897 return value
->GetEnumValue();
900 SMILAnimationFunction::SMILCalcMode
SMILAnimationFunction::GetCalcMode() const {
901 const nsAttrValue
* value
= GetAttr(nsGkAtoms::calcMode
);
902 if (!value
) return CALC_LINEAR
;
904 return SMILCalcMode(value
->GetEnumValue());
907 //----------------------------------------------------------------------
908 // Property setters / un-setters:
910 nsresult
SMILAnimationFunction::SetAccumulate(const nsAString
& aAccumulate
,
911 nsAttrValue
& aResult
) {
914 aResult
.ParseEnumValue(aAccumulate
, sAccumulateTable
, true);
915 SetAccumulateErrorFlag(!parseResult
);
916 return parseResult
? NS_OK
: NS_ERROR_FAILURE
;
919 void SMILAnimationFunction::UnsetAccumulate() {
920 SetAccumulateErrorFlag(false);
924 nsresult
SMILAnimationFunction::SetAdditive(const nsAString
& aAdditive
,
925 nsAttrValue
& aResult
) {
927 bool parseResult
= aResult
.ParseEnumValue(aAdditive
, sAdditiveTable
, true);
928 SetAdditiveErrorFlag(!parseResult
);
929 return parseResult
? NS_OK
: NS_ERROR_FAILURE
;
932 void SMILAnimationFunction::UnsetAdditive() {
933 SetAdditiveErrorFlag(false);
937 nsresult
SMILAnimationFunction::SetCalcMode(const nsAString
& aCalcMode
,
938 nsAttrValue
& aResult
) {
940 bool parseResult
= aResult
.ParseEnumValue(aCalcMode
, sCalcModeTable
, true);
941 SetCalcModeErrorFlag(!parseResult
);
942 return parseResult
? NS_OK
: NS_ERROR_FAILURE
;
945 void SMILAnimationFunction::UnsetCalcMode() {
946 SetCalcModeErrorFlag(false);
950 nsresult
SMILAnimationFunction::SetKeySplines(const nsAString
& aKeySplines
,
951 nsAttrValue
& aResult
) {
953 aResult
.SetTo(aKeySplines
);
957 if (!SMILParserUtils::ParseKeySplines(aKeySplines
, mKeySplines
)) {
959 return NS_ERROR_FAILURE
;
965 void SMILAnimationFunction::UnsetKeySplines() {
967 SetKeySplinesErrorFlag(false);
971 nsresult
SMILAnimationFunction::SetKeyTimes(const nsAString
& aKeyTimes
,
972 nsAttrValue
& aResult
) {
974 aResult
.SetTo(aKeyTimes
);
978 if (!SMILParserUtils::ParseSemicolonDelimitedProgressList(aKeyTimes
, true,
981 return NS_ERROR_FAILURE
;
987 void SMILAnimationFunction::UnsetKeyTimes() {
989 SetKeyTimesErrorFlag(false);
993 } // namespace mozilla