gecko/layout/style/nsStyleAnimation.cpp

3532 lines
126 KiB
C++
Raw Normal View History

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