void CSSAnimation::QueueEvents(EventArray& aEventsToDispatch) { if (!mEffect) { return; } ComputedTiming computedTiming = mEffect->GetComputedTiming(); if (computedTiming.mPhase == ComputedTiming::AnimationPhase_Null) { return; // do nothing } // Note that script can change the start time, so we have to handle moving // backwards through the animation as well as forwards. An 'animationstart' // is dispatched if we enter the active phase (regardless if that is from // before or after the animation's active phase). An 'animationend' is // dispatched if we leave the active phase (regardless if that is to before // or after the animation's active phase). bool wasActive = mPreviousPhaseOrIteration != PREVIOUS_PHASE_BEFORE && mPreviousPhaseOrIteration != PREVIOUS_PHASE_AFTER; bool isActive = computedTiming.mPhase == ComputedTiming::AnimationPhase_Active; bool isSameIteration = computedTiming.mCurrentIteration == mPreviousPhaseOrIteration; bool skippedActivePhase = (mPreviousPhaseOrIteration == PREVIOUS_PHASE_BEFORE && computedTiming.mPhase == ComputedTiming::AnimationPhase_After) || (mPreviousPhaseOrIteration == PREVIOUS_PHASE_AFTER && computedTiming.mPhase == ComputedTiming::AnimationPhase_Before); MOZ_ASSERT(!skippedActivePhase || (!isActive && !wasActive), "skippedActivePhase only makes sense if we were & are inactive"); if (computedTiming.mPhase == ComputedTiming::AnimationPhase_Before) { mPreviousPhaseOrIteration = PREVIOUS_PHASE_BEFORE; } else if (computedTiming.mPhase == ComputedTiming::AnimationPhase_Active) { mPreviousPhaseOrIteration = computedTiming.mCurrentIteration; } else if (computedTiming.mPhase == ComputedTiming::AnimationPhase_After) { mPreviousPhaseOrIteration = PREVIOUS_PHASE_AFTER; } dom::Element* target; nsCSSPseudoElements::Type targetPseudoType; mEffect->GetTarget(target, targetPseudoType); uint32_t message; if (!wasActive && isActive) { message = NS_ANIMATION_START; } else if (wasActive && !isActive) { message = NS_ANIMATION_END; } else if (wasActive && isActive && !isSameIteration) { message = NS_ANIMATION_ITERATION; } else if (skippedActivePhase) { // First notifying for start of 0th iteration by appending an // 'animationstart': StickyTimeDuration elapsedTime = std::min(StickyTimeDuration(mEffect->InitialAdvance()), computedTiming.mActiveDuration); AnimationEventInfo ei(target, Name(), NS_ANIMATION_START, elapsedTime, PseudoTypeAsString(targetPseudoType)); aEventsToDispatch.AppendElement(ei); // Then have the shared code below append an 'animationend': message = NS_ANIMATION_END; } else { return; // No events need to be sent } StickyTimeDuration elapsedTime; if (message == NS_ANIMATION_START || message == NS_ANIMATION_ITERATION) { TimeDuration iterationStart = mEffect->Timing().mIterationDuration * computedTiming.mCurrentIteration; elapsedTime = StickyTimeDuration(std::max(iterationStart, mEffect->InitialAdvance())); } else { MOZ_ASSERT(message == NS_ANIMATION_END); elapsedTime = computedTiming.mActiveDuration; } AnimationEventInfo ei(target, Name(), message, elapsedTime, PseudoTypeAsString(targetPseudoType)); aEventsToDispatch.AppendElement(ei); }
void nsAnimationManager::GetEventsForCurrentTime(AnimationPlayerCollection* aCollection, EventArray& aEventsToDispatch) { for (size_t playerIdx = aCollection->mPlayers.Length(); playerIdx-- != 0; ) { AnimationPlayer* player = aCollection->mPlayers[playerIdx]; Animation* anim = player->GetSource(); if (!anim) { continue; } ComputedTiming computedTiming = anim->GetComputedTiming(); switch (computedTiming.mPhase) { case ComputedTiming::AnimationPhase_Null: case ComputedTiming::AnimationPhase_Before: // Do nothing break; case ComputedTiming::AnimationPhase_Active: // Dispatch 'animationstart' or 'animationiteration' when needed. if (computedTiming.mCurrentIteration != anim->LastNotification()) { // 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. uint32_t message = anim->LastNotification() == Animation::LAST_NOTIFICATION_NONE ? NS_ANIMATION_START : NS_ANIMATION_ITERATION; anim->SetLastNotification(computedTiming.mCurrentIteration); TimeDuration iterationStart = anim->Timing().mIterationDuration * computedTiming.mCurrentIteration; TimeDuration elapsedTime = std::max(iterationStart, anim->InitialAdvance()); AnimationEventInfo ei(aCollection->mElement, player->Name(), message, StickyTimeDuration(elapsedTime), aCollection->PseudoElement()); aEventsToDispatch.AppendElement(ei); } break; case ComputedTiming::AnimationPhase_After: // If we skipped the animation interval entirely, dispatch // 'animationstart' first if (anim->LastNotification() == Animation::LAST_NOTIFICATION_NONE) { // Notifying for start of 0th iteration. // (This is overwritten below but we set it here to maintain // internal consistency.) anim->SetLastNotification(0); StickyTimeDuration elapsedTime = std::min(StickyTimeDuration(anim->InitialAdvance()), computedTiming.mActiveDuration); AnimationEventInfo ei(aCollection->mElement, player->Name(), NS_ANIMATION_START, elapsedTime, aCollection->PseudoElement()); aEventsToDispatch.AppendElement(ei); } // Dispatch 'animationend' when needed. if (anim->LastNotification() != Animation::LAST_NOTIFICATION_END) { anim->SetLastNotification(Animation::LAST_NOTIFICATION_END); AnimationEventInfo ei(aCollection->mElement, player->Name(), NS_ANIMATION_END, computedTiming.mActiveDuration, aCollection->PseudoElement()); aEventsToDispatch.AppendElement(ei); } break; } } }
void CSSAnimationPlayer::QueueEvents(EventArray& aEventsToDispatch) { if (!mSource) { return; } ComputedTiming computedTiming = mSource->GetComputedTiming(); dom::Element* target; nsCSSPseudoElements::Type targetPseudoType; mSource->GetTarget(target, targetPseudoType); switch (computedTiming.mPhase) { case ComputedTiming::AnimationPhase_Null: case ComputedTiming::AnimationPhase_Before: // Do nothing break; case ComputedTiming::AnimationPhase_Active: // Dispatch 'animationstart' or 'animationiteration' when needed. if (computedTiming.mCurrentIteration != 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. uint32_t message = mLastNotification == LAST_NOTIFICATION_NONE ? NS_ANIMATION_START : NS_ANIMATION_ITERATION; mLastNotification = computedTiming.mCurrentIteration; TimeDuration iterationStart = mSource->Timing().mIterationDuration * computedTiming.mCurrentIteration; TimeDuration elapsedTime = std::max(iterationStart, mSource->InitialAdvance()); AnimationEventInfo ei(target, Name(), message, StickyTimeDuration(elapsedTime), PseudoTypeAsString(targetPseudoType)); aEventsToDispatch.AppendElement(ei); } break; case ComputedTiming::AnimationPhase_After: // If we skipped the animation interval entirely, dispatch // 'animationstart' first if (mLastNotification == LAST_NOTIFICATION_NONE) { // Notifying for start of 0th iteration. // (This is overwritten below but we set it here to maintain // internal consistency.) mLastNotification = 0; StickyTimeDuration elapsedTime = std::min(StickyTimeDuration(mSource->InitialAdvance()), computedTiming.mActiveDuration); AnimationEventInfo ei(target, Name(), NS_ANIMATION_START, elapsedTime, PseudoTypeAsString(targetPseudoType)); aEventsToDispatch.AppendElement(ei); } // Dispatch 'animationend' when needed. if (mLastNotification != LAST_NOTIFICATION_END) { mLastNotification = LAST_NOTIFICATION_END; AnimationEventInfo ei(target, Name(), NS_ANIMATION_END, computedTiming.mActiveDuration, PseudoTypeAsString(targetPseudoType)); aEventsToDispatch.AppendElement(ei); } break; } }
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"); } } } }