author Brian Birtles <>
Fri, 13 Dec 2013 13:41:52 +0900
changeset 176379 63737099fabf73ed9c10e80ed5998c7e5d6fa66a
parent 172038 34c84bda8feb3a6b54c3fd303adfb13de36f4595
permissions -rw-r--r--
Bug 948245 part 2 - Allow the min attribute to extend the active duration; r=dholbert The min attribute on an animation element can extend the active duration making it longer than the "repeat duration" (the amount of the time the animation runs including all repeats). For the remaining time after the repeat duration has completed until the end of the active duration the animation should apply its fill behavior. Previously this was not implemented and min could not extend the active duration. Allowing this effectively introduces an additional kind of state where we are both within the active interval but not animating. In this case we set the animation function (referred to as the "client" for historical reasons) to inactive so that effectively the timing model is active but the animation model is inactive. (In the future we will come up with something a little easier to understand when we rework this in terms of Web Animations components.)

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at */


#include "nsISMILAttr.h"
#include "nsGkAtoms.h"
#include "nsString.h"
#include "nsSMILTargetIdentifier.h"
#include "nsSMILTimeValue.h"
#include "nsSMILKeySpline.h"
#include "nsSMILValue.h"
#include "nsAutoPtr.h"
#include "nsTArray.h"
#include "nsAttrValue.h"
#include "nsSMILTypes.h"

namespace mozilla {
namespace dom {
class SVGAnimationElement;

// nsSMILAnimationFunction
// The animation function calculates animation values. It it is provided with
// time parameters (sample time, repeat iteration etc.) and it uses this to
// build an appropriate animation value by performing interpolation and
// addition operations.
// It is responsible for implementing the animation parameters of an animation
// element (e.g. from, by, to, values, calcMode, additive, accumulate, keyTimes,
// keySplines)
class nsSMILAnimationFunction

   * Sets the owning animation element which this class uses to query attribute
   * values and compare document positions.
  void SetAnimationElement(mozilla::dom::SVGAnimationElement* aAnimationElement);

   * Sets animation-specific attributes (or marks them dirty, in the case
   * of from/to/by/values).
   * @param aAttribute The attribute being set
   * @param aValue     The updated value of the attribute.
   * @param aResult    The nsAttrValue object that may be used for storing the
   *                   parsed result.
   * @param aParseResult  Outparam used for reporting parse errors. Will be set
   *                      to NS_OK if everything succeeds.
   * @return  true if aAttribute is a recognized animation-related
   *          attribute; false otherwise.
  virtual bool SetAttr(nsIAtom* aAttribute, const nsAString& aValue,
                         nsAttrValue& aResult, nsresult* aParseResult = nullptr);

   * Unsets the given attribute.
   * @returns true if aAttribute is a recognized animation-related
   *          attribute; false otherwise.
  virtual bool UnsetAttr(nsIAtom* aAttribute);

   * Indicate a new sample has occurred.
   * @param aSampleTime The sample time for this timed element expressed in
   *                    simple time.
   * @param aSimpleDuration The simple duration for this timed element.
   * @param aRepeatIteration  The repeat iteration for this sample. The first
   *                          iteration has a value of 0.
  void SampleAt(nsSMILTime aSampleTime,
                const nsSMILTimeValue& aSimpleDuration,
                uint32_t aRepeatIteration);

   * Indicate to sample using the last value defined for the animation function.
   * This value is not normally sampled due to the end-point exclusive timing
   * model but only occurs when the fill mode is "freeze" and the active
   * duration is an even multiple of the simple duration.
   * @param aRepeatIteration  The repeat iteration for this sample. The first
   *                          iteration has a value of 0.
  void SampleLastValue(uint32_t aRepeatIteration);

