void ElementTransitions::EnsureStyleRuleFor(TimeStamp aRefreshTime) { if (!mStyleRule || mStyleRuleRefreshTime != aRefreshTime) { mStyleRule = new css::AnimValuesStyleRule(); mStyleRuleRefreshTime = aRefreshTime; for (PRUint32 i = 0, i_end = mPropertyTransitions.Length(); i < i_end; ++i) { ElementPropertyTransition &pt = mPropertyTransitions[i]; if (pt.IsRemovedSentinel()) { continue; } nsStyleAnimation::Value *val = mStyleRule->AddEmptyValue(pt.mProperty); double valuePortion = pt.ValuePortionFor(aRefreshTime); #ifdef DEBUG bool ok = #endif nsStyleAnimation::Interpolate(pt.mProperty, pt.mStartValue, pt.mEndValue, valuePortion, *val); NS_ABORT_IF_FALSE(ok, "could not interpolate values"); } } }
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"); } }
void ElementAnimations::EnsureStyleRuleFor(TimeStamp aRefreshTime, EventArray& aEventsToDispatch) { if (!mNeedsRefreshes) { // All of our animations are paused or completed. mStyleRuleRefreshTime = aRefreshTime; return; } // mStyleRule may be null and valid, if we have no style to apply. if (mStyleRuleRefreshTime.IsNull() || mStyleRuleRefreshTime != aRefreshTime) { mStyleRuleRefreshTime = aRefreshTime; mStyleRule = nsnull; // We'll set mNeedsRefreshes to true below in all cases where we need them. mNeedsRefreshes = false; // FIXME(spec): assume that properties in higher animations override // those in lower ones. // Therefore, we iterate from last animation to first. nsCSSPropertySet properties; for (PRUint32 animIdx = mAnimations.Length(); animIdx-- != 0; ) { ElementAnimation &anim = mAnimations[animIdx]; if (anim.mProperties.Length() == 0 || anim.mIterationDuration.ToMilliseconds() <= 0.0) { // No animation data. continue; } TimeDuration currentTimeDuration; if (anim.IsPaused()) { // FIXME: avoid recalculating every time currentTimeDuration = anim.mPauseStart - anim.mStartTime; } else { currentTimeDuration = aRefreshTime - anim.mStartTime; } // Set |currentIterationCount| to the (fractional) number of // iterations we've completed up to the current position. double currentIterationCount = currentTimeDuration / anim.mIterationDuration; bool dispatchStartOrIteration = false; if (currentIterationCount >= double(anim.mIterationCount)) { // Dispatch 'animationend' when needed. if (IsForElement() && anim.mLastNotification != ElementAnimation::LAST_NOTIFICATION_END) { anim.mLastNotification = ElementAnimation::LAST_NOTIFICATION_END; AnimationEventInfo ei(mElement, anim.mName, NS_ANIMATION_END, currentTimeDuration); aEventsToDispatch.AppendElement(ei); } if (!anim.FillsForwards()) { // No animation data. continue; } currentIterationCount = double(anim.mIterationCount); } else { if (!anim.IsPaused()) { mNeedsRefreshes = true; } if (currentIterationCount < 0.0) { if (!anim.FillsBackwards()) { // No animation data. continue; } currentIterationCount = 0.0; } else { dispatchStartOrIteration = !anim.IsPaused(); } } // Set |positionInIteration| to the position from 0% to 100% along // the keyframes. NS_ABORT_IF_FALSE(currentIterationCount >= 0.0, "must be positive"); PRUint32 whichIteration = int(currentIterationCount); if (whichIteration == anim.mIterationCount && whichIteration != 0) { // When the animation's iteration count is an integer (as it // normally is), we need to end at 100% of its last iteration // rather than 0% of the next one (unless it's zero). --whichIteration; } double positionInIteration = currentIterationCount - double(whichIteration); bool thisIterationReverse = false; switch (anim.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 = (whichIteration & 1) == 1; break; case NS_STYLE_ANIMATION_DIRECTION_ALTERNATE_REVERSE: thisIterationReverse = (whichIteration & 1) == 0; break; } if (thisIterationReverse) { positionInIteration = 1.0 - positionInIteration; } // Dispatch 'animationstart' or 'animationiteration' when needed. if (IsForElement() && dispatchStartOrIteration && whichIteration != anim.mLastNotification) { // Notify 'animationstart' even if a negative delay puts us // past the first iteration. // Note that when somebody changes the animation-duration // dynamically, this will fire an extra iteration event // immediately in many cases. It's not clear to me if that's the // right thing to do. PRUint32 message = anim.mLastNotification == ElementAnimation::LAST_NOTIFICATION_NONE ? NS_ANIMATION_START : NS_ANIMATION_ITERATION; anim.mLastNotification = whichIteration; AnimationEventInfo ei(mElement, anim.mName, message, currentTimeDuration); aEventsToDispatch.AppendElement(ei); } NS_ABORT_IF_FALSE(0.0 <= positionInIteration && positionInIteration <= 1.0, "position should be in [0-1]"); for (PRUint32 propIdx = 0, propEnd = anim.mProperties.Length(); propIdx != propEnd; ++propIdx) { const AnimationProperty &prop = anim.mProperties[propIdx]; NS_ABORT_IF_FALSE(prop.mSegments[0].mFromKey == 0.0, "incorrect first from key"); NS_ABORT_IF_FALSE(prop.mSegments[prop.mSegments.Length() - 1].mToKey == 1.0, "incorrect last to key"); if (properties.HasProperty(prop.mProperty)) { // A later animation already set this property. continue; } properties.AddProperty(prop.mProperty); NS_ABORT_IF_FALSE(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(); while (segment->mToKey < positionInIteration) { NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey, "incorrect keys"); ++segment; NS_ABORT_IF_FALSE(segment->mFromKey == (segment-1)->mToKey, "incorrect keys"); } NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey, "incorrect keys"); NS_ABORT_IF_FALSE(segment - prop.mSegments.Elements() < prop.mSegments.Length(), "ran off end"); if (!mStyleRule) { // Allocate the style rule now that we know we have animation data. mStyleRule = new css::AnimValuesStyleRule(); } double positionInSegment = (positionInIteration - segment->mFromKey) / (segment->mToKey - segment->mFromKey); double valuePosition = segment->mTimingFunction.GetValue(positionInSegment); nsStyleAnimation::Value *val = mStyleRule->AddEmptyValue(prop.mProperty); #ifdef DEBUG bool result = #endif nsStyleAnimation::Interpolate(prop.mProperty, segment->mFromValue, segment->mToValue, valuePosition, *val); NS_ABORT_IF_FALSE(result, "interpolate must succeed now"); } } } }