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