2014-08-10 00:06:46 -07:00
|
|
|
/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
|
|
|
|
/* 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 http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
|
|
#include "mozilla/dom/Animation.h"
|
|
|
|
#include "mozilla/dom/AnimationBinding.h"
|
2014-08-22 05:42:47 -07:00
|
|
|
#include "mozilla/dom/AnimationEffect.h"
|
2014-08-10 00:06:50 -07:00
|
|
|
#include "mozilla/FloatingPoint.h"
|
2014-08-10 00:06:46 -07:00
|
|
|
|
|
|
|
namespace mozilla {
|
2014-08-10 00:06:50 -07:00
|
|
|
|
|
|
|
void
|
|
|
|
ComputedTimingFunction::Init(const nsTimingFunction &aFunction)
|
|
|
|
{
|
|
|
|
mType = aFunction.mType;
|
|
|
|
if (mType == nsTimingFunction::Function) {
|
|
|
|
mTimingFunction.Init(aFunction.mFunc.mX1, aFunction.mFunc.mY1,
|
|
|
|
aFunction.mFunc.mX2, aFunction.mFunc.mY2);
|
|
|
|
} else {
|
|
|
|
mSteps = aFunction.mSteps;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline double
|
|
|
|
StepEnd(uint32_t aSteps, double aPortion)
|
|
|
|
{
|
|
|
|
NS_ABORT_IF_FALSE(0.0 <= aPortion && aPortion <= 1.0, "out of range");
|
|
|
|
uint32_t step = uint32_t(aPortion * aSteps); // floor
|
|
|
|
return double(step) / double(aSteps);
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// In the Web Animations model, the time fraction can be outside the range
|
|
|
|
// [0.0, 1.0] but it shouldn't be Infinity.
|
|
|
|
const double ComputedTiming::kNullTimeFraction = PositiveInfinity<double>();
|
|
|
|
|
2014-08-10 00:06:46 -07:00
|
|
|
namespace dom {
|
|
|
|
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Animation, mDocument)
|
|
|
|
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(Animation, AddRef)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(Animation, Release)
|
|
|
|
|
|
|
|
JSObject*
|
|
|
|
Animation::WrapObject(JSContext* aCx)
|
|
|
|
{
|
|
|
|
return AnimationBinding::Wrap(aCx, this);
|
|
|
|
}
|
|
|
|
|
2014-08-22 05:42:47 -07:00
|
|
|
already_AddRefed<AnimationEffect>
|
|
|
|
Animation::GetEffect()
|
|
|
|
{
|
|
|
|
nsRefPtr<AnimationEffect> effect = new AnimationEffect(this);
|
|
|
|
return effect.forget();
|
|
|
|
}
|
|
|
|
|
2014-08-10 00:06:48 -07:00
|
|
|
void
|
|
|
|
Animation::SetParentTime(Nullable<TimeDuration> aParentTime)
|
|
|
|
{
|
|
|
|
mParentTime = aParentTime;
|
|
|
|
}
|
|
|
|
|
2014-08-10 00:06:50 -07:00
|
|
|
ComputedTiming
|
|
|
|
Animation::GetComputedTimingAt(const Nullable<TimeDuration>& aLocalTime,
|
|
|
|
const AnimationTiming& aTiming)
|
|
|
|
{
|
|
|
|
const TimeDuration zeroDuration;
|
|
|
|
|
|
|
|
// Currently we expect negative durations to be picked up during CSS
|
|
|
|
// parsing but when we start receiving timing parameters from other sources
|
|
|
|
// we will need to clamp negative durations here.
|
|
|
|
// For now, if we're hitting this it probably means we're overflowing
|
|
|
|
// integer arithmetic in mozilla::TimeStamp.
|
|
|
|
MOZ_ASSERT(aTiming.mIterationDuration >= zeroDuration,
|
|
|
|
"Expecting iteration duration >= 0");
|
|
|
|
|
|
|
|
// Always return the same object to benefit from return-value optimization.
|
|
|
|
ComputedTiming result;
|
|
|
|
|
|
|
|
result.mActiveDuration = ActiveDuration(aTiming);
|
|
|
|
|
|
|
|
// The default constructor for ComputedTiming sets all other members to
|
|
|
|
// values consistent with an animation that has not been sampled.
|
|
|
|
if (aLocalTime.IsNull()) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
const TimeDuration& localTime = aLocalTime.Value();
|
|
|
|
|
|
|
|
// When we finish exactly at the end of an iteration we need to report
|
|
|
|
// the end of the final iteration and not the start of the next iteration
|
|
|
|
// so we set up a flag for that case.
|
|
|
|
bool isEndOfFinalIteration = false;
|
|
|
|
|
|
|
|
// Get the normalized time within the active interval.
|
2014-09-24 22:25:50 -07:00
|
|
|
StickyTimeDuration activeTime;
|
|
|
|
if (localTime >= aTiming.mDelay + result.mActiveDuration) {
|
2014-08-10 00:06:50 -07:00
|
|
|
result.mPhase = ComputedTiming::AnimationPhase_After;
|
|
|
|
if (!aTiming.FillsForwards()) {
|
|
|
|
// The animation isn't active or filling at this time.
|
|
|
|
result.mTimeFraction = ComputedTiming::kNullTimeFraction;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
activeTime = result.mActiveDuration;
|
|
|
|
// Note that infinity == floor(infinity) so this will also be true when we
|
|
|
|
// have finished an infinitely repeating animation of zero duration.
|
|
|
|
isEndOfFinalIteration =
|
|
|
|
aTiming.mIterationCount != 0.0 &&
|
|
|
|
aTiming.mIterationCount == floor(aTiming.mIterationCount);
|
|
|
|
} else if (localTime < aTiming.mDelay) {
|
|
|
|
result.mPhase = ComputedTiming::AnimationPhase_Before;
|
|
|
|
if (!aTiming.FillsBackwards()) {
|
|
|
|
// The animation isn't active or filling at this time.
|
|
|
|
result.mTimeFraction = ComputedTiming::kNullTimeFraction;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
// activeTime is zero
|
|
|
|
} else {
|
|
|
|
MOZ_ASSERT(result.mActiveDuration != zeroDuration,
|
|
|
|
"How can we be in the middle of a zero-duration interval?");
|
|
|
|
result.mPhase = ComputedTiming::AnimationPhase_Active;
|
|
|
|
activeTime = localTime - aTiming.mDelay;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the position within the current iteration.
|
2014-09-24 22:25:50 -07:00
|
|
|
StickyTimeDuration iterationTime;
|
2014-08-10 00:06:50 -07:00
|
|
|
if (aTiming.mIterationDuration != zeroDuration) {
|
|
|
|
iterationTime = isEndOfFinalIteration
|
2014-09-24 22:25:50 -07:00
|
|
|
? StickyTimeDuration(aTiming.mIterationDuration)
|
2014-08-10 00:06:50 -07:00
|
|
|
: activeTime % aTiming.mIterationDuration;
|
|
|
|
} /* else, iterationTime is zero */
|
|
|
|
|
|
|
|
// Determine the 0-based index of the current iteration.
|
|
|
|
if (isEndOfFinalIteration) {
|
|
|
|
result.mCurrentIteration =
|
|
|
|
aTiming.mIterationCount == NS_IEEEPositiveInfinity()
|
|
|
|
? UINT64_MAX // FIXME: When we return this via the API we'll need
|
|
|
|
// to make sure it ends up being infinity.
|
|
|
|
: static_cast<uint64_t>(aTiming.mIterationCount) - 1;
|
|
|
|
} else if (activeTime == zeroDuration) {
|
|
|
|
// If the active time is zero we're either in the first iteration
|
|
|
|
// (including filling backwards) or we have finished an animation with an
|
|
|
|
// iteration duration of zero that is filling forwards (but we're not at
|
|
|
|
// the exact end of an iteration since we deal with that above).
|
|
|
|
result.mCurrentIteration =
|
|
|
|
result.mPhase == ComputedTiming::AnimationPhase_After
|
|
|
|
? static_cast<uint64_t>(aTiming.mIterationCount) // floor
|
|
|
|
: 0;
|
|
|
|
} else {
|
|
|
|
result.mCurrentIteration =
|
|
|
|
static_cast<uint64_t>(activeTime / aTiming.mIterationDuration); // floor
|
|
|
|
}
|
|
|
|
|
|
|
|
// Normalize the iteration time into a fraction of the iteration duration.
|
|
|
|
if (result.mPhase == ComputedTiming::AnimationPhase_Before) {
|
|
|
|
result.mTimeFraction = 0.0;
|
|
|
|
} else if (result.mPhase == ComputedTiming::AnimationPhase_After) {
|
|
|
|
result.mTimeFraction = isEndOfFinalIteration
|
|
|
|
? 1.0
|
|
|
|
: fmod(aTiming.mIterationCount, 1.0f);
|
|
|
|
} else {
|
|
|
|
// We are in the active phase so the iteration duration can't be zero.
|
|
|
|
MOZ_ASSERT(aTiming.mIterationDuration != zeroDuration,
|
|
|
|
"In the active phase of a zero-duration animation?");
|
|
|
|
result.mTimeFraction =
|
|
|
|
aTiming.mIterationDuration == TimeDuration::Forever()
|
|
|
|
? 0.0
|
|
|
|
: iterationTime / aTiming.mIterationDuration;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool thisIterationReverse = false;
|
|
|
|
switch (aTiming.mDirection) {
|
|
|
|
case NS_STYLE_ANIMATION_DIRECTION_NORMAL:
|
|
|
|
thisIterationReverse = false;
|
|
|
|
break;
|
|
|
|
case NS_STYLE_ANIMATION_DIRECTION_REVERSE:
|
|
|
|
thisIterationReverse = true;
|
|
|
|
break;
|
|
|
|
case NS_STYLE_ANIMATION_DIRECTION_ALTERNATE:
|
|
|
|
thisIterationReverse = (result.mCurrentIteration & 1) == 1;
|
|
|
|
break;
|
|
|
|
case NS_STYLE_ANIMATION_DIRECTION_ALTERNATE_REVERSE:
|
|
|
|
thisIterationReverse = (result.mCurrentIteration & 1) == 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (thisIterationReverse) {
|
|
|
|
result.mTimeFraction = 1.0 - result.mTimeFraction;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2014-09-24 22:25:50 -07:00
|
|
|
StickyTimeDuration
|
2014-08-10 00:06:50 -07:00
|
|
|
Animation::ActiveDuration(const AnimationTiming& aTiming)
|
|
|
|
{
|
|
|
|
if (aTiming.mIterationCount == mozilla::PositiveInfinity<float>()) {
|
|
|
|
// An animation that repeats forever has an infinite active duration
|
|
|
|
// unless its iteration duration is zero, in which case it has a zero
|
|
|
|
// active duration.
|
2014-09-24 22:25:50 -07:00
|
|
|
const StickyTimeDuration zeroDuration;
|
2014-08-10 00:06:50 -07:00
|
|
|
return aTiming.mIterationDuration == zeroDuration
|
|
|
|
? zeroDuration
|
2014-09-24 22:25:50 -07:00
|
|
|
: StickyTimeDuration::Forever();
|
2014-08-10 00:06:50 -07:00
|
|
|
}
|
2014-09-24 22:25:50 -07:00
|
|
|
return StickyTimeDuration(
|
|
|
|
aTiming.mIterationDuration.MultDouble(aTiming.mIterationCount));
|
2014-08-10 00:06:50 -07:00
|
|
|
}
|
|
|
|
|
2014-10-01 23:14:14 -07:00
|
|
|
bool
|
|
|
|
Animation::IsCurrent() const
|
|
|
|
{
|
|
|
|
if (IsFinishedTransition()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
ComputedTiming computedTiming = GetComputedTiming();
|
|
|
|
return computedTiming.mPhase == ComputedTiming::AnimationPhase_Before ||
|
|
|
|
computedTiming.mPhase == ComputedTiming::AnimationPhase_Active;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
|
|
Animation::IsInEffect() const
|
|
|
|
{
|
|
|
|
if (IsFinishedTransition()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
ComputedTiming computedTiming = GetComputedTiming();
|
|
|
|
return computedTiming.mTimeFraction != ComputedTiming::kNullTimeFraction;
|
|
|
|
}
|
|
|
|
|
2014-08-10 00:06:49 -07:00
|
|
|
bool
|
|
|
|
Animation::HasAnimationOfProperty(nsCSSProperty aProperty) const
|
|
|
|
{
|
|
|
|
for (size_t propIdx = 0, propEnd = mProperties.Length();
|
|
|
|
propIdx != propEnd; ++propIdx) {
|
|
|
|
if (aProperty == mProperties[propIdx].mProperty) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2014-08-10 00:06:46 -07:00
|
|
|
} // namespace dom
|
|
|
|
} // namespace mozilla
|