internal bool ParseTransitionTimingFunctionValues(ref nsCSSValue aValue) { Debug.Assert(!mHavePushBack && mToken.mType == nsCSSTokenType.Function && mToken.mIdentStr.LowerCaseEqualsLiteral("cubic-bezier"), "unexpected initial state"); nsCSSValue[] val = new nsCSSValue[4]; float x1 = 0, x2 = 0, y1 = 0, y2 = 0; if (!ParseTransitionTimingFunctionValueComponent(ref x1, ',', true) || !ParseTransitionTimingFunctionValueComponent(ref y1, ',', false) || !ParseTransitionTimingFunctionValueComponent(ref x2, ',', true) || !ParseTransitionTimingFunctionValueComponent(ref y2, ')', false)) { return false; } val[0].SetFloatValue(x1, nsCSSUnit.Number); val[1].SetFloatValue(y1, nsCSSUnit.Number); val[2].SetFloatValue(x2, nsCSSUnit.Number); val[3].SetFloatValue(y2, nsCSSUnit.Number); aValue.SetArrayValue(val, nsCSSUnit.CubicBezier); return true; }
bool ParseBackgroundPositionValues(ref nsCSSValue aOut, bool aAcceptsInherit) { // css3-background allows positions to be defined as offsets // from an edge. There can be 2 keywords and 2 offsets given. These // four 'values' are stored in an array in the following order: // [keyword offset keyword offset]. If a keyword or offset isn't // parsed the value of the corresponding array element is set // to nsCSSUnit.Null by a call to nsCSSValue.Reset(). if (aAcceptsInherit && ParseVariant(ref aOut, VARIANT_INHERIT, null)) { return true; } nsCSSValue[] value = new nsCSSValue[4]; aOut.SetArrayValue(value, nsCSSUnit.Array); // The following clarifies organisation of the array. nsCSSValue xEdge = value[0], xOffset = value[1], yEdge = value[2], yOffset = value[3]; // Parse all the values into the array. uint32_t valueCount = 0; for (int32_t i = 0; i < 4; i++) { if (!ParseVariant(ref value[i], VARIANT_LPCALC | VARIANT_KEYWORD, nsCSSProps.kBackgroundPositionKTable)) { break; } ++valueCount; } switch (valueCount) { case 4: // "If three or four values are given, then each <percentage> or <length> // represents an offset and must be preceded by a keyword, which specifies // from which edge the offset is given." if (nsCSSUnit.Enumerated != xEdge.GetUnit() || BG_CENTER == xEdge.GetIntValue() || nsCSSUnit.Enumerated == xOffset.GetUnit() || nsCSSUnit.Enumerated != yEdge.GetUnit() || BG_CENTER == yEdge.GetIntValue() || nsCSSUnit.Enumerated == yOffset.GetUnit()) { return false; } break; case 3: // "If three or four values are given, then each <percentage> or<length> // represents an offset and must be preceded by a keyword, which specifies // from which edge the offset is given." ... "If three values are given, // the missing offset is assumed to be zero." if (nsCSSUnit.Enumerated != value[1].GetUnit()) { // keyword offset keyword // Second value is non-keyword, thus first value must be a non-center // keyword. if (nsCSSUnit.Enumerated != value[0].GetUnit() || BG_CENTER == value[0].GetIntValue()) { return false; } // Remaining value must be a keyword. if (nsCSSUnit.Enumerated != value[2].GetUnit()) { return false; } yOffset.Reset(); // Everything else is in the correct position. } else if (nsCSSUnit.Enumerated != value[2].GetUnit()) { // keyword keyword offset // Third value is non-keyword, thus second value must be non-center // keyword. if (BG_CENTER == value[1].GetIntValue()) { return false; } // Remaining value must be a keyword. if (nsCSSUnit.Enumerated != value[0].GetUnit()) { return false; } // Move the values to the correct position in the array. value[3] = value[2]; // yOffset value[2] = value[1]; // yEdge value[1].Reset(); // xOffset } else { return false; } break; case 2: // "If two values are given and at least one value is not a keyword, then // the first value represents the horizontal position (or offset) and the // second represents the vertical position (or offset)" if (nsCSSUnit.Enumerated == value[0].GetUnit()) { if (nsCSSUnit.Enumerated == value[1].GetUnit()) { // keyword keyword value[2] = value[1]; // move yEdge to correct position xOffset.Reset(); yOffset.Reset(); } else { // keyword offset // First value must represent horizontal position. if (((BG_TOP | BG_BOTTOM) & value[0].GetIntValue()) != 0) { return false; } value[3] = value[1]; // move yOffset to correct position xOffset.Reset(); yEdge.Reset(); } } else { if (nsCSSUnit.Enumerated == value[1].GetUnit()) { // offset keyword // Second value must represent vertical position. if (((BG_LEFT | BG_RIGHT) & value[1].GetIntValue()) != 0) { return false; } value[2] = value[1]; // move yEdge to correct position value[1] = value[0]; // move xOffset to correct position xEdge.Reset(); yOffset.Reset(); } else { // offset offset value[3] = value[1]; // move yOffset to correct position value[1] = value[0]; // move xOffset to correct position xEdge.Reset(); yEdge.Reset(); } } break; case 1: // "If only one value is specified, the second value is assumed to be // center." if (nsCSSUnit.Enumerated == value[0].GetUnit()) { xOffset.Reset(); } else { value[1] = value[0]; // move xOffset to correct position xEdge.Reset(); } yEdge.SetIntValue(nsStyle.BG_POSITION_CENTER, nsCSSUnit.Enumerated); yOffset.Reset(); break; default: return false; } // For compatibility with CSS2.1 code the edges can be unspecified. // Unspecified edges are recorded as NULL. Debug.Assert((nsCSSUnit.Enumerated == xEdge.GetUnit() || nsCSSUnit.Null == xEdge.GetUnit()) && (nsCSSUnit.Enumerated == yEdge.GetUnit() || nsCSSUnit.Null == yEdge.GetUnit()) && nsCSSUnit.Enumerated != xOffset.GetUnit() && nsCSSUnit.Enumerated != yOffset.GetUnit(), "Unexpected units"); // Keywords in first and second pairs can not both be vertical or // horizontal keywords. (eg. left right, bottom top). Additionally, // non-center keyword can not be duplicated (eg. left left). int32_t xEdgeEnum = xEdge.GetUnit() == nsCSSUnit.Enumerated ? xEdge.GetIntValue() : 0; int32_t yEdgeEnum = yEdge.GetUnit() == nsCSSUnit.Enumerated ? yEdge.GetIntValue() : 0; if ((xEdgeEnum | yEdgeEnum) == (BG_LEFT | BG_RIGHT) || (xEdgeEnum | yEdgeEnum) == (BG_TOP | BG_BOTTOM) || ((xEdgeEnum & yEdgeEnum & ~BG_CENTER) != 0)) { return false; } // The values could be in an order that is different than expected. // eg. x contains vertical information, y contains horizontal information. // Swap if incorrect order. if (((xEdgeEnum & (BG_TOP | BG_BOTTOM)) != 0) || ((yEdgeEnum & (BG_LEFT | BG_RIGHT)) != 0)) { nsCSSValue swapEdge = xEdge; nsCSSValue swapOffset = xOffset; xEdge = yEdge; xOffset = yOffset; yEdge = swapEdge; yOffset = swapOffset; } return true; }
internal bool ParseShadowItem(ref nsCSSValue aValue, bool aIsBoxShadow) { // A shadow list item is an array, with entries in this sequence: const int IndexX = 0; const int IndexY = 1; const int IndexRadius = 2; const int IndexSpread = 3; const int IndexColor = 4; const int IndexInset = 5; nsCSSValue[] val = new nsCSSValue[6]; if (aIsBoxShadow) { // Optional inset keyword (ignore errors) ParseVariant(ref val[IndexInset], VARIANT_KEYWORD, nsCSSProps.kBoxShadowTypeKTable); } var xOrColor = new nsCSSValue(); bool haveColor = false; if (!ParseVariant(ref xOrColor, VARIANT_COLOR | VARIANT_LENGTH | VARIANT_CALC, null)) { return false; } if (xOrColor.IsLengthUnit() || xOrColor.IsCalcUnit()) { val[IndexX] = xOrColor; } else { // Must be a color (as string or color value) Debug.Assert(xOrColor.GetUnit() == nsCSSUnit.Ident || xOrColor.GetUnit() == nsCSSUnit.Color || xOrColor.GetUnit() == nsCSSUnit.EnumColor, "Must be a color value"); val[IndexColor] = xOrColor; haveColor = true; // X coordinate mandatory after color if (!ParseVariant(ref val[IndexX], VARIANT_LENGTH | VARIANT_CALC, null)) { return false; } } // Y coordinate; mandatory if (!ParseVariant(ref val[IndexY], VARIANT_LENGTH | VARIANT_CALC, null)) { return false; } // Optional radius. Ignore errors except if they pass a negative // value which we must reject. If we use ParseNonNegativeVariant // we can't tell the difference between an unspecified radius // and a negative radius. if (ParseVariant(ref val[IndexRadius], VARIANT_LENGTH | VARIANT_CALC, null) && val[IndexRadius].IsLengthUnit() && val[IndexRadius].GetFloatValue() < 0) { return false; } if (aIsBoxShadow) { // Optional spread ParseVariant(ref val[IndexSpread], VARIANT_LENGTH | VARIANT_CALC, null); } if (!haveColor) { // Optional color ParseVariant(ref val[IndexColor], VARIANT_COLOR, null); } if (aIsBoxShadow && val[IndexInset].GetUnit() == nsCSSUnit.Null) { // Optional inset keyword ParseVariant(ref val[IndexInset], VARIANT_KEYWORD, nsCSSProps.kBoxShadowTypeKTable); } aValue.SetArrayValue(val, nsCSSUnit.Array); return true; }
internal bool ParseTransitionStepTimingFunctionValues(ref nsCSSValue aValue) { Debug.Assert(!mHavePushBack && mToken.mType == nsCSSTokenType.Function && mToken.mIdentStr.LowerCaseEqualsLiteral("steps"), "unexpected initial state"); nsCSSValue[] val = new nsCSSValue[2]; if (!ParseOneOrLargerVariant(ref val[0], VARIANT_INTEGER, null)) { return false; } int32_t type = nsStyle.TRANSITION_TIMING_FUNCTION_STEP_END; if (ExpectSymbol(',', true)) { if (!GetToken(true)) { return false; } type = -1; if (mToken.mType == nsCSSTokenType.Ident) { if (mToken.mIdentStr.LowerCaseEqualsLiteral("start")) { type = nsStyle.TRANSITION_TIMING_FUNCTION_STEP_START; } else if (mToken.mIdentStr.LowerCaseEqualsLiteral("end")) { type = nsStyle.TRANSITION_TIMING_FUNCTION_STEP_END; } } if (type == -1) { UngetToken(); return false; } } val[1].SetIntValue(type, nsCSSUnit.Enumerated); if (!ExpectSymbol(')', true)) { return false; } aValue.SetArrayValue(val, nsCSSUnit.Steps); return true; }
// src: ( uri-src | local-src ) (',' ( uri-src | local-src ) )* // uri-src: uri [ 'format(' string ( ',' string )* ')' ] // local-src: 'local(' ( string | ident ) ')' internal bool ParseFontSrc(ref nsCSSValue aValue) { // could we maybe turn nsCSSValue[] into List<nsCSSValue>? var values = new List<nsCSSValue>(); var cur = new nsCSSValue(); for (;;) { if (!GetToken(true)) break; if (mToken.mType == nsCSSTokenType.URL) { SetValueToURL(ref cur, mToken.mIdentStr); values.AppendElement(cur); if (!ParseFontSrcFormat(values)) return false; } else if (mToken.mType == nsCSSTokenType.Function && mToken.mIdentStr.LowerCaseEqualsLiteral("local")) { // css3-fonts does not specify a formal grammar for local(). // The text permits both unquoted identifiers and quoted // strings. We resolve this ambiguity in the spec by // assuming that the appropriate production is a single // <family-name>, possibly surrounded by whitespace. var family = new StringBuilder(); bool single = false; if (!ParseOneFamily(family, ref single)) { SkipUntil(')'); return false; } if (!ExpectSymbol(')', true)) { SkipUntil(')'); return false; } // the style parameters to the nsFont constructor are ignored, // because it's only being used to call EnumerateFamilies var font = new nsFont(family, 0, 0, 0, 0, 0, 0); var dat = new ExtractFirstFamilyData(); font.EnumerateFamilies(ExtractFirstFamily, (object) dat); if (!dat.mGood) return false; cur.SetStringValue(dat.mFamilyName, nsCSSUnit.LocalFont); values.AppendElement(cur); } else { // We don't know what to do with this token; unget it and error out UngetToken(); return false; } if (!ExpectSymbol(',', true)) break; } if (values.Length() == 0) return false; nsCSSValue[] srcVals = new nsCSSValue[values.Length()]; uint32_t i = 0; for (i = 0; i < values.Length(); i++) srcVals[i] = values[i]; aValue.SetArrayValue(srcVals, nsCSSUnit.Array); return true; }
/* Parses a function [ input of the form (a [, b]*) ] and stores it * as an nsCSSValue that holds a function of the form * function-name arg1 arg2 ... argN * * On error, the return value is false. * * @param aFunction The name of the function that we're reading. * @param aAllowedTypes An array of values corresponding to the legal * types for each element in the function. The zeroth element in the * array corresponds to the first function parameter, etc. The length * of this array _must_ be greater than or equal to aMaxElems or the * behavior is undefined. * @param aMinElems Minimum number of elements to read. Reading fewer than * this many elements will result in the function failing. * @param aMaxElems Maximum number of elements to read. Reading more than * this many elements will result in the function failing. * @param aValue (out) The value that was parsed. */ internal bool ParseFunction(string aFunction, int32_t[] aAllowedTypes, uint16_t aMinElems, uint16_t aMaxElems, ref nsCSSValue aValue) { /* 2^16 - 2, so that if we have 2^16 - 2 transforms, we have 2^16 - 1 * elements stored in the the nsCSSValue[]. */ const size_t MAX_ALLOWED_ELEMS = 0xFFFE; /* Make a copy of the function name, since the reference is _probably_ to * mToken.mIdentStr, which is going to get overwritten during the course of this * function. */ string functionName = aFunction; /* Read in a list of values as an array, failing if we can't or if * it's out of bounds. */ var foundValues = new List<nsCSSValue>(); if (!ParseFunctionInternals(aAllowedTypes, aMinElems, aMaxElems, foundValues)) return false; /* Now, convert this array into an nsCSSValue[] object. * We'll need N + 1 spots, one for the function name and the rest for the * arguments. In case the user has given us more than 2^16 - 2 arguments, * we'll truncate them at 2^16 - 2 arguments. */ uint16_t numElements = (uint16_t)(foundValues.Length() <= MAX_ALLOWED_ELEMS ? foundValues.Length() + 1 : MAX_ALLOWED_ELEMS); nsCSSValue[] convertedArray = new nsCSSValue[numElements]; /* Copy things over. */ convertedArray[0].SetStringValue(functionName, nsCSSUnit.Ident); for (uint16_t index = 0; index + 1 < numElements; ++index) convertedArray[index + 1] = foundValues[((size_t)(index))]; /* Fill in the outparam value with the array. */ aValue.SetArrayValue(convertedArray, nsCSSUnit.Function); /* Return it! */ return true; }
// font-ranges: urange ( ',' urange )* internal bool ParseFontRanges(ref nsCSSValue aValue) { var ranges = new List<uint32_t>(); for (;;) { if (!GetToken(true)) break; if (mToken.mType != nsCSSTokenType.URange) { UngetToken(); break; } // An invalid range token is a parsing error, causing the entire // descriptor to be ignored. if (!mToken.mIntegerValid) return false; uint32_t low = mToken.mInteger; uint32_t high = mToken.mInteger2; // A range that descends, or a range that is entirely outside the // current range of Unicode (U+0-10FFFF) is ignored, but does not // invalidate the descriptor. A range that straddles the high end // is clipped. if (low <= 0x10FFFF && low <= high) { if (high > 0x10FFFF) high = 0x10FFFF; ranges.AppendElement(low); ranges.AppendElement(high); } if (!ExpectSymbol(',', true)) break; } if (ranges.Length() == 0) return false; nsCSSValue[] srcVals = new nsCSSValue[ranges.Length()]; for (uint32_t i = 0; i < ranges.Length(); i++) srcVals[i].SetIntValue(ranges[i], nsCSSUnit.Integer); aValue.SetArrayValue(srcVals, nsCSSUnit.Array); return true; }
internal bool ParseCounter(ref nsCSSValue aValue) { nsCSSUnit unit = (mToken.mIdentStr.LowerCaseEqualsLiteral("counter") ? nsCSSUnit.Counter : nsCSSUnit.Counters); // A non-iterative for loop to break out when an error occurs. for (;;) { if (!GetToken(true)) { break; } if (nsCSSTokenType.Ident != mToken.mType) { UngetToken(); break; } nsCSSValue[] val = new nsCSSValue[unit == nsCSSUnit.Counter ? 2 : 3]; val[0].SetStringValue(mToken.mIdentStr, nsCSSUnit.Ident); if (nsCSSUnit.Counters == unit) { // must have a comma and then a separator string if (!ExpectSymbol(',', true) || !GetToken(true)) { break; } if (nsCSSTokenType.String != mToken.mType) { UngetToken(); break; } val[1].SetStringValue(mToken.mIdentStr, nsCSSUnit.String); } // get optional type int32_t type = nsStyle.LIST_STYLE_DECIMAL; if (ExpectSymbol(',', true)) { if (!GetToken(true)) { break; } nsCSSKeyword keyword; if (nsCSSTokenType.Ident != mToken.mType || (keyword = nsCSSKeywords.LookupKeyword(mToken.mIdentStr)) == nsCSSKeyword.UNKNOWN || !nsCSSProps.FindKeyword(keyword, nsCSSProps.kListStyleKTable, ref type)) { UngetToken(); break; } } int32_t typeItem = nsCSSUnit.Counters == unit ? 2 : 1; val[typeItem].SetIntValue(type, nsCSSUnit.Enumerated); if (!ExpectSymbol(')', true)) { break; } aValue.SetArrayValue(val, unit); return true; } SkipUntil(')'); return false; }
// * 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. internal bool ParseCalcMultiplicativeExpression(ref nsCSSValue aValue, ref int32_t aVariantMask, ref bool aHadFinalWS) { Debug.Assert(aVariantMask != 0, "unexpected variant mask"); bool gotValue = false; // already got the part with the unit bool afterDivision = false; nsCSSValue storage = aValue; for (;;) { int32_t variantMask = 0; if (afterDivision || gotValue) { variantMask = VARIANT_NUMBER; } else { variantMask = aVariantMask | VARIANT_NUMBER; } if (!ParseCalcTerm(ref storage, ref variantMask)) return false; Debug.Assert(variantMask != 0, "ParseCalcTerm did not set variantMask appropriately"); if ((variantMask & VARIANT_NUMBER) != 0) { // Simplify the value immediately so we can check for division by // zero. var ops = new ReduceNumberCalcOps(); float number = CommonUtil.ComputeCalc(storage, ops); if (number == 0.0 && afterDivision) return false; storage.SetFloatValue(number, nsCSSUnit.Number); } else { gotValue = true; if (storage != aValue) { // Simplify any numbers in the Times_L position (which are // not simplified by the check above). Debug.Assert(storage == aValue.GetArrayValue().Item(1), "unexpected relationship to current storage"); nsCSSValue leftValue = aValue.GetArrayValue().Item(0); var ops = new ReduceNumberCalcOps(); float number = CommonUtil.ComputeCalc(leftValue, ops); leftValue.SetFloatValue(number, nsCSSUnit.Number); } } bool hadWS = RequireWhitespace(); if (!GetToken(false)) { aHadFinalWS = hadWS; break; } nsCSSUnit unit = 0; if (mToken.IsSymbol('*')) { unit = gotValue ? nsCSSUnit.CalcTimesR : nsCSSUnit.CalcTimesL; afterDivision = false; } else if (mToken.IsSymbol('/')) { unit = nsCSSUnit.CalcDivided; afterDivision = true; } else { UngetToken(); aHadFinalWS = hadWS; break; } nsCSSValue[] arr = new nsCSSValue[2]; arr[0] = aValue; storage = arr[1]; aValue.SetArrayValue(arr, unit); } // Adjust aVariantMask (see comments above function) to reflect which // option we took. if ((aVariantMask & VARIANT_NUMBER) != 0) { if (gotValue) { aVariantMask &= ~((int32_t)(VARIANT_NUMBER)); } else { aVariantMask = VARIANT_NUMBER; } } else { if (!gotValue) { // We had to find a value, but we didn't. return false; } } return true; }
// We optimize away the <value-expression> production given that // ParseVariant consumes initial whitespace and we call // ExpectSymbol(')') with 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. internal bool ParseCalcAdditiveExpression(ref nsCSSValue aValue, ref int32_t aVariantMask) { Debug.Assert(aVariantMask != 0, "unexpected variant mask"); nsCSSValue storage = aValue; for (;;) { bool haveWS = false; if (!ParseCalcMultiplicativeExpression(ref storage, ref aVariantMask, ref haveWS)) return false; if (!haveWS || !GetToken(false)) return true; nsCSSUnit unit = 0; if (mToken.IsSymbol('+')) { unit = nsCSSUnit.CalcPlus; } else if (mToken.IsSymbol('-')) { unit = nsCSSUnit.CalcMinus; } else { UngetToken(); return true; } if (!RequireWhitespace()) return false; nsCSSValue[] arr = new nsCSSValue[2]; arr[0] = aValue; storage = arr[1]; aValue.SetArrayValue(arr, unit); } }
// Parse the top level of a calc() expression. internal bool ParseCalc(ref nsCSSValue aValue, int32_t 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. Debug.Assert(!((aVariantMask & VARIANT_NUMBER) != 0), "unexpected variant mask"); Debug.Assert(aVariantMask != 0, "unexpected variant mask"); bool oldUnitlessLengthQuirk = mUnitlessLengthQuirk; mUnitlessLengthQuirk = false; // One-iteration loop so we can break to the error-handling case. do { // The toplevel of a calc() is always an nsCSSValue[] of length 1. nsCSSValue[] arr = new nsCSSValue[1]; if (!ParseCalcAdditiveExpression(ref arr[0], ref aVariantMask)) break; if (!ExpectSymbol(')', true)) break; aValue.SetArrayValue(arr, nsCSSUnit.Calc); mUnitlessLengthQuirk = oldUnitlessLengthQuirk; return true; } while (false); SkipUntil(')'); mUnitlessLengthQuirk = oldUnitlessLengthQuirk; return false; }