Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/animation/ComputedTimingFunction.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 file,
5
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "ComputedTimingFunction.h"
8
#include "nsAlgorithm.h" // For clamped()
9
#include "nsStyleUtil.h"
10
11
namespace mozilla {
12
13
void
14
ComputedTimingFunction::Init(const nsTimingFunction &aFunction)
15
0
{
16
0
  mType = aFunction.mType;
17
0
  if (nsTimingFunction::IsSplineType(mType)) {
18
0
    mTimingFunction.Init(aFunction.mFunc.mX1, aFunction.mFunc.mY1,
19
0
                         aFunction.mFunc.mX2, aFunction.mFunc.mY2);
20
0
  } else {
21
0
    mStepsOrFrames = aFunction.mStepsOrFrames;
22
0
  }
23
0
}
24
25
static inline double
26
StepTiming(uint32_t aSteps,
27
           double aPortion,
28
           ComputedTimingFunction::BeforeFlag aBeforeFlag,
29
           nsTimingFunction::Type aType)
30
0
{
31
0
  MOZ_ASSERT(aType == nsTimingFunction::Type::StepStart ||
32
0
             aType == nsTimingFunction::Type::StepEnd, "invalid type");
33
0
34
0
  // Calculate current step using step-end behavior
35
0
  int32_t step = floor(aPortion * aSteps);
36
0
37
0
  // step-start is one step ahead
38
0
  if (aType == nsTimingFunction::Type::StepStart) {
39
0
    step++;
40
0
  }
41
0
42
0
  // If the "before flag" is set and we are at a transition point,
43
0
  // drop back a step
44
0
  if (aBeforeFlag == ComputedTimingFunction::BeforeFlag::Set &&
45
0
      fmod(aPortion * aSteps, 1) == 0) {
46
0
    step--;
47
0
  }
48
0
49
0
  // Convert to a progress value
50
0
  double result = double(step) / double(aSteps);
51
0
52
0
  // We should not produce a result outside [0, 1] unless we have an
53
0
  // input outside that range. This takes care of steps that would otherwise
54
0
  // occur at boundaries.
55
0
  if (result < 0.0 && aPortion >= 0.0) {
56
0
    return 0.0;
57
0
  }
58
0
  if (result > 1.0 && aPortion <= 1.0) {
59
0
    return 1.0;
60
0
  }
61
0
  return result;
62
0
}
63
64
static inline double
65
FramesTiming(uint32_t aFrames, double aPortion)
66
0
{
67
0
  MOZ_ASSERT(aFrames > 1, "the number of frames must be greater than 1");
68
0
  int32_t currentFrame = floor(aPortion * aFrames);
69
0
  double result = double(currentFrame) / double(aFrames - 1);
70
0
71
0
  // Don't overshoot the natural range of the animation (by producing an output
72
0
  // progress greater than 1.0) when we are at the exact end of its interval
73
0
  // (i.e. the input progress is 1.0).
74
0
  if (result > 1.0 && aPortion <= 1.0) {
75
0
    return 1.0;
76
0
  }
77
0
  return result;
78
0
}
79
80
double
81
ComputedTimingFunction::GetValue(
82
    double aPortion,
83
    ComputedTimingFunction::BeforeFlag aBeforeFlag) const
84
0
{
85
0
  if (HasSpline()) {
86
0
    // Check for a linear curve.
87
0
    // (GetSplineValue(), below, also checks this but doesn't work when
88
0
    // aPortion is outside the range [0.0, 1.0]).
89
0
    if (mTimingFunction.X1() == mTimingFunction.Y1() &&
90
0
        mTimingFunction.X2() == mTimingFunction.Y2()) {
91
0
      return aPortion;
92
0
    }
93
0
94
0
    // Ensure that we return 0 or 1 on both edges.
95
0
    if (aPortion == 0.0) {
96
0
      return 0.0;
97
0
    }
98
0
    if (aPortion == 1.0) {
99
0
      return 1.0;
100
0
    }
101
0
102
0
    // For negative values, try to extrapolate with tangent (p1 - p0) or,
103
0
    // if p1 is coincident with p0, with (p2 - p0).
104
0
    if (aPortion < 0.0) {
105
0
      if (mTimingFunction.X1() > 0.0) {
106
0
        return aPortion * mTimingFunction.Y1() / mTimingFunction.X1();
107
0
      } else if (mTimingFunction.Y1() == 0 && mTimingFunction.X2() > 0.0) {
108
0
        return aPortion * mTimingFunction.Y2() / mTimingFunction.X2();
109
0
      }
110
0
      // If we can't calculate a sensible tangent, don't extrapolate at all.
111
0
      return 0.0;
112
0
    }
113
0
114
0
    // For values greater than 1, try to extrapolate with tangent (p2 - p3) or,
115
0
    // if p2 is coincident with p3, with (p1 - p3).
116
0
    if (aPortion > 1.0) {
117
0
      if (mTimingFunction.X2() < 1.0) {
118
0
        return 1.0 + (aPortion - 1.0) *
119
0
          (mTimingFunction.Y2() - 1) / (mTimingFunction.X2() - 1);
120
0
      } else if (mTimingFunction.Y2() == 1 && mTimingFunction.X1() < 1.0) {
121
0
        return 1.0 + (aPortion - 1.0) *
122
0
          (mTimingFunction.Y1() - 1) / (mTimingFunction.X1() - 1);
123
0
      }
124
0
      // If we can't calculate a sensible tangent, don't extrapolate at all.
125
0
      return 1.0;
126
0
    }
127
0
128
0
    return mTimingFunction.GetSplineValue(aPortion);
129
0
  }
130
0
131
0
  return mType == nsTimingFunction::Type::Frames
132
0
         ? FramesTiming(mStepsOrFrames, aPortion)
133
0
         : StepTiming(mStepsOrFrames, aPortion, aBeforeFlag, mType);
134
0
}
135
136
int32_t
137
ComputedTimingFunction::Compare(const ComputedTimingFunction& aRhs) const
138
0
{
139
0
  if (mType != aRhs.mType) {
140
0
    return int32_t(mType) - int32_t(aRhs.mType);
141
0
  }
142
0
143
0
  if (mType == nsTimingFunction::Type::CubicBezier) {
144
0
    int32_t order = mTimingFunction.Compare(aRhs.mTimingFunction);
145
0
    if (order != 0) {
146
0
      return order;
147
0
    }
148
0
  } else if (mType == nsTimingFunction::Type::StepStart ||
149
0
             mType == nsTimingFunction::Type::StepEnd ||
150
0
             mType == nsTimingFunction::Type::Frames) {
151
0
    if (mStepsOrFrames != aRhs.mStepsOrFrames) {
152
0
      return int32_t(mStepsOrFrames) - int32_t(aRhs.mStepsOrFrames);
153
0
    }
154
0
  }
155
0
156
0
  return 0;
157
0
}
158
159
void
160
ComputedTimingFunction::AppendToString(nsAString& aResult) const
161
{
162
  switch (mType) {
163
    case nsTimingFunction::Type::CubicBezier:
164
      nsStyleUtil::AppendCubicBezierTimingFunction(mTimingFunction.X1(),
165
                                                   mTimingFunction.Y1(),
166
                                                   mTimingFunction.X2(),
167
                                                   mTimingFunction.Y2(),
168
                                                   aResult);
169
      break;
170
    case nsTimingFunction::Type::StepStart:
171
    case nsTimingFunction::Type::StepEnd:
172
      nsStyleUtil::AppendStepsTimingFunction(mType, mStepsOrFrames, aResult);
173
      break;
174
    case nsTimingFunction::Type::Frames:
175
      nsStyleUtil::AppendFramesTimingFunction(mStepsOrFrames, aResult);
176
      break;
177
    default:
178
      nsStyleUtil::AppendCubicBezierKeywordTimingFunction(mType, aResult);
179
      break;
180
  }
181
}
182
183
/* static */ int32_t
184
ComputedTimingFunction::Compare(const Maybe<ComputedTimingFunction>& aLhs,
185
                                const Maybe<ComputedTimingFunction>& aRhs)
186
0
{
187
0
  // We can't use |operator<| for const Maybe<>& here because
188
0
  // 'ease' is prior to 'linear' which is represented by Nothing().
189
0
  // So we have to convert Nothing() as 'linear' and check it first.
190
0
  nsTimingFunction::Type lhsType = aLhs.isNothing() ?
191
0
    nsTimingFunction::Type::Linear : aLhs->GetType();
192
0
  nsTimingFunction::Type rhsType = aRhs.isNothing() ?
193
0
    nsTimingFunction::Type::Linear : aRhs->GetType();
194
0
195
0
  if (lhsType != rhsType) {
196
0
    return int32_t(lhsType) - int32_t(rhsType);
197
0
  }
198
0
199
0
  // Both of them are Nothing().
200
0
  if (lhsType == nsTimingFunction::Type::Linear) {
201
0
    return 0;
202
0
  }
203
0
204
0
  // Other types.
205
0
  return aLhs->Compare(aRhs.value());
206
0
}
207
208
} // namespace mozilla