blob: 35601835921174753030f8c2cf6e6e2093bdfa19 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/css/parser/CSSParserFastPaths.h"
#include "build/build_config.h"
#include "core/StylePropertyShorthand.h"
#include "core/css/CSSColorValue.h"
#include "core/css/CSSFunctionValue.h"
#include "core/css/CSSIdentifierValue.h"
#include "core/css/CSSInheritedValue.h"
#include "core/css/CSSInitialValue.h"
#include "core/css/CSSPrimitiveValue.h"
#include "core/css/CSSUnsetValue.h"
#include "core/css/StyleColor.h"
#include "core/css/parser/CSSParserIdioms.h"
#include "core/css/parser/CSSPropertyParser.h"
#include "core/css/properties/CSSProperty.h"
#include "core/html/parser/HTMLParserIdioms.h"
#include "platform/runtime_enabled_features.h"
#include "platform/wtf/text/StringToNumber.h"
namespace blink {
using namespace cssvalue;
static inline bool IsSimpleLengthPropertyID(CSSPropertyID property_id,
bool& accepts_negative_numbers) {
switch (property_id) {
case CSSPropertyBlockSize:
case CSSPropertyInlineSize:
case CSSPropertyMinBlockSize:
case CSSPropertyMinInlineSize:
case CSSPropertyFontSize:
case CSSPropertyHeight:
case CSSPropertyWidth:
case CSSPropertyMinHeight:
case CSSPropertyMinWidth:
case CSSPropertyPaddingBottom:
case CSSPropertyPaddingLeft:
case CSSPropertyPaddingRight:
case CSSPropertyPaddingTop:
case CSSPropertyScrollMarginBlockEnd:
case CSSPropertyScrollMarginBlockStart:
case CSSPropertyScrollMarginBottom:
case CSSPropertyScrollMarginInlineEnd:
case CSSPropertyScrollMarginInlineStart:
case CSSPropertyScrollMarginLeft:
case CSSPropertyScrollMarginRight:
case CSSPropertyScrollMarginTop:
case CSSPropertyScrollPaddingBlockEnd:
case CSSPropertyScrollPaddingBlockStart:
case CSSPropertyScrollPaddingBottom:
case CSSPropertyScrollPaddingInlineEnd:
case CSSPropertyScrollPaddingInlineStart:
case CSSPropertyScrollPaddingLeft:
case CSSPropertyScrollPaddingRight:
case CSSPropertyScrollPaddingTop:
case CSSPropertyWebkitLogicalWidth:
case CSSPropertyWebkitLogicalHeight:
case CSSPropertyWebkitMinLogicalWidth:
case CSSPropertyWebkitMinLogicalHeight:
case CSSPropertyWebkitPaddingAfter:
case CSSPropertyWebkitPaddingBefore:
case CSSPropertyWebkitPaddingEnd:
case CSSPropertyWebkitPaddingStart:
case CSSPropertyShapeMargin:
case CSSPropertyR:
case CSSPropertyRx:
case CSSPropertyRy:
accepts_negative_numbers = false;
return true;
case CSSPropertyBottom:
case CSSPropertyCx:
case CSSPropertyCy:
case CSSPropertyLeft:
case CSSPropertyMarginBottom:
case CSSPropertyMarginLeft:
case CSSPropertyMarginRight:
case CSSPropertyMarginTop:
case CSSPropertyOffsetDistance:
case CSSPropertyRight:
case CSSPropertyTop:
case CSSPropertyWebkitMarginAfter:
case CSSPropertyWebkitMarginBefore:
case CSSPropertyWebkitMarginEnd:
case CSSPropertyWebkitMarginStart:
case CSSPropertyX:
case CSSPropertyY:
accepts_negative_numbers = true;
return true;
default:
return false;
}
}
template <typename CharacterType>
static inline bool ParseSimpleLength(const CharacterType* characters,
unsigned length,
CSSPrimitiveValue::UnitType& unit,
double& number) {
if (length > 2 && (characters[length - 2] | 0x20) == 'p' &&
(characters[length - 1] | 0x20) == 'x') {
length -= 2;
unit = CSSPrimitiveValue::UnitType::kPixels;
} else if (length > 1 && characters[length - 1] == '%') {
length -= 1;
unit = CSSPrimitiveValue::UnitType::kPercentage;
}
// We rely on charactersToDouble for validation as well. The function
// will set "ok" to "false" if the entire passed-in character range does
// not represent a double.
bool ok;
number = CharactersToDouble(characters, length, &ok);
if (!ok)
return false;
number = clampTo<double>(number, -std::numeric_limits<float>::max(),
std::numeric_limits<float>::max());
return true;
}
static CSSValue* ParseSimpleLengthValue(CSSPropertyID property_id,
const String& string,
CSSParserMode css_parser_mode) {
DCHECK(!string.IsEmpty());
bool accepts_negative_numbers = false;
// In @viewport, width and height are shorthands, not simple length values.
if (IsCSSViewportParsingEnabledForMode(css_parser_mode) ||
!IsSimpleLengthPropertyID(property_id, accepts_negative_numbers))
return nullptr;
unsigned length = string.length();
double number;
CSSPrimitiveValue::UnitType unit = CSSPrimitiveValue::UnitType::kNumber;
if (string.Is8Bit()) {
if (!ParseSimpleLength(string.Characters8(), length, unit, number))
return nullptr;
} else {
if (!ParseSimpleLength(string.Characters16(), length, unit, number))
return nullptr;
}
if (unit == CSSPrimitiveValue::UnitType::kNumber) {
if (css_parser_mode == kSVGAttributeMode)
unit = CSSPrimitiveValue::UnitType::kUserUnits;
else if (!number)
unit = CSSPrimitiveValue::UnitType::kPixels;
else
return nullptr;
}
if (number < 0 && !accepts_negative_numbers)
return nullptr;
return CSSPrimitiveValue::Create(number, unit);
}
static inline bool IsColorPropertyID(CSSPropertyID property_id) {
switch (property_id) {
case CSSPropertyCaretColor:
case CSSPropertyColor:
case CSSPropertyBackgroundColor:
case CSSPropertyBorderBottomColor:
case CSSPropertyBorderLeftColor:
case CSSPropertyBorderRightColor:
case CSSPropertyBorderTopColor:
case CSSPropertyFill:
case CSSPropertyFloodColor:
case CSSPropertyLightingColor:
case CSSPropertyOutlineColor:
case CSSPropertyStopColor:
case CSSPropertyStroke:
case CSSPropertyWebkitBorderAfterColor:
case CSSPropertyWebkitBorderBeforeColor:
case CSSPropertyWebkitBorderEndColor:
case CSSPropertyWebkitBorderStartColor:
case CSSPropertyColumnRuleColor:
case CSSPropertyWebkitTextEmphasisColor:
case CSSPropertyWebkitTextFillColor:
case CSSPropertyWebkitTextStrokeColor:
case CSSPropertyTextDecorationColor:
return true;
default:
return false;
}
}
// Returns the number of characters which form a valid double
// and are terminated by the given terminator character
template <typename CharacterType>
static int CheckForValidDouble(const CharacterType* string,
const CharacterType* end,
const bool terminated_by_space,
const char terminator) {
int length = end - string;
if (length < 1)
return 0;
bool decimal_mark_seen = false;
int processed_length = 0;
for (int i = 0; i < length; ++i) {
if (string[i] == terminator ||
(terminated_by_space && IsHTMLSpace<CharacterType>(string[i]))) {
processed_length = i;
break;
}
if (!IsASCIIDigit(string[i])) {
if (!decimal_mark_seen && string[i] == '.')
decimal_mark_seen = true;
else
return 0;
}
}
if (decimal_mark_seen && processed_length == 1)
return 0;
return processed_length;
}
// Returns the number of characters consumed for parsing a valid double
// terminated by the given terminator character
template <typename CharacterType>
static int ParseDouble(const CharacterType* string,
const CharacterType* end,
const char terminator,
const bool terminated_by_space,
double& value) {
int length =
CheckForValidDouble(string, end, terminated_by_space, terminator);
if (!length)
return 0;
int position = 0;
double local_value = 0;
// The consumed characters here are guaranteed to be
// ASCII digits with or without a decimal mark
for (; position < length; ++position) {
if (string[position] == '.')
break;
local_value = local_value * 10 + string[position] - '0';
}
if (++position == length) {
value = local_value;
return length;
}
double fraction = 0;
double scale = 1;
const double kMaxScale = 1000000;
while (position < length && scale < kMaxScale) {
fraction = fraction * 10 + string[position++] - '0';
scale *= 10;
}
value = local_value + fraction / scale;
return length;
}
template <typename CharacterType>
static bool ParseColorNumberOrPercentage(const CharacterType*& string,
const CharacterType* end,
const char terminator,
bool& should_whitespace_terminate,
bool is_first_value,
CSSPrimitiveValue::UnitType& expect,
int& value) {
const CharacterType* current = string;
double local_value = 0;
bool negative = false;
while (current != end && IsHTMLSpace<CharacterType>(*current))
current++;
if (current != end && *current == '-') {
negative = true;
current++;
}
if (current == end || !IsASCIIDigit(*current))
return false;
while (current != end && IsASCIIDigit(*current)) {
double new_value = local_value * 10 + *current++ - '0';
if (new_value >= 255) {
// Clamp values at 255.
local_value = 255;
while (current != end && IsASCIIDigit(*current))
++current;
break;
}
local_value = new_value;
}
if (current == end)
return false;
if (expect == CSSPrimitiveValue::UnitType::kNumber && *current == '%')
return false;
if (*current == '.') {
// We already parsed the integral part, try to parse
// the fraction part.
double fractional = 0;
int num_characters_parsed =
ParseDouble(current, end, '%', false, fractional);
if (num_characters_parsed) {
// Number is a percent.
current += num_characters_parsed;
if (*current != '%')
return false;
} else {
// Number is a decimal.
num_characters_parsed =
ParseDouble(current, end, terminator, true, fractional);
if (!num_characters_parsed)
return false;
current += num_characters_parsed;
}
local_value += fractional;
}
if (expect == CSSPrimitiveValue::UnitType::kPercentage && *current != '%')
return false;
if (*current == '%') {
expect = CSSPrimitiveValue::UnitType::kPercentage;
local_value = local_value / 100.0 * 255.0;
// Clamp values at 255 for percentages over 100%
if (local_value > 255)
local_value = 255;
current++;
} else {
expect = CSSPrimitiveValue::UnitType::kNumber;
}
while (current != end && IsHTMLSpace<CharacterType>(*current))
current++;
if (current == end || *current != terminator) {
if (!should_whitespace_terminate ||
!IsHTMLSpace<CharacterType>(*(current - 1))) {
return false;
}
} else if (should_whitespace_terminate && is_first_value) {
should_whitespace_terminate = false;
} else if (should_whitespace_terminate) {
return false;
}
if (!should_whitespace_terminate)
current++;
// Clamp negative values at zero.
value = negative ? 0 : static_cast<int>(roundf(local_value));
string = current;
return true;
}
template <typename CharacterType>
static inline bool IsTenthAlpha(const CharacterType* string, const int length) {
// "0.X"
if (length == 3 && string[0] == '0' && string[1] == '.' &&
IsASCIIDigit(string[2]))
return true;
// ".X"
if (length == 2 && string[0] == '.' && IsASCIIDigit(string[1]))
return true;
return false;
}
template <typename CharacterType>
static inline bool ParseAlphaValue(const CharacterType*& string,
const CharacterType* end,
const char terminator,
int& value) {
while (string != end && IsHTMLSpace<CharacterType>(*string))
string++;
bool negative = false;
if (string != end && *string == '-') {
negative = true;
string++;
}
value = 0;
int length = end - string;
if (length < 2)
return false;
if (string[length - 1] != terminator || !IsASCIIDigit(string[length - 2]))
return false;
if (string[0] != '0' && string[0] != '1' && string[0] != '.') {
if (CheckForValidDouble(string, end, false, terminator)) {
value = negative ? 0 : 255;
string = end;
return true;
}
return false;
}
if (length == 2 && string[0] != '.') {
value = !negative && string[0] == '1' ? 255 : 0;
string = end;
return true;
}
if (IsTenthAlpha(string, length - 1)) {
// Fast conversions for 0.1 steps of alpha values between 0.0 and 0.9,
// where 0.1 alpha is value 26 (25.5 rounded) and so on.
static const int kTenthAlphaValues[] = {0, 26, 51, 77, 102,
128, 153, 179, 204, 230};
value = negative ? 0 : kTenthAlphaValues[string[length - 2] - '0'];
string = end;
return true;
}
double alpha = 0;
if (!ParseDouble(string, end, terminator, false, alpha))
return false;
value =
negative ? 0 : static_cast<int>(roundf(std::min(alpha, 1.0) * 255.0f));
string = end;
return true;
}
template <typename CharacterType>
static inline bool MightBeRGBOrRGBA(const CharacterType* characters,
unsigned length) {
if (length < 5)
return false;
return IsASCIIAlphaCaselessEqual(characters[0], 'r') &&
IsASCIIAlphaCaselessEqual(characters[1], 'g') &&
IsASCIIAlphaCaselessEqual(characters[2], 'b') &&
(characters[3] == '(' ||
(IsASCIIAlphaCaselessEqual(characters[3], 'a') &&
characters[4] == '('));
}
template <typename CharacterType>
static bool FastParseColorInternal(RGBA32& rgb,
const CharacterType* characters,
unsigned length,
bool quirks_mode) {
CSSPrimitiveValue::UnitType expect = CSSPrimitiveValue::UnitType::kUnknown;
if (length >= 4 && characters[0] == '#')
return Color::ParseHexColor(characters + 1, length - 1, rgb);
if (quirks_mode && (length == 3 || length == 6)) {
if (Color::ParseHexColor(characters, length, rgb))
return true;
}
// rgb() and rgba() have the same syntax
if (MightBeRGBOrRGBA(characters, length)) {
int length_to_add = IsASCIIAlphaCaselessEqual(characters[3], 'a') ? 5 : 4;
const CharacterType* current = characters + length_to_add;
const CharacterType* end = characters + length;
int red;
int green;
int blue;
int alpha;
bool should_have_alpha = false;
bool should_whitespace_terminate = true;
bool no_whitespace_check = false;
if (!ParseColorNumberOrPercentage(current, end, ',',
should_whitespace_terminate,
true /* is_first_value */, expect, red))
return false;
if (!ParseColorNumberOrPercentage(
current, end, ',', should_whitespace_terminate,
false /* is_first_value */, expect, green))
return false;
if (!ParseColorNumberOrPercentage(current, end, ',', no_whitespace_check,
false /* is_first_value */, expect,
blue)) {
// Might have slash as separator
if (ParseColorNumberOrPercentage(current, end, '/', no_whitespace_check,
false /* is_first_value */, expect,
blue)) {
if (!should_whitespace_terminate)
return false;
should_have_alpha = true;
}
// Might not have alpha
else if (!ParseColorNumberOrPercentage(
current, end, ')', no_whitespace_check,
false /* is_first_value */, expect, blue))
return false;
} else {
if (should_whitespace_terminate)
return false;
should_have_alpha = true;
}
if (should_have_alpha) {
if (!ParseAlphaValue(current, end, ')', alpha))
return false;
if (current != end)
return false;
rgb = MakeRGBA(red, green, blue, alpha);
} else {
if (current != end)
return false;
rgb = MakeRGB(red, green, blue);
}
return true;
}
return false;
}
CSSValue* CSSParserFastPaths::ParseColor(const String& string,
CSSParserMode parser_mode) {
DCHECK(!string.IsEmpty());
CSSValueID value_id = CssValueKeywordID(string);
if (StyleColor::IsColorKeyword(value_id)) {
if (!isValueAllowedInMode(value_id, parser_mode))
return nullptr;
return CSSIdentifierValue::Create(value_id);
}
RGBA32 color;
bool quirks_mode = IsQuirksModeBehavior(parser_mode);
// Fast path for hex colors and rgb()/rgba() colors
bool parse_result;
if (string.Is8Bit())
parse_result = FastParseColorInternal(color, string.Characters8(),
string.length(), quirks_mode);
else
parse_result = FastParseColorInternal(color, string.Characters16(),
string.length(), quirks_mode);
if (!parse_result)
return nullptr;
return CSSColorValue::Create(color);
}
bool CSSParserFastPaths::IsValidKeywordPropertyAndValue(
CSSPropertyID property_id,
CSSValueID value_id,
CSSParserMode parser_mode) {
if (value_id == CSSValueInvalid ||
!isValueAllowedInMode(value_id, parser_mode))
return false;
// For range checks, enum ordering is defined by CSSValueKeywords.in.
switch (property_id) {
case CSSPropertyAlignmentBaseline:
return value_id == CSSValueAuto || value_id == CSSValueAlphabetic ||
value_id == CSSValueBaseline || value_id == CSSValueMiddle ||
(value_id >= CSSValueBeforeEdge &&
value_id <= CSSValueMathematical);
case CSSPropertyAll:
return false; // Only accepts css-wide keywords
case CSSPropertyBackgroundRepeatX:
case CSSPropertyBackgroundRepeatY:
return value_id == CSSValueRepeat || value_id == CSSValueNoRepeat;
case CSSPropertyBorderCollapse:
return value_id == CSSValueCollapse || value_id == CSSValueSeparate;
case CSSPropertyBorderTopStyle:
case CSSPropertyBorderRightStyle:
case CSSPropertyBorderBottomStyle:
case CSSPropertyBorderLeftStyle:
case CSSPropertyWebkitBorderAfterStyle:
case CSSPropertyWebkitBorderBeforeStyle:
case CSSPropertyWebkitBorderEndStyle:
case CSSPropertyWebkitBorderStartStyle:
case CSSPropertyColumnRuleStyle:
return value_id >= CSSValueNone && value_id <= CSSValueDouble;
case CSSPropertyBoxSizing:
return value_id == CSSValueBorderBox || value_id == CSSValueContentBox;
case CSSPropertyBufferedRendering:
return value_id == CSSValueAuto || value_id == CSSValueDynamic ||
value_id == CSSValueStatic;
case CSSPropertyCaptionSide:
return value_id == CSSValueTop || value_id == CSSValueBottom;
case CSSPropertyClear:
return value_id == CSSValueNone || value_id == CSSValueLeft ||
value_id == CSSValueRight || value_id == CSSValueBoth;
case CSSPropertyClipRule:
case CSSPropertyFillRule:
return value_id == CSSValueNonzero || value_id == CSSValueEvenodd;
case CSSPropertyColorInterpolation:
case CSSPropertyColorInterpolationFilters:
return value_id == CSSValueAuto || value_id == CSSValueSRGB ||
value_id == CSSValueLinearRGB;
case CSSPropertyColorRendering:
return value_id == CSSValueAuto || value_id == CSSValueOptimizeSpeed ||
value_id == CSSValueOptimizeQuality;
case CSSPropertyDirection:
return value_id == CSSValueLtr || value_id == CSSValueRtl;
case CSSPropertyDisplay:
return (value_id >= CSSValueInline && value_id <= CSSValueInlineFlex) ||
value_id == CSSValueWebkitFlex ||
value_id == CSSValueWebkitInlineFlex || value_id == CSSValueNone ||
value_id == CSSValueGrid || value_id == CSSValueInlineGrid ||
(RuntimeEnabledFeatures::CSSDisplayContentsEnabled() &&
value_id == CSSValueContents);
case CSSPropertyDominantBaseline:
return value_id == CSSValueAuto || value_id == CSSValueAlphabetic ||
value_id == CSSValueMiddle ||
(value_id >= CSSValueUseScript && value_id <= CSSValueResetSize) ||
(value_id >= CSSValueCentral && value_id <= CSSValueMathematical);
case CSSPropertyEmptyCells:
return value_id == CSSValueShow || value_id == CSSValueHide;
case CSSPropertyFloat:
return value_id == CSSValueLeft || value_id == CSSValueRight ||
value_id == CSSValueNone;
case CSSPropertyImageRendering:
return value_id == CSSValueAuto ||
value_id == CSSValueWebkitOptimizeContrast ||
value_id == CSSValuePixelated;
case CSSPropertyIsolation:
return value_id == CSSValueAuto || value_id == CSSValueIsolate;
case CSSPropertyListStylePosition:
return value_id == CSSValueInside || value_id == CSSValueOutside;
case CSSPropertyListStyleType:
return (value_id >= CSSValueDisc && value_id <= CSSValueKatakanaIroha) ||
value_id == CSSValueNone;
case CSSPropertyMaskType:
return value_id == CSSValueLuminance || value_id == CSSValueAlpha;
case CSSPropertyObjectFit:
return value_id == CSSValueFill || value_id == CSSValueContain ||
value_id == CSSValueCover || value_id == CSSValueNone ||
value_id == CSSValueScaleDown;
case CSSPropertyOutlineStyle:
return value_id == CSSValueAuto || value_id == CSSValueNone ||
(value_id >= CSSValueInset && value_id <= CSSValueDouble);
case CSSPropertyOverflowAnchor:
return value_id == CSSValueVisible || value_id == CSSValueNone ||
value_id == CSSValueAuto;
case CSSPropertyOverflowWrap:
case CSSPropertyWordWrap:
return value_id == CSSValueNormal || value_id == CSSValueBreakWord;
case CSSPropertyOverflowX:
return value_id == CSSValueVisible || value_id == CSSValueHidden ||
value_id == CSSValueScroll || value_id == CSSValueAuto ||
value_id == CSSValueOverlay;
case CSSPropertyOverflowY:
return value_id == CSSValueVisible || value_id == CSSValueHidden ||
value_id == CSSValueScroll || value_id == CSSValueAuto ||
value_id == CSSValueOverlay || value_id == CSSValueWebkitPagedX ||
value_id == CSSValueWebkitPagedY;
case CSSPropertyBreakAfter:
case CSSPropertyBreakBefore:
return value_id == CSSValueAuto || value_id == CSSValueAvoid ||
value_id == CSSValueAvoidPage || value_id == CSSValuePage ||
value_id == CSSValueLeft || value_id == CSSValueRight ||
value_id == CSSValueRecto || value_id == CSSValueVerso ||
value_id == CSSValueAvoidColumn || value_id == CSSValueColumn;
case CSSPropertyBreakInside:
return value_id == CSSValueAuto || value_id == CSSValueAvoid ||
value_id == CSSValueAvoidPage || value_id == CSSValueAvoidColumn;
case CSSPropertyPointerEvents:
return value_id == CSSValueVisible || value_id == CSSValueNone ||
value_id == CSSValueAll || value_id == CSSValueAuto ||
(value_id >= CSSValueVisiblePainted &&
value_id <= CSSValueBoundingBox);
case CSSPropertyPosition:
return value_id == CSSValueStatic || value_id == CSSValueRelative ||
value_id == CSSValueAbsolute || value_id == CSSValueFixed ||
value_id == CSSValueSticky;
case CSSPropertyResize:
return value_id == CSSValueNone || value_id == CSSValueBoth ||
value_id == CSSValueHorizontal || value_id == CSSValueVertical ||
value_id == CSSValueAuto;
case CSSPropertyScrollBehavior:
DCHECK(RuntimeEnabledFeatures::CSSOMSmoothScrollEnabled());
return value_id == CSSValueAuto || value_id == CSSValueSmooth;
case CSSPropertyShapeRendering:
return value_id == CSSValueAuto || value_id == CSSValueOptimizeSpeed ||
value_id == CSSValueCrispEdges ||
value_id == CSSValueGeometricPrecision;
case CSSPropertySpeak:
return value_id == CSSValueNone || value_id == CSSValueNormal ||
value_id == CSSValueSpellOut || value_id == CSSValueDigits ||
value_id == CSSValueLiteralPunctuation ||
value_id == CSSValueNoPunctuation;
case CSSPropertyStrokeLinejoin:
return value_id == CSSValueMiter || value_id == CSSValueRound ||
value_id == CSSValueBevel;
case CSSPropertyStrokeLinecap:
return value_id == CSSValueButt || value_id == CSSValueRound ||
value_id == CSSValueSquare;
case CSSPropertyTableLayout:
return value_id == CSSValueAuto || value_id == CSSValueFixed;
case CSSPropertyTextAlign:
return (value_id >= CSSValueWebkitAuto &&
value_id <= CSSValueInternalCenter) ||
value_id == CSSValueStart || value_id == CSSValueEnd;
case CSSPropertyTextAlignLast:
return (value_id >= CSSValueLeft && value_id <= CSSValueJustify) ||
value_id == CSSValueStart || value_id == CSSValueEnd ||
value_id == CSSValueAuto;
case CSSPropertyTextAnchor:
return value_id == CSSValueStart || value_id == CSSValueMiddle ||
value_id == CSSValueEnd;
case CSSPropertyTextCombineUpright:
return value_id == CSSValueNone || value_id == CSSValueAll;
case CSSPropertyTextDecorationStyle:
return value_id == CSSValueSolid || value_id == CSSValueDouble ||
value_id == CSSValueDotted || value_id == CSSValueDashed ||
value_id == CSSValueWavy;
case CSSPropertyTextDecorationSkipInk:
return value_id == CSSValueAuto || value_id == CSSValueNone;
case CSSPropertyTextJustify:
DCHECK(RuntimeEnabledFeatures::CSS3TextEnabled());
return value_id == CSSValueInterWord || value_id == CSSValueDistribute ||
value_id == CSSValueAuto || value_id == CSSValueNone;
case CSSPropertyTextOrientation:
return value_id == CSSValueMixed || value_id == CSSValueUpright ||
value_id == CSSValueSideways || value_id == CSSValueSidewaysRight;
case CSSPropertyWebkitTextOrientation:
return value_id == CSSValueSideways ||
value_id == CSSValueSidewaysRight ||
value_id == CSSValueVerticalRight || value_id == CSSValueUpright;
case CSSPropertyTextOverflow:
return value_id == CSSValueClip || value_id == CSSValueEllipsis;
case CSSPropertyTextRendering:
return value_id == CSSValueAuto || value_id == CSSValueOptimizeSpeed ||
value_id == CSSValueOptimizeLegibility ||
value_id == CSSValueGeometricPrecision;
case CSSPropertyTextTransform: // capitalize | uppercase | lowercase | none
return (value_id >= CSSValueCapitalize &&
value_id <= CSSValueLowercase) ||
value_id == CSSValueNone;
case CSSPropertyUnicodeBidi:
return value_id == CSSValueNormal || value_id == CSSValueEmbed ||
value_id == CSSValueBidiOverride ||
value_id == CSSValueWebkitIsolate ||
value_id == CSSValueWebkitIsolateOverride ||
value_id == CSSValueWebkitPlaintext ||
value_id == CSSValueIsolate ||
value_id == CSSValueIsolateOverride ||
value_id == CSSValuePlaintext;
case CSSPropertyVectorEffect:
return value_id == CSSValueNone || value_id == CSSValueNonScalingStroke;
case CSSPropertyVisibility:
return value_id == CSSValueVisible || value_id == CSSValueHidden ||
value_id == CSSValueCollapse;
case CSSPropertyWebkitAppRegion:
return value_id >= CSSValueDrag && value_id <= CSSValueNoDrag;
case CSSPropertyWebkitAppearance:
return (value_id >= CSSValueCheckbox && value_id <= CSSValueTextarea) ||
value_id == CSSValueNone;
case CSSPropertyBackfaceVisibility:
return value_id == CSSValueVisible || value_id == CSSValueHidden;
case CSSPropertyMixBlendMode:
return value_id == CSSValueNormal || value_id == CSSValueMultiply ||
value_id == CSSValueScreen || value_id == CSSValueOverlay ||
value_id == CSSValueDarken || value_id == CSSValueLighten ||
value_id == CSSValueColorDodge || value_id == CSSValueColorBurn ||
value_id == CSSValueHardLight || value_id == CSSValueSoftLight ||
value_id == CSSValueDifference || value_id == CSSValueExclusion ||
value_id == CSSValueHue || value_id == CSSValueSaturation ||
value_id == CSSValueColor || value_id == CSSValueLuminosity;
case CSSPropertyWebkitBoxAlign:
return value_id == CSSValueStretch || value_id == CSSValueStart ||
value_id == CSSValueEnd || value_id == CSSValueCenter ||
value_id == CSSValueBaseline;
case CSSPropertyWebkitBoxDecorationBreak:
return value_id == CSSValueClone || value_id == CSSValueSlice;
case CSSPropertyWebkitBoxDirection:
return value_id == CSSValueNormal || value_id == CSSValueReverse;
case CSSPropertyWebkitBoxLines:
return value_id == CSSValueSingle || value_id == CSSValueMultiple;
case CSSPropertyWebkitBoxOrient:
return value_id == CSSValueHorizontal || value_id == CSSValueVertical ||
value_id == CSSValueInlineAxis || value_id == CSSValueBlockAxis;
case CSSPropertyWebkitBoxPack:
return value_id == CSSValueStart || value_id == CSSValueEnd ||
value_id == CSSValueCenter || value_id == CSSValueJustify;
case CSSPropertyColumnFill:
return value_id == CSSValueAuto || value_id == CSSValueBalance;
case CSSPropertyAlignContent:
// FIXME: Per CSS alignment, this property should accept an optional
// <overflow-position>. We should share this parsing code with
// 'justify-self'.
return value_id == CSSValueFlexStart || value_id == CSSValueFlexEnd ||
value_id == CSSValueCenter || value_id == CSSValueSpaceBetween ||
value_id == CSSValueSpaceAround || value_id == CSSValueStretch;
case CSSPropertyAlignItems:
// FIXME: Per CSS alignment, this property should accept the same
// arguments as 'justify-self' so we should share its parsing code.
return value_id == CSSValueFlexStart || value_id == CSSValueFlexEnd ||
value_id == CSSValueCenter || value_id == CSSValueBaseline ||
value_id == CSSValueStretch;
case CSSPropertyAlignSelf:
// FIXME: Per CSS alignment, this property should accept the same
// arguments as 'justify-self' so we should share its parsing code.
return value_id == CSSValueAuto || value_id == CSSValueFlexStart ||
value_id == CSSValueFlexEnd || value_id == CSSValueCenter ||
value_id == CSSValueBaseline || value_id == CSSValueStretch;
case CSSPropertyFlexDirection:
return value_id == CSSValueRow || value_id == CSSValueRowReverse ||
value_id == CSSValueColumn || value_id == CSSValueColumnReverse;
case CSSPropertyFlexWrap:
return value_id == CSSValueNowrap || value_id == CSSValueWrap ||
value_id == CSSValueWrapReverse;
case CSSPropertyHyphens:
#if defined(OS_ANDROID) || defined(OS_MACOSX)
return value_id == CSSValueAuto || value_id == CSSValueNone ||
value_id == CSSValueManual;
#else
return value_id == CSSValueNone || value_id == CSSValueManual;
#endif
case CSSPropertyJustifyContent:
// FIXME: Per CSS alignment, this property should accept an optional
// <overflow-position>. We should share this parsing code with
// 'justify-self'.
return value_id == CSSValueFlexStart || value_id == CSSValueFlexEnd ||
value_id == CSSValueCenter || value_id == CSSValueSpaceBetween ||
value_id == CSSValueSpaceAround;
case CSSPropertyFontKerning:
return value_id == CSSValueAuto || value_id == CSSValueNormal ||
value_id == CSSValueNone;
case CSSPropertyWebkitFontSmoothing:
return value_id == CSSValueAuto || value_id == CSSValueNone ||
value_id == CSSValueAntialiased ||
value_id == CSSValueSubpixelAntialiased;
case CSSPropertyLineBreak:
return value_id == CSSValueAuto || value_id == CSSValueLoose ||
value_id == CSSValueNormal || value_id == CSSValueStrict;
case CSSPropertyWebkitLineBreak:
return value_id == CSSValueAuto || value_id == CSSValueLoose ||
value_id == CSSValueNormal || value_id == CSSValueStrict ||
value_id == CSSValueAfterWhiteSpace;
case CSSPropertyWebkitMarginAfterCollapse:
case CSSPropertyWebkitMarginBeforeCollapse:
case CSSPropertyWebkitMarginBottomCollapse:
case CSSPropertyWebkitMarginTopCollapse:
return value_id == CSSValueCollapse || value_id == CSSValueSeparate ||
value_id == CSSValueDiscard;
case CSSPropertyWebkitPrintColorAdjust:
return value_id == CSSValueExact || value_id == CSSValueEconomy;
case CSSPropertyWebkitRtlOrdering:
return value_id == CSSValueLogical || value_id == CSSValueVisual;
case CSSPropertyWebkitRubyPosition:
return value_id == CSSValueBefore || value_id == CSSValueAfter;
case CSSPropertyWebkitTextCombine:
return value_id == CSSValueNone || value_id == CSSValueHorizontal;
case CSSPropertyWebkitTextSecurity:
return value_id == CSSValueDisc || value_id == CSSValueCircle ||
value_id == CSSValueSquare || value_id == CSSValueNone;
case CSSPropertyTransformBox:
return value_id == CSSValueBorderBox || value_id == CSSValueFillBox ||
value_id == CSSValueViewBox;
case CSSPropertyTransformStyle:
return value_id == CSSValueFlat || value_id == CSSValuePreserve3d;
case CSSPropertyWebkitUserDrag:
return value_id == CSSValueAuto || value_id == CSSValueNone ||
value_id == CSSValueElement;
case CSSPropertyWebkitUserModify:
return value_id == CSSValueReadOnly || value_id == CSSValueReadWrite ||
value_id == CSSValueReadWritePlaintextOnly;
case CSSPropertyUserSelect:
return value_id == CSSValueAuto || value_id == CSSValueNone ||
value_id == CSSValueText || value_id == CSSValueAll;
case CSSPropertyWebkitWritingMode:
return value_id >= CSSValueHorizontalTb && value_id <= CSSValueVerticalLr;
case CSSPropertyWritingMode:
return value_id == CSSValueHorizontalTb ||
value_id == CSSValueVerticalRl || value_id == CSSValueVerticalLr ||
value_id == CSSValueLrTb || value_id == CSSValueRlTb ||
value_id == CSSValueTbRl || value_id == CSSValueLr ||
value_id == CSSValueRl || value_id == CSSValueTb;
case CSSPropertyWhiteSpace:
return value_id == CSSValueNormal || value_id == CSSValuePre ||
value_id == CSSValuePreWrap || value_id == CSSValuePreLine ||
value_id == CSSValueNowrap;
case CSSPropertyWordBreak:
return value_id == CSSValueNormal || value_id == CSSValueBreakAll ||
value_id == CSSValueKeepAll || value_id == CSSValueBreakWord;
case CSSPropertyScrollSnapStop:
DCHECK(RuntimeEnabledFeatures::CSSScrollSnapPointsEnabled());
return value_id == CSSValueNormal || value_id == CSSValueAlways;
case CSSPropertyOverscrollBehaviorX:
return value_id == CSSValueAuto || value_id == CSSValueContain ||
value_id == CSSValueNone;
case CSSPropertyOverscrollBehaviorY:
return value_id == CSSValueAuto || value_id == CSSValueContain ||
value_id == CSSValueNone;
default:
NOTREACHED();
return false;
}
}
bool CSSParserFastPaths::IsKeywordPropertyID(CSSPropertyID property_id) {
switch (property_id) {
case CSSPropertyAlignmentBaseline:
case CSSPropertyAll:
case CSSPropertyMixBlendMode:
case CSSPropertyIsolation:
case CSSPropertyBackgroundRepeatX:
case CSSPropertyBackgroundRepeatY:
case CSSPropertyBorderBottomStyle:
case CSSPropertyBorderCollapse:
case CSSPropertyBorderLeftStyle:
case CSSPropertyBorderRightStyle:
case CSSPropertyBorderTopStyle:
case CSSPropertyBoxSizing:
case CSSPropertyBufferedRendering:
case CSSPropertyCaptionSide:
case CSSPropertyClear:
case CSSPropertyClipRule:
case CSSPropertyColorInterpolation:
case CSSPropertyColorInterpolationFilters:
case CSSPropertyColorRendering:
case CSSPropertyDirection:
case CSSPropertyDisplay:
case CSSPropertyDominantBaseline:
case CSSPropertyEmptyCells:
case CSSPropertyFillRule:
case CSSPropertyFloat:
case CSSPropertyHyphens:
case CSSPropertyImageRendering:
case CSSPropertyListStylePosition:
case CSSPropertyListStyleType:
case CSSPropertyMaskType:
case CSSPropertyObjectFit:
case CSSPropertyOutlineStyle:
case CSSPropertyOverflowAnchor:
case CSSPropertyOverflowWrap:
case CSSPropertyOverflowX:
case CSSPropertyOverflowY:
case CSSPropertyBreakAfter:
case CSSPropertyBreakBefore:
case CSSPropertyBreakInside:
case CSSPropertyPointerEvents:
case CSSPropertyPosition:
case CSSPropertyResize:
case CSSPropertyScrollBehavior:
case CSSPropertyOverscrollBehaviorX:
case CSSPropertyOverscrollBehaviorY:
case CSSPropertyShapeRendering:
case CSSPropertySpeak:
case CSSPropertyStrokeLinecap:
case CSSPropertyStrokeLinejoin:
case CSSPropertyTableLayout:
case CSSPropertyTextAlign:
case CSSPropertyTextAlignLast:
case CSSPropertyTextAnchor:
case CSSPropertyTextCombineUpright:
case CSSPropertyTextDecorationStyle:
case CSSPropertyTextDecorationSkipInk:
case CSSPropertyTextJustify:
case CSSPropertyTextOrientation:
case CSSPropertyWebkitTextOrientation:
case CSSPropertyTextOverflow:
case CSSPropertyTextRendering:
case CSSPropertyTextTransform:
case CSSPropertyUnicodeBidi:
case CSSPropertyVectorEffect:
case CSSPropertyVisibility:
case CSSPropertyWebkitAppRegion:
case CSSPropertyWebkitAppearance:
case CSSPropertyBackfaceVisibility:
case CSSPropertyWebkitBorderAfterStyle:
case CSSPropertyWebkitBorderBeforeStyle:
case CSSPropertyWebkitBorderEndStyle:
case CSSPropertyWebkitBorderStartStyle:
case CSSPropertyWebkitBoxAlign:
case CSSPropertyWebkitBoxDecorationBreak:
case CSSPropertyWebkitBoxDirection:
case CSSPropertyWebkitBoxLines:
case CSSPropertyWebkitBoxOrient:
case CSSPropertyWebkitBoxPack:
case CSSPropertyColumnFill:
case CSSPropertyColumnRuleStyle:
case CSSPropertyFlexDirection:
case CSSPropertyFlexWrap:
case CSSPropertyFontKerning:
case CSSPropertyWebkitFontSmoothing:
case CSSPropertyLineBreak:
case CSSPropertyWebkitLineBreak:
case CSSPropertyWebkitMarginAfterCollapse:
case CSSPropertyWebkitMarginBeforeCollapse:
case CSSPropertyWebkitMarginBottomCollapse:
case CSSPropertyWebkitMarginTopCollapse:
case CSSPropertyWebkitPrintColorAdjust:
case CSSPropertyWebkitRtlOrdering:
case CSSPropertyWebkitRubyPosition:
case CSSPropertyWebkitTextCombine:
case CSSPropertyWebkitTextSecurity:
case CSSPropertyTransformBox:
case CSSPropertyTransformStyle:
case CSSPropertyWebkitUserDrag:
case CSSPropertyWebkitUserModify:
case CSSPropertyUserSelect:
case CSSPropertyWebkitWritingMode:
case CSSPropertyWhiteSpace:
case CSSPropertyWordBreak:
case CSSPropertyWordWrap:
case CSSPropertyWritingMode:
case CSSPropertyScrollSnapStop:
return true;
default:
return false;
}
}
bool CSSParserFastPaths::IsPartialKeywordPropertyID(CSSPropertyID property_id) {
switch (property_id) {
case CSSPropertyDisplay:
return true;
default:
return false;
}
}
static CSSValue* ParseKeywordValue(CSSPropertyID property_id,
const String& string,
CSSParserMode parser_mode) {
DCHECK(!string.IsEmpty());
if (!CSSParserFastPaths::IsKeywordPropertyID(property_id)) {
// All properties accept the values of "initial," "inherit" and "unset".
if (!EqualIgnoringASCIICase(string, "initial") &&
!EqualIgnoringASCIICase(string, "inherit") &&
!EqualIgnoringASCIICase(string, "unset"))
return nullptr;
// Parse initial/inherit/unset shorthands using the CSSPropertyParser.
if (shorthandForProperty(property_id).length())
return nullptr;
// Descriptors do not support css wide keywords.
if (!CSSProperty::Get(property_id).IsProperty())
return nullptr;
}
CSSValueID value_id = CssValueKeywordID(string);
if (!value_id)
return nullptr;
if (value_id == CSSValueInherit)
return CSSInheritedValue::Create();
if (value_id == CSSValueInitial)
return CSSInitialValue::Create();
if (value_id == CSSValueUnset)
return CSSUnsetValue::Create();
if (CSSParserFastPaths::IsValidKeywordPropertyAndValue(property_id, value_id,
parser_mode))
return CSSIdentifierValue::Create(value_id);
return nullptr;
}
template <typename CharType>
static bool ParseTransformTranslateArguments(
CharType*& pos,
CharType* end,
unsigned expected_count,
CSSFunctionValue* transform_value) {
while (expected_count) {
size_t delimiter =
WTF::Find(pos, end - pos, expected_count == 1 ? ')' : ',');
if (delimiter == kNotFound)
return false;
unsigned argument_length = static_cast<unsigned>(delimiter);
CSSPrimitiveValue::UnitType unit = CSSPrimitiveValue::UnitType::kNumber;
double number;
if (!ParseSimpleLength(pos, argument_length, unit, number))
return false;
if (unit != CSSPrimitiveValue::UnitType::kPixels &&
(number || unit != CSSPrimitiveValue::UnitType::kNumber))
return false;
transform_value->Append(*CSSPrimitiveValue::Create(
number, CSSPrimitiveValue::UnitType::kPixels));
pos += argument_length + 1;
--expected_count;
}
return true;
}
template <typename CharType>
static bool ParseTransformNumberArguments(CharType*& pos,
CharType* end,
unsigned expected_count,
CSSFunctionValue* transform_value) {
while (expected_count) {
size_t delimiter =
WTF::Find(pos, end - pos, expected_count == 1 ? ')' : ',');
if (delimiter == kNotFound)
return false;
unsigned argument_length = static_cast<unsigned>(delimiter);
bool ok;
double number = CharactersToDouble(pos, argument_length, &ok);
if (!ok)
return false;
transform_value->Append(*CSSPrimitiveValue::Create(
number, CSSPrimitiveValue::UnitType::kNumber));
pos += argument_length + 1;
--expected_count;
}
return true;
}
static const int kShortestValidTransformStringLength = 12;
template <typename CharType>
static CSSFunctionValue* ParseSimpleTransformValue(CharType*& pos,
CharType* end) {
if (end - pos < kShortestValidTransformStringLength)
return nullptr;
const bool is_translate =
ToASCIILower(pos[0]) == 't' && ToASCIILower(pos[1]) == 'r' &&
ToASCIILower(pos[2]) == 'a' && ToASCIILower(pos[3]) == 'n' &&
ToASCIILower(pos[4]) == 's' && ToASCIILower(pos[5]) == 'l' &&
ToASCIILower(pos[6]) == 'a' && ToASCIILower(pos[7]) == 't' &&
ToASCIILower(pos[8]) == 'e';
if (is_translate) {
CSSValueID transform_type;
unsigned expected_argument_count = 1;
unsigned argument_start = 11;
CharType c9 = ToASCIILower(pos[9]);
if (c9 == 'x' && pos[10] == '(') {
transform_type = CSSValueTranslateX;
} else if (c9 == 'y' && pos[10] == '(') {
transform_type = CSSValueTranslateY;
} else if (c9 == 'z' && pos[10] == '(') {
transform_type = CSSValueTranslateZ;
} else if (c9 == '(') {
transform_type = CSSValueTranslate;
expected_argument_count = 2;
argument_start = 10;
} else if (c9 == '3' && ToASCIILower(pos[10]) == 'd' && pos[11] == '(') {
transform_type = CSSValueTranslate3d;
expected_argument_count = 3;
argument_start = 12;
} else {
return nullptr;
}
pos += argument_start;
CSSFunctionValue* transform_value =
CSSFunctionValue::Create(transform_type);
if (!ParseTransformTranslateArguments(pos, end, expected_argument_count,
transform_value))
return nullptr;
return transform_value;
}
const bool is_matrix3d =
ToASCIILower(pos[0]) == 'm' && ToASCIILower(pos[1]) == 'a' &&
ToASCIILower(pos[2]) == 't' && ToASCIILower(pos[3]) == 'r' &&
ToASCIILower(pos[4]) == 'i' && ToASCIILower(pos[5]) == 'x' &&
pos[6] == '3' && ToASCIILower(pos[7]) == 'd' && pos[8] == '(';
if (is_matrix3d) {
pos += 9;
CSSFunctionValue* transform_value =
CSSFunctionValue::Create(CSSValueMatrix3d);
if (!ParseTransformNumberArguments(pos, end, 16, transform_value))
return nullptr;
return transform_value;
}
const bool is_scale3d =
ToASCIILower(pos[0]) == 's' && ToASCIILower(pos[1]) == 'c' &&
ToASCIILower(pos[2]) == 'a' && ToASCIILower(pos[3]) == 'l' &&
ToASCIILower(pos[4]) == 'e' && pos[5] == '3' &&
ToASCIILower(pos[6]) == 'd' && pos[7] == '(';
if (is_scale3d) {
pos += 8;
CSSFunctionValue* transform_value =
CSSFunctionValue::Create(CSSValueScale3d);
if (!ParseTransformNumberArguments(pos, end, 3, transform_value))
return nullptr;
return transform_value;
}
return nullptr;
}
template <typename CharType>
static bool TransformCanLikelyUseFastPath(const CharType* chars,
unsigned length) {
// Very fast scan that attempts to reject most transforms that couldn't
// take the fast path. This avoids doing the malloc and string->double
// conversions in parseSimpleTransformValue only to discard them when we
// run into a transform component we don't understand.
unsigned i = 0;
while (i < length) {
if (IsCSSSpace(chars[i])) {
++i;
continue;
}
if (length - i < kShortestValidTransformStringLength)
return false;
switch (ToASCIILower(chars[i])) {
case 't':
// translate, translateX, translateY, translateZ, translate3d.
if (ToASCIILower(chars[i + 8]) != 'e')
return false;
i += 9;
break;
case 'm':
// matrix3d.
if (ToASCIILower(chars[i + 7]) != 'd')
return false;
i += 8;
break;
case 's':
// scale3d.
if (ToASCIILower(chars[i + 6]) != 'd')
return false;
i += 7;
break;
default:
// All other things, ex. rotate.
return false;
}
size_t arguments_end = WTF::Find(chars, length, ')', i);
if (arguments_end == kNotFound)
return false;
// Advance to the end of the arguments.
i = arguments_end + 1;
}
return i == length;
}
template <typename CharType>
static CSSValueList* ParseSimpleTransformList(const CharType* chars,
unsigned length) {
if (!TransformCanLikelyUseFastPath(chars, length))
return nullptr;
const CharType*& pos = chars;
const CharType* end = chars + length;
CSSValueList* transform_list = nullptr;
while (pos < end) {
while (pos < end && IsCSSSpace(*pos))
++pos;
if (pos >= end)
break;
CSSFunctionValue* transform_value = ParseSimpleTransformValue(pos, end);
if (!transform_value)
return nullptr;
if (!transform_list)
transform_list = CSSValueList::CreateSpaceSeparated();
transform_list->Append(*transform_value);
}
return transform_list;
}
static CSSValue* ParseSimpleTransform(CSSPropertyID property_id,
const String& string) {
DCHECK(!string.IsEmpty());
if (property_id != CSSPropertyTransform)
return nullptr;
if (string.Is8Bit())
return ParseSimpleTransformList(string.Characters8(), string.length());
return ParseSimpleTransformList(string.Characters16(), string.length());
}
CSSValue* CSSParserFastPaths::MaybeParseValue(CSSPropertyID property_id,
const String& string,
CSSParserMode parser_mode) {
if (CSSValue* length =
ParseSimpleLengthValue(property_id, string, parser_mode))
return length;
if (IsColorPropertyID(property_id))
return ParseColor(string, parser_mode);
if (CSSValue* keyword = ParseKeywordValue(property_id, string, parser_mode))
return keyword;
if (CSSValue* transform = ParseSimpleTransform(property_id, string))
return transform;
return nullptr;
}
} // namespace blink