blob: 583f9a8ae8a311e5725be4a1942b04a66108d346 [file] [log] [blame]
// Copyright 2016 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/cssom/style_property_map.h"
#include "third_party/blink/renderer/core/css/css_identifier_value.h"
#include "third_party/blink/renderer/core/css/css_property_name.h"
#include "third_party/blink/renderer/core/css/css_value_list.h"
#include "third_party/blink/renderer/core/css/css_value_pair.h"
#include "third_party/blink/renderer/core/css/cssom/css_style_value.h"
#include "third_party/blink/renderer/core/css/cssom/cssom_types.h"
#include "third_party/blink/renderer/core/css/cssom/style_value_factory.h"
#include "third_party/blink/renderer/core/css/parser/css_parser.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_context.h"
#include "third_party/blink/renderer/core/css/parser/css_tokenizer.h"
#include "third_party/blink/renderer/core/css/properties/css_property.h"
#include "third_party/blink/renderer/core/style_property_shorthand.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
namespace blink {
namespace {
CSSValueList* CssValueListForPropertyID(CSSPropertyID property_id) {
DCHECK(CSSProperty::Get(property_id).IsRepeated());
char separator = CSSProperty::Get(property_id).RepetitionSeparator();
switch (separator) {
case ' ':
return CSSValueList::CreateSpaceSeparated();
case ',':
return CSSValueList::CreateCommaSeparated();
case '/':
return CSSValueList::CreateSlashSeparated();
default:
NOTREACHED();
return nullptr;
}
}
const CSSValue* StyleValueToCSSValue(
const CSSProperty& property,
const AtomicString& custom_property_name,
const CSSStyleValue& style_value,
const ExecutionContext& execution_context) {
DCHECK_EQ(property.IDEquals(CSSPropertyID::kVariable),
!custom_property_name.IsNull());
const CSSPropertyID property_id = property.PropertyID();
if (!CSSOMTypes::PropertyCanTake(property_id, custom_property_name,
style_value)) {
return nullptr;
}
if (style_value.GetType() == CSSStyleValue::kUnknownType) {
return CSSParser::ParseSingleValue(
property.PropertyID(), style_value.toString(),
MakeGarbageCollected<CSSParserContext>(execution_context));
}
// Handle properties that use ad-hoc structures for their CSSValues:
// TODO(https://crbug.com/545324): Move this into a method on
// CSSProperty when there are more of these cases.
switch (property_id) {
case CSSPropertyID::kBorderBottomLeftRadius:
case CSSPropertyID::kBorderBottomRightRadius:
case CSSPropertyID::kBorderTopLeftRadius:
case CSSPropertyID::kBorderTopRightRadius:
case CSSPropertyID::kBorderEndEndRadius:
case CSSPropertyID::kBorderEndStartRadius:
case CSSPropertyID::kBorderStartEndRadius:
case CSSPropertyID::kBorderStartStartRadius: {
// level 1 only accept single <length-percentages>, but border-radius-*
// expects pairs.
const auto* value = style_value.ToCSSValue();
if (value->IsPrimitiveValue()) {
return MakeGarbageCollected<CSSValuePair>(
value, value, CSSValuePair::kDropIdenticalValues);
}
break;
}
case CSSPropertyID::kContain: {
// level 1 only accepts single values, which are stored internally
// as a single element list.
const auto* value = style_value.ToCSSValue();
if ((value->IsIdentifierValue() && !value->IsCSSWideKeyword()) ||
value->IsPrimitiveValue()) {
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
list->Append(*style_value.ToCSSValue());
return list;
}
break;
}
case CSSPropertyID::kFontVariantEastAsian:
case CSSPropertyID::kFontVariantLigatures:
case CSSPropertyID::kFontVariantNumeric: {
// level 1 only accept single keywords, but font-variant-* store
// them as a list
if (const auto* value =
DynamicTo<CSSIdentifierValue>(style_value.ToCSSValue())) {
// 'none' and 'normal' are stored as a single value
if (value->GetValueID() == CSSValueID::kNone ||
value->GetValueID() == CSSValueID::kNormal) {
break;
}
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
list->Append(*style_value.ToCSSValue());
return list;
}
break;
}
case CSSPropertyID::kGridAutoFlow: {
// level 1 only accepts single keywords
const auto* value = style_value.ToCSSValue();
// single keywords are wrapped in a list.
if (value->IsIdentifierValue() && !value->IsCSSWideKeyword()) {
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
list->Append(*style_value.ToCSSValue());
return list;
}
break;
}
case CSSPropertyID::kOffsetRotate: {
// level 1 only accepts single values, which are stored internally
// as a single element list.
const auto* value = style_value.ToCSSValue();
if ((value->IsIdentifierValue() && !value->IsCSSWideKeyword()) ||
value->IsPrimitiveValue()) {
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
list->Append(*style_value.ToCSSValue());
return list;
}
break;
}
case CSSPropertyID::kOverflowX:
case CSSPropertyID::kOverflowY: {
if (!RuntimeEnabledFeatures::OverflowClipEnabled()) {
auto* identifier_value =
DynamicTo<CSSIdentifierValue>(style_value.ToCSSValue());
if (identifier_value &&
identifier_value->GetValueID() == CSSValueID::kClip) {
return nullptr;
}
}
break;
}
case CSSPropertyID::kPaintOrder: {
// level 1 only accepts single keywords
const auto* value = style_value.ToCSSValue();
// only 'normal' is stored as an identifier, the other keywords are
// wrapped in a list.
auto* identifier_value = DynamicTo<CSSIdentifierValue>(value);
if (identifier_value && !value->IsCSSWideKeyword() &&
identifier_value->GetValueID() != CSSValueID::kNormal) {
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
list->Append(*style_value.ToCSSValue());
return list;
}
break;
}
case CSSPropertyID::kTextDecorationLine: {
// level 1 only accepts single keywords
const auto* value = style_value.ToCSSValue();
// only 'none' is stored as an identifier, the other keywords are
// wrapped in a list.
auto* identifier_value = DynamicTo<CSSIdentifierValue>(value);
if (identifier_value && !value->IsCSSWideKeyword() &&
identifier_value->GetValueID() != CSSValueID::kNone) {
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
list->Append(*style_value.ToCSSValue());
return list;
}
break;
}
case CSSPropertyID::kTextIndent: {
// level 1 only accepts single values, which are stored internally
// as a single element list.
const auto* value = style_value.ToCSSValue();
if (value->IsPrimitiveValue()) {
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
list->Append(*value);
return list;
}
break;
}
case CSSPropertyID::kTransitionProperty:
case CSSPropertyID::kTouchAction: {
// level 1 only accepts single keywords, which are stored internally
// as a single element list
const auto* value = style_value.ToCSSValue();
if (value->IsIdentifierValue() && !value->IsCSSWideKeyword()) {
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
list->Append(*style_value.ToCSSValue());
return list;
}
break;
}
default:
break;
}
return style_value.ToCSSValueWithProperty(property_id);
}
const CSSValue* CoerceStyleValueOrString(
const CSSProperty& property,
const AtomicString& custom_property_name,
const CSSStyleValueOrString& value,
const ExecutionContext& execution_context) {
DCHECK(!property.IsRepeated());
DCHECK_EQ(property.IDEquals(CSSPropertyID::kVariable),
!custom_property_name.IsNull());
if (value.IsCSSStyleValue()) {
if (!value.GetAsCSSStyleValue())
return nullptr;
return StyleValueToCSSValue(property, custom_property_name,
*value.GetAsCSSStyleValue(), execution_context);
} else {
DCHECK(value.IsString());
const auto values = StyleValueFactory::FromString(
property.PropertyID(), custom_property_name, value.GetAsString(),
MakeGarbageCollected<CSSParserContext>(execution_context));
if (values.size() != 1U)
return nullptr;
return StyleValueToCSSValue(property, custom_property_name, *values[0],
execution_context);
}
}
const CSSValue* CoerceStyleValuesOrStrings(
const CSSProperty& property,
const AtomicString& custom_property_name,
const HeapVector<CSSStyleValueOrString>& values,
const ExecutionContext& execution_context) {
DCHECK(property.IsRepeated());
DCHECK_EQ(property.IDEquals(CSSPropertyID::kVariable),
!custom_property_name.IsNull());
if (values.IsEmpty())
return nullptr;
CSSStyleValueVector style_values =
StyleValueFactory::CoerceStyleValuesOrStrings(
property, custom_property_name, values, execution_context);
if (style_values.IsEmpty())
return nullptr;
CSSValueList* result = CssValueListForPropertyID(property.PropertyID());
for (const auto& style_value : style_values) {
const CSSValue* css_value = StyleValueToCSSValue(
property, custom_property_name, *style_value, execution_context);
if (!css_value)
return nullptr;
if (css_value->IsCSSWideKeyword() || css_value->IsVariableReferenceValue())
return style_values.size() == 1U ? css_value : nullptr;
result->Append(*css_value);
}
return result;
}
} // namespace
void StylePropertyMap::set(const ExecutionContext* execution_context,
const String& property_name,
const HeapVector<CSSStyleValueOrString>& values,
ExceptionState& exception_state) {
const CSSPropertyID property_id =
CssPropertyID(execution_context, property_name);
if (property_id == CSSPropertyID::kInvalid) {
exception_state.ThrowTypeError("Invalid propertyName: " + property_name);
return;
}
DCHECK(IsValidCSSPropertyID(property_id));
const CSSProperty& property = CSSProperty::Get(property_id);
if (property.IsShorthand()) {
if (values.size() != 1) {
exception_state.ThrowTypeError("Invalid type for property");
return;
}
String css_text;
if (values[0].IsCSSStyleValue()) {
CSSStyleValue* style_value = values[0].GetAsCSSStyleValue();
if (style_value &&
CSSOMTypes::PropertyCanTake(property_id, g_null_atom, *style_value)) {
css_text = style_value->toString();
}
} else {
css_text = values[0].GetAsString();
}
if (css_text.IsEmpty() ||
!SetShorthandProperty(property.PropertyID(), css_text,
execution_context->GetSecureContextMode()))
exception_state.ThrowTypeError("Invalid type for property");
return;
}
AtomicString custom_property_name = (property_id == CSSPropertyID::kVariable)
? AtomicString(property_name)
: g_null_atom;
const CSSValue* result = nullptr;
if (property.IsRepeated()) {
result = CoerceStyleValuesOrStrings(property, custom_property_name, values,
*execution_context);
} else if (values.size() == 1U) {
result = CoerceStyleValueOrString(property, custom_property_name, values[0],
*execution_context);
}
if (!result) {
exception_state.ThrowTypeError("Invalid type for property");
return;
}
if (property_id == CSSPropertyID::kVariable)
SetCustomProperty(custom_property_name, *result);
else
SetProperty(property_id, *result);
}
void StylePropertyMap::append(const ExecutionContext* execution_context,
const String& property_name,
const HeapVector<CSSStyleValueOrString>& values,
ExceptionState& exception_state) {
if (values.IsEmpty())
return;
const CSSPropertyID property_id =
CssPropertyID(execution_context, property_name);
if (property_id == CSSPropertyID::kInvalid) {
exception_state.ThrowTypeError("Invalid propertyName: " + property_name);
return;
}
const CSSProperty& property = CSSProperty::Get(property_id);
if (property_id == CSSPropertyID::kVariable) {
exception_state.ThrowTypeError(
"Appending to custom properties is not supported");
return;
}
if (!property.IsRepeated()) {
exception_state.ThrowTypeError("Property does not support multiple values");
return;
}
CSSValueList* current_value = nullptr;
if (const CSSValue* css_value = GetProperty(property_id)) {
current_value = To<CSSValueList>(css_value)->Copy();
} else {
current_value = CssValueListForPropertyID(property_id);
}
const CSSValue* result = CoerceStyleValuesOrStrings(
property, g_null_atom, values, *execution_context);
const auto* result_value_list = DynamicTo<CSSValueList>(result);
if (!result_value_list) {
exception_state.ThrowTypeError("Invalid type for property");
return;
}
for (const auto& value : *result_value_list) {
current_value->Append(*value);
}
SetProperty(property_id, *current_value);
}
void StylePropertyMap::remove(const ExecutionContext* execution_context,
const String& property_name,
ExceptionState& exception_state) {
CSSPropertyID property_id = CssPropertyID(execution_context, property_name);
if (property_id == CSSPropertyID::kInvalid) {
exception_state.ThrowTypeError("Invalid property name: " + property_name);
return;
}
if (property_id == CSSPropertyID::kVariable) {
RemoveCustomProperty(AtomicString(property_name));
} else {
RemoveProperty(property_id);
}
}
void StylePropertyMap::clear() {
RemoveAllProperties();
}
} // namespace blink