blob: 38482b44efd34b047d6ba293ce7de59659ef082b [file] [log] [blame]
/*
* (C) 1999-2003 Lars Knoll (knoll@kde.org)
* Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012 Apple Inc. All
* rights reserved.
* Copyright (C) 2011 Research In Motion Limited. All rights reserved.
* Copyright (C) 2013 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 "third_party/blink/renderer/core/css/style_property_serializer.h"
#include <bitset>
#include "base/memory/values_equivalent.h"
#include "third_party/blink/renderer/core/animation/css/css_animation_data.h"
#include "third_party/blink/renderer/core/css/css_grid_template_areas_value.h"
#include "third_party/blink/renderer/core/css/css_identifier_value.h"
#include "third_party/blink/renderer/core/css/css_markup.h"
#include "third_party/blink/renderer/core/css/css_pending_substitution_value.h"
#include "third_party/blink/renderer/core/css/css_pending_system_font_value.h"
#include "third_party/blink/renderer/core/css/css_repeat_style_value.h"
#include "third_party/blink/renderer/core/css/css_unparsed_declaration_value.h"
#include "third_party/blink/renderer/core/css/css_value_pair.h"
#include "third_party/blink/renderer/core/css/css_value_pool.h"
#include "third_party/blink/renderer/core/css/cssom_utils.h"
#include "third_party/blink/renderer/core/css/properties/css_property.h"
#include "third_party/blink/renderer/core/css/properties/css_property_instances.h"
#include "third_party/blink/renderer/core/css/properties/longhand.h"
#include "third_party/blink/renderer/core/css/properties/longhands.h"
#include "third_party/blink/renderer/core/css/resolver/css_to_style_map.h"
#include "third_party/blink/renderer/core/css_value_keywords.h"
#include "third_party/blink/renderer/core/style_property_shorthand.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
#include "base/logging.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
namespace blink {
namespace {
template <typename T>
T ConvertIdentifierTo(const CSSValue* value, T initial_value) {
if (const auto* ident = DynamicTo<CSSIdentifierValue>(value)) {
return ident->ConvertTo<T>();
}
DCHECK(value->IsInitialValue());
return initial_value;
}
inline WhiteSpaceCollapse ToWhiteSpaceCollapse(const CSSValue* value) {
return ConvertIdentifierTo<WhiteSpaceCollapse>(
value, ComputedStyleInitialValues::InitialWhiteSpaceCollapse());
}
inline TextWrap ToTextWrap(const CSSValue* value) {
return ConvertIdentifierTo<TextWrap>(
value, ComputedStyleInitialValues::InitialTextWrap());
}
bool IsZeroPercent(const CSSValue* value) {
if (const auto* num = DynamicTo<CSSNumericLiteralValue>(value)) {
return num->IsZero() && num->IsPercentage();
}
return false;
}
} // namespace
StylePropertySerializer::CSSPropertyValueSetForSerializer::
CSSPropertyValueSetForSerializer(const CSSPropertyValueSet& properties)
: property_set_(&properties),
all_index_(property_set_->FindPropertyIndex(CSSPropertyID::kAll)),
need_to_expand_all_(false) {
if (!HasAllProperty()) {
return;
}
CSSPropertyValueSet::PropertyReference all_property =
property_set_->PropertyAt(all_index_);
for (unsigned i = 0; i < property_set_->PropertyCount(); ++i) {
CSSPropertyValueSet::PropertyReference property =
property_set_->PropertyAt(i);
if (property.IsAffectedByAll()) {
if (all_property.IsImportant() && !property.IsImportant()) {
continue;
}
if (static_cast<unsigned>(all_index_) >= i) {
continue;
}
if (property.Value() == all_property.Value() &&
property.IsImportant() == all_property.IsImportant()) {
continue;
}
need_to_expand_all_ = true;
}
if (!IsCSSPropertyIDWithName(property.Id())) {
continue;
}
longhand_property_used_.set(GetCSSPropertyIDIndex(property.Id()));
}
}
void StylePropertySerializer::CSSPropertyValueSetForSerializer::Trace(
blink::Visitor* visitor) const {
visitor->Trace(property_set_);
}
unsigned
StylePropertySerializer::CSSPropertyValueSetForSerializer::PropertyCount()
const {
unsigned count = property_set_->PropertyCount();
if (HasExpandedAllProperty()) {
// When expanding all:* we need to serialize all properties set by the "all"
// property, but also still walk the actual property set to include any
// custom property declarations.
count += kIntLastCSSProperty - kIntFirstCSSProperty + 1;
}
return count;
}
StylePropertySerializer::PropertyValueForSerializer
StylePropertySerializer::CSSPropertyValueSetForSerializer::PropertyAt(
unsigned index) const {
if (IsIndexInPropertySet(index)) {
return StylePropertySerializer::PropertyValueForSerializer(
property_set_->PropertyAt(index));
}
// When expanding "all" into longhands, PropertyAt() is called with indices
// outside the size of the property_set_ to serialize all longshands.
DCHECK(HasExpandedAllProperty());
CSSPropertyID property_id = IndexToPropertyID(index);
DCHECK(IsCSSPropertyIDWithName(property_id));
if (longhand_property_used_.test(GetCSSPropertyIDIndex(property_id))) {
// A property declaration for property_id overrides the "all" declaration.
// Access that declaration from the property set.
int real_index = property_set_->FindPropertyIndex(property_id);
DCHECK_NE(real_index, -1);
return StylePropertySerializer::PropertyValueForSerializer(
property_set_->PropertyAt(real_index));
}
CSSPropertyValueSet::PropertyReference property =
property_set_->PropertyAt(all_index_);
return StylePropertySerializer::PropertyValueForSerializer(
CSSProperty::Get(property_id).GetCSSPropertyName(), &property.Value(),
property.IsImportant());
}
bool StylePropertySerializer::CSSPropertyValueSetForSerializer::
ShouldProcessPropertyAt(unsigned index) const {
// CSSPropertyValueSet has all valid longhands. We should process.
if (!HasAllProperty()) {
return true;
}
// If all is not expanded, we need to process "all" and properties which
// are not overwritten by "all".
if (!need_to_expand_all_) {
CSSPropertyValueSet::PropertyReference property =
property_set_->PropertyAt(index);
if (property.Id() == CSSPropertyID::kAll || !property.IsAffectedByAll()) {
return true;
}
if (!IsCSSPropertyIDWithName(property.Id())) {
return false;
}
return longhand_property_used_.test(GetCSSPropertyIDIndex(property.Id()));
}
// Custom property declarations are never overridden by "all" and are only
// traversed for the indices into the property set.
if (IsIndexInPropertySet(index)) {
return property_set_->PropertyAt(index).Id() == CSSPropertyID::kVariable;
}
CSSPropertyID property_id = IndexToPropertyID(index);
DCHECK(IsCSSPropertyIDWithName(property_id));
const CSSProperty& property_class =
CSSProperty::Get(ResolveCSSPropertyID(property_id));
// Since "all" is expanded, we don't need to process "all".
// We should not process expanded shorthands (e.g. font, background,
// and so on) either.
if (property_class.IsShorthand() ||
property_class.IDEquals(CSSPropertyID::kAll)) {
return false;
}
// The all property is a shorthand that resets all CSS properties except
// direction and unicode-bidi. It only accepts the CSS-wide keywords.
// c.f. https://drafts.csswg.org/css-cascade/#all-shorthand
if (!property_class.IsAffectedByAll()) {
return longhand_property_used_.test(GetCSSPropertyIDIndex(property_id));
}
return true;
}
int StylePropertySerializer::CSSPropertyValueSetForSerializer::
FindPropertyIndex(const CSSProperty& property) const {
CSSPropertyID property_id = property.PropertyID();
if (!HasExpandedAllProperty()) {
return property_set_->FindPropertyIndex(property_id);
}
return GetCSSPropertyIDIndex(property_id) + property_set_->PropertyCount();
}
const CSSValue*
StylePropertySerializer::CSSPropertyValueSetForSerializer::GetPropertyCSSValue(
const CSSProperty& property) const {
int index = FindPropertyIndex(property);
if (index == -1) {
return nullptr;
}
StylePropertySerializer::PropertyValueForSerializer value = PropertyAt(index);
return value.Value();
}
bool StylePropertySerializer::CSSPropertyValueSetForSerializer::
IsDescriptorContext() const {
return property_set_->CssParserMode() == kCSSFontFaceRuleMode;
}
StylePropertySerializer::StylePropertySerializer(
const CSSPropertyValueSet& properties)
: property_set_(properties) {}
String StylePropertySerializer::GetCustomPropertyText(
const PropertyValueForSerializer& property,
bool is_not_first_decl) const {
DCHECK_EQ(property.Name().Id(), CSSPropertyID::kVariable);
StringBuilder result;
if (is_not_first_decl) {
result.Append(' ');
}
const CSSValue* value = property.Value();
SerializeIdentifier(property.Name().ToAtomicString(), result,
is_not_first_decl);
result.Append(": ");
result.Append(value->CssText());
if (property.IsImportant()) {
result.Append(" !important");
}
result.Append(';');
return result.ReleaseString();
}
String StylePropertySerializer::GetPropertyText(const CSSPropertyName& name,
const String& value,
bool is_important,
bool is_not_first_decl) const {
StringBuilder result;
if (is_not_first_decl) {
result.Append(' ');
}
result.Append(name.ToAtomicString());
result.Append(": ");
result.Append(value);
if (is_important) {
result.Append(" !important");
}
result.Append(';');
return result.ReleaseString();
}
String StylePropertySerializer::AsText() const {
StringBuilder result;
std::bitset<kNumCSSPropertyIDs> longhand_serialized;
std::bitset<kNumCSSPropertyIDs> shorthand_appeared;
unsigned size = property_set_.PropertyCount();
unsigned num_decls = 0;
for (unsigned n = 0; n < size; ++n) {
if (!property_set_.ShouldProcessPropertyAt(n)) {
continue;
}
StylePropertySerializer::PropertyValueForSerializer property =
property_set_.PropertyAt(n);
const CSSPropertyName& name = property.Name();
CSSPropertyID property_id = name.Id();
#if DCHECK_IS_ON()
if (property_id != CSSPropertyID::kVariable) {
const CSSProperty& property_class = CSSProperty::Get(property_id);
// Only web exposed properties should be part of the style.
DCHECK(property_class.IsWebExposed());
// All shorthand properties should have been expanded at parse time.
DCHECK(property_set_.IsDescriptorContext() ||
(property_class.IsProperty() && !property_class.IsShorthand()));
DCHECK(!property_set_.IsDescriptorContext() ||
property_class.IsDescriptor());
}
#endif // DCHECK_IS_ON()
switch (property_id) {
case CSSPropertyID::kVariable:
result.Append(GetCustomPropertyText(property, num_decls++));
continue;
case CSSPropertyID::kAll:
result.Append(GetPropertyText(name, property.Value()->CssText(),
property.IsImportant(), num_decls++));
continue;
default:
break;
}
if (longhand_serialized.test(GetCSSPropertyIDIndex(property_id))) {
continue;
}
Vector<StylePropertyShorthand, 4> shorthands;
getMatchingShorthandsForLonghand(property_id, &shorthands);
bool serialized_as_shorthand = false;
for (const StylePropertyShorthand& shorthand : shorthands) {
// Some aliases are implemented as a shorthand, in which case
// we prefer to not use the shorthand.
if (shorthand.length() == 1) {
continue;
}
CSSPropertyID shorthand_property = shorthand.id();
int shorthand_property_index = GetCSSPropertyIDIndex(shorthand_property);
// We already tried serializing as this shorthand
if (shorthand_appeared.test(shorthand_property_index)) {
continue;
}
shorthand_appeared.set(shorthand_property_index);
bool serialized_other_longhand = false;
for (unsigned i = 0; i < shorthand.length(); i++) {
if (longhand_serialized.test(GetCSSPropertyIDIndex(
shorthand.properties()[i]->PropertyID()))) {
serialized_other_longhand = true;
break;
}
}
if (serialized_other_longhand) {
continue;
}
String shorthand_result = SerializeShorthand(shorthand_property);
if (shorthand_result.empty()) {
continue;
}
result.Append(GetPropertyText(
CSSProperty::Get(shorthand_property).GetCSSPropertyName(),
shorthand_result, property.IsImportant(), num_decls++));
serialized_as_shorthand = true;
for (unsigned i = 0; i < shorthand.length(); i++) {
longhand_serialized.set(
GetCSSPropertyIDIndex(shorthand.properties()[i]->PropertyID()));
}
break;
}
if (serialized_as_shorthand) {
continue;
}
result.Append(GetPropertyText(name, property.Value()->CssText(),
property.IsImportant(), num_decls++));
}
DCHECK(!num_decls ^ !result.empty());
return result.ReleaseString();
}
// As per css-cascade, shorthands do not expand longhands to the value
// "initial", except when the shorthand is set to "initial", instead
// setting "missing" sub-properties to their initial values. This means
// that a shorthand can never represent a list of subproperties where
// some are "initial" and some are not, and so serialization should
// always fail in these cases (as per cssom). However we currently use
// "initial" instead of the initial values for certain shorthands, so
// these are special-cased here.
// TODO(timloh): Don't use "initial" in shorthands and remove this
// special-casing
static bool AllowInitialInShorthand(CSSPropertyID property_id) {
switch (property_id) {
case CSSPropertyID::kBackground:
case CSSPropertyID::kBorder:
case CSSPropertyID::kBorderTop:
case CSSPropertyID::kBorderRight:
case CSSPropertyID::kBorderBottom:
case CSSPropertyID::kBorderLeft:
case CSSPropertyID::kBorderBlockStart:
case CSSPropertyID::kBorderBlockEnd:
case CSSPropertyID::kBorderInlineStart:
case CSSPropertyID::kBorderInlineEnd:
case CSSPropertyID::kBorderBlock:
case CSSPropertyID::kBorderInline:
case CSSPropertyID::kOutline:
case CSSPropertyID::kColumnRule:
case CSSPropertyID::kColumns:
case CSSPropertyID::kGridColumn:
case CSSPropertyID::kGridRow:
case CSSPropertyID::kGridArea:
case CSSPropertyID::kGap:
case CSSPropertyID::kListStyle:
case CSSPropertyID::kTextDecoration:
case CSSPropertyID::kTextEmphasis:
case CSSPropertyID::kMask:
case CSSPropertyID::kWebkitTextStroke:
case CSSPropertyID::kWhiteSpace:
return true;
default:
return false;
}
}
String StylePropertySerializer::CommonShorthandChecks(
const StylePropertyShorthand& shorthand) const {
unsigned longhand_count = shorthand.length();
if (!longhand_count || longhand_count > kMaxShorthandExpansion) {
NOTREACHED();
return g_empty_string;
}
const CSSValue* longhands[kMaxShorthandExpansion] = {};
bool has_important = false;
bool has_non_important = false;
for (unsigned i = 0; i < longhand_count; i++) {
int index = property_set_.FindPropertyIndex(*shorthand.properties()[i]);
if (index == -1) {
return g_empty_string;
}
PropertyValueForSerializer value = property_set_.PropertyAt(index);
has_important |= value.IsImportant();
has_non_important |= !value.IsImportant();
longhands[i] = value.Value();
}
if (has_important && has_non_important) {
return g_empty_string;
}
if (longhands[0]->IsCSSWideKeyword() ||
longhands[0]->IsPendingSubstitutionValue()) {
bool success = true;
for (unsigned i = 1; i < longhand_count; i++) {
if (!base::ValuesEquivalent(longhands[i], longhands[0])) {
// This should just return emptyString but some shorthands currently
// allow 'initial' for their longhands.
success = false;
break;
}
}
if (success) {
if (const auto* substitution_value =
DynamicTo<cssvalue::CSSPendingSubstitutionValue>(longhands[0])) {
if (substitution_value->ShorthandPropertyId() != shorthand.id()) {
return g_empty_string;
}
return substitution_value->ShorthandValue()->CssText();
}
return longhands[0]->CssText();
}
}
bool allow_initial = AllowInitialInShorthand(shorthand.id());
for (unsigned i = 0; i < longhand_count; i++) {
const CSSValue& value = *longhands[i];
if (!allow_initial && value.IsInitialValue()) {
return g_empty_string;
}
if ((value.IsCSSWideKeyword() && !value.IsInitialValue()) ||
value.IsPendingSubstitutionValue()) {
return g_empty_string;
}
if (value.IsUnparsedDeclaration()) {
return g_empty_string;
}
}
return String();
}
String StylePropertySerializer::SerializeShorthand(
CSSPropertyID property_id) const {
const StylePropertyShorthand& shorthand = shorthandForProperty(property_id);
DCHECK(shorthand.length());
String result = CommonShorthandChecks(shorthand);
if (!result.IsNull()) {
return result;
}
switch (property_id) {
case CSSPropertyID::kAnimation:
return GetLayeredShorthandValue(animationShorthand());
case CSSPropertyID::kAlternativeAnimationWithTimeline:
return GetLayeredShorthandValue(
alternativeAnimationWithTimelineShorthand());
case CSSPropertyID::kAlternativeAnimationWithDelayStartEnd:
return GetLayeredShorthandValue(
alternativeAnimationWithDelayStartEndShorthand());
case CSSPropertyID::kAlternativeAnimationDelay:
return AnimationDelayShorthandValue();
case CSSPropertyID::kAnimationRange:
return AnimationRangeShorthandValue();
case CSSPropertyID::kBorderSpacing:
return Get2Values(borderSpacingShorthand());
case CSSPropertyID::kBackgroundPosition:
return GetLayeredShorthandValue(backgroundPositionShorthand());
case CSSPropertyID::kBackground:
return GetLayeredShorthandValue(backgroundShorthand());
case CSSPropertyID::kBorder:
return BorderPropertyValue(borderWidthShorthand(), borderStyleShorthand(),
borderColorShorthand());
case CSSPropertyID::kBorderImage:
return BorderImagePropertyValue();
case CSSPropertyID::kBorderTop:
return GetShorthandValue(borderTopShorthand());
case CSSPropertyID::kBorderRight:
return GetShorthandValue(borderRightShorthand());
case CSSPropertyID::kBorderBottom:
return GetShorthandValue(borderBottomShorthand());
case CSSPropertyID::kBorderLeft:
return GetShorthandValue(borderLeftShorthand());
case CSSPropertyID::kBorderBlock:
return BorderPropertyValue(borderBlockWidthShorthand(),
borderBlockStyleShorthand(),
borderBlockColorShorthand());
case CSSPropertyID::kBorderBlockColor:
return Get2Values(borderBlockColorShorthand());
case CSSPropertyID::kBorderBlockStyle:
return Get2Values(borderBlockStyleShorthand());
case CSSPropertyID::kBorderBlockWidth:
return Get2Values(borderBlockWidthShorthand());
case CSSPropertyID::kBorderBlockStart:
return GetShorthandValue(borderBlockStartShorthand());
case CSSPropertyID::kBorderBlockEnd:
return GetShorthandValue(borderBlockEndShorthand());
case CSSPropertyID::kBorderInline:
return BorderPropertyValue(borderInlineWidthShorthand(),
borderInlineStyleShorthand(),
borderInlineColorShorthand());
case CSSPropertyID::kBorderInlineColor:
return Get2Values(borderInlineColorShorthand());
case CSSPropertyID::kBorderInlineStyle:
return Get2Values(borderInlineStyleShorthand());
case CSSPropertyID::kBorderInlineWidth:
return Get2Values(borderInlineWidthShorthand());
case CSSPropertyID::kBorderInlineStart:
return GetShorthandValue(borderInlineStartShorthand());
case CSSPropertyID::kBorderInlineEnd:
return GetShorthandValue(borderInlineEndShorthand());
case CSSPropertyID::kContainer:
return ContainerValue();
case CSSPropertyID::kOutline:
return GetShorthandValue(outlineShorthand());
case CSSPropertyID::kBorderColor:
return Get4Values(borderColorShorthand());
case CSSPropertyID::kBorderWidth:
return Get4Values(borderWidthShorthand());
case CSSPropertyID::kBorderStyle:
return Get4Values(borderStyleShorthand());
case CSSPropertyID::kColumnRule:
return GetShorthandValueForColumnRule(columnRuleShorthand());
case CSSPropertyID::kColumns:
return GetShorthandValueForColumns(columnsShorthand());
case CSSPropertyID::kContainIntrinsicSize:
return ContainIntrinsicSizeValue();
case CSSPropertyID::kFlex:
return GetShorthandValue(flexShorthand());
case CSSPropertyID::kFlexFlow:
return GetShorthandValueForDoubleBarCombinator(flexFlowShorthand());
case CSSPropertyID::kGrid:
return GetShorthandValueForGrid(gridShorthand());
case CSSPropertyID::kGridTemplate:
return GetShorthandValueForGridTemplate(gridTemplateShorthand());
case CSSPropertyID::kGridColumn:
return GetShorthandValueForGridLine(gridColumnShorthand());
case CSSPropertyID::kGridRow:
return GetShorthandValueForGridLine(gridRowShorthand());
case CSSPropertyID::kGridArea:
return GetShorthandValueForGridArea(gridAreaShorthand());
case CSSPropertyID::kGap:
return Get2Values(gapShorthand());
case CSSPropertyID::kInset:
return Get4Values(insetShorthand());
case CSSPropertyID::kInsetBlock:
return Get2Values(insetBlockShorthand());
case CSSPropertyID::kInsetInline:
return Get2Values(insetInlineShorthand());
case CSSPropertyID::kPlaceContent:
return Get2Values(placeContentShorthand());
case CSSPropertyID::kPlaceItems:
return Get2Values(placeItemsShorthand());
case CSSPropertyID::kPlaceSelf:
return Get2Values(placeSelfShorthand());
case CSSPropertyID::kFont:
return FontValue();
case CSSPropertyID::kFontSynthesis:
return FontSynthesisValue();
case CSSPropertyID::kFontVariant:
return FontVariantValue();
case CSSPropertyID::kMargin:
return Get4Values(marginShorthand());
case CSSPropertyID::kMarginBlock:
return Get2Values(marginBlockShorthand());
case CSSPropertyID::kMarginInline:
return Get2Values(marginInlineShorthand());
case CSSPropertyID::kOffset:
return OffsetValue();
case CSSPropertyID::kOverflow:
return Get2Values(overflowShorthand());
case CSSPropertyID::kOverscrollBehavior:
return Get2Values(overscrollBehaviorShorthand());
case CSSPropertyID::kPadding:
return Get4Values(paddingShorthand());
case CSSPropertyID::kPaddingBlock:
return Get2Values(paddingBlockShorthand());
case CSSPropertyID::kPaddingInline:
return Get2Values(paddingInlineShorthand());
case CSSPropertyID::kTextDecoration:
return TextDecorationValue();
case CSSPropertyID::kTransition:
return GetLayeredShorthandValue(transitionShorthand());
case CSSPropertyID::kListStyle:
return GetShorthandValue(listStyleShorthand());
case CSSPropertyID::kMaskPosition:
return GetLayeredShorthandValue(maskPositionShorthand());
case CSSPropertyID::kMask:
return GetLayeredShorthandValue(maskShorthand());
case CSSPropertyID::kTextEmphasis:
return GetShorthandValue(textEmphasisShorthand());
case CSSPropertyID::kTextSpacing:
return TextSpacingValue();
case CSSPropertyID::kWebkitTextStroke:
return GetShorthandValue(webkitTextStrokeShorthand());
case CSSPropertyID::kMarker: {
if (const CSSValue* start =
property_set_.GetPropertyCSSValue(GetCSSPropertyMarkerStart())) {
const CSSValue* mid =
property_set_.GetPropertyCSSValue(GetCSSPropertyMarkerMid());
const CSSValue* end =
property_set_.GetPropertyCSSValue(GetCSSPropertyMarkerEnd());
if (mid && end && *start == *mid && *start == *end) {
return start->CssText();
}
}
return String();
}
case CSSPropertyID::kBorderRadius:
return BorderRadiusValue();
case CSSPropertyID::kScrollPadding:
return Get4Values(scrollPaddingShorthand());
case CSSPropertyID::kScrollPaddingBlock:
return Get2Values(scrollPaddingBlockShorthand());
case CSSPropertyID::kScrollPaddingInline:
return Get2Values(scrollPaddingInlineShorthand());
case CSSPropertyID::kScrollMargin:
return Get4Values(scrollMarginShorthand());
case CSSPropertyID::kScrollMarginBlock:
return Get2Values(scrollMarginBlockShorthand());
case CSSPropertyID::kScrollMarginInline:
return Get2Values(scrollMarginInlineShorthand());
case CSSPropertyID::kScrollTimeline:
return ScrollTimelineValue();
case CSSPropertyID::kPageBreakAfter:
return PageBreakPropertyValue(pageBreakAfterShorthand());
case CSSPropertyID::kPageBreakBefore:
return PageBreakPropertyValue(pageBreakBeforeShorthand());
case CSSPropertyID::kPageBreakInside:
return PageBreakPropertyValue(pageBreakInsideShorthand());
case CSSPropertyID::kViewTimeline:
return ViewTimelineValue();
case CSSPropertyID::kAlternativeViewTimelineWithInset:
return AlternativeViewTimelineWithInsetValue();
case CSSPropertyID::kWhiteSpace:
return WhiteSpaceValue();
case CSSPropertyID::kWebkitColumnBreakAfter:
case CSSPropertyID::kWebkitColumnBreakBefore:
case CSSPropertyID::kWebkitColumnBreakInside:
case CSSPropertyID::kWebkitMaskBoxImage:
// Temporary exceptions to the NOTREACHED() below.
// TODO(crbug.com/1316689): Write something real here.
return String();
case CSSPropertyID::kScrollStart:
return ScrollStartValue();
case CSSPropertyID::kScrollStartTarget:
return ScrollStartTargetValue();
case CSSPropertyID::kPositionTry:
return PositionTryValue();
default:
NOTREACHED()
<< "Shorthand property "
<< CSSPropertyName(property_id).ToAtomicString()
<< " must be handled in StylePropertySerializer::SerializeShorthand.";
return String();
}
}
// The font shorthand only allows keyword font-stretch values. Thus, we check if
// a percentage value can be parsed as a keyword, and if so, serialize it as
// that keyword.
const CSSValue* GetFontStretchKeyword(const CSSValue* font_stretch_value) {
if (IsA<CSSIdentifierValue>(font_stretch_value)) {
return font_stretch_value;
}
if (auto* primitive_value =
DynamicTo<CSSPrimitiveValue>(font_stretch_value)) {
double value = primitive_value->GetDoubleValue();
if (value == 50) {
return CSSIdentifierValue::Create(CSSValueID::kUltraCondensed);
}
if (value == 62.5) {
return CSSIdentifierValue::Create(CSSValueID::kExtraCondensed);
}
if (value == 75) {
return CSSIdentifierValue::Create(CSSValueID::kCondensed);
}
if (value == 87.5) {
return CSSIdentifierValue::Create(CSSValueID::kSemiCondensed);
}
if (value == 100) {
return CSSIdentifierValue::Create(CSSValueID::kNormal);
}
if (value == 112.5) {
return CSSIdentifierValue::Create(CSSValueID::kSemiExpanded);
}
if (value == 125) {
return CSSIdentifierValue::Create(CSSValueID::kExpanded);
}
if (value == 150) {
return CSSIdentifierValue::Create(CSSValueID::kExtraExpanded);
}
if (value == 200) {
return CSSIdentifierValue::Create(CSSValueID::kUltraExpanded);
}
}
return nullptr;
}
// Returns false if the value cannot be represented in the font shorthand
bool StylePropertySerializer::AppendFontLonghandValueIfNotNormal(
const CSSProperty& property,
StringBuilder& result) const {
int found_property_index = property_set_.FindPropertyIndex(property);
DCHECK_NE(found_property_index, -1);
const CSSValue* val = property_set_.PropertyAt(found_property_index).Value();
if (property.IDEquals(CSSPropertyID::kFontStretch)) {
const CSSValue* keyword = GetFontStretchKeyword(val);
if (!keyword) {
return false;
}
val = keyword;
}
auto* identifier_value = DynamicTo<CSSIdentifierValue>(val);
if (identifier_value &&
identifier_value->GetValueID() == CSSValueID::kNormal) {
return true;
}
String value;
if (property.IDEquals(CSSPropertyID::kFontVariantLigatures) &&
identifier_value && identifier_value->GetValueID() == CSSValueID::kNone) {
// A shorter representation is preferred in general. Thus, 'none' returns
// instead of the spelling-out form.
// https://www.w3.org/Bugs/Public/show_bug.cgi?id=29594#c1
value = "none";
} else {
value = val->CssText();
}
// The font longhand property values can be empty where the font shorthand
// properties (e.g., font, font-variant, etc.) initialize them.
if (value.empty()) {
return true;
}
if (!result.empty()) {
switch (property.PropertyID()) {
case CSSPropertyID::kFontStyle:
break; // No prefix.
case CSSPropertyID::kFontFamily:
case CSSPropertyID::kFontStretch:
case CSSPropertyID::kFontVariantCaps:
case CSSPropertyID::kFontVariantLigatures:
case CSSPropertyID::kFontVariantNumeric:
case CSSPropertyID::kFontVariantEastAsian:
case CSSPropertyID::kFontVariantAlternates:
case CSSPropertyID::kFontVariantPosition:
case CSSPropertyID::kFontWeight:
result.Append(' ');
break;
case CSSPropertyID::kLineHeight:
result.Append(" / ");
break;
default:
NOTREACHED();
}
}
result.Append(value);
return true;
}
String StylePropertySerializer::ContainerValue() const {
CHECK_EQ(containerShorthand().length(), 2u);
CHECK_EQ(containerShorthand().properties()[0],
&GetCSSPropertyContainerName());
CHECK_EQ(containerShorthand().properties()[1],
&GetCSSPropertyContainerType());
CSSValueList* list = CSSValueList::CreateSlashSeparated();
const CSSValue* name =
property_set_.GetPropertyCSSValue(GetCSSPropertyContainerName());
const CSSValue* type =
property_set_.GetPropertyCSSValue(GetCSSPropertyContainerType());
DCHECK(name);
DCHECK(type);
list->Append(*name);
if (const auto* ident_value = DynamicTo<CSSIdentifierValue>(type);
!ident_value || ident_value->GetValueID() != CSSValueID::kNormal) {
list->Append(*type);
}
return list->CssText();
}
namespace {
bool IsIdentifier(const CSSValue& value, CSSValueID ident) {
const auto* ident_value = DynamicTo<CSSIdentifierValue>(value);
return ident_value && ident_value->GetValueID() == ident;
}
bool IsIdentifierPair(const CSSValue& value, CSSValueID ident) {
const auto* pair_value = DynamicTo<CSSValuePair>(value);
return pair_value && IsIdentifier(pair_value->First(), ident) &&
IsIdentifier(pair_value->Second(), ident);
}
CSSValue* TimelineValueItem(wtf_size_t index,
const CSSValueList& name_list,
const CSSValueList& axis_list,
const CSSValueList* inset_list) {
DCHECK_LT(index, name_list.length());
DCHECK_LT(index, axis_list.length());
DCHECK(!inset_list || index < inset_list->length());
const CSSValue& name = name_list.Item(index);
const CSSValue& axis = axis_list.Item(index);
const CSSValue* inset = inset_list ? &inset_list->Item(index) : nullptr;
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
// Note that the name part can never be omitted, since e.g. serializing
// "view-timeline:none inline" as "view-timeline:inline" doesn't roundtrip.
// (It would set view-timeline-name to inline).
list->Append(name);
if (!IsIdentifier(axis, CSSValueID::kBlock)) {
list->Append(axis);
}
if (inset && !IsIdentifierPair(*inset, CSSValueID::kAuto)) {
list->Append(*inset);
}
return list;
}
} // namespace
String StylePropertySerializer::TimelineValue(
const StylePropertyShorthand& shorthand) const {
CHECK_GE(shorthand.length(), 2u);
CHECK_LE(shorthand.length(), 3u);
const CSSValueList& name_list = To<CSSValueList>(
*property_set_.GetPropertyCSSValue(*shorthand.properties()[0]));
const CSSValueList& axis_list = To<CSSValueList>(
*property_set_.GetPropertyCSSValue(*shorthand.properties()[1]));
const CSSValueList* inset_list =
shorthand.length() == 3u
? To<CSSValueList>(
property_set_.GetPropertyCSSValue(*shorthand.properties()[2]))
: nullptr;
// The scroll/view-timeline shorthand can not expand to longhands of two
// different lengths, so we can also not contract two different-longhands
// into a single shorthand.
if (name_list.length() != axis_list.length()) {
return "";
}
if (inset_list && name_list.length() != inset_list->length()) {
return "";
}
CSSValueList* list = CSSValueList::CreateCommaSeparated();
for (wtf_size_t i = 0; i < name_list.length(); ++i) {
list->Append(*TimelineValueItem(i, name_list, axis_list, inset_list));
}
return list->CssText();
}
String StylePropertySerializer::ScrollTimelineValue() const {
CHECK_EQ(scrollTimelineShorthand().length(), 2u);
CHECK_EQ(scrollTimelineShorthand().properties()[0],
&GetCSSPropertyScrollTimelineName());
CHECK_EQ(scrollTimelineShorthand().properties()[1],
&GetCSSPropertyScrollTimelineAxis());
return TimelineValue(scrollTimelineShorthand());
}
String StylePropertySerializer::ViewTimelineValue() const {
CHECK_EQ(viewTimelineShorthand().length(), 2u);
CHECK_EQ(viewTimelineShorthand().properties()[0],
&GetCSSPropertyViewTimelineName());
CHECK_EQ(viewTimelineShorthand().properties()[1],
&GetCSSPropertyViewTimelineAxis());
return TimelineValue(viewTimelineShorthand());
}
String StylePropertySerializer::AlternativeViewTimelineWithInsetValue() const {
CHECK_EQ(alternativeViewTimelineWithInsetShorthand().length(), 3u);
CHECK_EQ(alternativeViewTimelineWithInsetShorthand().properties()[0],
&GetCSSPropertyViewTimelineName());
CHECK_EQ(alternativeViewTimelineWithInsetShorthand().properties()[1],
&GetCSSPropertyViewTimelineAxis());
CHECK_EQ(alternativeViewTimelineWithInsetShorthand().properties()[2],
&GetCSSPropertyViewTimelineInset());
return TimelineValue(alternativeViewTimelineWithInsetShorthand());
}
namespace {
CSSValue* AnimationDelayShorthandValueItem(wtf_size_t index,
const CSSValueList& start_list,
const CSSValueList& end_list) {
DCHECK_LT(index, start_list.length());
DCHECK_LT(index, end_list.length());
const CSSValue& start = start_list.Item(index);
const CSSValue& end = end_list.Item(index);
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
list->Append(start);
if (const auto* primitive = DynamicTo<CSSPrimitiveValue>(end);
!primitive || !primitive->IsZero()) {
list->Append(end);
}
return list;
}
// Return the name and offset (in percent). This is useful for
// contracting '<somename> 0%' and '<somename> 100%' into just <somename>.
//
// If the offset is present, but not a <percentage>, -1 is returned as the
// offset. Otherwise (also in the 'normal' case), the `default_offset_percent`
// is returned.
std::pair<CSSValueID, double> GetTimelineRangePercent(
const CSSValue& value,
double default_offset_percent) {
const auto* list = DynamicTo<CSSValueList>(value);
if (!list) {
return {CSSValueID::kNormal, default_offset_percent};
}
DCHECK_GE(list->length(), 1u);
DCHECK_LE(list->length(), 2u);
CSSValueID name = CSSValueID::kNormal;
double offset_percent = default_offset_percent;
if (list->Item(0).IsIdentifierValue()) {
name = To<CSSIdentifierValue>(list->Item(0)).GetValueID();
if (list->length() == 2u) {
const auto& offset = To<CSSPrimitiveValue>(list->Item(1));
offset_percent = offset.IsPercentage() ? offset.GetValue<double>() : -1.0;
}
} else {
const auto& offset = To<CSSPrimitiveValue>(list->Item(0));
offset_percent = offset.IsPercentage() ? offset.GetValue<double>() : -1.0;
}
return {name, offset_percent};
}
CSSValue* AnimationRangeShorthandValueItem(wtf_size_t index,
const CSSValueList& start_list,
const CSSValueList& end_list) {
DCHECK_LT(index, start_list.length());
DCHECK_LT(index, end_list.length());
const CSSValue& start = start_list.Item(index);
const CSSValue& end = end_list.Item(index);
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
list->Append(start);
// The form "name X name 100%" must contract to "name X".
//
// https://github.com/w3c/csswg-drafts/issues/8438
const auto& start_pair = GetTimelineRangePercent(start, 0.0);
const auto& end_pair = GetTimelineRangePercent(end, 100.0);
std::pair<CSSValueID, double> omittable_end = {start_pair.first, 100.0};
if (end_pair != omittable_end) {
list->Append(end);
}
return list;
}
} // namespace
String StylePropertySerializer::AnimationDelayShorthandValue() const {
CHECK_EQ(alternativeAnimationDelayShorthand().length(), 2u);
CHECK_EQ(alternativeAnimationDelayShorthand().properties()[0],
&GetCSSPropertyAnimationDelayStart());
CHECK_EQ(alternativeAnimationDelayShorthand().properties()[1],
&GetCSSPropertyAnimationDelayEnd());
const CSSValueList& start_list = To<CSSValueList>(
*property_set_.GetPropertyCSSValue(GetCSSPropertyAnimationDelayStart()));
const CSSValueList& end_list = To<CSSValueList>(
*property_set_.GetPropertyCSSValue(GetCSSPropertyAnimationDelayEnd()));
if (start_list.length() != end_list.length()) {
return "";
}
CSSValueList* list = CSSValueList::CreateCommaSeparated();
for (wtf_size_t i = 0; i < start_list.length(); ++i) {
list->Append(*AnimationDelayShorthandValueItem(i, start_list, end_list));
}
return list->CssText();
}
String StylePropertySerializer::AnimationRangeShorthandValue() const {
CHECK_EQ(animationRangeShorthand().length(), 2u);
CHECK_EQ(animationRangeShorthand().properties()[0],
&GetCSSPropertyAnimationRangeStart());
CHECK_EQ(animationRangeShorthand().properties()[1],
&GetCSSPropertyAnimationRangeEnd());
const CSSValueList& start_list = To<CSSValueList>(
*property_set_.GetPropertyCSSValue(GetCSSPropertyAnimationRangeStart()));
const CSSValueList& end_list = To<CSSValueList>(
*property_set_.GetPropertyCSSValue(GetCSSPropertyAnimationRangeEnd()));
if (start_list.length() != end_list.length()) {
return "";
}
CSSValueList* list = CSSValueList::CreateCommaSeparated();
for (wtf_size_t i = 0; i < start_list.length(); ++i) {
list->Append(*AnimationRangeShorthandValueItem(i, start_list, end_list));
}
return list->CssText();
}
String StylePropertySerializer::FontValue() const {
int font_size_property_index =
property_set_.FindPropertyIndex(GetCSSPropertyFontSize());
int font_family_property_index =
property_set_.FindPropertyIndex(GetCSSPropertyFontFamily());
int font_variant_caps_property_index =
property_set_.FindPropertyIndex(GetCSSPropertyFontVariantCaps());
int font_variant_ligatures_property_index =
property_set_.FindPropertyIndex(GetCSSPropertyFontVariantLigatures());
int font_variant_numeric_property_index =
property_set_.FindPropertyIndex(GetCSSPropertyFontVariantNumeric());
int font_variant_east_asian_property_index =
property_set_.FindPropertyIndex(GetCSSPropertyFontVariantEastAsian());
int font_kerning_property_index =
property_set_.FindPropertyIndex(GetCSSPropertyFontKerning());
int font_optical_sizing_property_index =
property_set_.FindPropertyIndex(GetCSSPropertyFontOpticalSizing());
int font_variation_settings_property_index =
property_set_.FindPropertyIndex(GetCSSPropertyFontVariationSettings());
int font_feature_settings_property_index =
property_set_.FindPropertyIndex(GetCSSPropertyFontFeatureSettings());
DCHECK_NE(font_size_property_index, -1);
DCHECK_NE(font_family_property_index, -1);
DCHECK_NE(font_variant_caps_property_index, -1);
DCHECK_NE(font_variant_ligatures_property_index, -1);
DCHECK_NE(font_variant_numeric_property_index, -1);
DCHECK_NE(font_variant_east_asian_property_index, -1);
DCHECK_NE(font_kerning_property_index, -1);
DCHECK_NE(font_optical_sizing_property_index, -1);
DCHECK_NE(font_variation_settings_property_index, -1);
DCHECK_NE(font_feature_settings_property_index, -1);
PropertyValueForSerializer font_size_property =
property_set_.PropertyAt(font_size_property_index);
PropertyValueForSerializer font_family_property =
property_set_.PropertyAt(font_family_property_index);
PropertyValueForSerializer font_variant_caps_property =
property_set_.PropertyAt(font_variant_caps_property_index);
PropertyValueForSerializer font_variant_ligatures_property =
property_set_.PropertyAt(font_variant_ligatures_property_index);
PropertyValueForSerializer font_variant_numeric_property =
property_set_.PropertyAt(font_variant_numeric_property_index);
PropertyValueForSerializer font_variant_east_asian_property =
property_set_.PropertyAt(font_variant_east_asian_property_index);
PropertyValueForSerializer font_kerning_property =
property_set_.PropertyAt(font_kerning_property_index);
PropertyValueForSerializer font_optical_sizing_property =
property_set_.PropertyAt(font_optical_sizing_property_index);
PropertyValueForSerializer font_variation_settings_property =
property_set_.PropertyAt(font_variation_settings_property_index);
PropertyValueForSerializer font_feature_settings_property =
property_set_.PropertyAt(font_feature_settings_property_index);
// Check that non-initial font-variant subproperties are not conflicting with
// this serialization.
const CSSValue* ligatures_value = font_variant_ligatures_property.Value();
const CSSValue* numeric_value = font_variant_numeric_property.Value();
const CSSValue* east_asian_value = font_variant_east_asian_property.Value();
const CSSValue* feature_settings_value =
font_feature_settings_property.Value();
const CSSValue* variation_settings_value =
font_variation_settings_property.Value();
auto IsPropertyNonInitial = [](const CSSValue& value,
const CSSValueID initial_value_id) {
auto* identifier_value = DynamicTo<CSSIdentifierValue>(value);
return (identifier_value &&
identifier_value->GetValueID() != initial_value_id);
};
if (IsPropertyNonInitial(*ligatures_value, CSSValueID::kNormal) ||
ligatures_value->IsValueList()) {
return g_empty_string;
}
if (IsPropertyNonInitial(*numeric_value, CSSValueID::kNormal) ||
numeric_value->IsValueList()) {
return g_empty_string;
}
if (IsPropertyNonInitial(*east_asian_value, CSSValueID::kNormal) ||
east_asian_value->IsValueList()) {
return g_empty_string;
}
if (IsPropertyNonInitial(*font_kerning_property.Value(), CSSValueID::kAuto) ||
IsPropertyNonInitial(*font_optical_sizing_property.Value(),
CSSValueID::kAuto)) {
return g_empty_string;
}
if (IsPropertyNonInitial(*variation_settings_value, CSSValueID::kNormal) ||
variation_settings_value->IsValueList()) {
return g_empty_string;
}
if (IsPropertyNonInitial(*feature_settings_value, CSSValueID::kNormal) ||
feature_settings_value->IsValueList()) {
return g_empty_string;
}
int font_variant_alternates_property_index =
property_set_.FindPropertyIndex(GetCSSPropertyFontVariantAlternates());
DCHECK_NE(font_variant_alternates_property_index, -1);
PropertyValueForSerializer font_variant_alternates_property =
property_set_.PropertyAt(font_variant_alternates_property_index);
const CSSValue* alternates_value = font_variant_alternates_property.Value();
if (IsPropertyNonInitial(*alternates_value, CSSValueID::kNormal) ||
alternates_value->IsValueList()) {
return g_empty_string;
}
int font_variant_position_property_index =
property_set_.FindPropertyIndex(GetCSSPropertyFontVariantPosition());
DCHECK_NE(font_variant_position_property_index, -1);
PropertyValueForSerializer font_variant_position_property =
property_set_.PropertyAt(font_variant_position_property_index);
if (IsPropertyNonInitial(*font_variant_position_property.Value(),
CSSValueID::kNormal)) {
return g_empty_string;
}
if (RuntimeEnabledFeatures::CSSFontSizeAdjustEnabled()) {
int font_size_adjust_property_index =
property_set_.FindPropertyIndex(GetCSSPropertyFontSizeAdjust());
DCHECK_NE(font_size_adjust_property_index, -1);
PropertyValueForSerializer font_size_adjust_property =
property_set_.PropertyAt(font_size_adjust_property_index);
const CSSValue* size_adjust_value = font_size_adjust_property.Value();
if (IsPropertyNonInitial(*size_adjust_value, CSSValueID::kNone) ||
size_adjust_value->IsNumericLiteralValue()) {
return g_empty_string;
}
}
const StylePropertyShorthand& shorthand = fontShorthand();
const CSSProperty** longhands = shorthand.properties();
unsigned length = shorthand.length();
const CSSValue* first = property_set_.GetPropertyCSSValue(*longhands[0]);
if (const auto* system_font =
DynamicTo<cssvalue::CSSPendingSystemFontValue>(first)) {
for (unsigned i = 1; i < length; i++) {
const CSSValue* value = property_set_.GetPropertyCSSValue(*longhands[i]);
if (!base::ValuesEquivalent(first, value)) {
return g_empty_string;
}
}
return getValueName(system_font->SystemFontId());
} else {
for (unsigned i = 1; i < length; i++) {
const CSSValue* value = property_set_.GetPropertyCSSValue(*longhands[i]);
if (value->IsPendingSystemFontValue()) {
return g_empty_string;
}
}
}
StringBuilder result;
AppendFontLonghandValueIfNotNormal(GetCSSPropertyFontStyle(), result);
const CSSValue* val = font_variant_caps_property.Value();
auto* identifier_value = DynamicTo<CSSIdentifierValue>(val);
if (identifier_value &&
(identifier_value->GetValueID() != CSSValueID::kSmallCaps &&
identifier_value->GetValueID() != CSSValueID::kNormal)) {
return g_empty_string;
}
AppendFontLonghandValueIfNotNormal(GetCSSPropertyFontVariantCaps(), result);
AppendFontLonghandValueIfNotNormal(GetCSSPropertyFontWeight(), result);
bool font_stretch_valid =
AppendFontLonghandValueIfNotNormal(GetCSSPropertyFontStretch(), result);
if (!font_stretch_valid) {
return String();
}
if (!result.empty()) {
result.Append(' ');
}
result.Append(font_size_property.Value()->CssText());
AppendFontLonghandValueIfNotNormal(GetCSSPropertyLineHeight(), result);
if (!result.empty()) {
result.Append(' ');
}
result.Append(font_family_property.Value()->CssText());
return result.ReleaseString();
}
String StylePropertySerializer::FontVariantValue() const {
StringBuilder result;
bool is_variant_ligatures_none = false;
AppendFontLonghandValueIfNotNormal(GetCSSPropertyFontVariantLigatures(),
result);
if (result.ToString() == "none") {
is_variant_ligatures_none = true;
}
const unsigned variant_ligatures_result_length = result.length();
AppendFontLonghandValueIfNotNormal(GetCSSPropertyFontVariantCaps(), result);
AppendFontLonghandValueIfNotNormal(GetCSSPropertyFontVariantAlternates(),
result);
AppendFontLonghandValueIfNotNormal(GetCSSPropertyFontVariantNumeric(),
result);
AppendFontLonghandValueIfNotNormal(GetCSSPropertyFontVariantEastAsian(),
result);
AppendFontLonghandValueIfNotNormal(GetCSSPropertyFontVariantPosition(),
result);
// The font-variant shorthand should return an empty string where
// it cannot represent "font-variant-ligatures: none" along
// with any other non-normal longhands.
// https://drafts.csswg.org/cssom-1/#serializing-css-values
if (is_variant_ligatures_none &&
result.length() != variant_ligatures_result_length) {
return g_empty_string;
}
if (result.empty()) {
return "normal";
}
return result.ReleaseString();
}
String StylePropertySerializer::FontSynthesisValue() const {
StringBuilder result;
int font_synthesis_weight_property_index =
property_set_.FindPropertyIndex(GetCSSPropertyFontSynthesisWeight());
int font_synthesis_style_property_index =
property_set_.FindPropertyIndex(GetCSSPropertyFontSynthesisStyle());
int font_synthesis_small_caps_property_index =
property_set_.FindPropertyIndex(GetCSSPropertyFontSynthesisSmallCaps());
DCHECK_NE(font_synthesis_weight_property_index, -1);
DCHECK_NE(font_synthesis_style_property_index, -1);
DCHECK_NE(font_synthesis_small_caps_property_index, -1);
PropertyValueForSerializer font_synthesis_weight_property =
property_set_.PropertyAt(font_synthesis_weight_property_index);
PropertyValueForSerializer font_synthesis_style_property =
property_set_.PropertyAt(font_synthesis_style_property_index);
PropertyValueForSerializer font_synthesis_small_caps_property =
property_set_.PropertyAt(font_synthesis_small_caps_property_index);
const CSSValue* font_synthesis_weight_value =
font_synthesis_weight_property.Value();
const CSSValue* font_synthesis_style_value =
font_synthesis_style_property.Value();
const CSSValue* font_synthesis_small_caps_value =
font_synthesis_small_caps_property.Value();
auto* font_synthesis_weight_identifier_value =
DynamicTo<CSSIdentifierValue>(font_synthesis_weight_value);
if (font_synthesis_weight_identifier_value &&
font_synthesis_weight_identifier_value->GetValueID() ==
CSSValueID::kAuto) {
result.Append("weight");
}
auto* font_synthesis_style_identifier_value =
DynamicTo<CSSIdentifierValue>(font_synthesis_style_value);
if (font_synthesis_style_identifier_value &&
font_synthesis_style_identifier_value->GetValueID() ==
CSSValueID::kAuto) {
if (!result.empty()) {
result.Append(' ');
}
result.Append("style");
}
auto* font_synthesis_small_caps_identifier_value =
DynamicTo<CSSIdentifierValue>(font_synthesis_small_caps_value);
if (font_synthesis_small_caps_identifier_value &&
font_synthesis_small_caps_identifier_value->GetValueID() ==
CSSValueID::kAuto) {
if (!result.empty()) {
result.Append(' ');
}
result.Append("small-caps");
}
if (result.empty()) {
return "none";
}
return result.ReleaseString();
}
String StylePropertySerializer::OffsetValue() const {
const CSSValue* position =
property_set_.GetPropertyCSSValue(GetCSSPropertyOffsetPosition());
const CSSValue* path =
property_set_.GetPropertyCSSValue(GetCSSPropertyOffsetPath());
const CSSValue* distance =
property_set_.GetPropertyCSSValue(GetCSSPropertyOffsetDistance());
const CSSValue* rotate =
property_set_.GetPropertyCSSValue(GetCSSPropertyOffsetRotate());
const CSSValue* anchor =
property_set_.GetPropertyCSSValue(GetCSSPropertyOffsetAnchor());
auto is_initial_identifier_value = [](const CSSValue* value,
CSSValueID id) -> bool {
return value->IsIdentifierValue() &&
DynamicTo<CSSIdentifierValue>(value)->GetValueID() == id;
};
bool use_distance =
distance && !(distance->IsNumericLiteralValue() &&
To<CSSNumericLiteralValue>(*distance).DoubleValue() == 0.0);
const auto* rotate_list_value = DynamicTo<CSSValueList>(rotate);
bool is_rotate_auto = rotate_list_value && rotate_list_value->length() == 1 &&
is_initial_identifier_value(&rotate_list_value->First(),
CSSValueID::kAuto);
bool is_rotate_zero =
rotate_list_value && rotate_list_value->length() == 1 &&
rotate_list_value->First().IsNumericLiteralValue() &&
(To<CSSNumericLiteralValue>(rotate_list_value->First()).DoubleValue() ==
0.0);
bool is_rotate_auto_zero =
rotate_list_value && rotate_list_value->length() == 2 &&
rotate_list_value->Item(1).IsNumericLiteralValue() &&
(To<CSSNumericLiteralValue>(rotate_list_value->Item(1)).DoubleValue() ==
0.0) &&
is_initial_identifier_value(&rotate_list_value->Item(0),
CSSValueID::kAuto);
bool use_rotate =
rotate && ((use_distance && is_rotate_zero) ||
(!is_initial_identifier_value(rotate, CSSValueID::kAuto) &&
!is_rotate_auto && !is_rotate_auto_zero));
bool use_path =
path && (use_rotate || use_distance ||
!is_initial_identifier_value(path, CSSValueID::kNone));
bool use_position =
position && (!use_path ||
!is_initial_identifier_value(position, CSSValueID::kNormal));
bool use_anchor =
anchor && (!is_initial_identifier_value(anchor, CSSValueID::kAuto));
StringBuilder result;
if (use_position) {
result.Append(position->CssText());
}
if (use_path) {
if (!result.empty()) {
result.Append(" ");
}
result.Append(path->CssText());
}
if (use_distance) {
result.Append(" ");
result.Append(distance->CssText());
}
if (use_rotate) {
result.Append(" ");
result.Append(rotate->CssText());
}
if (use_anchor) {
result.Append(" / ");
result.Append(anchor->CssText());
}
return result.ReleaseString();
}
String StylePropertySerializer::TextDecorationValue() const {
StringBuilder result;
const auto& shorthand = shorthandForProperty(CSSPropertyID::kTextDecoration);
for (unsigned i = 0; i < shorthand.length(); ++i) {
const CSSValue* value =
property_set_.GetPropertyCSSValue(*shorthand.properties()[i]);
String value_text = value->CssText();
if (value->IsInitialValue()) {
continue;
}
if (shorthand.properties()[i]->PropertyID() ==
CSSPropertyID::kTextDecorationThickness) {
if (auto* identifier_value = DynamicTo<CSSIdentifierValue>(value)) {
// Do not include initial value 'auto' for thickness.
// TODO(https://crbug.com/1093826): general shorthand serialization
// issues remain, in particular for text-decoration.
CSSValueID value_id = identifier_value->GetValueID();
if (value_id == CSSValueID::kAuto) {
continue;
}
}
}
if (!result.empty()) {
result.Append(" ");
}
result.Append(value_text);
}
if (result.empty()) {
return "none";
}
return result.ReleaseString();
}
String StylePropertySerializer::Get2Values(
const StylePropertyShorthand& shorthand) const {
// Assume the properties are in the usual order start, end.
int start_value_index =
property_set_.FindPropertyIndex(*shorthand.properties()[0]);
int end_value_index =
property_set_.FindPropertyIndex(*shorthand.properties()[1]);
if (start_value_index == -1 || end_value_index == -1) {
return String();
}
PropertyValueForSerializer start =
property_set_.PropertyAt(start_value_index);
PropertyValueForSerializer end = property_set_.PropertyAt(end_value_index);
bool show_end = !base::ValuesEquivalent(start.Value(), end.Value());
StringBuilder result;
result.Append(start.Value()->CssText());
if (show_end) {
result.Append(' ');
result.Append(end.Value()->CssText());
}
return result.ReleaseString();
}
String StylePropertySerializer::Get4Values(
const StylePropertyShorthand& shorthand) const {
// Assume the properties are in the usual order top, right, bottom, left.
int top_value_index =
property_set_.FindPropertyIndex(*shorthand.properties()[0]);
int right_value_index =
property_set_.FindPropertyIndex(*shorthand.properties()[1]);
int bottom_value_index =
property_set_.FindPropertyIndex(*shorthand.properties()[2]);
int left_value_index =
property_set_.FindPropertyIndex(*shorthand.properties()[3]);
if (top_value_index == -1 || right_value_index == -1 ||
bottom_value_index == -1 || left_value_index == -1) {
return String();
}
PropertyValueForSerializer top = property_set_.PropertyAt(top_value_index);
PropertyValueForSerializer right =
property_set_.PropertyAt(right_value_index);
PropertyValueForSerializer bottom =
property_set_.PropertyAt(bottom_value_index);
PropertyValueForSerializer left = property_set_.PropertyAt(left_value_index);
bool show_left = !base::ValuesEquivalent(right.Value(), left.Value());
bool show_bottom =
!base::ValuesEquivalent(top.Value(), bottom.Value()) || show_left;
bool show_right =
!base::ValuesEquivalent(top.Value(), right.Value()) || show_bottom;
StringBuilder result;
result.Append(top.Value()->CssText());
if (show_right) {
result.Append(' ');
result.Append(right.Value()->CssText());
}
if (show_bottom) {
result.Append(' ');
result.Append(bottom.Value()->CssText());
}
if (show_left) {
result.Append(' ');
result.Append(left.Value()->CssText());
}
return result.ReleaseString();
}
namespace {
// Serialize clip and origin (https://drafts.fxtf.org/css-masking/#the-mask):
// * If one <geometry-box> value and the no-clip keyword are present then
// <geometry-box> sets mask-origin and no-clip sets mask-clip to that value.
// * If one <geometry-box> value and no no-clip keyword are present then
// <geometry-box> sets both mask-origin and mask-clip to that value.
// * If two <geometry-box> values are present, then the first sets mask-origin
// and the second mask-clip.
// Additionally, omits components when possible (see:
// https://drafts.csswg.org/cssom/#serialize-a-css-value).
void SerializeMaskOriginAndClip(StringBuilder& result,
const CSSValueID& origin_id,
const CSSValueID& clip_id) {
// If both values are border-box, omit everything as it is the default.
if (origin_id == CSSValueID::kBorderBox &&
clip_id == CSSValueID::kBorderBox) {
return;
}
if (!result.empty()) {
result.Append(' ');
}
if (origin_id == clip_id) {
// If the values are the same, only emit one value. Note that mask-origin
// does not support no-clip, so there is no need to consider no-clip
// special cases.
result.Append(getValueName(origin_id));
} else if (origin_id == CSSValueID::kBorderBox &&
clip_id == CSSValueID::kNoClip) {
// Mask-origin does not support no-clip, so mask-origin can be omitted if it
// is the default.
result.Append(getValueName(clip_id));
} else {
result.Append(getValueName(origin_id));
result.Append(' ');
result.Append(getValueName(clip_id));
}
}
} // namespace
String StylePropertySerializer::GetLayeredShorthandValue(
const StylePropertyShorthand& shorthand) const {
const unsigned size = shorthand.length();
// Begin by collecting the properties into a vector.
HeapVector<Member<const CSSValue>> values(size);
// If the below loop succeeds, there should always be at minimum 1 layer.
wtf_size_t num_layers = 1U;
// TODO(timloh): Shouldn't we fail if the lists are differently sized, with
// the exception of background-color?
for (unsigned i = 0; i < size; i++) {
values[i] = property_set_.GetPropertyCSSValue(*shorthand.properties()[i]);
if (values[i]->IsBaseValueList()) {
const CSSValueList* value_list = To<CSSValueList>(values[i].Get());
num_layers = std::max(num_layers, value_list->length());
}
}
StringBuilder result;
// Now stitch the properties together.
for (wtf_size_t layer = 0; layer < num_layers; layer++) {
StringBuilder layer_result;
bool is_position_x_serialized = false;
bool is_position_y_serialized = false;
const CSSValue* mask_position_x = nullptr;
CSSValueID mask_origin_value = CSSValueID::kBorderBox;
for (unsigned property_index = 0; property_index < size; property_index++) {
const CSSValue* value = nullptr;
const CSSProperty* property = shorthand.properties()[property_index];
// Get a CSSValue for this property and layer.
if (values[property_index]->IsBaseValueList()) {
const auto* property_values =
To<CSSValueList>(values[property_index].Get());
// There might not be an item for this layer for this property.
if (layer < property_values->length()) {
value = &property_values->Item(layer);
}
} else if ((layer == 0 &&
!property->IDEquals(CSSPropertyID::kBackgroundColor)) ||
(layer == num_layers - 1 &&
property->IDEquals(CSSPropertyID::kBackgroundColor))) {
// Singletons except background color belong in the 0th layer.
// Background color belongs in the last layer.
value = values[property_index];
}
// No point proceeding if there's not a value to look at.
if (!value) {
continue;
}
bool omit_value = value->IsInitialValue();
// The shorthand can not represent the following properties if they have
// non-initial values. This is because they are always reset to their
// initial value by the shorthand.
//
// Note that initial values for animation-* properties only contain
// one list item, hence the check for 'layer > 0'.
if (property->IDEquals(CSSPropertyID::kAnimationTimeline)) {
auto* ident = DynamicTo<CSSIdentifierValue>(value);
if (!ident ||
(ident->GetValueID() !=
CSSAnimationData::InitialTimeline().GetKeyword()) ||
layer > 0) {
DCHECK(RuntimeEnabledFeatures::ScrollTimelineEnabled());
return g_empty_string;
}
omit_value = true;
}
if (property->IDEquals(CSSPropertyID::kAnimationDelayEnd)) {
if (CSSToStyleMap::MapAnimationDelayEnd(*value) !=
CSSTimingData::InitialDelayEnd() ||
layer > 0) {
return g_empty_string;
}
omit_value = true;
}
if (property->IDEquals(CSSPropertyID::kAnimationRangeStart)) {
auto* ident = DynamicTo<CSSIdentifierValue>(value);
if (!ident || (ident->GetValueID() != CSSValueID::kNormal) ||
layer > 0) {
DCHECK(RuntimeEnabledFeatures::ScrollTimelineEnabled());
return g_empty_string;
}
omit_value = true;
}
if (property->IDEquals(CSSPropertyID::kAnimationRangeEnd)) {
auto* ident = DynamicTo<CSSIdentifierValue>(value);
if (!ident || (ident->GetValueID() != CSSValueID::kNormal) ||
layer > 0) {
DCHECK(RuntimeEnabledFeatures::ScrollTimelineEnabled());
return g_empty_string;
}
omit_value = true;
}
if (property->IDEquals(CSSPropertyID::kTransitionBehavior)) {
auto* ident = DynamicTo<CSSIdentifierValue>(value);
CHECK(ident) << " transition-behavior should only have a "
"CSSIdentifierValue for a value. CssText: "
<< value->CssText();
if (ident->GetValueID() == CSSValueID::kNormal) {
// transition-behavior overrides InitialValue to return "normal"
// instead of "initial", but we don't want to include "normal" in the
// shorthand serialization, so this special case is needed.
// TODO(http://crbug.com/501673): We should have a better solution
// before fixing all CSS properties to fix the above bug.
omit_value = true;
}
}
if (shorthand.id() == CSSPropertyID::kMask) {
if (property->IDEquals(CSSPropertyID::kMaskImage)) {
if (auto* image_value = DynamicTo<CSSIdentifierValue>(value)) {
if (image_value->GetValueID() == CSSValueID::kNone) {
omit_value = true;
}
}
} else if (property->IDEquals(CSSPropertyID::kMaskOrigin)) {
if (auto* ident = DynamicTo<CSSIdentifierValue>(value)) {
mask_origin_value = ident->GetValueID();
}
// Omit this value as it is serialized alongside mask-clip.
omit_value = true;
} else if (property->IDEquals(CSSPropertyID::kMaskClip)) {
CSSValueID mask_clip_id = CSSValueID::kBorderBox;
if (auto* ident = DynamicTo<CSSIdentifierValue>(value)) {
mask_clip_id = ident->GetValueID();
}
SerializeMaskOriginAndClip(layer_result, mask_origin_value,
mask_clip_id);
omit_value = true;
} else if (property->IDEquals(CSSPropertyID::kMaskComposite)) {
if (auto* ident = DynamicTo<CSSIdentifierValue>(value)) {
if (ident->GetValueID() == CSSValueID::kAdd) {
omit_value = true;
}
}
} else if (property->IDEquals(CSSPropertyID::kMaskMode)) {
if (auto* ident = DynamicTo<CSSIdentifierValue>(value)) {
if (ident->GetValueID() == CSSValueID::kMatchSource) {
omit_value = true;
}
}
} else if (property->IDEquals(CSSPropertyID::kMaskRepeat)) {
if (auto* repeat = DynamicTo<CSSRepeatStyleValue>(value)) {
if (repeat->IsRepeat()) {
omit_value = true;
}
}
} else if (property->IDEquals(CSSPropertyID::kMaskSize)) {
if (auto* size_value = DynamicTo<CSSIdentifierValue>(value)) {
if (size_value->GetValueID() == CSSValueID::kAuto) {
omit_value = true;
}
}
} else if (property->IDEquals(CSSPropertyID::kWebkitMaskPositionX)) {
omit_value = true;
mask_position_x = value;
} else if (property->IDEquals(CSSPropertyID::kWebkitMaskPositionY)) {
omit_value = true;
if (!IsZeroPercent(mask_position_x) || !IsZeroPercent(value)) {
is_position_x_serialized = true;
is_position_y_serialized = true;
if (!layer_result.empty()) {
layer_result.Append(' ');
}
layer_result.Append(mask_position_x->CssText());
layer_result.Append(' ');
layer_result.Append(value->CssText());
}
}
}
if (!omit_value) {
if (property->IDEquals(CSSPropertyID::kBackgroundSize) ||
property->IDEquals(CSSPropertyID::kMaskSize)) {
if (is_position_y_serialized || is_position_x_serialized) {
layer_result.Append(" / ");
} else {
layer_result.Append(" 0% 0% / ");
}
} else if (!layer_result.empty()) {
// Do this second to avoid ending up with an extra space in the output
// if we hit the continue above.
layer_result.Append(' ');
}
layer_result.Append(value->CssText());
if (property->IDEquals(CSSPropertyID::kBackgroundPositionX)) {
is_position_x_serialized = true;
}
if (property->IDEquals(CSSPropertyID::kBackgroundPositionY)) {
is_position_y_serialized = true;
// background-position is a special case. If only the first offset is
// specified, the second one defaults to "center", not the same value.
}
}
}
if (shorthand.id() == CSSPropertyID::kMask && layer_result.empty()) {
layer_result.Append(getValueName(CSSValueID::kNone));
}
if (!layer_result.empty()) {
if (!result.empty()) {
result.Append(", ");
}
result.Append(layer_result);
}
}
return result.ReleaseString();
}
String StylePropertySerializer::GetShorthandValue(
const StylePropertyShorthand& shorthand,
String separator) const {
StringBuilder result;
for (unsigned i = 0; i < shorthand.length(); ++i) {
const CSSValue* value =
property_set_.GetPropertyCSSValue(*shorthand.properties()[i]);
String value_text = value->CssText();
if (value->IsInitialValue()) {
continue;
}
if (!result.empty()) {
result.Append(separator);
}
result.Append(value_text);
}
return result.ReleaseString();
}
String StylePropertySerializer::GetShorthandValueForColumnRule(
const StylePropertyShorthand& shorthand) const {
DCHECK_EQ(shorthand.length(), 3u);
const CSSValue* column_rule_width =
property_set_.GetPropertyCSSValue(*shorthand.properties()[0]);
const CSSValue* column_rule_style =
property_set_.GetPropertyCSSValue(*shorthand.properties()[1]);
const CSSValue* column_rule_color =
property_set_.GetPropertyCSSValue(*shorthand.properties()[2]);
StringBuilder result;
if (const auto* ident_value =
DynamicTo<CSSIdentifierValue>(column_rule_width);
!(ident_value && ident_value->GetValueID() == CSSValueID::kMedium) &&
!column_rule_width->IsInitialValue()) {
String column_rule_width_text = column_rule_width->CssText();
result.Append(column_rule_width_text);
}
if (const auto* ident_value =
DynamicTo<CSSIdentifierValue>(column_rule_style);
!(ident_value && ident_value->GetValueID() == CSSValueID::kNone) &&
!column_rule_style->IsInitialValue()) {
String column_rule_style_text = column_rule_style->CssText();
if (!result.empty()) {
result.Append(" ");
}
result.Append(column_rule_style_text);
}
if (const auto* ident_value =
DynamicTo<CSSIdentifierValue>(column_rule_color);
!(ident_value &&
ident_value->GetValueID() == CSSValueID::kCurrentcolor) &&
!column_rule_color->IsInitialValue()) {
String column_rule_color_text = column_rule_color->CssText();
if (!result.empty()) {
result.Append(" ");
}
result.Append(column_rule_color_text);
}
if (result.empty()) {
return "medium";
}
return result.ReleaseString();
}
String StylePropertySerializer::GetShorthandValueForColumns(
const StylePropertyShorthand& shorthand) const {
DCHECK_EQ(shorthand.length(), 2u);
StringBuilder result;
for (unsigned i = 0; i < shorthand.length(); ++i) {
const CSSValue* value =
property_set_.GetPropertyCSSValue(*shorthand.properties()[i]);
String value_text = value->CssText();
if (const auto* ident_value = DynamicTo<CSSIdentifierValue>(value);
ident_value && ident_value->GetValueID() == CSSValueID::kAuto) {
continue;
}
if (!result.empty()) {
result.Append(" ");
}
result.Append(value_text);
}
if (result.empty()) {
return "auto";
}
return result.ReleaseString();
}
String StylePropertySerializer::GetShorthandValueForDoubleBarCombinator(
const StylePropertyShorthand& shorthand) const {
StringBuilder result;
for (unsigned i = 0; i < shorthand.length(); ++i) {
const Longhand* longhand = To<Longhand>(shorthand.properties()[i]);
DCHECK(!longhand->InitialValue()->IsInitialValue())
<< "Without InitialValue() implemented, 'initial' will show up in the "
"serialization below.";
const CSSValue* value = property_set_.GetPropertyCSSValue(*longhand);
if (*value == *longhand->InitialValue()) {
continue;
}
String value_text = value->CssText();
if (!result.empty()) {
result.Append(" ");
}
result.Append(value_text);
}
if (result.empty()) {
return To<Longhand>(shorthand.properties()[0])->InitialValue()->CssText();
}
return result.ReleaseString();
}
String StylePropertySerializer::GetShorthandValueForGrid(
const StylePropertyShorthand& shorthand) const {
DCHECK_EQ(shorthand.length(), 6u);
const auto* template_row_values =
property_set_.GetPropertyCSSValue(*shorthand.properties()[0]);
const auto* template_column_values =
property_set_.GetPropertyCSSValue(*shorthand.properties()[1]);
const auto* template_area_value =
property_set_.GetPropertyCSSValue(*shorthand.properties()[2]);
const auto* auto_flow_values =
property_set_.GetPropertyCSSValue(*shorthand.properties()[3]);
const auto* auto_row_values =
property_set_.GetPropertyCSSValue(*shorthand.properties()[4]);
const auto* auto_column_values =
property_set_.GetPropertyCSSValue(*shorthand.properties()[5]);
// `auto-flow`, `grid-auto-rows`, and `grid-auto-columns` are parsed as either
// an identifier with the default value, or a CSSValueList containing a single
// entry with the default value. Unlike `grid-template-rows` and
// `grid-template-columns`, we *can* determine if the author specified them by
// the presence of an associated CSSValueList.
auto HasInitialValueListValue = [](const CSSValueList* value_list,
auto* definition) -> bool {
return value_list && value_list->length() == 1 &&
value_list->First() == *(To<Longhand>(definition()).InitialValue());
};
auto HasInitialIdentifierValue = [](const CSSValue* value,
CSSValueID initial_value) -> bool {
return IsA<CSSIdentifierValue>(value) &&
To<CSSIdentifierValue>(value)->GetValueID() == initial_value;
};
const auto* auto_row_value_list = DynamicTo<CSSValueList>(auto_row_values);
const bool is_auto_rows_initial_value =
HasInitialValueListValue(auto_row_value_list,
GetCSSPropertyGridAutoRows) ||
HasInitialIdentifierValue(auto_row_values, CSSValueID::kAuto);
const bool specified_non_initial_auto_rows =
auto_row_value_list && !is_auto_rows_initial_value;
const auto* auto_column_value_list =
DynamicTo<CSSValueList>(auto_column_values);
const bool is_auto_columns_initial_value =
HasInitialValueListValue(auto_column_value_list,
GetCSSPropertyGridAutoColumns) ||
HasInitialIdentifierValue(auto_column_values, CSSValueID::kAuto);
const bool specified_non_initial_auto_columns =
auto_column_value_list && !is_auto_columns_initial_value;
const auto* auto_flow_value_list = DynamicTo<CSSValueList>(auto_flow_values);
const bool is_auto_flow_initial_value =
HasInitialValueListValue(auto_flow_value_list,
GetCSSPropertyGridAutoFlow) ||
HasInitialIdentifierValue(auto_flow_values, CSSValueID::kRow);
// `grid-auto-*` along with named lines is not valid per the grammar.
if ((auto_flow_value_list || auto_row_value_list || auto_column_value_list) &&
*template_area_value !=
*(To<Longhand>(GetCSSPropertyGridTemplateAreas()).InitialValue())) {
return String();
}
// `grid-template-rows` and `grid-template-columns` are shorthards within this
// shorthand. Based on how parsing works, we can't differentiate between an
// author specifying `none` and uninitialized.
const bool non_initial_template_rows =
(*template_row_values !=
*(To<Longhand>(GetCSSPropertyGridTemplateRows()).InitialValue()));
const bool non_initial_template_columns =
*template_column_values !=
*(To<Longhand>(GetCSSPropertyGridTemplateColumns()).InitialValue());
// `grid-template-*` and `grid-auto-*` are mutually exclusive per direction.
if ((non_initial_template_rows && specified_non_initial_auto_rows) ||
(non_initial_template_columns && specified_non_initial_auto_columns) ||
(specified_non_initial_auto_rows && specified_non_initial_auto_columns)) {
return String();
}
// 1- <'grid-template'>
// If the author didn't specify `auto-flow`, we should go down the
// `grid-template` path. This should also round-trip if the author specified
// the initial value for `auto-flow`, unless `auto-columns` or `auto-rows`
// were also set, causing it to match the shorthand syntax below.
if (!auto_flow_value_list ||
(is_auto_flow_initial_value && !(specified_non_initial_auto_columns ||
specified_non_initial_auto_rows))) {
return GetShorthandValueForGridTemplate(shorthand);
} else if (non_initial_template_rows && non_initial_template_columns) {
// Specifying both rows and columns is not valid per the grammar.
return String();
}
// At this point, the syntax matches:
// <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>? |
// [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'>
// ...and thus will include `auto-flow` no matter what.
StringBuilder auto_flow_text;
auto_flow_text.Append("auto-flow");
if (auto_flow_value_list &&
auto_flow_value_list->HasValue(
*CSSIdentifierValue::Create(CSSValueID::kDense))) {
auto_flow_text.Append(" dense");
}
// 2- <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>?
// We can't distinguish between `grid-template-rows` being unspecified or
// being specified as `none` (see the comment near the definition of
// `non_initial_template_rows`), as both are initial values. So we must
// distinguish between the remaining two possible paths via `auto-flow`.
StringBuilder result;
if (auto_flow_value_list &&
auto_flow_value_list->HasValue(
*CSSIdentifierValue::Create(CSSValueID::kColumn))) {
result.Append(template_row_values->CssText());
result.Append(" / ");
result.Append(auto_flow_text);
if (specified_non_initial_auto_columns) {
result.Append(" ");
result.Append(auto_column_values->CssText());
}
} else {
// 3- [ auto-flow && dense? ] <'grid-auto-rows'>? /
// <'grid-template-columns'>
result.Append(auto_flow_text);
if (specified_non_initial_auto_rows) {
result.Append(" ");
result.Append(auto_row_values->CssText());
}
result.Append(" / ");
result.Append(template_column_values->CssText());
}
return result.ReleaseString();
}
String StylePropertySerializer::GetShorthandValueForGridArea(
const StylePropertyShorthand& shorthand) const {
const String separator = " / ";
DCHECK_EQ(shorthand.length(), 4u);
const CSSValue* grid_row_start =
property_set_.GetPropertyCSSValue(*shorthand.properties()[0]);
const CSSValue* grid_column_start =
property_set_.GetPropertyCSSValue(*shorthand.properties()[1]);
const CSSValue* grid_row_end =
property_set_.GetPropertyCSSValue(*shorthand.properties()[2]);
const CSSValue* grid_column_end =
property_set_.GetPropertyCSSValue(*shorthand.properties()[3]);
// `grid-row-end` depends on `grid-row-start`, and `grid-column-end` depends
// on on `grid-column-start`, but what's not consistent is that
// `grid-column-start` has a dependency on `grid-row-start`. For more details,
// see https://www.w3.org/TR/css-grid-2/#placement-shorthands
const bool include_column_start =
CSSOMUtils::IncludeDependentGridLineEndValue(grid_row_start,
grid_column_start);
const bool include_row_end = CSSOMUtils::IncludeDependentGridLineEndValue(
grid_row_start, grid_row_end);
const bool include_column_end = CSSOMUtils::IncludeDependentGridLineEndValue(
grid_column_start, grid_column_end);
StringBuilder result;
// `grid-row-start` is always included.
result.Append(grid_row_start->CssText());
// If `IncludeDependentGridLineEndValue` returns true for a property,
// all preceding values must be included.
if (include_column_start || include_row_end || include_column_end) {
result.Append(separator);
result.Append(grid_column_start->CssText());
}
if (include_row_end || include_column_end) {
result.Append(separator);
result.Append(grid_row_end->CssText());
}
if (include_column_end) {
result.Append(separator);
result.Append(grid_column_end->CssText());
}
return result.ReleaseString();
}
String StylePropertySerializer::GetShorthandValueForGridLine(
const StylePropertyShorthand& shorthand) const {
const String separator = " / ";
DCHECK_EQ(shorthand.length(), 2u);
const CSSValue* line_start =
property_set_.GetPropertyCSSValue(*shorthand.properties()[0]);
const CSSValue* line_end =
property_set_.GetPropertyCSSValue(*shorthand.properties()[1]);
StringBuilder result;
// `grid-line-start` is always included.
result.Append(line_start->CssText());
if (CSSOMUtils::IncludeDependentGridLineEndValue(line_start, line_end)) {
result.Append(separator);
result.Append(line_end->CssText());
}
return result.ReleaseString();
}
String StylePropertySerializer::GetShorthandValueForGridTemplate(
const StylePropertyShorthand& shorthand) const {
const CSSValue* template_row_values =
property_set_.GetPropertyCSSValue(*shorthand.properties()[0]);
const CSSValue* template_column_values =
property_set_.GetPropertyCSSValue(*shorthand.properties()[1]);
const CSSValue* template_area_values =
property_set_.GetPropertyCSSValue(*shorthand.properties()[2]);
const CSSValueList* grid_template_list =
CSSOMUtils::ComputedValueForGridTemplateShorthand(
template_row_values, template_column_values, template_area_values);
return grid_template_list->CssText();
}
// only returns a non-null value if all properties have the same, non-null value
String StylePropertySerializer::GetCommonValue(
const StylePropertyShorthand& shorthand) const {
String res;
for (unsigned i = 0; i < shorthand.length(); ++i) {
const CSSValue* value =
property_set_.GetPropertyCSSValue(*shorthand.properties()[i]);
// FIXME: CSSInitialValue::CssText should generate the right value.
String text = value->CssText();
if (res.IsNull()) {
res = text;
} else if (res != text) {
return String();
}
}
return res;
}
String StylePropertySerializer::BorderPropertyValue(
const StylePropertyShorthand& width,
const StylePropertyShorthand& style,
const StylePropertyShorthand& color) const {
const CSSProperty* border_image_properties[] = {
&GetCSSPropertyBorderImageSource(), &GetCSSPropertyBorderImageSlice(),
&GetCSSPropertyBorderImageWidth(), &GetCSSPropertyBorderImageOutset(),
&GetCSSPropertyBorderImageRepeat()};
// If any of the border-image longhands differ from their initial
// specified values, we should not serialize to a border shorthand
// declaration.
for (const auto* border_image_property : border_image_properties) {
const CSSValue* value =
property_set_.GetPropertyCSSValue(*border_image_property);
const CSSValue* initial_specified_value =
To<Longhand>(*border_image_property).InitialValue();
if (value && !value->IsInitialValue() &&
*value != *initial_specified_value) {
return String();
}
}
const StylePropertyShorthand shorthand_properties[3] = {width, style, color};
StringBuilder result;
for (const auto& shorthand_property : shorthand_properties) {
const String value = GetCommonValue(shorthand_property);
if (value.IsNull()) {
return String();
}
if (value == "initial") {
continue;
}
if (!result.empty()) {
result.Append(' ');
}
result.Append(value);
}
return result.empty() ? String() : result.ReleaseString();
}
String StylePropertySerializer::BorderImagePropertyValue() const {
StringBuilder result;
const CSSProperty* properties[] = {
&GetCSSPropertyBorderImageSource(), &GetCSSPropertyBorderImageSlice(),
&GetCSSPropertyBorderImageWidth(), &GetCSSPropertyBorderImageOutset(),
&GetCSSPropertyBorderImageRepeat()};
size_t length = std::size(properties);
for (size_t i = 0; i < length; ++i) {
const CSSValue& value = *property_set_.GetPropertyCSSValue(*properties[i]);
if (!result.empty()) {
result.Append(" ");
}
if (i == 2 || i == 3) {
result.Append("/ ");
}
result.Append(value.CssText());
}
return result.ReleaseString();
}
String StylePropertySerializer::BorderRadiusValue() const {
auto serialize = [](const CSSValue& top_left, const CSSValue& top_right,
const CSSValue& bottom_right,
const CSSValue& bottom_left) -> String {
bool show_bottom_left = !(top_right == bottom_left);
bool show_bottom_right = !(top_left == bottom_right) || show_bottom_left;
bool show_top_right = !(top_left == top_right) || show_bottom_right;
StringBuilder result;
result.Append(top_left.CssText());
if (show_top_right) {
result.Append(' ');
result.Append(top_right.CssText());
}
if (show_bottom_right) {
result.Append(' ');
result.Append(bottom_right.CssText());
}
if (show_bottom_left) {
result.Append(' ');
result.Append(bottom_left.CssText());
}
return result.ReleaseString();
};
const CSSValuePair& top_left = To<CSSValuePair>(
*property_set_.GetPropertyCSSValue(GetCSSPropertyBorderTopLeftRadius()));
const CSSValuePair& top_right = To<CSSValuePair>(
*property_set_.GetPropertyCSSValue(GetCSSPropertyBorderTopRightRadius()));
const CSSValuePair& bottom_right =
To<CSSValuePair>(*property_set_.GetPropertyCSSValue(
GetCSSPropertyBorderBottomRightRadius()));
const CSSValuePair& bottom_left =
To<CSSValuePair>(*property_set_.GetPropertyCSSValue(
GetCSSPropertyBorderBottomLeftRadius()));
StringBuilder builder;
builder.Append(serialize(top_left.First(), top_right.First(),
bottom_right.First(), bottom_left.First()));
if (!(top_left.First() == top_left.Second()) ||
!(top_right.First() == top_right.Second()) ||
!(bottom_right.First() == bottom_right.Second()) ||
!(bottom_left.First() == bottom_left.Second())) {
builder.Append(" / ");
builder.Append(serialize(top_left.Second(), top_right.Second(),
bottom_right.Second(), bottom_left.Second()));
}
return builder.ReleaseString();
}
String StylePropertySerializer::PageBreakPropertyValue(
const StylePropertyShorthand& shorthand) const {
const CSSValue* value =
property_set_.GetPropertyCSSValue(*shorthand.properties()[0]);
CSSValueID value_id = To<CSSIdentifierValue>(value)->GetValueID();
// https://drafts.csswg.org/css-break/#page-break-properties
if (value_id == CSSValueID::kPage) {
return "always";
}
if (value_id == CSSValueID::kAuto || value_id == CSSValueID::kLeft ||
value_id == CSSValueID::kRight || value_id == CSSValueID::kAvoid) {
return value->CssText();
}
return String();
}
String StylePropertySerializer::ContainIntrinsicSizeValue() const {
// If the two values are identical, we return just one.
String res = GetCommonValue(containIntrinsicSizeShorthand());
if (!res.IsNull()) {
return res;
}
// Otherwise just serialize them in sequence.
return GetShorthandValue(containIntrinsicSizeShorthand());
}
String StylePropertySerializer::TextSpacingValue() const {
const auto* autospace_value = DynamicTo<CSSIdentifierValue>(
property_set_.GetPropertyCSSValue(GetCSSPropertyTextAutospace()));
DCHECK(autospace_value);
const auto* spacing_trim_value = DynamicTo<CSSIdentifierValue>(
property_set_.GetPropertyCSSValue(GetCSSPropertyTextSpacingTrim()));
DCHECK(spacing_trim_value);
// Check if longhands are one of pre-defined keywords.
const CSSValueID autospace_id = autospace_value->GetValueID();
const CSSValueID spacing_trim_id = spacing_trim_value->GetValueID();
if (autospace_id == CSSValueID::kNormal &&
spacing_trim_id == CSSValueID::kNormal) {
return getValueName(CSSValueID::kNormal);
}
if (autospace_id == CSSValueID::kNoAutospace &&
spacing_trim_id == CSSValueID::kSpaceAll) {
return getValueName(CSSValueID::kNone);
}
// Otherwise build a multi-value list.
StringBuilder result;
if (spacing_trim_id != CSSValueID::kNormal) {
result.Append(getValueName(spacing_trim_id));
}
if (autospace_id != CSSValueID::kNormal) {
if (!result.empty()) {
result.Append(kSpaceCharacter);
}
result.Append(getValueName(autospace_id));
}
// When all longhands are initial values, it should be `normal`.
DCHECK(!result.empty());
return result.ToString();
}
String StylePropertySerializer::WhiteSpaceValue() const {
const CSSValue* collapse_value =
property_set_.GetPropertyCSSValue(GetCSSPropertyWhiteSpaceCollapse());
const CSSValue* wrap_value =
property_set_.GetPropertyCSSValue(GetCSSPropertyTextWrap());
if (!collapse_value || !wrap_value) {
// If any longhands are missing, don't serialize as a shorthand.
return g_empty_string;
}
// Check if longhands are one of pre-defined keywords of `white-space`.
const WhiteSpaceCollapse collapse = ToWhiteSpaceCollapse(collapse_value);
const TextWrap wrap = ToTextWrap(wrap_value);
const EWhiteSpace whitespace = ToWhiteSpace(collapse, wrap);
if (IsValidWhiteSpace(whitespace)) {
return getValueName(PlatformEnumToCSSValueID(whitespace));
}
// Otherwise build a multi-value list.
StringBuilder result;
if (collapse != ComputedStyleInitialValues::InitialWhiteSpaceCollapse()) {
result.Append(getValueName(PlatformEnumToCSSValueID(collapse)));
}
if (wrap != ComputedStyleInitialValues::InitialTextWrap()) {
if (!result.empty()) {
result.Append(kSpaceCharacter);
}
result.Append(getValueName(PlatformEnumToCSSValueID(wrap)));
}
// When all longhands are initial values, it should be `normal`, covered by
// `IsValidWhiteSpace()` above.
DCHECK(!result.empty());
return result.ToString();
}
String StylePropertySerializer::ScrollStartValue() const {
CHECK_EQ(scrollStartShorthand().length(), 2u);
CHECK_EQ(scrollStartShorthand().properties()[0],
&GetCSSPropertyScrollStartBlock());
CHECK_EQ(scrollStartShorthand().properties()[1],
&GetCSSPropertyScrollStartInline());
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
const CSSValue* block_value =
property_set_.GetPropertyCSSValue(GetCSSPropertyScrollStartBlock());
const CSSValue* inline_value =
property_set_.GetPropertyCSSValue(GetCSSPropertyScrollStartInline());
DCHECK(block_value);
DCHECK(inline_value);
list->Append(*block_value);
if (const auto* ident_value = DynamicTo<CSSIdentifierValue>(inline_value);
!ident_value || ident_value->GetValueID() != CSSValueID::kStart) {
list->Append(*inline_value);
}
return list->CssText();
}
String StylePropertySerializer::ScrollStartTargetValue() const {
CHECK_EQ(scrollStartTargetShorthand().length(), 2u);
CHECK_EQ(scrollStartTargetShorthand().properties()[0],
&GetCSSPropertyScrollStartTargetBlock());
CHECK_EQ(scrollStartTargetShorthand().properties()[1],
&GetCSSPropertyScrollStartTargetInline());
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
const CSSValue* block_value =
property_set_.GetPropertyCSSValue(GetCSSPropertyScrollStartTargetBlock());
const CSSValue* inline_value = property_set_.GetPropertyCSSValue(
GetCSSPropertyScrollStartTargetInline());
DCHECK(block_value);
DCHECK(inline_value);
list->Append(*block_value);
if (To<CSSIdentifierValue>(*inline_value).GetValueID() != CSSValueID::kNone) {
list->Append(*inline_value);
}
return list->CssText();
}
String StylePropertySerializer::PositionTryValue() const {
CHECK_EQ(positionTryShorthand().length(), 2u);
CHECK_EQ(positionTryShorthand().properties()[0],
&GetCSSPropertyPositionTryOrder());
CHECK_EQ(positionTryShorthand().properties()[1],
&GetCSSPropertyPositionTryOptions());
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
const CSSValue* order_value =
property_set_.GetPropertyCSSValue(GetCSSPropertyPositionTryOrder());
const CSSValue* options_value =
property_set_.GetPropertyCSSValue(GetCSSPropertyPositionTryOptions());
CHECK(order_value);
CHECK(options_value);
if (To<CSSIdentifierValue>(*order_value).GetValueID() !=
CSSValueID::kNormal) {
list->Append(*order_value);
}
list->Append(*options_value);
return list->CssText();
}
} // namespace blink