Backed out 2 changesets (bug 1539720) for causing caret related failures. CLOSED...
[gecko.git] / dom / smil / SMILAnimationFunction.cpp
blob3fe16f7276648f7fd8e924349ba037d940a7431a
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"
9 #include <math.h>
11 #include <algorithm>
12 #include <utility>
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"
23 #include "nsCOMPtr.h"
24 #include "nsContentUtils.h"
25 #include "nsGkAtoms.h"
26 #include "nsIContent.h"
27 #include "nsReadableUtils.h"
28 #include "nsString.h"
30 using namespace mozilla::dom;
32 namespace mozilla {
34 //----------------------------------------------------------------------
35 // Static members
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},
48 {nullptr, 0}};
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 //----------------------------------------------------------------------
55 // Constructors etc.
57 SMILAnimationFunction::SMILAnimationFunction()
58 : mSampleTime(-1),
59 mRepeatIteration(0),
60 mBeginTime(INT64_MIN),
61 mAnimationElement(nullptr),
62 mErrorFlags(0),
63 mIsActive(false),
64 mIsFrozen(false),
65 mLastValue(false),
66 mHasChanged(true),
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,
77 nsAttrValue& aResult,
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);
82 if (aParseResult) {
83 *aParseResult = NS_OK;
85 return true;
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
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;
115 if (foundMatch && aParseResult) {
116 *aParseResult = parseResult;
119 return foundMatch;
122 bool SMILAnimationFunction::UnsetAttr(nsAtom* aAttribute) {
123 if (IsDisallowedAttribute(aAttribute)) {
124 return true;
127 bool foundMatch = true;
129 if (aAttribute == nsGkAtoms::by || aAttribute == nsGkAtoms::from ||
130 aAttribute == nsGkAtoms::to || 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;
146 return foundMatch;
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?
157 mHasChanged |=
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;
169 mLastValue = false;
172 void SMILAnimationFunction::SampleLastValue(uint32_t aRepeatIteration) {
173 if (mHasChanged || !mLastValue || mRepeatIteration != aRepeatIteration) {
174 mHasChanged = true;
177 mRepeatIteration = aRepeatIteration;
178 mLastValue = true;
181 void SMILAnimationFunction::Activate(SMILTime aBeginTime) {
182 mBeginTime = aBeginTime;
183 mIsActive = true;
184 mIsFrozen = false;
185 mHasChanged = true;
188 void SMILAnimationFunction::Inactivate(bool aIsFrozen) {
189 mIsActive = false;
190 mIsFrozen = aIsFrozen;
191 mHasChanged = true;
194 void SMILAnimationFunction::ComposeResult(const SMILAttr& aSMILAttr,
195 SMILValue& aResult) {
196 mHasChanged = false;
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;
225 SMILValue result;
227 if (values.Length() == 1 && !IsToAnimation()) {
228 // Single-valued animation
229 result = values[0];
230 mPrevSampleWasSingleValueAnimation = true;
232 } else if (mLastValue) {
233 // Sampling last value
234 const SMILValue& last = values[values.Length() - 1];
235 result = last;
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);
244 } else {
245 // Interpolation
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
273 // its syncbase
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
281 // the document
282 MOZ_ASSERT(mAnimationElement != aOther->mAnimationElement,
283 "Two animations cannot have the same animation content element!");
285 return (nsContentUtils::PositionIsBefore(mAnimationElement,
286 aOther->mAnimationElement))
287 ? -1
288 : 1;
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;
309 return true;
311 return false;
314 //----------------------------------------------------------------------
315 // Implementation helpers
317 nsresult SMILAnimationFunction::InterpolateResult(const SMILValueArray& aValues,
318 SMILValue& aResult,
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;
349 if (dur > 0) {
350 simpleProgress = (double)mSampleTime / dur;
351 } // else leave simpleProgress at 0.0 (e.g. if mSampleTime == dur == 0)
354 nsresult rv = NS_OK;
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
359 // CSS.
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()) {
373 from = &aBaseValue;
374 to = &aValues[0];
375 if (calcMode == CALC_PACED) {
376 // Note: key[Times/Splines/Points] are ignored for calcMode="paced"
377 intervalProgress = simpleProgress;
378 } else {
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,
385 to);
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);
393 uint32_t index =
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];
438 } else {
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");
460 } else {
461 // Base the type of the zero value on the next element in the series.
462 SMILCSSValueType::FinalizeValue(aResult, aValues[index + 1]);
466 rv = NS_OK;
468 return rv;
471 nsresult SMILAnimationFunction::AccumulateResult(const SMILValueArray& aValues,
472 SMILValue& aResult) {
473 if (!IsToAnimation() && GetAccumulate() && mRepeatIteration) {
474 const SMILValue& lastValue = aValues[aValues.Length() - 1];
476 // If the target attribute type doesn't support addition, Add will
477 // fail and we leave aResult untouched.
478 aResult.Add(lastValue, mRepeatIteration);
481 return NS_OK;
485 * Given the simple progress for a paced animation, this method:
486 * - determines which two elements of the values array we're in between
487 * (returned as aFrom and aTo)
488 * - determines where we are between them
489 * (returned as aIntervalProgress)
491 * Returns NS_OK, or NS_ERROR_FAILURE if our values don't support distance
492 * computation.
494 nsresult SMILAnimationFunction::ComputePacedPosition(
495 const SMILValueArray& aValues, double aSimpleProgress,
496 double& aIntervalProgress, const SMILValue*& aFrom, const SMILValue*& aTo) {
497 NS_ASSERTION(0.0f <= aSimpleProgress && aSimpleProgress < 1.0f,
498 "aSimpleProgress is out of bounds");
499 NS_ASSERTION(GetCalcMode() == CALC_PACED,
500 "Calling paced-specific function, but not in paced mode");
501 MOZ_ASSERT(aValues.Length() >= 2, "Unexpected number of values");
503 // Trivial case: If we have just 2 values, then there's only one interval
504 // for us to traverse, and our progress across that interval is the exact
505 // same as our overall progress.
506 if (aValues.Length() == 2) {
507 aIntervalProgress = aSimpleProgress;
508 aFrom = &aValues[0];
509 aTo = &aValues[1];
510 return NS_OK;
513 double totalDistance = ComputePacedTotalDistance(aValues);
514 if (totalDistance == COMPUTE_DISTANCE_ERROR) return NS_ERROR_FAILURE;
516 // If we have 0 total distance, then it's unclear where our "paced" position
517 // should be. We can just fail, which drops us into discrete animation mode.
518 // (That's fine, since our values are apparently indistinguishable anyway.)
519 if (totalDistance == 0.0) {
520 return NS_ERROR_FAILURE;
523 // total distance we should have moved at this point in time.
524 // (called 'remainingDist' due to how it's used in loop below)
525 double remainingDist = aSimpleProgress * totalDistance;
527 // Must be satisfied, because totalDistance is a sum of (non-negative)
528 // distances, and aSimpleProgress is non-negative
529 NS_ASSERTION(remainingDist >= 0, "distance values must be non-negative");
531 // Find where remainingDist puts us in the list of values
532 // Note: We could optimize this next loop by caching the
533 // interval-distances in an array, but maybe that's excessive.
534 for (uint32_t i = 0; i < aValues.Length() - 1; i++) {
535 // Note: The following assertion is valid because remainingDist should
536 // start out non-negative, and this loop never shaves off more than its
537 // current value.
538 NS_ASSERTION(remainingDist >= 0, "distance values must be non-negative");
540 double curIntervalDist;
542 DebugOnly<nsresult> rv =
543 aValues[i].ComputeDistance(aValues[i + 1], curIntervalDist);
544 MOZ_ASSERT(NS_SUCCEEDED(rv),
545 "If we got through ComputePacedTotalDistance, we should "
546 "be able to recompute each sub-distance without errors");
548 NS_ASSERTION(curIntervalDist >= 0, "distance values must be non-negative");
549 // Clamp distance value at 0, just in case ComputeDistance is evil.
550 curIntervalDist = std::max(curIntervalDist, 0.0);
552 if (remainingDist >= curIntervalDist) {
553 remainingDist -= curIntervalDist;
554 } else {
555 // NOTE: If we get here, then curIntervalDist necessarily is not 0. Why?
556 // Because this clause is only hit when remainingDist < curIntervalDist,
557 // and if curIntervalDist were 0, that would mean remainingDist would
558 // have to be < 0. But that can't happen, because remainingDist (as
559 // a distance) is non-negative by definition.
560 NS_ASSERTION(curIntervalDist != 0,
561 "We should never get here with this set to 0...");
563 // We found the right spot -- an interpolated position between
564 // values i and i+1.
565 aFrom = &aValues[i];
566 aTo = &aValues[i + 1];
567 aIntervalProgress = remainingDist / curIntervalDist;
568 return NS_OK;
572 MOZ_ASSERT_UNREACHABLE(
573 "shouldn't complete loop & get here -- if we do, "
574 "then aSimpleProgress was probably out of bounds");
575 return NS_ERROR_FAILURE;
579 * Computes the total distance to be travelled by a paced animation.
581 * Returns the total distance, or returns COMPUTE_DISTANCE_ERROR if
582 * our values don't support distance computation.
584 double SMILAnimationFunction::ComputePacedTotalDistance(
585 const SMILValueArray& aValues) const {
586 NS_ASSERTION(GetCalcMode() == CALC_PACED,
587 "Calling paced-specific function, but not in paced mode");
589 double totalDistance = 0.0;
590 for (uint32_t i = 0; i < aValues.Length() - 1; i++) {
591 double tmpDist;
592 nsresult rv = aValues[i].ComputeDistance(aValues[i + 1], tmpDist);
593 if (NS_FAILED(rv)) {
594 return COMPUTE_DISTANCE_ERROR;
597 // Clamp distance value to 0, just in case we have an evil ComputeDistance
598 // implementation somewhere
599 MOZ_ASSERT(tmpDist >= 0.0f, "distance values must be non-negative");
600 tmpDist = std::max(tmpDist, 0.0);
602 totalDistance += tmpDist;
605 return totalDistance;
608 double SMILAnimationFunction::ScaleSimpleProgress(double aProgress,
609 SMILCalcMode aCalcMode) {
610 if (!HasAttr(nsGkAtoms::keyTimes)) return aProgress;
612 uint32_t numTimes = mKeyTimes.Length();
614 if (numTimes < 2) return aProgress;
616 uint32_t i = 0;
617 for (; i < numTimes - 2 && aProgress >= mKeyTimes[i + 1]; ++i) {
620 if (aCalcMode == CALC_DISCRETE) {
621 // discrete calcMode behaviour differs in that each keyTime defines the time
622 // from when the corresponding value is set, and therefore the last value
623 // needn't be 1. So check if we're in the last 'interval', that is, the
624 // space between the final value and 1.0.
625 if (aProgress >= mKeyTimes[i + 1]) {
626 MOZ_ASSERT(i == numTimes - 2,
627 "aProgress is not in range of the current interval, yet the "
628 "current interval is not the last bounded interval either.");
629 ++i;
631 return (double)i / numTimes;
634 double& intervalStart = mKeyTimes[i];
635 double& intervalEnd = mKeyTimes[i + 1];
637 double intervalLength = intervalEnd - intervalStart;
638 if (intervalLength <= 0.0) return intervalStart;
640 return (i + (aProgress - intervalStart) / intervalLength) /
641 double(numTimes - 1);
644 double SMILAnimationFunction::ScaleIntervalProgress(double aProgress,
645 uint32_t aIntervalIndex) {
646 if (GetCalcMode() != CALC_SPLINE) return aProgress;
648 if (!HasAttr(nsGkAtoms::keySplines)) return aProgress;
650 MOZ_ASSERT(aIntervalIndex < mKeySplines.Length(), "Invalid interval index");
652 SMILKeySpline const& spline = mKeySplines[aIntervalIndex];
653 return spline.GetSplineValue(aProgress);
656 bool SMILAnimationFunction::HasAttr(nsAtom* aAttName) const {
657 if (IsDisallowedAttribute(aAttName)) {
658 return false;
660 return mAnimationElement->HasAttr(aAttName);
663 const nsAttrValue* SMILAnimationFunction::GetAttr(nsAtom* aAttName) const {
664 if (IsDisallowedAttribute(aAttName)) {
665 return nullptr;
667 return mAnimationElement->GetParsedAttr(aAttName);
670 bool SMILAnimationFunction::GetAttr(nsAtom* aAttName,
671 nsAString& aResult) const {
672 if (IsDisallowedAttribute(aAttName)) {
673 return false;
675 return mAnimationElement->GetAttr(aAttName, aResult);
679 * A utility function to make querying an attribute that corresponds to an
680 * SMILValue a little neater.
682 * @param aAttName The attribute name (in the global namespace).
683 * @param aSMILAttr The SMIL attribute to perform the parsing.
684 * @param[out] aResult The resulting SMILValue.
685 * @param[out] aPreventCachingOfSandwich
686 * If |aResult| contains dependencies on its context that
687 * should prevent the result of the animation sandwich from
688 * being cached and reused in future samples (as reported
689 * by SMILAttr::ValueFromString), then this outparam
690 * will be set to true. Otherwise it is left unmodified.
692 * Returns false if a parse error occurred, otherwise returns true.
694 bool SMILAnimationFunction::ParseAttr(nsAtom* aAttName,
695 const SMILAttr& aSMILAttr,
696 SMILValue& aResult,
697 bool& aPreventCachingOfSandwich) const {
698 nsAutoString attValue;
699 if (GetAttr(aAttName, attValue)) {
700 nsresult rv = aSMILAttr.ValueFromString(attValue, mAnimationElement,
701 aResult, aPreventCachingOfSandwich);
702 if (NS_FAILED(rv)) return false;
704 return true;
708 * SMILANIM specifies the following rules for animation function values:
710 * (1) if values is set, it overrides everything
711 * (2) for from/to/by animation at least to or by must be specified, from on its
712 * own (or nothing) is an error--which we will ignore
713 * (3) if both by and to are specified only to will be used, by will be ignored
714 * (4) if by is specified without from (by animation), forces additive behaviour
715 * (5) if to is specified without from (to animation), special care needs to be
716 * taken when compositing animation as such animations are composited last.
718 * This helper method applies these rules to fill in the values list and to set
719 * some internal state.
721 nsresult SMILAnimationFunction::GetValues(const SMILAttr& aSMILAttr,
722 SMILValueArray& aResult) {
723 if (!mAnimationElement) return NS_ERROR_FAILURE;
725 mValueNeedsReparsingEverySample = false;
726 SMILValueArray result;
728 // If "values" is set, use it
729 if (HasAttr(nsGkAtoms::values)) {
730 nsAutoString attValue;
731 GetAttr(nsGkAtoms::values, attValue);
732 bool preventCachingOfSandwich = false;
733 if (!SMILParserUtils::ParseValues(attValue, mAnimationElement, aSMILAttr,
734 result, preventCachingOfSandwich)) {
735 return NS_ERROR_FAILURE;
738 if (preventCachingOfSandwich) {
739 mValueNeedsReparsingEverySample = true;
741 // Else try to/from/by
742 } else {
743 bool preventCachingOfSandwich = false;
744 bool parseOk = true;
745 SMILValue to, from, by;
746 parseOk &=
747 ParseAttr(nsGkAtoms::to, aSMILAttr, to, preventCachingOfSandwich);
748 parseOk &=
749 ParseAttr(nsGkAtoms::from, aSMILAttr, from, preventCachingOfSandwich);
750 parseOk &=
751 ParseAttr(nsGkAtoms::by, aSMILAttr, by, preventCachingOfSandwich);
753 if (preventCachingOfSandwich) {
754 mValueNeedsReparsingEverySample = true;
757 if (!parseOk || !result.SetCapacity(2, fallible)) {
758 return NS_ERROR_FAILURE;
761 // AppendElement() below must succeed, because SetCapacity() succeeded.
762 if (!to.IsNull()) {
763 if (!from.IsNull()) {
764 MOZ_ALWAYS_TRUE(result.AppendElement(from, fallible));
765 MOZ_ALWAYS_TRUE(result.AppendElement(to, fallible));
766 } else {
767 MOZ_ALWAYS_TRUE(result.AppendElement(to, fallible));
769 } else if (!by.IsNull()) {
770 SMILValue effectiveFrom(by.mType);
771 if (!from.IsNull()) effectiveFrom = from;
772 // Set values to 'from; from + by'
773 MOZ_ALWAYS_TRUE(result.AppendElement(effectiveFrom, fallible));
774 SMILValue effectiveTo(effectiveFrom);
775 if (!effectiveTo.IsNull() && NS_SUCCEEDED(effectiveTo.Add(by))) {
776 MOZ_ALWAYS_TRUE(result.AppendElement(effectiveTo, fallible));
777 } else {
778 // Using by-animation with non-additive type or bad base-value
779 return NS_ERROR_FAILURE;
781 } else {
782 // No values, no to, no by -- call it a day
783 return NS_ERROR_FAILURE;
787 aResult = std::move(result);
789 return NS_OK;
792 void SMILAnimationFunction::CheckValueListDependentAttrs(uint32_t aNumValues) {
793 CheckKeyTimes(aNumValues);
794 CheckKeySplines(aNumValues);
798 * Performs checks for the keyTimes attribute required by the SMIL spec but
799 * which depend on other attributes and therefore needs to be updated as
800 * dependent attributes are set.
802 void SMILAnimationFunction::CheckKeyTimes(uint32_t aNumValues) {
803 if (!HasAttr(nsGkAtoms::keyTimes)) return;
805 SMILCalcMode calcMode = GetCalcMode();
807 // attribute is ignored for calcMode = paced
808 if (calcMode == CALC_PACED) {
809 SetKeyTimesErrorFlag(false);
810 return;
813 uint32_t numKeyTimes = mKeyTimes.Length();
814 if (numKeyTimes < 1) {
815 // keyTimes isn't set or failed preliminary checks
816 SetKeyTimesErrorFlag(true);
817 return;
820 // no. keyTimes == no. values
821 // For to-animation the number of values is considered to be 2.
822 bool matchingNumOfValues = numKeyTimes == (IsToAnimation() ? 2 : aNumValues);
823 if (!matchingNumOfValues) {
824 SetKeyTimesErrorFlag(true);
825 return;
828 // first value must be 0
829 if (mKeyTimes[0] != 0.0) {
830 SetKeyTimesErrorFlag(true);
831 return;
834 // last value must be 1 for linear or spline calcModes
835 if (calcMode != CALC_DISCRETE && numKeyTimes > 1 &&
836 mKeyTimes[numKeyTimes - 1] != 1.0) {
837 SetKeyTimesErrorFlag(true);
838 return;
841 SetKeyTimesErrorFlag(false);
844 void SMILAnimationFunction::CheckKeySplines(uint32_t aNumValues) {
845 // attribute is ignored if calc mode is not spline
846 if (GetCalcMode() != CALC_SPLINE) {
847 SetKeySplinesErrorFlag(false);
848 return;
851 // calc mode is spline but the attribute is not set
852 if (!HasAttr(nsGkAtoms::keySplines)) {
853 SetKeySplinesErrorFlag(false);
854 return;
857 if (mKeySplines.Length() < 1) {
858 // keyTimes isn't set or failed preliminary checks
859 SetKeySplinesErrorFlag(true);
860 return;
863 // ignore splines if there's only one value
864 if (aNumValues == 1 && !IsToAnimation()) {
865 SetKeySplinesErrorFlag(false);
866 return;
869 // no. keySpline specs == no. values - 1
870 uint32_t splineSpecs = mKeySplines.Length();
871 if ((splineSpecs != aNumValues - 1 && !IsToAnimation()) ||
872 (IsToAnimation() && splineSpecs != 1)) {
873 SetKeySplinesErrorFlag(true);
874 return;
877 SetKeySplinesErrorFlag(false);
880 bool SMILAnimationFunction::IsValueFixedForSimpleDuration() const {
881 return mSimpleDuration.IsIndefinite() ||
882 (!mHasChanged && mPrevSampleWasSingleValueAnimation);
885 //----------------------------------------------------------------------
886 // Property getters
888 bool SMILAnimationFunction::GetAccumulate() const {
889 const nsAttrValue* value = GetAttr(nsGkAtoms::accumulate);
890 if (!value) return false;
892 return value->GetEnumValue();
895 bool SMILAnimationFunction::GetAdditive() const {
896 const nsAttrValue* value = GetAttr(nsGkAtoms::additive);
897 if (!value) return false;
899 return value->GetEnumValue();
902 SMILAnimationFunction::SMILCalcMode SMILAnimationFunction::GetCalcMode() const {
903 const nsAttrValue* value = GetAttr(nsGkAtoms::calcMode);
904 if (!value) return CALC_LINEAR;
906 return SMILCalcMode(value->GetEnumValue());
909 //----------------------------------------------------------------------
910 // Property setters / un-setters:
912 nsresult SMILAnimationFunction::SetAccumulate(const nsAString& aAccumulate,
913 nsAttrValue& aResult) {
914 mHasChanged = true;
915 bool parseResult =
916 aResult.ParseEnumValue(aAccumulate, sAccumulateTable, true);
917 SetAccumulateErrorFlag(!parseResult);
918 return parseResult ? NS_OK : NS_ERROR_FAILURE;
921 void SMILAnimationFunction::UnsetAccumulate() {
922 SetAccumulateErrorFlag(false);
923 mHasChanged = true;
926 nsresult SMILAnimationFunction::SetAdditive(const nsAString& aAdditive,
927 nsAttrValue& aResult) {
928 mHasChanged = true;
929 bool parseResult = aResult.ParseEnumValue(aAdditive, sAdditiveTable, true);
930 SetAdditiveErrorFlag(!parseResult);
931 return parseResult ? NS_OK : NS_ERROR_FAILURE;
934 void SMILAnimationFunction::UnsetAdditive() {
935 SetAdditiveErrorFlag(false);
936 mHasChanged = true;
939 nsresult SMILAnimationFunction::SetCalcMode(const nsAString& aCalcMode,
940 nsAttrValue& aResult) {
941 mHasChanged = true;
942 bool parseResult = aResult.ParseEnumValue(aCalcMode, sCalcModeTable, true);
943 SetCalcModeErrorFlag(!parseResult);
944 return parseResult ? NS_OK : NS_ERROR_FAILURE;
947 void SMILAnimationFunction::UnsetCalcMode() {
948 SetCalcModeErrorFlag(false);
949 mHasChanged = true;
952 nsresult SMILAnimationFunction::SetKeySplines(const nsAString& aKeySplines,
953 nsAttrValue& aResult) {
954 mKeySplines.Clear();
955 aResult.SetTo(aKeySplines);
957 mHasChanged = true;
959 if (!SMILParserUtils::ParseKeySplines(aKeySplines, mKeySplines)) {
960 mKeySplines.Clear();
961 return NS_ERROR_FAILURE;
964 return NS_OK;
967 void SMILAnimationFunction::UnsetKeySplines() {
968 mKeySplines.Clear();
969 SetKeySplinesErrorFlag(false);
970 mHasChanged = true;
973 nsresult SMILAnimationFunction::SetKeyTimes(const nsAString& aKeyTimes,
974 nsAttrValue& aResult) {
975 mKeyTimes.Clear();
976 aResult.SetTo(aKeyTimes);
978 mHasChanged = true;
980 if (!SMILParserUtils::ParseSemicolonDelimitedProgressList(aKeyTimes, true,
981 mKeyTimes)) {
982 mKeyTimes.Clear();
983 return NS_ERROR_FAILURE;
986 return NS_OK;
989 void SMILAnimationFunction::UnsetKeyTimes() {
990 mKeyTimes.Clear();
991 SetKeyTimesErrorFlag(false);
992 mHasChanged = true;
995 } // namespace mozilla