Bug 1276688 part 2 - Add tests for entries array handling when ComputeValues fails; r=heycam, r=smaug

MozReview-Commit-ID: DIQyt7f91an
This commit is contained in:
Brian Birtles 2016-06-28 16:10:10 +09:00
parent 307d2b9f11
commit 7dbb4e5cf3
4 changed files with 377 additions and 1 deletions

View File

@ -372,6 +372,12 @@ MakePropertyValuePair(nsCSSProperty aProperty, const nsAString& aStringValue,
static bool
HasValidOffsets(const nsTArray<Keyframe>& aKeyframes);
static void
MarkAsComputeValuesFailureKey(PropertyValuePair& aPair);
static bool
IsComputeValuesFailureKey(const PropertyValuePair& aPair);
static void
BuildSegmentsFromValueEntries(nsStyleContext* aStyleContext,
nsTArray<KeyframeValueEntry>& aEntries,
@ -518,7 +524,8 @@ KeyframeUtils::GetAnimationPropertiesFromKeyframes(
nsCSSValueTokenStream* tokenStream = pair.mValue.GetTokenStreamValue();
if (!StyleAnimationValue::ComputeValues(pair.mProperty,
CSSEnabledState::eForAllContent, aElement, aStyleContext,
tokenStream->mTokenStream, /* aUseSVGMode */ false, values)) {
tokenStream->mTokenStream, /* aUseSVGMode */ false, values) ||
IsComputeValuesFailureKey(pair)) {
continue;
}
} else {
@ -677,6 +684,17 @@ ConvertKeyframeSequence(JSContext* aCx,
MOZ_ASSERT(pair.mValues.Length() == 1);
keyframe->mPropertyValues.AppendElement(
MakePropertyValuePair(pair.mProperty, pair.mValues[0], parser, doc));
// When we go to convert keyframes into arrays of property values we
// call StyleAnimation::ComputeValues. This should normally return true
// but in order to test the case where it does not, BaseKeyframeDict
// includes a chrome-only member that can be set to indicate that
// ComputeValues should fail for shorthand property values on that
// keyframe.
if (nsCSSProps::IsShorthand(pair.mProperty) &&
keyframeDict.mSimulateComputeValuesFailure) {
MarkAsComputeValuesFailureKey(keyframe->mPropertyValues.LastElement());
}
}
}
@ -909,6 +927,51 @@ HasValidOffsets(const nsTArray<Keyframe>& aKeyframes)
return true;
}
/**
* Takes a property-value pair for a shorthand property and modifies the
* value to indicate that when we call StyleAnimationValue::ComputeValues on
* that value we should behave as if that function had failed.
*
* @param aPair The PropertyValuePair to modify. |aPair.mProperty| must be
* a shorthand property.
*/
static void
MarkAsComputeValuesFailureKey(PropertyValuePair& aPair)
{
MOZ_ASSERT(nsCSSProps::IsShorthand(aPair.mProperty),
"Only shorthand property values can be marked as failure values");
// We store shorthand values as nsCSSValueTokenStream objects whose mProperty
// and mShorthandPropertyID are eCSSProperty_UNKNOWN and whose mTokenStream
// member contains the shorthand property's value as a string.
//
// We need to leave mShorthandPropertyID as eCSSProperty_UNKNOWN so that
// nsCSSValue::AppendToString returns the mTokenStream value, but we can
// update mPropertyID to a special value to indicate that this is
// a special failure sentinel.
nsCSSValueTokenStream* tokenStream = aPair.mValue.GetTokenStreamValue();
MOZ_ASSERT(tokenStream->mPropertyID == eCSSProperty_UNKNOWN,
"Shorthand value should initially have an unknown property ID");
tokenStream->mPropertyID = eCSSPropertyExtra_no_properties;
}
/**
* Returns true if |aPair| is a property-value pair on which we have
* previously called MarkAsComputeValuesFailureKey (and hence we should
* simulate failure when calling StyleAnimationValue::ComputeValues using its
* value).
*
* @param aPair The property-value pair to test.
* @return True if |aPair| represents a failure value.
*/
static bool
IsComputeValuesFailureKey(const PropertyValuePair& aPair)
{
return nsCSSProps::IsShorthand(aPair.mProperty) &&
aPair.mValue.GetTokenStreamValue()->mPropertyID ==
eCSSPropertyExtra_no_properties;
}
static already_AddRefed<nsStyleContext>
CreateStyleContextForAnimationValue(nsCSSProperty aProperty,
StyleAnimationValue aValue,

View File

@ -505,6 +505,7 @@ var gTests = [
// Tests for CSS variable handling conversion
//
// ---------------------------------------------------------------------
{ desc: 'CSS variables are resolved to their corresponding values',
frames: { left: ['10px', 'var(--var-100px)'] },
expected: [ { property: 'left',
@ -533,6 +534,290 @@ var gTests = [
values: [ value(0, '10px', 'replace', 'linear'),
value(1, '200px', 'replace') ] } ]
},
// ---------------------------------------------------------------------
//
// Tests for properties that parse correctly but which we fail to
// convert to computed values.
//
// ---------------------------------------------------------------------
{ desc: 'a property that can\'t be resolved to computed values in'
+ ' initial keyframe',
frames: [ { margin: '5px', simulateComputeValuesFailure: true },
{ margin: '5px' } ],
expected: [ ]
},
{ desc: 'a property that can\'t be resolved to computed values in'
+ ' initial keyframe where we have enough values to create'
+ ' a final segment',
frames: [ { margin: '5px', simulateComputeValuesFailure: true },
{ margin: '5px' },
{ margin: '5px' } ],
expected: [ ]
},
{ desc: 'a property that can\'t be resolved to computed values in'
+ ' initial overlapping keyframes (first in series of two)',
frames: [ { margin: '5px', offset: 0,
simulateComputeValuesFailure: true },
{ margin: '5px', offset: 0 },
{ margin: '5px' } ],
expected: [ { property: 'margin-top',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] },
{ property: 'margin-right',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] },
{ property: 'margin-bottom',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] },
{ property: 'margin-left',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] } ]
},
{ desc: 'a property that can\'t be resolved to computed values in'
+ ' initial overlapping keyframes (second in series of two)',
frames: [ { margin: '5px', offset: 0 },
{ margin: '5px', offset: 0,
simulateComputeValuesFailure: true },
{ margin: '5px' } ],
expected: [ { property: 'margin-top',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] },
{ property: 'margin-right',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] },
{ property: 'margin-bottom',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] },
{ property: 'margin-left',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] } ]
},
{ desc: 'a property that can\'t be resolved to computed values in'
+ ' initial overlapping keyframes (second in series of three)',
frames: [ { margin: '5px', offset: 0 },
{ margin: '5px', offset: 0,
simulateComputeValuesFailure: true },
{ margin: '5px', offset: 0 },
{ margin: '5px' } ],
expected: [ { property: 'margin-top',
values: [ value(0, '5px', 'replace'),
value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] },
{ property: 'margin-right',
values: [ value(0, '5px', 'replace'),
value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] },
{ property: 'margin-bottom',
values: [ value(0, '5px', 'replace'),
value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] },
{ property: 'margin-left',
values: [ value(0, '5px', 'replace'),
value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] } ]
},
{ desc: 'a property that can\'t be resolved to computed values in'
+ ' final keyframe',
frames: [ { margin: '5px' },
{ margin: '5px', simulateComputeValuesFailure: true } ],
expected: [ ]
},
{ desc: 'a property that can\'t be resolved to computed values in'
+ ' final keyframe where it forms the last segment in the series',
frames: [ { margin: '5px' },
{ margin: '5px',
marginLeft: '5px',
marginRight: '5px',
marginBottom: '5px',
// margin-top sorts last and only it will be missing since
// the other longhand components are specified
simulateComputeValuesFailure: true } ],
expected: [ { property: 'margin-bottom',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] },
{ property: 'margin-left',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] },
{ property: 'margin-right',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] } ]
},
{ desc: 'a property that can\'t be resolved to computed values in'
+ ' final keyframe where we have enough values to create'
+ ' an initial segment',
frames: [ { margin: '5px' },
{ margin: '5px' },
{ margin: '5px', simulateComputeValuesFailure: true } ],
expected: [ ]
},
{ desc: 'a property that can\'t be resolved to computed values in'
+ ' final overlapping keyframes (first in series of two)',
frames: [ { margin: '5px' },
{ margin: '5px', offset: 1,
simulateComputeValuesFailure: true },
{ margin: '5px', offset: 1 } ],
expected: [ { property: 'margin-top',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] },
{ property: 'margin-right',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] },
{ property: 'margin-bottom',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] },
{ property: 'margin-left',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] } ]
},
{ desc: 'a property that can\'t be resolved to computed values in'
+ ' final overlapping keyframes (second in series of two)',
frames: [ { margin: '5px' },
{ margin: '5px', offset: 1 },
{ margin: '5px', offset: 1,
simulateComputeValuesFailure: true } ],
expected: [ { property: 'margin-top',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] },
{ property: 'margin-right',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] },
{ property: 'margin-bottom',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] },
{ property: 'margin-left',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] } ]
},
{ desc: 'a property that can\'t be resolved to computed values in'
+ ' final overlapping keyframes (second in series of three)',
frames: [ { margin: '5px' },
{ margin: '5px', offset: 1 },
{ margin: '5px', offset: 1,
simulateComputeValuesFailure: true },
{ margin: '5px', offset: 1 } ],
expected: [ { property: 'margin-top',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace'),
value(1, '5px', 'replace') ] },
{ property: 'margin-right',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace'),
value(1, '5px', 'replace') ] },
{ property: 'margin-bottom',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace'),
value(1, '5px', 'replace') ] },
{ property: 'margin-left',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace'),
value(1, '5px', 'replace') ] } ]
},
{ desc: 'a property that can\'t be resolved to computed values in'
+ ' intermediate keyframe',
frames: [ { margin: '5px' },
{ margin: '5px', simulateComputeValuesFailure: true },
{ margin: '5px' } ],
expected: [ { property: 'margin-top',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] },
{ property: 'margin-right',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] },
{ property: 'margin-bottom',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] },
{ property: 'margin-left',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] } ]
},
{ desc: 'a property that can\'t be resolved to computed values in'
+ ' initial keyframe along with other values',
// simulateComputeValuesFailure only applies to shorthands so we can set
// it on the same keyframe and it will only apply to |margin| and not
// |left|.
frames: [ { margin: '77%', left: '10px',
simulateComputeValuesFailure: true },
{ margin: '5px', left: '20px' } ],
expected: [ { property: 'left',
values: [ value(0, '10px', 'replace', 'linear'),
value(1, '20px', 'replace') ] } ],
},
{ desc: 'a property that can\'t be resolved to computed values in'
+ ' initial keyframe along with other values where those'
+ ' values sort after the property with missing values',
frames: [ { margin: '77%', right: '10px',
simulateComputeValuesFailure: true },
{ margin: '5px', right: '20px' } ],
expected: [ { property: 'right',
values: [ value(0, '10px', 'replace', 'linear'),
value(1, '20px', 'replace') ] } ],
},
{ desc: 'a property that can\'t be resolved to computed values in'
+ ' final keyframe along with other values',
frames: [ { margin: '5px', left: '10px' },
{ margin: '5px', left: '20px',
simulateComputeValuesFailure: true } ],
expected: [ { property: 'left',
values: [ value(0, '10px', 'replace', 'linear'),
value(1, '20px', 'replace') ] } ],
},
{ desc: 'a property that can\'t be resolved to computed values in'
+ ' final keyframe along with other values where those'
+ ' values sort after the property with missing values',
frames: [ { margin: '5px', right: '10px' },
{ margin: '5px', right: '20px',
simulateComputeValuesFailure: true } ],
expected: [ { property: 'right',
values: [ value(0, '10px', 'replace', 'linear'),
value(1, '20px', 'replace') ] } ],
},
{ desc: 'a property that can\'t be resolved to computed values in'
+ ' an intermediate keyframe along with other values',
frames: [ { margin: '5px', left: '10px' },
{ margin: '5px', left: '20px',
simulateComputeValuesFailure: true },
{ margin: '5px', left: '30px' } ],
expected: [ { property: 'margin-top',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] },
{ property: 'margin-right',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] },
{ property: 'margin-bottom',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] },
{ property: 'margin-left',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] },
{ property: 'left',
values: [ value(0, '10px', 'replace', 'linear'),
value(0.5, '20px', 'replace', 'linear'),
value(1, '30px', 'replace') ] } ]
},
{ desc: 'a property that can\'t be resolved to computed values in'
+ ' an intermediate keyframe by itself',
frames: [ { margin: '5px', left: '10px' },
{ margin: '5px',
simulateComputeValuesFailure: true },
{ margin: '5px', left: '30px' } ],
expected: [ { property: 'margin-top',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] },
{ property: 'margin-right',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] },
{ property: 'margin-bottom',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] },
{ property: 'margin-left',
values: [ value(0, '5px', 'replace', 'linear'),
value(1, '5px', 'replace') ] },
{ property: 'left',
values: [ value(0, '10px', 'replace', 'linear'),
value(1, '30px', 'replace') ] } ]
},
];
gTests.forEach(function(subtest) {

View File

@ -28,6 +28,19 @@ dictionary BaseKeyframe {
double? offset = null;
DOMString easing = "linear";
CompositeOperation composite;
// Non-standard extensions
// Member to allow testing when StyleAnimationValue::ComputeValues fails.
//
// Note that we currently only apply this to shorthand properties since
// it's easier to annotate shorthand property values and because we have
// only ever observed ComputeValues failing on shorthand values.
//
// Bug 1216844 should remove this member since after that bug is fixed we will
// have a well-defined behavior to use when animation endpoints are not
// available.
[ChromeOnly] boolean simulateComputeValuesFailure = false;
};
dictionary BaseComputedKeyframe : BaseKeyframe {

View File

@ -1554,6 +1554,21 @@ private:
nsCSSValueGradient& operator=(const nsCSSValueGradient& aOther) = delete;
};
// A string value used primarily to represent variable references.
//
// Animation code, specifically the KeyframeUtils class, also uses this
// type as a container for various string values including:
//
// * Shorthand property values
// * Shorthand sentinel values used for testing failure conditions
// * Invalid longhand property values
//
// For the most part, the above values are not passed to functions that
// manipulate nsCSSValue objects in a generic fashion. Instead KeyframeUtils
// extracts the string from the nsCSSValueTokenStream and passes that around
// instead. The single exception is nsCSSValue::AppendToString which we use
// to serialize the string contained in the nsCSSValueTokenStream by ensuring
// the mShorthandPropertyID is set to eCSSProperty_UNKNOWN.
struct nsCSSValueTokenStream final {
nsCSSValueTokenStream();