blob: 949dbf7f7bab250152a2addbf7271bdaeb0799fe [file] [log] [blame]
// Copyright 2015 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 "third_party/blink/renderer/core/css/parser/css_property_parser.h"
#include "third_party/blink/renderer/core/css/css_inherited_value.h"
#include "third_party/blink/renderer/core/css/css_initial_value.h"
#include "third_party/blink/renderer/core/css/css_pending_substitution_value.h"
#include "third_party/blink/renderer/core/css/css_unicode_range_value.h"
#include "third_party/blink/renderer/core/css/css_unset_value.h"
#include "third_party/blink/renderer/core/css/css_variable_reference_value.h"
#include "third_party/blink/renderer/core/css/hash_tools.h"
#include "third_party/blink/renderer/core/css/parser/at_rule_descriptor_parser.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_local_context.h"
#include "third_party/blink/renderer/core/css/parser/css_property_parser_helpers.h"
#include "third_party/blink/renderer/core/css/parser/css_variable_parser.h"
#include "third_party/blink/renderer/core/css/properties/css_parsing_utils.h"
#include "third_party/blink/renderer/core/css/properties/css_property.h"
#include "third_party/blink/renderer/core/css/properties/shorthand.h"
#include "third_party/blink/renderer/core/style_property_shorthand.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
namespace blink {
using css_property_parser_helpers::ConsumeIdent;
using css_property_parser_helpers::IsImplicitProperty;
using css_property_parser_helpers::ParseLonghand;
class CSSIdentifierValue;
namespace {
const CSSValue* MaybeConsumeCSSWideKeyword(CSSParserTokenRange& range) {
CSSParserTokenRange local_range = range;
CSSValueID id = local_range.ConsumeIncludingWhitespace().Id();
if (!local_range.AtEnd())
return nullptr;
const CSSValue* value = nullptr;
if (id == CSSValueID::kInitial)
value = CSSInitialValue::Create();
if (id == CSSValueID::kInherit)
value = CSSInheritedValue::Create();
if (id == CSSValueID::kUnset)
value = cssvalue::CSSUnsetValue::Create();
if (value)
range = local_range;
return value;
}
} // namespace
CSSPropertyParser::CSSPropertyParser(
const CSSParserTokenRange& range,
const CSSParserContext* context,
HeapVector<CSSPropertyValue, 256>* parsed_properties)
: range_(range), context_(context), parsed_properties_(parsed_properties) {
range_.ConsumeWhitespace();
}
bool CSSPropertyParser::ParseValue(
CSSPropertyID unresolved_property,
bool important,
const CSSParserTokenRange& range,
const CSSParserContext* context,
HeapVector<CSSPropertyValue, 256>& parsed_properties,
StyleRule::RuleType rule_type) {
int parsed_properties_size = parsed_properties.size();
CSSPropertyParser parser(range, context, &parsed_properties);
CSSPropertyID resolved_property = resolveCSSPropertyID(unresolved_property);
bool parse_success;
if (rule_type == StyleRule::kViewport) {
parse_success =
(RuntimeEnabledFeatures::CSSViewportEnabled() ||
IsUASheetBehavior(context->Mode())) &&
parser.ParseViewportDescriptor(resolved_property, important);
} else if (rule_type == StyleRule::kFontFace) {
parse_success = parser.ParseFontFaceDescriptor(resolved_property);
} else {
parse_success = parser.ParseValueStart(unresolved_property, important);
}
// This doesn't count UA style sheets
if (parse_success)
context->Count(context->Mode(), unresolved_property);
if (!parse_success)
parsed_properties.Shrink(parsed_properties_size);
return parse_success;
}
const CSSValue* CSSPropertyParser::ParseSingleValue(
CSSPropertyID property,
const CSSParserTokenRange& range,
const CSSParserContext* context) {
DCHECK(context);
CSSPropertyParser parser(range, context, nullptr);
if (const CSSValue* value = MaybeConsumeCSSWideKeyword(parser.range_))
return value;
const CSSValue* value = ParseLonghand(property, CSSPropertyID::kInvalid,
*parser.context_, parser.range_);
if (!value || !parser.range_.AtEnd())
return nullptr;
return value;
}
bool CSSPropertyParser::ParseValueStart(CSSPropertyID unresolved_property,
bool important) {
if (ConsumeCSSWideKeyword(unresolved_property, important))
return true;
CSSParserTokenRange original_range = range_;
CSSPropertyID property_id = resolveCSSPropertyID(unresolved_property);
const CSSProperty& property = CSSProperty::Get(property_id);
// If a CSSPropertyID is only a known descriptor (@fontface, @viewport), not a
// style property, it will not be a valid declaration.
if (!property.IsProperty())
return false;
bool is_shorthand = property.IsShorthand();
DCHECK(context_);
if (is_shorthand) {
const auto local_context =
CSSParserLocalContext()
.WithAliasParsing(isPropertyAlias(unresolved_property))
.WithCurrentShorthand(property_id);
// Variable references will fail to parse here and will fall out to the
// variable ref parser below.
if (To<Shorthand>(property).ParseShorthand(
important, range_, *context_, local_context, *parsed_properties_))
return true;
} else {
if (const CSSValue* parsed_value = ParseLonghand(
unresolved_property, CSSPropertyID::kInvalid, *context_, range_)) {
if (range_.AtEnd()) {
AddProperty(property_id, CSSPropertyID::kInvalid, *parsed_value,
important, IsImplicitProperty::kNotImplicit,
*parsed_properties_);
return true;
}
}
}
if (CSSVariableParser::ContainsValidVariableReferences(original_range)) {
bool is_animation_tainted = false;
auto* variable = MakeGarbageCollected<CSSVariableReferenceValue>(
CSSVariableData::Create(original_range, is_animation_tainted, true,
context_->BaseURL(), context_->Charset()),
*context_);
if (is_shorthand) {
const cssvalue::CSSPendingSubstitutionValue& pending_value =
*MakeGarbageCollected<cssvalue::CSSPendingSubstitutionValue>(
property_id, variable);
css_property_parser_helpers::AddExpandedPropertyForValue(
property_id, pending_value, important, *parsed_properties_);
} else {
AddProperty(property_id, CSSPropertyID::kInvalid, *variable, important,
IsImplicitProperty::kNotImplicit, *parsed_properties_);
}
return true;
}
return false;
}
static inline bool IsExposedInMode(const CSSProperty& property,
CSSParserMode mode) {
return mode == kUASheetMode ? property.IsUAExposed()
: property.IsWebExposed();
}
template <typename CharacterType>
static CSSPropertyID UnresolvedCSSPropertyID(const CharacterType* property_name,
unsigned length,
CSSParserMode mode) {
if (length == 0)
return CSSPropertyID::kInvalid;
if (length >= 2 && property_name[0] == '-' && property_name[1] == '-')
return CSSPropertyID::kVariable;
if (length > maxCSSPropertyNameLength)
return CSSPropertyID::kInvalid;
char buffer[maxCSSPropertyNameLength + 1]; // 1 for null character
for (unsigned i = 0; i != length; ++i) {
CharacterType c = property_name[i];
if (c == 0 || c >= 0x7F)
return CSSPropertyID::kInvalid; // illegal character
buffer[i] = ToASCIILower(c);
}
buffer[length] = '\0';
const char* name = buffer;
const Property* hash_table_entry = FindProperty(name, length);
if (!hash_table_entry)
return CSSPropertyID::kInvalid;
CSSPropertyID property_id = static_cast<CSSPropertyID>(hash_table_entry->id);
const CSSProperty& property =
CSSProperty::Get(resolveCSSPropertyID(property_id));
bool exposed = IsExposedInMode(property, mode);
return exposed ? property_id : CSSPropertyID::kInvalid;
}
CSSPropertyID unresolvedCSSPropertyID(const String& string) {
unsigned length = string.length();
CSSParserMode mode = kHTMLStandardMode;
return string.Is8Bit()
? UnresolvedCSSPropertyID(string.Characters8(), length, mode)
: UnresolvedCSSPropertyID(string.Characters16(), length, mode);
}
CSSPropertyID UnresolvedCSSPropertyID(StringView string, CSSParserMode mode) {
unsigned length = string.length();
return string.Is8Bit()
? UnresolvedCSSPropertyID(string.Characters8(), length, mode)
: UnresolvedCSSPropertyID(string.Characters16(), length, mode);
}
template <typename CharacterType>
static CSSValueID CssValueKeywordID(const CharacterType* value_keyword,
unsigned length) {
char buffer[maxCSSValueKeywordLength + 1]; // 1 for null character
for (unsigned i = 0; i != length; ++i) {
CharacterType c = value_keyword[i];
if (c == 0 || c >= 0x7F)
return CSSValueID::kInvalid; // illegal character
buffer[i] = WTF::ToASCIILower(c);
}
buffer[length] = '\0';
const Value* hash_table_entry = FindValue(buffer, length);
return hash_table_entry ? static_cast<CSSValueID>(hash_table_entry->id)
: CSSValueID::kInvalid;
}
CSSValueID CssValueKeywordID(StringView string) {
unsigned length = string.length();
if (!length)
return CSSValueID::kInvalid;
if (length > maxCSSValueKeywordLength)
return CSSValueID::kInvalid;
return string.Is8Bit() ? CssValueKeywordID(string.Characters8(), length)
: CssValueKeywordID(string.Characters16(), length);
}
bool CSSPropertyParser::ConsumeCSSWideKeyword(CSSPropertyID unresolved_property,
bool important) {
CSSParserTokenRange range_copy = range_;
const CSSValue* value = MaybeConsumeCSSWideKeyword(range_copy);
if (!value)
return false;
CSSPropertyID property = resolveCSSPropertyID(unresolved_property);
const StylePropertyShorthand& shorthand = shorthandForProperty(property);
if (!shorthand.length()) {
if (!CSSProperty::Get(property).IsProperty())
return false;
AddProperty(property, CSSPropertyID::kInvalid, *value, important,
IsImplicitProperty::kNotImplicit, *parsed_properties_);
} else {
css_property_parser_helpers::AddExpandedPropertyForValue(
property, *value, important, *parsed_properties_);
}
range_ = range_copy;
return true;
}
static CSSValue* ConsumeSingleViewportDescriptor(
CSSParserTokenRange& range,
CSSPropertyID prop_id,
CSSParserMode css_parser_mode) {
CSSValueID id = range.Peek().Id();
switch (prop_id) {
case CSSPropertyID::kMinWidth:
case CSSPropertyID::kMaxWidth:
case CSSPropertyID::kMinHeight:
case CSSPropertyID::kMaxHeight:
if (id == CSSValueID::kAuto || id == CSSValueID::kInternalExtendToZoom)
return ConsumeIdent(range);
return css_property_parser_helpers::ConsumeLengthOrPercent(
range, css_parser_mode, kValueRangeNonNegative);
case CSSPropertyID::kMinZoom:
case CSSPropertyID::kMaxZoom:
case CSSPropertyID::kZoom: {
if (id == CSSValueID::kAuto)
return ConsumeIdent(range);
CSSValue* parsed_value = css_property_parser_helpers::ConsumeNumber(
range, kValueRangeNonNegative);
if (parsed_value)
return parsed_value;
return css_property_parser_helpers::ConsumePercent(
range, kValueRangeNonNegative);
}
case CSSPropertyID::kUserZoom:
return ConsumeIdent<CSSValueID::kZoom, CSSValueID::kFixed>(range);
case CSSPropertyID::kOrientation:
return ConsumeIdent<CSSValueID::kAuto, CSSValueID::kPortrait,
CSSValueID::kLandscape>(range);
case CSSPropertyID::kViewportFit:
return ConsumeIdent<CSSValueID::kAuto, CSSValueID::kContain,
CSSValueID::kCover>(range);
default:
NOTREACHED();
break;
}
NOTREACHED();
return nullptr;
}
bool CSSPropertyParser::ParseViewportDescriptor(CSSPropertyID prop_id,
bool important) {
DCHECK(RuntimeEnabledFeatures::CSSViewportEnabled() ||
IsUASheetBehavior(context_->Mode()));
switch (prop_id) {
case CSSPropertyID::kWidth: {
CSSValue* min_width = ConsumeSingleViewportDescriptor(
range_, CSSPropertyID::kMinWidth, context_->Mode());
if (!min_width)
return false;
CSSValue* max_width = min_width;
if (!range_.AtEnd()) {
max_width = ConsumeSingleViewportDescriptor(
range_, CSSPropertyID::kMaxWidth, context_->Mode());
}
if (!max_width || !range_.AtEnd())
return false;
AddProperty(CSSPropertyID::kMinWidth, CSSPropertyID::kInvalid, *min_width,
important, IsImplicitProperty::kNotImplicit,
*parsed_properties_);
AddProperty(CSSPropertyID::kMaxWidth, CSSPropertyID::kInvalid, *max_width,
important, IsImplicitProperty::kNotImplicit,
*parsed_properties_);
return true;
}
case CSSPropertyID::kHeight: {
CSSValue* min_height = ConsumeSingleViewportDescriptor(
range_, CSSPropertyID::kMinHeight, context_->Mode());
if (!min_height)
return false;
CSSValue* max_height = min_height;
if (!range_.AtEnd()) {
max_height = ConsumeSingleViewportDescriptor(
range_, CSSPropertyID::kMaxHeight, context_->Mode());
}
if (!max_height || !range_.AtEnd())
return false;
AddProperty(CSSPropertyID::kMinHeight, CSSPropertyID::kInvalid,
*min_height, important, IsImplicitProperty::kNotImplicit,
*parsed_properties_);
AddProperty(CSSPropertyID::kMaxHeight, CSSPropertyID::kInvalid,
*max_height, important, IsImplicitProperty::kNotImplicit,
*parsed_properties_);
return true;
}
case CSSPropertyID::kViewportFit:
case CSSPropertyID::kMinWidth:
case CSSPropertyID::kMaxWidth:
case CSSPropertyID::kMinHeight:
case CSSPropertyID::kMaxHeight:
case CSSPropertyID::kMinZoom:
case CSSPropertyID::kMaxZoom:
case CSSPropertyID::kZoom:
case CSSPropertyID::kUserZoom:
case CSSPropertyID::kOrientation: {
CSSValue* parsed_value =
ConsumeSingleViewportDescriptor(range_, prop_id, context_->Mode());
if (!parsed_value || !range_.AtEnd())
return false;
AddProperty(prop_id, CSSPropertyID::kInvalid, *parsed_value, important,
IsImplicitProperty::kNotImplicit, *parsed_properties_);
return true;
}
default:
return false;
}
}
bool CSSPropertyParser::ParseFontFaceDescriptor(
CSSPropertyID resolved_property) {
// TODO(meade): This function should eventually take an AtRuleDescriptorID.
const AtRuleDescriptorID id =
CSSPropertyIDAsAtRuleDescriptor(resolved_property);
if (id == AtRuleDescriptorID::Invalid)
return false;
CSSValue* parsed_value =
AtRuleDescriptorParser::ParseFontFaceDescriptor(id, range_, *context_);
if (!parsed_value)
return false;
AddProperty(resolved_property,
CSSPropertyID::kInvalid /* current_shorthand */, *parsed_value,
false /* important */, IsImplicitProperty::kNotImplicit,
*parsed_properties_);
return true;
}
} // namespace blink