Coverage Report

Created: 2018-09-25 14:53

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