   * Indicate that this animation is now active. This is used to instruct the
   * animation function that it should now add its result to the animation
   * sandwich. The begin time is also provided for proper prioritization of
   * animation functions, and for this reason, this method must be called
   * before either of the Sample methods.
   * @param aBeginTime The begin time for the newly active interval.
  void Activate(nsSMILTime aBeginTime);

   * Indicate that this animation is no longer active. This is used to instruct
   * the animation function that it should no longer add its result to the
   * animation sandwich.
   * @param aIsFrozen true if this animation should continue to contribute
   *                  to the animation sandwich using the most recent sample
   *                  parameters.
  void Inactivate(bool aIsFrozen);

   * Combines the result of this animation function for the last sample with the
   * specified value.
   * @param aSMILAttr This animation's target attribute. Used here for
   *                  doing attribute-specific parsing of from/to/by/values.
   * @param aResult   The value to compose with.
  void ComposeResult(const nsISMILAttr& aSMILAttr, nsSMILValue& aResult);

   * Returns the relative priority of this animation to another. The priority is
   * used for determining the position of the animation in the animation
   * sandwich -- higher priority animations are applied on top of lower
   * priority animations.
   * @return  -1 if this animation has lower priority or 1 if this animation has
   *          higher priority
   * This method should never return any other value, including 0.
  int8_t CompareTo(const nsSMILAnimationFunction* aOther) const;

   * The following methods are provided so that the compositor can optimize its
   * operations by only composing those animation that will affect the final
   * result.

   * Indicates if the animation is currently active or frozen. Inactive
   * animations will not contribute to the composed result.
   * @return  true if the animation is active or frozen, false otherwise.
  bool IsActiveOrFrozen() const
     * - Frozen animations should be considered active for the purposes of
     * compositing.
     * - This function does not assume that our nsSMILValues (by/from/to/values)
     * have already been parsed.
    return (mIsActive || mIsFrozen);

   * Indicates if the animation is active.
   * @return  true if the animation is active, false otherwise.
  bool IsActive() const {
    return mIsActive;

   * Indicates if this animation will replace the passed in result rather than
   * adding to it. Animations that replace the underlying value may be called
   * without first calling lower priority animations.
   * @return  True if the animation will replace, false if it will add or
   *          otherwise build on the passed in value.
  virtual bool WillReplace() const;

   * Indicates if the parameters for this animation have changed since the last
   * time it was composited. This allows rendering to be performed only when
   * necessary, particularly when no animations are active.
   * Note that the caller is responsible for determining if the animation
   * target has changed (with help from my UpdateCachedTarget() method).
   * @return  true if the animation parameters have changed, false
   *          otherwise.
  bool HasChanged() const;

   * This method lets us clear the 'HasChanged' flag for inactive animations
   * after we've reacted to their change to the 'inactive' state, so that we
   * won't needlessly recompose their targets in every sample.
   * This should only be called on an animation function that is inactive and
   * that returns true from HasChanged().
  void ClearHasChanged()
                      "clearing mHasChanged flag, when it's already false");
                      "clearing mHasChanged flag for active animation");
    mHasChanged = false;

