/* -*- 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 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 arr = nsCSSValue::Array::Create(nargs + 1); arr->Item(0).SetIntValue(aTransformFunction, eCSSUnit_Enumerated); return arr.forget(); } static already_AddRefed ToPrimitive(nsCSSValue::Array* aArray) { nsCSSKeyword tfunc = nsStyleTransformMatrix::TransformFunctionOf(aArray); nsCSSKeyword primitive = ToPrimitive(tfunc); RefPtr 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 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 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 MOZ_ALWAYS_INLINE T EnsureNotNan(T aValue) { return aValue; } template<> MOZ_ALWAYS_INLINE float EnsureNotNan(float aValue) { // This would benefit from a MOZ_FLOAT_IS_NaN if we had one. return MOZ_LIKELY(!mozilla::IsNaN(aValue)) ? aValue : 0; } template<> MOZ_ALWAYS_INLINE double EnsureNotNan(double aValue) { return MOZ_LIKELY(!mozilla::IsNaN(aValue)) ? aValue : 0; } template T RestrictValue(uint32_t aRestrictions, T aValue) { T result = EnsureNotNan(aValue); switch (aRestrictions) { case 0: break; case CSS_PROPERTY_VALUE_NONNEGATIVE: if (result < 0) { result = 0; } break; case CSS_PROPERTY_VALUE_AT_LEAST_ONE: if (result < 1) { result = 1; } break; default: MOZ_ASSERT(false, "bad value restriction"); break; } return result; } template 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 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 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 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 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 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 none(AddTransformLists(0, list2, 0, list2)); aDistance = ComputeTransformListDistance(none, list2); } else if (list2->mValue.GetUnit() == eCSSUnit_None) { nsAutoPtr 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& aHead, UniquePtr&& 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 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 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(); 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 StyleAnimationValue::AppendTransformFunction(nsCSSKeyword aTransformFunction, nsCSSValueList**& aListTail) { RefPtr arr = AppendFunction(aTransformFunction); nsCSSValueList *item = new nsCSSValueList; item->mValue.SetArrayValue(arr, eCSSUnit_Function); *aListTail = item; aListTail = &item->mNext; return arr.forget(); } template 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 result; nsCSSValueList **resultTail = getter_Transfers(result); RefPtr 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 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 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(); 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 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 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 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 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 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 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 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* 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 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 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 ). 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 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 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 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 result; nsCSSValueList **resultTail = getter_Transfers(result); do { RefPtr 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 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::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 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 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 result; nsCSSValueList* tail = nullptr; while (aShadow1 && aShadow2) { UniquePtr 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 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 AddWeightedFilterList(double aCoeff1, const nsCSSValueList* aList1, double aCoeff2, const nsCSSValueList* aList2, ColorAdditionType aColorAdditionType) { UniquePtr 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 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(); 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 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 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 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(*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 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 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 result; nsCSSValueList **resultTail = getter_Transfers(result); for (uint32_t i = 0, i_end = EuclidLCM(len1, len2); i != i_end; ++i) { const nsCSSValue &v1 = list1->mValue; const nsCSSValue &v2 = list2->mValue; 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 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 result = AddShapeFunction(aProperty, aCoeff1, aValue1.GetCSSValueArrayValue(), aCoeff2, aValue2.GetCSSValueArrayValue()); if (!result) { return false; } aResultValue.SetCSSValueArrayValue(result, eUnit_Shape); return true; } case eUnit_Filter: { UniquePtr 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 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 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 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 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 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(); 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 BuildStyleRule(nsCSSPropertyID aProperty, dom::Element* aTargetElement, const nsAString& aSpecifiedValue, bool aUseSVGMode) { // Set up an empty CSS Declaration RefPtr declaration(new css::Declaration()); declaration->InitializeEmpty(); bool changed; // ignored, but needed as outparam for ParseProperty nsIDocument* doc = aTargetElement->OwnerDoc(); nsCOMPtr baseURI = aTargetElement->GetBaseURI(); nsCSSParser parser(doc->CSSLoader()); 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 rule = new css::StyleRule(nullptr, declaration, 0, 0); return rule.forget(); } already_AddRefed 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 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 rule = new css::StyleRule(nullptr, declaration, 0, 0); return rule.forget(); } static bool ComputeValuesFromStyleContext( nsCSSPropertyID aProperty, CSSEnabledState aEnabledState, nsStyleContext* aStyleContext, nsTArray& 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& 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 tmpStyleContext; if (aIsContextSensitive) { MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty), "to correctly set aIsContextSensitive for shorthand properties, " "this code must be adjusted"); nsCOMArray 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 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 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 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 bool ComputeValuesFromSpecifiedValue( nsCSSPropertyID aProperty, CSSEnabledState aEnabledState, dom::Element* aTargetElement, nsStyleContext* aStyleContext, T& aSpecifiedValue, bool aUseSVGMode, nsTArray& 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 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& 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& aResult) { return ComputeValuesFromSpecifiedValue(aProperty, aEnabledState, aTargetElement, aStyleContext, aSpecifiedValue, aUseSVGMode, aResult); } /* static */ bool StyleAnimationValue::ComputeValues( nsCSSPropertyID aProperty, CSSEnabledState aEnabledState, nsStyleContext* aStyleContext, const RawServoDeclarationBlock& aDeclarations, nsTArray& 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(&aDeclarations); RefPtr computedValues = Servo_RestyleWithAddedDeclaration(declarations, previousStyle).Consume(); if (!computedValues) { return false; } RefPtr 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 computedList = aComputedValue.TakeCSSValueListValue(); if (computedList) { aSpecifiedValue.AdoptListValue(Move(computedList)); } else { aSpecifiedValue.SetNoneValue(); } } break; case eUnit_CSSValuePairList: { UniquePtr 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 inline const T& StyleDataAtOffset(const void* aStyleStruct, ptrdiff_t aOffset) { return *reinterpret_cast( reinterpret_cast(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 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 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 // 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 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 // 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 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 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 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 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 functionArray; switch (shape->GetShapeType()) { case StyleBasicShapeType::Circle: case StyleBasicShapeType::Ellipse: { const nsTArray& 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& 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& 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 radiusArray = nsCSSValue::Array::Create(4); const nsStyleCorners& radii = shape->GetRadius(); NS_FOR_CSS_FULL_CORNERS(corner) { auto pair = MakeUnique(); 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(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(styleStruct)-> GetComputedColumnRuleWidth()); break; case eCSSProperty_column_count: { const nsStyleColumn *styleColumn = static_cast(styleStruct); if (styleColumn->mColumnCount == NS_STYLE_COLUMN_COUNT_AUTO) { aComputedValue.SetAutoValue(); } else { aComputedValue.SetIntValue(styleColumn->mColumnCount, eUnit_Integer); } break; } case eCSSProperty_order: { const nsStylePosition *stylePosition = static_cast(styleStruct); aComputedValue.SetIntValue(stylePosition->mOrder, eUnit_Integer); break; } case eCSSProperty_border_spacing: { const nsStyleTableBorder *styleTableBorder = static_cast(styleStruct); nsAutoPtr 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(styleStruct); nsAutoPtr 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(styleStruct); nsAutoPtr 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(styleStruct); if (!svg->mStrokeDasharray.IsEmpty()) { nsAutoPtr 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(styleStruct)->mFont.stretch; static_assert(NS_STYLE_FONT_STRETCH_ULTRA_CONDENSED == -4 && NS_STYLE_FONT_STRETCH_ULTRA_EXPANDED == 4, "font stretch constants not as expected"); if (stretch < NS_STYLE_FONT_STRETCH_ULTRA_CONDENSED || stretch > NS_STYLE_FONT_STRETCH_ULTRA_EXPANDED) { return false; } aComputedValue.SetIntValue(stretch, eUnit_Enumerated); return true; } case eCSSProperty_font_weight: { uint16_t weight = static_cast(styleStruct)->mFont.weight; if (weight % 100 != 0) { return false; } aComputedValue.SetIntValue(weight, eUnit_Integer); return true; } case eCSSProperty_image_region: { const nsStyleList *list = static_cast(styleStruct); const nsRect &srect = list->mImageRegion; if (srect.IsEmpty()) { aComputedValue.SetAutoValue(); break; } nsCSSRect *vrect = new nsCSSRect; 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(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(styleStruct); nsAutoPtr val(new nsCSSValue); SetPositionValue(stylePos->mObjectPosition, *val); aComputedValue.SetAndAdoptCSSValueValue(val.forget(), eUnit_ObjectPosition); break; } case eCSSProperty_background_position_x: { const nsStyleImageLayers& layers = static_cast(styleStruct)->mImage; ExtractImageLayerPositionXList(layers, aComputedValue); break; } case eCSSProperty_background_position_y: { const nsStyleImageLayers& layers = static_cast(styleStruct)->mImage; ExtractImageLayerPositionYList(layers, aComputedValue); break; } #ifdef MOZ_ENABLE_MASK_AS_SHORTHAND case eCSSProperty_mask_position_x: { const nsStyleImageLayers& layers = static_cast(styleStruct)->mMask; ExtractImageLayerPositionXList(layers, aComputedValue); break; } case eCSSProperty_mask_position_y: { const nsStyleImageLayers& layers = static_cast(styleStruct)->mMask; ExtractImageLayerPositionYList(layers, aComputedValue); break; } #endif case eCSSProperty_background_size: { const nsStyleImageLayers& layers = static_cast(styleStruct)->mImage; ExtractImageLayerSizePairList(layers, aComputedValue); break; } #ifdef MOZ_ENABLE_MASK_AS_SHORTHAND case eCSSProperty_mask_size: { const nsStyleImageLayers& layers = static_cast(styleStruct)->mMask; ExtractImageLayerSizePairList(layers, aComputedValue); break; } #endif case eCSSProperty_clip_path: { const nsStyleSVGReset* svgReset = static_cast(styleStruct); const StyleClipPath& clipPath = svgReset->mClipPath; const StyleShapeSourceType type = clipPath.GetType(); if (type == StyleShapeSourceType::URL) { auto result = MakeUnique(); 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 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(styleStruct); const nsTArray& filters = effects->mFilters; nsAutoPtr result; nsCSSValueList **resultTail = getter_Transfers(result); for (uint32_t i = 0; i < filters.Length(); ++i) { nsCSSValueList *item = new nsCSSValueList; *resultTail = item; resultTail = &item->mNext; const nsStyleFilter& filter = filters[i]; int32_t type = filter.GetType(); if (type == NS_STYLE_FILTER_URL) { 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 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(styleStruct); nsAutoPtr result; if (display->mSpecifiedTransform) { // Clone, and convert all lengths (not percents) to pixels. nsCSSValueList **resultTail = getter_Transfers(result); for (const nsCSSValueList *l = display->mSpecifiedTransform->mHead; l; l = l->mNext) { nsCSSValueList *clone = new nsCSSValueList; *resultTail = clone; resultTail = &clone->mNext; SubstitutePixelValues(aStyleContext, l->mValue, clone->mValue); } } else { result = new nsCSSValueList(); result->mValue.SetNoneValue(); } aComputedValue.SetTransformValue( new nsCSSValueSharedList(result.forget())); break; } default: MOZ_ASSERT(false, "missing property implementation"); return false; }; return true; case eStyleAnimType_Coord: { const nsStyleCoord& coord = StyleDataAtOffset(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(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(styleStruct, ssOffset); uint8_t fullCorner = animType - eStyleAnimType_Corner_TopLeft; const nsStyleCoord &horiz = corners.Get(NS_FULL_TO_HALF_CORNER(fullCorner, false)); const nsStyleCoord &vert = corners.Get(NS_FULL_TO_HALF_CORNER(fullCorner, true)); nsAutoPtr pair(new nsCSSValuePair); if (!StyleCoordToCSSValue(horiz, pair->mXValue) || !StyleCoordToCSSValue(vert, pair->mYValue)) { return false; } aComputedValue.SetAndAdoptCSSValuePairValue(pair.forget(), eUnit_CSSValuePair); return true; } case eStyleAnimType_nscoord: aComputedValue.SetCoordValue( StyleDataAtOffset(styleStruct, ssOffset)); return true; case eStyleAnimType_float: aComputedValue.SetFloatValue( StyleDataAtOffset(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(styleStruct, ssOffset)); return true; case eStyleAnimType_ComplexColor: { aComputedValue.SetComplexColorValue( StyleDataAtOffset(styleStruct, ssOffset)); return true; } case eStyleAnimType_PaintServer: { const nsStyleSVGPaint& paint = StyleDataAtOffset(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 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 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>(styleStruct, ssOffset); if (!shadowArray) { aComputedValue.SetAndAdoptCSSValueListValue(nullptr, eUnit_Shadow); return true; } nsAutoPtr result; nsCSSValueList **resultTail = getter_Transfers(result); for (uint32_t i = 0, i_end = shadowArray->Length(); i < i_end; ++i) { AppendCSSShadowValue(shadowArray->ShadowAt(i), resultTail); } aComputedValue.SetAndAdoptCSSValueListValue(result.forget(), eUnit_Shadow); return true; } case eStyleAnimType_Discrete: { if (aProperty == eCSSProperty_visibility) { aComputedValue.SetIntValue( static_cast(styleStruct)->mVisible, eUnit_Visibility); return true; } auto cssValue = MakeUnique(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 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; }