bool SizesAttributeParser::calculateLengthInPixels(CSSParserTokenRange range, float& result) { const CSSParserToken& startToken = range.peek(); CSSParserTokenType type = startToken.type(); if (type == DimensionToken) { double length; if (!CSSPrimitiveValue::isLength(startToken.unitType())) return false; if ((m_mediaValues->computeLength(startToken.numericValue(), startToken.unitType(), length)) && (length >= 0)) { result = clampTo<float>(length); return true; } } else if (type == FunctionToken) { SizesCalcParser calcParser(range, m_mediaValues); if (!calcParser.isValid()) return false; result = calcParser.result(); return true; } else if (type == NumberToken && !startToken.numericValue()) { result = 0; return true; } return false; }
CSSPrimitiveValue* consumeAngle(CSSParserTokenRange& range) { const CSSParserToken& token = range.peek(); if (token.type() == DimensionToken) { switch (token.unitType()) { case CSSPrimitiveValue::UnitType::Degrees: case CSSPrimitiveValue::UnitType::Radians: case CSSPrimitiveValue::UnitType::Gradians: case CSSPrimitiveValue::UnitType::Turns: return cssValuePool().createValue(range.consumeIncludingWhitespace().numericValue(), token.unitType()); default: return nullptr; } } if (token.type() == NumberToken && token.numericValue() == 0) { range.consumeIncludingWhitespace(); return cssValuePool().createValue(0, CSSPrimitiveValue::UnitType::Degrees); } CalcParser calcParser(range, ValueRangeAll); if (const CSSCalcValue* calculation = calcParser.value()) { if (calculation->category() == CalcAngle) return calcParser.consumeValue(); } return nullptr; }
bool SizesAttributeParser::calculateLengthInPixels(CSSParserTokenIterator startToken, CSSParserTokenIterator endToken, float& result) { if (startToken == endToken) return false; CSSParserTokenType type = startToken->type(); if (type == DimensionToken) { double length; if (!CSSPrimitiveValue::isLength(startToken->unitType())) return false; if ((m_mediaValues->computeLength(startToken->numericValue(), startToken->unitType(), length)) && (length >= 0)) { result = clampTo<float>(length); return true; } } else if (type == FunctionToken) { SizesCalcParser calcParser(startToken, endToken, m_mediaValues); if (!calcParser.isValid()) return false; result = calcParser.result(); return true; } else if (type == NumberToken && !startToken->numericValue()) { result = 0; return true; } return false; }
bool consumeNumberRaw(CSSParserTokenRange& range, double& result) { if (range.peek().type() == NumberToken) { result = range.consumeIncludingWhitespace().numericValue(); return true; } CalcParser calcParser(range, ValueRangeAll); return calcParser.consumeNumberRaw(result); }
CSSPrimitiveValue* consumeLengthOrPercent(CSSParserTokenRange& range, CSSParserMode cssParserMode, ValueRange valueRange, UnitlessQuirk unitless) { const CSSParserToken& token = range.peek(); if (token.type() == DimensionToken || token.type() == NumberToken) return consumeLength(range, cssParserMode, valueRange, unitless); if (token.type() == PercentageToken) return consumePercent(range, valueRange); CalcParser calcParser(range, valueRange); if (const CSSCalcValue* calculation = calcParser.value()) { if (calculation->category() == CalcLength || calculation->category() == CalcPercent || calculation->category() == CalcPercentLength) return calcParser.consumeValue(); } return nullptr; }
CSSPrimitiveValue* consumePercent(CSSParserTokenRange& range, ValueRange valueRange) { const CSSParserToken& token = range.peek(); if (token.type() == PercentageToken) { if (valueRange == ValueRangeNonNegative && token.numericValue() < 0) return nullptr; return cssValuePool().createValue(range.consumeIncludingWhitespace().numericValue(), CSSPrimitiveValue::UnitType::Percentage); } CalcParser calcParser(range, valueRange); if (const CSSCalcValue* calculation = calcParser.value()) { if (calculation->category() == CalcPercent) return calcParser.consumeValue(); } return nullptr; }
CSSPrimitiveValue* consumeLength(CSSParserTokenRange& range, CSSParserMode cssParserMode, ValueRange valueRange, UnitlessQuirk unitless) { const CSSParserToken& token = range.peek(); if (token.type() == DimensionToken) { switch (token.unitType()) { case CSSPrimitiveValue::UnitType::QuirkyEms: if (cssParserMode != UASheetMode) return nullptr; /* fallthrough intentional */ case CSSPrimitiveValue::UnitType::Ems: case CSSPrimitiveValue::UnitType::Rems: case CSSPrimitiveValue::UnitType::Chs: case CSSPrimitiveValue::UnitType::Exs: case CSSPrimitiveValue::UnitType::Pixels: case CSSPrimitiveValue::UnitType::Centimeters: case CSSPrimitiveValue::UnitType::Millimeters: case CSSPrimitiveValue::UnitType::Inches: case CSSPrimitiveValue::UnitType::Points: case CSSPrimitiveValue::UnitType::Picas: case CSSPrimitiveValue::UnitType::UserUnits: case CSSPrimitiveValue::UnitType::ViewportWidth: case CSSPrimitiveValue::UnitType::ViewportHeight: case CSSPrimitiveValue::UnitType::ViewportMin: case CSSPrimitiveValue::UnitType::ViewportMax: break; default: return nullptr; } if (valueRange == ValueRangeNonNegative && token.numericValue() < 0) return nullptr; return cssValuePool().createValue(range.consumeIncludingWhitespace().numericValue(), token.unitType()); } if (token.type() == NumberToken) { if (!shouldAcceptUnitlessLength(token.numericValue(), cssParserMode, unitless) || (valueRange == ValueRangeNonNegative && token.numericValue() < 0)) return nullptr; CSSPrimitiveValue::UnitType unitType = CSSPrimitiveValue::UnitType::Pixels; if (cssParserMode == SVGAttributeMode) unitType = CSSPrimitiveValue::UnitType::UserUnits; return cssValuePool().createValue(range.consumeIncludingWhitespace().numericValue(), unitType); } if (cssParserMode == SVGAttributeMode) return nullptr; CalcParser calcParser(range, valueRange); if (calcParser.value() && calcParser.value()->category() == CalcLength) return calcParser.consumeValue(); return nullptr; }
CSSPrimitiveValue* consumeTime(CSSParserTokenRange& range, ValueRange valueRange) { const CSSParserToken& token = range.peek(); if (token.type() == DimensionToken) { if (valueRange == ValueRangeNonNegative && token.numericValue() < 0) return nullptr; CSSPrimitiveValue::UnitType unit = token.unitType(); if (unit == CSSPrimitiveValue::UnitType::Milliseconds || unit == CSSPrimitiveValue::UnitType::Seconds) return cssValuePool().createValue(range.consumeIncludingWhitespace().numericValue(), token.unitType()); return nullptr; } CalcParser calcParser(range, valueRange); if (const CSSCalcValue* calculation = calcParser.value()) { if (calculation->category() == CalcTime) return calcParser.consumeValue(); } return nullptr; }
// TODO(timloh): Work out if this can just call consumeNumberRaw CSSPrimitiveValue* consumeNumber(CSSParserTokenRange& range, ValueRange valueRange) { const CSSParserToken& token = range.peek(); if (token.type() == NumberToken) { if (valueRange == ValueRangeNonNegative && token.numericValue() < 0) return nullptr; return cssValuePool().createValue(range.consumeIncludingWhitespace().numericValue(), token.unitType()); } CalcParser calcParser(range, ValueRangeAll); if (const CSSCalcValue* calculation = calcParser.value()) { // TODO(rwlbuis) Calcs should not be subject to parse time range checks. // spec: https://drafts.csswg.org/css-values-3/#calc-range if (calculation->category() != CalcNumber || (valueRange == ValueRangeNonNegative && calculation->isNegative())) return nullptr; return calcParser.consumeNumber(); } return nullptr; }
CSSPrimitiveValue* consumeInteger(CSSParserTokenRange& range, double minimumValue) { const CSSParserToken& token = range.peek(); if (token.type() == NumberToken) { if (token.numericValueType() == NumberValueType || token.numericValue() < minimumValue) return nullptr; return cssValuePool().createValue(range.consumeIncludingWhitespace().numericValue(), CSSPrimitiveValue::UnitType::Integer); } CalcParser calcParser(range); if (const CSSCalcValue* calculation = calcParser.value()) { if (calculation->category() != CalcNumber || !calculation->isInt()) return nullptr; double value = calculation->doubleValue(); if (value < minimumValue) return nullptr; return calcParser.consumeNumber(); } return nullptr; }
TEST(SizesCalcParserTest, Basic) { TestCase testCases[] = { {"calc(500px + 10em)", 660, true, false}, {"calc(500px / 8)", 62.5, true, false}, {"calc(500px + 2 * 10em)", 820, true, false}, {"calc(500px + 2*10em)", 820, true, false}, {"calc(500px + 0.5*10em)", 580, true, false}, {"calc(500px + (0.5*10em + 13px))", 593, true, false}, {"calc(100vw + (0.5*10em + 13px))", 593, true, false}, {"calc(100vh + (0.5*10em + 13px))", 736, true, false}, {"calc(100vh + calc(0.5*10em + 13px))", 736, true, true}, // CSSCalculationValue does not parse internal "calc(". {"calc(100vh + (50%*10em + 13px))", 0, false, false}, {"calc(50em+13px)", 0, false, false}, {"calc(50em-13px)", 0, false, false}, {"calc(500px + 10)", 0, false, false}, {"calc(500 + 10)", 0, false, false}, {"calc(500px + 10s)", 0, false, true}, // This test ASSERTs in CSSCalculationValue. {"calc(500px + 1cm)", 537.795276, true, false}, {"calc(500px - 10s)", 0, false, true}, // This test ASSERTs in CSSCalculationValue. {"calc(500px - 1cm)", 462.204724, true, false}, {"calc(500px - 1vw)", 495, true, false}, {"calc(50px*10)", 500, true, false}, {"calc(50px*10px)", 0, false, false}, {"calc(50px/10px)", 0, false, false}, {"calc(500px/10)", 50, true, false}, {"calc(500/10)", 0, false, false}, {"calc(500px/0.5)", 1000, true, false}, {"calc(500px/.5)", 1000, true, false}, {"calc(500/0)", 0, false, false}, {"calc(500px/0)", 0, false, false}, {"calc(-500px/10)", 0, true, true}, // CSSCalculationValue does not clamp negative values to 0. {"calc(((4) * ((10px))))", 40, true, false}, {"calc(50px / 0)", 0, false, false}, {"calc(50px / (10 + 10))", 2.5, true, false}, {"calc(50px / (10 - 10))", 0, false, false}, {"calc(50px / (10 * 10))", 0.5, true, false}, {"calc(50px / (10 / 10))", 50, true, false}, {"calc(200px*)", 0, false, false}, {"calc(+ +200px)", 0, false, false}, {"calc()", 0, false, false}, {"calc(100px + + +100px)", 0, false, false}, {"calc(200px 200px)", 0, false, false}, {"calc(100px * * 2)", 0, false, false}, {"calc(100px @ 2)", 0, false, false}, {"calc(1 flim 2)", 0, false, false}, {"calc(100px @ 2)", 0, false, false}, {"calc(1 flim 2)", 0, false, false}, {"calc(1 flim (2))", 0, false, false}, {0, 0, true, false} // Do not remove the terminator line. }; MediaValuesCached::MediaValuesCachedData data; data.viewportWidth = 500; data.viewportHeight = 643; data.deviceWidth = 500; data.deviceHeight = 643; data.devicePixelRatio = 2.0; data.colorBitsPerComponent = 24; data.monochromeBitsPerComponent = 0; data.primaryPointerType = PointerTypeFine; data.defaultFontSize = 16; data.threeDEnabled = true; data.mediaType = MediaTypeNames::screen; data.strictMode = true; data.displayMode = WebDisplayModeBrowser; RawPtr<MediaValues> mediaValues = MediaValuesCached::create(data); for (unsigned i = 0; testCases[i].input; ++i) { SizesCalcParser calcParser(CSSTokenizer::Scope(testCases[i].input).tokenRange(), mediaValues); ASSERT_EQ(testCases[i].valid, calcParser.isValid()); if (calcParser.isValid()) ASSERT_EQ(testCases[i].output, calcParser.result()); } for (unsigned i = 0; testCases[i].input; ++i) { if (testCases[i].dontRunInCSSCalc) continue; verifyCSSCalc(testCases[i].input, testCases[i].output, testCases[i].valid, data.defaultFontSize, data.viewportWidth, data.viewportHeight); } }