Implement parsing of calc(). (Bug 363249) r=bzbarsky

This commit is contained in:
L. David Baron 2010-05-11 08:49:43 -07:00
Родитель 1a7c32ce5f
Коммит 328cba210e
1 изменённых файлов: 389 добавлений и 1 удалений

Просмотреть файл

@ -87,6 +87,7 @@
#include "nsAutoPtr.h"
#include "nsTArray.h"
#include "prlog.h"
#include "CSSCalc.h"
// Flags for ParseVariant method
#define VARIANT_KEYWORD 0x000001 // K
@ -114,6 +115,7 @@
#define VARIANT_IMAGE_RECT 0x01000000 // eCSSUnit_Function
// This is an extra bit that says that a VARIANT_ANGLE allows unitless zero:
#define VARIANT_ZERO_ANGLE 0x02000000 // unitless zero for angles
#define VARIANT_CALC 0x04000000 // eCSSUnit_Calc
// Common combinations of variants
#define VARIANT_AL (VARIANT_AUTO | VARIANT_LENGTH)
@ -479,6 +481,18 @@ protected:
PRInt32 aSourceType);
PRBool ParseBorderStyle();
PRBool ParseBorderWidth();
PRBool ParseCalc(nsCSSValue &aValue, PRInt32 aVariantMask);
PRBool ParseCalcAdditiveExpression(nsCSSValue& aValue,
PRInt32& aVariantMask);
PRBool ParseCalcMultiplicativeExpression(nsCSSValue& aValue,
PRInt32& aVariantMask,
PRBool *aHadFinalWS);
PRBool ParseCalcTerm(nsCSSValue& aValue, PRInt32& aVariantMask);
PRBool ParseCalcMinMax(nsCSSValue& aValue, nsCSSUnit aUnit,
PRInt32& aVariantMask);
PRBool RequireWhitespace();
// for 'clip' and '-moz-image-region'
PRBool ParseRect(nsCSSRect& aRect,
nsCSSProperty aPropID);
@ -4388,6 +4402,10 @@ CSSParserImpl::TranslateDimension(nsCSSValue& aValue,
return PR_FALSE;
}
// Note that this does include VARIANT_CALC, which is numeric. This is
// because calc() parsing, as proposed, drops range restrictions inside
// the calc() expression and clamps the result of the calculation to the
// range.
#define VARIANT_ALL_NONNUMERIC \
VARIANT_KEYWORD | \
VARIANT_COLOR | \
@ -4403,8 +4421,13 @@ CSSParserImpl::TranslateDimension(nsCSSValue& aValue,
VARIANT_SYSFONT | \
VARIANT_GRADIENT | \
VARIANT_CUBIC_BEZIER | \
VARIANT_ALL
VARIANT_ALL | \
VARIANT_CALC
// Note that callers passing VARIANT_CALC in aVariantMask will get
// full-range parsing inside the calc() expression, and the code that
// computes the calc will be required to clamp the resulting value to an
// appropriate range.
PRBool
CSSParserImpl::ParseNonNegativeVariant(nsCSSValue& aValue,
PRInt32 aVariantMask,
@ -4443,6 +4466,10 @@ CSSParserImpl::ParseNonNegativeVariant(nsCSSValue& aValue,
return PR_FALSE;
}
// Note that callers passing VARIANT_CALC in aVariantMask will get
// full-range parsing inside the calc() expression, and the code that
// computes the calc will be required to clamp the resulting value to an
// appropriate range.
PRBool
CSSParserImpl::ParsePositiveNonZeroVariant(nsCSSValue& aValue,
PRInt32 aVariantMask,
@ -4663,6 +4690,14 @@ CSSParserImpl::ParseVariant(nsCSSValue& aValue,
return ParseTransitionTimingFunctionValues(aValue);
}
}
if ((aVariantMask & VARIANT_CALC) &&
(eCSSToken_Function == tk->mType) &&
(tk->mIdent.LowerCaseEqualsLiteral("-moz-calc") ||
tk->mIdent.LowerCaseEqualsLiteral("-moz-min") ||
tk->mIdent.LowerCaseEqualsLiteral("-moz-max"))) {
// calc() currently allows only lengths and percents inside it.
return ParseCalc(aValue, aVariantMask & VARIANT_LP);
}
UngetToken();
return PR_FALSE;
@ -7152,6 +7187,359 @@ CSSParserImpl::ParseBorderColors(nsCSSValueList** aResult,
return PR_FALSE;
}
// Parse the top level of a calc() expression, which can be calc(),
// min(), or max().
PRBool
CSSParserImpl::ParseCalc(nsCSSValue &aValue, PRInt32 aVariantMask)
{
// Parsing calc expressions requires, in a number of cases, looking
// for a token that is *either* a value of the property or a number.
// This can be done without lookahead when we assume that the property
// values cannot themselves be numbers.
NS_ASSERTION(!(aVariantMask & VARIANT_NUMBER), "unexpected variant mask");
NS_ABORT_IF_FALSE(aVariantMask != 0, "unexpected variant mask");
nsCSSUnit unit;
if (mToken.mIdent.LowerCaseEqualsLiteral("-moz-min")) {
unit = eCSSUnit_Calc_Minimum;
} else if (mToken.mIdent.LowerCaseEqualsLiteral("-moz-max")) {
unit = eCSSUnit_Calc_Maximum;
} else {
NS_ASSERTION(mToken.mIdent.LowerCaseEqualsLiteral("-moz-calc"),
"unexpected function");
unit = eCSSUnit_Calc;
}
if (unit != eCSSUnit_Calc) {
return ParseCalcMinMax(aValue, unit, aVariantMask);
}
// One-iteration loop so we can break to the error-handling case.
do {
// The toplevel of a calc() is always an nsCSSValue::Array of length 1.
nsRefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(1);
if (!arr) {
mScanner.SetLowLevelError(NS_ERROR_OUT_OF_MEMORY);
break;
}
if (!ParseCalcAdditiveExpression(arr->Item(0), aVariantMask))
break;
if (!ExpectSymbol(')', PR_TRUE))
break;
aValue.SetArrayValue(arr, eCSSUnit_Calc);
return PR_TRUE;
} while (PR_FALSE);
SkipUntil(')');
return PR_FALSE;
}
// We optimize away the <value-expression> production given that
// ParseVariant consumes initial whitespace and we call
// ExpectSymbol(')') with PR_TRUE for aSkipWS.
// * If aVariantMask is VARIANT_NUMBER, this function parses the
// <number-additive-expression> production.
// * If aVariantMask does not contain VARIANT_NUMBER, this function
// parses the <value-additive-expression> production.
// * Otherwise (VARIANT_NUMBER and other bits) this function parses
// whichever one of the productions matches ***and modifies
// aVariantMask*** to reflect which one it has parsed by either
// removing VARIANT_NUMBER or removing all other bits.
// It does so iteratively, but builds the correct recursive
// data structure.
PRBool
CSSParserImpl::ParseCalcAdditiveExpression(nsCSSValue& aValue,
PRInt32& aVariantMask)
{
NS_ABORT_IF_FALSE(aVariantMask != 0, "unexpected variant mask");
nsCSSValue *storage = &aValue;
for (;;) {
PRBool haveWS;
if (!ParseCalcMultiplicativeExpression(*storage, aVariantMask, &haveWS))
return PR_FALSE;
if (!haveWS || !GetToken(PR_FALSE))
return PR_TRUE;
nsCSSUnit unit;
if (mToken.IsSymbol('+')) {
unit = eCSSUnit_Calc_Plus;
} else if (mToken.IsSymbol('-')) {
unit = eCSSUnit_Calc_Minus;
} else {
UngetToken();
return PR_TRUE;
}
if (!RequireWhitespace())
return PR_FALSE;
nsRefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(2);
if (!arr) {
mScanner.SetLowLevelError(NS_ERROR_OUT_OF_MEMORY);
return PR_FALSE;
}
arr->Item(0) = aValue;
storage = &arr->Item(1);
aValue.SetArrayValue(arr, unit);
}
}
struct ReduceNumberCalcOps : public mozilla::css::BasicFloatCalcOps
{
struct ComputeData {};
static result_type ComputeLeafValue(const nsCSSValue& aValue,
const ComputeData& aClosure)
{
NS_ABORT_IF_FALSE(aValue.GetUnit() == eCSSUnit_Number, "unexpected unit");
return aValue.GetFloatValue();
}
static float ComputeNumber(const nsCSSValue& aValue)
{
return mozilla::css::ComputeCalc<ReduceNumberCalcOps>(
aValue, ReduceNumberCalcOps::ComputeData());
}
};
// * If aVariantMask is VARIANT_NUMBER, this function parses the
// <number-multiplicative-expression> production.
// * If aVariantMask does not contain VARIANT_NUMBER, this function
// parses the <value-multiplicative-expression> production.
// * Otherwise (VARIANT_NUMBER and other bits) this function parses
// whichever one of the productions matches ***and modifies
// aVariantMask*** to reflect which one it has parsed by either
// removing VARIANT_NUMBER or removing all other bits.
// It does so iteratively, but builds the correct recursive data
// structure.
// This function always consumes *trailing* whitespace when it returns
// true; whether there was any such whitespace is returned in the
// aHadFinalWS parameter.
PRBool
CSSParserImpl::ParseCalcMultiplicativeExpression(nsCSSValue& aValue,
PRInt32& aVariantMask,
PRBool *aHadFinalWS)
{
NS_ABORT_IF_FALSE(aVariantMask != 0, "unexpected variant mask");
PRBool gotValue = PR_FALSE; // already got the part with the unit
PRBool afterDivision = PR_FALSE;
nsCSSValue *storage = &aValue;
for (;;) {
PRInt32 variantMask;
if (afterDivision || gotValue) {
variantMask = VARIANT_NUMBER;
} else {
variantMask = aVariantMask | VARIANT_NUMBER;
}
if (!ParseCalcTerm(*storage, variantMask))
return PR_FALSE;
NS_ABORT_IF_FALSE(variantMask != 0,
"ParseCalcTerm did not set variantMask appropriately");
NS_ABORT_IF_FALSE(!(variantMask & VARIANT_NUMBER) ||
!(variantMask & ~PRInt32(VARIANT_NUMBER)),
"ParseCalcTerm did not set variantMask appropriately");
if (variantMask & VARIANT_NUMBER) {
// Simplify the value immediately so we can check for division by
// zero.
float number = mozilla::css::ComputeCalc<ReduceNumberCalcOps>(
*storage, ReduceNumberCalcOps::ComputeData());
if (number == 0.0 && afterDivision)
return PR_FALSE;
storage->SetFloatValue(number, eCSSUnit_Number);
} else {
gotValue = PR_TRUE;
if (storage != &aValue) {
// Simplify any numbers in the Times_L position (which are
// not simplified by the check above).
NS_ABORT_IF_FALSE(storage == &aValue.GetArrayValue()->Item(1),
"unexpected relationship to current storage");
nsCSSValue &leftValue = aValue.GetArrayValue()->Item(0);
float number = mozilla::css::ComputeCalc<ReduceNumberCalcOps>(
leftValue, ReduceNumberCalcOps::ComputeData());
leftValue.SetFloatValue(number, eCSSUnit_Number);
}
}
PRBool hadWS = RequireWhitespace();
if (!GetToken(PR_FALSE)) {
*aHadFinalWS = hadWS;
break;
}
nsCSSUnit unit;
if (mToken.IsSymbol('*')) {
unit = gotValue ? eCSSUnit_Calc_Times_R : eCSSUnit_Calc_Times_L;
afterDivision = PR_FALSE;
} else if (mToken.IsSymbol('/')) {
unit = eCSSUnit_Calc_Divided;
afterDivision = PR_TRUE;
} else {
UngetToken();
*aHadFinalWS = hadWS;
break;
}
nsRefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(2);
if (!arr) {
mScanner.SetLowLevelError(NS_ERROR_OUT_OF_MEMORY);
return PR_FALSE;
}
arr->Item(0) = aValue;
storage = &arr->Item(1);
aValue.SetArrayValue(arr, unit);
}
// Adjust aVariantMask (see comments above function) to reflect which
// option we took.
if (aVariantMask & VARIANT_NUMBER) {
if (gotValue) {
aVariantMask &= ~PRInt32(VARIANT_NUMBER);
} else {
aVariantMask = VARIANT_NUMBER;
}
} else {
if (!gotValue) {
// We had to find a value, but we didn't.
return PR_FALSE;
}
}
return PR_TRUE;
}
// * If aVariantMask is VARIANT_NUMBER, this function parses the
// <number-term> production.
// * If aVariantMask does not contain VARIANT_NUMBER, this function
// parses the <value-term> production.
// * Otherwise (VARIANT_NUMBER and other bits) this function parses
// whichever one of the productions matches ***and modifies
// aVariantMask*** to reflect which one it has parsed by either
// removing VARIANT_NUMBER or removing all other bits.
PRBool
CSSParserImpl::ParseCalcTerm(nsCSSValue& aValue, PRInt32& aVariantMask)
{
NS_ABORT_IF_FALSE(aVariantMask != 0, "unexpected variant mask");
if (!GetToken(PR_TRUE))
return PR_FALSE;
// Either an additive expression in parentheses...
if (mToken.IsSymbol('(')) {
if (!ParseCalcAdditiveExpression(aValue, aVariantMask) ||
!ExpectSymbol(')', PR_TRUE)) {
SkipUntil(')');
return PR_FALSE;
}
return PR_TRUE;
}
// ... or a min() or max() expression
if (mToken.mType == eCSSToken_Function &&
(mToken.mIdent.LowerCaseEqualsLiteral("min") ||
mToken.mIdent.LowerCaseEqualsLiteral("max"))) {
nsCSSUnit unit = mToken.mIdent.LowerCaseEqualsLiteral("min")
? eCSSUnit_Calc_Minimum : eCSSUnit_Calc_Maximum;
return ParseCalcMinMax(aValue, unit, aVariantMask);
}
// ... or just a value
UngetToken();
if (!ParseVariant(aValue, aVariantMask, nsnull)) {
return PR_FALSE;
}
// If we did the value parsing, we need to adjust aVariantMask to
// reflect which option we took (see above).
if (aVariantMask & VARIANT_NUMBER) {
if (aValue.GetUnit() == eCSSUnit_Number) {
aVariantMask = VARIANT_NUMBER;
} else {
aVariantMask &= ~PRInt32(VARIANT_NUMBER);
}
}
return PR_TRUE;
}
// This function handles and modifies aVariantMask exactly as
// described for ParcCalcTerm above.
PRBool
CSSParserImpl::ParseCalcMinMax(nsCSSValue& aValue, nsCSSUnit aUnit,
PRInt32& aVariantMask)
{
NS_ABORT_IF_FALSE(aVariantMask != 0, "unexpected variant mask");
NS_ASSERTION(aUnit == eCSSUnit_Calc_Minimum ||
aUnit == eCSSUnit_Calc_Maximum,
"unexpected unit");
NS_ASSERTION(mToken.mType == eCSSToken_Function, "unexpected current token");
NS_ASSERTION(aUnit != eCSSUnit_Calc_Minimum ||
mToken.mIdent.LowerCaseEqualsLiteral("min") ||
mToken.mIdent.LowerCaseEqualsLiteral("-moz-min"),
"unexpected current token");
NS_ASSERTION(aUnit != eCSSUnit_Calc_Maximum ||
mToken.mIdent.LowerCaseEqualsLiteral("max") ||
mToken.mIdent.LowerCaseEqualsLiteral("-moz-max"),
"unexpected current token");
nsAutoTArray<nsCSSValue, 4> values;
for (;;) {
nsCSSValue *v = values.AppendElement();
if (!v) {
mScanner.SetLowLevelError(NS_ERROR_OUT_OF_MEMORY);
return PR_FALSE;
}
if (!ParseCalcAdditiveExpression(*v, aVariantMask))
return PR_FALSE;
NS_ABORT_IF_FALSE(!(aVariantMask & VARIANT_NUMBER) ||
!(aVariantMask & ~PRInt32(VARIANT_NUMBER)),
"parsing additive expr did not adjust variant mask");
NS_ABORT_IF_FALSE(aVariantMask != 0, "unexpected variant mask");
if (ExpectSymbol(',', PR_TRUE))
continue;
if (ExpectSymbol(')', PR_TRUE))
break;
SkipUntil(')');
return PR_FALSE;
}
// We allow min() and max() to take 1 or more arguments; the code
// above already ensures that.
NS_ABORT_IF_FALSE(values.Length() > 0, "unexpected length");
nsRefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(values.Length());
if (!arr) {
mScanner.SetLowLevelError(NS_ERROR_OUT_OF_MEMORY);
return PR_FALSE;
}
for (PRUint32 i = 0, i_end = values.Length(); i < i_end; ++i) {
arr->Item(i) = values[i];
}
aValue.SetArrayValue(arr, aUnit);
return PR_TRUE;
}
// This function consumes all consecutive whitespace and returns whether
// there was any.
PRBool
CSSParserImpl::RequireWhitespace()
{
if (!GetToken(PR_FALSE))
return PR_FALSE;
if (mToken.mType != eCSSToken_WhiteSpace) {
UngetToken();
return PR_FALSE;
}
// Skip any additional whitespace tokens.
if (GetToken(PR_TRUE)) {
UngetToken();
}
return PR_TRUE;
}
PRBool
CSSParserImpl::ParseRect(nsCSSRect& aRect, nsCSSProperty aPropID)
{