| /* |
| * Copyright (C) 2003 Lars Knoll (knoll@kde.org) |
| * Copyright (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com) |
| * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Apple Inc. All rights reserved. |
| * Copyright (C) 2007 Nicholas Shanks <webkit@nickshanks.com> |
| * Copyright (C) 2008 Eric Seidel <eric@webkit.org> |
| * Copyright (C) 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) |
| * Copyright (C) 2012 Adobe Systems Incorporated. All rights reserved. |
| * Copyright (C) 2012 Intel Corporation. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "core/css/parser/CSSPropertyParser.h" |
| |
| #include "core/StylePropertyShorthand.h" |
| #include "core/css/CSSCustomIdentValue.h" |
| #include "core/css/CSSFunctionValue.h" |
| #include "core/css/CSSGridAutoRepeatValue.h" |
| #include "core/css/CSSGridLineNamesValue.h" |
| #include "core/css/CSSPrimitiveValueMappings.h" |
| #include "core/css/CSSValuePair.h" |
| #include "core/css/CSSValuePool.h" |
| #include "core/css/parser/CSSParserValues.h" |
| #include "core/style/GridArea.h" |
| #include "platform/RuntimeEnabledFeatures.h" |
| |
| namespace blink { |
| |
| void CSSPropertyParser::addProperty(CSSPropertyID propId, PassRefPtrWillBeRawPtr<CSSValue> value, bool important, bool implicit) |
| { |
| ASSERT(!isPropertyAlias(propId)); |
| |
| int shorthandIndex = 0; |
| bool setFromShorthand = false; |
| |
| if (m_currentShorthand) { |
| Vector<StylePropertyShorthand, 4> shorthands; |
| getMatchingShorthandsForLonghand(propId, &shorthands); |
| setFromShorthand = true; |
| if (shorthands.size() > 1) |
| shorthandIndex = indexOfShorthandForLonghand(m_currentShorthand, shorthands); |
| } |
| |
| m_parsedProperties->append(CSSProperty(propId, value, important, setFromShorthand, shorthandIndex, implicit)); |
| } |
| |
| bool CSSPropertyParser::validCalculationUnit(CSSParserValue* value, Units unitflags, ReleaseParsedCalcValueCondition releaseCalc) |
| { |
| bool mustBeNonNegative = unitflags & (FNonNeg | FPositiveInteger); |
| |
| if (!parseCalculation(value, mustBeNonNegative ? ValueRangeNonNegative : ValueRangeAll)) |
| return false; |
| |
| bool b = false; |
| switch (m_parsedCalculation->category()) { |
| case CalcLength: |
| b = (unitflags & FLength); |
| break; |
| case CalcNumber: |
| b = (unitflags & FNumber); |
| if (!b && (unitflags & (FInteger | FPositiveInteger)) && m_parsedCalculation->isInt()) |
| b = true; |
| if (b && mustBeNonNegative && m_parsedCalculation->isNegative()) |
| b = false; |
| // Always resolve calc() to a UnitType::Number in the CSSParserValue if there are no non-numbers specified in the unitflags. |
| if (b && !(unitflags & ~(FInteger | FNumber | FPositiveInteger | FNonNeg))) { |
| double number = m_parsedCalculation->doubleValue(); |
| if ((unitflags & FPositiveInteger) && number <= 0) { |
| b = false; |
| } else { |
| delete value->calcFunction; |
| value->setUnit(CSSPrimitiveValue::UnitType::Number); |
| value->fValue = number; |
| value->isInt = m_parsedCalculation->isInt(); |
| } |
| m_parsedCalculation.release(); |
| return b; |
| } |
| break; |
| case CalcPercent: |
| b = (unitflags & FPercent); |
| if (b && mustBeNonNegative && m_parsedCalculation->isNegative()) |
| b = false; |
| break; |
| case CalcPercentLength: |
| b = (unitflags & FPercent) && (unitflags & FLength); |
| break; |
| case CalcPercentNumber: |
| b = (unitflags & FPercent) && (unitflags & FNumber); |
| break; |
| case CalcAngle: |
| b = (unitflags & FAngle); |
| break; |
| case CalcTime: |
| b = (unitflags & FTime); |
| break; |
| case CalcFrequency: |
| b = (unitflags & FFrequency); |
| break; |
| case CalcOther: |
| break; |
| } |
| if (!b || releaseCalc == ReleaseParsedCalcValue) |
| m_parsedCalculation.release(); |
| return b; |
| } |
| |
| inline bool CSSPropertyParser::shouldAcceptUnitLessValues(CSSParserValue* value, Units unitflags, CSSParserMode cssParserMode) |
| { |
| // Quirks mode for certain properties and presentation attributes accept unit-less values for certain units. |
| return (unitflags & (FLength | FAngle)) |
| && (!value->fValue // 0 can always be unitless. |
| || isUnitLessLengthParsingEnabledForMode(cssParserMode) // HTML and SVG attribute values can always be unitless. |
| || (cssParserMode == HTMLQuirksMode && (unitflags & FUnitlessQuirk))); |
| } |
| |
| inline bool isCalculation(CSSParserValue* value) |
| { |
| return value->m_unit == CSSParserValue::CalcFunction; |
| } |
| |
| bool CSSPropertyParser::validUnit(CSSParserValue* value, Units unitflags, CSSParserMode cssParserMode, ReleaseParsedCalcValueCondition releaseCalc) |
| { |
| if (isCalculation(value)) |
| return validCalculationUnit(value, unitflags, releaseCalc); |
| |
| if (unitflags & FNonNeg && value->fValue < 0) |
| return false; |
| switch (value->unit()) { |
| case CSSPrimitiveValue::UnitType::Number: |
| if (unitflags & FNumber) |
| return true; |
| if (shouldAcceptUnitLessValues(value, unitflags, cssParserMode)) { |
| value->setUnit((unitflags & FLength) ? CSSPrimitiveValue::UnitType::Pixels : CSSPrimitiveValue::UnitType::Degrees); |
| return true; |
| } |
| if ((unitflags & FInteger) && value->isInt) |
| return true; |
| if ((unitflags & FPositiveInteger) && value->isInt && value->fValue > 0) |
| return true; |
| return false; |
| case CSSPrimitiveValue::UnitType::Percentage: |
| return unitflags & FPercent; |
| case CSSPrimitiveValue::UnitType::QuirkyEms: |
| if (cssParserMode != UASheetMode) |
| return false; |
| /* 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: |
| return unitflags & FLength; |
| case CSSPrimitiveValue::UnitType::Milliseconds: |
| case CSSPrimitiveValue::UnitType::Seconds: |
| return unitflags & FTime; |
| case CSSPrimitiveValue::UnitType::Degrees: |
| case CSSPrimitiveValue::UnitType::Radians: |
| case CSSPrimitiveValue::UnitType::Gradians: |
| case CSSPrimitiveValue::UnitType::Turns: |
| return unitflags & FAngle; |
| case CSSPrimitiveValue::UnitType::DotsPerPixel: |
| case CSSPrimitiveValue::UnitType::DotsPerInch: |
| case CSSPrimitiveValue::UnitType::DotsPerCentimeter: |
| return unitflags & FResolution; |
| default: |
| return false; |
| } |
| } |
| |
| PassRefPtrWillBeRawPtr<CSSPrimitiveValue> CSSPropertyParser::createPrimitiveNumericValue(CSSParserValue* value) |
| { |
| if (m_parsedCalculation) { |
| ASSERT(isCalculation(value)); |
| return CSSPrimitiveValue::create(m_parsedCalculation.release()); |
| } |
| |
| ASSERT((value->unit() >= CSSPrimitiveValue::UnitType::Number && value->unit() <= CSSPrimitiveValue::UnitType::Kilohertz) |
| || (value->unit() >= CSSPrimitiveValue::UnitType::Turns && value->unit() <= CSSPrimitiveValue::UnitType::Chs) |
| || (value->unit() >= CSSPrimitiveValue::UnitType::ViewportWidth && value->unit() <= CSSPrimitiveValue::UnitType::ViewportMax) |
| || (value->unit() >= CSSPrimitiveValue::UnitType::DotsPerPixel && value->unit() <= CSSPrimitiveValue::UnitType::DotsPerCentimeter)); |
| return cssValuePool().createValue(value->fValue, value->unit()); |
| } |
| |
| inline PassRefPtrWillBeRawPtr<CSSCustomIdentValue> CSSPropertyParser::createPrimitiveCustomIdentValue(CSSParserValue* value) |
| { |
| ASSERT(value->m_unit == CSSParserValue::String || value->m_unit == CSSParserValue::Identifier); |
| return CSSCustomIdentValue::create(value->string); |
| } |
| |
| static inline bool isComma(CSSParserValue* value) |
| { |
| ASSERT(value); |
| return value->m_unit == CSSParserValue::Operator && value->iValue == ','; |
| } |
| |
| static inline bool isForwardSlashOperator(CSSParserValue* value) |
| { |
| ASSERT(value); |
| return value->m_unit == CSSParserValue::Operator && value->iValue == '/'; |
| } |
| |
| void CSSPropertyParser::addExpandedPropertyForValue(CSSPropertyID propId, PassRefPtrWillBeRawPtr<CSSValue> prpValue, bool important) |
| { |
| const StylePropertyShorthand& shorthand = shorthandForProperty(propId); |
| unsigned shorthandLength = shorthand.length(); |
| if (!shorthandLength) { |
| addProperty(propId, prpValue, important); |
| return; |
| } |
| |
| RefPtrWillBeRawPtr<CSSValue> value = prpValue; |
| ShorthandScope scope(this, propId); |
| const CSSPropertyID* longhands = shorthand.properties(); |
| for (unsigned i = 0; i < shorthandLength; ++i) |
| addProperty(longhands[i], value, important); |
| } |
| |
| bool CSSPropertyParser::legacyParseAndApplyValue(CSSPropertyID propertyID, bool important) |
| { |
| RefPtrWillBeRawPtr<CSSValue> result = legacyParseValue(propertyID); |
| if (!result) |
| return false; |
| addProperty(propertyID, result.release(), important); |
| return true; |
| } |
| |
| PassRefPtrWillBeRawPtr<CSSValue> CSSPropertyParser::legacyParseValue(CSSPropertyID unresolvedProperty) |
| { |
| CSSPropertyID propId = resolveCSSPropertyID(unresolvedProperty); |
| |
| // Note: m_parsedCalculation is used to pass the calc value to validUnit and then cleared at the end of this function. |
| // FIXME: This is to avoid having to pass parsedCalc to all validUnit callers. |
| ASSERT(!m_parsedCalculation); |
| |
| RefPtrWillBeRawPtr<CSSValue> parsedValue = nullptr; |
| |
| switch (propId) { |
| case CSSPropertyGridAutoFlow: |
| ASSERT(RuntimeEnabledFeatures::cssGridLayoutEnabled()); |
| parsedValue = parseGridAutoFlow(*m_valueList); |
| break; |
| case CSSPropertyGridAutoColumns: |
| case CSSPropertyGridAutoRows: |
| ASSERT(RuntimeEnabledFeatures::cssGridLayoutEnabled()); |
| parsedValue = parseGridTrackSize(*m_valueList); |
| break; |
| |
| case CSSPropertyGridTemplateColumns: |
| case CSSPropertyGridTemplateRows: |
| ASSERT(RuntimeEnabledFeatures::cssGridLayoutEnabled()); |
| parsedValue = parseGridTrackList(); |
| break; |
| |
| case CSSPropertyGridTemplateAreas: |
| ASSERT(RuntimeEnabledFeatures::cssGridLayoutEnabled()); |
| parsedValue = parseGridTemplateAreas(); |
| break; |
| |
| // Everything else is handled in CSSPropertyParser.cpp |
| default: |
| return nullptr; |
| } |
| |
| ASSERT(!m_parsedCalculation); |
| if (parsedValue) { |
| if (!m_valueList->current() || inShorthand()) |
| return parsedValue.release(); |
| } |
| return nullptr; |
| } |
| |
| bool CSSPropertyParser::legacyParseShorthand(CSSPropertyID propertyID, bool important) |
| { |
| switch (propertyID) { |
| case CSSPropertyGridColumn: |
| case CSSPropertyGridRow: |
| ASSERT(RuntimeEnabledFeatures::cssGridLayoutEnabled()); |
| return parseGridItemPositionShorthand(propertyID, important); |
| |
| case CSSPropertyGridArea: |
| ASSERT(RuntimeEnabledFeatures::cssGridLayoutEnabled()); |
| return parseGridAreaShorthand(important); |
| |
| case CSSPropertyGridTemplate: |
| ASSERT(RuntimeEnabledFeatures::cssGridLayoutEnabled()); |
| return parseGridTemplateShorthand(important); |
| |
| case CSSPropertyGrid: |
| ASSERT(RuntimeEnabledFeatures::cssGridLayoutEnabled()); |
| return parseGridShorthand(important); |
| |
| // The remaining shorthands are handled in CSSPropertyParser.cpp |
| default: |
| return false; |
| } |
| } |
| |
| static inline bool isCSSWideKeyword(const CSSParserValue& value) |
| { |
| return value.id == CSSValueInitial || value.id == CSSValueInherit || value.id == CSSValueUnset || value.id == CSSValueDefault; |
| } |
| |
| static inline bool isValidCustomIdentForGridPositions(const CSSParserValue& value) |
| { |
| // FIXME: we need a more general solution for <custom-ident> in all properties. |
| return value.m_unit == CSSParserValue::Identifier && value.id != CSSValueSpan && value.id != CSSValueAuto && !isCSSWideKeyword(value); |
| } |
| |
| // The function parses [ <integer> || <custom-ident> ] in <grid-line> (which can be stand alone or with 'span'). |
| bool CSSPropertyParser::parseIntegerOrCustomIdentFromGridPosition(RefPtrWillBeRawPtr<CSSPrimitiveValue>& numericValue, RefPtrWillBeRawPtr<CSSCustomIdentValue>& gridLineName) |
| { |
| CSSParserValue* value = m_valueList->current(); |
| if (validUnit(value, FInteger) && value->fValue) { |
| numericValue = createPrimitiveNumericValue(value); |
| value = m_valueList->next(); |
| if (value && isValidCustomIdentForGridPositions(*value)) { |
| gridLineName = createPrimitiveCustomIdentValue(m_valueList->current()); |
| m_valueList->next(); |
| } |
| return true; |
| } |
| |
| if (isValidCustomIdentForGridPositions(*value)) { |
| gridLineName = createPrimitiveCustomIdentValue(m_valueList->current()); |
| value = m_valueList->next(); |
| if (value && validUnit(value, FInteger) && value->fValue) { |
| numericValue = createPrimitiveNumericValue(value); |
| m_valueList->next(); |
| } |
| return true; |
| } |
| |
| return false; |
| } |
| |
| PassRefPtrWillBeRawPtr<CSSValue> CSSPropertyParser::parseGridPosition() |
| { |
| ASSERT(RuntimeEnabledFeatures::cssGridLayoutEnabled()); |
| |
| CSSParserValue* value = m_valueList->current(); |
| if (value->id == CSSValueAuto) { |
| m_valueList->next(); |
| return cssValuePool().createIdentifierValue(CSSValueAuto); |
| } |
| |
| RefPtrWillBeRawPtr<CSSPrimitiveValue> numericValue = nullptr; |
| RefPtrWillBeRawPtr<CSSCustomIdentValue> gridLineName = nullptr; |
| bool hasSeenSpanKeyword = false; |
| |
| if (parseIntegerOrCustomIdentFromGridPosition(numericValue, gridLineName)) { |
| value = m_valueList->current(); |
| if (value && value->id == CSSValueSpan) { |
| hasSeenSpanKeyword = true; |
| m_valueList->next(); |
| } |
| } else if (value->id == CSSValueSpan) { |
| hasSeenSpanKeyword = true; |
| if (CSSParserValue* nextValue = m_valueList->next()) { |
| if (!isForwardSlashOperator(nextValue) && !parseIntegerOrCustomIdentFromGridPosition(numericValue, gridLineName)) |
| return nullptr; |
| } |
| } |
| |
| // Check that we have consumed all the value list. For shorthands, the parser will pass |
| // the whole value list (including the opposite position). |
| if (m_valueList->current() && !isForwardSlashOperator(m_valueList->current())) |
| return nullptr; |
| |
| // If we didn't parse anything, this is not a valid grid position. |
| if (!hasSeenSpanKeyword && !gridLineName && !numericValue) |
| return nullptr; |
| |
| // Negative numbers are not allowed for span (but are for <integer>). |
| if (hasSeenSpanKeyword && numericValue && numericValue->getIntValue() < 0) |
| return nullptr; |
| |
| // For the <custom-ident> case. |
| if (gridLineName && !numericValue && !hasSeenSpanKeyword) |
| return CSSCustomIdentValue::create(gridLineName->value()); |
| |
| RefPtrWillBeRawPtr<CSSValueList> values = CSSValueList::createSpaceSeparated(); |
| if (hasSeenSpanKeyword) |
| values->append(cssValuePool().createIdentifierValue(CSSValueSpan)); |
| if (numericValue) |
| values->append(numericValue.release()); |
| if (gridLineName) |
| values->append(gridLineName.release()); |
| ASSERT(values->length()); |
| return values.release(); |
| } |
| |
| static PassRefPtrWillBeRawPtr<CSSValue> gridMissingGridPositionValue(CSSValue* value) |
| { |
| if (value->isCustomIdentValue()) |
| return value; |
| |
| return cssValuePool().createIdentifierValue(CSSValueAuto); |
| } |
| |
| bool CSSPropertyParser::parseGridItemPositionShorthand(CSSPropertyID shorthandId, bool important) |
| { |
| ShorthandScope scope(this, shorthandId); |
| const StylePropertyShorthand& shorthand = shorthandForProperty(shorthandId); |
| ASSERT(shorthand.length() == 2); |
| |
| RefPtrWillBeRawPtr<CSSValue> startValue = parseGridPosition(); |
| if (!startValue) |
| return false; |
| |
| RefPtrWillBeRawPtr<CSSValue> endValue = nullptr; |
| if (m_valueList->current()) { |
| if (!isForwardSlashOperator(m_valueList->current())) |
| return false; |
| |
| if (!m_valueList->next()) |
| return false; |
| |
| endValue = parseGridPosition(); |
| if (!endValue || m_valueList->current()) |
| return false; |
| } else { |
| endValue = gridMissingGridPositionValue(startValue.get()); |
| } |
| |
| addProperty(shorthand.properties()[0], startValue, important); |
| addProperty(shorthand.properties()[1], endValue, important); |
| return true; |
| } |
| |
| PassRefPtrWillBeRawPtr<CSSValue> CSSPropertyParser::parseGridTemplateColumns(bool important) |
| { |
| if (!(m_valueList->current() && isForwardSlashOperator(m_valueList->current()) && m_valueList->next())) |
| return nullptr; |
| if (RefPtrWillBeRawPtr<CSSValue> columnsValue = parseGridTrackList()) { |
| if (m_valueList->current()) |
| return nullptr; |
| return columnsValue; |
| } |
| |
| return nullptr; |
| } |
| |
| bool CSSPropertyParser::parseGridTemplateRowsAndAreasAndColumns(bool important) |
| { |
| NamedGridAreaMap gridAreaMap; |
| size_t rowCount = 0; |
| size_t columnCount = 0; |
| bool trailingIdentWasAdded = false; |
| RefPtrWillBeRawPtr<CSSValueList> templateRows = CSSValueList::createSpaceSeparated(); |
| |
| // At least template-areas strings must be defined. |
| if (!m_valueList->current() || isForwardSlashOperator(m_valueList->current())) |
| return false; |
| |
| while (m_valueList->current() && !isForwardSlashOperator(m_valueList->current())) { |
| // Handle leading <custom-ident>*. |
| if (!parseGridLineNames(*m_valueList, *templateRows, trailingIdentWasAdded ? toCSSGridLineNamesValue(templateRows->item(templateRows->length() - 1)) : nullptr)) |
| return false; |
| |
| // Handle a template-area's row. |
| if (!parseGridTemplateAreasRow(gridAreaMap, rowCount, columnCount)) |
| return false; |
| ++rowCount; |
| |
| // Handle template-rows's track-size. |
| if (m_valueList->current() && m_valueList->current()->m_unit != CSSParserValue::Operator && m_valueList->current()->m_unit != CSSParserValue::String) { |
| RefPtrWillBeRawPtr<CSSValue> value = parseGridTrackSize(*m_valueList); |
| if (!value) |
| return false; |
| templateRows->append(value); |
| } else { |
| templateRows->append(cssValuePool().createIdentifierValue(CSSValueAuto)); |
| } |
| |
| // This will handle the trailing/leading <custom-ident>* in the grammar. |
| if (!parseGridLineNames(*m_valueList, *templateRows)) |
| return false; |
| trailingIdentWasAdded = templateRows->item(templateRows->length() - 1)->isGridLineNamesValue(); |
| } |
| |
| RefPtrWillBeRawPtr<CSSValue> columnsValue = nullptr; |
| if (m_valueList->current()) { |
| ASSERT(isForwardSlashOperator(m_valueList->current())); |
| columnsValue = parseGridTemplateColumns(important); |
| if (!columnsValue) |
| return false; |
| // The template-columns <track-list> can't be 'none'. |
| if (columnsValue->isPrimitiveValue() && toCSSPrimitiveValue(*columnsValue).getValueID() == CSSValueNone) |
| return false; |
| } |
| |
| addProperty(CSSPropertyGridTemplateRows, templateRows.release(), important); |
| addProperty(CSSPropertyGridTemplateColumns, columnsValue ? columnsValue.release() : cssValuePool().createIdentifierValue(CSSValueNone), important); |
| |
| RefPtrWillBeRawPtr<CSSValue> templateAreas = CSSGridTemplateAreasValue::create(gridAreaMap, rowCount, columnCount); |
| addProperty(CSSPropertyGridTemplateAreas, templateAreas.release(), important); |
| |
| return true; |
| } |
| |
| |
| bool CSSPropertyParser::parseGridTemplateShorthand(bool important) |
| { |
| ASSERT(RuntimeEnabledFeatures::cssGridLayoutEnabled()); |
| |
| ShorthandScope scope(this, CSSPropertyGridTemplate); |
| ASSERT(gridTemplateShorthand().length() == 3); |
| |
| // At least "none" must be defined. |
| if (!m_valueList->current()) |
| return false; |
| |
| bool firstValueIsNone = m_valueList->current()->id == CSSValueNone; |
| |
| // 1- 'none' case. |
| if (firstValueIsNone && !m_valueList->next()) { |
| addProperty(CSSPropertyGridTemplateColumns, cssValuePool().createIdentifierValue(CSSValueNone), important); |
| addProperty(CSSPropertyGridTemplateRows, cssValuePool().createIdentifierValue(CSSValueNone), important); |
| addProperty(CSSPropertyGridTemplateAreas, cssValuePool().createIdentifierValue(CSSValueNone), important); |
| return true; |
| } |
| |
| // 2- <grid-template-rows> / <grid-template-columns> |
| RefPtrWillBeRawPtr<CSSValue> rowsValue = nullptr; |
| if (firstValueIsNone) { |
| rowsValue = cssValuePool().createIdentifierValue(CSSValueNone); |
| } else { |
| rowsValue = parseGridTrackList(); |
| } |
| |
| if (rowsValue) { |
| RefPtrWillBeRawPtr<CSSValue> columnsValue = parseGridTemplateColumns(important); |
| if (!columnsValue) |
| return false; |
| |
| addProperty(CSSPropertyGridTemplateRows, rowsValue.release(), important); |
| addProperty(CSSPropertyGridTemplateColumns, columnsValue.release(), important); |
| addProperty(CSSPropertyGridTemplateAreas, cssValuePool().createIdentifierValue(CSSValueNone), important); |
| return true; |
| } |
| |
| // 3- [<line-names>? <string> <track-size>? <line-names>? ]+ syntax. |
| // It requires to rewind parsing due to previous syntax failures. |
| m_valueList->setCurrentIndex(0); |
| return parseGridTemplateRowsAndAreasAndColumns(important); |
| } |
| |
| bool CSSPropertyParser::parseGridShorthand(bool important) |
| { |
| ShorthandScope scope(this, CSSPropertyGrid); |
| ASSERT(shorthandForProperty(CSSPropertyGrid).length() == 8); |
| |
| // 1- <grid-template> |
| if (parseGridTemplateShorthand(important)) { |
| // It can only be specified the explicit or the implicit grid properties in a single grid declaration. |
| // The sub-properties not specified are set to their initial value, as normal for shorthands. |
| addProperty(CSSPropertyGridAutoFlow, cssValuePool().createImplicitInitialValue(), important); |
| addProperty(CSSPropertyGridAutoColumns, cssValuePool().createImplicitInitialValue(), important); |
| addProperty(CSSPropertyGridAutoRows, cssValuePool().createImplicitInitialValue(), important); |
| addProperty(CSSPropertyGridColumnGap, cssValuePool().createImplicitInitialValue(), important); |
| addProperty(CSSPropertyGridRowGap, cssValuePool().createImplicitInitialValue(), important); |
| return true; |
| } |
| |
| // Need to rewind parsing to explore the alternative syntax of this shorthand. |
| m_valueList->setCurrentIndex(0); |
| |
| // 2- <grid-auto-flow> [ <grid-auto-rows> [ / <grid-auto-columns> ]? ] |
| if (!legacyParseAndApplyValue(CSSPropertyGridAutoFlow, important)) |
| return false; |
| |
| RefPtrWillBeRawPtr<CSSValue> autoColumnsValue = nullptr; |
| RefPtrWillBeRawPtr<CSSValue> autoRowsValue = nullptr; |
| |
| if (m_valueList->current()) { |
| autoRowsValue = parseGridTrackSize(*m_valueList); |
| if (!autoRowsValue) |
| return false; |
| if (m_valueList->current()) { |
| if (!isForwardSlashOperator(m_valueList->current()) || !m_valueList->next()) |
| return false; |
| autoColumnsValue = parseGridTrackSize(*m_valueList); |
| if (!autoColumnsValue) |
| return false; |
| } |
| if (m_valueList->current()) |
| return false; |
| } else { |
| // Other omitted values are set to their initial values. |
| autoColumnsValue = cssValuePool().createImplicitInitialValue(); |
| autoRowsValue = cssValuePool().createImplicitInitialValue(); |
| } |
| |
| // if <grid-auto-columns> value is omitted, it is set to the value specified for grid-auto-rows. |
| if (!autoColumnsValue) |
| autoColumnsValue = autoRowsValue; |
| |
| addProperty(CSSPropertyGridAutoColumns, autoColumnsValue, important); |
| addProperty(CSSPropertyGridAutoRows, autoRowsValue, important); |
| |
| // It can only be specified the explicit or the implicit grid properties in a single grid declaration. |
| // The sub-properties not specified are set to their initial value, as normal for shorthands. |
| addProperty(CSSPropertyGridTemplateColumns, cssValuePool().createImplicitInitialValue(), important); |
| addProperty(CSSPropertyGridTemplateRows, cssValuePool().createImplicitInitialValue(), important); |
| addProperty(CSSPropertyGridTemplateAreas, cssValuePool().createImplicitInitialValue(), important); |
| addProperty(CSSPropertyGridColumnGap, cssValuePool().createImplicitInitialValue(), important); |
| addProperty(CSSPropertyGridRowGap, cssValuePool().createImplicitInitialValue(), important); |
| |
| return true; |
| } |
| |
| bool CSSPropertyParser::parseGridAreaShorthand(bool important) |
| { |
| ASSERT(RuntimeEnabledFeatures::cssGridLayoutEnabled()); |
| |
| ShorthandScope scope(this, CSSPropertyGridArea); |
| const StylePropertyShorthand& shorthand = gridAreaShorthand(); |
| ASSERT_UNUSED(shorthand, shorthand.length() == 4); |
| |
| RefPtrWillBeRawPtr<CSSValue> rowStartValue = parseGridPosition(); |
| if (!rowStartValue) |
| return false; |
| |
| RefPtrWillBeRawPtr<CSSValue> columnStartValue = nullptr; |
| if (!parseSingleGridAreaLonghand(columnStartValue)) |
| return false; |
| |
| RefPtrWillBeRawPtr<CSSValue> rowEndValue = nullptr; |
| if (!parseSingleGridAreaLonghand(rowEndValue)) |
| return false; |
| |
| RefPtrWillBeRawPtr<CSSValue> columnEndValue = nullptr; |
| if (!parseSingleGridAreaLonghand(columnEndValue)) |
| return false; |
| |
| if (!columnStartValue) |
| columnStartValue = gridMissingGridPositionValue(rowStartValue.get()); |
| |
| if (!rowEndValue) |
| rowEndValue = gridMissingGridPositionValue(rowStartValue.get()); |
| |
| if (!columnEndValue) |
| columnEndValue = gridMissingGridPositionValue(columnStartValue.get()); |
| |
| addProperty(CSSPropertyGridRowStart, rowStartValue, important); |
| addProperty(CSSPropertyGridColumnStart, columnStartValue, important); |
| addProperty(CSSPropertyGridRowEnd, rowEndValue, important); |
| addProperty(CSSPropertyGridColumnEnd, columnEndValue, important); |
| return true; |
| } |
| |
| bool CSSPropertyParser::parseSingleGridAreaLonghand(RefPtrWillBeRawPtr<CSSValue>& property) |
| { |
| if (!m_valueList->current()) |
| return true; |
| |
| if (!isForwardSlashOperator(m_valueList->current())) |
| return false; |
| |
| if (!m_valueList->next()) |
| return false; |
| |
| property = parseGridPosition(); |
| return true; |
| } |
| |
| static inline bool isClosingBracket(const CSSParserValue& value) |
| { |
| return value.m_unit == CSSParserValue::Operator && value.iValue == ']'; |
| } |
| |
| bool CSSPropertyParser::parseGridLineNames(CSSParserValueList& inputList, CSSValueList& valueList, CSSGridLineNamesValue* previousNamedAreaTrailingLineNames) |
| { |
| if (!inputList.current() || inputList.current()->m_unit != CSSParserValue::Operator || inputList.current()->iValue != '[') |
| return true; |
| |
| // Skip '[' |
| inputList.next(); |
| |
| RefPtrWillBeRawPtr<CSSGridLineNamesValue> lineNames = previousNamedAreaTrailingLineNames; |
| if (!lineNames) |
| lineNames = CSSGridLineNamesValue::create(); |
| |
| while (CSSParserValue* identValue = inputList.current()) { |
| if (isClosingBracket(*identValue)) |
| break; |
| |
| if (!isValidCustomIdentForGridPositions(*identValue)) |
| return false; |
| |
| RefPtrWillBeRawPtr<CSSCustomIdentValue> lineName = createPrimitiveCustomIdentValue(identValue); |
| lineNames->append(lineName.release()); |
| inputList.next(); |
| } |
| |
| if (!inputList.current() || !isClosingBracket(*inputList.current())) |
| return false; |
| |
| if (!previousNamedAreaTrailingLineNames) |
| valueList.append(lineNames.release()); |
| |
| // Consume ']' |
| inputList.next(); |
| return true; |
| } |
| |
| static bool allTracksAreFixedSized(CSSValueList& valueList) |
| { |
| for (auto value : valueList) { |
| if (value->isGridLineNamesValue()) |
| continue; |
| // The auto-repeat value holds a <fixed-size> = <fixed-breadth> | minmax( <fixed-breadth>, <track-breadth> ) |
| if (value->isGridAutoRepeatValue()) { |
| if (!allTracksAreFixedSized(toCSSValueList(*value))) |
| return false; |
| continue; |
| } |
| ASSERT(value->isPrimitiveValue() || (value->isFunctionValue() && toCSSFunctionValue(*value).item(0))); |
| const CSSPrimitiveValue& primitiveValue = value->isPrimitiveValue() |
| ? toCSSPrimitiveValue(*value) |
| : toCSSPrimitiveValue(*toCSSFunctionValue(*value).item(0)); |
| CSSValueID valueID = primitiveValue.getValueID(); |
| if (valueID == CSSValueMinContent || valueID == CSSValueMaxContent || valueID == CSSValueAuto || primitiveValue.isFlex()) |
| return false; |
| } |
| return true; |
| } |
| |
| PassRefPtrWillBeRawPtr<CSSValue> CSSPropertyParser::parseGridTrackList() |
| { |
| ASSERT(RuntimeEnabledFeatures::cssGridLayoutEnabled()); |
| |
| CSSParserValue* value = m_valueList->current(); |
| if (value->id == CSSValueNone) { |
| m_valueList->next(); |
| return cssValuePool().createIdentifierValue(CSSValueNone); |
| } |
| |
| RefPtrWillBeRawPtr<CSSValueList> values = CSSValueList::createSpaceSeparated(); |
| // Handle leading <custom-ident>*. |
| if (!parseGridLineNames(*m_valueList, *values)) |
| return nullptr; |
| |
| bool seenTrackSizeOrRepeatFunction = false; |
| bool seenAutoRepeat = false; |
| while (CSSParserValue* currentValue = m_valueList->current()) { |
| if (isForwardSlashOperator(currentValue)) |
| break; |
| if (currentValue->m_unit == CSSParserValue::Function && currentValue->function->id == CSSValueRepeat) { |
| bool isAutoRepeat; |
| if (!parseGridTrackRepeatFunction(*values, isAutoRepeat)) |
| return nullptr; |
| if (isAutoRepeat && seenAutoRepeat) |
| return nullptr; |
| seenTrackSizeOrRepeatFunction = true; |
| seenAutoRepeat = seenAutoRepeat || isAutoRepeat; |
| } else { |
| RefPtrWillBeRawPtr<CSSValue> value = parseGridTrackSize(*m_valueList, seenAutoRepeat ? FixedSizeOnly : AllowAll); |
| if (!value) |
| return nullptr; |
| values->append(value); |
| seenTrackSizeOrRepeatFunction = true; |
| } |
| // This will handle the trailing <custom-ident>* in the grammar. |
| if (!parseGridLineNames(*m_valueList, *values)) |
| return nullptr; |
| } |
| |
| // We should have found a <track-size> or else it is not a valid <track-list> |
| if (!seenTrackSizeOrRepeatFunction) |
| return nullptr; |
| |
| // <auto-repeat> requires definite minimum track sizes in order to compute the number of repetitions. |
| // The above while loop detects those appearances after the <auto-repeat> but not the ones before. |
| if (seenAutoRepeat && !allTracksAreFixedSized(*values)) |
| return nullptr; |
| |
| return values; |
| } |
| |
| bool CSSPropertyParser::parseGridTrackRepeatFunction(CSSValueList& list, bool& isAutoRepeat) |
| { |
| CSSParserValueList* arguments = m_valueList->current()->function->args.get(); |
| if (!arguments || arguments->size() < 3 || !isComma(arguments->valueAt(1))) |
| return false; |
| |
| CSSParserValue* currentValue = arguments->valueAt(0); |
| isAutoRepeat = currentValue->id == CSSValueAutoFill || currentValue->id == CSSValueAutoFit; |
| if (!isAutoRepeat && !validUnit(currentValue, FPositiveInteger)) |
| return false; |
| |
| // The number of repetitions for <auto-repeat> is not important at parsing level |
| // because it will be computed later, let's set it to 1. |
| size_t repetitions = isAutoRepeat ? 1 : clampTo<size_t>(currentValue->fValue, 0, kGridMaxTracks); |
| |
| RefPtrWillBeRawPtr<CSSValueList> repeatedValues = isAutoRepeat ? CSSGridAutoRepeatValue::create(currentValue->id) : CSSValueList::createSpaceSeparated(); |
| arguments->next(); // Skip the repetition count. |
| arguments->next(); // Skip the comma. |
| |
| // Handle leading <custom-ident>*. |
| if (!parseGridLineNames(*arguments, *repeatedValues)) |
| return false; |
| |
| size_t numberOfTracks = 0; |
| TrackSizeRestriction restriction = isAutoRepeat ? FixedSizeOnly : AllowAll; |
| while (arguments->current()) { |
| if (isAutoRepeat && numberOfTracks) |
| return false; |
| |
| RefPtrWillBeRawPtr<CSSValue> trackSize = parseGridTrackSize(*arguments, restriction); |
| if (!trackSize) |
| return false; |
| |
| repeatedValues->append(trackSize); |
| ++numberOfTracks; |
| |
| // This takes care of any trailing <custom-ident>* in the grammar. |
| if (!parseGridLineNames(*arguments, *repeatedValues)) |
| return false; |
| } |
| |
| // We should have found at least one <track-size> or else it is not a valid <track-list>. |
| if (!numberOfTracks) |
| return false; |
| |
| if (isAutoRepeat) { |
| list.append(repeatedValues.release()); |
| } else { |
| // We clamp the number of repetitions to a multiple of the repeat() track list's size, while staying below the max |
| // grid size. |
| repetitions = std::min(repetitions, kGridMaxTracks / numberOfTracks); |
| |
| for (size_t i = 0; i < repetitions; ++i) { |
| for (size_t j = 0; j < repeatedValues->length(); ++j) |
| list.append(repeatedValues->item(j)); |
| } |
| } |
| |
| // parseGridTrackSize iterated over the repeat arguments, move to the next value. |
| m_valueList->next(); |
| return true; |
| } |
| |
| |
| PassRefPtrWillBeRawPtr<CSSValue> CSSPropertyParser::parseGridTrackSize(CSSParserValueList& inputList, TrackSizeRestriction restriction) |
| { |
| ASSERT(RuntimeEnabledFeatures::cssGridLayoutEnabled()); |
| |
| CSSParserValue* currentValue = inputList.current(); |
| inputList.next(); |
| |
| if (currentValue->id == CSSValueAuto) |
| return restriction == AllowAll ? cssValuePool().createIdentifierValue(CSSValueAuto) : nullptr; |
| |
| if (currentValue->m_unit == CSSParserValue::Function && currentValue->function->id == CSSValueMinmax) { |
| // The spec defines the following grammar: minmax( <track-breadth> , <track-breadth> ) |
| CSSParserValueList* arguments = currentValue->function->args.get(); |
| if (!arguments || arguments->size() != 3 || !isComma(arguments->valueAt(1))) |
| return nullptr; |
| |
| RefPtrWillBeRawPtr<CSSPrimitiveValue> minTrackBreadth = parseGridBreadth(arguments->valueAt(0), restriction); |
| if (!minTrackBreadth) |
| return nullptr; |
| |
| RefPtrWillBeRawPtr<CSSPrimitiveValue> maxTrackBreadth = parseGridBreadth(arguments->valueAt(2)); |
| if (!maxTrackBreadth) |
| return nullptr; |
| |
| RefPtrWillBeRawPtr<CSSFunctionValue> result = CSSFunctionValue::create(CSSValueMinmax); |
| result->append(minTrackBreadth); |
| result->append(maxTrackBreadth); |
| return result.release(); |
| } |
| |
| return parseGridBreadth(currentValue, restriction); |
| } |
| |
| PassRefPtrWillBeRawPtr<CSSPrimitiveValue> CSSPropertyParser::parseGridBreadth(CSSParserValue* currentValue, TrackSizeRestriction restriction) |
| { |
| if (currentValue->id == CSSValueMinContent || currentValue->id == CSSValueMaxContent || currentValue->id == CSSValueAuto) |
| return restriction == AllowAll ? cssValuePool().createIdentifierValue(currentValue->id) : nullptr; |
| |
| if (currentValue->unit() == CSSPrimitiveValue::UnitType::Fraction) { |
| if (restriction == FixedSizeOnly) |
| return nullptr; |
| |
| double flexValue = currentValue->fValue; |
| |
| // Fractional unit is a non-negative dimension. |
| if (flexValue < 0) |
| return nullptr; |
| |
| return cssValuePool().createValue(flexValue, CSSPrimitiveValue::UnitType::Fraction); |
| } |
| |
| if (!validUnit(currentValue, FNonNeg | FLength | FPercent)) |
| return nullptr; |
| |
| return createPrimitiveNumericValue(currentValue); |
| } |
| |
| static Vector<String> parseGridTemplateAreasColumnNames(const String& gridRowNames) |
| { |
| ASSERT(!gridRowNames.isEmpty()); |
| Vector<String> columnNames; |
| // Using StringImpl to avoid checks and indirection in every call to String::operator[]. |
| StringImpl& text = *gridRowNames.impl(); |
| |
| StringBuilder areaName; |
| for (unsigned i = 0; i < text.length(); ++i) { |
| if (text[i] == ' ') { |
| if (!areaName.isEmpty()) { |
| columnNames.append(areaName.toString()); |
| areaName.clear(); |
| } |
| continue; |
| } |
| if (text[i] == '.') { |
| if (areaName == ".") |
| continue; |
| if (!areaName.isEmpty()) { |
| columnNames.append(areaName.toString()); |
| areaName.clear(); |
| } |
| } else { |
| if (areaName == ".") { |
| columnNames.append(areaName.toString()); |
| areaName.clear(); |
| } |
| } |
| |
| areaName.append(text[i]); |
| } |
| |
| if (!areaName.isEmpty()) |
| columnNames.append(areaName.toString()); |
| |
| return columnNames; |
| } |
| |
| bool CSSPropertyParser::parseGridTemplateAreasRow(NamedGridAreaMap& gridAreaMap, const size_t rowCount, size_t& columnCount) |
| { |
| CSSParserValue* currentValue = m_valueList->current(); |
| if (!currentValue || currentValue->m_unit != CSSParserValue::String) |
| return false; |
| |
| String gridRowNames = currentValue->string; |
| if (gridRowNames.isEmpty() || gridRowNames.containsOnlyWhitespace()) |
| return false; |
| |
| Vector<String> columnNames = parseGridTemplateAreasColumnNames(gridRowNames); |
| if (!columnCount) { |
| columnCount = columnNames.size(); |
| ASSERT(columnCount); |
| } else if (columnCount != columnNames.size()) { |
| // The declaration is invalid is all the rows don't have the number of columns. |
| return false; |
| } |
| |
| for (size_t currentCol = 0; currentCol < columnCount; ++currentCol) { |
| const String& gridAreaName = columnNames[currentCol]; |
| |
| // Unamed areas are always valid (we consider them to be 1x1). |
| if (gridAreaName == ".") |
| continue; |
| |
| // We handle several grid areas with the same name at once to simplify the validation code. |
| size_t lookAheadCol; |
| for (lookAheadCol = currentCol + 1; lookAheadCol < columnCount; ++lookAheadCol) { |
| if (columnNames[lookAheadCol] != gridAreaName) |
| break; |
| } |
| |
| NamedGridAreaMap::iterator gridAreaIt = gridAreaMap.find(gridAreaName); |
| if (gridAreaIt == gridAreaMap.end()) { |
| gridAreaMap.add(gridAreaName, GridArea(GridSpan::translatedDefiniteGridSpan(rowCount, rowCount + 1), GridSpan::translatedDefiniteGridSpan(currentCol, lookAheadCol))); |
| } else { |
| GridArea& gridArea = gridAreaIt->value; |
| |
| // The following checks test that the grid area is a single filled-in rectangle. |
| // 1. The new row is adjacent to the previously parsed row. |
| if (rowCount != gridArea.rows.endLine()) |
| return false; |
| |
| // 2. The new area starts at the same position as the previously parsed area. |
| if (currentCol != gridArea.columns.startLine()) |
| return false; |
| |
| // 3. The new area ends at the same position as the previously parsed area. |
| if (lookAheadCol != gridArea.columns.endLine()) |
| return false; |
| |
| gridArea.rows = GridSpan::translatedDefiniteGridSpan(gridArea.rows.startLine(), gridArea.rows.endLine() + 1); |
| } |
| currentCol = lookAheadCol - 1; |
| } |
| |
| m_valueList->next(); |
| return true; |
| } |
| |
| PassRefPtrWillBeRawPtr<CSSValue> CSSPropertyParser::parseGridTemplateAreas() |
| { |
| if (m_valueList->current() && m_valueList->current()->id == CSSValueNone) { |
| m_valueList->next(); |
| return cssValuePool().createIdentifierValue(CSSValueNone); |
| } |
| |
| NamedGridAreaMap gridAreaMap; |
| size_t rowCount = 0; |
| size_t columnCount = 0; |
| |
| while (m_valueList->current()) { |
| if (!parseGridTemplateAreasRow(gridAreaMap, rowCount, columnCount)) |
| return nullptr; |
| ++rowCount; |
| } |
| |
| if (!rowCount || !columnCount) |
| return nullptr; |
| |
| return CSSGridTemplateAreasValue::create(gridAreaMap, rowCount, columnCount); |
| } |
| |
| PassRefPtrWillBeRawPtr<CSSValue> CSSPropertyParser::parseGridAutoFlow(CSSParserValueList& list) |
| { |
| // [ row | column ] || dense |
| ASSERT(RuntimeEnabledFeatures::cssGridLayoutEnabled()); |
| |
| CSSParserValue* value = list.current(); |
| if (!value) |
| return nullptr; |
| |
| RefPtrWillBeRawPtr<CSSValueList> parsedValues = CSSValueList::createSpaceSeparated(); |
| |
| // First parameter. |
| CSSValueID firstId = value->id; |
| if (firstId != CSSValueRow && firstId != CSSValueColumn && firstId != CSSValueDense) |
| return nullptr; |
| parsedValues->append(cssValuePool().createIdentifierValue(firstId)); |
| |
| // Second parameter, if any. |
| value = list.next(); |
| if (value) { |
| switch (firstId) { |
| case CSSValueRow: |
| case CSSValueColumn: |
| if (value->id != CSSValueDense) |
| return parsedValues; |
| break; |
| case CSSValueDense: |
| if (value->id != CSSValueRow && value->id != CSSValueColumn) |
| return parsedValues; |
| break; |
| default: |
| return parsedValues; |
| } |
| parsedValues->append(cssValuePool().createIdentifierValue(value->id)); |
| list.next(); |
| } |
| |
| return parsedValues; |
| } |
| |
| bool CSSPropertyParser::parseCalculation(CSSParserValue* value, ValueRange range) |
| { |
| ASSERT(isCalculation(value)); |
| |
| CSSParserTokenRange args = value->calcFunction->args; |
| |
| ASSERT(!m_parsedCalculation); |
| m_parsedCalculation = CSSCalcValue::create(args, range); |
| |
| if (!m_parsedCalculation) |
| return false; |
| |
| return true; |
| } |
| |
| } // namespace blink |