Bug 1245748 - Add KeyframeEffectReadOnly::SetFrames; r?heycam
Earlier in this patch series we divided keyframe processing into two stages:
(1) Turning javascript objects into an array of Keyframe objects
(2) Calculating AnimationProperty arrays from the Keyframe objects
This patch creates a SetFrames method so that CSS animations and
CSS transitions can skip (1) and pass the frames constructed from CSS syntax
into (2).
It also adds the following additional processing:
a. Notifying animation mutation observers when the set of frames has changed.
This is currently performed by nsAnimationManager but ultimately we should
encapsulate this logic inside the effect itself. Furthermore, it will be
needed when we implement effect.setFrames() (i.e. the Javascript-facing
wrapper for this method).
b. Preserving the mWinsInCascade and mIsRunningOnCompositor state on properties
when updating them.
This is currently performed by:
bool KeyframeEffectReadOnly::UpdateProperties(
const InfallibleTArray<AnimationProperty>& aProperties)
which is what nsAnimationManager currently uses. We will ultimately remove
the above method and here we are just moving this code to the new version
of UpdateProperties.
c. Requesting a restyle when the set of AnimationProperty objects has changed.
Again, this is currently performed by the existing UpdateProperties method
so we are just moving it here. This behavior will also be required when
we implement effect.setFrames() and when we call UpdateProperties from
elsewhere in restyling code.
This is
bug 1235002 but we fix it here and leave that bug to just do further
cleanup work (e.g. re-instating the check for an empty property set before
requesting a restyle in NotifyAnimationTimingUpdated).
d. Marking the cascade as needing an update when the set of AnimationProperty
objects has changed.
This is in preparation for calling UpdateProperties from elsewhere in
restyling (e.g. when the nsStyleContext is updated).
MozReview-Commit-ID: 2ll26lsWZTm
--- a/dom/animation/KeyframeEffect.cpp
+++ b/dom/animation/KeyframeEffect.cpp
@@ -444,16 +444,56 @@ KeyframeEffectReadOnly::IsInEffect() con
void
KeyframeEffectReadOnly::SetAnimation(Animation* aAnimation)
{
mAnimation = aAnimation;
NotifyAnimationTimingUpdated();
}
+static bool
+KeyframesEqualIgnoringComputedOffsets(const nsTArray<Keyframe>& aLhs,
+ const nsTArray<Keyframe>& aRhs)
+{
+ if (aLhs.Length() != aRhs.Length()) {
+ return false;
+ }
+
+ for (size_t i = 0, len = aLhs.Length(); i < len; ++i) {
+ const Keyframe& a = aLhs[i];
+ const Keyframe& b = aRhs[i];
+ if (a.mOffset != b.mOffset ||
+ a.mTimingFunction != b.mTimingFunction ||
+ a.mPropertyValues != b.mPropertyValues) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void
+KeyframeEffectReadOnly::SetFrames(nsTArray<Keyframe>&& aFrames,
+ nsStyleContext* aStyleContext)
+{
+ if (KeyframesEqualIgnoringComputedOffsets(aFrames, mFrames)) {
+ return;
+ }
+
+ mFrames = Move(aFrames);
+ KeyframeUtils::ApplyDistributeSpacing(mFrames);
+
+ if (mAnimation) {
+ nsNodeUtils::AnimationChanged(mAnimation);
+ }
+
+ if (aStyleContext) {
+ UpdateProperties(aStyleContext);
+ }
+}
+
const AnimationProperty*
KeyframeEffectReadOnly::GetAnimationOfProperty(nsCSSProperty aProperty) const
{
for (size_t propIdx = 0, propEnd = mProperties.Length();
propIdx != propEnd; ++propIdx) {
if (aProperty == mProperties[propIdx].mProperty) {
const AnimationProperty* result = &mProperties[propIdx];
if (!result->mWinsInCascade) {
@@ -519,16 +559,74 @@ KeyframeEffectReadOnly::UpdateProperties
mAnimation->CascadeLevel());
}
}
return true;
}
void
+KeyframeEffectReadOnly::UpdateProperties(nsStyleContext* aStyleContext)
+{
+ MOZ_ASSERT(aStyleContext);
+
+ nsTArray<AnimationProperty> properties;
+ if (mTarget) {
+ properties =
+ KeyframeUtils::GetAnimationPropertiesFromKeyframes(aStyleContext,
+ mTarget,
+ mPseudoType,
+ mFrames);
+ }
+
+ if (mProperties == properties) {
+ return;
+ }
+
+ // Preserve the state of mWinsInCascade and mIsRunningOnCompositor flags.
+ nsCSSPropertySet winningInCascadeProperties;
+ nsCSSPropertySet runningOnCompositorProperties;
+
+ for (const AnimationProperty& property : mProperties) {
+ if (property.mWinsInCascade) {
+ winningInCascadeProperties.AddProperty(property.mProperty);
+ }
+ if (property.mIsRunningOnCompositor) {
+ runningOnCompositorProperties.AddProperty(property.mProperty);
+ }
+ }
+
+ mProperties = Move(properties);
+
+ for (AnimationProperty& property : mProperties) {
+ property.mWinsInCascade =
+ winningInCascadeProperties.HasProperty(property.mProperty);
+ property.mIsRunningOnCompositor =
+ runningOnCompositorProperties.HasProperty(property.mProperty);
+ }
+
+ if (mTarget) {
+ EffectSet* effectSet = EffectSet::GetEffectSet(mTarget, mPseudoType);
+ if (effectSet) {
+ effectSet->MarkCascadeNeedsUpdate();
+ }
+ }
+
+ if (mAnimation) {
+ nsPresContext* presContext = GetPresContext();
+ if (presContext) {
+ presContext->EffectCompositor()->
+ RequestRestyle(mTarget, mPseudoType,
+ EffectCompositor::RestyleType::Layer,
+ mAnimation->CascadeLevel());
+ }
+ }
+}
+
+void
KeyframeEffectReadOnly::ComposeStyle(RefPtr<AnimValuesStyleRule>& aStyleRule,
nsCSSPropertySet& aSetProperties)
{
ComputedTiming computedTiming = GetComputedTiming();
mProgressOnLastCompose = computedTiming.mProgress;
// If the progress is null, we don't have fill data for the current
// time so we shouldn't animate.
@@ -707,42 +805,33 @@ KeyframeEffectReadOnly::ConstructKeyfram
return nullptr;
}
nsTArray<Keyframe> keyframes =
KeyframeUtils::GetKeyframesFromObject(aGlobal.Context(), aFrames, aRv);
if (aRv.Failed()) {
return nullptr;
}
- KeyframeUtils::ApplyDistributeSpacing(keyframes);
+
+ RefPtr<KeyframeEffectType> effect =
+ new KeyframeEffectType(targetElement->OwnerDoc(), targetElement,
+ pseudoType, timingParams);
RefPtr<nsStyleContext> styleContext;
nsIPresShell* shell = doc->GetShell();
if (shell && targetElement) {
nsIAtom* pseudo =
pseudoType < CSSPseudoElementType::Count ?
nsCSSPseudoElements::GetPseudoAtom(pseudoType) : nullptr;
styleContext =
nsComputedDOMStyle::GetStyleContextForElement(targetElement, pseudo,
shell);
}
+ effect->SetFrames(Move(keyframes), styleContext);
- nsTArray<AnimationProperty> animationProperties;
- if (styleContext) {
- animationProperties =
- KeyframeUtils::GetAnimationPropertiesFromKeyframes(styleContext,
- targetElement,
- pseudoType,
- keyframes);
- }
-
- RefPtr<KeyframeEffectType> effect =
- new KeyframeEffectType(targetElement->OwnerDoc(), targetElement,
- pseudoType, timingParams);
- effect->mProperties = Move(animationProperties);
return effect.forget();
}
void
KeyframeEffectReadOnly::ResetIsRunningOnCompositor()
{
for (AnimationProperty& property : mProperties) {
property.mIsRunningOnCompositor = false;
--- a/dom/animation/KeyframeEffect.h
+++ b/dom/animation/KeyframeEffect.h
@@ -58,16 +58,21 @@ struct AnimationPropertyDetails;
*/
struct PropertyValuePair
{
nsCSSProperty mProperty;
// The specified value for the property. For shorthand properties or invalid
// property values, we store the specified property value as a token stream
// (string).
nsCSSValue mValue;
+
+ bool operator==(const PropertyValuePair& aOther) const {
+ return mProperty == aOther.mProperty &&
+ mValue == aOther.mValue;
+ }
};
/**
* A single keyframe.
*
* This is the canonical form in which keyframe effects are stored and
* corresponds closely to the type of objects returned via the getFrames() API.
*
@@ -269,16 +274,17 @@ public:
bool IsInPlay() const;
bool IsCurrent() const;
bool IsInEffect() const;
void SetAnimation(Animation* aAnimation);
Animation* GetAnimation() const { return mAnimation; }
+ void SetFrames(nsTArray<Keyframe>&& aFrames, nsStyleContext* aStyleContext);
const AnimationProperty*
GetAnimationOfProperty(nsCSSProperty aProperty) const;
bool HasAnimationOfProperty(nsCSSProperty aProperty) const {
return GetAnimationOfProperty(aProperty) != nullptr;
}
bool HasAnimationOfProperties(const nsCSSProperty* aProperties,
size_t aPropertyCount) const;
const InfallibleTArray<AnimationProperty>& Properties() const {
@@ -289,16 +295,20 @@ public:
}
// Updates the set of properties using the supplied list whilst preserving
// the mWinsInCascade and mIsRunningOnCompositor state of any matching
// properties.
// Returns true if we updated anything in the properties.
bool UpdateProperties(
const InfallibleTArray<AnimationProperty>& aProperties);
+ // Update |mProperties| by recalculating from |mFrames| using |aStyleContext|
+ // to resolve specified values.
+ void UpdateProperties(nsStyleContext* aStyleContext);
+
// Updates |aStyleRule| with the animation values produced by this
// AnimationEffect for the current time except any properties already
// contained in |aSetProperties|.
// Any updated properties are added to |aSetProperties|.
void ComposeStyle(RefPtr<AnimValuesStyleRule>& aStyleRule,
nsCSSPropertySet& aSetProperties);
// Returns true if at least one property is being animated on compositor.
bool IsRunningOnCompositor() const;
@@ -358,17 +368,21 @@ protected:
void UpdateTargetRegistration();
nsCOMPtr<Element> mTarget;
RefPtr<Animation> mAnimation;
OwningNonNull<AnimationEffectTimingReadOnly> mTiming;
CSSPseudoElementType mPseudoType;
- InfallibleTArray<AnimationProperty> mProperties;
+ // The specified keyframes.
+ nsTArray<Keyframe> mFrames;
+
+ // A set of per-property value arrays, derived from |mFrames|.
+ nsTArray<AnimationProperty> mProperties;
// The computed progress last time we composed the style rule. This is
// used to detect when the progress is not changing (e.g. due to a step
// timing function) so we can avoid unnecessary style updates.
Nullable<double> mProgressOnLastCompose;
// We need to track when we go to or from being "in effect" since
// we need to re-evaluate the cascade of animations when that changes.
--- a/dom/animation/KeyframeUtils.cpp
+++ b/dom/animation/KeyframeUtils.cpp
@@ -462,16 +462,19 @@ KeyframeUtils::ApplyDistributeSpacing(ns
/* static */ nsTArray<AnimationProperty>
KeyframeUtils::GetAnimationPropertiesFromKeyframes(
nsStyleContext* aStyleContext,
dom::Element* aElement,
CSSPseudoElementType aPseudoType,
const nsTArray<Keyframe>& aFrames)
{
+ MOZ_ASSERT(aStyleContext);
+ MOZ_ASSERT(aElement);
+
nsTArray<KeyframeValueEntry> entries;
for (const Keyframe& frame : aFrames) {
nsCSSPropertySet propertiesOnThisKeyframe;
for (const PropertyValuePair& pair :
PropertyPriorityIterator(frame.mPropertyValues)) {
// We currently store invalid longhand values on keyframes as a token
// stream so if we see one of them, just keep moving.