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-10-19 21:55:46 -07:00
|
|
|
#include "AnimationCommon.h"
|
|
|
|
#include "nsCSSPropertySet.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 {
|
|
|
|
|
2014-10-01 23:14:15 -07:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Animation, mDocument, mTarget)
|
2014-08-10 00:06:46 -07:00
|
|
|
|
|
|
|
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-10-19 21:55:46 -07:00
|
|
|
void
|
|
|
|
Animation::ComposeStyle(nsRefPtr<css::AnimValuesStyleRule>& aStyleRule,
|
|
|
|
nsCSSPropertySet& aSetProperties)
|
|
|
|
{
|
|
|
|
ComputedTiming computedTiming = GetComputedTiming();
|
|
|
|
|
|
|
|
// If the time fraction is null, we don't have fill data for the current
|
|
|
|
// time so we shouldn't animate.
|
|
|
|
if (computedTiming.mTimeFraction == ComputedTiming::kNullTimeFraction) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
MOZ_ASSERT(0.0 <= computedTiming.mTimeFraction &&
|
|
|
|
computedTiming.mTimeFraction <= 1.0,
|
|
|
|
"timing fraction should be in [0-1]");
|
|
|
|
|
|
|
|
for (size_t propIdx = 0, propEnd = mProperties.Length();
|
|
|
|
propIdx != propEnd; ++propIdx)
|
|
|
|
{
|
|
|
|
const AnimationProperty& prop = mProperties[propIdx];
|
|
|
|
|
|
|
|
MOZ_ASSERT(prop.mSegments[0].mFromKey == 0.0, "incorrect first from key");
|
|
|
|
MOZ_ASSERT(prop.mSegments[prop.mSegments.Length() - 1].mToKey == 1.0,
|
|
|
|
"incorrect last to key");
|
|
|
|
|
|
|
|
if (aSetProperties.HasProperty(prop.mProperty)) {
|
|
|
|
// Animations are composed by AnimationPlayerCollection by iterating
|
|
|
|
// from the last animation to first. For animations targetting the
|
|
|
|
// same property, the later one wins. So if this property is already set,
|
|
|
|
// we should not override it.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
aSetProperties.AddProperty(prop.mProperty);
|
|
|
|
|
|
|
|
MOZ_ASSERT(prop.mSegments.Length() > 0,
|
|
|
|
"property should not be in animations if it has no segments");
|
|
|
|
|
|
|
|
// FIXME: Maybe cache the current segment?
|
|
|
|
const AnimationPropertySegment *segment = prop.mSegments.Elements(),
|
|
|
|
*segmentEnd = segment + prop.mSegments.Length();
|
|
|
|
while (segment->mToKey < computedTiming.mTimeFraction) {
|
|
|
|
MOZ_ASSERT(segment->mFromKey < segment->mToKey, "incorrect keys");
|
|
|
|
++segment;
|
|
|
|
if (segment == segmentEnd) {
|
|
|
|
MOZ_ASSERT_UNREACHABLE("incorrect time fraction");
|
|
|
|
break; // in order to continue in outer loop (just below)
|
|
|
|
}
|
|
|
|
MOZ_ASSERT(segment->mFromKey == (segment-1)->mToKey, "incorrect keys");
|
|
|
|
}
|
|
|
|
if (segment == segmentEnd) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
MOZ_ASSERT(segment->mFromKey < segment->mToKey, "incorrect keys");
|
|
|
|
MOZ_ASSERT(segment >= prop.mSegments.Elements() &&
|
|
|
|
size_t(segment - prop.mSegments.Elements()) <
|
|
|
|
prop.mSegments.Length(),
|
|
|
|
"out of array bounds");
|
|
|
|
|
|
|
|
if (!aStyleRule) {
|
|
|
|
// Allocate the style rule now that we know we have animation data.
|
|
|
|
aStyleRule = new css::AnimValuesStyleRule();
|
|
|
|
}
|
|
|
|
|
|
|
|
double positionInSegment =
|
|
|
|
(computedTiming.mTimeFraction - segment->mFromKey) /
|
|
|
|
(segment->mToKey - segment->mFromKey);
|
|
|
|
double valuePosition =
|
|
|
|
segment->mTimingFunction.GetValue(positionInSegment);
|
|
|
|
|
|
|
|
StyleAnimationValue *val = aStyleRule->AddEmptyValue(prop.mProperty);
|
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
bool result =
|
|
|
|
#endif
|
|
|
|
StyleAnimationValue::Interpolate(prop.mProperty,
|
|
|
|
segment->mFromValue,
|
|
|
|
segment->mToValue,
|
|
|
|
valuePosition, *val);
|
|
|
|
MOZ_ASSERT(result, "interpolate must succeed now");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-10 00:06:46 -07:00
|
|
|
} // namespace dom
|
|
|
|
} // namespace mozilla
|