зеркало из https://github.com/mozilla/gecko-dev.git
Implement parsing of calc(). (Bug 363249) r=bzbarsky
This commit is contained in:
Родитель
1a7c32ce5f
Коммит
328cba210e
|
@ -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)
|
||||
{
|
||||
|
|
Загрузка…
Ссылка в новой задаче