diff -r 000000000000 -r 6474c204b198 layout/style/nsStyleAnimation.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layout/style/nsStyleAnimation.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,3832 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Utilities for animation of computed style values */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/MathAlgorithms.h" + +#include "nsStyleAnimation.h" +#include "nsStyleTransformMatrix.h" +#include "nsCOMArray.h" +#include "nsIStyleRule.h" +#include "mozilla/css/StyleRule.h" +#include "nsString.h" +#include "nsStyleContext.h" +#include "nsStyleSet.h" +#include "nsComputedDOMStyle.h" +#include "nsCSSParser.h" +#include "mozilla/css/Declaration.h" +#include "mozilla/dom/Element.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/Likely.h" +#include "gfxMatrix.h" +#include "gfxQuaternion.h" +#include "nsIDocument.h" + +using namespace mozilla; + +// HELPER METHODS +// -------------- +/* + * Given two units, this method returns a common unit that they can both be + * converted into, if possible. This is intended to facilitate + * interpolation, distance-computation, and addition between "similar" units. + * + * The ordering of the arguments should not affect the output of this method. + * + * If there's no sensible common unit, this method returns eUnit_Null. + * + * @param aFirstUnit One unit to resolve. + * @param aFirstUnit The other unit to resolve. + * @return A "common" unit that both source units can be converted into, or + * eUnit_Null if that's not possible. + */ +static +nsStyleAnimation::Unit +GetCommonUnit(nsCSSProperty aProperty, + nsStyleAnimation::Unit aFirstUnit, + nsStyleAnimation::Unit aSecondUnit) +{ + if (aFirstUnit != aSecondUnit) { + if (nsCSSProps::PropHasFlags(aProperty, CSS_PROPERTY_STORES_CALC) && + (aFirstUnit == nsStyleAnimation::eUnit_Coord || + aFirstUnit == nsStyleAnimation::eUnit_Percent || + aFirstUnit == nsStyleAnimation::eUnit_Calc) && + (aSecondUnit == nsStyleAnimation::eUnit_Coord || + aSecondUnit == nsStyleAnimation::eUnit_Percent || + aSecondUnit == nsStyleAnimation::eUnit_Calc)) { + // We can use calc() as the common unit. + return nsStyleAnimation::eUnit_Calc; + } + return nsStyleAnimation::eUnit_Null; + } + return aFirstUnit; +} + +static +nsCSSUnit +GetCommonUnit(nsCSSProperty aProperty, + nsCSSUnit aFirstUnit, + nsCSSUnit aSecondUnit) +{ + if (aFirstUnit != aSecondUnit) { + if (nsCSSProps::PropHasFlags(aProperty, CSS_PROPERTY_STORES_CALC) && + (aFirstUnit == eCSSUnit_Pixel || + aFirstUnit == eCSSUnit_Percent || + aFirstUnit == eCSSUnit_Calc) && + (aSecondUnit == eCSSUnit_Pixel || + aSecondUnit == eCSSUnit_Percent || + aSecondUnit == eCSSUnit_Calc)) { + // We can use calc() as the common unit. + return eCSSUnit_Calc; + } + return eCSSUnit_Null; + } + return aFirstUnit; +} + +static nsCSSKeyword +ToPrimitive(nsCSSKeyword aKeyword) +{ + switch (aKeyword) { + case eCSSKeyword_translatex: + case eCSSKeyword_translatey: + case eCSSKeyword_translatez: + case eCSSKeyword_translate: + return eCSSKeyword_translate3d; + case eCSSKeyword_scalex: + case eCSSKeyword_scaley: + case eCSSKeyword_scalez: + case eCSSKeyword_scale: + return eCSSKeyword_scale3d; + default: + return aKeyword; + } +} + +static already_AddRefed +AppendFunction(nsCSSKeyword aTransformFunction) +{ + uint32_t nargs; + switch (aTransformFunction) { + case eCSSKeyword_matrix3d: + nargs = 16; + break; + case eCSSKeyword_matrix: + nargs = 6; + break; + case eCSSKeyword_rotate3d: + nargs = 4; + break; + case eCSSKeyword_interpolatematrix: + case eCSSKeyword_translate3d: + case eCSSKeyword_scale3d: + nargs = 3; + break; + case eCSSKeyword_translate: + case eCSSKeyword_skew: + case eCSSKeyword_scale: + nargs = 2; + break; + default: + NS_ERROR("must be a transform function"); + case eCSSKeyword_translatex: + case eCSSKeyword_translatey: + case eCSSKeyword_translatez: + case eCSSKeyword_scalex: + case eCSSKeyword_scaley: + case eCSSKeyword_scalez: + case eCSSKeyword_skewx: + case eCSSKeyword_skewy: + case eCSSKeyword_rotate: + case eCSSKeyword_rotatex: + case eCSSKeyword_rotatey: + case eCSSKeyword_rotatez: + case eCSSKeyword_perspective: + nargs = 1; + break; + } + + nsRefPtr arr = nsCSSValue::Array::Create(nargs + 1); + arr->Item(0).SetIntValue(aTransformFunction, eCSSUnit_Enumerated); + + return arr.forget(); +} + +static already_AddRefed +ToPrimitive(nsCSSValue::Array* aArray) +{ + nsCSSKeyword tfunc = nsStyleTransformMatrix::TransformFunctionOf(aArray); + nsCSSKeyword primitive = ToPrimitive(tfunc); + nsRefPtr arr = AppendFunction(primitive); + + // FIXME: This would produce fewer calc() expressions if the + // zero were of compatible type (length vs. percent) when + // needed. + + nsCSSValue zero(0.0f, eCSSUnit_Pixel); + nsCSSValue one(1.0f, eCSSUnit_Number); + switch(tfunc) { + case eCSSKeyword_translate: + { + NS_ABORT_IF_FALSE(aArray->Count() == 2 || aArray->Count() == 3, + "unexpected count"); + arr->Item(1) = aArray->Item(1); + arr->Item(2) = aArray->Count() == 3 ? aArray->Item(2) : zero; + arr->Item(3) = zero; + break; + } + case eCSSKeyword_translatex: + { + NS_ABORT_IF_FALSE(aArray->Count() == 2, "unexpected count"); + arr->Item(1) = aArray->Item(1); + arr->Item(2) = zero; + arr->Item(3) = zero; + break; + } + case eCSSKeyword_translatey: + { + NS_ABORT_IF_FALSE(aArray->Count() == 2, "unexpected count"); + arr->Item(1) = zero; + arr->Item(2) = aArray->Item(1); + arr->Item(3) = zero; + break; + } + case eCSSKeyword_translatez: + { + NS_ABORT_IF_FALSE(aArray->Count() == 2, "unexpected count"); + arr->Item(1) = zero; + arr->Item(2) = zero; + arr->Item(3) = aArray->Item(1); + break; + } + case eCSSKeyword_scale: + { + NS_ABORT_IF_FALSE(aArray->Count() == 2 || aArray->Count() == 3, + "unexpected count"); + arr->Item(1) = aArray->Item(1); + arr->Item(2) = aArray->Count() == 3 ? aArray->Item(2) : aArray->Item(1); + arr->Item(3) = one; + break; + } + case eCSSKeyword_scalex: + { + NS_ABORT_IF_FALSE(aArray->Count() == 2, "unexpected count"); + arr->Item(1) = aArray->Item(1); + arr->Item(2) = one; + arr->Item(3) = one; + break; + } + case eCSSKeyword_scaley: + { + NS_ABORT_IF_FALSE(aArray->Count() == 2, "unexpected count"); + arr->Item(1) = one; + arr->Item(2) = aArray->Item(1); + arr->Item(3) = one; + break; + } + case eCSSKeyword_scalez: + { + NS_ABORT_IF_FALSE(aArray->Count() == 2, "unexpected count"); + arr->Item(1) = one; + arr->Item(2) = one; + arr->Item(3) = aArray->Item(1); + break; + } + default: + arr = aArray; + } + return arr.forget(); +} + +inline void +nscoordToCSSValue(nscoord aCoord, nsCSSValue& aCSSValue) +{ + aCSSValue.SetFloatValue(nsPresContext::AppUnitsToFloatCSSPixels(aCoord), + eCSSUnit_Pixel); +} + +static void +AppendCSSShadowValue(const nsCSSShadowItem *aShadow, + nsCSSValueList **&aResultTail) +{ + NS_ABORT_IF_FALSE(aShadow, "shadow expected"); + + // X, Y, Radius, Spread, Color, Inset + nsRefPtr 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; + bool mHasPercent; +}; + +// Requires a canonical calc() value that we generated. +static CalcValue +ExtractCalcValueInternal(const nsCSSValue& aValue) +{ + NS_ABORT_IF_FALSE(aValue.GetUnit() == eCSSUnit_Calc, "unexpected unit"); + nsCSSValue::Array *arr = aValue.GetArrayValue(); + NS_ABORT_IF_FALSE(arr->Count() == 1, "unexpected length"); + + const nsCSSValue &topval = arr->Item(0); + CalcValue result; + if (topval.GetUnit() == eCSSUnit_Pixel) { + result.mLength = topval.GetFloatValue(); + result.mPercent = 0.0f; + result.mHasPercent = false; + } else { + NS_ABORT_IF_FALSE(topval.GetUnit() == eCSSUnit_Calc_Plus, + "unexpected unit"); + nsCSSValue::Array *arr2 = topval.GetArrayValue(); + const nsCSSValue &len = arr2->Item(0); + const nsCSSValue &pct = arr2->Item(1); + NS_ABORT_IF_FALSE(len.GetUnit() == eCSSUnit_Pixel, "unexpected unit"); + NS_ABORT_IF_FALSE(pct.GetUnit() == eCSSUnit_Percent, "unexpected unit"); + result.mLength = len.GetFloatValue(); + result.mPercent = pct.GetPercentValue(); + result.mHasPercent = true; + } + + return result; +} + +// Requires a canonical calc() value that we generated. +static CalcValue +ExtractCalcValue(const nsStyleAnimation::Value& aValue) +{ + CalcValue result; + if (aValue.GetUnit() == nsStyleAnimation::eUnit_Coord) { + result.mLength = + nsPresContext::AppUnitsToFloatCSSPixels(aValue.GetCoordValue()); + result.mPercent = 0.0f; + result.mHasPercent = false; + return result; + } + if (aValue.GetUnit() == nsStyleAnimation::eUnit_Percent) { + result.mLength = 0.0f; + result.mPercent = aValue.GetPercentValue(); + result.mHasPercent = true; + return result; + } + NS_ABORT_IF_FALSE(aValue.GetUnit() == nsStyleAnimation::eUnit_Calc, + "unexpected unit"); + nsCSSValue *val = aValue.GetCSSValueValue(); + return ExtractCalcValueInternal(*val); +} + +static CalcValue +ExtractCalcValue(const nsCSSValue& aValue) +{ + CalcValue result; + if (aValue.GetUnit() == eCSSUnit_Pixel) { + result.mLength = aValue.GetFloatValue(); + result.mPercent = 0.0f; + result.mHasPercent = false; + return result; + } + if (aValue.GetUnit() == eCSSUnit_Percent) { + result.mLength = 0.0f; + result.mPercent = aValue.GetPercentValue(); + result.mHasPercent = true; + return result; + } + return ExtractCalcValueInternal(aValue); +} + +static void +SetCalcValue(const nsStyleCoord::Calc* aCalc, nsCSSValue& aValue) +{ + nsRefPtr arr = nsCSSValue::Array::Create(1); + if (!aCalc->mHasPercent) { + nscoordToCSSValue(aCalc->mLength, arr->Item(0)); + } else { + nsCSSValue::Array *arr2 = nsCSSValue::Array::Create(2); + arr->Item(0).SetArrayValue(arr2, eCSSUnit_Calc_Plus); + nscoordToCSSValue(aCalc->mLength, arr2->Item(0)); + arr2->Item(1).SetPercentValue(aCalc->mPercent); + } + + aValue.SetArrayValue(arr, eCSSUnit_Calc); +} + +static void +SetCalcValue(const CalcValue& aCalc, nsCSSValue& aValue) +{ + nsRefPtr arr = nsCSSValue::Array::Create(1); + if (!aCalc.mHasPercent) { + arr->Item(0).SetFloatValue(aCalc.mLength, eCSSUnit_Pixel); + } else { + nsCSSValue::Array *arr2 = nsCSSValue::Array::Create(2); + arr->Item(0).SetArrayValue(arr2, eCSSUnit_Calc_Plus); + arr2->Item(0).SetFloatValue(aCalc.mLength, eCSSUnit_Pixel); + arr2->Item(1).SetPercentValue(aCalc.mPercent); + } + + aValue.SetArrayValue(arr, eCSSUnit_Calc); +} + +static already_AddRefed +GetURIAsUtf16StringBuffer(nsIURI* aUri) +{ + nsAutoCString utf8String; + nsresult rv = aUri->GetSpec(utf8String); + NS_ENSURE_SUCCESS(rv, nullptr); + + return nsCSSValue::BufferFromString(NS_ConvertUTF8toUTF16(utf8String)); +} + +// CLASS METHODS +// ------------- + +bool +nsStyleAnimation::ComputeDistance(nsCSSProperty aProperty, + const Value& aStartValue, + const Value& aEndValue, + double& aDistance) +{ + Unit commonUnit = + GetCommonUnit(aProperty, aStartValue.GetUnit(), aEndValue.GetUnit()); + + switch (commonUnit) { + case eUnit_Null: + case eUnit_Auto: + case eUnit_None: + case eUnit_Normal: + case eUnit_UnparsedString: + return false; + + case eUnit_Enumerated: + switch (aProperty) { + case eCSSProperty_font_stretch: { + // just like eUnit_Integer. + int32_t startInt = aStartValue.GetIntValue(); + int32_t endInt = aEndValue.GetIntValue(); + aDistance = Abs(endInt - startInt); + return true; + } + default: + return false; + } + case eUnit_Visibility: { + int32_t startEnum = aStartValue.GetIntValue(); + int32_t endEnum = aEndValue.GetIntValue(); + if (startEnum == endEnum) { + aDistance = 0; + return true; + } + if ((startEnum == NS_STYLE_VISIBILITY_VISIBLE) == + (endEnum == NS_STYLE_VISIBILITY_VISIBLE)) { + return false; + } + aDistance = 1; + return true; + } + case eUnit_Integer: { + int32_t startInt = aStartValue.GetIntValue(); + int32_t endInt = aEndValue.GetIntValue(); + aDistance = Abs(double(endInt) - double(startInt)); + return true; + } + case eUnit_Coord: { + nscoord startCoord = aStartValue.GetCoordValue(); + nscoord endCoord = aEndValue.GetCoordValue(); + aDistance = Abs(double(endCoord) - double(startCoord)); + return true; + } + case eUnit_Percent: { + float startPct = aStartValue.GetPercentValue(); + float endPct = aEndValue.GetPercentValue(); + aDistance = Abs(double(endPct) - double(startPct)); + return true; + } + case eUnit_Float: { + // Special case for flex-grow and flex-shrink: animations are + // disallowed between 0 and other values. + if ((aProperty == eCSSProperty_flex_grow || + aProperty == eCSSProperty_flex_shrink) && + (aStartValue.GetFloatValue() == 0.0f || + aEndValue.GetFloatValue() == 0.0f) && + aStartValue.GetFloatValue() != aEndValue.GetFloatValue()) { + return false; + } + + float startFloat = aStartValue.GetFloatValue(); + float endFloat = aEndValue.GetFloatValue(); + aDistance = Abs(double(endFloat) - double(startFloat)); + return true; + } + case eUnit_Color: { + // http://www.w3.org/TR/smil-animation/#animateColorElement says + // that we should use Euclidean RGB cube distance. However, we + // have to extend that to RGBA. For now, we'll just use the + // Euclidean distance in the (part of the) 4-cube of premultiplied + // colors. + // FIXME (spec): The CSS transitions spec doesn't say whether + // colors are premultiplied, but things work better when they are, + // so use premultiplication. Spec issue is still open per + // http://lists.w3.org/Archives/Public/www-style/2009Jul/0050.html + nscolor startColor = aStartValue.GetColorValue(); + nscolor endColor = aEndValue.GetColorValue(); + + // Get a color component on a 0-1 scale, which is much easier to + // deal with when working with alpha. + #define GET_COMPONENT(component_, color_) \ + (NS_GET_##component_(color_) * (1.0 / 255.0)) + + double startA = GET_COMPONENT(A, startColor); + double startR = GET_COMPONENT(R, startColor) * startA; + double startG = GET_COMPONENT(G, startColor) * startA; + double startB = GET_COMPONENT(B, startColor) * startA; + double endA = GET_COMPONENT(A, endColor); + double endR = GET_COMPONENT(R, endColor) * endA; + double endG = GET_COMPONENT(G, endColor) * endA; + double endB = GET_COMPONENT(B, endColor) * endA; + + #undef GET_COMPONENT + + double diffA = startA - endA; + double diffR = startR - endR; + double diffG = startG - endG; + double diffB = startB - endB; + aDistance = sqrt(diffA * diffA + diffR * diffR + + diffG * diffG + diffB * diffB); + return true; + } + case eUnit_Calc: { + CalcValue v1 = ExtractCalcValue(aStartValue); + CalcValue v2 = ExtractCalcValue(aEndValue); + float difflen = v2.mLength - v1.mLength; + float diffpct = v2.mPercent - v1.mPercent; + aDistance = sqrt(difflen * difflen + diffpct * diffpct); + return true; + } + case eUnit_CSSValuePair: { + const nsCSSValuePair *pair1 = aStartValue.GetCSSValuePairValue(); + const nsCSSValuePair *pair2 = aEndValue.GetCSSValuePairValue(); + nsCSSUnit unit[2]; + unit[0] = GetCommonUnit(aProperty, pair1->mXValue.GetUnit(), + pair2->mXValue.GetUnit()); + unit[1] = GetCommonUnit(aProperty, pair1->mYValue.GetUnit(), + pair2->mYValue.GetUnit()); + if (unit[0] == eCSSUnit_Null || unit[1] == eCSSUnit_Null || + unit[0] == eCSSUnit_URL || unit[0] == eCSSUnit_Enumerated) { + return false; + } + + double squareDistance = 0.0; + static nsCSSValue nsCSSValuePair::* const pairValues[2] = { + &nsCSSValuePair::mXValue, &nsCSSValuePair::mYValue + }; + for (uint32_t i = 0; i < 2; ++i) { + nsCSSValue nsCSSValuePair::*member = pairValues[i]; + double diffsquared; + switch (unit[i]) { + case eCSSUnit_Pixel: { + float diff = (pair1->*member).GetFloatValue() - + (pair2->*member).GetFloatValue(); + diffsquared = diff * diff; + break; + } + case eCSSUnit_Percent: { + float diff = (pair1->*member).GetPercentValue() - + (pair2->*member).GetPercentValue(); + diffsquared = diff * diff; + break; + } + case eCSSUnit_Calc: { + CalcValue v1 = ExtractCalcValue(pair1->*member); + CalcValue v2 = ExtractCalcValue(pair2->*member); + float difflen = v2.mLength - v1.mLength; + float diffpct = v2.mPercent - v1.mPercent; + diffsquared = difflen * difflen + diffpct * diffpct; + break; + } + default: + NS_ABORT_IF_FALSE(false, "unexpected unit"); + return false; + } + squareDistance += diffsquared; + } + + aDistance = sqrt(squareDistance); + return true; + } + case eUnit_CSSValueTriplet: { + const nsCSSValueTriplet *triplet1 = aStartValue.GetCSSValueTripletValue(); + const nsCSSValueTriplet *triplet2 = aEndValue.GetCSSValueTripletValue(); + nsCSSUnit unit[3]; + unit[0] = GetCommonUnit(aProperty, triplet1->mXValue.GetUnit(), + triplet2->mXValue.GetUnit()); + unit[1] = GetCommonUnit(aProperty, triplet1->mYValue.GetUnit(), + triplet2->mYValue.GetUnit()); + unit[2] = GetCommonUnit(aProperty, triplet1->mZValue.GetUnit(), + triplet2->mZValue.GetUnit()); + if (unit[0] == eCSSUnit_Null || unit[1] == eCSSUnit_Null || + unit[2] == eCSSUnit_Null) { + return false; + } + + double squareDistance = 0.0; + static nsCSSValue nsCSSValueTriplet::* const pairValues[3] = { + &nsCSSValueTriplet::mXValue, &nsCSSValueTriplet::mYValue, &nsCSSValueTriplet::mZValue + }; + for (uint32_t i = 0; i < 3; ++i) { + nsCSSValue nsCSSValueTriplet::*member = pairValues[i]; + double diffsquared; + switch (unit[i]) { + case eCSSUnit_Pixel: { + float diff = (triplet1->*member).GetFloatValue() - + (triplet2->*member).GetFloatValue(); + diffsquared = diff * diff; + break; + } + case eCSSUnit_Percent: { + float diff = (triplet1->*member).GetPercentValue() - + (triplet2->*member).GetPercentValue(); + diffsquared = diff * diff; + break; + } + case eCSSUnit_Calc: { + CalcValue v1 = ExtractCalcValue(triplet1->*member); + CalcValue v2 = ExtractCalcValue(triplet2->*member); + float difflen = v2.mLength - v1.mLength; + float diffpct = v2.mPercent - v1.mPercent; + diffsquared = difflen * difflen + diffpct * diffpct; + break; + } + case eCSSUnit_Null: + diffsquared = 0; + break; + default: + NS_ABORT_IF_FALSE(false, "unexpected unit"); + return false; + } + squareDistance += diffsquared; + } + + aDistance = sqrt(squareDistance); + return true; + } + case eUnit_CSSRect: { + const nsCSSRect *rect1 = aStartValue.GetCSSRectValue(); + const nsCSSRect *rect2 = aEndValue.GetCSSRectValue(); + if (rect1->mTop.GetUnit() != rect2->mTop.GetUnit() || + rect1->mRight.GetUnit() != rect2->mRight.GetUnit() || + rect1->mBottom.GetUnit() != rect2->mBottom.GetUnit() || + rect1->mLeft.GetUnit() != rect2->mLeft.GetUnit()) { + // At least until we have calc() + return false; + } + + double squareDistance = 0.0; + for (uint32_t i = 0; i < ArrayLength(nsCSSRect::sides); ++i) { + nsCSSValue nsCSSRect::*member = nsCSSRect::sides[i]; + NS_ABORT_IF_FALSE((rect1->*member).GetUnit() == + (rect2->*member).GetUnit(), + "should have returned above"); + double diff; + switch ((rect1->*member).GetUnit()) { + case eCSSUnit_Pixel: + diff = (rect1->*member).GetFloatValue() - + (rect2->*member).GetFloatValue(); + break; + case eCSSUnit_Auto: + diff = 0; + break; + default: + NS_ABORT_IF_FALSE(false, "unexpected unit"); + return false; + } + squareDistance += diff * diff; + } + + aDistance = sqrt(squareDistance); + return true; + } + case eUnit_Dasharray: { + // NOTE: This produces results on substantially different scales + // for length values and percentage values, which might even be + // mixed in the same property value. This means the result isn't + // particularly useful for paced animation. + + // Call AddWeighted to make us lists of the same length. + Value normValue1, normValue2; + if (!AddWeighted(aProperty, 1.0, aStartValue, 0.0, aEndValue, + normValue1) || + !AddWeighted(aProperty, 0.0, aStartValue, 1.0, aEndValue, + normValue2)) { + return false; + } + + double squareDistance = 0.0; + const nsCSSValueList *list1 = normValue1.GetCSSValueListValue(); + const nsCSSValueList *list2 = normValue2.GetCSSValueListValue(); + + NS_ABORT_IF_FALSE(!list1 == !list2, "lists should be same length"); + while (list1) { + const nsCSSValue &val1 = list1->mValue; + const nsCSSValue &val2 = list2->mValue; + + NS_ABORT_IF_FALSE(val1.GetUnit() == val2.GetUnit(), + "unit match should be assured by AddWeighted"); + double diff; + switch (val1.GetUnit()) { + case eCSSUnit_Percent: + diff = val1.GetPercentValue() - val2.GetPercentValue(); + break; + case eCSSUnit_Number: + diff = val1.GetFloatValue() - val2.GetFloatValue(); + break; + default: + NS_ABORT_IF_FALSE(false, "unexpected unit"); + return false; + } + squareDistance += diff * diff; + + list1 = list1->mNext; + list2 = list2->mNext; + NS_ABORT_IF_FALSE(!list1 == !list2, "lists should be same length"); + } + + aDistance = sqrt(squareDistance); + return true; + } + case eUnit_Shadow: { + // Call AddWeighted to make us lists of the same length. + Value normValue1, normValue2; + if (!AddWeighted(aProperty, 1.0, aStartValue, 0.0, aEndValue, + normValue1) || + !AddWeighted(aProperty, 0.0, aStartValue, 1.0, aEndValue, + normValue2)) { + return false; + } + + const nsCSSValueList *shadow1 = normValue1.GetCSSValueListValue(); + const nsCSSValueList *shadow2 = normValue2.GetCSSValueListValue(); + + double squareDistance = 0.0; + NS_ABORT_IF_FALSE(!shadow1 == !shadow2, "lists should be same length"); + while (shadow1) { + nsCSSValue::Array *array1 = shadow1->mValue.GetArrayValue(); + nsCSSValue::Array *array2 = shadow2->mValue.GetArrayValue(); + for (size_t i = 0; i < 4; ++i) { + NS_ABORT_IF_FALSE(array1->Item(i).GetUnit() == eCSSUnit_Pixel, + "unexpected unit"); + NS_ABORT_IF_FALSE(array2->Item(i).GetUnit() == eCSSUnit_Pixel, + "unexpected unit"); + double diff = array1->Item(i).GetFloatValue() - + array2->Item(i).GetFloatValue(); + squareDistance += diff * diff; + } + + const nsCSSValue &color1 = array1->Item(4); + const nsCSSValue &color2 = array2->Item(4); +#ifdef DEBUG + { + const nsCSSValue &inset1 = array1->Item(5); + const nsCSSValue &inset2 = array2->Item(5); + // There are only two possible states of the inset value: + // (1) GetUnit() == eCSSUnit_Null + // (2) GetUnit() == eCSSUnit_Enumerated && + // GetIntValue() == NS_STYLE_BOX_SHADOW_INSET + NS_ABORT_IF_FALSE(((color1.IsNumericColorUnit() && + color2.IsNumericColorUnit()) || + (color1.GetUnit() == color2.GetUnit())) && + inset1 == inset2, + "AddWeighted should have failed"); + } +#endif + + if (color1.GetUnit() != eCSSUnit_Null) { + nsStyleAnimation::Value color1Value + (color1.GetColorValue(), nsStyleAnimation::Value::ColorConstructor); + nsStyleAnimation::Value color2Value + (color2.GetColorValue(), nsStyleAnimation::Value::ColorConstructor); + double colorDistance; + + #ifdef DEBUG + bool ok = + #endif + nsStyleAnimation::ComputeDistance(eCSSProperty_color, + color1Value, color2Value, + colorDistance); + NS_ABORT_IF_FALSE(ok, "should not fail"); + squareDistance += colorDistance * colorDistance; + } + + shadow1 = shadow1->mNext; + shadow2 = shadow2->mNext; + NS_ABORT_IF_FALSE(!shadow1 == !shadow2, "lists should be same length"); + } + aDistance = sqrt(squareDistance); + return true; + } + case eUnit_Filter: + // FIXME: Support paced animations for filter function interpolation. + case eUnit_Transform: { + return false; + } + case eUnit_BackgroundPosition: { + const nsCSSValueList *position1 = aStartValue.GetCSSValueListValue(); + const nsCSSValueList *position2 = aEndValue.GetCSSValueListValue(); + + double squareDistance = 0.0; + NS_ABORT_IF_FALSE(!position1 == !position2, "lists should be same length"); + + while (position1 && position2) { + NS_ASSERTION(position1->mValue.GetUnit() == eCSSUnit_Array && + position2->mValue.GetUnit() == eCSSUnit_Array, + "Expected two arrays"); + + CalcValue calcVal[4]; + + nsCSSValue::Array* bgArray = position1->mValue.GetArrayValue(); + NS_ABORT_IF_FALSE(bgArray->Count() == 4, "Invalid background-position"); + NS_ASSERTION(bgArray->Item(0).GetUnit() == eCSSUnit_Null && + bgArray->Item(2).GetUnit() == eCSSUnit_Null, + "Invalid list used"); + for (int i = 0; i < 2; ++i) { + NS_ABORT_IF_FALSE(bgArray->Item(i*2+1).GetUnit() != eCSSUnit_Null, + "Invalid background-position"); + calcVal[i] = ExtractCalcValue(bgArray->Item(i*2+1)); + } + + bgArray = position2->mValue.GetArrayValue(); + NS_ABORT_IF_FALSE(bgArray->Count() == 4, "Invalid background-position"); + NS_ASSERTION(bgArray->Item(0).GetUnit() == eCSSUnit_Null && + bgArray->Item(2).GetUnit() == eCSSUnit_Null, + "Invalid list used"); + for (int i = 0; i < 2; ++i) { + NS_ABORT_IF_FALSE(bgArray->Item(i*2+1).GetUnit() != eCSSUnit_Null, + "Invalid background-position"); + calcVal[i+2] = ExtractCalcValue(bgArray->Item(i*2+1)); + } + + for (int i = 0; i < 2; ++i) { + float difflen = calcVal[i+2].mLength - calcVal[i].mLength; + float diffpct = calcVal[i+2].mPercent - calcVal[i].mPercent; + squareDistance += difflen * difflen + diffpct * diffpct; + } + + position1 = position1->mNext; + position2 = position2->mNext; + } + // fail if lists differ in length. + if (position1 || position2) { + return false; + } + + aDistance = sqrt(squareDistance); + return true; + } + case eUnit_CSSValuePairList: { + const nsCSSValuePairList *list1 = aStartValue.GetCSSValuePairListValue(); + const nsCSSValuePairList *list2 = aEndValue.GetCSSValuePairListValue(); + double squareDistance = 0.0; + do { + static nsCSSValue nsCSSValuePairList::* const pairListValues[] = { + &nsCSSValuePairList::mXValue, + &nsCSSValuePairList::mYValue, + }; + for (uint32_t i = 0; i < ArrayLength(pairListValues); ++i) { + const nsCSSValue &v1 = list1->*(pairListValues[i]); + const nsCSSValue &v2 = list2->*(pairListValues[i]); + nsCSSUnit unit = + GetCommonUnit(aProperty, v1.GetUnit(), v2.GetUnit()); + if (unit == eCSSUnit_Null) { + return false; + } + double diffsquared = 0.0; + switch (unit) { + case eCSSUnit_Pixel: { + float diff = v1.GetFloatValue() - v2.GetFloatValue(); + diffsquared = diff * diff; + break; + } + case eCSSUnit_Percent: { + float diff = v1.GetPercentValue() - v2.GetPercentValue(); + diffsquared = diff * diff; + break; + } + case eCSSUnit_Calc: { + CalcValue val1 = ExtractCalcValue(v1); + CalcValue val2 = ExtractCalcValue(v2); + float difflen = val2.mLength - val1.mLength; + float diffpct = val2.mPercent - val1.mPercent; + diffsquared = difflen * difflen + diffpct * diffpct; + break; + } + default: + if (v1 != v2) { + return false; + } + break; + } + squareDistance += diffsquared; + } + list1 = list1->mNext; + list2 = list2->mNext; + } while (list1 && list2); + if (list1 || list2) { + // We can't interpolate lists of different lengths. + return false; + } + aDistance = sqrt(squareDistance); + return true; + } + } + + NS_ABORT_IF_FALSE(false, "Can't compute distance using the given common unit"); + return false; +} + +#define MAX_PACKED_COLOR_COMPONENT 255 + +inline uint8_t ClampColor(double aColor) +{ + if (aColor >= MAX_PACKED_COLOR_COMPONENT) + return MAX_PACKED_COLOR_COMPONENT; + if (aColor <= 0.0) + return 0; + return NSToIntRound(aColor); +} + +// Ensure that a float/double value isn't NaN by returning zero instead +// (NaN doesn't have a sign) as a general restriction for floating point +// values in RestrictValue. +template +MOZ_ALWAYS_INLINE T +EnsureNotNan(T aValue) +{ + return aValue; +} +template<> +MOZ_ALWAYS_INLINE float +EnsureNotNan(float aValue) +{ + // This would benefit from a MOZ_FLOAT_IS_NaN if we had one. + return MOZ_LIKELY(!mozilla::IsNaN(aValue)) ? aValue : 0; +} +template<> +MOZ_ALWAYS_INLINE double +EnsureNotNan(double aValue) +{ + return MOZ_LIKELY(!mozilla::IsNaN(aValue)) ? aValue : 0; +} + +template +T +RestrictValue(uint32_t aRestrictions, T aValue) +{ + T result = EnsureNotNan(aValue); + switch (aRestrictions) { + case 0: + break; + case CSS_PROPERTY_VALUE_NONNEGATIVE: + if (result < 0) { + result = 0; + } + break; + case CSS_PROPERTY_VALUE_AT_LEAST_ONE: + if (result < 1) { + result = 1; + } + break; + default: + NS_ABORT_IF_FALSE(false, "bad value restriction"); + break; + } + return result; +} + +template +T +RestrictValue(nsCSSProperty aProperty, T aValue) +{ + return RestrictValue(nsCSSProps::ValueRestrictions(aProperty), aValue); +} + +static inline void +AddCSSValuePixel(double aCoeff1, const nsCSSValue &aValue1, + double aCoeff2, const nsCSSValue &aValue2, + nsCSSValue &aResult, uint32_t aValueRestrictions = 0) +{ + NS_ABORT_IF_FALSE(aValue1.GetUnit() == eCSSUnit_Pixel, "unexpected unit"); + NS_ABORT_IF_FALSE(aValue2.GetUnit() == eCSSUnit_Pixel, "unexpected unit"); + aResult.SetFloatValue(RestrictValue(aValueRestrictions, + aCoeff1 * aValue1.GetFloatValue() + + aCoeff2 * aValue2.GetFloatValue()), + eCSSUnit_Pixel); +} + +static inline void +AddCSSValueNumber(double aCoeff1, const nsCSSValue &aValue1, + double aCoeff2, const nsCSSValue &aValue2, + nsCSSValue &aResult, uint32_t aValueRestrictions = 0) +{ + NS_ABORT_IF_FALSE(aValue1.GetUnit() == eCSSUnit_Number, "unexpected unit"); + NS_ABORT_IF_FALSE(aValue2.GetUnit() == eCSSUnit_Number, "unexpected unit"); + aResult.SetFloatValue(RestrictValue(aValueRestrictions, + aCoeff1 * aValue1.GetFloatValue() + + aCoeff2 * aValue2.GetFloatValue()), + eCSSUnit_Number); +} + +static inline void +AddCSSValuePercent(double aCoeff1, const nsCSSValue &aValue1, + double aCoeff2, const nsCSSValue &aValue2, + nsCSSValue &aResult, uint32_t aValueRestrictions = 0) +{ + NS_ABORT_IF_FALSE(aValue1.GetUnit() == eCSSUnit_Percent, "unexpected unit"); + NS_ABORT_IF_FALSE(aValue2.GetUnit() == eCSSUnit_Percent, "unexpected unit"); + aResult.SetPercentValue(RestrictValue(aValueRestrictions, + aCoeff1 * aValue1.GetPercentValue() + + aCoeff2 * aValue2.GetPercentValue())); +} + +// Add two canonical-form calc values (eUnit_Calc) to make another +// canonical-form calc value. +static void +AddCSSValueCanonicalCalc(double aCoeff1, const nsCSSValue &aValue1, + double aCoeff2, const nsCSSValue &aValue2, + nsCSSValue &aResult) +{ + CalcValue v1 = ExtractCalcValue(aValue1); + CalcValue v2 = ExtractCalcValue(aValue2); + CalcValue result; + result.mLength = aCoeff1 * v1.mLength + aCoeff2 * v2.mLength; + result.mPercent = aCoeff1 * v1.mPercent + aCoeff2 * v2.mPercent; + result.mHasPercent = v1.mHasPercent || v2.mHasPercent; + MOZ_ASSERT(result.mHasPercent || result.mPercent == 0.0f, + "can't have a nonzero percentage part without having percentages"); + SetCalcValue(result, aResult); +} + +static void +AddCSSValueAngle(double aCoeff1, const nsCSSValue &aValue1, + double aCoeff2, const nsCSSValue &aValue2, + nsCSSValue &aResult) +{ + aResult.SetFloatValue(aCoeff1 * aValue1.GetAngleValueInRadians() + + aCoeff2 * aValue2.GetAngleValueInRadians(), + eCSSUnit_Radian); +} + +static bool +AddCSSValuePixelPercentCalc(const uint32_t aValueRestrictions, + const nsCSSUnit aCommonUnit, + double aCoeff1, const nsCSSValue &aValue1, + double aCoeff2, const nsCSSValue &aValue2, + nsCSSValue &aResult) +{ + switch (aCommonUnit) { + case eCSSUnit_Pixel: + AddCSSValuePixel(aCoeff1, aValue1, + aCoeff2, aValue2, + aResult, aValueRestrictions); + break; + case eCSSUnit_Percent: + AddCSSValuePercent(aCoeff1, aValue1, + aCoeff2, aValue2, + aResult, aValueRestrictions); + break; + case eCSSUnit_Calc: + AddCSSValueCanonicalCalc(aCoeff1, aValue1, + aCoeff2, aValue2, + aResult); + break; + default: + return false; + } + + 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, + nsCSSValueList **&aResultTail) +{ + // X, Y, Radius, Spread, Color, Inset + NS_ABORT_IF_FALSE(aValue1.GetUnit() == eCSSUnit_Array, + "wrong unit"); + NS_ABORT_IF_FALSE(aValue2.GetUnit() == eCSSUnit_Array, + "wrong unit"); + nsCSSValue::Array *array1 = aValue1.GetArrayValue(); + nsCSSValue::Array *array2 = aValue2.GetArrayValue(); + nsRefPtr resultArray = nsCSSValue::Array::Create(6); + + for (size_t i = 0; i < 4; ++i) { + AddCSSValuePixel(aCoeff1, array1->Item(i), aCoeff2, array2->Item(i), + resultArray->Item(i), + // blur radius must be nonnegative + (i == 2) ? CSS_PROPERTY_VALUE_NONNEGATIVE : 0); + } + + const nsCSSValue& color1 = array1->Item(4); + const nsCSSValue& color2 = array2->Item(4); + const nsCSSValue& inset1 = array1->Item(5); + const nsCSSValue& inset2 = array2->Item(5); + if (color1.GetUnit() != color2.GetUnit() || + inset1.GetUnit() != inset2.GetUnit()) { + // We don't know how to animate between color and no-color, or + // between inset and not-inset. + return false; + } + + if (color1.GetUnit() != eCSSUnit_Null) { + nsStyleAnimation::Value color1Value + (color1.GetColorValue(), nsStyleAnimation::Value::ColorConstructor); + nsStyleAnimation::Value color2Value + (color2.GetColorValue(), nsStyleAnimation::Value::ColorConstructor); + nsStyleAnimation::Value resultColorValue; + #ifdef DEBUG + bool ok = + #endif + nsStyleAnimation::AddWeighted(eCSSProperty_color, aCoeff1, color1Value, + aCoeff2, color2Value, resultColorValue); + NS_ABORT_IF_FALSE(ok, "should not fail"); + resultArray->Item(4).SetColorValue(resultColorValue.GetColorValue()); + } + + NS_ABORT_IF_FALSE(inset1 == inset2, "should match"); + resultArray->Item(5) = inset1; + + nsCSSValueList *resultItem = new nsCSSValueList; + if (!resultItem) { + return false; + } + resultItem->mValue.SetArrayValue(resultArray, eCSSUnit_Array); + *aResultTail = resultItem; + aResultTail = &resultItem->mNext; + return true; +} + +static void +AddTransformTranslate(double aCoeff1, const nsCSSValue &aValue1, + double aCoeff2, const nsCSSValue &aValue2, + nsCSSValue &aResult) +{ + NS_ABORT_IF_FALSE(aValue1.GetUnit() == eCSSUnit_Percent || + aValue1.GetUnit() == eCSSUnit_Pixel || + aValue1.IsCalcUnit(), + "unexpected unit"); + NS_ABORT_IF_FALSE(aValue2.GetUnit() == eCSSUnit_Percent || + aValue2.GetUnit() == eCSSUnit_Pixel || + aValue2.IsCalcUnit(), + "unexpected unit"); + + if (aValue1.GetUnit() != aValue2.GetUnit() || aValue1.IsCalcUnit()) { + // different units; create a calc() expression + AddCSSValueCanonicalCalc(aCoeff1, aValue1, aCoeff2, aValue2, aResult); + } else if (aValue1.GetUnit() == eCSSUnit_Percent) { + // both percent + AddCSSValuePercent(aCoeff1, aValue1, aCoeff2, aValue2, aResult); + } else { + // both pixels + AddCSSValuePixel(aCoeff1, aValue1, aCoeff2, aValue2, aResult); + } +} + +static void +AddTransformScale(double aCoeff1, const nsCSSValue &aValue1, + double aCoeff2, const nsCSSValue &aValue2, + nsCSSValue &aResult) +{ + // Handle scale, and the two matrix components where identity is 1, by + // subtracting 1, multiplying by the coefficients, and then adding 1 + // back. This gets the right AddWeighted behavior and gets us the + // interpolation-against-identity behavior for free. + NS_ABORT_IF_FALSE(aValue1.GetUnit() == eCSSUnit_Number, "unexpected unit"); + NS_ABORT_IF_FALSE(aValue2.GetUnit() == eCSSUnit_Number, "unexpected unit"); + + float v1 = aValue1.GetFloatValue() - 1.0f, + v2 = aValue2.GetFloatValue() - 1.0f; + float result = v1 * aCoeff1 + v2 * aCoeff2; + aResult.SetFloatValue(result + 1.0f, eCSSUnit_Number); +} + +/* static */ already_AddRefed +nsStyleAnimation::AppendTransformFunction(nsCSSKeyword aTransformFunction, + nsCSSValueList**& aListTail) +{ + nsRefPtr arr = AppendFunction(aTransformFunction); + nsCSSValueList *item = new nsCSSValueList; + item->mValue.SetArrayValue(arr, eCSSUnit_Function); + + *aListTail = item; + aListTail = &item->mNext; + + return arr.forget(); +} + +/* + * The relevant section of the transitions specification: + * http://dev.w3.org/csswg/css3-transitions/#animation-of-property-types- + * defers all of the details to the 2-D and 3-D transforms specifications. + * For the 2-D transforms specification (all that's relevant for us, right + * now), the relevant section is: + * http://dev.w3.org/csswg/css3-2d-transforms/#animation + * This, in turn, refers to the unmatrix program in Graphics Gems, + * available from http://tog.acm.org/resources/GraphicsGems/ , and in + * particular as the file GraphicsGems/gemsii/unmatrix.c + * in http://tog.acm.org/resources/GraphicsGems/AllGems.tar.gz + * + * The unmatrix reference is for general 3-D transform matrices (any of the + * 16 components can have any value). + * + * For CSS 2-D transforms, we have a 2-D matrix with the bottom row constant: + * + * [ A C E ] + * [ B D F ] + * [ 0 0 1 ] + * + * For that case, I believe the algorithm in unmatrix reduces to: + * + * (1) If A * D - B * C == 0, the matrix is singular. Fail. + * + * (2) Set translation components (Tx and Ty) to the translation parts of + * the matrix (E and F) and then ignore them for the rest of the time. + * (For us, E and F each actually consist of three constants: a + * length, a multiplier for the width, and a multiplier for the + * height. This actually requires its own decomposition, but I'll + * keep that separate.) + * + * (3) Let the X scale (Sx) be sqrt(A^2 + B^2). Then divide both A and B + * by it. + * + * (4) Let the XY shear (K) be A * C + B * D. From C, subtract A times + * the XY shear. From D, subtract B times the XY shear. + * + * (5) Let the Y scale (Sy) be sqrt(C^2 + D^2). Divide C, D, and the XY + * shear (K) by it. + * + * (6) At this point, A * D - B * C is either 1 or -1. If it is -1, + * negate the XY shear (K), the X scale (Sx), and A, B, C, and D. + * (Alternatively, we could negate the XY shear (K) and the Y scale + * (Sy).) + * + * (7) Let the rotation be R = atan2(B, A). + * + * Then the resulting decomposed transformation is: + * + * translate(Tx, Ty) rotate(R) skewX(atan(K)) scale(Sx, Sy) + * + * An interesting result of this is that all of the simple transform + * functions (i.e., all functions other than matrix()), in isolation, + * decompose back to themselves except for: + * 'skewY(φ)', which is 'matrix(1, tan(φ), 0, 1, 0, 0)', which decomposes + * to 'rotate(φ) skewX(φ) scale(sec(φ), cos(φ))' since (ignoring the + * alternate sign possibilities that would get fixed in step 6): + * In step 3, the X scale factor is sqrt(1+tan²(φ)) = sqrt(sec²(φ)) = sec(φ). + * Thus, after step 3, A = 1/sec(φ) = cos(φ) and B = tan(φ) / sec(φ) = sin(φ). + * In step 4, the XY shear is sin(φ). + * Thus, after step 4, C = -cos(φ)sin(φ) and D = 1 - sin²(φ) = cos²(φ). + * Thus, in step 5, the Y scale is sqrt(cos²(φ)(sin²(φ) + cos²(φ)) = cos(φ). + * Thus, after step 5, C = -sin(φ), D = cos(φ), and the XY shear is tan(φ). + * Thus, in step 6, A * D - B * C = cos²(φ) + sin²(φ) = 1. + * In step 7, the rotation is thus φ. + * + * skew(θ, φ), which is matrix(1, tan(φ), tan(θ), 1, 0, 0), which decomposes + * to 'rotate(φ) skewX(θ + φ) scale(sec(φ), cos(φ))' since (ignoring + * the alternate sign possibilities that would get fixed in step 6): + * In step 3, the X scale factor is sqrt(1+tan²(φ)) = sqrt(sec²(φ)) = sec(φ). + * Thus, after step 3, A = 1/sec(φ) = cos(φ) and B = tan(φ) / sec(φ) = sin(φ). + * In step 4, the XY shear is cos(φ)tan(θ) + sin(φ). + * Thus, after step 4, + * C = tan(θ) - cos(φ)(cos(φ)tan(θ) + sin(φ)) = tan(θ)sin²(φ) - cos(φ)sin(φ) + * D = 1 - sin(φ)(cos(φ)tan(θ) + sin(φ)) = cos²(φ) - sin(φ)cos(φ)tan(θ) + * Thus, in step 5, the Y scale is sqrt(C² + D²) = + * sqrt(tan²(θ)(sin⁴(φ) + sin²(φ)cos²(φ)) - + * 2 tan(θ)(sin³(φ)cos(φ) + sin(φ)cos³(φ)) + + * (sin²(φ)cos²(φ) + cos⁴(φ))) = + * sqrt(tan²(θ)sin²(φ) - 2 tan(θ)sin(φ)cos(φ) + cos²(φ)) = + * cos(φ) - tan(θ)sin(φ) (taking the negative of the obvious solution so + * we avoid flipping in step 6). + * After step 5, C = -sin(φ) and D = cos(φ), and the XY shear is + * (cos(φ)tan(θ) + sin(φ)) / (cos(φ) - tan(θ)sin(φ)) = + * (dividing both numerator and denominator by cos(φ)) + * (tan(θ) + tan(φ)) / (1 - tan(θ)tan(φ)) = tan(θ + φ). + * (See http://en.wikipedia.org/wiki/List_of_trigonometric_identities .) + * Thus, in step 6, A * D - B * C = cos²(φ) + sin²(φ) = 1. + * In step 7, the rotation is thus φ. + * + * To check this result, we can multiply things back together: + * + * [ cos(φ) -sin(φ) ] [ 1 tan(θ + φ) ] [ sec(φ) 0 ] + * [ sin(φ) cos(φ) ] [ 0 1 ] [ 0 cos(φ) ] + * + * [ cos(φ) cos(φ)tan(θ + φ) - sin(φ) ] [ sec(φ) 0 ] + * [ sin(φ) sin(φ)tan(θ + φ) + cos(φ) ] [ 0 cos(φ) ] + * + * but since tan(θ + φ) = (tan(θ) + tan(φ)) / (1 - tan(θ)tan(φ)), + * cos(φ)tan(θ + φ) - sin(φ) + * = cos(φ)(tan(θ) + tan(φ)) - sin(φ) + sin(φ)tan(θ)tan(φ) + * = cos(φ)tan(θ) + sin(φ) - sin(φ) + sin(φ)tan(θ)tan(φ) + * = cos(φ)tan(θ) + sin(φ)tan(θ)tan(φ) + * = tan(θ) (cos(φ) + sin(φ)tan(φ)) + * = tan(θ) sec(φ) (cos²(φ) + sin²(φ)) + * = tan(θ) sec(φ) + * and + * sin(φ)tan(θ + φ) + cos(φ) + * = sin(φ)(tan(θ) + tan(φ)) + cos(φ) - cos(φ)tan(θ)tan(φ) + * = tan(θ) (sin(φ) - sin(φ)) + sin(φ)tan(φ) + cos(φ) + * = sec(φ) (sin²(φ) + cos²(φ)) + * = sec(φ) + * so the above is: + * [ cos(φ) tan(θ) sec(φ) ] [ sec(φ) 0 ] + * [ sin(φ) sec(φ) ] [ 0 cos(φ) ] + * + * [ 1 tan(θ) ] + * [ tan(φ) 1 ] + */ + +/* + * Decompose2DMatrix implements the above decomposition algorithm. + */ + +#define XYSHEAR 0 +#define XZSHEAR 1 +#define YZSHEAR 2 + +static bool +Decompose2DMatrix(const gfxMatrix &aMatrix, gfxPoint3D &aScale, + float aShear[3], gfxQuaternion &aRotate, + gfxPoint3D &aTranslate) +{ + float A = aMatrix.xx, + B = aMatrix.yx, + C = aMatrix.xy, + D = aMatrix.yy; + if (A * D == B * C) { + // singular matrix + return false; + } + + float scaleX = sqrt(A * A + B * B); + A /= scaleX; + B /= scaleX; + + float XYshear = A * C + B * D; + C -= A * XYshear; + D -= B * XYshear; + + float scaleY = sqrt(C * C + D * D); + C /= scaleY; + D /= scaleY; + XYshear /= scaleY; + + // A*D - B*C should now be 1 or -1 + NS_ASSERTION(0.99 < Abs(A*D - B*C) && Abs(A*D - B*C) < 1.01, + "determinant should now be 1 or -1"); + if (A * D < B * C) { + A = -A; + B = -B; + C = -C; + D = -D; + XYshear = -XYshear; + scaleX = -scaleX; + } + + float rotate = atan2f(B, A); + aRotate = gfxQuaternion(0, 0, sin(rotate/2), cos(rotate/2)); + aShear[XYSHEAR] = XYshear; + aScale.x = scaleX; + aScale.y = scaleY; + aTranslate.x = aMatrix.x0; + aTranslate.y = aMatrix.y0; + return true; +} + +/** + * Implementation of the unmatrix algorithm, specified by: + * + * http://dev.w3.org/csswg/css3-2d-transforms/#unmatrix + * + * This, in turn, refers to the unmatrix program in Graphics Gems, + * available from http://tog.acm.org/resources/GraphicsGems/ , and in + * particular as the file GraphicsGems/gemsii/unmatrix.c + * in http://tog.acm.org/resources/GraphicsGems/AllGems.tar.gz + */ +static bool +Decompose3DMatrix(const gfx3DMatrix &aMatrix, gfxPoint3D &aScale, + float aShear[3], gfxQuaternion &aRotate, + gfxPoint3D &aTranslate, gfxPointH3D &aPerspective) +{ + gfx3DMatrix local = aMatrix; + + if (local[3][3] == 0) { + return false; + } + /* Normalize the matrix */ + local.Normalize(); + + /** + * perspective is used to solve for perspective, but it also provides + * an easy way to test for singularity of the upper 3x3 component. + */ + gfx3DMatrix perspective = local; + gfxPointH3D empty(0, 0, 0, 1); + perspective.SetTransposedVector(3, empty); + + if (perspective.Determinant() == 0.0) { + return false; + } + + /* First, isolate perspective. */ + if (local[0][3] != 0 || local[1][3] != 0 || + local[2][3] != 0) { + /* aPerspective is the right hand side of the equation. */ + aPerspective = local.TransposedVector(3); + + /** + * Solve the equation by inverting perspective and multiplying + * aPerspective by the inverse. + */ + perspective.Invert(); + aPerspective = perspective.TransposeTransform4D(aPerspective); + + /* Clear the perspective partition */ + local.SetTransposedVector(3, empty); + } else { + aPerspective = gfxPointH3D(0, 0, 0, 1); + } + + /* Next take care of translation */ + for (int i = 0; i < 3; i++) { + aTranslate[i] = local[3][i]; + local[3][i] = 0; + } + + /* Now get scale and shear. */ + + /* Compute X scale factor and normalize first row. */ + aScale.x = local[0].Length(); + local[0] /= aScale.x; + + /* Compute XY shear factor and make 2nd local orthogonal to 1st. */ + aShear[XYSHEAR] = local[0].DotProduct(local[1]); + local[1] -= local[0] * aShear[XYSHEAR]; + + /* Now, compute Y scale and normalize 2nd local. */ + aScale.y = local[1].Length(); + local[1] /= aScale.y; + aShear[XYSHEAR] /= aScale.y; + + /* Compute XZ and YZ shears, make 3rd local orthogonal */ + aShear[XZSHEAR] = local[0].DotProduct(local[2]); + local[2] -= local[0] * aShear[XZSHEAR]; + aShear[YZSHEAR] = local[1].DotProduct(local[2]); + local[2] -= local[1] * aShear[YZSHEAR]; + + /* Next, get Z scale and normalize 3rd local. */ + aScale.z = local[2].Length(); + local[2] /= aScale.z; + + aShear[XZSHEAR] /= aScale.z; + aShear[YZSHEAR] /= aScale.z; + + /** + * At this point, the matrix (in locals) is orthonormal. + * Check for a coordinate system flip. If the determinant + * is -1, then negate the matrix and the scaling factors. + */ + if (local[0].DotProduct(local[1].CrossProduct(local[2])) < 0) { + aScale *= -1; + for (int i = 0; i < 3; i++) { + local[i] *= -1; + } + } + + /* Now, get the rotations out */ + aRotate = gfxQuaternion(local); + + return true; +} + +template +T InterpolateNumerically(const T& aOne, const T& aTwo, double aCoeff) +{ + return aOne + (aTwo - aOne) * aCoeff; +} + + +/* static */ gfx3DMatrix +nsStyleAnimation::InterpolateTransformMatrix(const gfx3DMatrix &aMatrix1, + const gfx3DMatrix &aMatrix2, + double aProgress) +{ + // Decompose both matrices + + // TODO: What do we do if one of these returns false (singular matrix) + + gfxPoint3D scale1(1, 1, 1), translate1; + gfxPointH3D perspective1(0, 0, 0, 1); + gfxQuaternion rotate1; + float shear1[3] = { 0.0f, 0.0f, 0.0f}; + + gfxPoint3D scale2(1, 1, 1), translate2; + gfxPointH3D perspective2(0, 0, 0, 1); + gfxQuaternion rotate2; + float shear2[3] = { 0.0f, 0.0f, 0.0f}; + + gfxMatrix matrix2d1, matrix2d2; + if (aMatrix1.Is2D(&matrix2d1) && aMatrix2.Is2D(&matrix2d2)) { + Decompose2DMatrix(matrix2d1, scale1, shear1, rotate1, translate1); + Decompose2DMatrix(matrix2d2, scale2, shear2, rotate2, translate2); + } else { + Decompose3DMatrix(aMatrix1, scale1, shear1, + rotate1, translate1, perspective1); + Decompose3DMatrix(aMatrix2, scale2, shear2, + rotate2, translate2, perspective2); + } + + // Interpolate each of the pieces + gfx3DMatrix result; + + gfxPointH3D perspective = + InterpolateNumerically(perspective1, perspective2, aProgress); + result.SetTransposedVector(3, perspective); + + gfxPoint3D translate = + InterpolateNumerically(translate1, translate2, aProgress); + result.Translate(translate); + + gfxQuaternion q3 = rotate1.Slerp(rotate2, aProgress); + gfx3DMatrix rotate = q3.ToMatrix(); + if (!rotate.IsIdentity()) { + result = rotate * result; + } + + // TODO: Would it be better to interpolate these as angles? How do we convert back to angles? + float yzshear = + InterpolateNumerically(shear1[YZSHEAR], shear2[YZSHEAR], aProgress); + if (yzshear != 0.0) { + result.SkewYZ(yzshear); + } + + float xzshear = + InterpolateNumerically(shear1[XZSHEAR], shear2[XZSHEAR], aProgress); + if (xzshear != 0.0) { + result.SkewXZ(xzshear); + } + + float xyshear = + InterpolateNumerically(shear1[XYSHEAR], shear2[XYSHEAR], aProgress); + if (xyshear != 0.0) { + result.SkewXY(xyshear); + } + + gfxPoint3D scale = + InterpolateNumerically(scale1, scale2, aProgress); + if (scale != gfxPoint3D(1.0, 1.0, 1.0)) { + result.Scale(scale.x, scale.y, scale.z); + } + + return result; +} + +static nsCSSValueList* +AddDifferentTransformLists(double aCoeff1, const nsCSSValueList* aList1, + double aCoeff2, const nsCSSValueList* aList2) +{ + nsAutoPtr result; + nsCSSValueList **resultTail = getter_Transfers(result); + + nsRefPtr arr; + arr = nsStyleAnimation::AppendTransformFunction(eCSSKeyword_interpolatematrix, resultTail); + + // FIXME: We should change the other transform code to also only + // take a single progress value, as having values that don't + // sum to 1 doesn't make sense for these. + if (aList1 == aList2) { + arr->Item(1).Reset(); + } else { + aList1->CloneInto(arr->Item(1).SetListValue()); + } + + aList2->CloneInto(arr->Item(2).SetListValue()); + arr->Item(3).SetPercentValue(aCoeff2); + + return result.forget(); +} + +static bool +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 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 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 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) +{ + nsAutoPtr result; + nsCSSValueList **resultTail = getter_Transfers(result); + + do { + nsRefPtr a1 = ToPrimitive(aList1->mValue.GetArrayValue()), + a2 = ToPrimitive(aList2->mValue.GetArrayValue()); + NS_ABORT_IF_FALSE(TransformFunctionsMatch(nsStyleTransformMatrix::TransformFunctionOf(a1), + nsStyleTransformMatrix::TransformFunctionOf(a2)), + "transform function mismatch"); + NS_ABORT_IF_FALSE(!*resultTail, + "resultTail isn't pointing to the tail (may leak)"); + + nsCSSKeyword tfunc = nsStyleTransformMatrix::TransformFunctionOf(a1); + nsRefPtr arr; + if (tfunc != eCSSKeyword_matrix && + tfunc != eCSSKeyword_matrix3d && + tfunc != eCSSKeyword_interpolatematrix && + tfunc != eCSSKeyword_rotate3d && + tfunc != eCSSKeyword_perspective) { + arr = nsStyleAnimation::AppendTransformFunction(tfunc, resultTail); + } + + switch (tfunc) { + case eCSSKeyword_translate3d: { + NS_ABORT_IF_FALSE(a1->Count() == 4, "unexpected count"); + NS_ABORT_IF_FALSE(a2->Count() == 4, "unexpected count"); + AddTransformTranslate(aCoeff1, a1->Item(1), aCoeff2, a2->Item(1), + arr->Item(1)); + AddTransformTranslate(aCoeff1, a1->Item(2), aCoeff2, a2->Item(2), + arr->Item(2)); + AddTransformTranslate(aCoeff1, a1->Item(3), aCoeff2, a2->Item(3), + arr->Item(3)); + break; + } + case eCSSKeyword_scale3d: { + NS_ABORT_IF_FALSE(a1->Count() == 4, "unexpected count"); + NS_ABORT_IF_FALSE(a2->Count() == 4, "unexpected count"); + + AddTransformScale(aCoeff1, a1->Item(1), aCoeff2, a2->Item(1), + arr->Item(1)); + AddTransformScale(aCoeff1, a1->Item(2), aCoeff2, a2->Item(2), + arr->Item(2)); + AddTransformScale(aCoeff1, a1->Item(3), aCoeff2, a2->Item(3), + arr->Item(3)); + + break; + } + // It would probably be nicer to animate skew in tangent space + // rather than angle space. However, it's easy to specify + // skews with infinite tangents, and behavior changes pretty + // drastically when crossing such skews (since the direction of + // animation flips), so interop is probably more important here. + case eCSSKeyword_skew: { + NS_ABORT_IF_FALSE(a1->Count() == 2 || a1->Count() == 3, + "unexpected count"); + NS_ABORT_IF_FALSE(a2->Count() == 2 || a2->Count() == 3, + "unexpected count"); + + nsCSSValue zero(0.0f, eCSSUnit_Radian); + // Add Y component of skew. + AddCSSValueAngle(aCoeff1, + a1->Count() == 3 ? a1->Item(2) : zero, + aCoeff2, + a2->Count() == 3 ? a2->Item(2) : zero, + arr->Item(2)); + + // Add X component of skew (which can be merged with case below + // in non-DEBUG). + AddCSSValueAngle(aCoeff1, a1->Item(1), aCoeff2, a2->Item(1), + arr->Item(1)); + + break; + } + case eCSSKeyword_skewx: + case eCSSKeyword_skewy: + case eCSSKeyword_rotate: + case eCSSKeyword_rotatex: + case eCSSKeyword_rotatey: + case eCSSKeyword_rotatez: { + NS_ABORT_IF_FALSE(a1->Count() == 2, "unexpected count"); + NS_ABORT_IF_FALSE(a2->Count() == 2, "unexpected count"); + + AddCSSValueAngle(aCoeff1, a1->Item(1), aCoeff2, a2->Item(1), + arr->Item(1)); + + break; + } + case eCSSKeyword_matrix: + case eCSSKeyword_matrix3d: + case eCSSKeyword_interpolatematrix: + case eCSSKeyword_rotate3d: + case eCSSKeyword_perspective: { + // FIXME: If the matrix contains only numbers then we could decompose + // here. + + // Construct temporary lists with only this item in them. + nsCSSValueList tempList1, tempList2; + tempList1.mValue = aList1->mValue; + tempList2.mValue = aList2->mValue; + + if (aList1 == aList2) { + *resultTail = + AddDifferentTransformLists(aCoeff1, &tempList1, aCoeff2, &tempList1); + } else { + *resultTail = + AddDifferentTransformLists(aCoeff1, &tempList1, aCoeff2, &tempList2); + } + + // Now advance resultTail to point to the new tail slot. + while (*resultTail) { + resultTail = &(*resultTail)->mNext; + } + + break; + } + default: + NS_ABORT_IF_FALSE(false, "unknown transform function"); + } + + aList1 = aList1->mNext; + aList2 = aList2->mNext; + } while (aList1); + NS_ABORT_IF_FALSE(!aList2, "list length mismatch"); + NS_ABORT_IF_FALSE(!*resultTail, + "resultTail isn't pointing to the tail"); + + return result.forget(); +} + +bool +nsStyleAnimation::AddWeighted(nsCSSProperty aProperty, + double aCoeff1, const Value& aValue1, + double aCoeff2, const Value& aValue2, + Value& aResultValue) +{ + Unit commonUnit = + GetCommonUnit(aProperty, aValue1.GetUnit(), aValue2.GetUnit()); + // Maybe need a followup method to convert the inputs into the common + // unit-type, if they don't already match it. (Or would it make sense to do + // that in GetCommonUnit? in which case maybe ConvertToCommonUnit would be + // better.) + + switch (commonUnit) { + case eUnit_Null: + case eUnit_Auto: + case eUnit_None: + case eUnit_Normal: + case eUnit_UnparsedString: + return false; + + case eUnit_Enumerated: + switch (aProperty) { + case eCSSProperty_font_stretch: { + // Animate just like eUnit_Integer. + int32_t result = floor(aCoeff1 * double(aValue1.GetIntValue()) + + aCoeff2 * double(aValue2.GetIntValue())); + if (result < NS_STYLE_FONT_STRETCH_ULTRA_CONDENSED) { + result = NS_STYLE_FONT_STRETCH_ULTRA_CONDENSED; + } else if (result > NS_STYLE_FONT_STRETCH_ULTRA_EXPANDED) { + result = NS_STYLE_FONT_STRETCH_ULTRA_EXPANDED; + } + aResultValue.SetIntValue(result, eUnit_Enumerated); + return true; + } + default: + return false; + } + case eUnit_Visibility: { + int32_t enum1 = aValue1.GetIntValue(); + int32_t enum2 = aValue2.GetIntValue(); + if (enum1 == enum2) { + aResultValue.SetIntValue(enum1, eUnit_Visibility); + return true; + } + if ((enum1 == NS_STYLE_VISIBILITY_VISIBLE) == + (enum2 == NS_STYLE_VISIBILITY_VISIBLE)) { + return false; + } + int32_t val1 = enum1 == NS_STYLE_VISIBILITY_VISIBLE; + int32_t val2 = enum2 == NS_STYLE_VISIBILITY_VISIBLE; + double interp = aCoeff1 * val1 + aCoeff2 * val2; + int32_t result = interp > 0.0 ? NS_STYLE_VISIBILITY_VISIBLE + : (val1 ? enum2 : enum1); + aResultValue.SetIntValue(result, eUnit_Visibility); + return true; + } + case eUnit_Integer: { + // http://dev.w3.org/csswg/css3-transitions/#animation-of-property-types- + // says we should use floor + int32_t result = floor(aCoeff1 * double(aValue1.GetIntValue()) + + aCoeff2 * double(aValue2.GetIntValue())); + if (aProperty == eCSSProperty_font_weight) { + if (result < 100) { + result = 100; + } else if (result > 900) { + result = 900; + } + result -= result % 100; + } else { + result = RestrictValue(aProperty, result); + } + aResultValue.SetIntValue(result, eUnit_Integer); + return true; + } + case eUnit_Coord: { + aResultValue.SetCoordValue(RestrictValue(aProperty, NSToCoordRound( + aCoeff1 * aValue1.GetCoordValue() + + aCoeff2 * aValue2.GetCoordValue()))); + return true; + } + case eUnit_Percent: { + aResultValue.SetPercentValue(RestrictValue(aProperty, + aCoeff1 * aValue1.GetPercentValue() + + aCoeff2 * aValue2.GetPercentValue())); + return true; + } + case eUnit_Float: { + // Special case for flex-grow and flex-shrink: animations are + // disallowed between 0 and other values. + if ((aProperty == eCSSProperty_flex_grow || + aProperty == eCSSProperty_flex_shrink) && + (aValue1.GetFloatValue() == 0.0f || + aValue2.GetFloatValue() == 0.0f) && + aValue1.GetFloatValue() != aValue2.GetFloatValue()) { + return false; + } + + aResultValue.SetFloatValue(RestrictValue(aProperty, + aCoeff1 * aValue1.GetFloatValue() + + aCoeff2 * aValue2.GetFloatValue())); + return true; + } + case eUnit_Color: { + nscolor color1 = aValue1.GetColorValue(); + nscolor color2 = aValue2.GetColorValue(); + // FIXME (spec): The CSS transitions spec doesn't say whether + // colors are premultiplied, but things work better when they are, + // so use premultiplication. Spec issue is still open per + // http://lists.w3.org/Archives/Public/www-style/2009Jul/0050.html + + // To save some math, scale the alpha down to a 0-1 scale, but + // leave the color components on a 0-255 scale. + double A1 = NS_GET_A(color1) * (1.0 / 255.0); + double R1 = NS_GET_R(color1) * A1; + double G1 = NS_GET_G(color1) * A1; + double B1 = NS_GET_B(color1) * A1; + double A2 = NS_GET_A(color2) * (1.0 / 255.0); + double R2 = NS_GET_R(color2) * A2; + double G2 = NS_GET_G(color2) * A2; + double B2 = NS_GET_B(color2) * A2; + double Aresf = (A1 * aCoeff1 + A2 * aCoeff2); + nscolor resultColor; + if (Aresf <= 0.0) { + resultColor = NS_RGBA(0, 0, 0, 0); + } else { + if (Aresf > 1.0) { + Aresf = 1.0; + } + double factor = 1.0 / Aresf; + uint8_t Ares = NSToIntRound(Aresf * 255.0); + uint8_t Rres = ClampColor((R1 * aCoeff1 + R2 * aCoeff2) * factor); + uint8_t Gres = ClampColor((G1 * aCoeff1 + G2 * aCoeff2) * factor); + uint8_t Bres = ClampColor((B1 * aCoeff1 + B2 * aCoeff2) * factor); + resultColor = NS_RGBA(Rres, Gres, Bres, Ares); + } + aResultValue.SetColorValue(resultColor); + return true; + } + case eUnit_Calc: { + CalcValue v1 = ExtractCalcValue(aValue1); + CalcValue v2 = ExtractCalcValue(aValue2); + double len = aCoeff1 * v1.mLength + aCoeff2 * v2.mLength; + double pct = aCoeff1 * v1.mPercent + aCoeff2 * v2.mPercent; + bool hasPct = (aCoeff1 != 0.0 && v1.mHasPercent) || + (aCoeff2 != 0.0 && v2.mHasPercent); + nsCSSValue *val = new nsCSSValue(); + nsCSSValue::Array *arr = nsCSSValue::Array::Create(1); + val->SetArrayValue(arr, eCSSUnit_Calc); + if (hasPct) { + nsCSSValue::Array *arr2 = nsCSSValue::Array::Create(2); + arr2->Item(0).SetFloatValue(len, eCSSUnit_Pixel); + arr2->Item(1).SetPercentValue(pct); + arr->Item(0).SetArrayValue(arr2, eCSSUnit_Calc_Plus); + } else { + arr->Item(0).SetFloatValue(len, eCSSUnit_Pixel); + } + aResultValue.SetAndAdoptCSSValueValue(val, eUnit_Calc); + return true; + } + case eUnit_CSSValuePair: { + const nsCSSValuePair *pair1 = aValue1.GetCSSValuePairValue(); + const nsCSSValuePair *pair2 = aValue2.GetCSSValuePairValue(); + nsCSSUnit unit[2]; + unit[0] = GetCommonUnit(aProperty, pair1->mXValue.GetUnit(), + pair2->mXValue.GetUnit()); + unit[1] = GetCommonUnit(aProperty, pair1->mYValue.GetUnit(), + pair2->mYValue.GetUnit()); + if (unit[0] == eCSSUnit_Null || unit[1] == eCSSUnit_Null || + unit[0] == eCSSUnit_URL || unit[0] == eCSSUnit_Enumerated) { + return false; + } + + nsAutoPtr result(new nsCSSValuePair); + static nsCSSValue nsCSSValuePair::* const pairValues[2] = { + &nsCSSValuePair::mXValue, &nsCSSValuePair::mYValue + }; + uint32_t restrictions = nsCSSProps::ValueRestrictions(aProperty); + for (uint32_t i = 0; i < 2; ++i) { + nsCSSValue nsCSSValuePair::*member = pairValues[i]; + if (!AddCSSValuePixelPercentCalc(restrictions, unit[i], + aCoeff1, pair1->*member, + aCoeff2, pair2->*member, + result->*member) ) { + NS_ABORT_IF_FALSE(false, "unexpected unit"); + return false; + } + } + + aResultValue.SetAndAdoptCSSValuePairValue(result.forget(), + eUnit_CSSValuePair); + return true; + } + case eUnit_CSSValueTriplet: { + nsCSSValueTriplet triplet1(*aValue1.GetCSSValueTripletValue()); + nsCSSValueTriplet triplet2(*aValue2.GetCSSValueTripletValue()); + + nsCSSUnit unit[3]; + unit[0] = GetCommonUnit(aProperty, triplet1.mXValue.GetUnit(), + triplet2.mXValue.GetUnit()); + unit[1] = GetCommonUnit(aProperty, triplet1.mYValue.GetUnit(), + triplet2.mYValue.GetUnit()); + unit[2] = GetCommonUnit(aProperty, triplet1.mZValue.GetUnit(), + triplet2.mZValue.GetUnit()); + if (unit[0] == eCSSUnit_Null || unit[1] == eCSSUnit_Null || + unit[2] == eCSSUnit_Null) { + return false; + } + + nsAutoPtr result(new nsCSSValueTriplet); + static nsCSSValue nsCSSValueTriplet::* const tripletValues[3] = { + &nsCSSValueTriplet::mXValue, &nsCSSValueTriplet::mYValue, &nsCSSValueTriplet::mZValue + }; + uint32_t restrictions = nsCSSProps::ValueRestrictions(aProperty); + for (uint32_t i = 0; i < 3; ++i) { + nsCSSValue nsCSSValueTriplet::*member = tripletValues[i]; + if (!AddCSSValuePixelPercentCalc(restrictions, unit[i], + aCoeff1, &triplet1->*member, + aCoeff2, &triplet2->*member, + result->*member) ) { + NS_ABORT_IF_FALSE(false, "unexpected unit"); + return false; + } + } + + aResultValue.SetAndAdoptCSSValueTripletValue(result.forget(), + eUnit_CSSValueTriplet); + return true; + } + case eUnit_CSSRect: { + NS_ABORT_IF_FALSE(nsCSSProps::ValueRestrictions(aProperty) == 0, + "must add code for handling value restrictions"); + const nsCSSRect *rect1 = aValue1.GetCSSRectValue(); + const nsCSSRect *rect2 = aValue2.GetCSSRectValue(); + if (rect1->mTop.GetUnit() != rect2->mTop.GetUnit() || + rect1->mRight.GetUnit() != rect2->mRight.GetUnit() || + rect1->mBottom.GetUnit() != rect2->mBottom.GetUnit() || + rect1->mLeft.GetUnit() != rect2->mLeft.GetUnit()) { + // At least until we have calc() + return false; + } + + nsAutoPtr result(new nsCSSRect); + for (uint32_t i = 0; i < ArrayLength(nsCSSRect::sides); ++i) { + nsCSSValue nsCSSRect::*member = nsCSSRect::sides[i]; + NS_ABORT_IF_FALSE((rect1->*member).GetUnit() == + (rect2->*member).GetUnit(), + "should have returned above"); + switch ((rect1->*member).GetUnit()) { + case eCSSUnit_Pixel: + AddCSSValuePixel(aCoeff1, rect1->*member, aCoeff2, rect2->*member, + result->*member); + break; + case eCSSUnit_Auto: + if (float(aCoeff1 + aCoeff2) != 1.0f) { + // Interpolating between two auto values makes sense; + // adding in other ratios does not. + return false; + } + (result->*member).SetAutoValue(); + break; + default: + NS_ABORT_IF_FALSE(false, "unexpected unit"); + return false; + } + } + + aResultValue.SetAndAdoptCSSRectValue(result.forget(), eUnit_CSSRect); + return true; + } + case eUnit_Dasharray: { + const nsCSSValueList *list1 = aValue1.GetCSSValueListValue(); + const nsCSSValueList *list2 = aValue2.GetCSSValueListValue(); + + uint32_t len1 = 0, len2 = 0; + for (const nsCSSValueList *v = list1; v; v = v->mNext) { + ++len1; + } + for (const nsCSSValueList *v = list2; v; v = v->mNext) { + ++len2; + } + NS_ABORT_IF_FALSE(len1 > 0 && len2 > 0, "unexpected length"); + if (list1->mValue.GetUnit() == eCSSUnit_None || + list2->mValue.GetUnit() == eCSSUnit_None) { + // One of our values is "none". Can't do addition with that. + NS_ABORT_IF_FALSE( + (list1->mValue.GetUnit() != eCSSUnit_None || len1 == 1) && + (list2->mValue.GetUnit() != eCSSUnit_None || len2 == 1), + "multi-value valuelist with 'none' as first element"); + return false; + } + + nsAutoPtr result; + nsCSSValueList **resultTail = getter_Transfers(result); + for (uint32_t i = 0, i_end = EuclidLCM(len1, len2); i != i_end; ++i) { + const nsCSSValue &v1 = list1->mValue; + const nsCSSValue &v2 = list2->mValue; + NS_ABORT_IF_FALSE(v1.GetUnit() == eCSSUnit_Number || + v1.GetUnit() == eCSSUnit_Percent, "unexpected"); + NS_ABORT_IF_FALSE(v2.GetUnit() == eCSSUnit_Number || + v2.GetUnit() == eCSSUnit_Percent, "unexpected"); + if (v1.GetUnit() != v2.GetUnit()) { + // Can't animate between lengths and percentages (until calc()). + return false; + } + + nsCSSValueList *item = new nsCSSValueList; + if (!item) { + return false; + } + *resultTail = item; + resultTail = &item->mNext; + + if (v1.GetUnit() == eCSSUnit_Number) { + AddCSSValueNumber(aCoeff1, v1, aCoeff2, v2, item->mValue, + CSS_PROPERTY_VALUE_NONNEGATIVE); + } else { + AddCSSValuePercent(aCoeff1, v1, aCoeff2, v2, item->mValue, + CSS_PROPERTY_VALUE_NONNEGATIVE); + } + + list1 = list1->mNext; + if (!list1) { + list1 = aValue1.GetCSSValueListValue(); + } + list2 = list2->mNext; + if (!list2) { + list2 = aValue2.GetCSSValueListValue(); + } + } + + aResultValue.SetAndAdoptCSSValueListValue(result.forget(), + eUnit_Dasharray); + return true; + } + case eUnit_Shadow: { + // This is implemented according to: + // http://dev.w3.org/csswg/css3-transitions/#animation-of-property-types- + // and the third item in the summary of: + // http://lists.w3.org/Archives/Public/www-style/2009Jul/0050.html + const nsCSSValueList *shadow1 = aValue1.GetCSSValueListValue(); + const nsCSSValueList *shadow2 = aValue2.GetCSSValueListValue(); + nsAutoPtr result; + nsCSSValueList **resultTail = getter_Transfers(result); + while (shadow1 && shadow2) { + if (!AddShadowItems(aCoeff1, shadow1->mValue, + aCoeff2, shadow2->mValue, + resultTail)) { + return false; + } + shadow1 = shadow1->mNext; + shadow2 = shadow2->mNext; + } + if (shadow1 || shadow2) { + const nsCSSValueList *longShadow; + double longCoeff; + if (shadow1) { + longShadow = shadow1; + longCoeff = aCoeff1; + } else { + longShadow = shadow2; + longCoeff = aCoeff2; + } + + while (longShadow) { + // Passing coefficients that add to less than 1 produces the + // desired result of interpolating "0 0 0 transparent" with + // the current shadow. + if (!AddShadowItems(longCoeff, longShadow->mValue, + 0.0, longShadow->mValue, + resultTail)) { + return false; + } + + longShadow = longShadow->mNext; + } + } + aResultValue.SetAndAdoptCSSValueListValue(result.forget(), eUnit_Shadow); + return true; + } + + case eUnit_Filter: { + const nsCSSValueList *list1 = aValue1.GetCSSValueListValue(); + const nsCSSValueList *list2 = aValue2.GetCSSValueListValue(); + + nsAutoPtr 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.GetCSSValueSharedListValue()->mHead; + const nsCSSValueList* list2 = aValue2.GetCSSValueSharedListValue()->mHead; + + MOZ_ASSERT(list1); + MOZ_ASSERT(list2); + + // We want to avoid the matrix decomposition when we can, since + // avoiding it can produce better results both for compound + // transforms and for skew and skewY (see below). We can do this + // in two cases: + // (1) if one of the transforms is 'none' + // (2) if the lists have the same length and the transform + // functions match + nsAutoPtr result; + if (list1->mValue.GetUnit() == eCSSUnit_None) { + if (list2->mValue.GetUnit() == eCSSUnit_None) { + result = new nsCSSValueList; + if (result) { + result->mValue.SetNoneValue(); + } + } else { + result = AddTransformLists(0, list2, aCoeff2, list2); + } + } else { + if (list2->mValue.GetUnit() == eCSSUnit_None) { + result = AddTransformLists(0, list1, aCoeff1, list1); + } else { + bool match = true; + + { + const nsCSSValueList *item1 = list1, *item2 = list2; + do { + nsCSSKeyword func1 = nsStyleTransformMatrix::TransformFunctionOf( + item1->mValue.GetArrayValue()); + nsCSSKeyword func2 = nsStyleTransformMatrix::TransformFunctionOf( + item2->mValue.GetArrayValue()); + + if (!TransformFunctionsMatch(func1, func2)) { + break; + } + + item1 = item1->mNext; + item2 = item2->mNext; + } while (item1 && item2); + if (item1 || item2) { + // Either |break| above or length mismatch. + match = false; + } + } + + if (match) { + result = AddTransformLists(aCoeff1, list1, aCoeff2, list2); + } else { + result = AddDifferentTransformLists(aCoeff1, list1, aCoeff2, list2); + } + } + } + + aResultValue.SetTransformValue(new nsCSSValueSharedList(result.forget())); + return true; + } + case eUnit_BackgroundPosition: { + const nsCSSValueList *position1 = aValue1.GetCSSValueListValue(); + const nsCSSValueList *position2 = aValue2.GetCSSValueListValue(); + nsAutoPtr result; + nsCSSValueList **resultTail = getter_Transfers(result); + while (position1 && position2) { + nsCSSValueList *item = new nsCSSValueList; + if (!item) { + return false; + } + *resultTail = item; + resultTail = &item->mNext; + + nsCSSValue::Array* bgPos1 = position1->mValue.GetArrayValue(); + nsCSSValue::Array* bgPos2 = position2->mValue.GetArrayValue(); + nsCSSValue::Array* bgPosRes = nsCSSValue::Array::Create(4); + item->mValue.SetArrayValue(bgPosRes, eCSSUnit_Array); + + uint32_t restrictions = nsCSSProps::ValueRestrictions(aProperty); + + /* Only iterate over elements 1 and 3. The background position is + * 'uncomputed' to only those elements. + */ + for (int i = 1; i < 4; i+=2) { + const nsCSSValue& v1 = bgPos1->Item(i); + const nsCSSValue& v2 = bgPos2->Item(i); + nsCSSValue& vr = bgPosRes->Item(i); + + nsCSSUnit unit = GetCommonUnit(aProperty, v1.GetUnit(), v2.GetUnit()); + + if (!AddCSSValuePixelPercentCalc(restrictions, unit, aCoeff1, v1, + aCoeff2, v2, vr) ) { + if (v1 != v2) { + return false; + } + vr = v1; + } + } + + position1 = position1->mNext; + position2 = position2->mNext; + } + + // Check for different lengths + if (position1 || position2) { + return false; + } + + aResultValue.SetAndAdoptCSSValueListValue(result.forget(), + eUnit_BackgroundPosition); + return true; + } + case eUnit_CSSValuePairList: { + const nsCSSValuePairList *list1 = aValue1.GetCSSValuePairListValue(); + const nsCSSValuePairList *list2 = aValue2.GetCSSValuePairListValue(); + nsAutoPtr result; + nsCSSValuePairList **resultTail = getter_Transfers(result); + do { + nsCSSValuePairList *item = new nsCSSValuePairList; + if (!item) { + return false; + } + *resultTail = item; + resultTail = &item->mNext; + + static nsCSSValue nsCSSValuePairList::* const pairListValues[] = { + &nsCSSValuePairList::mXValue, + &nsCSSValuePairList::mYValue, + }; + uint32_t restrictions = nsCSSProps::ValueRestrictions(aProperty); + for (uint32_t i = 0; i < ArrayLength(pairListValues); ++i) { + const nsCSSValue &v1 = list1->*(pairListValues[i]); + const nsCSSValue &v2 = list2->*(pairListValues[i]); + nsCSSValue &vr = item->*(pairListValues[i]); + nsCSSUnit unit = + GetCommonUnit(aProperty, v1.GetUnit(), v2.GetUnit()); + if (unit == eCSSUnit_Null) { + return false; + } + if (!AddCSSValuePixelPercentCalc(restrictions, unit, aCoeff1, v1, + aCoeff2, v2, vr) ) { + if (v1 != v2) { + return false; + } + vr = v1; + } + } + list1 = list1->mNext; + list2 = list2->mNext; + } while (list1 && list2); + if (list1 || list2) { + // We can't interpolate lists of different lengths. + return false; + } + + aResultValue.SetAndAdoptCSSValuePairListValue(result.forget()); + return true; + } + } + + NS_ABORT_IF_FALSE(false, "Can't interpolate using the given common unit"); + return false; +} + +already_AddRefed +BuildStyleRule(nsCSSProperty aProperty, + dom::Element* aTargetElement, + const nsAString& aSpecifiedValue, + bool aUseSVGMode) +{ + // Set up an empty CSS Declaration + nsAutoPtr declaration(new css::Declaration()); + declaration->InitializeEmpty(); + + bool changed; // ignored, but needed as outparam for ParseProperty + nsIDocument* doc = aTargetElement->OwnerDoc(); + nsCOMPtr baseURI = aTargetElement->GetBaseURI(); + nsCSSParser parser(doc->CSSLoader()); + + nsCSSProperty propertyToCheck = nsCSSProps::IsShorthand(aProperty) ? + nsCSSProps::SubpropertyEntryFor(aProperty)[0] : aProperty; + + // Get a parser, parse the property, and check for CSS parsing errors. + // If any of these steps fails, we bail out and delete the declaration. + if (NS_FAILED(parser.ParseProperty(aProperty, aSpecifiedValue, + doc->GetDocumentURI(), baseURI, + aTargetElement->NodePrincipal(), + declaration, &changed, false, + aUseSVGMode)) || + // check whether property parsed without CSS parsing errors + !declaration->HasNonImportantValueFor(propertyToCheck)) { + NS_WARNING("failure in BuildStyleRule"); + return nullptr; + } + + nsRefPtr rule = new css::StyleRule(nullptr, declaration.forget()); + return rule.forget(); +} + +inline +already_AddRefed +LookupStyleContext(dom::Element* aElement) +{ + nsIDocument* doc = aElement->GetCurrentDoc(); + nsIPresShell* shell = doc->GetShell(); + if (!shell) { + return nullptr; + } + return nsComputedDOMStyle::GetStyleContextForElement(aElement, nullptr, shell); +} + +bool +nsStyleAnimation::ComputeValue(nsCSSProperty aProperty, + dom::Element* aTargetElement, + const nsAString& aSpecifiedValue, + bool aUseSVGMode, + Value& aComputedValue, + bool* aIsContextSensitive) +{ + NS_ABORT_IF_FALSE(aTargetElement, "null target element"); + NS_ABORT_IF_FALSE(aTargetElement->GetCurrentDoc(), + "we should only be able to actively animate nodes that " + "are in a document"); + + nsCSSProperty propToParse = + nsCSSProps::PropHasFlags(aProperty, CSS_PROPERTY_REPORT_OTHER_NAME) + ? nsCSSProps::OtherNameFor(aProperty) : aProperty; + + // Parse specified value into a temporary css::StyleRule + nsRefPtr styleRule = + BuildStyleRule(propToParse, aTargetElement, aSpecifiedValue, aUseSVGMode); + if (!styleRule) { + return false; + } + + if (nsCSSProps::IsShorthand(aProperty) || + nsCSSProps::kAnimTypeTable[aProperty] == eStyleAnimType_None) { + // Just capture the specified value + aComputedValue.SetUnparsedStringValue(nsString(aSpecifiedValue)); + if (aIsContextSensitive) { + // Since we're just returning the string as-is, aComputedValue isn't going + // to change depending on the context + *aIsContextSensitive = false; + } + return true; + } + + // Look up style context for our target element + nsRefPtr styleContext = LookupStyleContext(aTargetElement); + if (!styleContext) { + return false; + } + nsStyleSet* styleSet = styleContext->PresContext()->StyleSet(); + + nsRefPtr tmpStyleContext; + if (aIsContextSensitive) { + nsCOMArray ruleArray; + ruleArray.AppendObject(styleSet->InitialStyleRule()); + ruleArray.AppendObject(styleRule); + styleRule->RuleMatched(); + tmpStyleContext = + styleSet->ResolveStyleByAddingRules(styleContext, ruleArray); + if (!tmpStyleContext) { + return false; + } + + // Force walk of rule tree + nsStyleStructID sid = nsCSSProps::kSIDTable[aProperty]; + tmpStyleContext->StyleData(sid); + + // If the rule node will have cached style data if the value is not + // context-sensitive. So if there's nothing cached, it's not context + // sensitive. + *aIsContextSensitive = + !tmpStyleContext->RuleNode()->NodeHasCachedData(sid); + } + + // If we're not concerned whether the property is context sensitive then just + // add the rule to a new temporary style context alongside the target + // element's style context. + // Also, if we previously discovered that this property IS context-sensitive + // then we need to throw the temporary style context out since the property's + // value may have been biased by the 'initial' values supplied. + if (!aIsContextSensitive || *aIsContextSensitive) { + nsCOMArray ruleArray; + ruleArray.AppendObject(styleRule); + styleRule->RuleMatched(); + tmpStyleContext = + styleSet->ResolveStyleByAddingRules(styleContext, ruleArray); + if (!tmpStyleContext) { + return false; + } + } + + // Extract computed value of our property from the temporary style rule + return ExtractComputedValue(aProperty, tmpStyleContext, aComputedValue); +} + +bool +nsStyleAnimation::UncomputeValue(nsCSSProperty aProperty, + const Value& aComputedValue, + nsCSSValue& aSpecifiedValue) +{ + switch (aComputedValue.GetUnit()) { + case eUnit_Normal: + aSpecifiedValue.SetNormalValue(); + break; + case eUnit_Auto: + aSpecifiedValue.SetAutoValue(); + break; + case eUnit_None: + aSpecifiedValue.SetNoneValue(); + break; + case eUnit_Enumerated: + case eUnit_Visibility: + aSpecifiedValue. + SetIntValue(aComputedValue.GetIntValue(), eCSSUnit_Enumerated); + break; + case eUnit_Integer: + aSpecifiedValue. + SetIntValue(aComputedValue.GetIntValue(), eCSSUnit_Integer); + break; + case eUnit_Coord: + nscoordToCSSValue(aComputedValue.GetCoordValue(), aSpecifiedValue); + break; + case eUnit_Percent: + aSpecifiedValue.SetPercentValue(aComputedValue.GetPercentValue()); + break; + case eUnit_Float: + aSpecifiedValue. + SetFloatValue(aComputedValue.GetFloatValue(), eCSSUnit_Number); + break; + case eUnit_Color: + // colors can be alone, or part of a paint server + aSpecifiedValue.SetColorValue(aComputedValue.GetColorValue()); + break; + case eUnit_Calc: { + nsCSSValue *val = aComputedValue.GetCSSValueValue(); + NS_ABORT_IF_FALSE(val->GetUnit() == eCSSUnit_Calc, "unexpected unit"); + aSpecifiedValue = *val; + break; + } + case eUnit_CSSValuePair: { + // Rule node processing expects pair values to be collapsed to a + // single value if both halves would be equal, for most but not + // all properties. At present, all animatable properties that + // use pairs do expect collapsing. + const nsCSSValuePair* pair = aComputedValue.GetCSSValuePairValue(); + if (pair->mXValue == pair->mYValue) { + aSpecifiedValue = pair->mXValue; + } else { + aSpecifiedValue.SetPairValue(pair); + } + } break; + case eUnit_CSSValueTriplet: { + // Rule node processing expects triplet values to be collapsed to a + // single value if both halves would be equal, for most but not + // all properties. At present, all animatable properties that + // use pairs do expect collapsing. + const nsCSSValueTriplet* triplet = aComputedValue.GetCSSValueTripletValue(); + if (triplet->mXValue == triplet->mYValue && triplet->mYValue == triplet->mZValue) { + aSpecifiedValue = triplet->mXValue; + } else { + aSpecifiedValue.SetTripletValue(triplet); + } + } break; + case eUnit_CSSRect: { + nsCSSRect& rect = aSpecifiedValue.SetRectValue(); + rect = *aComputedValue.GetCSSRectValue(); + } break; + case eUnit_Dasharray: + case eUnit_Shadow: + case eUnit_Filter: + case eUnit_BackgroundPosition: + aSpecifiedValue. + SetDependentListValue(aComputedValue.GetCSSValueListValue()); + break; + case eUnit_Transform: + aSpecifiedValue. + SetSharedListValue(aComputedValue.GetCSSValueSharedListValue()); + break; + case eUnit_CSSValuePairList: + aSpecifiedValue. + SetDependentPairListValue(aComputedValue.GetCSSValuePairListValue()); + break; + default: + return false; + } + return true; +} + +bool +nsStyleAnimation::UncomputeValue(nsCSSProperty aProperty, + const Value& aComputedValue, + nsAString& aSpecifiedValue) +{ + aSpecifiedValue.Truncate(); // Clear outparam, if it's not already empty + + if (aComputedValue.GetUnit() == eUnit_UnparsedString) { + aComputedValue.GetStringValue(aSpecifiedValue); + return true; + } + nsCSSValue val; + if (!nsStyleAnimation::UncomputeValue(aProperty, aComputedValue, val)) { + return false; + } + + val.AppendToString(aProperty, aSpecifiedValue, nsCSSValue::eNormalized); + return true; +} + +inline const void* +StyleDataAtOffset(const void* aStyleStruct, ptrdiff_t aOffset) +{ + return reinterpret_cast(aStyleStruct) + aOffset; +} + +inline void* +StyleDataAtOffset(void* aStyleStruct, ptrdiff_t aOffset) +{ + return reinterpret_cast(aStyleStruct) + aOffset; +} + +static void +ExtractBorderColor(nsStyleContext* aStyleContext, const void* aStyleBorder, + mozilla::css::Side aSide, nsStyleAnimation::Value& aComputedValue) +{ + nscolor color; + bool foreground; + static_cast(aStyleBorder)-> + GetBorderColor(aSide, color, foreground); + if (foreground) { + // FIXME: should add test for this + color = aStyleContext->StyleColor()->mColor; + } + aComputedValue.SetColorValue(color); +} + +static bool +StyleCoordToValue(const nsStyleCoord& aCoord, nsStyleAnimation::Value& aValue) +{ + switch (aCoord.GetUnit()) { + case eStyleUnit_Normal: + aValue.SetNormalValue(); + break; + case eStyleUnit_Auto: + aValue.SetAutoValue(); + break; + case eStyleUnit_None: + aValue.SetNoneValue(); + break; + case eStyleUnit_Percent: + aValue.SetPercentValue(aCoord.GetPercentValue()); + break; + case eStyleUnit_Factor: + aValue.SetFloatValue(aCoord.GetFactorValue()); + break; + case eStyleUnit_Coord: + aValue.SetCoordValue(aCoord.GetCoordValue()); + break; + case eStyleUnit_Enumerated: + aValue.SetIntValue(aCoord.GetIntValue(), + nsStyleAnimation::eUnit_Enumerated); + break; + case eStyleUnit_Integer: + aValue.SetIntValue(aCoord.GetIntValue(), + nsStyleAnimation::eUnit_Integer); + break; + case eStyleUnit_Calc: { + nsAutoPtr val(new nsCSSValue); + SetCalcValue(aCoord.GetCalcValue(), *val); + aValue.SetAndAdoptCSSValueValue(val.forget(), + nsStyleAnimation::eUnit_Calc); + break; + } + default: + return false; + } + return true; +} + +static bool +StyleCoordToCSSValue(const nsStyleCoord& aCoord, nsCSSValue& aCSSValue) +{ + switch (aCoord.GetUnit()) { + 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; + } + return true; +} + +/* + * Assign |aOutput = aInput|, except with any non-pixel lengths + * replaced with the equivalent in pixels, and any non-canonical calc() + * expressions replaced with canonical ones. + */ +static void +SubstitutePixelValues(nsStyleContext* aStyleContext, + const nsCSSValue& aInput, nsCSSValue& aOutput) +{ + if (aInput.IsCalcUnit()) { + bool canStoreInRuleTree = true; + nsRuleNode::ComputedCalc c = + nsRuleNode::SpecifiedCalcToComputedCalc(aInput, aStyleContext, + aStyleContext->PresContext(), + canStoreInRuleTree); + nsStyleCoord::Calc c2; + c2.mLength = c.mLength; + c2.mPercent = c.mPercent; + c2.mHasPercent = true; // doesn't matter for transform translate + SetCalcValue(&c2, aOutput); + } else if (aInput.UnitHasArrayValue()) { + const nsCSSValue::Array *inputArray = aInput.GetArrayValue(); + nsRefPtr outputArray = + nsCSSValue::Array::Create(inputArray->Count()); + for (size_t i = 0, i_end = inputArray->Count(); i < i_end; ++i) { + SubstitutePixelValues(aStyleContext, + inputArray->Item(i), outputArray->Item(i)); + } + aOutput.SetArrayValue(outputArray, aInput.GetUnit()); + } else if (aInput.IsLengthUnit() && + aInput.GetUnit() != eCSSUnit_Pixel) { + bool canStoreInRuleTree = true; + nscoord len = nsRuleNode::CalcLength(aInput, aStyleContext, + aStyleContext->PresContext(), + canStoreInRuleTree); + aOutput.SetFloatValue(nsPresContext::AppUnitsToFloatCSSPixels(len), + eCSSUnit_Pixel); + } else { + aOutput = aInput; + } +} + +bool +nsStyleAnimation::ExtractComputedValue(nsCSSProperty aProperty, + nsStyleContext* aStyleContext, + Value& aComputedValue) +{ + NS_ABORT_IF_FALSE(0 <= aProperty && + aProperty < eCSSProperty_COUNT_no_shorthands, + "bad property"); + const void* styleStruct = + aStyleContext->StyleData(nsCSSProps::kSIDTable[aProperty]); + ptrdiff_t ssOffset = nsCSSProps::kStyleStructOffsetTable[aProperty]; + nsStyleAnimType animType = nsCSSProps::kAnimTypeTable[aProperty]; + NS_ABORT_IF_FALSE(0 <= ssOffset || animType == eStyleAnimType_Custom, + "must be dealing with animatable property"); + switch (animType) { + case eStyleAnimType_Custom: + switch (aProperty) { + // For border-width, ignore the border-image business (which + // only exists until we update our implementation to the current + // spec) and use GetComputedBorder + + #define BORDER_WIDTH_CASE(prop_, side_) \ + case prop_: \ + aComputedValue.SetCoordValue( \ + static_cast(styleStruct)-> \ + GetComputedBorder().side_); \ + break; + BORDER_WIDTH_CASE(eCSSProperty_border_bottom_width, bottom) + BORDER_WIDTH_CASE(eCSSProperty_border_left_width_value, left) + BORDER_WIDTH_CASE(eCSSProperty_border_right_width_value, right) + BORDER_WIDTH_CASE(eCSSProperty_border_top_width, top) + #undef BORDER_WIDTH_CASE + + case eCSSProperty__moz_column_rule_width: + aComputedValue.SetCoordValue( + static_cast(styleStruct)-> + GetComputedColumnRuleWidth()); + break; + + case eCSSProperty_border_bottom_color: + ExtractBorderColor(aStyleContext, styleStruct, NS_SIDE_BOTTOM, + aComputedValue); + break; + case eCSSProperty_border_left_color_value: + ExtractBorderColor(aStyleContext, styleStruct, NS_SIDE_LEFT, + aComputedValue); + break; + case eCSSProperty_border_right_color_value: + ExtractBorderColor(aStyleContext, styleStruct, NS_SIDE_RIGHT, + aComputedValue); + break; + case eCSSProperty_border_top_color: + ExtractBorderColor(aStyleContext, styleStruct, NS_SIDE_TOP, + aComputedValue); + break; + + case eCSSProperty_outline_color: { + const nsStyleOutline *styleOutline = + static_cast(styleStruct); + nscolor color; + if (!styleOutline->GetOutlineColor(color)) + color = aStyleContext->StyleColor()->mColor; + aComputedValue.SetColorValue(color); + break; + } + + case eCSSProperty__moz_column_rule_color: { + const nsStyleColumn *styleColumn = + static_cast(styleStruct); + nscolor color; + if (styleColumn->mColumnRuleColorIsForeground) { + color = aStyleContext->StyleColor()->mColor; + } else { + color = styleColumn->mColumnRuleColor; + } + aComputedValue.SetColorValue(color); + break; + } + + case eCSSProperty__moz_column_count: { + const nsStyleColumn *styleColumn = + static_cast(styleStruct); + if (styleColumn->mColumnCount == NS_STYLE_COLUMN_COUNT_AUTO) { + aComputedValue.SetAutoValue(); + } else { + aComputedValue.SetIntValue(styleColumn->mColumnCount, + eUnit_Integer); + } + break; + } + + case eCSSProperty_order: { + const nsStylePosition *stylePosition = + static_cast(styleStruct); + aComputedValue.SetIntValue(stylePosition->mOrder, + eUnit_Integer); + break; + } + + case eCSSProperty_text_decoration_color: { + const nsStyleTextReset *styleTextReset = + static_cast(styleStruct); + nscolor color; + bool isForeground; + styleTextReset->GetDecorationColor(color, isForeground); + if (isForeground) { + color = aStyleContext->StyleColor()->mColor; + } + aComputedValue.SetColorValue(color); + break; + } + + case eCSSProperty_text_decoration_style: { + uint8_t decorationStyle = + static_cast(styleStruct)-> + GetDecorationStyle(); + aComputedValue.SetIntValue(decorationStyle, eUnit_Enumerated); + break; + } + + case eCSSProperty_border_spacing: { + const nsStyleTableBorder *styleTableBorder = + static_cast(styleStruct); + nsAutoPtr pair(new nsCSSValuePair); + if (!pair) { + return false; + } + nscoordToCSSValue(styleTableBorder->mBorderSpacingX, pair->mXValue); + nscoordToCSSValue(styleTableBorder->mBorderSpacingY, pair->mYValue); + aComputedValue.SetAndAdoptCSSValuePairValue(pair.forget(), + eUnit_CSSValuePair); + break; + } + + case eCSSProperty_transform_origin: { + const nsStyleDisplay *styleDisplay = + static_cast(styleStruct); + nsAutoPtr triplet(new nsCSSValueTriplet); + if (!triplet || + !StyleCoordToCSSValue(styleDisplay->mTransformOrigin[0], + triplet->mXValue) || + !StyleCoordToCSSValue(styleDisplay->mTransformOrigin[1], + triplet->mYValue) || + !StyleCoordToCSSValue(styleDisplay->mTransformOrigin[2], + triplet->mZValue)) { + return false; + } + aComputedValue.SetAndAdoptCSSValueTripletValue(triplet.forget(), + eUnit_CSSValueTriplet); + break; + } + + case eCSSProperty_perspective_origin: { + const nsStyleDisplay *styleDisplay = + static_cast(styleStruct); + nsAutoPtr pair(new nsCSSValuePair); + if (!pair || + !StyleCoordToCSSValue(styleDisplay->mPerspectiveOrigin[0], + pair->mXValue) || + !StyleCoordToCSSValue(styleDisplay->mPerspectiveOrigin[1], + pair->mYValue)) { + return false; + } + aComputedValue.SetAndAdoptCSSValuePairValue(pair.forget(), + eUnit_CSSValuePair); + break; + } + + case eCSSProperty_stroke_dasharray: { + const nsStyleSVG *svg = static_cast(styleStruct); + NS_ABORT_IF_FALSE((svg->mStrokeDasharray != nullptr) == + (svg->mStrokeDasharrayLength != 0), + "pointer/length mismatch"); + nsAutoPtr result; + if (svg->mStrokeDasharray) { + NS_ABORT_IF_FALSE(svg->mStrokeDasharrayLength > 0, + "non-null list should have positive length"); + nsCSSValueList **resultTail = getter_Transfers(result); + for (uint32_t i = 0, i_end = svg->mStrokeDasharrayLength; + i != i_end; ++i) { + nsCSSValueList *item = new nsCSSValueList; + if (!item) { + return false; + } + *resultTail = item; + resultTail = &item->mNext; + + const nsStyleCoord &coord = svg->mStrokeDasharray[i]; + nsCSSValue &value = item->mValue; + switch (coord.GetUnit()) { + case eStyleUnit_Coord: + // Number means the same thing as length; we want to + // animate them the same way. Normalize both to number + // since it has more accuracy (float vs nscoord). + value.SetFloatValue(nsPresContext:: + AppUnitsToFloatCSSPixels(coord.GetCoordValue()), + eCSSUnit_Number); + break; + case eStyleUnit_Factor: + value.SetFloatValue(coord.GetFactorValue(), + eCSSUnit_Number); + break; + case eStyleUnit_Percent: + value.SetPercentValue(coord.GetPercentValue()); + break; + default: + NS_ABORT_IF_FALSE(false, "unexpected unit"); + return false; + } + } + } else { + result = new nsCSSValueList; + if (!result) { + return false; + } + result->mValue.SetNoneValue(); + } + aComputedValue.SetAndAdoptCSSValueListValue(result.forget(), + eUnit_Dasharray); + break; + } + + case eCSSProperty_font_stretch: { + int16_t stretch = + static_cast(styleStruct)->mFont.stretch; + static_assert(NS_STYLE_FONT_STRETCH_ULTRA_CONDENSED == -4 && + NS_STYLE_FONT_STRETCH_ULTRA_EXPANDED == 4, + "font stretch constants not as expected"); + if (stretch < NS_STYLE_FONT_STRETCH_ULTRA_CONDENSED || + stretch > NS_STYLE_FONT_STRETCH_ULTRA_EXPANDED) { + return false; + } + aComputedValue.SetIntValue(stretch, eUnit_Enumerated); + return true; + } + + case eCSSProperty_font_weight: { + uint16_t weight = + static_cast(styleStruct)->mFont.weight; + if (weight % 100 != 0) { + return false; + } + aComputedValue.SetIntValue(weight, eUnit_Integer); + return true; + } + + case eCSSProperty_image_region: { + const nsStyleList *list = + static_cast(styleStruct); + const nsRect &srect = list->mImageRegion; + if (srect.IsEmpty()) { + aComputedValue.SetAutoValue(); + break; + } + + nsCSSRect *vrect = new nsCSSRect; + nscoordToCSSValue(srect.x, vrect->mLeft); + nscoordToCSSValue(srect.y, vrect->mTop); + nscoordToCSSValue(srect.XMost(), vrect->mRight); + nscoordToCSSValue(srect.YMost(), vrect->mBottom); + aComputedValue.SetAndAdoptCSSRectValue(vrect, eUnit_CSSRect); + break; + } + + case eCSSProperty_clip: { + const nsStyleDisplay *display = + static_cast(styleStruct); + if (!(display->mClipFlags & NS_STYLE_CLIP_RECT)) { + aComputedValue.SetAutoValue(); + } else { + nsCSSRect *vrect = new nsCSSRect; + const nsRect &srect = display->mClip; + if (display->mClipFlags & NS_STYLE_CLIP_TOP_AUTO) { + vrect->mTop.SetAutoValue(); + } else { + nscoordToCSSValue(srect.y, vrect->mTop); + } + if (display->mClipFlags & NS_STYLE_CLIP_RIGHT_AUTO) { + vrect->mRight.SetAutoValue(); + } else { + nscoordToCSSValue(srect.XMost(), vrect->mRight); + } + if (display->mClipFlags & NS_STYLE_CLIP_BOTTOM_AUTO) { + vrect->mBottom.SetAutoValue(); + } else { + nscoordToCSSValue(srect.YMost(), vrect->mBottom); + } + if (display->mClipFlags & NS_STYLE_CLIP_LEFT_AUTO) { + vrect->mLeft.SetAutoValue(); + } else { + nscoordToCSSValue(srect.x, vrect->mLeft); + } + aComputedValue.SetAndAdoptCSSRectValue(vrect, eUnit_CSSRect); + } + break; + } + + case eCSSProperty_background_position: { + const nsStyleBackground *bg = + static_cast(styleStruct); + nsAutoPtr result; + nsCSSValueList **resultTail = getter_Transfers(result); + NS_ABORT_IF_FALSE(bg->mPositionCount > 0, "unexpected count"); + for (uint32_t i = 0, i_end = bg->mPositionCount; i != i_end; ++i) { + nsCSSValueList *item = new nsCSSValueList; + *resultTail = item; + resultTail = &item->mNext; + nsRefPtr bgArray = nsCSSValue::Array::Create(4); + item->mValue.SetArrayValue(bgArray.get(), eCSSUnit_Array); + + const nsStyleBackground::Position &pos = bg->mLayers[i].mPosition; + // XXXbz is there a good reason we can't just + // SetCalcValue(&pos.mXPosition, item->mXValue) here? + nsCSSValue &xValue = bgArray->Item(1), + &yValue = bgArray->Item(3); + if (!pos.mXPosition.mHasPercent) { + NS_ABORT_IF_FALSE(pos.mXPosition.mPercent == 0.0f, + "Shouldn't have mPercent!"); + nscoordToCSSValue(pos.mXPosition.mLength, xValue); + } else if (pos.mXPosition.mLength == 0) { + xValue.SetPercentValue(pos.mXPosition.mPercent); + } else { + SetCalcValue(&pos.mXPosition, xValue); + } + + if (!pos.mYPosition.mHasPercent) { + NS_ABORT_IF_FALSE(pos.mYPosition.mPercent == 0.0f, + "Shouldn't have mPercent!"); + nscoordToCSSValue(pos.mYPosition.mLength, yValue); + } else if (pos.mYPosition.mLength == 0) { + yValue.SetPercentValue(pos.mYPosition.mPercent); + } else { + SetCalcValue(&pos.mYPosition, yValue); + } + } + + aComputedValue.SetAndAdoptCSSValueListValue(result.forget(), + eUnit_BackgroundPosition); + break; + } + + case eCSSProperty_background_size: { + const nsStyleBackground *bg = + static_cast(styleStruct); + nsAutoPtr result; + nsCSSValuePairList **resultTail = getter_Transfers(result); + NS_ABORT_IF_FALSE(bg->mSizeCount > 0, "unexpected count"); + for (uint32_t i = 0, i_end = bg->mSizeCount; i != i_end; ++i) { + nsCSSValuePairList *item = new nsCSSValuePairList; + *resultTail = item; + resultTail = &item->mNext; + + const nsStyleBackground::Size &size = bg->mLayers[i].mSize; + switch (size.mWidthType) { + case nsStyleBackground::Size::eContain: + case nsStyleBackground::Size::eCover: + item->mXValue.SetIntValue(size.mWidthType, + eCSSUnit_Enumerated); + break; + case nsStyleBackground::Size::eAuto: + item->mXValue.SetAutoValue(); + break; + case nsStyleBackground::Size::eLengthPercentage: + // XXXbz is there a good reason we can't just + // SetCalcValue(&size.mWidth, item->mXValue) here? + if (!size.mWidth.mHasPercent && + // negative values must have come from calc() + size.mWidth.mLength >= 0) { + NS_ABORT_IF_FALSE(size.mWidth.mPercent == 0.0f, + "Shouldn't have mPercent"); + nscoordToCSSValue(size.mWidth.mLength, item->mXValue); + } else if (size.mWidth.mLength == 0 && + // negative values must have come from calc() + size.mWidth.mPercent >= 0.0f) { + item->mXValue.SetPercentValue(size.mWidth.mPercent); + } else { + SetCalcValue(&size.mWidth, item->mXValue); + } + break; + } + + switch (size.mHeightType) { + case nsStyleBackground::Size::eContain: + case nsStyleBackground::Size::eCover: + // leave it null + break; + case nsStyleBackground::Size::eAuto: + item->mYValue.SetAutoValue(); + break; + case nsStyleBackground::Size::eLengthPercentage: + // XXXbz is there a good reason we can't just + // SetCalcValue(&size.mHeight, item->mYValue) here? + if (!size.mHeight.mHasPercent && + // negative values must have come from calc() + size.mHeight.mLength >= 0) { + NS_ABORT_IF_FALSE(size.mHeight.mPercent == 0.0f, + "Shouldn't have mPercent"); + nscoordToCSSValue(size.mHeight.mLength, item->mYValue); + } else if (size.mHeight.mLength == 0 && + // negative values must have come from calc() + size.mHeight.mPercent >= 0.0f) { + item->mYValue.SetPercentValue(size.mHeight.mPercent); + } else { + SetCalcValue(&size.mHeight, item->mYValue); + } + break; + } + } + + aComputedValue.SetAndAdoptCSSValuePairListValue(result.forget()); + break; + } + + case eCSSProperty_filter: { + const nsStyleSVGReset *svgReset = + static_cast(styleStruct); + const nsTArray& filters = svgReset->mFilters; + nsAutoPtr 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 uriAsStringBuffer = + GetURIAsUtf16StringBuffer(filter.GetURL()); + nsRefPtr 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 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(styleStruct); + nsAutoPtr result; + if (display->mSpecifiedTransform) { + // Clone, and convert all lengths (not percents) to pixels. + nsCSSValueList **resultTail = getter_Transfers(result); + for (const nsCSSValueList *l = display->mSpecifiedTransform->mHead; + l; l = l->mNext) { + nsCSSValueList *clone = new nsCSSValueList; + *resultTail = clone; + resultTail = &clone->mNext; + + SubstitutePixelValues(aStyleContext, l->mValue, clone->mValue); + } + } else { + result = new nsCSSValueList(); + result->mValue.SetNoneValue(); + } + + aComputedValue.SetTransformValue( + new nsCSSValueSharedList(result.forget())); + break; + } + + default: + NS_ABORT_IF_FALSE(false, "missing property implementation"); + return false; + }; + return true; + case eStyleAnimType_Coord: + return StyleCoordToValue(*static_cast( + StyleDataAtOffset(styleStruct, ssOffset)), aComputedValue); + case eStyleAnimType_Sides_Top: + case eStyleAnimType_Sides_Right: + case eStyleAnimType_Sides_Bottom: + case eStyleAnimType_Sides_Left: { + static_assert( + NS_SIDE_TOP == eStyleAnimType_Sides_Top -eStyleAnimType_Sides_Top && + NS_SIDE_RIGHT == eStyleAnimType_Sides_Right -eStyleAnimType_Sides_Top && + NS_SIDE_BOTTOM == eStyleAnimType_Sides_Bottom-eStyleAnimType_Sides_Top && + NS_SIDE_LEFT == eStyleAnimType_Sides_Left -eStyleAnimType_Sides_Top, + "box side constants out of sync with animation side constants"); + + const nsStyleCoord &coord = static_cast( + StyleDataAtOffset(styleStruct, ssOffset))-> + Get(mozilla::css::Side(animType - eStyleAnimType_Sides_Top)); + return StyleCoordToValue(coord, aComputedValue); + } + case eStyleAnimType_Corner_TopLeft: + case eStyleAnimType_Corner_TopRight: + case eStyleAnimType_Corner_BottomRight: + case eStyleAnimType_Corner_BottomLeft: { + static_assert( + NS_CORNER_TOP_LEFT == eStyleAnimType_Corner_TopLeft - + eStyleAnimType_Corner_TopLeft && + NS_CORNER_TOP_RIGHT == eStyleAnimType_Corner_TopRight - + eStyleAnimType_Corner_TopLeft && + NS_CORNER_BOTTOM_RIGHT == eStyleAnimType_Corner_BottomRight - + eStyleAnimType_Corner_TopLeft && + NS_CORNER_BOTTOM_LEFT == eStyleAnimType_Corner_BottomLeft - + eStyleAnimType_Corner_TopLeft, + "box corner constants out of sync with animation corner constants"); + + const nsStyleCorners *corners = static_cast( + StyleDataAtOffset(styleStruct, ssOffset)); + uint8_t fullCorner = animType - eStyleAnimType_Corner_TopLeft; + const nsStyleCoord &horiz = + corners->Get(NS_FULL_TO_HALF_CORNER(fullCorner, false)); + const nsStyleCoord &vert = + corners->Get(NS_FULL_TO_HALF_CORNER(fullCorner, true)); + nsAutoPtr pair(new nsCSSValuePair); + if (!pair || + !StyleCoordToCSSValue(horiz, pair->mXValue) || + !StyleCoordToCSSValue(vert, pair->mYValue)) { + return false; + } + aComputedValue.SetAndAdoptCSSValuePairValue(pair.forget(), + eUnit_CSSValuePair); + return true; + } + case eStyleAnimType_nscoord: + aComputedValue.SetCoordValue(*static_cast( + StyleDataAtOffset(styleStruct, ssOffset))); + return true; + case eStyleAnimType_EnumU8: + aComputedValue.SetIntValue(*static_cast( + StyleDataAtOffset(styleStruct, ssOffset)), eUnit_Enumerated); + return true; + case eStyleAnimType_float: + aComputedValue.SetFloatValue(*static_cast( + StyleDataAtOffset(styleStruct, ssOffset))); + if (aProperty == eCSSProperty_font_size_adjust && + aComputedValue.GetFloatValue() == 0.0f) { + // In nsStyleFont, we set mFont.sizeAdjust to 0 to represent + // font-size-adjust: none. Here, we have to treat this as a keyword + // instead of a float value, to make sure we don't end up doing + // interpolation with it. + aComputedValue.SetNoneValue(); + } + return true; + case eStyleAnimType_Color: + aComputedValue.SetColorValue(*static_cast( + StyleDataAtOffset(styleStruct, ssOffset))); + return true; + case eStyleAnimType_PaintServer: { + const nsStyleSVGPaint &paint = *static_cast( + StyleDataAtOffset(styleStruct, ssOffset)); + if (paint.mType == eStyleSVGPaintType_Color) { + aComputedValue.SetColorValue(paint.mPaint.mColor); + return true; + } + if (paint.mType == eStyleSVGPaintType_Server) { + if (!paint.mPaint.mPaintServer) { + NS_WARNING("Null paint server"); + return false; + } + nsAutoPtr pair(new nsCSSValuePair); + nsRefPtr uriAsStringBuffer = + GetURIAsUtf16StringBuffer(paint.mPaint.mPaintServer); + NS_ENSURE_TRUE(!!uriAsStringBuffer, false); + nsIDocument* doc = aStyleContext->PresContext()->Document(); + nsRefPtr url = + new mozilla::css::URLValue(paint.mPaint.mPaintServer, + uriAsStringBuffer, + doc->GetDocumentURI(), + doc->NodePrincipal()); + pair->mXValue.SetURLValue(url); + pair->mYValue.SetColorValue(paint.mFallbackColor); + aComputedValue.SetAndAdoptCSSValuePairValue(pair.forget(), + eUnit_CSSValuePair); + return true; + } + if (paint.mType == eStyleSVGPaintType_ContextFill || + paint.mType == eStyleSVGPaintType_ContextStroke) { + nsAutoPtr pair(new nsCSSValuePair); + pair->mXValue.SetIntValue(paint.mType == eStyleSVGPaintType_ContextFill ? + NS_COLOR_CONTEXT_FILL : NS_COLOR_CONTEXT_STROKE, + eCSSUnit_Enumerated); + pair->mYValue.SetColorValue(paint.mFallbackColor); + aComputedValue.SetAndAdoptCSSValuePairValue(pair.forget(), + eUnit_CSSValuePair); + return true; + } + NS_ABORT_IF_FALSE(paint.mType == eStyleSVGPaintType_None, + "Unexpected SVG paint type"); + aComputedValue.SetNoneValue(); + return true; + } + case eStyleAnimType_Shadow: { + const nsCSSShadowArray *shadowArray = + *static_cast*>( + StyleDataAtOffset(styleStruct, ssOffset)); + if (!shadowArray) { + aComputedValue.SetAndAdoptCSSValueListValue(nullptr, eUnit_Shadow); + return true; + } + nsAutoPtr result; + nsCSSValueList **resultTail = getter_Transfers(result); + for (uint32_t i = 0, i_end = shadowArray->Length(); i < i_end; ++i) { + AppendCSSShadowValue(shadowArray->ShadowAt(i), resultTail); + } + aComputedValue.SetAndAdoptCSSValueListValue(result.forget(), + eUnit_Shadow); + return true; + } + case eStyleAnimType_None: + NS_NOTREACHED("shouldn't use on non-animatable properties"); + } + return false; +} + +nsStyleAnimation::Value::Value(int32_t aInt, Unit aUnit, + IntegerConstructorType) +{ + NS_ASSERTION(IsIntUnit(aUnit), "unit must be of integer type"); + mUnit = aUnit; + mValue.mInt = aInt; +} + +nsStyleAnimation::Value::Value(nscoord aLength, CoordConstructorType) +{ + mUnit = eUnit_Coord; + mValue.mCoord = aLength; +} + +nsStyleAnimation::Value::Value(float aPercent, PercentConstructorType) +{ + mUnit = eUnit_Percent; + mValue.mFloat = aPercent; + MOZ_ASSERT(!mozilla::IsNaN(mValue.mFloat)); +} + +nsStyleAnimation::Value::Value(float aFloat, FloatConstructorType) +{ + mUnit = eUnit_Float; + mValue.mFloat = aFloat; + MOZ_ASSERT(!mozilla::IsNaN(mValue.mFloat)); +} + +nsStyleAnimation::Value::Value(nscolor aColor, ColorConstructorType) +{ + mUnit = eUnit_Color; + mValue.mColor = aColor; +} + +nsStyleAnimation::Value& +nsStyleAnimation::Value::operator=(const Value& aOther) +{ + FreeValue(); + + mUnit = aOther.mUnit; + switch (mUnit) { + case eUnit_Null: + case eUnit_Normal: + case eUnit_Auto: + case eUnit_None: + break; + case eUnit_Enumerated: + case eUnit_Visibility: + case eUnit_Integer: + mValue.mInt = aOther.mValue.mInt; + break; + case eUnit_Coord: + mValue.mCoord = aOther.mValue.mCoord; + break; + case eUnit_Percent: + case eUnit_Float: + mValue.mFloat = aOther.mValue.mFloat; + MOZ_ASSERT(!mozilla::IsNaN(mValue.mFloat)); + break; + case eUnit_Color: + mValue.mColor = aOther.mValue.mColor; + break; + case eUnit_Calc: + NS_ABORT_IF_FALSE(aOther.mValue.mCSSValue, "values may not be null"); + mValue.mCSSValue = new nsCSSValue(*aOther.mValue.mCSSValue); + if (!mValue.mCSSValue) { + mUnit = eUnit_Null; + } + break; + case eUnit_CSSValuePair: + NS_ABORT_IF_FALSE(aOther.mValue.mCSSValuePair, + "value pairs may not be null"); + mValue.mCSSValuePair = new nsCSSValuePair(*aOther.mValue.mCSSValuePair); + if (!mValue.mCSSValuePair) { + mUnit = eUnit_Null; + } + break; + case eUnit_CSSValueTriplet: + NS_ABORT_IF_FALSE(aOther.mValue.mCSSValueTriplet, + "value triplets may not be null"); + mValue.mCSSValueTriplet = new nsCSSValueTriplet(*aOther.mValue.mCSSValueTriplet); + if (!mValue.mCSSValueTriplet) { + mUnit = eUnit_Null; + } + break; + case eUnit_CSSRect: + NS_ABORT_IF_FALSE(aOther.mValue.mCSSRect, "rects may not be null"); + mValue.mCSSRect = new nsCSSRect(*aOther.mValue.mCSSRect); + if (!mValue.mCSSRect) { + mUnit = eUnit_Null; + } + break; + case eUnit_Filter: + case eUnit_Dasharray: + case eUnit_Shadow: + case eUnit_BackgroundPosition: + 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) { + mUnit = eUnit_Null; + } + } else { + mValue.mCSSValueList = nullptr; + } + break; + case eUnit_Transform: + mValue.mCSSValueSharedList = aOther.mValue.mCSSValueSharedList; + mValue.mCSSValueSharedList->AddRef(); + break; + case eUnit_CSSValuePairList: + NS_ABORT_IF_FALSE(aOther.mValue.mCSSValuePairList, + "value pair lists may not be null"); + mValue.mCSSValuePairList = aOther.mValue.mCSSValuePairList->Clone(); + if (!mValue.mCSSValuePairList) { + mUnit = eUnit_Null; + } + break; + case eUnit_UnparsedString: + NS_ABORT_IF_FALSE(aOther.mValue.mString, "expecting non-null string"); + mValue.mString = aOther.mValue.mString; + mValue.mString->AddRef(); + break; + } + + return *this; +} + +void +nsStyleAnimation::Value::SetNormalValue() +{ + FreeValue(); + mUnit = eUnit_Normal; +} + +void +nsStyleAnimation::Value::SetAutoValue() +{ + FreeValue(); + mUnit = eUnit_Auto; +} + +void +nsStyleAnimation::Value::SetNoneValue() +{ + FreeValue(); + mUnit = eUnit_None; +} + +void +nsStyleAnimation::Value::SetIntValue(int32_t aInt, Unit aUnit) +{ + NS_ASSERTION(IsIntUnit(aUnit), "unit must be of integer type"); + FreeValue(); + mUnit = aUnit; + mValue.mInt = aInt; +} + +void +nsStyleAnimation::Value::SetCoordValue(nscoord aLength) +{ + FreeValue(); + mUnit = eUnit_Coord; + mValue.mCoord = aLength; +} + +void +nsStyleAnimation::Value::SetPercentValue(float aPercent) +{ + FreeValue(); + mUnit = eUnit_Percent; + mValue.mFloat = aPercent; + MOZ_ASSERT(!mozilla::IsNaN(mValue.mFloat)); +} + +void +nsStyleAnimation::Value::SetFloatValue(float aFloat) +{ + FreeValue(); + mUnit = eUnit_Float; + mValue.mFloat = aFloat; + MOZ_ASSERT(!mozilla::IsNaN(mValue.mFloat)); +} + +void +nsStyleAnimation::Value::SetColorValue(nscolor aColor) +{ + FreeValue(); + mUnit = eUnit_Color; + mValue.mColor = aColor; +} + +void +nsStyleAnimation::Value::SetUnparsedStringValue(const nsString& aString) +{ + FreeValue(); + mUnit = eUnit_UnparsedString; + mValue.mString = nsCSSValue::BufferFromString(aString).take(); +} + +void +nsStyleAnimation::Value::SetAndAdoptCSSValueValue(nsCSSValue *aValue, + Unit aUnit) +{ + FreeValue(); + NS_ABORT_IF_FALSE(IsCSSValueUnit(aUnit), "bad unit"); + NS_ABORT_IF_FALSE(aValue != nullptr, "values may not be null"); + mUnit = aUnit; + mValue.mCSSValue = aValue; // take ownership +} + +void +nsStyleAnimation::Value::SetAndAdoptCSSValuePairValue( + nsCSSValuePair *aValuePair, Unit aUnit) +{ + FreeValue(); + NS_ABORT_IF_FALSE(IsCSSValuePairUnit(aUnit), "bad unit"); + NS_ABORT_IF_FALSE(aValuePair != nullptr, "value pairs may not be null"); + mUnit = aUnit; + mValue.mCSSValuePair = aValuePair; // take ownership +} + +void +nsStyleAnimation::Value::SetAndAdoptCSSValueTripletValue( + nsCSSValueTriplet *aValueTriplet, Unit aUnit) +{ + FreeValue(); + NS_ABORT_IF_FALSE(IsCSSValueTripletUnit(aUnit), "bad unit"); + NS_ABORT_IF_FALSE(aValueTriplet != nullptr, "value pairs may not be null"); + mUnit = aUnit; + mValue.mCSSValueTriplet = aValueTriplet; // take ownership +} + +void +nsStyleAnimation::Value::SetAndAdoptCSSRectValue(nsCSSRect *aRect, Unit aUnit) +{ + FreeValue(); + NS_ABORT_IF_FALSE(IsCSSRectUnit(aUnit), "bad unit"); + NS_ABORT_IF_FALSE(aRect != nullptr, "value pairs may not be null"); + mUnit = aUnit; + mValue.mCSSRect = aRect; // take ownership +} + +void +nsStyleAnimation::Value::SetAndAdoptCSSValueListValue( + nsCSSValueList *aValueList, Unit aUnit) +{ + FreeValue(); + NS_ABORT_IF_FALSE(IsCSSValueListUnit(aUnit), "bad unit"); + NS_ABORT_IF_FALSE(aUnit == eUnit_Shadow || aUnit == eUnit_Filter || + aValueList != nullptr, + "value lists other than shadows and filters may not be null"); + mUnit = aUnit; + mValue.mCSSValueList = aValueList; // take ownership +} + +void +nsStyleAnimation::Value::SetTransformValue(nsCSSValueSharedList* aList) +{ + FreeValue(); + mUnit = eUnit_Transform; + mValue.mCSSValueSharedList = aList; + mValue.mCSSValueSharedList->AddRef(); +} + +void +nsStyleAnimation::Value::SetAndAdoptCSSValuePairListValue( + nsCSSValuePairList *aValuePairList) +{ + FreeValue(); + NS_ABORT_IF_FALSE(aValuePairList, "may not be null"); + mUnit = eUnit_CSSValuePairList; + mValue.mCSSValuePairList = aValuePairList; // take ownership +} + +void +nsStyleAnimation::Value::FreeValue() +{ + if (IsCSSValueUnit(mUnit)) { + delete mValue.mCSSValue; + } else if (IsCSSValueListUnit(mUnit)) { + delete mValue.mCSSValueList; + } else if (IsCSSValueSharedListValue(mUnit)) { + mValue.mCSSValueSharedList->Release(); + } else if (IsCSSValuePairUnit(mUnit)) { + delete mValue.mCSSValuePair; + } else if (IsCSSValueTripletUnit(mUnit)) { + delete mValue.mCSSValueTriplet; + } else if (IsCSSRectUnit(mUnit)) { + delete mValue.mCSSRect; + } else if (IsCSSValuePairListUnit(mUnit)) { + delete mValue.mCSSValuePairList; + } else if (IsStringUnit(mUnit)) { + NS_ABORT_IF_FALSE(mValue.mString, "expecting non-null string"); + mValue.mString->Release(); + } +} + +bool +nsStyleAnimation::Value::operator==(const Value& aOther) const +{ + if (mUnit != aOther.mUnit) { + return false; + } + + switch (mUnit) { + case eUnit_Null: + case eUnit_Normal: + case eUnit_Auto: + case eUnit_None: + return true; + case eUnit_Enumerated: + case eUnit_Visibility: + case eUnit_Integer: + return mValue.mInt == aOther.mValue.mInt; + case eUnit_Coord: + return mValue.mCoord == aOther.mValue.mCoord; + case eUnit_Percent: + case eUnit_Float: + return mValue.mFloat == aOther.mValue.mFloat; + case eUnit_Color: + return mValue.mColor == aOther.mValue.mColor; + case eUnit_Calc: + return *mValue.mCSSValue == *aOther.mValue.mCSSValue; + case eUnit_CSSValuePair: + return *mValue.mCSSValuePair == *aOther.mValue.mCSSValuePair; + case eUnit_CSSValueTriplet: + return *mValue.mCSSValueTriplet == *aOther.mValue.mCSSValueTriplet; + case eUnit_CSSRect: + return *mValue.mCSSRect == *aOther.mValue.mCSSRect; + case eUnit_Dasharray: + case eUnit_Filter: + case eUnit_Shadow: + case eUnit_BackgroundPosition: + return *mValue.mCSSValueList == *aOther.mValue.mCSSValueList; + case eUnit_Transform: + return *mValue.mCSSValueSharedList == *aOther.mValue.mCSSValueSharedList; + case eUnit_CSSValuePairList: + return *mValue.mCSSValuePairList == *aOther.mValue.mCSSValuePairList; + case eUnit_UnparsedString: + return (NS_strcmp(GetStringBufferValue(), + aOther.GetStringBufferValue()) == 0); + } + + NS_NOTREACHED("incomplete case"); + return false; +} +