Bug 1339332 - Part 1: Introduce neutral value concept for missing keyframe in CSS Animation. r=birtles,hiro
MozReview-Commit-ID: F3qvRY3SRAp
--- a/dom/animation/KeyframeEffectReadOnly.cpp
+++ b/dom/animation/KeyframeEffectReadOnly.cpp
@@ -373,25 +373,26 @@ KeyframeEffectReadOnly::DoUpdateProperti
/* static */ StyleAnimationValue
KeyframeEffectReadOnly::CompositeValue(
nsCSSPropertyID aProperty,
const StyleAnimationValue& aValueToComposite,
const StyleAnimationValue& aUnderlyingValue,
CompositeOperation aCompositeOperation)
{
+ // Just return the underlying value if |aValueToComposite| is null
+ // (i.e. missing keyframe).
+ if (aValueToComposite.IsNull()) {
+ return aUnderlyingValue;
+ }
+
switch (aCompositeOperation) {
case dom::CompositeOperation::Replace:
return aValueToComposite;
case dom::CompositeOperation::Add: {
- // Just return the underlying value if |aValueToComposite| is null (i.e.
- // missing keyframe).
- if (aValueToComposite.IsNull()) {
- return aUnderlyingValue;
- }
StyleAnimationValue result(aValueToComposite);
return StyleAnimationValue::Add(aProperty,
aUnderlyingValue,
Move(result));
}
case dom::CompositeOperation::Accumulate: {
StyleAnimationValue result(aValueToComposite);
return StyleAnimationValue::Accumulate(aProperty,
@@ -458,57 +459,44 @@ StyleAnimationValue
KeyframeEffectReadOnly::CompositeValue(
nsCSSPropertyID aProperty,
const RefPtr<AnimValuesStyleRule>& aAnimationRule,
const StyleAnimationValue& aValueToComposite,
CompositeOperation aCompositeOperation)
{
MOZ_ASSERT(mTarget, "CompositeValue should be called with target element");
- StyleAnimationValue result = aValueToComposite;
-
- if (aCompositeOperation == CompositeOperation::Replace) {
- MOZ_ASSERT(!aValueToComposite.IsNull(),
- "Input value should be valid in case of replace composite");
- // Just copy the input value in case of 'Replace'.
- return result;
+ // FIXME: Bug 1311257: Get the base value for the servo backend.
+ if (mDocument->IsStyledByServo()) {
+ return aValueToComposite;
}
- // FIXME: Bug 1311257: Get the base value for the servo backend.
- if (mDocument->IsStyledByServo()) {
- return result;
- }
-
- MOZ_ASSERT(!aValueToComposite.IsNull() ||
- aCompositeOperation == CompositeOperation::Add,
- "InputValue should be null only if additive composite");
-
- result = GetUnderlyingStyle(aProperty, aAnimationRule);
+ StyleAnimationValue underlyingValue =
+ GetUnderlyingStyle(aProperty, aAnimationRule);
return CompositeValue(aProperty,
aValueToComposite,
- result,
+ underlyingValue,
aCompositeOperation);
}
void
KeyframeEffectReadOnly::EnsureBaseStyles(
nsStyleContext* aStyleContext,
const nsTArray<AnimationProperty>& aProperties)
{
if (!mTarget) {
return;
}
mBaseStyleValues.Clear();
for (const AnimationProperty& property : aProperties) {
for (const AnimationPropertySegment& segment : property.mSegments) {
- if (segment.mFromComposite == dom::CompositeOperation::Replace &&
- segment.mToComposite == dom::CompositeOperation::Replace) {
+ if (segment.HasReplacableValues()) {
continue;
}
Unused << ResolveBaseStyle(property.mProperty, aStyleContext);
break;
}
}
}
@@ -1226,26 +1214,43 @@ KeyframeEffectReadOnly::GetKeyframes(JSC
JS::Rooted<JSObject*> keyframeObject(aCx, &keyframeJSValue.toObject());
for (const PropertyValuePair& propertyValue : keyframe.mPropertyValues) {
nsAutoString stringValue;
if (propertyValue.mServoDeclarationBlock) {
Servo_DeclarationBlock_SerializeOneValue(
propertyValue.mServoDeclarationBlock,
propertyValue.mProperty, &stringValue);
+ } else if (nsCSSProps::IsShorthand(propertyValue.mProperty)) {
+ // nsCSSValue::AppendToString does not accept shorthands properties but
+ // works with token stream values if we pass eCSSProperty_UNKNOWN as
+ // the property.
+ propertyValue.mValue.AppendToString(
+ eCSSProperty_UNKNOWN, stringValue, nsCSSValue::eNormalized);
} else {
- // nsCSSValue::AppendToString does not accept shorthands properties but
- // works with token stream values if we pass eCSSProperty_UNKNOWN as
- // the property.
- nsCSSPropertyID propertyForSerializing =
- nsCSSProps::IsShorthand(propertyValue.mProperty)
- ? eCSSProperty_UNKNOWN
- : propertyValue.mProperty;
- propertyValue.mValue.AppendToString(
- propertyForSerializing, stringValue, nsCSSValue::eNormalized);
+ nsCSSValue cssValue = propertyValue.mValue;
+ if (cssValue.GetUnit() == eCSSUnit_Null) {
+ // We use an uninitialized nsCSSValue to represent the
+ // "neutral value". We currently only do this for keyframes generated
+ // from CSS animations with missing 0%/100% keyframes. Furthermore,
+ // currently (at least until bug 1339334) keyframes generated from
+ // CSS animations only contain longhand properties so we only need to
+ // handle null nsCSSValues for longhand properties.
+ DebugOnly<bool> uncomputeResult =
+ StyleAnimationValue::UncomputeValue(
+ propertyValue.mProperty, Move(BaseStyle(propertyValue.mProperty)),
+ cssValue);
+
+ MOZ_ASSERT(uncomputeResult,
+ "Unable to get specified value from computed value");
+ MOZ_ASSERT(cssValue.GetUnit() != eCSSUnit_Null,
+ "Got null computed value");
+ }
+ cssValue.AppendToString(propertyValue.mProperty,
+ stringValue, nsCSSValue::eNormalized);
}
const char* name = nsCSSProps::PropertyIDLName(propertyValue.mProperty);
JS::Rooted<JS::Value> value(aCx);
if (!ToJSValue(aCx, stringValue, &value) ||
!JS_DefineProperty(aCx, keyframeObject, name, value,
JSPROP_ENUMERATE)) {
aRv.Throw(NS_ERROR_FAILURE);
@@ -1620,22 +1625,21 @@ KeyframeEffectReadOnly::CalculateCumulat
if (mDocument->IsStyledByServo()) {
// FIXME (bug 1303235): Do this for Servo too
return;
}
mCumulativeChangeHint = nsChangeHint(0);
for (const AnimationProperty& property : mProperties) {
for (const AnimationPropertySegment& segment : property.mSegments) {
- // In case composite operation is not 'replace', we can't throttle
- // animations which will not cause any layout changes on invisible
- // elements because we can't calculate the change hint for such properties
- // until we compose it.
- if (segment.mFromComposite != CompositeOperation::Replace ||
- segment.mToComposite != CompositeOperation::Replace) {
+ // In case composite operation is not 'replace' or value is null,
+ // we can't throttle animations which will not cause any layout changes
+ // on invisible elements because we can't calculate the change hint for
+ // such properties until we compose it.
+ if (!segment.HasReplacableValues()) {
mCumulativeChangeHint = ~nsChangeHint_Hints_CanIgnoreIfNotVisible;
return;
}
RefPtr<nsStyleContext> fromContext =
CreateStyleContextForAnimationValue(property.mProperty,
segment.mFromValue.mGecko,
aStyleContext);
--- a/dom/animation/KeyframeEffectReadOnly.h
+++ b/dom/animation/KeyframeEffectReadOnly.h
@@ -61,16 +61,33 @@ struct AnimationPropertySegment
// NOTE: In the case that no keyframe for 0 or 1 offset is specified
// the unit of mFromValue or mToValue is eUnit_Null.
AnimationValue mFromValue, mToValue;
Maybe<ComputedTimingFunction> mTimingFunction;
dom::CompositeOperation mFromComposite = dom::CompositeOperation::Replace;
dom::CompositeOperation mToComposite = dom::CompositeOperation::Replace;
+ bool HasReplacableValues() const
+ {
+ return HasReplacableFromValue() && HasReplacableToValue();
+ }
+
+ bool HasReplacableFromValue() const
+ {
+ return !mFromValue.IsNull() &&
+ mFromComposite == dom::CompositeOperation::Replace;
+ }
+
+ bool HasReplacableToValue() const
+ {
+ return !mToValue.IsNull() &&
+ mToComposite == dom::CompositeOperation::Replace;
+ }
+
bool operator==(const AnimationPropertySegment& aOther) const
{
return mFromKey == aOther.mFromKey &&
mToKey == aOther.mToKey &&
mFromValue == aOther.mFromValue &&
mToValue == aOther.mToValue &&
mTimingFunction == aOther.mTimingFunction &&
mFromComposite == aOther.mFromComposite &&
--- a/dom/animation/KeyframeUtils.cpp
+++ b/dom/animation/KeyframeUtils.cpp
@@ -649,16 +649,22 @@ KeyframeUtils::GetComputedKeyframeValues
if (nsCSSProps::IsShorthand(pair.mProperty)) {
nsCSSValueTokenStream* tokenStream = pair.mValue.GetTokenStreamValue();
if (!StyleAnimationValue::ComputeValues(pair.mProperty,
CSSEnabledState::eForAllContent, aElement, aStyleContext,
tokenStream->mTokenStream, /* aUseSVGMode */ false, values) ||
IsComputeValuesFailureKey(pair)) {
continue;
}
+ } else if (pair.mValue.GetUnit() == eCSSUnit_Null) {
+ // An uninitialized nsCSSValue represents the underlying value which
+ // we represent as an uninitialized AnimationValue so we just leave
+ // neutralPair->mValue as-is.
+ PropertyStyleAnimationValuePair* neutralPair = values.AppendElement();
+ neutralPair->mProperty = pair.mProperty;
} else {
if (!StyleAnimationValue::ComputeValues(pair.mProperty,
CSSEnabledState::eForAllContent, aElement, aStyleContext,
pair.mValue, /* aUseSVGMode */ false, values)) {
continue;
}
MOZ_ASSERT(values.Length() == 1,
"Longhand properties should produce a single"
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -593,20 +593,20 @@ GetMinAndMaxScaleForAnimationProperty(co
// FIXME: Bug 1311257: We need to get the baseStyle for
// RawServoAnimationValue.
UpdateMinMaxScale(aFrame, { baseStyle, nullptr }, aMinScale, aMaxScale);
}
for (const AnimationPropertySegment& segment : prop.mSegments) {
// In case of add or accumulate composite, StyleAnimationValue does
// not have a valid value.
- if (segment.mFromComposite == dom::CompositeOperation::Replace) {
+ if (segment.HasReplacableFromValue()) {
UpdateMinMaxScale(aFrame, segment.mFromValue, aMinScale, aMaxScale);
}
- if (segment.mToComposite == dom::CompositeOperation::Replace) {
+ if (segment.HasReplacableToValue()) {
UpdateMinMaxScale(aFrame, segment.mToValue, aMinScale, aMaxScale);
}
}
}
}
}
gfxSize
--- a/layout/style/nsAnimationManager.cpp
+++ b/layout/style/nsAnimationManager.cpp
@@ -465,17 +465,16 @@ private:
nsPresContext* aPresContext,
nsCSSKeyframeRule* aKeyframeRule,
const Maybe<ComputedTimingFunction>& aInheritedTimingFunction);
nsTArray<PropertyValuePair> GetKeyframePropertyValues(
nsPresContext* aPresContext,
nsCSSKeyframeRule* aKeyframeRule,
nsCSSPropertyIDSet& aAnimatedProperties);
void FillInMissingKeyframeValues(
- nsPresContext* aPresContext,
nsCSSPropertyIDSet aAnimatedProperties,
nsCSSPropertyIDSet aPropertiesSetAtStart,
nsCSSPropertyIDSet aPropertiesSetAtEnd,
const Maybe<ComputedTimingFunction>& aInheritedTimingFunction,
nsTArray<Keyframe>& aKeyframes);
void AppendProperty(nsPresContext* aPresContext,
nsCSSPropertyID aProperty,
nsTArray<PropertyValuePair>& aPropertyValues);
@@ -800,19 +799,19 @@ GeckoCSSAnimationBuilder::BuildAnimation
keyframes.RemoveElementAt(keyframeIdx - 1);
// existingKeyframe might dangle now
}
}
// Finally, we need to look for any animated properties that have an
// implicit 'to' or 'from' value and fill in the appropriate keyframe
// with the current computed style.
- FillInMissingKeyframeValues(aPresContext, animatedProperties,
- propertiesSetAtStart, propertiesSetAtEnd,
- inheritedTimingFunction, keyframes);
+ FillInMissingKeyframeValues(animatedProperties, propertiesSetAtStart,
+ propertiesSetAtEnd, inheritedTimingFunction,
+ keyframes);
return keyframes;
}
Maybe<ComputedTimingFunction>
GeckoCSSAnimationBuilder::GetKeyframeTimingFunction(
nsPresContext* aPresContext,
nsCSSKeyframeRule* aKeyframeRule,
@@ -922,17 +921,16 @@ FindMatchingKeyframe(
}
++aIndex;
}
return false;
}
void
GeckoCSSAnimationBuilder::FillInMissingKeyframeValues(
- nsPresContext* aPresContext,
nsCSSPropertyIDSet aAnimatedProperties,
nsCSSPropertyIDSet aPropertiesSetAtStart,
nsCSSPropertyIDSet aPropertiesSetAtEnd,
const Maybe<ComputedTimingFunction>& aInheritedTimingFunction,
nsTArray<Keyframe>& aKeyframes)
{
static const size_t kNotSet = static_cast<size_t>(-1);
@@ -979,20 +977,24 @@ GeckoCSSAnimationBuilder::FillInMissingK
for (nsCSSPropertyID prop = nsCSSPropertyID(0);
prop < eCSSProperty_COUNT_no_shorthands;
prop = nsCSSPropertyID(prop + 1)) {
if (!aAnimatedProperties.HasProperty(prop)) {
continue;
}
if (startKeyframe && !aPropertiesSetAtStart.HasProperty(prop)) {
- AppendProperty(aPresContext, prop, startKeyframe->mPropertyValues);
+ PropertyValuePair propertyValue;
+ propertyValue.mProperty = prop;
+ startKeyframe->mPropertyValues.AppendElement(Move(propertyValue));
}
if (endKeyframe && !aPropertiesSetAtEnd.HasProperty(prop)) {
- AppendProperty(aPresContext, prop, endKeyframe->mPropertyValues);
+ PropertyValuePair propertyValue;
+ propertyValue.mProperty = prop;
+ endKeyframe->mPropertyValues.AppendElement(Move(propertyValue));
}
}
}
void
GeckoCSSAnimationBuilder::AppendProperty(
nsPresContext* aPresContext,
nsCSSPropertyID aProperty,