   * Updates the cached record of our animation target, and returns a boolean
   * that indicates whether the target has changed since the last call to this
   * function. (This lets nsSMILCompositor check whether its animation
   * functions have changed value or target since the last sample.  If none of
   * them have, then the compositor doesn't need to do anything.)
   * @param aNewTarget A nsSMILTargetIdentifier representing the animation
   *                   target of this function for this sample.
   * @return  true if |aNewTarget| is different from the old cached value;
   *          otherwise, false.
  bool UpdateCachedTarget(const nsSMILTargetIdentifier& aNewTarget);

   * Returns true if this function was skipped in the previous sample (because
   * there was a higher-priority non-additive animation). If a skipped animation
   * function is later used, then the animation sandwich must be recomposited.
  bool WasSkippedInPrevSample() const {
    return mWasSkippedInPrevSample;

   * Mark this animation function as having been skipped. By marking the
   * function as skipped, if it is used in a subsequent sample we'll know to
   * recomposite the sandwich.
  void SetWasSkipped() {
    mWasSkippedInPrevSample = true;

  // Comparator utility class, used for sorting nsSMILAnimationFunctions
  class Comparator {
      bool Equals(const nsSMILAnimationFunction* aElem1,
                    const nsSMILAnimationFunction* aElem2) const {
        return (aElem1->CompareTo(aElem2) == 0);
      bool LessThan(const nsSMILAnimationFunction* aElem1,
                      const nsSMILAnimationFunction* aElem2) const {
        return (aElem1->CompareTo(aElem2) < 0);

  // Typedefs
  typedef FallibleTArray<nsSMILValue> nsSMILValueArray;

  // Types
  enum nsSMILCalcMode

  // Used for sorting nsSMILAnimationFunctions
  nsSMILTime GetBeginTime() const { return mBeginTime; }

  // Property getters
  bool                   GetAccumulate() const;
  bool                   GetAdditive() const;
  virtual nsSMILCalcMode GetCalcMode() const;

  // Property setters
  nsresult SetAccumulate(const nsAString& aAccumulate, nsAttrValue& aResult);
  nsresult SetAdditive(const nsAString& aAdditive, nsAttrValue& aResult);
  nsresult SetCalcMode(const nsAString& aCalcMode, nsAttrValue& aResult);
  nsresult SetKeyTimes(const nsAString& aKeyTimes, nsAttrValue& aResult);
  nsresult SetKeySplines(const nsAString& aKeySplines, nsAttrValue& aResult);

  // Property un-setters
  void     UnsetAccumulate();
  void     UnsetAdditive();
  void     UnsetCalcMode();
  void     UnsetKeyTimes();
  void     UnsetKeySplines();

  // Helpers
  virtual nsresult InterpolateResult(const nsSMILValueArray& aValues,
                                     nsSMILValue& aResult,
                                     nsSMILValue& aBaseValue);
  nsresult AccumulateResult(const nsSMILValueArray& aValues,
                            nsSMILValue& aResult);

  nsresult ComputePacedPosition(const nsSMILValueArray& aValues,
                                double aSimpleProgress,
                                double& aIntervalProgress,
                                const nsSMILValue*& aFrom,
                                const nsSMILValue*& aTo);
  double   ComputePacedTotalDistance(const nsSMILValueArray& aValues) const;

   * Adjust the simple progress, that is, the point within the simple duration,
   * by applying any keyTimes.
  double   ScaleSimpleProgress(double aProgress, nsSMILCalcMode aCalcMode);
   * Adjust the progress within an interval, that is, between two animation
   * values, by applying any keySplines.
  double   ScaleIntervalProgress(double aProgress, uint32_t aIntervalIndex);

  // Convenience attribute getters -- use these instead of querying
  // mAnimationElement as these may need to be overridden by subclasses
  virtual bool               HasAttr(nsIAtom* aAttName) const;
  virtual const nsAttrValue* GetAttr(nsIAtom* aAttName) const;
  virtual bool               GetAttr(nsIAtom* aAttName,
                                     nsAString& aResult) const;

  bool     ParseAttr(nsIAtom* aAttName, const nsISMILAttr& aSMILAttr,
                     nsSMILValue& aResult,
                     bool& aPreventCachingOfSandwich) const;

  virtual nsresult GetValues(const nsISMILAttr& aSMILAttr,
                             nsSMILValueArray& aResult);

  virtual void CheckValueListDependentAttrs(uint32_t aNumValues);
  void         CheckKeyTimes(uint32_t aNumValues);
  void         CheckKeySplines(uint32_t aNumValues);

  virtual bool IsToAnimation() const {
    return !HasAttr(nsGkAtoms::values) &&
            HasAttr(nsGkAtoms::to) &&

  // Returns true if we know our composited value won't change over the
  // simple duration of this animation (for a fixed base value).
  virtual bool IsValueFixedForSimpleDuration() const;

  inline bool IsAdditive() const {
     * Animation is additive if:
     * (1) additive = "sum" (GetAdditive() == true), or
     * (2) it is 'by animation' (by is set, from and values are not)
     * Although animation is not additive if it is 'to animation'
    bool isByAnimation = (!HasAttr(nsGkAtoms::values) &&
                             HasAttr(nsGkAtoms::by) &&
    return !IsToAnimation() && (GetAdditive() || isByAnimation);

  // Setters for error flags
  // These correspond to bit-indices in mErrorFlags, for tracking parse errors
  // in these attributes, when those parse errors should block us from doing
  // animation.
  enum AnimationAttributeIdx {
    BF_ADDITIVE    = 1,
    BF_CALC_MODE   = 2,
    BF_KEY_TIMES   = 3,
    BF_KEY_POINTS  = 5 // <animateMotion> only

  inline void SetAccumulateErrorFlag(bool aNewValue) {
    SetErrorFlag(BF_ACCUMULATE, aNewValue);
  inline void SetAdditiveErrorFlag(bool aNewValue) {
    SetErrorFlag(BF_ADDITIVE, aNewValue);
  inline void SetCalcModeErrorFlag(bool aNewValue) {
    SetErrorFlag(BF_CALC_MODE, aNewValue);
  inline void SetKeyTimesErrorFlag(bool aNewValue) {
    SetErrorFlag(BF_KEY_TIMES, aNewValue);
  inline void SetKeySplinesErrorFlag(bool aNewValue) {
    SetErrorFlag(BF_KEY_SPLINES, aNewValue);
  inline void SetKeyPointsErrorFlag(bool aNewValue) {
    SetErrorFlag(BF_KEY_POINTS, aNewValue);
  inline void SetErrorFlag(AnimationAttributeIdx aField, bool aValue) {
    if (aValue) {
      mErrorFlags |=  (0x01 << aField);
    } else {
      mErrorFlags &= ~(0x01 << aField);

  // Members
  // -------

  static nsAttrValue::EnumTable sAdditiveTable[];
  static nsAttrValue::EnumTable sCalcModeTable[];
  static nsAttrValue::EnumTable sAccumulateTable[];

  FallibleTArray<double>          mKeyTimes;
  FallibleTArray<nsSMILKeySpline> mKeySplines;

  // These are the parameters provided by the previous sample. Currently we
  // perform lazy calculation. That is, we only calculate the result if and when
  // instructed by the compositor. This allows us to apply the result directly
  // to the animation value and allows the compositor to filter out functions
  // that it determines will not contribute to the final result.
  nsSMILTime                    mSampleTime; // sample time within simple dur
  nsSMILTimeValue               mSimpleDuration;
  uint32_t                      mRepeatIteration;

  nsSMILTime                    mBeginTime; // document time

  // The owning animation element. This is used for sorting based on document
  // position and for fetching attribute values stored in the element.
  // Raw pointer is OK here, because this nsSMILAnimationFunction can't outlive
  // its owning animation element.
  mozilla::dom::SVGAnimationElement* mAnimationElement;

  // Which attributes have been set but have had errors. This is not used for
  // all attributes but only those which have specified error behaviour
  // associated with them.
  uint16_t                      mErrorFlags;

  // Allows us to check whether an animation function has changed target from
  // sample to sample (because if neither target nor animated value have
  // changed, we don't have to do anything).
  nsSMILWeakTargetIdentifier    mLastTarget;

  // Boolean flags
  bool mIsActive:1;
  bool mIsFrozen:1;
  bool mLastValue:1;
  bool mHasChanged:1;
  bool mValueNeedsReparsingEverySample:1;
  bool mPrevSampleWasSingleValueAnimation:1;
  bool mWasSkippedInPrevSample:1;