Bug 896050 - Implement animation of CSS filter property. r=dholbert

This commit is contained in:
Dirk Schulze 2013-09-11 15:24:03 -07:00
parent 49e92d12a3
commit fafa0b24ab
4 changed files with 532 additions and 31 deletions

View File

@ -3374,7 +3374,7 @@ CSS_PROP_SVGRESET(
0,
nullptr,
CSS_PROP_NO_OFFSET,
eStyleAnimType_None)
eStyleAnimType_Custom)
CSS_PROP_SVGRESET(
flood-color,
flood_color,

View File

@ -248,6 +248,34 @@ nscoordToCSSValue(nscoord aCoord, nsCSSValue& aCSSValue)
eCSSUnit_Pixel);
}
static void
AppendCSSShadowValue(const nsCSSShadowItem *aShadow,
nsCSSValueList **&aResultTail)
{
NS_ABORT_IF_FALSE(aShadow, "shadow expected");
// X, Y, Radius, Spread, Color, Inset
nsRefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(6);
nscoordToCSSValue(aShadow->mXOffset, arr->Item(0));
nscoordToCSSValue(aShadow->mYOffset, arr->Item(1));
nscoordToCSSValue(aShadow->mRadius, arr->Item(2));
// NOTE: This code sometimes stores mSpread: 0 even when
// the parser would be required to leave it null.
nscoordToCSSValue(aShadow->mSpread, arr->Item(3));
if (aShadow->mHasColor) {
arr->Item(4).SetColorValue(aShadow->mColor);
}
if (aShadow->mInset) {
arr->Item(5).SetIntValue(NS_STYLE_BOX_SHADOW_INSET,
eCSSUnit_Enumerated);
}
nsCSSValueList *resultItem = new nsCSSValueList;
resultItem->mValue.SetArrayValue(arr, eCSSUnit_Array);
*aResultTail = resultItem;
aResultTail = &resultItem->mNext;
}
// Like nsStyleCoord::Calc, but with length in float pixels instead of nscoord.
struct CalcValue {
float mLength, mPercent;
@ -753,6 +781,8 @@ nsStyleAnimation::ComputeDistance(nsCSSProperty aProperty,
aDistance = sqrt(squareDistance);
return true;
}
case eUnit_Filter:
// FIXME: Support paced animations for filter function interpolation.
case eUnit_Transform: {
return false;
}
@ -1032,6 +1062,38 @@ AddCSSValuePixelPercentCalc(const uint32_t aValueRestrictions,
return true;
}
static inline float
GetNumberOrPercent(const nsCSSValue &aValue)
{
nsCSSUnit unit = aValue.GetUnit();
NS_ABORT_IF_FALSE(unit == eCSSUnit_Number || unit == eCSSUnit_Percent,
"unexpected unit");
return (unit == eCSSUnit_Number) ?
aValue.GetFloatValue() : aValue.GetPercentValue();
}
static inline void
AddCSSValuePercentNumber(const uint32_t aValueRestrictions,
double aCoeff1, const nsCSSValue &aValue1,
double aCoeff2, const nsCSSValue &aValue2,
nsCSSValue &aResult, float aInitialVal)
{
float n1 = GetNumberOrPercent(aValue1);
float n2 = GetNumberOrPercent(aValue2);
// Rather than interpolating aValue1 and aValue2 directly, we
// interpolate their *distances from aInitialVal* (the initial value,
// which is either 1 or 0 for "filter" functions). This matters in
// cases where aInitialVal is nonzero and the coefficients don't add
// up to 1. For example, if initialVal is 1, aCoeff1 is 0.5, and
// aCoeff2 is 0, then we'll return the value halfway between 1 and
// aValue1, rather than the value halfway between 0 and aValue1.
// Note that we do something similar in AddTransformScale().
float result = (n1 - aInitialVal) * aCoeff1 + (n2 - aInitialVal) * aCoeff2;
aResult.SetFloatValue(RestrictValue(aValueRestrictions, result + aInitialVal),
eCSSUnit_Number);
}
static bool
AddShadowItems(double aCoeff1, const nsCSSValue &aValue1,
double aCoeff2, const nsCSSValue &aValue2,
@ -1547,6 +1609,121 @@ TransformFunctionsMatch(nsCSSKeyword func1, nsCSSKeyword func2)
return ToPrimitive(func1) == ToPrimitive(func2);
}
static bool
AddFilterFunctionImpl(double aCoeff1, const nsCSSValueList* aList1,
double aCoeff2, const nsCSSValueList* aList2,
nsCSSValueList**& aResultTail)
{
// AddFilterFunction should be our only caller, and it should ensure that both
// args are non-null.
NS_ABORT_IF_FALSE(aList1, "expected filter list");
NS_ABORT_IF_FALSE(aList2, "expected filter list");
NS_ABORT_IF_FALSE(aList1->mValue.GetUnit() == eCSSUnit_Function,
"expected function");
NS_ABORT_IF_FALSE(aList2->mValue.GetUnit() == eCSSUnit_Function,
"expected function");
nsRefPtr<nsCSSValue::Array> a1 = aList1->mValue.GetArrayValue(),
a2 = aList2->mValue.GetArrayValue();
nsCSSKeyword filterFunction = a1->Item(0).GetKeywordValue();
if (filterFunction != a2->Item(0).GetKeywordValue())
return false; // Can't add two filters of different types.
nsAutoPtr<nsCSSValueList> resultListEntry(new nsCSSValueList);
nsCSSValue::Array* result =
resultListEntry->mValue.InitFunction(filterFunction, 1);
// "hue-rotate" is the only filter-function that accepts negative values, and
// we don't use this "restrictions" variable in its clause below.
const uint32_t restrictions = CSS_PROPERTY_VALUE_NONNEGATIVE;
const nsCSSValue& funcArg1 = a1->Item(1);
const nsCSSValue& funcArg2 = a2->Item(1);
nsCSSValue& resultArg = result->Item(1);
float initialVal = 1.0f;
switch (filterFunction) {
case eCSSKeyword_blur: {
nsCSSUnit unit;
if (funcArg1.GetUnit() == funcArg2.GetUnit()) {
unit = funcArg1.GetUnit();
} else {
// If units differ, we'll just combine them with calc().
unit = eCSSUnit_Calc;
}
if (!AddCSSValuePixelPercentCalc(restrictions,
unit,
aCoeff1, funcArg1,
aCoeff2, funcArg2,
resultArg)) {
return false;
}
break;
}
case eCSSKeyword_grayscale:
case eCSSKeyword_invert:
case eCSSKeyword_sepia:
initialVal = 0.0f;
case eCSSKeyword_brightness:
case eCSSKeyword_contrast:
case eCSSKeyword_opacity:
case eCSSKeyword_saturate:
AddCSSValuePercentNumber(restrictions,
aCoeff1, funcArg1,
aCoeff2, funcArg2,
resultArg,
initialVal);
break;
case eCSSKeyword_hue_rotate:
AddCSSValueAngle(aCoeff1, funcArg1,
aCoeff2, funcArg2,
resultArg);
break;
case eCSSKeyword_drop_shadow: {
nsCSSValueList* resultShadow = resultArg.SetListValue();
nsAutoPtr<nsCSSValueList> shadowValue;
nsCSSValueList **shadowTail = getter_Transfers(shadowValue);
NS_ABORT_IF_FALSE(!funcArg1.GetListValue()->mNext &&
!funcArg2.GetListValue()->mNext,
"drop-shadow filter func doesn't support lists");
if (!AddShadowItems(aCoeff1, funcArg1.GetListValue()->mValue,
aCoeff2, funcArg2.GetListValue()->mValue,
shadowTail)) {
return false;
}
*resultShadow = *shadowValue;
break;
}
default:
NS_ABORT_IF_FALSE(false, "unknown filter function");
return false;
}
*aResultTail = resultListEntry.forget();
aResultTail = &(*aResultTail)->mNext;
return true;
}
static bool
AddFilterFunction(double aCoeff1, const nsCSSValueList* aList1,
double aCoeff2, const nsCSSValueList* aList2,
nsCSSValueList**& aResultTail)
{
NS_ABORT_IF_FALSE(aList1 || aList2,
"one function list item must not be null");
// Note that one of our arguments could be null, indicating that
// it's the initial value. Rather than adding special null-handling
// logic, we just check for null values and replace them with
// 0 * the other value. That way, AddFilterFunctionImpl can assume
// its args are non-null.
if (!aList1) {
return AddFilterFunctionImpl(aCoeff2, aList2, 0, aList2, aResultTail);
}
if (!aList2) {
return AddFilterFunctionImpl(aCoeff1, aList1, 0, aList1, aResultTail);
}
return AddFilterFunctionImpl(aCoeff1, aList1, aCoeff2, aList2, aResultTail);
}
static nsCSSValueList*
AddTransformLists(double aCoeff1, const nsCSSValueList* aList1,
double aCoeff2, const nsCSSValueList* aList2)
@ -2060,6 +2237,44 @@ nsStyleAnimation::AddWeighted(nsCSSProperty aProperty,
aResultValue.SetAndAdoptCSSValueListValue(result.forget(), eUnit_Shadow);
return true;
}
case eUnit_Filter: {
const nsCSSValueList *list1 = aValue1.GetCSSValueListValue();
const nsCSSValueList *list2 = aValue2.GetCSSValueListValue();
nsAutoPtr<nsCSSValueList> result;
nsCSSValueList **resultTail = getter_Transfers(result);
while (list1 || list2) {
NS_ABORT_IF_FALSE(!*resultTail,
"resultTail isn't pointing to the tail (may leak)");
if ((list1 && list1->mValue.GetUnit() != eCSSUnit_Function) ||
(list2 && list2->mValue.GetUnit() != eCSSUnit_Function)) {
// If we don't have filter-functions, we must have filter-URLs, which
// we can't add or interpolate.
return false;
}
if (!AddFilterFunction(aCoeff1, list1, aCoeff2, list2, resultTail)) {
// filter function mismatch
return false;
}
// move to next list items
if (list1) {
list1 = list1->mNext;
}
if (list2) {
list2 = list2->mNext;
}
};
NS_ABORT_IF_FALSE(!*resultTail,
"resultTail isn't pointing to the tail (may leak)");
aResultValue.SetAndAdoptCSSValueListValue(result.forget(),
eUnit_Filter);
return true;
}
case eUnit_Transform: {
const nsCSSValueList *list1 = aValue1.GetCSSValueListValue();
const nsCSSValueList *list2 = aValue2.GetCSSValueListValue();
@ -2432,6 +2647,7 @@ nsStyleAnimation::UncomputeValue(nsCSSProperty aProperty,
} break;
case eUnit_Dasharray:
case eUnit_Shadow:
case eUnit_Filter:
case eUnit_Transform:
case eUnit_BackgroundPosition:
aSpecifiedValue.
@ -2544,12 +2760,27 @@ StyleCoordToCSSValue(const nsStyleCoord& aCoord, nsCSSValue& aCSSValue)
case eStyleUnit_Coord:
nscoordToCSSValue(aCoord.GetCoordValue(), aCSSValue);
break;
case eStyleUnit_Factor:
aCSSValue.SetFloatValue(aCoord.GetFactorValue(), eCSSUnit_Number);
break;
case eStyleUnit_Percent:
aCSSValue.SetPercentValue(aCoord.GetPercentValue());
break;
case eStyleUnit_Calc:
SetCalcValue(aCoord.GetCalcValue(), aCSSValue);
break;
case eStyleUnit_Degree:
aCSSValue.SetFloatValue(aCoord.GetAngleValue(), eCSSUnit_Degree);
break;
case eStyleUnit_Grad:
aCSSValue.SetFloatValue(aCoord.GetAngleValue(), eCSSUnit_Grad);
break;
case eStyleUnit_Radian:
aCSSValue.SetFloatValue(aCoord.GetAngleValue(), eCSSUnit_Radian);
break;
case eStyleUnit_Turn:
aCSSValue.SetFloatValue(aCoord.GetAngleValue(), eCSSUnit_Turn);
break;
default:
NS_ABORT_IF_FALSE(false, "unexpected unit");
return false;
@ -3012,6 +3243,63 @@ nsStyleAnimation::ExtractComputedValue(nsCSSProperty aProperty,
break;
}
case eCSSProperty_filter: {
const nsStyleSVGReset *svgReset =
static_cast<const nsStyleSVGReset*>(styleStruct);
const nsTArray<nsStyleFilter>& filters = svgReset->mFilters;
nsAutoPtr<nsCSSValueList> result;
nsCSSValueList **resultTail = getter_Transfers(result);
for (uint32_t i = 0; i < filters.Length(); ++i) {
nsCSSValueList *item = new nsCSSValueList;
*resultTail = item;
resultTail = &item->mNext;
const nsStyleFilter& filter = filters[i];
int32_t type = filter.GetType();
if (type == NS_STYLE_FILTER_URL) {
nsIDocument* doc = aStyleContext->PresContext()->Document();
nsRefPtr<nsStringBuffer> uriAsStringBuffer =
GetURIAsUtf16StringBuffer(filter.GetURL());
nsRefPtr<mozilla::css::URLValue> url =
new mozilla::css::URLValue(filter.GetURL(),
uriAsStringBuffer,
doc->GetDocumentURI(),
doc->NodePrincipal());
item->mValue.SetURLValue(url);
} else {
nsCSSKeyword functionName =
nsCSSProps::ValueToKeywordEnum(type,
nsCSSProps::kFilterFunctionKTable);
nsCSSValue::Array* filterArray =
item->mValue.InitFunction(functionName, 1);
if (type >= NS_STYLE_FILTER_BLUR && type <= NS_STYLE_FILTER_HUE_ROTATE) {
if (!StyleCoordToCSSValue(
filter.GetFilterParameter(),
filterArray->Item(1))) {
return false;
}
} else if (type == NS_STYLE_FILTER_DROP_SHADOW) {
nsCSSValueList* shadowResult = filterArray->Item(1).SetListValue();
nsAutoPtr<nsCSSValueList> tmpShadowValue;
nsCSSValueList **tmpShadowResultTail = getter_Transfers(tmpShadowValue);
nsCSSShadowArray* shadowArray = filter.GetDropShadow();
NS_ABORT_IF_FALSE(shadowArray->Length() == 1,
"expected exactly one shadow");
AppendCSSShadowValue(shadowArray->ShadowAt(0), tmpShadowResultTail);
*shadowResult = *tmpShadowValue;
} else {
// We checked all possible nsStyleFilter types but
// NS_STYLE_FILTER_NULL before. We should never enter this path.
NS_NOTREACHED("no other filter functions defined");
return false;
}
}
}
aComputedValue.SetAndAdoptCSSValueListValue(result.forget(),
eUnit_Filter);
break;
}
case eCSSProperty_transform: {
const nsStyleDisplay *display =
static_cast<const nsStyleDisplay*>(styleStruct);
@ -3172,30 +3460,7 @@ nsStyleAnimation::ExtractComputedValue(nsCSSProperty aProperty,
nsAutoPtr<nsCSSValueList> result;
nsCSSValueList **resultTail = getter_Transfers(result);
for (uint32_t i = 0, i_end = shadowArray->Length(); i < i_end; ++i) {
const nsCSSShadowItem *shadow = shadowArray->ShadowAt(i);
// X, Y, Radius, Spread, Color, Inset
nsRefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(6);
nscoordToCSSValue(shadow->mXOffset, arr->Item(0));
nscoordToCSSValue(shadow->mYOffset, arr->Item(1));
nscoordToCSSValue(shadow->mRadius, arr->Item(2));
// NOTE: This code sometimes stores mSpread: 0 even when
// the parser would be required to leave it null.
nscoordToCSSValue(shadow->mSpread, arr->Item(3));
if (shadow->mHasColor) {
arr->Item(4).SetColorValue(shadow->mColor);
}
if (shadow->mInset) {
arr->Item(5).SetIntValue(NS_STYLE_BOX_SHADOW_INSET,
eCSSUnit_Enumerated);
}
nsCSSValueList *resultItem = new nsCSSValueList;
if (!resultItem) {
return false;
}
resultItem->mValue.SetArrayValue(arr, eCSSUnit_Array);
*resultTail = resultItem;
resultTail = &resultItem->mNext;
AppendCSSShadowValue(shadowArray->ShadowAt(i), resultTail);
}
aComputedValue.SetAndAdoptCSSValueListValue(result.forget(),
eUnit_Shadow);
@ -3299,12 +3564,14 @@ nsStyleAnimation::Value::operator=(const Value& aOther)
mUnit = eUnit_Null;
}
break;
case eUnit_Filter:
case eUnit_Dasharray:
case eUnit_Shadow:
case eUnit_Transform:
case eUnit_BackgroundPosition:
NS_ABORT_IF_FALSE(mUnit == eUnit_Shadow || aOther.mValue.mCSSValueList,
"value lists other than shadows may not be null");
NS_ABORT_IF_FALSE(mUnit == eUnit_Shadow || mUnit == eUnit_Filter ||
aOther.mValue.mCSSValueList,
"value lists other than shadows and filters may not be null");
if (aOther.mValue.mCSSValueList) {
mValue.mCSSValueList = aOther.mValue.mCSSValueList->Clone();
if (!mValue.mCSSValueList) {
@ -3453,8 +3720,9 @@ nsStyleAnimation::Value::SetAndAdoptCSSValueListValue(
{
FreeValue();
NS_ABORT_IF_FALSE(IsCSSValueListUnit(aUnit), "bad unit");
NS_ABORT_IF_FALSE(aUnit != eUnit_Dasharray || aValueList != nullptr,
"dasharrays may not be null");
NS_ABORT_IF_FALSE(aUnit != eUnit_Dasharray || aUnit != eUnit_Filter ||
aValueList != nullptr,
"dasharrays and filters may not be null");
mUnit = aUnit;
mValue.mCSSValueList = aValueList; // take ownership
}
@ -3523,6 +3791,7 @@ nsStyleAnimation::Value::operator==(const Value& aOther) const
case eUnit_CSSRect:
return *mValue.mCSSRect == *aOther.mValue.mCSSRect;
case eUnit_Dasharray:
case eUnit_Filter:
case eUnit_Shadow:
case eUnit_Transform:
case eUnit_BackgroundPosition:

View File

@ -221,6 +221,7 @@ public:
eUnit_CSSValueTriplet, // nsCSSValueTriplet* (never null)
eUnit_CSSRect, // nsCSSRect* (never null)
eUnit_Dasharray, // nsCSSValueList* (never null)
eUnit_Filter, // nsCSSValueList* (may be null)
eUnit_Shadow, // nsCSSValueList* (may be null)
eUnit_Transform, // nsCSSValueList* (never null)
eUnit_BackgroundPosition, // nsCSSValueList* (never null)
@ -380,8 +381,9 @@ public:
return aUnit == eUnit_CSSRect;
}
static bool IsCSSValueListUnit(Unit aUnit) {
return aUnit == eUnit_Dasharray || aUnit == eUnit_Shadow ||
aUnit == eUnit_Transform || aUnit == eUnit_BackgroundPosition;
return aUnit == eUnit_Dasharray || aUnit == eUnit_Filter ||
aUnit == eUnit_Shadow || aUnit == eUnit_Transform ||
aUnit == eUnit_BackgroundPosition;
}
static bool IsCSSValuePairListUnit(Unit aUnit) {
return aUnit == eUnit_CSSValuePairList;

View File

@ -122,6 +122,7 @@ var supported_properties = {
// opacity is clamped in computed style
// (not parsing/interpolation)
test_float_zeroToOne_clamped ],
"filter" : [ test_filter_transition ],
"flood-color": [ test_color_transition ],
"flood-opacity" : [ test_float_zeroToOne_transition,
// opacity is clamped in computed style
@ -576,6 +577,156 @@ var transformTests = [
round_error_ok: true },
];
var filterTests = [
{ start: "none", end: "none",
expected: ["none"] },
// function from none (number/length)
{ start: "none", end: "brightness(0.5)",
expected: ["brightness", 0.875] },
{ start: "none", end: "contrast(0.5)",
expected: ["contrast", 0.875] },
{ start: "none", end: "grayscale(0.5)",
expected: ["grayscale", 0.125] },
{ start: "none", end: "invert(0.5)",
expected: ["invert", 0.125] },
{ start: "none", end: "opacity(0.5)",
expected: ["opacity", 0.875] },
{ start: "none", end: "saturate(0.5)",
expected: ["saturate", 0.875] },
{ start: "none", end: "sepia(0.5)",
expected: ["sepia", 0.125] },
{ start: "none", end: "blur(50px)",
expected: ["blur", 12.5] },
// function to none (number/length)
{ start: "brightness(0.5)", end: "none",
expected: ["brightness", 0.625] },
{ start: "contrast(0.5)", end: "none",
expected: ["contrast", 0.625] },
{ start: "grayscale(0.5)", end: "none",
expected: ["grayscale", 0.375] },
{ start: "invert(0.5)", end: "none",
expected: ["invert", 0.375] },
{ start: "opacity(0.5)", end: "none",
expected: ["opacity", 0.625] },
{ start: "saturate(0.5)", end: "none",
expected: ["saturate", 0.625] },
{ start: "sepia(0.5)", end: "none",
expected: ["sepia", 0.375] },
{ start: "blur(50px)", end: "none",
expected: ["blur", 37.5] },
// function to same function (number/length)
{ start: "brightness(0.25)", end: "brightness(0.75)",
expected: ["brightness", 0.375] },
{ start: "contrast(0.25)", end: "contrast(0.75)",
expected: ["contrast", 0.375] },
{ start: "grayscale(0.25)", end: "grayscale(0.75)",
expected: ["grayscale", 0.375] },
{ start: "invert(0.25)", end: "invert(0.75)",
expected: ["invert", 0.375] },
{ start: "opacity(0.25)", end: "opacity(0.75)",
expected: ["opacity", 0.375] },
{ start: "saturate(0.25)", end: "saturate(0.75)",
expected: ["saturate", 0.375] },
{ start: "sepia(0.25)", end: "sepia(0.75)",
expected: ["sepia", 0.375] },
{ start: "blur(25px)", end: "blur(75px)",
expected: ["blur", 37.5] },
// function to same function (percent)
{ start: "brightness(25%)", end: "brightness(75%)",
expected: ["brightness", 0.375] },
{ start: "contrast(25%)", end: "contrast(75%)",
expected: ["contrast", 0.375] },
{ start: "grayscale(25%)", end: "grayscale(75%)",
expected: ["grayscale", 0.375] },
{ start: "invert(25%)", end: "invert(75%)",
expected: ["invert", 0.375] },
{ start: "opacity(25%)", end: "opacity(75%)",
expected: ["opacity", 0.375] },
{ start: "saturate(25%)", end: "saturate(75%)",
expected: ["saturate", 0.375] },
{ start: "sepia(25%)", end: "sepia(75%)",
expected: ["sepia", 0.375] },
// function to same function (percent, number/length)
{ start: "brightness(0.25)", end: "brightness(75%)",
expected: ["brightness", 0.375] },
{ start: "contrast(25%)", end: "contrast(0.75)",
expected: ["contrast", 0.375] },
// hue-rotate with different angle values
{ start: "hue-rotate(0deg)", end: "hue-rotate(720deg)",
expected: ["hue-rotate", Math.PI.toFixed(5)] },
{ start: "hue-rotate(0rad)", end: "hue-rotate("+4*Math.PI+"rad)",
expected: ["hue-rotate", Math.PI.toFixed(5)] },
{ start: "hue-rotate(0grad)", end: "hue-rotate(800grad)",
expected: ["hue-rotate", Math.PI.toFixed(5)] },
{ start: "hue-rotate(0turn)", end: "hue-rotate(2turn)",
expected: ["hue-rotate", Math.PI.toFixed(5)] },
{ start: "hue-rotate(0deg)", end: "hue-rotate("+4*Math.PI+"rad)",
expected: ["hue-rotate", Math.PI.toFixed(5)] },
{ start: "hue-rotate(0turn)", end: "hue-rotate(800grad)",
expected: ["hue-rotate", Math.PI.toFixed(5)] },
{ start: "hue-rotate(0grad)", end: "hue-rotate("+4*Math.PI+"rad)",
expected: ["hue-rotate", Math.PI.toFixed(5)] },
{ start: "hue-rotate(0grad)", end: "hue-rotate(0turn)",
expected: ["hue-rotate", 0] },
// multiple matching functions, same length
{ start: "contrast(25%) brightness(0.25) blur(25px) sepia(75%)",
end: "contrast(75%) brightness(0.75) blur(75px) sepia(25%)",
expected: ["contrast", 0.375, "brightness", 0.375, "blur", 37.5, "sepia", 0.625] },
{ start: "invert(25%) brightness(0.25) blur(25px) invert(50%) brightness(0.5) blur(50px)",
end: "invert(75%) brightness(0.75) blur(75px)",
expected: ["invert", 0.375, "brightness", 0.375, "blur", 37.5, "invert", 0.375, "brightness", 0.625, "blur", 37.5] },
// multiple matching functions, different length
{ start: "contrast(25%) brightness(0.5) blur(50px)",
end: "contrast(75%)",
expected: ["contrast", 0.375, "brightness", 0.625, "blur", 37.5] },
// mismatching filter functions
{ start: "contrast(0%)", end: "blur(10px)",
expected: ["blur", 10] },
// not supported interpolations
{ start: "none", end: "url('#b')",
expected: ["url", "\""+document.URL+"#b\""] },
{ start: "url('#a')", end: "none",
expected: ["none"] },
{ start: "url('#a')", end: "url('#b')",
expected: ["url", "\""+document.URL+"#b\""] },
{ start: "url('#a')", end: "blur(10px)",
expected: ["blur", 10] },
{ start: "blur(10px)", end: "url('#a')",
expected: ["url", "\""+document.URL+"#a\""] },
{ start: "blur(0px) url('#a')", end: "blur(20px)",
expected: ["blur", 20] },
{ start: "blur(0px)", end: "blur(20px) url('#a')",
expected: ["blur", 20, "url", "\""+document.URL+"#a\""] },
{ start: "contrast(0.25) brightness(0.25) blur(25px)",
end: "contrast(0.75) url('#a')",
expected: ["contrast", 0.75, "url", "\""+document.URL+"#a\""] },
{ start: "contrast(0.25) brightness(0.25) blur(75px)",
end: "brightness(0.75) contrast(0.75) blur(25px)",
expected: ["brightness", 0.75, "contrast", 0.75, "blur", 25] },
{ start: "contrast(0.25) brightness(0.25) blur(25px)",
end: "contrast(0.75) brightness(0.75) contrast(0.75)",
expected: ["contrast", 0.75, "brightness", 0.75, "contrast", 0.75] },
// drop-shadow animation
{ start: "none",
end: "drop-shadow(rgb(0, 0, 0) 4px 4px 0px)",
expected: ["drop-shadow", "rgba(0, 0, 0, 0.25) 1px 1px 0px"] },
{ start: "drop-shadow(rgb(0, 0, 0) 0px 0px 0px)",
end: "drop-shadow(rgb(0, 0, 0) 4px 4px 0px)",
expected: ["drop-shadow", "rgb(0, 0, 0) 1px 1px 0px"] },
{ start: "drop-shadow(#038000 4px 4px)",
end: "drop-shadow(8px 8px 8px red)",
expected: ["drop-shadow", "rgb(66, 96, 0) 5px 5px 2px"] },
{ start: "blur(25px) drop-shadow(8px 8px)",
end: "blur(75px)",
expected: ["blur", 37.5, "drop-shadow", "rgb(0, 0, 0) 6px 6px 0px"] },
{ start: "blur(75px)",
end: "blur(25px) drop-shadow(8px 8px)",
expected: ["blur", 62.5, "drop-shadow", "rgb(0, 0, 0) 2px 2px 0px"] },
{ start: "drop-shadow(2px 2px blue)",
end: "none",
expected: ["drop-shadow", "rgba(0, 0, 255, 0.75) 1.5px 1.5px 0px"] },
];
var prop;
for (prop in supported_properties) {
// Test that prop is in the property database.
@ -995,6 +1146,85 @@ function test_border_color_transition(prop) {
div.style.removeProperty("color");
}
function filter_function_list_equals(string, expectedList)
{
// Check simple case "none"
if (string == "none" && string == expectedList[0]) {
return true;
}
// The regular expression does not filter out the last parenthesis.
// Remove last character for now.
is(string.substring(string.length - 1, string.length), ')', "Check if last character is close-paren.")
string = string.substring(0, string.length - 1);
var reg = /\)*\s*(blur|brightness|contrast|grayscale|hue\-rotate|invert|opacity|saturate|sepia|drop\-shadow|url)\(/
var matches = string.split(reg);
// First item must be empty. All other items are of functionName, functionValue.
if (!matches || matches.shift() != "") {
ok(false, "computed style of 'filter' isn't in the format we expect");
return false;
}
// Odd items are the function name, even items the function value.
if (!matches.length || matches.length % 2 ||
expectedList.length != matches.length) {
ok(false, "computed style of 'filter' isn't in the format we expect");
return false;
}
for (var i = 0; i < matches.length; i += 2) {
var functionName = matches[i];
var functionValue = matches[i+1];
var expected = expectedList[i+1]
var tolerance = 0;
// Check if we have the expected function.
if (functionName != expectedList[i]) {
return false;
}
if (functionName == "blur") {
// Last two characters must be "px".
if (functionValue.search("px") != functionValue.length - 2) {
return false;
}
functionValue = functionValue.substring(0, functionValue.length - 2);
} else if (functionName == "hue-rotate") {
// Last two characters must be "rad".
if (functionValue.search("rad") != functionValue.length - 3) {
return false;
}
tolerance = 0.001;
functionValue = functionValue.substring(0, functionValue.length - 3);
} else if (functionName == "drop-shadow" || functionName == "url") {
if (functionValue != expected) {
return false;
}
continue;
}
// Check if string is not a number or difference is not in tolerance level.
if (isNaN(functionValue) ||
Math.abs(parseFloat(functionValue) - expected) > tolerance) {
return false;
}
}
return true;
}
function test_filter_transition(prop) {
if (!SpecialPowers.getBoolPref("layout.css.filters.enabled")) {
return;
}
for (var i in filterTests) {
var test = filterTests[i];
div.style.setProperty("transition-property", "none", "");
div.style.setProperty(prop, test.start, "");
cs.getPropertyValue(prop);
div.style.setProperty("transition-property", prop, "");
div.style.setProperty(prop, test.end, "");
var actual = cs.getPropertyValue(prop);
ok(filter_function_list_equals(actual, test.expected),
"Filter property is " + actual + " expected values of " +
test.expected);
}
}
function test_shadow_transition(prop) {
var spreadStr = (prop == "box-shadow") ? " 0px" : "";