void nsAnimationManager::BuildAnimations(nsStyleContext* aStyleContext, InfallibleTArray<ElementAnimation>& aAnimations) { NS_ABORT_IF_FALSE(aAnimations.IsEmpty(), "expect empty array"); ResolvedStyleCache resolvedStyles; const nsStyleDisplay *disp = aStyleContext->StyleDisplay(); TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh(); for (uint32_t animIdx = 0, animEnd = disp->mAnimationNameCount; animIdx != animEnd; ++animIdx) { const nsAnimation& aSrc = disp->mAnimations[animIdx]; ElementAnimation& aDest = *aAnimations.AppendElement(); aDest.mName = aSrc.GetName(); aDest.mIterationCount = aSrc.GetIterationCount(); aDest.mDirection = aSrc.GetDirection(); aDest.mFillMode = aSrc.GetFillMode(); aDest.mPlayState = aSrc.GetPlayState(); aDest.mDelay = TimeDuration::FromMilliseconds(aSrc.GetDelay()); aDest.mStartTime = now; if (aDest.IsPaused()) { aDest.mPauseStart = now; } else { aDest.mPauseStart = TimeStamp(); } aDest.mIterationDuration = TimeDuration::FromMilliseconds(aSrc.GetDuration()); nsCSSKeyframesRule* rule = mPresContext->StyleSet()->KeyframesRuleForName(mPresContext, aDest.mName); if (!rule) { // no segments continue; } // While current drafts of css3-animations say that later keyframes // with the same key entirely replace earlier ones (no cascading), // this is a bad idea and contradictory to the rest of CSS. So // we're going to keep all the keyframes for each key and then do // the replacement on a per-property basis rather than a per-rule // basis, just like everything else in CSS. AutoInfallibleTArray<KeyframeData, 16> sortedKeyframes; for (uint32_t ruleIdx = 0, ruleEnd = rule->StyleRuleCount(); ruleIdx != ruleEnd; ++ruleIdx) { css::Rule* cssRule = rule->GetStyleRuleAt(ruleIdx); NS_ABORT_IF_FALSE(cssRule, "must have rule"); NS_ABORT_IF_FALSE(cssRule->GetType() == css::Rule::KEYFRAME_RULE, "must be keyframe rule"); nsCSSKeyframeRule *kfRule = static_cast<nsCSSKeyframeRule*>(cssRule); const nsTArray<float> &keys = kfRule->GetKeys(); for (uint32_t keyIdx = 0, keyEnd = keys.Length(); keyIdx != keyEnd; ++keyIdx) { float key = keys[keyIdx]; // FIXME (spec): The spec doesn't say what to do with // out-of-range keyframes. We'll ignore them. // (And PercentageHashKey currently assumes we either ignore or // clamp them.) if (0.0f <= key && key <= 1.0f) { KeyframeData *data = sortedKeyframes.AppendElement(); data->mKey = key; data->mIndex = ruleIdx; data->mRule = kfRule; } } } sortedKeyframes.Sort(KeyframeDataComparator()); if (sortedKeyframes.Length() == 0) { // no segments continue; } // Record the properties that are present in any keyframe rules we // are using. nsCSSPropertySet properties; for (uint32_t kfIdx = 0, kfEnd = sortedKeyframes.Length(); kfIdx != kfEnd; ++kfIdx) { css::Declaration *decl = sortedKeyframes[kfIdx].mRule->Declaration(); for (uint32_t propIdx = 0, propEnd = decl->Count(); propIdx != propEnd; ++propIdx) { properties.AddProperty(decl->OrderValueAt(propIdx)); } } for (nsCSSProperty prop = nsCSSProperty(0); prop < eCSSProperty_COUNT_no_shorthands; prop = nsCSSProperty(prop + 1)) { if (!properties.HasProperty(prop) || nsCSSProps::kAnimTypeTable[prop] == eStyleAnimType_None) { continue; } // Build a list of the keyframes to use for this property. This // means we need every keyframe with the property in it, except // for those keyframes where a later keyframe with the *same key* // also has the property. AutoInfallibleTArray<uint32_t, 16> keyframesWithProperty; float lastKey = 100.0f; // an invalid key for (uint32_t kfIdx = 0, kfEnd = sortedKeyframes.Length(); kfIdx != kfEnd; ++kfIdx) { KeyframeData &kf = sortedKeyframes[kfIdx]; if (!kf.mRule->Declaration()->HasProperty(prop)) { continue; } if (kf.mKey == lastKey) { // Replace previous occurrence of same key. keyframesWithProperty[keyframesWithProperty.Length() - 1] = kfIdx; } else { keyframesWithProperty.AppendElement(kfIdx); } lastKey = kf.mKey; } AnimationProperty &propData = *aDest.mProperties.AppendElement(); propData.mProperty = prop; KeyframeData *fromKeyframe = nullptr; nsRefPtr<nsStyleContext> fromContext; bool interpolated = true; for (uint32_t wpIdx = 0, wpEnd = keyframesWithProperty.Length(); wpIdx != wpEnd; ++wpIdx) { uint32_t kfIdx = keyframesWithProperty[wpIdx]; KeyframeData &toKeyframe = sortedKeyframes[kfIdx]; nsRefPtr<nsStyleContext> toContext = resolvedStyles.Get(mPresContext, aStyleContext, toKeyframe.mRule); if (fromKeyframe) { interpolated = interpolated && BuildSegment(propData.mSegments, prop, aSrc, fromKeyframe->mKey, fromContext, fromKeyframe->mRule->Declaration(), toKeyframe.mKey, toContext); } else { if (toKeyframe.mKey != 0.0f) { // There's no data for this property at 0%, so use the // cascaded value above us. interpolated = interpolated && BuildSegment(propData.mSegments, prop, aSrc, 0.0f, aStyleContext, nullptr, toKeyframe.mKey, toContext); } } fromContext = toContext; fromKeyframe = &toKeyframe; } if (fromKeyframe->mKey != 1.0f) { // There's no data for this property at 100%, so use the // cascaded value above us. interpolated = interpolated && BuildSegment(propData.mSegments, prop, aSrc, fromKeyframe->mKey, fromContext, fromKeyframe->mRule->Declaration(), 1.0f, aStyleContext); } // If we failed to build any segments due to inability to // interpolate, remove the property from the animation. (It's not // clear if this is the right thing to do -- we could run some of // the segments, but it's really not clear whether we should skip // values (which?) or skip segments, so best to skip the whole // thing for now.) if (!interpolated) { aDest.mProperties.RemoveElementAt(aDest.mProperties.Length() - 1); } } } }
void nsAnimationManager::BuildAnimations(nsStyleContext* aStyleContext, dom::Element* aTarget, dom::AnimationTimeline* aTimeline, AnimationPlayerPtrArray& aPlayers) { NS_ABORT_IF_FALSE(aPlayers.IsEmpty(), "expect empty array"); ResolvedStyleCache resolvedStyles; const nsStyleDisplay *disp = aStyleContext->StyleDisplay(); Nullable<TimeDuration> now = aTimeline->GetCurrentTimeDuration(); for (size_t animIdx = 0, animEnd = disp->mAnimationNameCount; animIdx != animEnd; ++animIdx) { const StyleAnimation& src = disp->mAnimations[animIdx]; // CSS Animations whose animation-name does not match a @keyframes rule do // not generate animation events. This includes when the animation-name is // "none" which is represented by an empty name in the StyleAnimation. // Since such animations neither affect style nor dispatch events, we do // not generate a corresponding AnimationPlayer for them. nsCSSKeyframesRule* rule = src.GetName().IsEmpty() ? nullptr : mPresContext->StyleSet()->KeyframesRuleForName(mPresContext, src.GetName()); if (!rule) { continue; } nsRefPtr<AnimationPlayer> dest = *aPlayers.AppendElement(new AnimationPlayer(aTimeline)); AnimationTiming timing; timing.mIterationDuration = TimeDuration::FromMilliseconds(src.GetDuration()); timing.mDelay = TimeDuration::FromMilliseconds(src.GetDelay()); timing.mIterationCount = src.GetIterationCount(); timing.mDirection = src.GetDirection(); timing.mFillMode = src.GetFillMode(); nsRefPtr<Animation> destAnim = new Animation(mPresContext->Document(), aTarget, aStyleContext->GetPseudoType(), timing, src.GetName()); dest->SetSource(destAnim); dest->mStartTime = now; dest->mPlayState = src.GetPlayState(); if (dest->IsPaused()) { dest->mHoldTime.SetValue(TimeDuration(0)); } // While current drafts of css3-animations say that later keyframes // with the same key entirely replace earlier ones (no cascading), // this is a bad idea and contradictory to the rest of CSS. So // we're going to keep all the keyframes for each key and then do // the replacement on a per-property basis rather than a per-rule // basis, just like everything else in CSS. AutoInfallibleTArray<KeyframeData, 16> sortedKeyframes; for (uint32_t ruleIdx = 0, ruleEnd = rule->StyleRuleCount(); ruleIdx != ruleEnd; ++ruleIdx) { css::Rule* cssRule = rule->GetStyleRuleAt(ruleIdx); NS_ABORT_IF_FALSE(cssRule, "must have rule"); NS_ABORT_IF_FALSE(cssRule->GetType() == css::Rule::KEYFRAME_RULE, "must be keyframe rule"); nsCSSKeyframeRule *kfRule = static_cast<nsCSSKeyframeRule*>(cssRule); const nsTArray<float> &keys = kfRule->GetKeys(); for (uint32_t keyIdx = 0, keyEnd = keys.Length(); keyIdx != keyEnd; ++keyIdx) { float key = keys[keyIdx]; // FIXME (spec): The spec doesn't say what to do with // out-of-range keyframes. We'll ignore them. if (0.0f <= key && key <= 1.0f) { KeyframeData *data = sortedKeyframes.AppendElement(); data->mKey = key; data->mIndex = ruleIdx; data->mRule = kfRule; } } } sortedKeyframes.Sort(KeyframeDataComparator()); if (sortedKeyframes.Length() == 0) { // no segments continue; } // Record the properties that are present in any keyframe rules we // are using. nsCSSPropertySet properties; for (uint32_t kfIdx = 0, kfEnd = sortedKeyframes.Length(); kfIdx != kfEnd; ++kfIdx) { css::Declaration *decl = sortedKeyframes[kfIdx].mRule->Declaration(); for (uint32_t propIdx = 0, propEnd = decl->Count(); propIdx != propEnd; ++propIdx) { nsCSSProperty prop = decl->GetPropertyAt(propIdx); if (prop != eCSSPropertyExtra_variable) { // CSS Variables are not animatable properties.AddProperty(prop); } } } for (nsCSSProperty prop = nsCSSProperty(0); prop < eCSSProperty_COUNT_no_shorthands; prop = nsCSSProperty(prop + 1)) { if (!properties.HasProperty(prop) || nsCSSProps::kAnimTypeTable[prop] == eStyleAnimType_None) { continue; } // Build a list of the keyframes to use for this property. This // means we need every keyframe with the property in it, except // for those keyframes where a later keyframe with the *same key* // also has the property. AutoInfallibleTArray<uint32_t, 16> keyframesWithProperty; float lastKey = 100.0f; // an invalid key for (uint32_t kfIdx = 0, kfEnd = sortedKeyframes.Length(); kfIdx != kfEnd; ++kfIdx) { KeyframeData &kf = sortedKeyframes[kfIdx]; if (!kf.mRule->Declaration()->HasProperty(prop)) { continue; } if (kf.mKey == lastKey) { // Replace previous occurrence of same key. keyframesWithProperty[keyframesWithProperty.Length() - 1] = kfIdx; } else { keyframesWithProperty.AppendElement(kfIdx); } lastKey = kf.mKey; } AnimationProperty &propData = *destAnim->Properties().AppendElement(); propData.mProperty = prop; KeyframeData *fromKeyframe = nullptr; nsRefPtr<nsStyleContext> fromContext; bool interpolated = true; for (uint32_t wpIdx = 0, wpEnd = keyframesWithProperty.Length(); wpIdx != wpEnd; ++wpIdx) { uint32_t kfIdx = keyframesWithProperty[wpIdx]; KeyframeData &toKeyframe = sortedKeyframes[kfIdx]; nsRefPtr<nsStyleContext> toContext = resolvedStyles.Get(mPresContext, aStyleContext, toKeyframe.mRule); if (fromKeyframe) { interpolated = interpolated && BuildSegment(propData.mSegments, prop, src, fromKeyframe->mKey, fromContext, fromKeyframe->mRule->Declaration(), toKeyframe.mKey, toContext); } else { if (toKeyframe.mKey != 0.0f) { // There's no data for this property at 0%, so use the // cascaded value above us. interpolated = interpolated && BuildSegment(propData.mSegments, prop, src, 0.0f, aStyleContext, nullptr, toKeyframe.mKey, toContext); } } fromContext = toContext; fromKeyframe = &toKeyframe; } if (fromKeyframe->mKey != 1.0f) { // There's no data for this property at 100%, so use the // cascaded value above us. interpolated = interpolated && BuildSegment(propData.mSegments, prop, src, fromKeyframe->mKey, fromContext, fromKeyframe->mRule->Declaration(), 1.0f, aStyleContext); } // If we failed to build any segments due to inability to // interpolate, remove the property from the animation. (It's not // clear if this is the right thing to do -- we could run some of // the segments, but it's really not clear whether we should skip // values (which?) or skip segments, so best to skip the whole // thing for now.) if (!interpolated) { destAnim->Properties().RemoveElementAt( destAnim->Properties().Length() - 1); } } } }