diff options
Diffstat (limited to 'layout/style/StyleAnimationValue.cpp')
-rw-r--r-- | layout/style/StyleAnimationValue.cpp | 4990 |
1 files changed, 4990 insertions, 0 deletions
diff --git a/layout/style/StyleAnimationValue.cpp b/layout/style/StyleAnimationValue.cpp new file mode 100644 index 000000000..eb34c3d83 --- /dev/null +++ b/layout/style/StyleAnimationValue.cpp @@ -0,0 +1,4990 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "mozilla/RuleNodeCacheConditions.h" +#include "mozilla/StyleAnimationValue.h" +#include "mozilla/Tuple.h" +#include "mozilla/UniquePtr.h" +#include "nsStyleTransformMatrix.h" +#include "nsAutoPtr.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 "nsCSSPseudoElements.h" +#include "mozilla/css/Declaration.h" +#include "mozilla/dom/Element.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/Likely.h" +#include "mozilla/ServoBindings.h" // RawServoDeclarationBlock +#include "gfxMatrix.h" +#include "gfxQuaternion.h" +#include "nsIDocument.h" +#include "nsIFrame.h" +#include "gfx2DGlue.h" + +using namespace mozilla; +using namespace mozilla::css; +using namespace mozilla::gfx; +using nsStyleTransformMatrix::Decompose2DMatrix; +using nsStyleTransformMatrix::Decompose3DMatrix; +using nsStyleTransformMatrix::ShearType; + +// 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 +StyleAnimationValue::Unit +GetCommonUnit(nsCSSPropertyID aProperty, + StyleAnimationValue::Unit aFirstUnit, + StyleAnimationValue::Unit aSecondUnit) +{ + if (aFirstUnit != aSecondUnit) { + if (nsCSSProps::PropHasFlags(aProperty, CSS_PROPERTY_STORES_CALC) && + (aFirstUnit == StyleAnimationValue::eUnit_Coord || + aFirstUnit == StyleAnimationValue::eUnit_Percent || + aFirstUnit == StyleAnimationValue::eUnit_Calc) && + (aSecondUnit == StyleAnimationValue::eUnit_Coord || + aSecondUnit == StyleAnimationValue::eUnit_Percent || + aSecondUnit == StyleAnimationValue::eUnit_Calc)) { + // We can use calc() as the common unit. + return StyleAnimationValue::eUnit_Calc; + } + if ((aFirstUnit == StyleAnimationValue::eUnit_Color || + aFirstUnit == StyleAnimationValue::eUnit_CurrentColor || + aFirstUnit == StyleAnimationValue::eUnit_ComplexColor) && + (aSecondUnit == StyleAnimationValue::eUnit_Color || + aSecondUnit == StyleAnimationValue::eUnit_CurrentColor || + aSecondUnit == StyleAnimationValue::eUnit_ComplexColor)) { + // We can use complex color as the common unit. + return StyleAnimationValue::eUnit_ComplexColor; + } + return StyleAnimationValue::eUnit_Null; + } + return aFirstUnit; +} + +static +nsCSSUnit +GetCommonUnit(nsCSSPropertyID 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 bool +TransformFunctionsMatch(nsCSSKeyword func1, nsCSSKeyword func2) +{ + return ToPrimitive(func1) == ToPrimitive(func2); +} + +static already_AddRefed<nsCSSValue::Array> +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"); + MOZ_FALLTHROUGH; + 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; + } + + RefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(nargs + 1); + arr->Item(0).SetIntValue(aTransformFunction, eCSSUnit_Enumerated); + + return arr.forget(); +} + +static already_AddRefed<nsCSSValue::Array> +ToPrimitive(nsCSSValue::Array* aArray) +{ + nsCSSKeyword tfunc = nsStyleTransformMatrix::TransformFunctionOf(aArray); + nsCSSKeyword primitive = ToPrimitive(tfunc); + RefPtr<nsCSSValue::Array> 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: + { + MOZ_ASSERT(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: + { + MOZ_ASSERT(aArray->Count() == 2, "unexpected count"); + arr->Item(1) = aArray->Item(1); + arr->Item(2) = zero; + arr->Item(3) = zero; + break; + } + case eCSSKeyword_translatey: + { + MOZ_ASSERT(aArray->Count() == 2, "unexpected count"); + arr->Item(1) = zero; + arr->Item(2) = aArray->Item(1); + arr->Item(3) = zero; + break; + } + case eCSSKeyword_translatez: + { + MOZ_ASSERT(aArray->Count() == 2, "unexpected count"); + arr->Item(1) = zero; + arr->Item(2) = zero; + arr->Item(3) = aArray->Item(1); + break; + } + case eCSSKeyword_scale: + { + MOZ_ASSERT(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: + { + MOZ_ASSERT(aArray->Count() == 2, "unexpected count"); + arr->Item(1) = aArray->Item(1); + arr->Item(2) = one; + arr->Item(3) = one; + break; + } + case eCSSKeyword_scaley: + { + MOZ_ASSERT(aArray->Count() == 2, "unexpected count"); + arr->Item(1) = one; + arr->Item(2) = aArray->Item(1); + arr->Item(3) = one; + break; + } + case eCSSKeyword_scalez: + { + MOZ_ASSERT(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(); +} + +static void +AppendCSSShadowValue(const nsCSSShadowItem *aShadow, + nsCSSValueList **&aResultTail) +{ + MOZ_ASSERT(aShadow, "shadow expected"); + + // X, Y, Radius, Spread, Color, Inset + RefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(6); + arr->Item(0).SetIntegerCoordValue(aShadow->mXOffset); + arr->Item(1).SetIntegerCoordValue(aShadow->mYOffset); + arr->Item(2).SetIntegerCoordValue(aShadow->mRadius); + // NOTE: This code sometimes stores mSpread: 0 even when + // the parser would be required to leave it null. + arr->Item(3).SetIntegerCoordValue(aShadow->mSpread); + if (aShadow->mHasColor) { + arr->Item(4).SetColorValue(aShadow->mColor); + } + if (aShadow->mInset) { + arr->Item(5).SetIntValue(uint8_t(StyleBoxShadowType::Inset), + eCSSUnit_Enumerated); + } + + nsCSSValueList *resultItem = new nsCSSValueList; + resultItem->mValue.SetArrayValue(arr, eCSSUnit_Array); + *aResultTail = resultItem; + aResultTail = &resultItem->mNext; +} + +// Like nsStyleCoord::CalcValue, but with length in float pixels instead +// of nscoord. +struct PixelCalcValue +{ + float mLength, mPercent; + bool mHasPercent; +}; + +// Requires a canonical calc() value that we generated. +static PixelCalcValue +ExtractCalcValueInternal(const nsCSSValue& aValue) +{ + MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Calc, "unexpected unit"); + nsCSSValue::Array *arr = aValue.GetArrayValue(); + MOZ_ASSERT(arr->Count() == 1, "unexpected length"); + + const nsCSSValue &topval = arr->Item(0); + PixelCalcValue result; + if (topval.GetUnit() == eCSSUnit_Pixel) { + result.mLength = topval.GetFloatValue(); + result.mPercent = 0.0f; + result.mHasPercent = false; + } else { + MOZ_ASSERT(topval.GetUnit() == eCSSUnit_Calc_Plus, + "unexpected unit"); + nsCSSValue::Array *arr2 = topval.GetArrayValue(); + const nsCSSValue &len = arr2->Item(0); + const nsCSSValue &pct = arr2->Item(1); + MOZ_ASSERT(len.GetUnit() == eCSSUnit_Pixel, "unexpected unit"); + MOZ_ASSERT(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 PixelCalcValue +ExtractCalcValue(const StyleAnimationValue& aValue) +{ + PixelCalcValue result; + if (aValue.GetUnit() == StyleAnimationValue::eUnit_Coord) { + result.mLength = + nsPresContext::AppUnitsToFloatCSSPixels(aValue.GetCoordValue()); + result.mPercent = 0.0f; + result.mHasPercent = false; + return result; + } + if (aValue.GetUnit() == StyleAnimationValue::eUnit_Percent) { + result.mLength = 0.0f; + result.mPercent = aValue.GetPercentValue(); + result.mHasPercent = true; + return result; + } + MOZ_ASSERT(aValue.GetUnit() == StyleAnimationValue::eUnit_Calc, + "unexpected unit"); + nsCSSValue *val = aValue.GetCSSValueValue(); + return ExtractCalcValueInternal(*val); +} + +static PixelCalcValue +ExtractCalcValue(const nsCSSValue& aValue) +{ + PixelCalcValue 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 +CalcValueToCSSValue(const nsStyleCoord::CalcValue* aCalc, nsCSSValue& aValue) +{ + aValue.SetCalcValue(aCalc); +} + +static void +CalcValueToCSSValue(const PixelCalcValue& aCalc, nsCSSValue& aValue) +{ + RefPtr<nsCSSValue::Array> 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); +} + +double +CalcPositionSquareDistance(const nsCSSValue& aPos1, + const nsCSSValue& aPos2) +{ + NS_ASSERTION(aPos1.GetUnit() == eCSSUnit_Array && + aPos2.GetUnit() == eCSSUnit_Array, + "Expected two arrays"); + + PixelCalcValue calcVal[4]; + + nsCSSValue::Array* posArray = aPos1.GetArrayValue(); + MOZ_ASSERT(posArray->Count() == 4, "Invalid position value"); + NS_ASSERTION(posArray->Item(0).GetUnit() == eCSSUnit_Null && + posArray->Item(2).GetUnit() == eCSSUnit_Null, + "Invalid list used"); + for (int i = 0; i < 2; ++i) { + MOZ_ASSERT(posArray->Item(i*2+1).GetUnit() != eCSSUnit_Null, + "Invalid position value"); + calcVal[i] = ExtractCalcValue(posArray->Item(i*2+1)); + } + + posArray = aPos2.GetArrayValue(); + MOZ_ASSERT(posArray->Count() == 4, "Invalid position value"); + NS_ASSERTION(posArray->Item(0).GetUnit() == eCSSUnit_Null && + posArray->Item(2).GetUnit() == eCSSUnit_Null, + "Invalid list used"); + for (int i = 0; i < 2; ++i) { + MOZ_ASSERT(posArray->Item(i*2+1).GetUnit() != eCSSUnit_Null, + "Invalid position value"); + calcVal[i+2] = ExtractCalcValue(posArray->Item(i*2+1)); + } + + double squareDistance = 0.0; + 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; + } + + return squareDistance; +} + +static PixelCalcValue +CalcBackgroundCoord(const nsCSSValue& aCoord) +{ + NS_ASSERTION(aCoord.GetUnit() == eCSSUnit_Array, + "Expected array"); + + nsCSSValue::Array* array = aCoord.GetArrayValue(); + MOZ_ASSERT(array->Count() == 2 && + array->Item(0).GetUnit() == eCSSUnit_Null && + array->Item(1).GetUnit() != eCSSUnit_Null, + "Invalid position value"); + return ExtractCalcValue(array->Item(1)); +} + +double +CalcPositionCoordSquareDistance(const nsCSSValue& aPos1, + const nsCSSValue& aPos2) +{ + PixelCalcValue calcVal1 = CalcBackgroundCoord(aPos1); + PixelCalcValue calcVal2 = CalcBackgroundCoord(aPos2); + + float difflen = calcVal2.mLength - calcVal1.mLength; + float diffpct = calcVal2.mPercent - calcVal1.mPercent; + return difflen * difflen + diffpct * diffpct; +} + +// 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<typename T> +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 <typename T> +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: + MOZ_ASSERT(false, "bad value restriction"); + break; + } + return result; +} + +template <typename T> +T +RestrictValue(nsCSSPropertyID aProperty, T aValue) +{ + return RestrictValue(nsCSSProps::ValueRestrictions(aProperty), aValue); +} + +static void +AddCSSValueAngle(double aCoeff1, const nsCSSValue &aValue1, + double aCoeff2, const nsCSSValue &aValue2, + nsCSSValue &aResult) +{ + if (aValue1.GetUnit() == aValue2.GetUnit()) { + // To avoid floating point error, if the units match, maintain the unit. + aResult.SetFloatValue( + EnsureNotNan(aCoeff1 * aValue1.GetFloatValue() + + aCoeff2 * aValue2.GetFloatValue()), + aValue1.GetUnit()); + } else { + aResult.SetFloatValue( + EnsureNotNan(aCoeff1 * aValue1.GetAngleValueInRadians() + + aCoeff2 * aValue2.GetAngleValueInRadians()), + eCSSUnit_Radian); + } +} + +static inline void +AddCSSValuePercent(double aCoeff1, const nsCSSValue &aValue1, + double aCoeff2, const nsCSSValue &aValue2, + nsCSSValue &aResult, uint32_t aValueRestrictions = 0) +{ + MOZ_ASSERT(aValue1.GetUnit() == eCSSUnit_Percent, "unexpected unit"); + MOZ_ASSERT(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) +{ + PixelCalcValue v1 = ExtractCalcValue(aValue1); + PixelCalcValue v2 = ExtractCalcValue(aValue2); + PixelCalcValue 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"); + CalcValueToCSSValue(result, aResult); +} + +static inline void +AddCSSValuePixel(double aCoeff1, const nsCSSValue &aValue1, + double aCoeff2, const nsCSSValue &aValue2, + nsCSSValue &aResult, uint32_t aValueRestrictions = 0) +{ + MOZ_ASSERT(aValue1.GetUnit() == eCSSUnit_Pixel, "unexpected unit"); + MOZ_ASSERT(aValue2.GetUnit() == eCSSUnit_Pixel, "unexpected unit"); + aResult.SetFloatValue(RestrictValue(aValueRestrictions, + aCoeff1 * aValue1.GetFloatValue() + + aCoeff2 * aValue2.GetFloatValue()), + eCSSUnit_Pixel); +} + +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 void +AddTransformTranslate(double aCoeff1, const nsCSSValue &aValue1, + double aCoeff2, const nsCSSValue &aValue2, + nsCSSValue &aResult) +{ + // Only three possible units: eCSSUnit_Pixel, eCSSUnit_Percent, or + // eCSSUnit_Calc. + MOZ_ASSERT(aValue1.GetUnit() == eCSSUnit_Percent || + aValue1.GetUnit() == eCSSUnit_Pixel || + aValue1.IsCalcUnit(), + "unexpected unit"); + MOZ_ASSERT(aValue2.GetUnit() == eCSSUnit_Percent || + aValue2.GetUnit() == eCSSUnit_Pixel || + aValue2.IsCalcUnit(), + "unexpected unit"); + AddCSSValuePixelPercentCalc(0, + (aValue1.GetUnit() != aValue2.GetUnit() || + aValue1.IsCalcUnit()) + ? eCSSUnit_Calc + : aValue1.GetUnit(), + aCoeff1, aValue1, + aCoeff2, aValue2, + aResult); +} + +// CLASS METHODS +// ------------- + +static RGBAColorData +ExtractColor(const nsCSSValue& aValue) +{ + MOZ_ASSERT(aValue.IsNumericColorUnit(), "The unit should be color"); + // PercentageRGBColor and PercentageRGBAColor component value might be + // greater than 1.0 in case when the color value is accumulated, so we + // can't use nsCSSValue::GetColorValue() here because that function + // clamps its values. + if (aValue.GetUnit() == eCSSUnit_PercentageRGBColor || + aValue.GetUnit() == eCSSUnit_PercentageRGBAColor) { + nsCSSValueFloatColor* floatColor = aValue.GetFloatColorValue(); + return { + floatColor->Comp1(), + floatColor->Comp2(), + floatColor->Comp3(), + floatColor->Alpha() + }; + } + return RGBAColorData(aValue.GetColorValue()); +} + +static RGBAColorData +ExtractColor(const StyleAnimationValue& aValue) +{ + MOZ_ASSERT(aValue.GetUnit() == StyleAnimationValue::eUnit_Color); + nsCSSValue* value = aValue.GetCSSValueValue(); + MOZ_ASSERT(value, "CSS value must be valid"); + return ExtractColor(*value); +} + +static ComplexColorData +ExtractComplexColor(const StyleAnimationValue& aValue) +{ + switch (aValue.GetUnit()) { + case StyleAnimationValue::eUnit_Color: + return ComplexColorData(ExtractColor(aValue), 0.0f); + case StyleAnimationValue::eUnit_CurrentColor: + return ComplexColorData({0, 0, 0, 0}, 1.0f); + case StyleAnimationValue::eUnit_ComplexColor: + return ComplexColorData(aValue.GetComplexColorData()); + default: + MOZ_ASSERT_UNREACHABLE("Unknown unit"); + return ComplexColorData({0, 0, 0, 0}, 0.0f); + } +} + +double +StyleAnimationValue::ComputeColorDistance(const RGBAColorData& aStartColor, + const RGBAColorData& aEndColor) +{ + // 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. + double startA = aStartColor.mA; + double startR = aStartColor.mR * startA; + double startG = aStartColor.mG * startA; + double startB = aStartColor.mB * startA; + double endA = aEndColor.mA; + double endR = aEndColor.mR * endA; + double endG = aEndColor.mG * endA; + double endB = aEndColor.mB * endA; + + double diffA = startA - endA; + double diffR = startR - endR; + double diffG = startG - endG; + double diffB = startB - endB; + return sqrt(diffA * diffA + diffR * diffR + diffG * diffG + diffB * diffB); +} + +enum class Restrictions { + Enable, + Disable +}; + +static already_AddRefed<nsCSSValue::Array> +AddShapeFunction(nsCSSPropertyID aProperty, + double aCoeff1, const nsCSSValue::Array* aArray1, + double aCoeff2, const nsCSSValue::Array* aArray2, + Restrictions aRestriction = Restrictions::Enable); + +static double +ComputeShapeDistance(nsCSSPropertyID aProperty, + const nsCSSValue::Array* aArray1, + const nsCSSValue::Array* aArray2) +{ + // Use AddShapeFunction to get the difference between two shape functions. + RefPtr<nsCSSValue::Array> diffShape = + AddShapeFunction(aProperty, 1.0, aArray2, -1.0, aArray1, + Restrictions::Disable); + if (!diffShape) { + return 0.0; + } + + // A helper function to convert a calc() diff value into a double distance. + auto pixelCalcDistance = [](const PixelCalcValue& aValue) { + MOZ_ASSERT(aValue.mHasPercent || aValue.mPercent == 0.0f, + "can't have a nonzero percentage part without having percentages"); + return aValue.mLength * aValue.mLength + aValue.mPercent * aValue.mPercent; + }; + + double squareDistance = 0.0; + const nsCSSValue::Array* func = diffShape->Item(0).GetArrayValue(); + nsCSSKeyword shapeFuncName = func->Item(0).GetKeywordValue(); + switch (shapeFuncName) { + case eCSSKeyword_ellipse: + case eCSSKeyword_circle: { + // Skip the first element which is the function keyword. + // Also, skip the last element which is an array for <position> + const size_t len = func->Count(); + for (size_t i = 1; i < len - 1; ++i) { + squareDistance += pixelCalcDistance(ExtractCalcValue(func->Item(i))); + } + // Only iterate over elements 1 and 3. The <position> is 'uncomputed' to + // only those elements. See also the comment in SetPositionValue. + for (size_t i = 1; i < 4; i += 2) { + const nsCSSValue& value = func->Item(len - 1).GetArrayValue()->Item(i); + squareDistance += pixelCalcDistance(ExtractCalcValue(value)); + } + break; + } + case eCSSKeyword_polygon: { + // Don't care about the first element which is the function keyword, and + // the second element which is the fill rule. + const nsCSSValuePairList* list = func->Item(2).GetPairListValue(); + do { + squareDistance += pixelCalcDistance(ExtractCalcValue(list->mXValue)) + + pixelCalcDistance(ExtractCalcValue(list->mYValue)); + list = list->mNext; + } while (list); + break; + } + case eCSSKeyword_inset: { + // Items 1-4 are respectively the top, right, bottom and left offsets + // from the reference box. + for (size_t i = 1; i <= 4; ++i) { + const nsCSSValue& value = func->Item(i); + squareDistance += pixelCalcDistance(ExtractCalcValue(value)); + } + // Item 5 contains the radii of the rounded corners for the inset + // rectangle. + const nsCSSValue::Array* array = func->Item(5).GetArrayValue(); + const size_t len = array->Count(); + for (size_t i = 0; i < len; ++i) { + const nsCSSValuePair& pair = array->Item(i).GetPairValue(); + squareDistance += pixelCalcDistance(ExtractCalcValue(pair.mXValue)) + + pixelCalcDistance(ExtractCalcValue(pair.mYValue)); + } + break; + } + default: + MOZ_ASSERT_UNREACHABLE("Unknown shape type"); + } + return sqrt(squareDistance); +} + +static nsCSSValueList* +AddTransformLists(double aCoeff1, const nsCSSValueList* aList1, + double aCoeff2, const nsCSSValueList* aList2); + +static double +ComputeTransform2DMatrixDistance(const Matrix& aMatrix1, + const Matrix& aMatrix2) +{ + Point3D scale1(1, 1, 1); + Point3D translate1; + gfxQuaternion rotate1; + nsStyleTransformMatrix::ShearArray shear1{0.0f, 0.0f, 0.0f}; + Decompose2DMatrix(aMatrix1, scale1, shear1, rotate1, translate1); + + Point3D scale2(1, 1, 1); + Point3D translate2; + gfxQuaternion rotate2; + nsStyleTransformMatrix::ShearArray shear2{0.0f, 0.0f, 0.0f}; + Decompose2DMatrix(aMatrix2, scale2, shear2, rotate2, translate2); + + // Note: + // 1. Shear factor is the tangent value of shear angle, so we need to + // call atan() to get the angle. For 2D transform, we only have XYSHEAR. + // 2. The quaternion vector of the decomposed 2d matrix is got by + // "gfxQuaternion(0, 0, sin(rotate/2), cos(rotate/2))" + // ^^^^^^^^^^^^^ ^^^^^^^^^^^^^ + // z w + // Therefore, we can get the rotate angle by 2 * atan2f(z, w). + // + // However, we can also get the rotate angle by the inner product of + // two quaternion vectors, just as what we do for eCSSKeyword_rotate3d. + // e.g. + // rotate3d(0, 0, 1, 60deg) => rotate3d(0, 0, 1, 120deg); + // quaternion 1: (0, 0, sin(30deg), cos(30deg)) = (0, 0, 1/2, sqrt(3)/2) + // quaternion 2: (0, 0, sin(60deg), cos(60deg)) = (0, 0, sqrt(3)/2, 1/2) + // inner product: sqrt(3)/4 + sqrt(3)/4 = sqrt(3)/2 + // Finally, the rotate angle: 2 * acos(sqrt(3)/2) = 60deg + // + // I think doing atan() may be faster than doing inner product together + // with acos(), so let's adopt atan2f(). + const Point3D diffTranslate = translate2 - translate1; + const Point3D diffScale = scale2 - scale1; + const double diffShear = atan(shear2[ShearType::XYSHEAR]) - + atan(shear1[ShearType::XYSHEAR]); + const double diffRotate = 2.0 * (atan2f(rotate2.z, rotate2.w) - + atan2f(rotate1.z, rotate1.w)); + // Returns the sum of squares because we will take a square root in + // ComputeTransformListDistance. + return diffTranslate.DotProduct(diffTranslate) + + diffScale.DotProduct(diffScale) + + diffRotate * diffRotate + + diffShear * diffShear; +} + +static double +ComputeTransform3DMatrixDistance(const Matrix4x4& aMatrix1, + const Matrix4x4& aMatrix2) +{ + Point3D scale1(1, 1, 1); + Point3D translate1; + Point4D perspective1(0, 0, 0, 1); + gfxQuaternion rotate1; + nsStyleTransformMatrix::ShearArray shear1{0.0f, 0.0f, 0.0f}; + Decompose3DMatrix(aMatrix1, scale1, shear1, rotate1, translate1, + perspective1); + + Point3D scale2(1, 1, 1); + Point3D translate2; + Point4D perspective2(0, 0, 0, 1); + gfxQuaternion rotate2; + nsStyleTransformMatrix::ShearArray shear2{0.0f, 0.0f, 0.0f}; + Decompose3DMatrix(aMatrix2, scale2, shear2, rotate2, translate2, + perspective2); + + // Note: + // 1. Shear factor is the tangent value of shear angle, so we need to + // call atan() to get the angle. + // 2. We use the same way to get the rotate angle of two quaternion vectors as + // what we do for rotate3d. + const Point3D diffTranslate = translate2 - translate1; + const Point3D diffScale = scale2 - scale1; + const Point3D diffShear(atan(shear2[ShearType::XYSHEAR]) - + atan(shear1[ShearType::XYSHEAR]), + atan(shear2[ShearType::XZSHEAR]) - + atan(shear1[ShearType::XZSHEAR]), + atan(shear2[ShearType::YZSHEAR]) - + atan(shear1[ShearType::YZSHEAR])); + const Point4D diffPerspective = perspective2 - perspective1; + const double dot = clamped(rotate1.DotProduct(rotate2), -1.0, 1.0); + const double diffRotate = 2.0 * acos(dot); + // Returns the sum of squares because we will take a square root in + // ComputeTransformListDistance. + return diffTranslate.DotProduct(diffTranslate) + + diffScale.DotProduct(diffScale) + + diffPerspective.DotProduct(diffPerspective) + + diffShear.DotProduct(diffShear) + + diffRotate * diffRotate; +} + +static double +ComputeTransformDistance(nsCSSValue::Array* aArray1, + nsCSSValue::Array* aArray2) +{ + MOZ_ASSERT(aArray1, "aArray1 should be non-null."); + MOZ_ASSERT(aArray2, "aArray2 should be non-null."); + + // Normalize translate and scale functions to equivalent "translate3d" and + // "scale3d" functions. + RefPtr<nsCSSValue::Array> a1 = ToPrimitive(aArray1), + a2 = ToPrimitive(aArray2); + nsCSSKeyword tfunc = nsStyleTransformMatrix::TransformFunctionOf(a1); + MOZ_ASSERT(nsStyleTransformMatrix::TransformFunctionOf(a2) == tfunc); + + double distance = 0.0; + switch (tfunc) { + case eCSSKeyword_translate3d: { + MOZ_ASSERT(a1->Count() == 4, "unexpected count"); + MOZ_ASSERT(a2->Count() == 4, "unexpected count"); + + nsCSSValue x, y, z; + AddTransformTranslate(1.0, a2->Item(1), -1.0, a1->Item(1), x); + AddTransformTranslate(1.0, a2->Item(2), -1.0, a1->Item(2), y); + AddTransformTranslate(1.0, a2->Item(3), -1.0, a1->Item(3), z); + // Drop percent part because we only compute distance by computed values. + double c1 = ExtractCalcValue(x).mLength; + double c2 = ExtractCalcValue(y).mLength; + double c3 = z.GetFloatValue(); + distance = c1 * c1 + c2 * c2 + c3 * c3; + break; + } + case eCSSKeyword_scale3d: { + MOZ_ASSERT(a1->Count() == 4, "unexpected count"); + MOZ_ASSERT(a2->Count() == 4, "unexpected count"); + + auto ComputeScaleDiff = [](const nsCSSValue& aValue1, + const nsCSSValue& aValue2) { + float v1 = aValue1.GetFloatValue(); + float v2 = aValue2.GetFloatValue(); + return EnsureNotNan(v2 - v1); + }; + + double c1 = ComputeScaleDiff(a1->Item(1), a2->Item(1)); + double c2 = ComputeScaleDiff(a1->Item(2), a2->Item(2)); + double c3 = ComputeScaleDiff(a1->Item(3), a2->Item(3)); + distance = c1 * c1 + c2 * c2 + c3 * c3; + break; + } + case eCSSKeyword_skew: { + MOZ_ASSERT(a1->Count() == 2 || a1->Count() == 3, "unexpected count"); + MOZ_ASSERT(a2->Count() == 2 || a2->Count() == 3, "unexpected count"); + + const nsCSSValue zero(0.0f, eCSSUnit_Radian); + nsCSSValue x, y; + AddCSSValueAngle(1.0, a2->Item(1), -1.0, a1->Item(1), x); + AddCSSValueAngle(1.0, a2->Count() == 3 ? a2->Item(2) : zero, + -1.0, a1->Count() == 3 ? a1->Item(2) : zero, + y); + distance = x.GetAngleValueInRadians() * x.GetAngleValueInRadians() + + y.GetAngleValueInRadians() * y.GetAngleValueInRadians(); + break; + } + case eCSSKeyword_skewx: + case eCSSKeyword_skewy: + case eCSSKeyword_rotate: + case eCSSKeyword_rotatex: + case eCSSKeyword_rotatey: + case eCSSKeyword_rotatez: { + MOZ_ASSERT(a1->Count() == 2, "unexpected count"); + MOZ_ASSERT(a2->Count() == 2, "unexpected count"); + + nsCSSValue angle; + AddCSSValueAngle(1.0, a2->Item(1), -1.0, a1->Item(1), angle); + distance = angle.GetAngleValueInRadians() * + angle.GetAngleValueInRadians(); + break; + } + case eCSSKeyword_rotate3d: { + MOZ_ASSERT(a1->Count() == 5, "unexpected count"); + MOZ_ASSERT(a2->Count() == 5, "unexpected count"); + + Point3D vector1(a1->Item(1).GetFloatValue(), + a1->Item(2).GetFloatValue(), + a1->Item(3).GetFloatValue()); + vector1.Normalize(); + Point3D vector2(a2->Item(1).GetFloatValue(), + a2->Item(2).GetFloatValue(), + a2->Item(3).GetFloatValue()); + vector2.Normalize(); + + if (vector1 == vector2) { + // Handle rotate3d with matched (normalized) vectors. + nsCSSValue angle; + AddCSSValueAngle(1.0, a2->Item(4), -1.0, a1->Item(4), angle); + distance = angle.GetAngleValueInRadians() * + angle.GetAngleValueInRadians(); + } else { + // Use quaternion vectors to get the angle difference. Both q1 and q2 + // are unit vectors, so we can get their angle difference by + // cos(theta/2) = (q1 dot q2) / (|q1| * |q2|) = q1 dot q2. + gfxQuaternion q1(vector1, a1->Item(4).GetAngleValueInRadians()); + gfxQuaternion q2(vector2, a2->Item(4).GetAngleValueInRadians()); + distance = 2.0 * acos(clamped(q1.DotProduct(q2), -1.0, 1.0)); + distance = distance * distance; + } + break; + } + case eCSSKeyword_perspective: { + MOZ_ASSERT(a1->Count() == 2, "unexpected count"); + MOZ_ASSERT(a2->Count() == 2, "unexpected count"); + + // We convert a perspective function into an equivalent matrix3d, and + // then do matrix decomposition to get the distance. + // Why don't we just subtract one perspective depth from the other? + // I think it's better to follow the logic of our interpolation, + // which does linear interpolation between two decomposed perspective + // vectors. + // e.g. + // Do interpolation between perspective(100px) and perspective(1000px). + // 1) Convert them into matrix3d, and then do matrix decomposition: + // perspective vector 1: perspective(0, 0, -1/100, 1); + // perspective vector 2: perspective(0, 0, -1/1000, 1); + // 2) Do linear interpolation between these two vectors. + // Therefore, we use the same rule to get the distance as what we do for + // matrix3d. + + using nsStyleTransformMatrix::ApplyPerspectiveToMatrix; + Matrix4x4 m1; + ApplyPerspectiveToMatrix(m1, a1->Item(1).GetFloatValue()); + Matrix4x4 m2; + ApplyPerspectiveToMatrix(m2, a2->Item(1).GetFloatValue()); + + distance = ComputeTransform3DMatrixDistance(m1, m2); + break; + } + case eCSSKeyword_matrix: { + MOZ_ASSERT(a1->Count() == 7, "unexpected count"); + MOZ_ASSERT(a2->Count() == 7, "unexpected count"); + + distance = ComputeTransform2DMatrixDistance( + nsStyleTransformMatrix::CSSValueArrayTo2DMatrix(a1), + nsStyleTransformMatrix::CSSValueArrayTo2DMatrix(a2)); + break; + } + case eCSSKeyword_matrix3d: { + MOZ_ASSERT(a1->Count() == 17, "unexpected count"); + MOZ_ASSERT(a2->Count() == 17, "unexpected count"); + + distance = ComputeTransform3DMatrixDistance( + nsStyleTransformMatrix::CSSValueArrayTo3DMatrix(a1), + nsStyleTransformMatrix::CSSValueArrayTo3DMatrix(a2)); + break; + } + case eCSSKeyword_interpolatematrix: + default: + MOZ_ASSERT_UNREACHABLE("Unsupported transform function"); + break; + } + return distance; +} + +static double +ComputeTransformListDistance(const nsCSSValueList* aList1, + const nsCSSValueList* aList2) +{ + MOZ_ASSERT(aList1, "aList1 should be non-null."); + MOZ_ASSERT(aList2, "aList2 should be non-null."); + + double distance = 0.0; + do { + distance += ComputeTransformDistance(aList1->mValue.GetArrayValue(), + aList2->mValue.GetArrayValue()); + aList1 = aList1->mNext; + aList2 = aList2->mNext; + MOZ_ASSERT(!aList1 == !aList2, + "aList1 and aList2 should have the same length."); + } while (aList1); + return sqrt(distance); +} + +static double +ComputeMismatchedTransfromListDistance(const nsCSSValueList* aList1, + const nsCSSValueList* aList2, + nsStyleContext* aStyleContext) +{ + // We need nsStyleContext and nsPresContext to compute calc() values while + // processing the translate part of transforms. + if (!aStyleContext) { + return 0.0; + } + + RuleNodeCacheConditions dontCare; + bool dontCareBool; + nsStyleTransformMatrix::TransformReferenceBox emptyRefBox; + + Matrix4x4 m1 = nsStyleTransformMatrix::ReadTransforms( + aList1, + aStyleContext, + aStyleContext->PresContext(), + dontCare, + emptyRefBox, + nsPresContext::AppUnitsPerCSSPixel(), + &dontCareBool); + Matrix4x4 m2 = nsStyleTransformMatrix::ReadTransforms( + aList2, + aStyleContext, + aStyleContext->PresContext(), + dontCare, + emptyRefBox, + nsPresContext::AppUnitsPerCSSPixel(), + &dontCareBool); + return sqrt(ComputeTransform3DMatrixDistance(m1, m2)); +} + +bool +StyleAnimationValue::ComputeDistance(nsCSSPropertyID aProperty, + const StyleAnimationValue& aStartValue, + const StyleAnimationValue& aEndValue, + nsStyleContext* aStyleContext, + 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: + case eUnit_URL: + case eUnit_DiscreteCSSValue: + 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: { + float startFloat = aStartValue.GetFloatValue(); + float endFloat = aEndValue.GetFloatValue(); + aDistance = Abs(double(endFloat) - double(startFloat)); + return true; + } + case eUnit_Color: { + aDistance = ComputeColorDistance(ExtractColor(aStartValue), + ExtractColor(aEndValue)); + return true; + } + case eUnit_CurrentColor: { + aDistance = 0; + return true; + } + case eUnit_ComplexColor: { + ComplexColorData color1 = ExtractComplexColor(aStartValue); + ComplexColorData color2 = ExtractComplexColor(aEndValue); + // Common case is interpolating between a color and a currentcolor + if (color1.IsNumericColor() && color2.IsCurrentColor()) { + double dist = ComputeColorDistance(color1.mColor, NS_RGBA(0, 0, 0, 0)); + aDistance = sqrt(dist * dist + 1); + return true; + } + if (color1.IsCurrentColor() && color2.IsNumericColor()) { + double dist = ComputeColorDistance(NS_RGBA(0, 0, 0, 0), color2.mColor); + aDistance = sqrt(dist * dist + 1); + return true; + } + // If we ever reach here, we may want to use the code in + // bug 1299741 comment 79 to compute it. + MOZ_ASSERT_UNREACHABLE("We shouldn't get here as we only call " + "ComputeDistance on pre-interpolation values"); + aDistance = 0.0; + return true; + } + case eUnit_Calc: { + PixelCalcValue v1 = ExtractCalcValue(aStartValue); + PixelCalcValue 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_ObjectPosition: { + const nsCSSValue* position1 = aStartValue.GetCSSValueValue(); + const nsCSSValue* position2 = aEndValue.GetCSSValueValue(); + double squareDistance = + CalcPositionSquareDistance(*position1, + *position2); + aDistance = sqrt(squareDistance); + 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: { + PixelCalcValue v1 = ExtractCalcValue(pair1->*member); + PixelCalcValue v2 = ExtractCalcValue(pair2->*member); + float difflen = v2.mLength - v1.mLength; + float diffpct = v2.mPercent - v1.mPercent; + diffsquared = difflen * difflen + diffpct * diffpct; + break; + } + default: + MOZ_ASSERT(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: { + PixelCalcValue v1 = ExtractCalcValue(triplet1->*member); + PixelCalcValue 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: + MOZ_ASSERT(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]; + MOZ_ASSERT((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: + MOZ_ASSERT(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. + StyleAnimationValue 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(); + + MOZ_ASSERT(!list1 == !list2, "lists should be same length"); + while (list1) { + const nsCSSValue &val1 = list1->mValue; + const nsCSSValue &val2 = list2->mValue; + + MOZ_ASSERT(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: + MOZ_ASSERT(false, "unexpected unit"); + return false; + } + squareDistance += diff * diff; + + list1 = list1->mNext; + list2 = list2->mNext; + MOZ_ASSERT(!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. + StyleAnimationValue 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; + MOZ_ASSERT(!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) { + MOZ_ASSERT(array1->Item(i).GetUnit() == eCSSUnit_Pixel, + "unexpected unit"); + MOZ_ASSERT(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 + MOZ_ASSERT(((color1.IsNumericColorUnit() && + color2.IsNumericColorUnit()) || + (color1.GetUnit() == color2.GetUnit())) && + inset1 == inset2, + "AddWeighted should have failed"); + } +#endif + + if (color1.GetUnit() != eCSSUnit_Null) { + double colorDistance = ComputeColorDistance(color1.GetColorValue(), + color2.GetColorValue()); + squareDistance += colorDistance * colorDistance; + } + + shadow1 = shadow1->mNext; + shadow2 = shadow2->mNext; + MOZ_ASSERT(!shadow1 == !shadow2, "lists should be same length"); + } + aDistance = sqrt(squareDistance); + return true; + } + case eUnit_Shape: + aDistance = ComputeShapeDistance(aProperty, + aStartValue.GetCSSValueArrayValue(), + aEndValue.GetCSSValueArrayValue()); + return true; + + case eUnit_Filter: + // Bug 1286151: Support paced animations for filter function + // interpolation. + return false; + + case eUnit_Transform: { + // FIXME: We don't have an official spec to define the distance of + // two transform lists, but paced spacing (defined in Web Animations API) + // needs this, so we implement this according to the concept of the + // interpolation of two transform lists. + // Issue: https://www.w3.org/TR/web-animations-1/#issue-789f9fd1 + + const nsCSSValueList* list1 = + aStartValue.GetCSSValueSharedListValue()->mHead; + const nsCSSValueList* list2 = + aEndValue.GetCSSValueSharedListValue()->mHead; + MOZ_ASSERT(list1); + MOZ_ASSERT(list2); + + if (list1->mValue.GetUnit() == eCSSUnit_None && + list2->mValue.GetUnit() == eCSSUnit_None) { + // Both none, nothing happens. + aDistance = 0.0; + } else if (list1->mValue.GetUnit() == eCSSUnit_None) { + nsAutoPtr<nsCSSValueList> none(AddTransformLists(0, list2, 0, list2)); + aDistance = ComputeTransformListDistance(none, list2); + } else if (list2->mValue.GetUnit() == eCSSUnit_None) { + nsAutoPtr<nsCSSValueList> none(AddTransformLists(0, list1, 0, list1)); + aDistance = ComputeTransformListDistance(list1, none); + } else { + 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 the transform function types don't match or + // the lengths don't match. + aDistance = + ComputeMismatchedTransfromListDistance(list1, list2, aStyleContext); + } else { + aDistance = ComputeTransformListDistance(list1, list2); + } + } + return true; + } + case eUnit_BackgroundPositionCoord: { + const nsCSSValueList *position1 = aStartValue.GetCSSValueListValue(); + const nsCSSValueList *position2 = aEndValue.GetCSSValueListValue(); + + double squareDistance = 0.0; + MOZ_ASSERT(!position1 == !position2, "lists should be same length"); + + while (position1 && position2) { + squareDistance += CalcPositionCoordSquareDistance(position1->mValue, + position2->mValue); + 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: { + PixelCalcValue val1 = ExtractCalcValue(v1); + PixelCalcValue 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; + } + } + + MOZ_ASSERT(false, "Can't compute distance using the given common unit"); + return false; +} + +static inline void +AddCSSValueNumber(double aCoeff1, const nsCSSValue &aValue1, + double aCoeff2, const nsCSSValue &aValue2, + nsCSSValue &aResult, uint32_t aValueRestrictions = 0) +{ + MOZ_ASSERT(aValue1.GetUnit() == eCSSUnit_Number, "unexpected unit"); + MOZ_ASSERT(aValue2.GetUnit() == eCSSUnit_Number, "unexpected unit"); + aResult.SetFloatValue(RestrictValue(aValueRestrictions, + aCoeff1 * aValue1.GetFloatValue() + + aCoeff2 * aValue2.GetFloatValue()), + eCSSUnit_Number); +} + +static inline float +GetNumberOrPercent(const nsCSSValue &aValue) +{ + nsCSSUnit unit = aValue.GetUnit(); + MOZ_ASSERT(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); +} + +enum class ColorAdditionType { + Clamped, // Clamp each color channel after adding. + Unclamped // Do not clamp color channels after adding. +}; + +// Unclamped AddWeightedColors. +static RGBAColorData +AddWeightedColors(double aCoeff1, const RGBAColorData& aValue1, + double aCoeff2, const RGBAColorData& aValue2) +{ + float factor1 = aValue1.mA * aCoeff1; + float factor2 = aValue2.mA * aCoeff2; + float resultA = factor1 + factor2; + if (resultA <= 0.0) { + return {0, 0, 0, 0}; + } + + if (resultA > 1.0) { + resultA = 1.0; + } + + float resultFactor = 1.0f / resultA; + return RGBAColorData( + (aValue1.mR * factor1 + aValue2.mR * factor2) * resultFactor, + (aValue1.mG * factor1 + aValue2.mG * factor2) * resultFactor, + (aValue1.mB * factor1 + aValue2.mB * factor2) * resultFactor, + resultA); +} + +// Multiplies |aValue| color by |aDilutionRation|. +static nscolor +DiluteColor(const RGBAColorData& aValue, double aDilutionRatio) +{ + MOZ_ASSERT(aDilutionRatio >= 0.0 && aDilutionRatio <= 1.0, + "Dilution ratio should be in [0, 1]"); + float resultA = aValue.mA * aDilutionRatio; + return resultA <= 0.0 ? NS_RGBA(0, 0, 0, 0) + : aValue.WithAlpha(resultA).ToColor(); +} + +// Clamped AddWeightedColors. +static nscolor +AddWeightedColorsAndClamp(double aCoeff1, const RGBAColorData& aValue1, + double aCoeff2, const RGBAColorData& aValue2) +{ + // We are using AddWeighted() with a zero aCoeff2 for colors to + // pretend AddWeighted() against transparent color, i.e. rgba(0, 0, 0, 0). + // But unpremultiplication in AddWeightedColors() does not work well + // for such cases, so we use another function named DiluteColor() which + // has a similar logic to AddWeightedColors(). + return aCoeff2 == 0.0 + ? DiluteColor(aValue1, aCoeff1) + : AddWeightedColors(aCoeff1, aValue1, aCoeff2, aValue2).ToColor(); +} + +void +AppendToCSSValueList(UniquePtr<nsCSSValueList>& aHead, + UniquePtr<nsCSSValueList>&& aValueToAppend, + nsCSSValueList** aTail) +{ + MOZ_ASSERT(!aHead == !*aTail, + "Can't have head w/o tail, & vice versa"); + + if (!aHead) { + aHead = Move(aValueToAppend); + *aTail = aHead.get(); + } else { + (*aTail) = (*aTail)->mNext = aValueToAppend.release(); + } +} + +static UniquePtr<nsCSSValueList> +AddWeightedShadowItems(double aCoeff1, const nsCSSValue &aValue1, + double aCoeff2, const nsCSSValue &aValue2, + ColorAdditionType aColorAdditionType) +{ + // X, Y, Radius, Spread, Color, Inset + MOZ_ASSERT(aValue1.GetUnit() == eCSSUnit_Array, + "wrong unit"); + MOZ_ASSERT(aValue2.GetUnit() == eCSSUnit_Array, + "wrong unit"); + nsCSSValue::Array *array1 = aValue1.GetArrayValue(); + nsCSSValue::Array *array2 = aValue2.GetArrayValue(); + RefPtr<nsCSSValue::Array> 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& colorValue1 = array1->Item(4); + const nsCSSValue& colorValue2 = array2->Item(4); + const nsCSSValue& inset1 = array1->Item(5); + const nsCSSValue& inset2 = array2->Item(5); + if ((colorValue1.GetUnit() != colorValue2.GetUnit() && + (!colorValue1.IsNumericColorUnit() || + !colorValue2.IsNumericColorUnit())) || + inset1.GetUnit() != inset2.GetUnit()) { + // We don't know how to animate between color and no-color, or + // between inset and not-inset. + // NOTE: In case when both colors' units are eCSSUnit_Null, that means + // neither color value was specified, so we can interpolate. + return nullptr; + } + + if (colorValue1.GetUnit() != eCSSUnit_Null) { + RGBAColorData color1 = ExtractColor(colorValue1); + RGBAColorData color2 = ExtractColor(colorValue2); + if (aColorAdditionType == ColorAdditionType::Clamped) { + resultArray->Item(4).SetColorValue( + AddWeightedColorsAndClamp(aCoeff1, color1, aCoeff2, color2)); + } else { + resultArray->Item(4).SetRGBAColorValue( + AddWeightedColors(aCoeff1, color1, aCoeff2, color2)); + } + } + + MOZ_ASSERT(inset1 == inset2, "should match"); + resultArray->Item(5) = inset1; + + auto resultItem = MakeUnique<nsCSSValueList>(); + resultItem->mValue.SetArrayValue(resultArray, eCSSUnit_Array); + return resultItem; +} + +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. + MOZ_ASSERT(aValue1.GetUnit() == eCSSUnit_Number, "unexpected unit"); + MOZ_ASSERT(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(EnsureNotNan(result + 1.0f), eCSSUnit_Number); +} + +/* static */ already_AddRefed<nsCSSValue::Array> +StyleAnimationValue::AppendTransformFunction(nsCSSKeyword aTransformFunction, + nsCSSValueList**& aListTail) +{ + RefPtr<nsCSSValue::Array> arr = AppendFunction(aTransformFunction); + nsCSSValueList *item = new nsCSSValueList; + item->mValue.SetArrayValue(arr, eCSSUnit_Function); + + *aListTail = item; + aListTail = &item->mNext; + + return arr.forget(); +} + +template<typename T> +T InterpolateNumerically(const T& aOne, const T& aTwo, double aCoeff) +{ + return aOne + (aTwo - aOne) * aCoeff; +} + + +/* static */ Matrix4x4 +StyleAnimationValue::InterpolateTransformMatrix(const Matrix4x4 &aMatrix1, + const Matrix4x4 &aMatrix2, + double aProgress) +{ + // Decompose both matrices + + // TODO: What do we do if one of these returns false (singular matrix) + Point3D scale1(1, 1, 1), translate1; + Point4D perspective1(0, 0, 0, 1); + gfxQuaternion rotate1; + nsStyleTransformMatrix::ShearArray shear1{0.0f, 0.0f, 0.0f}; + + Point3D scale2(1, 1, 1), translate2; + Point4D perspective2(0, 0, 0, 1); + gfxQuaternion rotate2; + nsStyleTransformMatrix::ShearArray shear2{0.0f, 0.0f, 0.0f}; + + Matrix 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 + Matrix4x4 result; + + Point4D perspective = + InterpolateNumerically(perspective1, perspective2, aProgress); + result.SetTransposedVector(3, perspective); + + Point3D translate = + InterpolateNumerically(translate1, translate2, aProgress); + result.PreTranslate(translate.x, translate.y, translate.z); + + gfxQuaternion q3 = rotate1.Slerp(rotate2, aProgress); + Matrix4x4 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[ShearType::YZSHEAR], + shear2[ShearType::YZSHEAR], + aProgress); + if (yzshear != 0.0) { + result.SkewYZ(yzshear); + } + + float xzshear = + InterpolateNumerically(shear1[ShearType::XZSHEAR], + shear2[ShearType::XZSHEAR], + aProgress); + if (xzshear != 0.0) { + result.SkewXZ(xzshear); + } + + float xyshear = + InterpolateNumerically(shear1[ShearType::XYSHEAR], + shear2[ShearType::XYSHEAR], + aProgress); + if (xyshear != 0.0) { + result.SkewXY(xyshear); + } + + Point3D scale = + InterpolateNumerically(scale1, scale2, aProgress); + if (scale != Point3D(1.0, 1.0, 1.0)) { + result.PreScale(scale.x, scale.y, scale.z); + } + + return result; +} + +static nsCSSValueList* +AddDifferentTransformLists(double aCoeff1, const nsCSSValueList* aList1, + double aCoeff2, const nsCSSValueList* aList2) +{ + nsAutoPtr<nsCSSValueList> result; + nsCSSValueList **resultTail = getter_Transfers(result); + + RefPtr<nsCSSValue::Array> arr; + arr = + StyleAnimationValue::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 UniquePtr<nsCSSValueList> +AddWeightedFilterFunctionImpl(double aCoeff1, const nsCSSValueList* aList1, + double aCoeff2, const nsCSSValueList* aList2, + ColorAdditionType aColorAdditionType) +{ + // AddWeightedFilterFunction should be our only caller, and it should ensure + // that both args are non-null. + MOZ_ASSERT(aList1, "expected filter list"); + MOZ_ASSERT(aList2, "expected filter list"); + MOZ_ASSERT(aList1->mValue.GetUnit() == eCSSUnit_Function, + "expected function"); + MOZ_ASSERT(aList2->mValue.GetUnit() == eCSSUnit_Function, + "expected function"); + RefPtr<nsCSSValue::Array> a1 = aList1->mValue.GetArrayValue(), + a2 = aList2->mValue.GetArrayValue(); + nsCSSKeyword filterFunction = a1->Item(0).GetKeywordValue(); + if (filterFunction != a2->Item(0).GetKeywordValue()) { + return nullptr; // Can't add two filters of different types. + } + + auto resultList = MakeUnique<nsCSSValueList>(); + nsCSSValue::Array* result = + resultList->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 nullptr; + } + break; + } + case eCSSKeyword_grayscale: + case eCSSKeyword_invert: + case eCSSKeyword_sepia: + initialVal = 0.0f; + MOZ_FALLTHROUGH; + 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: { + MOZ_ASSERT(!funcArg1.GetListValue()->mNext && + !funcArg2.GetListValue()->mNext, + "drop-shadow filter func doesn't support lists"); + UniquePtr<nsCSSValueList> shadowValue = + AddWeightedShadowItems(aCoeff1, + funcArg1.GetListValue()->mValue, + aCoeff2, + funcArg2.GetListValue()->mValue, + aColorAdditionType); + if (!shadowValue) { + return nullptr; + } + resultArg.AdoptListValue(Move(shadowValue)); + break; + } + default: + MOZ_ASSERT(false, "unknown filter function"); + return nullptr; + } + + return resultList; +} + +static UniquePtr<nsCSSValueList> +AddWeightedFilterFunction(double aCoeff1, const nsCSSValueList* aList1, + double aCoeff2, const nsCSSValueList* aList2, + ColorAdditionType aColorAdditionType) +{ + MOZ_ASSERT(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, AddWeightedFilterFunctionImpl can assume + // its args are non-null. + if (!aList1) { + return AddWeightedFilterFunctionImpl(aCoeff2, aList2, 0, aList2, + aColorAdditionType); + } + if (!aList2) { + return AddWeightedFilterFunctionImpl(aCoeff1, aList1, 0, aList1, + aColorAdditionType); + } + + return AddWeightedFilterFunctionImpl(aCoeff1, aList1, aCoeff2, aList2, + aColorAdditionType); +} + +static inline uint32_t +ShapeArgumentCount(nsCSSKeyword aShapeFunction) +{ + switch (aShapeFunction) { + case eCSSKeyword_circle: + return 2; // radius and center point + case eCSSKeyword_polygon: + return 2; // fill rule and a list of points + case eCSSKeyword_ellipse: + return 3; // two radii and center point + case eCSSKeyword_inset: + return 5; // four edge offsets and a list of corner radii + default: + MOZ_ASSERT_UNREACHABLE("Unknown shape type"); + return 0; + } +} + +static void +AddPositions(double aCoeff1, const nsCSSValue& aPos1, + double aCoeff2, const nsCSSValue& aPos2, + nsCSSValue& aResultPos) +{ + MOZ_ASSERT(aPos1.GetUnit() == eCSSUnit_Array && + aPos2.GetUnit() == eCSSUnit_Array, + "Args should be CSS <position>s, encoded as arrays"); + + const nsCSSValue::Array* posArray1 = aPos1.GetArrayValue(); + const nsCSSValue::Array* posArray2 = aPos2.GetArrayValue(); + MOZ_ASSERT(posArray1->Count() == 4 && posArray2->Count() == 4, + "CSSParserImpl::ParsePositionValue creates an array of length " + "4 - how did we get here?"); + + nsCSSValue::Array* resultPosArray = nsCSSValue::Array::Create(4); + aResultPos.SetArrayValue(resultPosArray, eCSSUnit_Array); + + // Only iterate over elements 1 and 3. The <position> is 'uncomputed' to + // only those elements. See also the comment in SetPositionValue. + for (size_t i = 1; i < 4; i += 2) { + const nsCSSValue& v1 = posArray1->Item(i); + const nsCSSValue& v2 = posArray2->Item(i); + nsCSSValue& vr = resultPosArray->Item(i); + AddCSSValueCanonicalCalc(aCoeff1, v1, + aCoeff2, v2, vr); + } +} + +static Maybe<nsCSSValuePair> +AddCSSValuePair(nsCSSPropertyID aProperty, uint32_t aRestrictions, + double aCoeff1, const nsCSSValuePair* aPair1, + double aCoeff2, const nsCSSValuePair* aPair2) +{ + MOZ_ASSERT(aPair1, "expected pair"); + MOZ_ASSERT(aPair2, "expected pair"); + + Maybe<nsCSSValuePair> result; + nsCSSUnit unit[2]; + unit[0] = GetCommonUnit(aProperty, aPair1->mXValue.GetUnit(), + aPair2->mXValue.GetUnit()); + unit[1] = GetCommonUnit(aProperty, aPair1->mYValue.GetUnit(), + aPair2->mYValue.GetUnit()); + if (unit[0] == eCSSUnit_Null || unit[1] == eCSSUnit_Null || + unit[0] == eCSSUnit_URL || unit[0] == eCSSUnit_Enumerated) { + return result; // Nothing() (returning |result| for RVO) + } + + result.emplace(); + + static nsCSSValue nsCSSValuePair::* const pairValues[2] = { + &nsCSSValuePair::mXValue, &nsCSSValuePair::mYValue + }; + for (uint32_t i = 0; i < 2; ++i) { + nsCSSValue nsCSSValuePair::*member = pairValues[i]; + if (!AddCSSValuePixelPercentCalc(aRestrictions, unit[i], + aCoeff1, aPair1->*member, + aCoeff2, aPair2->*member, + result.ref().*member) ) { + MOZ_ASSERT(false, "unexpected unit"); + result.reset(); + return result; // Nothing() (returning |result| for RVO) + } + } + + return result; +} + +static UniquePtr<nsCSSValuePairList> +AddCSSValuePairList(nsCSSPropertyID aProperty, + double aCoeff1, const nsCSSValuePairList* aList1, + double aCoeff2, const nsCSSValuePairList* aList2) +{ + MOZ_ASSERT(aList1, "Can't add a null list"); + MOZ_ASSERT(aList2, "Can't add a null list"); + + auto result = MakeUnique<nsCSSValuePairList>(); + nsCSSValuePairList* resultPtr = result.get(); + + do { + 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 = aList1->*(pairListValues[i]); + const nsCSSValue& v2 = aList2->*(pairListValues[i]); + + nsCSSValue& vr = resultPtr->*(pairListValues[i]); + nsCSSUnit unit = + GetCommonUnit(aProperty, v1.GetUnit(), v2.GetUnit()); + if (unit == eCSSUnit_Null) { + return nullptr; + } + if (!AddCSSValuePixelPercentCalc(restrictions, unit, + aCoeff1, v1, + aCoeff2, v2, vr)) { + if (v1 != v2) { + return nullptr; + } + vr = v1; + } + } + aList1 = aList1->mNext; + aList2 = aList2->mNext; + if (!aList1 || !aList2) { + break; + } + resultPtr->mNext = new nsCSSValuePairList; + resultPtr = resultPtr->mNext; + } while (aList1 && aList2); + + if (aList1 || aList2) { + return nullptr; // We can't interpolate lists of different lengths + } + + return result; +} + +static already_AddRefed<nsCSSValue::Array> +AddShapeFunction(nsCSSPropertyID aProperty, + double aCoeff1, const nsCSSValue::Array* aArray1, + double aCoeff2, const nsCSSValue::Array* aArray2, + Restrictions aRestriction) +{ + MOZ_ASSERT(aArray1 && aArray1->Count() == 2, "expected shape function"); + MOZ_ASSERT(aArray2 && aArray2->Count() == 2, "expected shape function"); + MOZ_ASSERT(aArray1->Item(0).GetUnit() == eCSSUnit_Function, + "expected function"); + MOZ_ASSERT(aArray2->Item(0).GetUnit() == eCSSUnit_Function, + "expected function"); + MOZ_ASSERT(aArray1->Item(1).GetUnit() == eCSSUnit_Enumerated, + "expected geometry-box"); + MOZ_ASSERT(aArray2->Item(1).GetUnit() == eCSSUnit_Enumerated, + "expected geometry-box"); + + if (aArray1->Item(1).GetIntValue() != aArray2->Item(1).GetIntValue()) { + return nullptr; // Both shapes must use the same reference box. + } + + const nsCSSValue::Array* func1 = aArray1->Item(0).GetArrayValue(); + const nsCSSValue::Array* func2 = aArray2->Item(0).GetArrayValue(); + nsCSSKeyword shapeFuncName = func1->Item(0).GetKeywordValue(); + if (shapeFuncName != func2->Item(0).GetKeywordValue()) { + return nullptr; // Can't add two shapes of different types. + } + + RefPtr<nsCSSValue::Array> result = nsCSSValue::Array::Create(2); + + nsCSSValue::Array* resultFuncArgs = + result->Item(0).InitFunction(shapeFuncName, + ShapeArgumentCount(shapeFuncName)); + switch (shapeFuncName) { + case eCSSKeyword_ellipse: + // Add ellipses' |ry| values (but fail if we encounter an enum): + if (!AddCSSValuePixelPercentCalc(aRestriction == Restrictions::Enable + ? CSS_PROPERTY_VALUE_NONNEGATIVE + : 0, + GetCommonUnit(aProperty, + func1->Item(2).GetUnit(), + func2->Item(2).GetUnit()), + aCoeff1, func1->Item(2), + aCoeff2, func2->Item(2), + resultFuncArgs->Item(2))) { + return nullptr; + } + MOZ_FALLTHROUGH; // to handle rx and center point + case eCSSKeyword_circle: { + // Add circles' |r| (or ellipses' |rx|) values: + if (!AddCSSValuePixelPercentCalc(aRestriction == Restrictions::Enable + ? CSS_PROPERTY_VALUE_NONNEGATIVE + : 0, + GetCommonUnit(aProperty, + func1->Item(1).GetUnit(), + func2->Item(1).GetUnit()), + aCoeff1, func1->Item(1), + aCoeff2, func2->Item(1), + resultFuncArgs->Item(1))) { + return nullptr; + } + // Add center points (defined as a <position>). + size_t posIndex = shapeFuncName == eCSSKeyword_circle ? 2 : 3; + AddPositions(aCoeff1, func1->Item(posIndex), + aCoeff2, func2->Item(posIndex), + resultFuncArgs->Item(posIndex)); + break; + } + case eCSSKeyword_polygon: { + // Add polygons' corresponding points (if the fill rule matches): + int32_t fillRule = func1->Item(1).GetIntValue(); + if (fillRule != func2->Item(1).GetIntValue()) { + return nullptr; // can't interpolate between different fill rules + } + resultFuncArgs->Item(1).SetIntValue(fillRule, eCSSUnit_Enumerated); + + const nsCSSValuePairList* points1 = func1->Item(2).GetPairListValue(); + const nsCSSValuePairList* points2 = func2->Item(2).GetPairListValue(); + UniquePtr<nsCSSValuePairList> resultPoints = + AddCSSValuePairList(aProperty, aCoeff1, points1, aCoeff2, points2); + if (!resultPoints) { + return nullptr; + } + resultFuncArgs->Item(2).AdoptPairListValue(Move(resultPoints)); + break; + } + case eCSSKeyword_inset: { + MOZ_ASSERT(func1->Count() == 6 && func2->Count() == 6, + "Update for CSSParserImpl::ParseInsetFunction changes"); + // Items 1-4 are respectively the top, right, bottom and left offsets + // from the reference box. + for (size_t i = 1; i <= 4; ++i) { + if (!AddCSSValuePixelPercentCalc(aRestriction == Restrictions::Enable + ? CSS_PROPERTY_VALUE_NONNEGATIVE + : 0, + GetCommonUnit(aProperty, + func1->Item(i).GetUnit(), + func2->Item(i).GetUnit()), + aCoeff1, func1->Item(i), + aCoeff2, func2->Item(i), + resultFuncArgs->Item(i))) { + return nullptr; + } + } + // Item 5 contains the radii of the rounded corners for the inset + // rectangle. + MOZ_ASSERT(func1->Item(5).GetUnit() == eCSSUnit_Array && + func2->Item(5).GetUnit() == eCSSUnit_Array, + "Expected two arrays"); + const nsCSSValue::Array* radii1 = func1->Item(5).GetArrayValue(); + const nsCSSValue::Array* radii2 = func2->Item(5).GetArrayValue(); + MOZ_ASSERT(radii1->Count() == 4 && radii2->Count() == 4); + nsCSSValue::Array* resultRadii = nsCSSValue::Array::Create(4); + resultFuncArgs->Item(5).SetArrayValue(resultRadii, eCSSUnit_Array); + // We use an arbitrary border-radius property here to get the appropriate + // restrictions for radii since this is a <border-radius> value. + uint32_t restrictions = + aRestriction == Restrictions::Enable + ? nsCSSProps::ValueRestrictions(eCSSProperty_border_top_left_radius) + : 0; + for (size_t i = 0; i < 4; ++i) { + const nsCSSValuePair& pair1 = radii1->Item(i).GetPairValue(); + const nsCSSValuePair& pair2 = radii2->Item(i).GetPairValue(); + const Maybe<nsCSSValuePair> pairResult = + AddCSSValuePair(aProperty, restrictions, + aCoeff1, &pair1, + aCoeff2, &pair2); + if (!pairResult) { + return nullptr; + } + resultRadii->Item(i).SetPairValue(pairResult.ptr()); + } + break; + } + default: + MOZ_ASSERT_UNREACHABLE("Unknown shape type"); + return nullptr; + } + + // set the geometry-box value + result->Item(1).SetIntValue(aArray1->Item(1).GetIntValue(), + eCSSUnit_Enumerated); + + return result.forget(); +} + +static nsCSSValueList* +AddTransformLists(double aCoeff1, const nsCSSValueList* aList1, + double aCoeff2, const nsCSSValueList* aList2) +{ + nsAutoPtr<nsCSSValueList> result; + nsCSSValueList **resultTail = getter_Transfers(result); + + do { + RefPtr<nsCSSValue::Array> a1 = ToPrimitive(aList1->mValue.GetArrayValue()), + a2 = ToPrimitive(aList2->mValue.GetArrayValue()); + MOZ_ASSERT( + TransformFunctionsMatch(nsStyleTransformMatrix::TransformFunctionOf(a1), + nsStyleTransformMatrix::TransformFunctionOf(a2)), + "transform function mismatch"); + MOZ_ASSERT(!*resultTail, + "resultTail isn't pointing to the tail (may leak)"); + + nsCSSKeyword tfunc = nsStyleTransformMatrix::TransformFunctionOf(a1); + RefPtr<nsCSSValue::Array> arr; + if (tfunc != eCSSKeyword_matrix && + tfunc != eCSSKeyword_matrix3d && + tfunc != eCSSKeyword_interpolatematrix && + tfunc != eCSSKeyword_rotate3d && + tfunc != eCSSKeyword_perspective) { + arr = StyleAnimationValue::AppendTransformFunction(tfunc, resultTail); + } + + switch (tfunc) { + case eCSSKeyword_translate3d: { + MOZ_ASSERT(a1->Count() == 4, "unexpected count"); + MOZ_ASSERT(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: { + MOZ_ASSERT(a1->Count() == 4, "unexpected count"); + MOZ_ASSERT(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: { + MOZ_ASSERT(a1->Count() == 2 || a1->Count() == 3, + "unexpected count"); + MOZ_ASSERT(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: { + MOZ_ASSERT(a1->Count() == 2, "unexpected count"); + MOZ_ASSERT(a2->Count() == 2, "unexpected count"); + + AddCSSValueAngle(aCoeff1, a1->Item(1), aCoeff2, a2->Item(1), + arr->Item(1)); + + break; + } + case eCSSKeyword_rotate3d: { + Point3D vector1(a1->Item(1).GetFloatValue(), + a1->Item(2).GetFloatValue(), + a1->Item(3).GetFloatValue()); + vector1.Normalize(); + Point3D vector2(a2->Item(1).GetFloatValue(), + a2->Item(2).GetFloatValue(), + a2->Item(3).GetFloatValue()); + vector2.Normalize(); + + // Handle rotate3d with matched (normalized) vectors, + // otherwise fallthrough to the next switch statement + // and do matrix decomposition. + if (vector1 == vector2) { + // We skipped appending a transform function above for rotate3d, + // so do it now. + arr = StyleAnimationValue::AppendTransformFunction(tfunc, resultTail); + arr->Item(1).SetFloatValue(vector1.x, eCSSUnit_Number); + arr->Item(2).SetFloatValue(vector1.y, eCSSUnit_Number); + arr->Item(3).SetFloatValue(vector1.z, eCSSUnit_Number); + + AddCSSValueAngle(aCoeff1, a1->Item(4), aCoeff2, a2->Item(4), + arr->Item(4)); + break; + } + MOZ_FALLTHROUGH; + } + case eCSSKeyword_matrix: + case eCSSKeyword_matrix3d: + case eCSSKeyword_perspective: + if (aCoeff1 == 0.0 && aCoeff2 == 0.0) { + // Special case. If both coefficients are 0.0, we should apply an + // identity transform function. + arr = StyleAnimationValue::AppendTransformFunction(tfunc, resultTail); + + if (tfunc == eCSSKeyword_rotate3d) { + arr->Item(1).SetFloatValue(0.0, eCSSUnit_Number); + arr->Item(2).SetFloatValue(0.0, eCSSUnit_Number); + arr->Item(3).SetFloatValue(1.0, eCSSUnit_Number); + arr->Item(4).SetFloatValue(0.0, eCSSUnit_Radian); + } else if (tfunc == eCSSKeyword_perspective) { + // The parameter of the identity perspective function is + // positive infinite. + arr->Item(1).SetFloatValue(std::numeric_limits<float>::infinity(), + eCSSUnit_Pixel); + } else { + nsStyleTransformMatrix::SetIdentityMatrix(arr); + } + break; + } + MOZ_FALLTHROUGH; + case eCSSKeyword_interpolatematrix: { + // 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: + MOZ_ASSERT(false, "unknown transform function"); + } + + aList1 = aList1->mNext; + aList2 = aList2->mNext; + } while (aList1); + MOZ_ASSERT(!aList2, "list length mismatch"); + MOZ_ASSERT(!*resultTail, + "resultTail isn't pointing to the tail"); + + return result.forget(); +} + +static void +AddPositionCoords(double aCoeff1, const nsCSSValue& aPos1, + double aCoeff2, const nsCSSValue& aPos2, + nsCSSValue& aResultPos) +{ + const nsCSSValue::Array* posArray1 = aPos1.GetArrayValue(); + const nsCSSValue::Array* posArray2 = aPos2.GetArrayValue(); + nsCSSValue::Array* resultPosArray = nsCSSValue::Array::Create(2); + aResultPos.SetArrayValue(resultPosArray, eCSSUnit_Array); + + /* Only compute element 1. The <position-coord> is + * 'uncomputed' to only that element. + */ + const nsCSSValue& v1 = posArray1->Item(1); + const nsCSSValue& v2 = posArray2->Item(1); + nsCSSValue& vr = resultPosArray->Item(1); + AddCSSValueCanonicalCalc(aCoeff1, v1, + aCoeff2, v2, vr); +} + +static UniquePtr<nsCSSValueList> +AddWeightedShadowList(double aCoeff1, + const nsCSSValueList* aShadow1, + double aCoeff2, + const nsCSSValueList* aShadow2, + ColorAdditionType aColorAdditionType) +{ + // 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 + UniquePtr<nsCSSValueList> result; + nsCSSValueList* tail = nullptr; + while (aShadow1 && aShadow2) { + UniquePtr<nsCSSValueList> shadowValue = + AddWeightedShadowItems(aCoeff1, aShadow1->mValue, + aCoeff2, aShadow2->mValue, + aColorAdditionType); + if (!shadowValue) { + return nullptr; + } + aShadow1 = aShadow1->mNext; + aShadow2 = aShadow2->mNext; + AppendToCSSValueList(result, Move(shadowValue), &tail); + } + if (aShadow1 || aShadow2) { + const nsCSSValueList *longShadow; + double longCoeff; + if (aShadow1) { + longShadow = aShadow1; + longCoeff = aCoeff1; + } else { + longShadow = aShadow2; + 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. + UniquePtr<nsCSSValueList> shadowValue = + AddWeightedShadowItems(longCoeff, longShadow->mValue, + 0.0, longShadow->mValue, + aColorAdditionType); + if (!shadowValue) { + return nullptr; + } + + longShadow = longShadow->mNext; + AppendToCSSValueList(result, Move(shadowValue), &tail); + } + } + return result; +} + +static UniquePtr<nsCSSValueList> +AddWeightedFilterList(double aCoeff1, const nsCSSValueList* aList1, + double aCoeff2, const nsCSSValueList* aList2, + ColorAdditionType aColorAdditionType) +{ + UniquePtr<nsCSSValueList> result; + nsCSSValueList* tail = nullptr; + while (aList1 || aList2) { + if ((aList1 && aList1->mValue.GetUnit() != eCSSUnit_Function) || + (aList2 && aList2->mValue.GetUnit() != eCSSUnit_Function)) { + // If we don't have filter-functions, we must have filter-URLs, which + // we can't add or interpolate. + return nullptr; + } + + UniquePtr<nsCSSValueList> resultFunction = + AddWeightedFilterFunction(aCoeff1, aList1, aCoeff2, aList2, + aColorAdditionType); + if (!resultFunction) { + // filter function mismatch + return nullptr; + } + + AppendToCSSValueList(result, Move(resultFunction), &tail); + + // move to next aList items + if (aList1) { + aList1 = aList1->mNext; + } + if (aList2) { + aList2 = aList2->mNext; + } + } + + return result; +} + +bool +StyleAnimationValue::AddWeighted(nsCSSPropertyID aProperty, + double aCoeff1, + const StyleAnimationValue& aValue1, + double aCoeff2, + const StyleAnimationValue& aValue2, + StyleAnimationValue& 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: + case eUnit_URL: + case eUnit_DiscreteCSSValue: + 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: { + aResultValue.SetFloatValue(RestrictValue(aProperty, + aCoeff1 * aValue1.GetFloatValue() + + aCoeff2 * aValue2.GetFloatValue())); + return true; + } + case eUnit_Color: { + RGBAColorData color1 = ExtractColor(aValue1); + RGBAColorData color2 = ExtractColor(aValue2); + auto resultColor = MakeUnique<nsCSSValue>(); + resultColor->SetColorValue( + AddWeightedColorsAndClamp(aCoeff1, color1, aCoeff2, color2)); + aResultValue.SetAndAdoptCSSValueValue(resultColor.release(), eUnit_Color); + return true; + } + case eUnit_CurrentColor: { + aResultValue.SetCurrentColorValue(); + return true; + } + case eUnit_ComplexColor: { + ComplexColorData color1 = ExtractComplexColor(aValue1); + ComplexColorData color2 = ExtractComplexColor(aValue2); + RefPtr<ComplexColorValue> result = new ComplexColorValue; + // Common case is interpolating between a color and a currentcolor. + if (color1.IsNumericColor() && color2.IsCurrentColor()) { + result->mColor = color1.mColor; + result->mForegroundRatio = aCoeff2; + } else if (color1.IsCurrentColor() && color2.IsNumericColor()) { + result->mColor = color2.mColor; + result->mForegroundRatio = aCoeff1; + } else { + float ratio1 = 1.0f - color1.mForegroundRatio; + float ratio2 = 1.0f - color2.mForegroundRatio; + float alpha1 = color1.mColor.mA * ratio1; + float alpha2 = color2.mColor.mA * ratio2; + RGBAColorData resultColor = + AddWeightedColors(aCoeff1, color1.mColor.WithAlpha(alpha1), + aCoeff2, color2.mColor.WithAlpha(alpha2)); + float resultRatio = color1.mForegroundRatio * aCoeff1 + + color2.mForegroundRatio * aCoeff2; + float resultAlpha = resultColor.mA / (1.0f - resultRatio); + result->mColor = resultColor.WithAlpha(resultAlpha); + result->mForegroundRatio = resultRatio; + } + aResultValue.SetComplexColorValue(result.forget()); + return true; + } + case eUnit_Calc: { + PixelCalcValue v1 = ExtractCalcValue(aValue1); + PixelCalcValue 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_ObjectPosition: { + const nsCSSValue* position1 = aValue1.GetCSSValueValue(); + const nsCSSValue* position2 = aValue2.GetCSSValueValue(); + + nsAutoPtr<nsCSSValue> result(new nsCSSValue); + AddPositions(aCoeff1, *position1, + aCoeff2, *position2, *result); + + aResultValue.SetAndAdoptCSSValueValue(result.forget(), + eUnit_ObjectPosition); + return true; + } + case eUnit_CSSValuePair: { + uint32_t restrictions = nsCSSProps::ValueRestrictions(aProperty); + Maybe<nsCSSValuePair> result = + AddCSSValuePair(aProperty, restrictions, + aCoeff1, aValue1.GetCSSValuePairValue(), + aCoeff2, aValue2.GetCSSValuePairValue()); + if (!result) { + return false; + } + + // We need a heap allocated object to adopt here: + auto heapResult = MakeUnique<nsCSSValuePair>(*result); + aResultValue.SetAndAdoptCSSValuePairValue(heapResult.release(), + 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<nsCSSValueTriplet> 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) ) { + MOZ_ASSERT(false, "unexpected unit"); + return false; + } + } + + aResultValue.SetAndAdoptCSSValueTripletValue(result.forget(), + eUnit_CSSValueTriplet); + return true; + } + case eUnit_CSSRect: { + MOZ_ASSERT(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<nsCSSRect> result(new nsCSSRect); + for (uint32_t i = 0; i < ArrayLength(nsCSSRect::sides); ++i) { + nsCSSValue nsCSSRect::*member = nsCSSRect::sides[i]; + MOZ_ASSERT((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: + MOZ_ASSERT(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; + } + MOZ_ASSERT(len1 > 0 && len2 > 0, "unexpected length"); + + nsAutoPtr<nsCSSValueList> result; + nsCSSValueList **resultTail = getter_Transfers(result); + for (uint32_t i = 0, i_end = EuclidLCM<uint32_t>(len1, len2); i != i_end; ++i) { + const nsCSSValue &v1 = list1->mValue; + const nsCSSValue &v2 = list2->mValue; + MOZ_ASSERT(v1.GetUnit() == eCSSUnit_Number || + v1.GetUnit() == eCSSUnit_Percent, "unexpected"); + MOZ_ASSERT(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; + *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: { + UniquePtr<nsCSSValueList> result = + AddWeightedShadowList(aCoeff1, + aValue1.GetCSSValueListValue(), + aCoeff2, + aValue2.GetCSSValueListValue(), + ColorAdditionType::Clamped); + if (!result) { + return false; + } + aResultValue.SetAndAdoptCSSValueListValue(result.release(), eUnit_Shadow); + return true; + } + case eUnit_Shape: { + RefPtr<nsCSSValue::Array> result = + AddShapeFunction(aProperty, + aCoeff1, aValue1.GetCSSValueArrayValue(), + aCoeff2, aValue2.GetCSSValueArrayValue()); + if (!result) { + return false; + } + aResultValue.SetCSSValueArrayValue(result, eUnit_Shape); + return true; + } + case eUnit_Filter: { + UniquePtr<nsCSSValueList> result = + AddWeightedFilterList(aCoeff1, aValue1.GetCSSValueListValue(), + aCoeff2, aValue2.GetCSSValueListValue(), + ColorAdditionType::Clamped); + if (!result) { + return false; + } + + aResultValue.SetAndAdoptCSSValueListValue(result.release(), + 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<nsCSSValueList> 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_BackgroundPositionCoord: { + const nsCSSValueList *position1 = aValue1.GetCSSValueListValue(); + const nsCSSValueList *position2 = aValue2.GetCSSValueListValue(); + nsAutoPtr<nsCSSValueList> result; + nsCSSValueList **resultTail = getter_Transfers(result); + while (position1 && position2) { + nsCSSValueList *item = new nsCSSValueList; + *resultTail = item; + resultTail = &item->mNext; + + AddPositionCoords(aCoeff1, position1->mValue, + aCoeff2, position2->mValue, item->mValue); + + position1 = position1->mNext; + position2 = position2->mNext; + } + + // Check for different lengths + if (position1 || position2) { + return false; + } + + aResultValue.SetAndAdoptCSSValueListValue(result.forget(), + eUnit_BackgroundPositionCoord); + return true; + } + case eUnit_CSSValuePairList: { + const nsCSSValuePairList *list1 = aValue1.GetCSSValuePairListValue(); + const nsCSSValuePairList *list2 = aValue2.GetCSSValuePairListValue(); + UniquePtr<nsCSSValuePairList> result = + AddCSSValuePairList(aProperty, aCoeff1, list1, aCoeff2, list2); + if (!result) { + return false; + } + aResultValue.SetAndAdoptCSSValuePairListValue(result.release()); + return true; + } + } + + MOZ_ASSERT(false, "Can't interpolate using the given common unit"); + return false; +} + +bool +StyleAnimationValue::Accumulate(nsCSSPropertyID aProperty, + StyleAnimationValue& aDest, + const StyleAnimationValue& aValueToAccumulate, + uint64_t aCount) +{ + Unit commonUnit = + GetCommonUnit(aProperty, aDest.GetUnit(), aValueToAccumulate.GetUnit()); + switch (commonUnit) { + case eUnit_Filter: { + UniquePtr<nsCSSValueList> result = + AddWeightedFilterList(1.0, aDest.GetCSSValueListValue(), + aCount, aValueToAccumulate.GetCSSValueListValue(), + ColorAdditionType::Unclamped); + if (!result) { + return false; + } + + aDest.SetAndAdoptCSSValueListValue(result.release(), eUnit_Filter); + return true; + } + case eUnit_Shadow: { + UniquePtr<nsCSSValueList> result = + AddWeightedShadowList(1.0, aDest.GetCSSValueListValue(), + aCount, aValueToAccumulate.GetCSSValueListValue(), + ColorAdditionType::Unclamped); + if (!result) { + return false; + } + aDest.SetAndAdoptCSSValueListValue(result.release(), eUnit_Shadow); + return true; + } + case eUnit_Color: { + RGBAColorData color1 = ExtractColor(aDest); + RGBAColorData color2 = ExtractColor(aValueToAccumulate); + auto resultColor = MakeUnique<nsCSSValue>(); + resultColor->SetRGBAColorValue( + AddWeightedColors(1.0, color1, aCount, color2)); + aDest.SetAndAdoptCSSValueValue(resultColor.release(), eUnit_Color); + return true; + } + default: + return Add(aProperty, aDest, aValueToAccumulate, aCount); + } + MOZ_ASSERT_UNREACHABLE("Can't accumulate using the given common unit"); + return false; +} + +already_AddRefed<css::StyleRule> +BuildStyleRule(nsCSSPropertyID aProperty, + dom::Element* aTargetElement, + const nsAString& aSpecifiedValue, + bool aUseSVGMode) +{ + // Set up an empty CSS Declaration + RefPtr<css::Declaration> declaration(new css::Declaration()); + declaration->InitializeEmpty(); + + bool changed; // ignored, but needed as outparam for ParseProperty + nsIDocument* doc = aTargetElement->OwnerDoc(); + nsCOMPtr<nsIURI> baseURI = aTargetElement->GetBaseURI(); + nsCSSParser parser(doc->CSSLoader()); + + nsCSSPropertyID propertyToCheck = nsCSSProps::IsShorthand(aProperty) ? + nsCSSProps::SubpropertyEntryFor(aProperty)[0] : aProperty; + + // Get a parser, parse the property, and check for CSS parsing errors. + // If this fails, we bail out and delete the declaration. + parser.ParseProperty(aProperty, aSpecifiedValue, doc->GetDocumentURI(), + baseURI, aTargetElement->NodePrincipal(), declaration, + &changed, false, aUseSVGMode); + + // check whether property parsed without CSS parsing errors + if (!declaration->HasNonImportantValueFor(propertyToCheck)) { + return nullptr; + } + + RefPtr<css::StyleRule> rule = new css::StyleRule(nullptr, + declaration, + 0, 0); + return rule.forget(); +} + +already_AddRefed<css::StyleRule> +BuildStyleRule(nsCSSPropertyID aProperty, + dom::Element* aTargetElement, + const nsCSSValue& aSpecifiedValue, + bool aUseSVGMode) +{ + MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty), + "Should be a longhand property"); + + // Check if longhand failed to parse correctly. + if (aSpecifiedValue.GetUnit() == eCSSUnit_Null) { + return nullptr; + } + + // Set up an empty CSS Declaration + RefPtr<css::Declaration> declaration(new css::Declaration()); + declaration->InitializeEmpty(); + + // Add our longhand value + nsCSSExpandedDataBlock block; + declaration->ExpandTo(&block); + block.AddLonghandProperty(aProperty, aSpecifiedValue); + declaration->ValueAppended(aProperty); + declaration->CompressFrom(&block); + + RefPtr<css::StyleRule> rule = new css::StyleRule(nullptr, declaration, 0, 0); + return rule.forget(); +} + +static bool +ComputeValuesFromStyleContext( + nsCSSPropertyID aProperty, + CSSEnabledState aEnabledState, + nsStyleContext* aStyleContext, + nsTArray<PropertyStyleAnimationValuePair>& aValues) +{ + // Extract computed value of our property (or all longhand components, if + // aProperty is a shorthand) from the temporary style context + if (nsCSSProps::IsShorthand(aProperty)) { + CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aProperty, aEnabledState) { + if (nsCSSProps::kAnimTypeTable[*p] == eStyleAnimType_None) { + // Skip non-animatable component longhands. + continue; + } + PropertyStyleAnimationValuePair* pair = aValues.AppendElement(); + pair->mProperty = *p; + if (!StyleAnimationValue::ExtractComputedValue(*p, aStyleContext, + pair->mValue)) { + return false; + } + } + return true; + } + + PropertyStyleAnimationValuePair* pair = aValues.AppendElement(); + pair->mProperty = aProperty; + return StyleAnimationValue::ExtractComputedValue(aProperty, aStyleContext, + pair->mValue); +} + +static bool +ComputeValuesFromStyleRule(nsCSSPropertyID aProperty, + CSSEnabledState aEnabledState, + nsStyleContext* aStyleContext, + css::StyleRule* aStyleRule, + nsTArray<PropertyStyleAnimationValuePair>& aValues, + bool* aIsContextSensitive) +{ + MOZ_ASSERT(aStyleContext); + if (!nsCSSProps::IsEnabled(aProperty, aEnabledState)) { + return false; + } + + MOZ_ASSERT(aStyleContext->PresContext()->StyleSet()->IsGecko(), + "ServoStyleSet should not use StyleAnimationValue for animations"); + nsStyleSet* styleSet = aStyleContext->PresContext()->StyleSet()->AsGecko(); + + RefPtr<nsStyleContext> tmpStyleContext; + if (aIsContextSensitive) { + MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty), + "to correctly set aIsContextSensitive for shorthand properties, " + "this code must be adjusted"); + + nsCOMArray<nsIStyleRule> ruleArray; + ruleArray.AppendObject(styleSet->InitialStyleRule()); + css::Declaration* declaration = aStyleRule->GetDeclaration(); + ruleArray.AppendObject(declaration); + declaration->SetImmutable(); + tmpStyleContext = + styleSet->ResolveStyleByAddingRules(aStyleContext, ruleArray); + if (!tmpStyleContext) { + return false; + } + + // Force walk of rule tree + nsStyleStructID sid = nsCSSProps::kSIDTable[aProperty]; + tmpStyleContext->StyleData(sid); + + // The rule node will have unconditional cached style data if the value is + // not context-sensitive. So if there's nothing cached, it's not context + // sensitive. + *aIsContextSensitive = + !tmpStyleContext->RuleNode()->NodeHasCachedUnconditionalData(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<nsIStyleRule> ruleArray; + css::Declaration* declaration = aStyleRule->GetDeclaration(); + ruleArray.AppendObject(declaration); + declaration->SetImmutable(); + tmpStyleContext = + styleSet->ResolveStyleByAddingRules(aStyleContext, ruleArray); + if (!tmpStyleContext) { + return false; + } + } + + return ComputeValuesFromStyleContext(aProperty, aEnabledState, + tmpStyleContext, aValues); +} + +/* static */ bool +StyleAnimationValue::ComputeValue(nsCSSPropertyID aProperty, + dom::Element* aTargetElement, + nsStyleContext* aStyleContext, + const nsAString& aSpecifiedValue, + bool aUseSVGMode, + StyleAnimationValue& aComputedValue, + bool* aIsContextSensitive) +{ + MOZ_ASSERT(aTargetElement, "null target element"); + + // Parse specified value into a temporary css::StyleRule + // Note: BuildStyleRule needs an element's OwnerDoc, BaseURI, and Principal. + // If it is a pseudo element, use its parent element's OwnerDoc, BaseURI, + // and Principal. + RefPtr<css::StyleRule> styleRule = + BuildStyleRule(aProperty, 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; + } + + AutoTArray<PropertyStyleAnimationValuePair,1> values; + bool ok = ComputeValuesFromStyleRule(aProperty, + CSSEnabledState::eIgnoreEnabledState, + aStyleContext, styleRule, + values, aIsContextSensitive); + if (!ok) { + return false; + } + + MOZ_ASSERT(values.Length() == 1); + MOZ_ASSERT(values[0].mProperty == aProperty); + + aComputedValue = values[0].mValue; + return true; +} + +template <class T> +bool +ComputeValuesFromSpecifiedValue( + nsCSSPropertyID aProperty, + CSSEnabledState aEnabledState, + dom::Element* aTargetElement, + nsStyleContext* aStyleContext, + T& aSpecifiedValue, + bool aUseSVGMode, + nsTArray<PropertyStyleAnimationValuePair>& aResult) +{ + MOZ_ASSERT(aTargetElement, "null target element"); + + // Parse specified value into a temporary css::StyleRule + // Note: BuildStyleRule needs an element's OwnerDoc, BaseURI, and Principal. + // If it is a pseudo element, use its parent element's OwnerDoc, BaseURI, + // and Principal. + RefPtr<css::StyleRule> styleRule = + BuildStyleRule(aProperty, aTargetElement, aSpecifiedValue, aUseSVGMode); + if (!styleRule) { + return false; + } + + aResult.Clear(); + return ComputeValuesFromStyleRule(aProperty, aEnabledState, + aStyleContext, styleRule, aResult, + /* aIsContextSensitive */ nullptr); +} + +/* static */ bool +StyleAnimationValue::ComputeValues( + nsCSSPropertyID aProperty, + CSSEnabledState aEnabledState, + dom::Element* aTargetElement, + nsStyleContext* aStyleContext, + const nsAString& aSpecifiedValue, + bool aUseSVGMode, + nsTArray<PropertyStyleAnimationValuePair>& aResult) +{ + return ComputeValuesFromSpecifiedValue(aProperty, aEnabledState, + aTargetElement, aStyleContext, + aSpecifiedValue, aUseSVGMode, + aResult); +} + +/* static */ bool +StyleAnimationValue::ComputeValues( + nsCSSPropertyID aProperty, + CSSEnabledState aEnabledState, + dom::Element* aTargetElement, + nsStyleContext* aStyleContext, + const nsCSSValue& aSpecifiedValue, + bool aUseSVGMode, + nsTArray<PropertyStyleAnimationValuePair>& aResult) +{ + return ComputeValuesFromSpecifiedValue(aProperty, aEnabledState, + aTargetElement, aStyleContext, + aSpecifiedValue, aUseSVGMode, + aResult); +} + +/* static */ bool +StyleAnimationValue::ComputeValues( + nsCSSPropertyID aProperty, + CSSEnabledState aEnabledState, + nsStyleContext* aStyleContext, + const RawServoDeclarationBlock& aDeclarations, + nsTArray<PropertyStyleAnimationValuePair>& aValues) +{ + MOZ_ASSERT(aStyleContext->PresContext()->StyleSet()->IsServo(), + "Should be using ServoStyleSet if we have a" + " RawServoDeclarationBlock"); + + if (!nsCSSProps::IsEnabled(aProperty, aEnabledState)) { + return false; + } + + const ServoComputedValues* previousStyle = + aStyleContext->StyleSource().AsServoComputedValues(); + + // FIXME: Servo bindings don't yet represent const-ness so we just + // cast it away for now. + auto declarations = const_cast<RawServoDeclarationBlock*>(&aDeclarations); + RefPtr<ServoComputedValues> computedValues = + Servo_RestyleWithAddedDeclaration(declarations, previousStyle).Consume(); + if (!computedValues) { + return false; + } + + RefPtr<nsStyleContext> tmpStyleContext = + NS_NewStyleContext(aStyleContext, aStyleContext->PresContext(), + aStyleContext->GetPseudo(), + aStyleContext->GetPseudoType(), + computedValues.forget(), + false /* skipFixup */); + + return ComputeValuesFromStyleContext(aProperty, aEnabledState, + tmpStyleContext, aValues); +} + +bool +StyleAnimationValue::UncomputeValue(nsCSSPropertyID aProperty, + const StyleAnimationValue& aComputedValue, + nsCSSValue& aSpecifiedValue) +{ + Unit unit = aComputedValue.GetUnit(); + switch (unit) { + 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: + aSpecifiedValue.SetIntegerCoordValue(aComputedValue.GetCoordValue()); + break; + case eUnit_Percent: + aSpecifiedValue.SetPercentValue(aComputedValue.GetPercentValue()); + break; + case eUnit_Float: + aSpecifiedValue. + SetFloatValue(aComputedValue.GetFloatValue(), eCSSUnit_Number); + break; + case eUnit_CurrentColor: + aSpecifiedValue.SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor); + break; + case eUnit_Calc: + case eUnit_Color: + case eUnit_ObjectPosition: + case eUnit_URL: + case eUnit_DiscreteCSSValue: { + nsCSSValue* val = aComputedValue.GetCSSValueValue(); + // Sanity-check that the underlying unit in the nsCSSValue is what we + // expect for our StyleAnimationValue::Unit: + MOZ_ASSERT((unit == eUnit_Calc && val->GetUnit() == eCSSUnit_Calc) || + (unit == eUnit_Color && + nsCSSValue::IsNumericColorUnit(val->GetUnit())) || + (unit == eUnit_ObjectPosition && + val->GetUnit() == eCSSUnit_Array) || + (unit == eUnit_URL && val->GetUnit() == eCSSUnit_URL) || + unit == eUnit_DiscreteCSSValue, + "unexpected unit"); + aSpecifiedValue = *val; + break; + } + case eUnit_ComplexColor: { + aSpecifiedValue.SetComplexColorValue( + do_AddRef(aComputedValue.mValue.mComplexColor)); + 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_BackgroundPositionCoord: + { + nsCSSValueList* computedList = aComputedValue.GetCSSValueListValue(); + if (computedList) { + aSpecifiedValue.SetDependentListValue(computedList); + } else { + aSpecifiedValue.SetNoneValue(); + } + } + break; + case eUnit_Shape: { + nsCSSValue::Array* computedArray = aComputedValue.GetCSSValueArrayValue(); + aSpecifiedValue.SetArrayValue(computedArray, eCSSUnit_Array); + break; + } + case eUnit_Transform: + aSpecifiedValue. + SetSharedListValue(aComputedValue.GetCSSValueSharedListValue()); + break; + case eUnit_CSSValuePairList: + aSpecifiedValue. + SetDependentPairListValue(aComputedValue.GetCSSValuePairListValue()); + break; + default: + return false; + } + return true; +} + +bool +StyleAnimationValue::UncomputeValue(nsCSSPropertyID aProperty, + StyleAnimationValue&& aComputedValue, + nsCSSValue& aSpecifiedValue) +{ + Unit unit = aComputedValue.GetUnit(); + switch (unit) { + case eUnit_Dasharray: + case eUnit_Shadow: + case eUnit_Filter: + case eUnit_BackgroundPositionCoord: + { + UniquePtr<nsCSSValueList> computedList = + aComputedValue.TakeCSSValueListValue(); + if (computedList) { + aSpecifiedValue.AdoptListValue(Move(computedList)); + } else { + aSpecifiedValue.SetNoneValue(); + } + } + break; + case eUnit_CSSValuePairList: + { + UniquePtr<nsCSSValuePairList> computedList = + aComputedValue.TakeCSSValuePairListValue(); + MOZ_ASSERT(computedList, "Pair list should never be null"); + aSpecifiedValue.AdoptPairListValue(Move(computedList)); + } + break; + default: + return UncomputeValue(aProperty, aComputedValue, aSpecifiedValue); + } + return true; +} + +bool +StyleAnimationValue::UncomputeValue(nsCSSPropertyID aProperty, + const StyleAnimationValue& 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 (!StyleAnimationValue::UncomputeValue(aProperty, aComputedValue, val)) { + return false; + } + + val.AppendToString(aProperty, aSpecifiedValue, nsCSSValue::eNormalized); + return true; +} + +template<typename T> +inline const T& +StyleDataAtOffset(const void* aStyleStruct, ptrdiff_t aOffset) +{ + return *reinterpret_cast<const T*>( + reinterpret_cast<const uint8_t*>(aStyleStruct) + aOffset); +} + +static bool +StyleCoordToValue(const nsStyleCoord& aCoord, StyleAnimationValue& 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(), + StyleAnimationValue::eUnit_Enumerated); + break; + case eStyleUnit_Integer: + aValue.SetIntValue(aCoord.GetIntValue(), + StyleAnimationValue::eUnit_Integer); + break; + case eStyleUnit_Calc: { + nsAutoPtr<nsCSSValue> val(new nsCSSValue); + CalcValueToCSSValue(aCoord.GetCalcValue(), *val); + aValue.SetAndAdoptCSSValueValue(val.forget(), + StyleAnimationValue::eUnit_Calc); + break; + } + default: + return false; + } + return true; +} + +static bool +StyleCoordToCSSValue(const nsStyleCoord& aCoord, nsCSSValue& aCSSValue) +{ + switch (aCoord.GetUnit()) { + case eStyleUnit_Coord: + aCSSValue.SetIntegerCoordValue(aCoord.GetCoordValue()); + break; + case eStyleUnit_Factor: + aCSSValue.SetFloatValue(aCoord.GetFactorValue(), eCSSUnit_Number); + break; + case eStyleUnit_Percent: + aCSSValue.SetPercentValue(aCoord.GetPercentValue()); + break; + case eStyleUnit_Calc: + CalcValueToCSSValue(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: + MOZ_ASSERT(false, "unexpected unit"); + return false; + } + return true; +} + +static void +SetPositionValue(const Position& aPos, nsCSSValue& aCSSValue) +{ + RefPtr<nsCSSValue::Array> posArray = nsCSSValue::Array::Create(4); + aCSSValue.SetArrayValue(posArray.get(), eCSSUnit_Array); + + // NOTE: Array entries #0 and #2 here are intentionally left untouched, with + // eCSSUnit_Null. The purpose of these entries in our specified-style + // <position> representation is to store edge names. But for values + // extracted from computed style (which is what we're dealing with here), + // we'll just have a normalized "x,y" position, with no edge names needed. + nsCSSValue& xValue = posArray->Item(1); + nsCSSValue& yValue = posArray->Item(3); + + CalcValueToCSSValue(&aPos.mXPosition, xValue); + CalcValueToCSSValue(&aPos.mYPosition, yValue); +} + +static void +SetPositionCoordValue(const Position::Coord& aPosCoord, + nsCSSValue& aCSSValue) +{ + RefPtr<nsCSSValue::Array> posArray = nsCSSValue::Array::Create(2); + aCSSValue.SetArrayValue(posArray.get(), eCSSUnit_Array); + + // NOTE: Array entry #0 here is intentionally left untouched, with + // eCSSUnit_Null. The purpose of this entry in our specified-style + // <position-coord> representation is to store edge names. But for values + // extracted from computed style (which is what we're dealing with here), + // we'll just have a normalized "x"/"y" position, with no edge names needed. + nsCSSValue& value = posArray->Item(1); + + CalcValueToCSSValue(&aPosCoord, value); +} + +/* + * 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()) { + RuleNodeCacheConditions conditions; + nsRuleNode::ComputedCalc c = + nsRuleNode::SpecifiedCalcToComputedCalc(aInput, aStyleContext, + aStyleContext->PresContext(), + conditions); + nsStyleCoord::CalcValue c2; + c2.mLength = c.mLength; + c2.mPercent = c.mPercent; + c2.mHasPercent = true; // doesn't matter for transform translate + CalcValueToCSSValue(&c2, aOutput); + } else if (aInput.UnitHasArrayValue()) { + const nsCSSValue::Array *inputArray = aInput.GetArrayValue(); + RefPtr<nsCSSValue::Array> 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) { + RuleNodeCacheConditions conditions; + nscoord len = nsRuleNode::CalcLength(aInput, aStyleContext, + aStyleContext->PresContext(), + conditions); + aOutput.SetFloatValue(nsPresContext::AppUnitsToFloatCSSPixels(len), + eCSSUnit_Pixel); + } else { + aOutput = aInput; + } +} + +static void +ExtractImageLayerPositionXList(const nsStyleImageLayers& aLayer, + StyleAnimationValue& aComputedValue) +{ + MOZ_ASSERT(aLayer.mPositionXCount > 0, "unexpected count"); + + nsAutoPtr<nsCSSValueList> result; + nsCSSValueList **resultTail = getter_Transfers(result); + for (uint32_t i = 0, i_end = aLayer.mPositionXCount; i != i_end; ++i) { + nsCSSValueList *item = new nsCSSValueList; + *resultTail = item; + resultTail = &item->mNext; + SetPositionCoordValue(aLayer.mLayers[i].mPosition.mXPosition, + item->mValue); + } + + aComputedValue.SetAndAdoptCSSValueListValue(result.forget(), + StyleAnimationValue::eUnit_BackgroundPositionCoord); +} + +static void +ExtractImageLayerPositionYList(const nsStyleImageLayers& aLayer, + StyleAnimationValue& aComputedValue) +{ + MOZ_ASSERT(aLayer.mPositionYCount > 0, "unexpected count"); + + nsAutoPtr<nsCSSValueList> result; + nsCSSValueList **resultTail = getter_Transfers(result); + for (uint32_t i = 0, i_end = aLayer.mPositionYCount; i != i_end; ++i) { + nsCSSValueList *item = new nsCSSValueList; + *resultTail = item; + resultTail = &item->mNext; + SetPositionCoordValue(aLayer.mLayers[i].mPosition.mYPosition, + item->mValue); + } + + aComputedValue.SetAndAdoptCSSValueListValue(result.forget(), + StyleAnimationValue::eUnit_BackgroundPositionCoord); +} + +static void +ExtractImageLayerSizePairList(const nsStyleImageLayers& aLayer, + StyleAnimationValue& aComputedValue) +{ + MOZ_ASSERT(aLayer.mSizeCount > 0, "unexpected count"); + + nsAutoPtr<nsCSSValuePairList> result; + nsCSSValuePairList **resultTail = getter_Transfers(result); + for (uint32_t i = 0, i_end = aLayer.mSizeCount; i != i_end; ++i) { + nsCSSValuePairList *item = new nsCSSValuePairList; + *resultTail = item; + resultTail = &item->mNext; + + const nsStyleImageLayers::Size &size = aLayer.mLayers[i].mSize; + switch (size.mWidthType) { + case nsStyleImageLayers::Size::eContain: + case nsStyleImageLayers::Size::eCover: + item->mXValue.SetIntValue(size.mWidthType, + eCSSUnit_Enumerated); + break; + case nsStyleImageLayers::Size::eAuto: + item->mXValue.SetAutoValue(); + break; + case nsStyleImageLayers::Size::eLengthPercentage: + // XXXbz is there a good reason we can't just + // CalcValueToCSSValue(&size.mWidth, item->mXValue) here? + if (!size.mWidth.mHasPercent && + // negative values must have come from calc() + size.mWidth.mLength >= 0) { + MOZ_ASSERT(size.mWidth.mPercent == 0.0f, + "Shouldn't have mPercent"); + item->mXValue.SetIntegerCoordValue(size.mWidth.mLength); + } 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 { + CalcValueToCSSValue(&size.mWidth, item->mXValue); + } + break; + } + + switch (size.mHeightType) { + case nsStyleImageLayers::Size::eContain: + case nsStyleImageLayers::Size::eCover: + // leave it null + break; + case nsStyleImageLayers::Size::eAuto: + item->mYValue.SetAutoValue(); + break; + case nsStyleImageLayers::Size::eLengthPercentage: + // XXXbz is there a good reason we can't just + // CalcValueToCSSValue(&size.mHeight, item->mYValue) here? + if (!size.mHeight.mHasPercent && + // negative values must have come from calc() + size.mHeight.mLength >= 0) { + MOZ_ASSERT(size.mHeight.mPercent == 0.0f, + "Shouldn't have mPercent"); + item->mYValue.SetIntegerCoordValue(size.mHeight.mLength); + } 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 { + CalcValueToCSSValue(&size.mHeight, item->mYValue); + } + break; + } + } + + aComputedValue.SetAndAdoptCSSValuePairListValue(result.forget()); +} + +static bool +StyleClipBasicShapeToCSSArray(const StyleClipPath& aClipPath, + nsCSSValue::Array* aResult) +{ + MOZ_ASSERT(aResult->Count() == 2, + "Expected array to be presized for a function and the sizing-box"); + + const StyleBasicShape* shape = aClipPath.GetBasicShape(); + nsCSSKeyword functionName = shape->GetShapeTypeName(); + RefPtr<nsCSSValue::Array> functionArray; + switch (shape->GetShapeType()) { + case StyleBasicShapeType::Circle: + case StyleBasicShapeType::Ellipse: { + const nsTArray<nsStyleCoord>& coords = shape->Coordinates(); + MOZ_ASSERT(coords.Length() == ShapeArgumentCount(functionName) - 1, + "Unexpected radii count"); + // The "+1" is for the center point: + functionArray = aResult->Item(0).InitFunction(functionName, + coords.Length() + 1); + for (size_t i = 0; i < coords.Length(); ++i) { + if (coords[i].GetUnit() == eStyleUnit_Enumerated) { + functionArray->Item(i + 1).SetIntValue(coords[i].GetIntValue(), + eCSSUnit_Enumerated); + } else if (!StyleCoordToCSSValue(coords[i], + functionArray->Item(i + 1))) { + return false; + } + } + // Set functionArray's last item to the circle or ellipse's center point: + SetPositionValue(shape->GetPosition(), + functionArray->Item(functionArray->Count() - 1)); + break; + } + case StyleBasicShapeType::Polygon: { + functionArray = + aResult->Item(0).InitFunction(functionName, + ShapeArgumentCount(functionName)); + functionArray->Item(1).SetIntValue(shape->GetFillRule(), + eCSSUnit_Enumerated); + nsCSSValuePairList* list = functionArray->Item(2).SetPairListValue(); + const nsTArray<nsStyleCoord>& coords = shape->Coordinates(); + MOZ_ASSERT((coords.Length() % 2) == 0); + for (size_t i = 0; i < coords.Length(); i += 2) { + if (i > 0) { + list->mNext = new nsCSSValuePairList; + list = list->mNext; + } + if (!StyleCoordToCSSValue(coords[i], list->mXValue) || + !StyleCoordToCSSValue(coords[i + 1], list->mYValue)) { + return false; + } + } + break; + } + case StyleBasicShapeType::Inset: { + const nsTArray<nsStyleCoord>& coords = shape->Coordinates(); + MOZ_ASSERT(coords.Length() == ShapeArgumentCount(functionName) - 1, + "Unexpected offset count"); + functionArray = + aResult->Item(0).InitFunction(functionName, coords.Length() + 1); + for (size_t i = 0; i < coords.Length(); ++i) { + if (!StyleCoordToCSSValue(coords[i], functionArray->Item(i + 1))) { + return false; + } + } + RefPtr<nsCSSValue::Array> radiusArray = nsCSSValue::Array::Create(4); + const nsStyleCorners& radii = shape->GetRadius(); + NS_FOR_CSS_FULL_CORNERS(corner) { + auto pair = MakeUnique<nsCSSValuePair>(); + if (!StyleCoordToCSSValue(radii.Get(NS_FULL_TO_HALF_CORNER(corner, false)), + pair->mXValue) || + !StyleCoordToCSSValue(radii.Get(NS_FULL_TO_HALF_CORNER(corner, true)), + pair->mYValue)) { + return false; + } + radiusArray->Item(corner).SetPairValue(pair.get()); + } + // Set the last item in functionArray to the radius array: + functionArray->Item(functionArray->Count() - 1). + SetArrayValue(radiusArray, eCSSUnit_Array); + break; + } + default: + MOZ_ASSERT_UNREACHABLE("Unknown shape type"); + return false; + } + aResult->Item(1).SetIntValue(aClipPath.GetReferenceBox(), + eCSSUnit_Enumerated); + return true; +} + +bool +StyleAnimationValue::ExtractComputedValue(nsCSSPropertyID aProperty, + nsStyleContext* aStyleContext, + StyleAnimationValue& aComputedValue) +{ + MOZ_ASSERT(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]; + MOZ_ASSERT(0 <= ssOffset || + animType == eStyleAnimType_Custom || + animType == eStyleAnimType_Discrete, + "all animation types other than Custom and Discrete must " \ + "specify a style struct offset to extract values from"); + 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<const nsStyleBorder*>(styleStruct)-> \ + GetComputedBorder().side_); \ + break; + BORDER_WIDTH_CASE(eCSSProperty_border_bottom_width, bottom) + BORDER_WIDTH_CASE(eCSSProperty_border_left_width, left) + BORDER_WIDTH_CASE(eCSSProperty_border_right_width, right) + BORDER_WIDTH_CASE(eCSSProperty_border_top_width, top) + #undef BORDER_WIDTH_CASE + + case eCSSProperty_column_rule_width: + aComputedValue.SetCoordValue( + static_cast<const nsStyleColumn*>(styleStruct)-> + GetComputedColumnRuleWidth()); + break; + + case eCSSProperty_column_count: { + const nsStyleColumn *styleColumn = + static_cast<const nsStyleColumn*>(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<const nsStylePosition*>(styleStruct); + aComputedValue.SetIntValue(stylePosition->mOrder, + eUnit_Integer); + break; + } + + case eCSSProperty_border_spacing: { + const nsStyleTableBorder *styleTableBorder = + static_cast<const nsStyleTableBorder*>(styleStruct); + nsAutoPtr<nsCSSValuePair> pair(new nsCSSValuePair); + pair->mXValue.SetIntegerCoordValue(styleTableBorder->mBorderSpacingCol); + pair->mYValue.SetIntegerCoordValue(styleTableBorder->mBorderSpacingRow); + aComputedValue.SetAndAdoptCSSValuePairValue(pair.forget(), + eUnit_CSSValuePair); + break; + } + + case eCSSProperty_transform_origin: { + const nsStyleDisplay *styleDisplay = + static_cast<const nsStyleDisplay*>(styleStruct); + nsAutoPtr<nsCSSValueTriplet> triplet(new nsCSSValueTriplet); + if (!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<const nsStyleDisplay*>(styleStruct); + nsAutoPtr<nsCSSValuePair> pair(new nsCSSValuePair); + if (!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<const nsStyleSVG*>(styleStruct); + if (!svg->mStrokeDasharray.IsEmpty()) { + nsAutoPtr<nsCSSValueList> result; + nsCSSValueList **resultTail = getter_Transfers(result); + for (uint32_t i = 0, i_end = svg->mStrokeDasharray.Length(); + i != i_end; ++i) { + nsCSSValueList *item = new nsCSSValueList; + *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: + MOZ_ASSERT(false, "unexpected unit"); + return false; + } + } + aComputedValue.SetAndAdoptCSSValueListValue(result.forget(), + eUnit_Dasharray); + } else if (svg->StrokeDasharrayFromObject()) { + // An empty dasharray with StrokeDasharrayFromObject() == true + // corresponds to the "context-value" keyword. + aComputedValue.SetIntValue(NS_STYLE_STROKE_PROP_CONTEXT_VALUE, + eUnit_Enumerated); + } else { + // Otherwise, an empty dasharray corresponds to the "none" keyword. + aComputedValue.SetNoneValue(); + } + break; + } + + case eCSSProperty_font_stretch: { + int16_t stretch = + static_cast<const nsStyleFont*>(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<const nsStyleFont*>(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<const nsStyleList*>(styleStruct); + const nsRect &srect = list->mImageRegion; + if (srect.IsEmpty()) { + aComputedValue.SetAutoValue(); + break; + } + + nsCSSRect *vrect = new nsCSSRect; + vrect->mLeft.SetIntegerCoordValue(srect.x); + vrect->mTop.SetIntegerCoordValue(srect.y); + vrect->mRight.SetIntegerCoordValue(srect.XMost()); + vrect->mBottom.SetIntegerCoordValue(srect.YMost()); + aComputedValue.SetAndAdoptCSSRectValue(vrect, eUnit_CSSRect); + break; + } + + case eCSSProperty_clip: { + const nsStyleEffects* effects = + static_cast<const nsStyleEffects*>(styleStruct); + if (!(effects->mClipFlags & NS_STYLE_CLIP_RECT)) { + aComputedValue.SetAutoValue(); + } else { + nsCSSRect *vrect = new nsCSSRect; + const nsRect &srect = effects->mClip; + if (effects->mClipFlags & NS_STYLE_CLIP_TOP_AUTO) { + vrect->mTop.SetAutoValue(); + } else { + vrect->mTop.SetIntegerCoordValue(srect.y); + } + if (effects->mClipFlags & NS_STYLE_CLIP_RIGHT_AUTO) { + vrect->mRight.SetAutoValue(); + } else { + vrect->mRight.SetIntegerCoordValue(srect.XMost()); + } + if (effects->mClipFlags & NS_STYLE_CLIP_BOTTOM_AUTO) { + vrect->mBottom.SetAutoValue(); + } else { + vrect->mBottom.SetIntegerCoordValue(srect.YMost()); + } + if (effects->mClipFlags & NS_STYLE_CLIP_LEFT_AUTO) { + vrect->mLeft.SetAutoValue(); + } else { + vrect->mLeft.SetIntegerCoordValue(srect.x); + } + aComputedValue.SetAndAdoptCSSRectValue(vrect, eUnit_CSSRect); + } + break; + } + + case eCSSProperty_object_position: { + const nsStylePosition* stylePos = + static_cast<const nsStylePosition*>(styleStruct); + + nsAutoPtr<nsCSSValue> val(new nsCSSValue); + SetPositionValue(stylePos->mObjectPosition, *val); + + aComputedValue.SetAndAdoptCSSValueValue(val.forget(), + eUnit_ObjectPosition); + break; + } + + case eCSSProperty_background_position_x: { + const nsStyleImageLayers& layers = + static_cast<const nsStyleBackground*>(styleStruct)->mImage; + ExtractImageLayerPositionXList(layers, aComputedValue); + break; + } + case eCSSProperty_background_position_y: { + const nsStyleImageLayers& layers = + static_cast<const nsStyleBackground*>(styleStruct)->mImage; + ExtractImageLayerPositionYList(layers, aComputedValue); + break; + + + + + } +#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND + case eCSSProperty_mask_position_x: { + const nsStyleImageLayers& layers = + static_cast<const nsStyleSVGReset*>(styleStruct)->mMask; + ExtractImageLayerPositionXList(layers, aComputedValue); + break; + } + case eCSSProperty_mask_position_y: { + const nsStyleImageLayers& layers = + static_cast<const nsStyleSVGReset*>(styleStruct)->mMask; + ExtractImageLayerPositionYList(layers, aComputedValue); + + break; + } +#endif + case eCSSProperty_background_size: { + const nsStyleImageLayers& layers = + static_cast<const nsStyleBackground*>(styleStruct)->mImage; + ExtractImageLayerSizePairList(layers, aComputedValue); + break; + } +#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND + case eCSSProperty_mask_size: { + const nsStyleImageLayers& layers = + static_cast<const nsStyleSVGReset*>(styleStruct)->mMask; + ExtractImageLayerSizePairList(layers, aComputedValue); + break; + } +#endif + + case eCSSProperty_clip_path: { + const nsStyleSVGReset* svgReset = + static_cast<const nsStyleSVGReset*>(styleStruct); + const StyleClipPath& clipPath = svgReset->mClipPath; + const StyleShapeSourceType type = clipPath.GetType(); + + if (type == StyleShapeSourceType::URL) { + auto result = MakeUnique<nsCSSValue>(); + result->SetURLValue(clipPath.GetURL()); + aComputedValue.SetAndAdoptCSSValueValue(result.release(), eUnit_URL); + } else if (type == StyleShapeSourceType::Box) { + aComputedValue.SetIntValue(clipPath.GetReferenceBox(), + eUnit_Enumerated); + } else if (type == StyleShapeSourceType::Shape) { + RefPtr<nsCSSValue::Array> result = nsCSSValue::Array::Create(2); + if (!StyleClipBasicShapeToCSSArray(clipPath, result)) { + return false; + } + aComputedValue.SetCSSValueArrayValue(result, eUnit_Shape); + + } else { + MOZ_ASSERT(type == StyleShapeSourceType::None, "unknown type"); + aComputedValue.SetNoneValue(); + } + break; + } + + case eCSSProperty_filter: { + const nsStyleEffects* effects = + static_cast<const nsStyleEffects*>(styleStruct); + const nsTArray<nsStyleFilter>& filters = effects->mFilters; + nsAutoPtr<nsCSSValueList> result; + nsCSSValueList **resultTail = getter_Transfers(result); + for (uint32_t i = 0; i < filters.Length(); ++i) { + nsCSSValueList *item = new nsCSSValueList; + *resultTail = item; + resultTail = &item->mNext; + const nsStyleFilter& filter = filters[i]; + int32_t type = filter.GetType(); + if (type == NS_STYLE_FILTER_URL) { + item->mValue.SetURLValue(filter.GetURL()); + } else { + nsCSSKeyword functionName = + nsCSSProps::ValueToKeywordEnum(type, + nsCSSProps::kFilterFunctionKTable); + nsCSSValue::Array* filterArray = + item->mValue.InitFunction(functionName, 1); + if (type >= NS_STYLE_FILTER_BLUR && type <= NS_STYLE_FILTER_HUE_ROTATE) { + if (!StyleCoordToCSSValue( + filter.GetFilterParameter(), + filterArray->Item(1))) { + return false; + } + } else if (type == NS_STYLE_FILTER_DROP_SHADOW) { + nsCSSValueList* shadowResult = filterArray->Item(1).SetListValue(); + nsAutoPtr<nsCSSValueList> tmpShadowValue; + nsCSSValueList **tmpShadowResultTail = getter_Transfers(tmpShadowValue); + nsCSSShadowArray* shadowArray = filter.GetDropShadow(); + MOZ_ASSERT(shadowArray->Length() == 1, + "expected exactly one shadow"); + AppendCSSShadowValue(shadowArray->ShadowAt(0), tmpShadowResultTail); + *shadowResult = *tmpShadowValue; + } else { + // We checked all possible nsStyleFilter types but + // NS_STYLE_FILTER_NULL before. We should never enter this path. + NS_NOTREACHED("no other filter functions defined"); + return false; + } + } + } + + aComputedValue.SetAndAdoptCSSValueListValue(result.forget(), + eUnit_Filter); + break; + } + + case eCSSProperty_transform: { + const nsStyleDisplay *display = + static_cast<const nsStyleDisplay*>(styleStruct); + nsAutoPtr<nsCSSValueList> 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: + MOZ_ASSERT(false, "missing property implementation"); + return false; + }; + return true; + case eStyleAnimType_Coord: { + const nsStyleCoord& coord = + StyleDataAtOffset<nsStyleCoord>(styleStruct, ssOffset); + if (nsCSSProps::PropHasFlags(aProperty, CSS_PROPERTY_NUMBERS_ARE_PIXELS) && + coord.GetUnit() == eStyleUnit_Coord) { + // For SVG properties where 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). + aComputedValue.SetFloatValue(nsPresContext:: + AppUnitsToFloatCSSPixels(coord.GetCoordValue())); + return true; + } + return StyleCoordToValue(coord, 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 = + StyleDataAtOffset<nsStyleSides>(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 = + StyleDataAtOffset<nsStyleCorners>(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<nsCSSValuePair> pair(new nsCSSValuePair); + if (!StyleCoordToCSSValue(horiz, pair->mXValue) || + !StyleCoordToCSSValue(vert, pair->mYValue)) { + return false; + } + aComputedValue.SetAndAdoptCSSValuePairValue(pair.forget(), + eUnit_CSSValuePair); + return true; + } + case eStyleAnimType_nscoord: + aComputedValue.SetCoordValue( + StyleDataAtOffset<nscoord>(styleStruct, ssOffset)); + return true; + case eStyleAnimType_float: + aComputedValue.SetFloatValue( + StyleDataAtOffset<float>(styleStruct, ssOffset)); + if (aProperty == eCSSProperty_font_size_adjust && + aComputedValue.GetFloatValue() == -1.0f) { + // In nsStyleFont, we set mFont.sizeAdjust to -1.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( + StyleDataAtOffset<nscolor>(styleStruct, ssOffset)); + return true; + case eStyleAnimType_ComplexColor: { + aComputedValue.SetComplexColorValue( + StyleDataAtOffset<StyleComplexColor>(styleStruct, ssOffset)); + return true; + } + case eStyleAnimType_PaintServer: { + const nsStyleSVGPaint& paint = + StyleDataAtOffset<nsStyleSVGPaint>(styleStruct, ssOffset); + switch (paint.Type()) { + case eStyleSVGPaintType_Color: + aComputedValue.SetColorValue(paint.GetColor()); + return true; + case eStyleSVGPaintType_Server: { + css::URLValue* url = paint.GetPaintServer(); + if (!url) { + NS_WARNING("Null paint server"); + return false; + } + nsAutoPtr<nsCSSValuePair> pair(new nsCSSValuePair); + pair->mXValue.SetURLValue(url); + pair->mYValue.SetColorValue(paint.GetFallbackColor()); + aComputedValue.SetAndAdoptCSSValuePairValue(pair.forget(), + eUnit_CSSValuePair); + return true; + } + case eStyleSVGPaintType_ContextFill: + case eStyleSVGPaintType_ContextStroke: { + nsAutoPtr<nsCSSValuePair> pair(new nsCSSValuePair); + pair->mXValue.SetIntValue(paint.Type() == eStyleSVGPaintType_ContextFill ? + NS_COLOR_CONTEXT_FILL : NS_COLOR_CONTEXT_STROKE, + eCSSUnit_Enumerated); + pair->mYValue.SetColorValue(paint.GetFallbackColor()); + aComputedValue.SetAndAdoptCSSValuePairValue(pair.forget(), + eUnit_CSSValuePair); + return true; + } + default: + MOZ_ASSERT(paint.Type() == eStyleSVGPaintType_None, + "Unexpected SVG paint type"); + aComputedValue.SetNoneValue(); + return true; + } + } + case eStyleAnimType_Shadow: { + const nsCSSShadowArray* shadowArray = + StyleDataAtOffset<RefPtr<nsCSSShadowArray>>(styleStruct, ssOffset); + if (!shadowArray) { + aComputedValue.SetAndAdoptCSSValueListValue(nullptr, eUnit_Shadow); + return true; + } + nsAutoPtr<nsCSSValueList> 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_Discrete: { + if (aProperty == eCSSProperty_visibility) { + aComputedValue.SetIntValue( + static_cast<const nsStyleVisibility*>(styleStruct)->mVisible, + eUnit_Visibility); + return true; + } + auto cssValue = MakeUnique<nsCSSValue>(eCSSUnit_Unset); + aStyleContext->RuleNode()->GetDiscretelyAnimatedCSSValue(aProperty, + cssValue.get()); + aComputedValue.SetAndAdoptCSSValueValue(cssValue.release(), + eUnit_DiscreteCSSValue); + return true; + } + case eStyleAnimType_None: + NS_NOTREACHED("shouldn't use on non-animatable properties"); + } + return false; +} + +gfxSize +StyleAnimationValue::GetScaleValue(const nsIFrame* aForFrame) const +{ + MOZ_ASSERT(aForFrame); + MOZ_ASSERT(GetUnit() == StyleAnimationValue::eUnit_Transform); + + nsCSSValueSharedList* list = GetCSSValueSharedListValue(); + MOZ_ASSERT(list->mHead); + + RuleNodeCacheConditions dontCare; + bool dontCareBool; + nsStyleTransformMatrix::TransformReferenceBox refBox(aForFrame); + Matrix4x4 transform = nsStyleTransformMatrix::ReadTransforms( + list->mHead, + aForFrame->StyleContext(), + aForFrame->PresContext(), dontCare, refBox, + aForFrame->PresContext()->AppUnitsPerDevPixel(), + &dontCareBool); + + Matrix transform2d; + bool canDraw2D = transform.CanDraw2D(&transform2d); + if (!canDraw2D) { + return gfxSize(); + } + + return ThebesMatrix(transform2d).ScaleFactors(true); +} + +StyleAnimationValue::StyleAnimationValue(int32_t aInt, Unit aUnit, + IntegerConstructorType) +{ + NS_ASSERTION(IsIntUnit(aUnit), "unit must be of integer type"); + mUnit = aUnit; + mValue.mInt = aInt; +} + +StyleAnimationValue::StyleAnimationValue(nscoord aLength, CoordConstructorType) +{ + mUnit = eUnit_Coord; + mValue.mCoord = aLength; +} + +StyleAnimationValue::StyleAnimationValue(float aPercent, + PercentConstructorType) +{ + mUnit = eUnit_Percent; + mValue.mFloat = aPercent; + MOZ_ASSERT(!mozilla::IsNaN(mValue.mFloat)); +} + +StyleAnimationValue::StyleAnimationValue(float aFloat, FloatConstructorType) +{ + mUnit = eUnit_Float; + mValue.mFloat = aFloat; + MOZ_ASSERT(!mozilla::IsNaN(mValue.mFloat)); +} + +StyleAnimationValue::StyleAnimationValue(nscolor aColor, ColorConstructorType) +{ + mUnit = eUnit_Color; + mValue.mCSSValue = new nsCSSValue(); + mValue.mCSSValue->SetColorValue(aColor); +} + +StyleAnimationValue& +StyleAnimationValue::operator=(const StyleAnimationValue& aOther) +{ + if (this == &aOther) { + return *this; + } + + FreeValue(); + + mUnit = aOther.mUnit; + switch (mUnit) { + case eUnit_Null: + case eUnit_Normal: + case eUnit_Auto: + case eUnit_None: + case eUnit_CurrentColor: + 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_Calc: + case eUnit_Color: + case eUnit_ObjectPosition: + case eUnit_URL: + case eUnit_DiscreteCSSValue: + MOZ_ASSERT(IsCSSValueUnit(mUnit), + "This clause is for handling nsCSSValue-backed units"); + MOZ_ASSERT(aOther.mValue.mCSSValue, "values may not be null"); + mValue.mCSSValue = new nsCSSValue(*aOther.mValue.mCSSValue); + break; + case eUnit_CSSValuePair: + MOZ_ASSERT(aOther.mValue.mCSSValuePair, + "value pairs may not be null"); + mValue.mCSSValuePair = new nsCSSValuePair(*aOther.mValue.mCSSValuePair); + break; + case eUnit_CSSValueTriplet: + MOZ_ASSERT(aOther.mValue.mCSSValueTriplet, + "value triplets may not be null"); + mValue.mCSSValueTriplet = new nsCSSValueTriplet(*aOther.mValue.mCSSValueTriplet); + break; + case eUnit_CSSRect: + MOZ_ASSERT(aOther.mValue.mCSSRect, "rects may not be null"); + mValue.mCSSRect = new nsCSSRect(*aOther.mValue.mCSSRect); + break; + case eUnit_Dasharray: + case eUnit_Shadow: + case eUnit_Filter: + case eUnit_BackgroundPositionCoord: + MOZ_ASSERT(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(); + } else { + mValue.mCSSValueList = nullptr; + } + break; + case eUnit_Shape: + MOZ_ASSERT(aOther.mValue.mCSSValueArray, + "value arrays may not be null"); + mValue.mCSSValueArray = aOther.mValue.mCSSValueArray; + mValue.mCSSValueArray->AddRef(); + break; + case eUnit_Transform: + mValue.mCSSValueSharedList = aOther.mValue.mCSSValueSharedList; + mValue.mCSSValueSharedList->AddRef(); + break; + case eUnit_CSSValuePairList: + MOZ_ASSERT(aOther.mValue.mCSSValuePairList, + "value pair lists may not be null"); + mValue.mCSSValuePairList = aOther.mValue.mCSSValuePairList->Clone(); + break; + case eUnit_UnparsedString: + MOZ_ASSERT(aOther.mValue.mString, "expecting non-null string"); + mValue.mString = aOther.mValue.mString; + mValue.mString->AddRef(); + break; + case eUnit_ComplexColor: + MOZ_ASSERT(aOther.mValue.mComplexColor); + mValue.mComplexColor = aOther.mValue.mComplexColor; + mValue.mComplexColor->AddRef(); + break; + } + + return *this; +} + +void +StyleAnimationValue::SetNormalValue() +{ + FreeValue(); + mUnit = eUnit_Normal; +} + +void +StyleAnimationValue::SetAutoValue() +{ + FreeValue(); + mUnit = eUnit_Auto; +} + +void +StyleAnimationValue::SetNoneValue() +{ + FreeValue(); + mUnit = eUnit_None; +} + +void +StyleAnimationValue::SetIntValue(int32_t aInt, Unit aUnit) +{ + NS_ASSERTION(IsIntUnit(aUnit), "unit must be of integer type"); + FreeValue(); + mUnit = aUnit; + mValue.mInt = aInt; +} + +void +StyleAnimationValue::SetCoordValue(nscoord aLength) +{ + FreeValue(); + mUnit = eUnit_Coord; + mValue.mCoord = aLength; +} + +void +StyleAnimationValue::SetPercentValue(float aPercent) +{ + FreeValue(); + mUnit = eUnit_Percent; + mValue.mFloat = aPercent; + MOZ_ASSERT(!mozilla::IsNaN(mValue.mFloat)); +} + +void +StyleAnimationValue::SetFloatValue(float aFloat) +{ + FreeValue(); + mUnit = eUnit_Float; + mValue.mFloat = aFloat; + MOZ_ASSERT(!mozilla::IsNaN(mValue.mFloat)); +} + +void +StyleAnimationValue::SetColorValue(nscolor aColor) +{ + FreeValue(); + mUnit = eUnit_Color; + mValue.mCSSValue = new nsCSSValue(); + mValue.mCSSValue->SetColorValue(aColor); +} + +void +StyleAnimationValue::SetCurrentColorValue() +{ + FreeValue(); + mUnit = eUnit_CurrentColor; +} + +void +StyleAnimationValue::SetComplexColorValue(const StyleComplexColor& aColor) +{ + if (aColor.IsCurrentColor()) { + SetCurrentColorValue(); + } else if (aColor.IsNumericColor()) { + SetColorValue(aColor.mColor); + } else { + SetComplexColorValue(do_AddRef(new ComplexColorValue(aColor))); + } +} + +void +StyleAnimationValue::SetComplexColorValue( + already_AddRefed<ComplexColorValue> aValue) +{ + FreeValue(); + mUnit = eUnit_ComplexColor; + mValue.mComplexColor = aValue.take(); +} + +void +StyleAnimationValue::SetUnparsedStringValue(const nsString& aString) +{ + FreeValue(); + mUnit = eUnit_UnparsedString; + mValue.mString = nsCSSValue::BufferFromString(aString).take(); +} + +void +StyleAnimationValue::SetAndAdoptCSSValueValue(nsCSSValue *aValue, + Unit aUnit) +{ + FreeValue(); + MOZ_ASSERT(IsCSSValueUnit(aUnit), "bad unit"); + MOZ_ASSERT(aValue != nullptr, "values may not be null"); + mUnit = aUnit; + mValue.mCSSValue = aValue; // take ownership +} + +void +StyleAnimationValue::SetAndAdoptCSSValuePairValue(nsCSSValuePair *aValuePair, + Unit aUnit) +{ + FreeValue(); + MOZ_ASSERT(IsCSSValuePairUnit(aUnit), "bad unit"); + MOZ_ASSERT(aValuePair != nullptr, "value pairs may not be null"); + mUnit = aUnit; + mValue.mCSSValuePair = aValuePair; // take ownership +} + +void +StyleAnimationValue::SetAndAdoptCSSValueTripletValue( + nsCSSValueTriplet *aValueTriplet, Unit aUnit) +{ + FreeValue(); + MOZ_ASSERT(IsCSSValueTripletUnit(aUnit), "bad unit"); + MOZ_ASSERT(aValueTriplet != nullptr, "value pairs may not be null"); + mUnit = aUnit; + mValue.mCSSValueTriplet = aValueTriplet; // take ownership +} + +void +StyleAnimationValue::SetAndAdoptCSSRectValue(nsCSSRect *aRect, Unit aUnit) +{ + FreeValue(); + MOZ_ASSERT(IsCSSRectUnit(aUnit), "bad unit"); + MOZ_ASSERT(aRect != nullptr, "value pairs may not be null"); + mUnit = aUnit; + mValue.mCSSRect = aRect; // take ownership +} + +void +StyleAnimationValue::SetCSSValueArrayValue(nsCSSValue::Array* aValue, + Unit aUnit) +{ + FreeValue(); + MOZ_ASSERT(IsCSSValueArrayUnit(aUnit), "bad unit"); + MOZ_ASSERT(aValue != nullptr, + "not currently expecting any arrays to be null"); + mUnit = aUnit; + mValue.mCSSValueArray = aValue; + mValue.mCSSValueArray->AddRef(); +} + +void +StyleAnimationValue::SetAndAdoptCSSValueListValue(nsCSSValueList *aValueList, + Unit aUnit) +{ + FreeValue(); + MOZ_ASSERT(IsCSSValueListUnit(aUnit), "bad unit"); + MOZ_ASSERT(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 +StyleAnimationValue::SetTransformValue(nsCSSValueSharedList* aList) +{ + FreeValue(); + mUnit = eUnit_Transform; + mValue.mCSSValueSharedList = aList; + mValue.mCSSValueSharedList->AddRef(); +} + +void +StyleAnimationValue::SetAndAdoptCSSValuePairListValue( + nsCSSValuePairList *aValuePairList) +{ + FreeValue(); + MOZ_ASSERT(aValuePairList, "may not be null"); + mUnit = eUnit_CSSValuePairList; + mValue.mCSSValuePairList = aValuePairList; // take ownership +} + +void +StyleAnimationValue::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 (IsCSSValueArrayUnit(mUnit)) { + mValue.mCSSValueArray->Release(); + } else if (IsStringUnit(mUnit)) { + MOZ_ASSERT(mValue.mString, "expecting non-null string"); + mValue.mString->Release(); + } else if (mUnit == eUnit_ComplexColor) { + mValue.mComplexColor->Release(); + } +} + +bool +StyleAnimationValue::operator==(const StyleAnimationValue& aOther) const +{ + if (mUnit != aOther.mUnit) { + return false; + } + + switch (mUnit) { + case eUnit_Null: + case eUnit_Normal: + case eUnit_Auto: + case eUnit_None: + case eUnit_CurrentColor: + 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_Calc: + case eUnit_Color: + case eUnit_ObjectPosition: + case eUnit_URL: + case eUnit_DiscreteCSSValue: + MOZ_ASSERT(IsCSSValueUnit(mUnit), + "This clause is for handling nsCSSValue-backed units"); + 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_Shadow: + case eUnit_Filter: + case eUnit_BackgroundPositionCoord: + return nsCSSValueList::Equal(mValue.mCSSValueList, + aOther.mValue.mCSSValueList); + case eUnit_Shape: + return *mValue.mCSSValueArray == *aOther.mValue.mCSSValueArray; + case eUnit_Transform: + return *mValue.mCSSValueSharedList == *aOther.mValue.mCSSValueSharedList; + case eUnit_CSSValuePairList: + return nsCSSValuePairList::Equal(mValue.mCSSValuePairList, + aOther.mValue.mCSSValuePairList); + case eUnit_UnparsedString: + return (NS_strcmp(GetStringBufferValue(), + aOther.GetStringBufferValue()) == 0); + case eUnit_ComplexColor: + return *mValue.mComplexColor == *aOther.mValue.mComplexColor; + } + + NS_NOTREACHED("incomplete case"); + return false; +} |