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