double
ComputedTimingFunction::GetValue(double aPortion) const
{
  switch (mType) {
    case nsTimingFunction::Function:
      return mTimingFunction.GetSplineValue(aPortion);
    case nsTimingFunction::StepStart:
      // There are diagrams in the spec that seem to suggest this check
      // and the bounds point should not be symmetric with StepEnd, but
      // should actually step up at rather than immediately after the
      // fraction points.  However, we rely on rounding negative values
      // up to zero, so we can't do that.  And it's not clear the spec
      // really meant it.
      return 1.0 - StepEnd(mSteps, 1.0 - aPortion);
    default:
      NS_ABORT_IF_FALSE(false, "bad type");
      // fall through
    case nsTimingFunction::StepEnd:
      return StepEnd(mSteps, aPortion);
  }
}
double
ComputedTimingFunction::GetValue(double aPortion) const
{
  if (HasSpline()) {
    // Check for a linear curve.
    // (GetSplineValue(), below, also checks this but doesn't work when
    // aPortion is outside the range [0.0, 1.0]).
    if (mTimingFunction.X1() == mTimingFunction.Y1() &&
        mTimingFunction.X2() == mTimingFunction.Y2()) {
      return aPortion;
    }

    // For negative values, try to extrapolate with tangent (p1 - p0) or,
    // if p1 is coincident with p0, with (p2 - p0).
    if (aPortion < 0.0) {
      if (mTimingFunction.X1() > 0.0) {
        return aPortion * mTimingFunction.Y1() / mTimingFunction.X1();
      } else if (mTimingFunction.Y1() == 0 && mTimingFunction.X2() > 0.0) {
        return aPortion * mTimingFunction.Y2() / mTimingFunction.X2();
      }
      // If we can't calculate a sensible tangent, don't extrapolate at all.
      return 0.0;
    }

    // For values greater than 1, try to extrapolate with tangent (p2 - p3) or,
    // if p2 is coincident with p3, with (p1 - p3).
    if (aPortion > 1.0) {
      if (mTimingFunction.X2() < 1.0) {
        return 1.0 + (aPortion - 1.0) *
          (mTimingFunction.Y2() - 1) / (mTimingFunction.X2() - 1);
      } else if (mTimingFunction.Y2() == 1 && mTimingFunction.X1() < 1.0) {
        return 1.0 + (aPortion - 1.0) *
          (mTimingFunction.Y1() - 1) / (mTimingFunction.X1() - 1);
      }
      // If we can't calculate a sensible tangent, don't extrapolate at all.
      return 1.0;
    }

    return mTimingFunction.GetSplineValue(aPortion);
  }

  // Since we use endpoint-exclusive timing, the output of a steps(start) timing
  // function when aPortion = 0.0 is the top of the first step. When aPortion is
  // negative, however, we should use the bottom of the first step. We handle
  // negative values of aPortion specially here since once we clamp aPortion
  // to [0,1] below we will no longer be able to distinguish to the two cases.
  if (aPortion < 0.0) {
    return 0.0;
  }

  // Clamp in case of steps(end) and steps(start) for values greater than 1.
  aPortion = clamped(aPortion, 0.0, 1.0);

  if (mType == nsTimingFunction::Type::StepStart) {
    // There are diagrams in the spec that seem to suggest this check
    // and the bounds point should not be symmetric with StepEnd, but
    // should actually step up at rather than immediately after the
    // fraction points.  However, we rely on rounding negative values
    // up to zero, so we can't do that.  And it's not clear the spec
    // really meant it.
    return 1.0 - StepEnd(mSteps, 1.0 - aPortion);
  }
  MOZ_ASSERT(mType == nsTimingFunction::Type::StepEnd, "bad type");
  return StepEnd(mSteps, aPortion);
}