dom/smil/nsSMILAnimationFunction.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial