diff -r 000000000000 -r 6474c204b198 layout/style/Declaration.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layout/style/Declaration.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,1541 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * representation of a declaration block (or style attribute) in a CSS + * stylesheet + */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/MemoryReporting.h" + +#include "mozilla/css/Declaration.h" +#include "nsPrintfCString.h" +#include "gfxFontConstants.h" +#include "nsStyleUtil.h" + +namespace mozilla { +namespace css { + +Declaration::Declaration() + : mImmutable(false) +{ + MOZ_COUNT_CTOR(mozilla::css::Declaration); +} + +Declaration::Declaration(const Declaration& aCopy) + : mOrder(aCopy.mOrder), + mVariableOrder(aCopy.mVariableOrder), + mData(aCopy.mData ? aCopy.mData->Clone() : nullptr), + mImportantData(aCopy.mImportantData ? + aCopy.mImportantData->Clone() : nullptr), + mVariables(aCopy.mVariables ? + new CSSVariableDeclarations(*aCopy.mVariables) : + nullptr), + mImportantVariables(aCopy.mImportantVariables ? + new CSSVariableDeclarations(*aCopy.mImportantVariables) : + nullptr), + mImmutable(false) +{ + MOZ_COUNT_CTOR(mozilla::css::Declaration); +} + +Declaration::~Declaration() +{ + MOZ_COUNT_DTOR(mozilla::css::Declaration); +} + +void +Declaration::ValueAppended(nsCSSProperty aProperty) +{ + NS_ABORT_IF_FALSE(!mData && !mImportantData, + "should only be called while expanded"); + NS_ABORT_IF_FALSE(!nsCSSProps::IsShorthand(aProperty), + "shorthands forbidden"); + // order IS important for CSS, so remove and add to the end + mOrder.RemoveElement(static_cast(aProperty)); + mOrder.AppendElement(static_cast(aProperty)); +} + +void +Declaration::RemoveProperty(nsCSSProperty aProperty) +{ + MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT); + + nsCSSExpandedDataBlock data; + ExpandTo(&data); + NS_ABORT_IF_FALSE(!mData && !mImportantData, "Expand didn't null things out"); + + if (nsCSSProps::IsShorthand(aProperty)) { + CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aProperty) { + data.ClearLonghandProperty(*p); + mOrder.RemoveElement(static_cast(*p)); + } + } else { + data.ClearLonghandProperty(aProperty); + mOrder.RemoveElement(static_cast(aProperty)); + } + + CompressFrom(&data); +} + +bool +Declaration::HasProperty(nsCSSProperty aProperty) const +{ + NS_ABORT_IF_FALSE(0 <= aProperty && + aProperty < eCSSProperty_COUNT_no_shorthands, + "property ID out of range"); + + nsCSSCompressedDataBlock *data = GetValueIsImportant(aProperty) + ? mImportantData : mData; + const nsCSSValue *val = data->ValueFor(aProperty); + return !!val; +} + +bool +Declaration::AppendValueToString(nsCSSProperty aProperty, + nsAString& aResult, + nsCSSValue::Serialization aSerialization) const +{ + NS_ABORT_IF_FALSE(0 <= aProperty && + aProperty < eCSSProperty_COUNT_no_shorthands, + "property ID out of range"); + + nsCSSCompressedDataBlock *data = GetValueIsImportant(aProperty) + ? mImportantData : mData; + const nsCSSValue *val = data->ValueFor(aProperty); + if (!val) { + return false; + } + + val->AppendToString(aProperty, aResult, aSerialization); + return true; +} + +// Helper to append |aString| with the shorthand sides notation used in e.g. +// 'padding'. |aProperties| and |aValues| are expected to have 4 elements. +static void +AppendSidesShorthandToString(const nsCSSProperty aProperties[], + const nsCSSValue* aValues[], + nsAString& aString, + nsCSSValue::Serialization aSerialization) +{ + const nsCSSValue& value1 = *aValues[0]; + const nsCSSValue& value2 = *aValues[1]; + const nsCSSValue& value3 = *aValues[2]; + const nsCSSValue& value4 = *aValues[3]; + + NS_ABORT_IF_FALSE(value1.GetUnit() != eCSSUnit_Null, "null value 1"); + value1.AppendToString(aProperties[0], aString, aSerialization); + if (value1 != value2 || value1 != value3 || value1 != value4) { + aString.Append(char16_t(' ')); + NS_ABORT_IF_FALSE(value2.GetUnit() != eCSSUnit_Null, "null value 2"); + value2.AppendToString(aProperties[1], aString, aSerialization); + if (value1 != value3 || value2 != value4) { + aString.Append(char16_t(' ')); + NS_ABORT_IF_FALSE(value3.GetUnit() != eCSSUnit_Null, "null value 3"); + value3.AppendToString(aProperties[2], aString, aSerialization); + if (value2 != value4) { + aString.Append(char16_t(' ')); + NS_ABORT_IF_FALSE(value4.GetUnit() != eCSSUnit_Null, "null value 4"); + value4.AppendToString(aProperties[3], aString, aSerialization); + } + } + } +} + +void +Declaration::GetValue(nsCSSProperty aProperty, nsAString& aValue) const +{ + GetValue(aProperty, aValue, nsCSSValue::eNormalized); +} + +void +Declaration::GetAuthoredValue(nsCSSProperty aProperty, nsAString& aValue) const +{ + GetValue(aProperty, aValue, nsCSSValue::eAuthorSpecified); +} + +void +Declaration::GetValue(nsCSSProperty aProperty, nsAString& aValue, + nsCSSValue::Serialization aSerialization) const +{ + aValue.Truncate(0); + + // simple properties are easy. + if (!nsCSSProps::IsShorthand(aProperty)) { + AppendValueToString(aProperty, aValue, aSerialization); + return; + } + + // DOM Level 2 Style says (when describing CSS2Properties, although + // not CSSStyleDeclaration.getPropertyValue): + // However, if there is no shorthand declaration that could be added + // to the ruleset without changing in any way the rules already + // declared in the ruleset (i.e., by adding longhand rules that were + // previously not declared in the ruleset), then the empty string + // should be returned for the shorthand property. + // This means we need to check a number of cases: + // (1) Since a shorthand sets all sub-properties, if some of its + // subproperties were not specified, we must return the empty + // string. + // (2) Since 'inherit', 'initial' and 'unset' can only be specified + // as the values for entire properties, we need to return the + // empty string if some but not all of the subproperties have one + // of those values. + // (3) Since a single value only makes sense with or without + // !important, we return the empty string if some values are + // !important and some are not. + // Since we're doing this check for 'inherit' and 'initial' up front, + // we can also simplify the property serialization code by serializing + // those values up front as well. + // + // Additionally, if a shorthand property was set using a value with a + // variable reference and none of its component longhand properties were + // then overridden on the declaration, we return the token stream + // assigned to the shorthand. + const nsCSSValue* tokenStream = nullptr; + uint32_t totalCount = 0, importantCount = 0, + initialCount = 0, inheritCount = 0, unsetCount = 0, + matchingTokenStreamCount = 0; + CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aProperty) { + if (*p == eCSSProperty__x_system_font || + nsCSSProps::PropHasFlags(*p, CSS_PROPERTY_DIRECTIONAL_SOURCE)) { + // The system-font subproperty and the *-source properties don't count. + continue; + } + ++totalCount; + const nsCSSValue *val = mData->ValueFor(*p); + NS_ABORT_IF_FALSE(!val || !mImportantData || !mImportantData->ValueFor(*p), + "can't be in both blocks"); + if (!val && mImportantData) { + ++importantCount; + val = mImportantData->ValueFor(*p); + } + if (!val) { + // Case (1) above: some subproperties not specified. + return; + } + if (val->GetUnit() == eCSSUnit_Inherit) { + ++inheritCount; + } else if (val->GetUnit() == eCSSUnit_Initial) { + ++initialCount; + } else if (val->GetUnit() == eCSSUnit_Unset) { + ++unsetCount; + } else if (val->GetUnit() == eCSSUnit_TokenStream && + val->GetTokenStreamValue()->mShorthandPropertyID == aProperty) { + tokenStream = val; + ++matchingTokenStreamCount; + } + } + if (importantCount != 0 && importantCount != totalCount) { + // Case (3), no consistent importance. + return; + } + if (initialCount == totalCount) { + // Simplify serialization below by serializing initial up-front. + nsCSSValue(eCSSUnit_Initial).AppendToString(eCSSProperty_UNKNOWN, aValue, + nsCSSValue::eNormalized); + return; + } + if (inheritCount == totalCount) { + // Simplify serialization below by serializing inherit up-front. + nsCSSValue(eCSSUnit_Inherit).AppendToString(eCSSProperty_UNKNOWN, aValue, + nsCSSValue::eNormalized); + return; + } + if (unsetCount == totalCount) { + // Simplify serialization below by serializing unset up-front. + nsCSSValue(eCSSUnit_Unset).AppendToString(eCSSProperty_UNKNOWN, aValue, + nsCSSValue::eNormalized); + return; + } + if (initialCount != 0 || inheritCount != 0 || unsetCount != 0) { + // Case (2): partially initial, inherit or unset. + return; + } + if (tokenStream) { + if (matchingTokenStreamCount == totalCount) { + // Shorthand was specified using variable references and all of its + // longhand components were set by the shorthand. + aValue.Append(tokenStream->GetTokenStreamValue()->mTokenStream); + } else { + // In all other cases, serialize to the empty string. + } + return; + } + + nsCSSCompressedDataBlock *data = importantCount ? mImportantData : mData; + switch (aProperty) { + case eCSSProperty_margin: + case eCSSProperty_padding: + case eCSSProperty_border_color: + case eCSSProperty_border_style: + case eCSSProperty_border_width: { + const nsCSSProperty* subprops = + nsCSSProps::SubpropertyEntryFor(aProperty); + NS_ABORT_IF_FALSE(nsCSSProps::GetStringValue(subprops[0]).Find("-top") != + kNotFound, "first subprop must be top"); + NS_ABORT_IF_FALSE(nsCSSProps::GetStringValue(subprops[1]).Find("-right") != + kNotFound, "second subprop must be right"); + NS_ABORT_IF_FALSE(nsCSSProps::GetStringValue(subprops[2]).Find("-bottom") != + kNotFound, "third subprop must be bottom"); + NS_ABORT_IF_FALSE(nsCSSProps::GetStringValue(subprops[3]).Find("-left") != + kNotFound, "fourth subprop must be left"); + const nsCSSValue* vals[4] = { + data->ValueFor(subprops[0]), + data->ValueFor(subprops[1]), + data->ValueFor(subprops[2]), + data->ValueFor(subprops[3]) + }; + AppendSidesShorthandToString(subprops, vals, aValue, aSerialization); + break; + } + case eCSSProperty_border_radius: + case eCSSProperty__moz_outline_radius: { + const nsCSSProperty* subprops = + nsCSSProps::SubpropertyEntryFor(aProperty); + const nsCSSValue* vals[4] = { + data->ValueFor(subprops[0]), + data->ValueFor(subprops[1]), + data->ValueFor(subprops[2]), + data->ValueFor(subprops[3]) + }; + + // For compatibility, only write a slash and the y-values + // if they're not identical to the x-values. + bool needY = false; + const nsCSSValue* xVals[4]; + const nsCSSValue* yVals[4]; + for (int i = 0; i < 4; i++) { + if (vals[i]->GetUnit() == eCSSUnit_Pair) { + needY = true; + xVals[i] = &vals[i]->GetPairValue().mXValue; + yVals[i] = &vals[i]->GetPairValue().mYValue; + } else { + xVals[i] = yVals[i] = vals[i]; + } + } + + AppendSidesShorthandToString(subprops, xVals, aValue, aSerialization); + if (needY) { + aValue.AppendLiteral(" / "); + AppendSidesShorthandToString(subprops, yVals, aValue, aSerialization); + } + break; + } + case eCSSProperty_border_image: { + // Even though there are some cases where we could omit + // 'border-image-source' (when it's none), it's probably not a + // good idea since it's likely to be confusing. It would also + // require adding the extra check that we serialize *something*. + AppendValueToString(eCSSProperty_border_image_source, aValue, + aSerialization); + + bool sliceDefault = data->HasDefaultBorderImageSlice(); + bool widthDefault = data->HasDefaultBorderImageWidth(); + bool outsetDefault = data->HasDefaultBorderImageOutset(); + + if (!sliceDefault || !widthDefault || !outsetDefault) { + aValue.Append(char16_t(' ')); + AppendValueToString(eCSSProperty_border_image_slice, aValue, + aSerialization); + if (!widthDefault || !outsetDefault) { + aValue.Append(NS_LITERAL_STRING(" /")); + if (!widthDefault) { + aValue.Append(char16_t(' ')); + AppendValueToString(eCSSProperty_border_image_width, aValue, + aSerialization); + } + if (!outsetDefault) { + aValue.Append(NS_LITERAL_STRING(" / ")); + AppendValueToString(eCSSProperty_border_image_outset, aValue, + aSerialization); + } + } + } + + bool repeatDefault = data->HasDefaultBorderImageRepeat(); + if (!repeatDefault) { + aValue.Append(char16_t(' ')); + AppendValueToString(eCSSProperty_border_image_repeat, aValue, + aSerialization); + } + break; + } + case eCSSProperty_border: { + // If we have a non-default value for any of the properties that + // this shorthand sets but cannot specify, we have to return the + // empty string. + if (data->ValueFor(eCSSProperty_border_image_source)->GetUnit() != + eCSSUnit_None || + !data->HasDefaultBorderImageSlice() || + !data->HasDefaultBorderImageWidth() || + !data->HasDefaultBorderImageOutset() || + !data->HasDefaultBorderImageRepeat() || + data->ValueFor(eCSSProperty_border_top_colors)->GetUnit() != + eCSSUnit_None || + data->ValueFor(eCSSProperty_border_right_colors)->GetUnit() != + eCSSUnit_None || + data->ValueFor(eCSSProperty_border_bottom_colors)->GetUnit() != + eCSSUnit_None || + data->ValueFor(eCSSProperty_border_left_colors)->GetUnit() != + eCSSUnit_None) { + break; + } + + const nsCSSProperty* subproptables[3] = { + nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_color), + nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_style), + nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_width) + }; + bool match = true; + for (const nsCSSProperty** subprops = subproptables, + **subprops_end = ArrayEnd(subproptables); + subprops < subprops_end; ++subprops) { + // Check only the first four subprops in each table, since the + // others are extras for dimensional box properties. + const nsCSSValue *firstSide = data->ValueFor((*subprops)[0]); + for (int32_t side = 1; side < 4; ++side) { + const nsCSSValue *otherSide = + data->ValueFor((*subprops)[side]); + if (*firstSide != *otherSide) + match = false; + } + } + if (!match) { + // We can't express what we have in the border shorthand + break; + } + // tweak aProperty and fall through + aProperty = eCSSProperty_border_top; + } + case eCSSProperty_border_top: + case eCSSProperty_border_right: + case eCSSProperty_border_bottom: + case eCSSProperty_border_left: + case eCSSProperty_border_start: + case eCSSProperty_border_end: + case eCSSProperty__moz_column_rule: + case eCSSProperty_outline: { + const nsCSSProperty* subprops = + nsCSSProps::SubpropertyEntryFor(aProperty); + NS_ABORT_IF_FALSE(StringEndsWith(nsCSSProps::GetStringValue(subprops[2]), + NS_LITERAL_CSTRING("-color")) || + StringEndsWith(nsCSSProps::GetStringValue(subprops[2]), + NS_LITERAL_CSTRING("-color-value")), + "third subprop must be the color property"); + const nsCSSValue *colorValue = data->ValueFor(subprops[2]); + bool isMozUseTextColor = + colorValue->GetUnit() == eCSSUnit_Enumerated && + colorValue->GetIntValue() == NS_STYLE_COLOR_MOZ_USE_TEXT_COLOR; + if (!AppendValueToString(subprops[0], aValue, aSerialization) || + !(aValue.Append(char16_t(' ')), + AppendValueToString(subprops[1], aValue, aSerialization)) || + // Don't output a third value when it's -moz-use-text-color. + !(isMozUseTextColor || + (aValue.Append(char16_t(' ')), + AppendValueToString(subprops[2], aValue, aSerialization)))) { + aValue.Truncate(); + } + break; + } + case eCSSProperty_margin_left: + case eCSSProperty_margin_right: + case eCSSProperty_margin_start: + case eCSSProperty_margin_end: + case eCSSProperty_padding_left: + case eCSSProperty_padding_right: + case eCSSProperty_padding_start: + case eCSSProperty_padding_end: + case eCSSProperty_border_left_color: + case eCSSProperty_border_left_style: + case eCSSProperty_border_left_width: + case eCSSProperty_border_right_color: + case eCSSProperty_border_right_style: + case eCSSProperty_border_right_width: + case eCSSProperty_border_start_color: + case eCSSProperty_border_start_style: + case eCSSProperty_border_start_width: + case eCSSProperty_border_end_color: + case eCSSProperty_border_end_style: + case eCSSProperty_border_end_width: { + const nsCSSProperty* subprops = + nsCSSProps::SubpropertyEntryFor(aProperty); + NS_ABORT_IF_FALSE(subprops[3] == eCSSProperty_UNKNOWN, + "not box property with physical vs. logical cascading"); + AppendValueToString(subprops[0], aValue, aSerialization); + break; + } + case eCSSProperty_background: { + // We know from above that all subproperties were specified. + // However, we still can't represent that in the shorthand unless + // they're all lists of the same length. So if they're different + // lengths, we need to bail out. + // We also need to bail out if an item has background-clip and + // background-origin that are different and not the default + // values. (We omit them if they're both default.) + const nsCSSValueList *image = + data->ValueFor(eCSSProperty_background_image)-> + GetListValue(); + const nsCSSValuePairList *repeat = + data->ValueFor(eCSSProperty_background_repeat)-> + GetPairListValue(); + const nsCSSValueList *attachment = + data->ValueFor(eCSSProperty_background_attachment)-> + GetListValue(); + const nsCSSValueList *position = + data->ValueFor(eCSSProperty_background_position)-> + GetListValue(); + const nsCSSValueList *clip = + data->ValueFor(eCSSProperty_background_clip)-> + GetListValue(); + const nsCSSValueList *origin = + data->ValueFor(eCSSProperty_background_origin)-> + GetListValue(); + const nsCSSValuePairList *size = + data->ValueFor(eCSSProperty_background_size)-> + GetPairListValue(); + for (;;) { + image->mValue.AppendToString(eCSSProperty_background_image, aValue, + aSerialization); + aValue.Append(char16_t(' ')); + repeat->mXValue.AppendToString(eCSSProperty_background_repeat, aValue, + aSerialization); + if (repeat->mYValue.GetUnit() != eCSSUnit_Null) { + repeat->mYValue.AppendToString(eCSSProperty_background_repeat, aValue, + aSerialization); + } + aValue.Append(char16_t(' ')); + attachment->mValue.AppendToString(eCSSProperty_background_attachment, + aValue, aSerialization); + aValue.Append(char16_t(' ')); + position->mValue.AppendToString(eCSSProperty_background_position, + aValue, aSerialization); + + if (size->mXValue.GetUnit() != eCSSUnit_Auto || + size->mYValue.GetUnit() != eCSSUnit_Auto) { + aValue.Append(char16_t(' ')); + aValue.Append(char16_t('/')); + aValue.Append(char16_t(' ')); + size->mXValue.AppendToString(eCSSProperty_background_size, aValue, + aSerialization); + aValue.Append(char16_t(' ')); + size->mYValue.AppendToString(eCSSProperty_background_size, aValue, + aSerialization); + } + + NS_ABORT_IF_FALSE(clip->mValue.GetUnit() == eCSSUnit_Enumerated && + origin->mValue.GetUnit() == eCSSUnit_Enumerated, + "should not have inherit/initial within list"); + + if (clip->mValue.GetIntValue() != NS_STYLE_BG_CLIP_BORDER || + origin->mValue.GetIntValue() != NS_STYLE_BG_ORIGIN_PADDING) { + MOZ_ASSERT(nsCSSProps::kKeywordTableTable[ + eCSSProperty_background_origin] == + nsCSSProps::kBackgroundOriginKTable); + MOZ_ASSERT(nsCSSProps::kKeywordTableTable[ + eCSSProperty_background_clip] == + nsCSSProps::kBackgroundOriginKTable); + static_assert(NS_STYLE_BG_CLIP_BORDER == + NS_STYLE_BG_ORIGIN_BORDER && + NS_STYLE_BG_CLIP_PADDING == + NS_STYLE_BG_ORIGIN_PADDING && + NS_STYLE_BG_CLIP_CONTENT == + NS_STYLE_BG_ORIGIN_CONTENT, + "bg-clip and bg-origin style constants must agree"); + aValue.Append(char16_t(' ')); + origin->mValue.AppendToString(eCSSProperty_background_origin, aValue, + aSerialization); + + if (clip->mValue != origin->mValue) { + aValue.Append(char16_t(' ')); + clip->mValue.AppendToString(eCSSProperty_background_clip, aValue, + aSerialization); + } + } + + image = image->mNext; + repeat = repeat->mNext; + attachment = attachment->mNext; + position = position->mNext; + clip = clip->mNext; + origin = origin->mNext; + size = size->mNext; + + if (!image) { + if (repeat || attachment || position || clip || origin || size) { + // Uneven length lists, so can't be serialized as shorthand. + aValue.Truncate(); + return; + } + break; + } + if (!repeat || !attachment || !position || !clip || !origin || !size) { + // Uneven length lists, so can't be serialized as shorthand. + aValue.Truncate(); + return; + } + aValue.Append(char16_t(',')); + aValue.Append(char16_t(' ')); + } + + aValue.Append(char16_t(' ')); + AppendValueToString(eCSSProperty_background_color, aValue, + aSerialization); + break; + } + case eCSSProperty_font: { + // systemFont might not be present; other values are guaranteed to be + // available based on the shorthand check at the beginning of the + // function, as long as the prop is enabled + const nsCSSValue *systemFont = + data->ValueFor(eCSSProperty__x_system_font); + const nsCSSValue *style = + data->ValueFor(eCSSProperty_font_style); + const nsCSSValue *variant = + data->ValueFor(eCSSProperty_font_variant); + const nsCSSValue *weight = + data->ValueFor(eCSSProperty_font_weight); + const nsCSSValue *size = + data->ValueFor(eCSSProperty_font_size); + const nsCSSValue *lh = + data->ValueFor(eCSSProperty_line_height); + const nsCSSValue *family = + data->ValueFor(eCSSProperty_font_family); + const nsCSSValue *stretch = + data->ValueFor(eCSSProperty_font_stretch); + const nsCSSValue *sizeAdjust = + data->ValueFor(eCSSProperty_font_size_adjust); + const nsCSSValue *featureSettings = + data->ValueFor(eCSSProperty_font_feature_settings); + const nsCSSValue *languageOverride = + data->ValueFor(eCSSProperty_font_language_override); + const nsCSSValue *fontKerning = + data->ValueFor(eCSSProperty_font_kerning); + const nsCSSValue *fontSynthesis = + data->ValueFor(eCSSProperty_font_synthesis); + const nsCSSValue *fontVariantAlternates = + data->ValueFor(eCSSProperty_font_variant_alternates); + const nsCSSValue *fontVariantCaps = + data->ValueFor(eCSSProperty_font_variant_caps); + const nsCSSValue *fontVariantEastAsian = + data->ValueFor(eCSSProperty_font_variant_east_asian); + const nsCSSValue *fontVariantLigatures = + data->ValueFor(eCSSProperty_font_variant_ligatures); + const nsCSSValue *fontVariantNumeric = + data->ValueFor(eCSSProperty_font_variant_numeric); + const nsCSSValue *fontVariantPosition = + data->ValueFor(eCSSProperty_font_variant_position); + + // if font features are not enabled, pointers for fontVariant + // values above may be null since the shorthand check ignores them + // font-variant-alternates enabled ==> layout.css.font-features.enabled is true + bool fontFeaturesEnabled = + nsCSSProps::IsEnabled(eCSSProperty_font_variant_alternates); + + if (systemFont && + systemFont->GetUnit() != eCSSUnit_None && + systemFont->GetUnit() != eCSSUnit_Null) { + if (style->GetUnit() != eCSSUnit_System_Font || + variant->GetUnit() != eCSSUnit_System_Font || + weight->GetUnit() != eCSSUnit_System_Font || + size->GetUnit() != eCSSUnit_System_Font || + lh->GetUnit() != eCSSUnit_System_Font || + family->GetUnit() != eCSSUnit_System_Font || + stretch->GetUnit() != eCSSUnit_System_Font || + sizeAdjust->GetUnit() != eCSSUnit_System_Font || + featureSettings->GetUnit() != eCSSUnit_System_Font || + languageOverride->GetUnit() != eCSSUnit_System_Font || + (fontFeaturesEnabled && + (fontKerning->GetUnit() != eCSSUnit_System_Font || + fontSynthesis->GetUnit() != eCSSUnit_System_Font || + fontVariantAlternates->GetUnit() != eCSSUnit_System_Font || + fontVariantCaps->GetUnit() != eCSSUnit_System_Font || + fontVariantEastAsian->GetUnit() != eCSSUnit_System_Font || + fontVariantLigatures->GetUnit() != eCSSUnit_System_Font || + fontVariantNumeric->GetUnit() != eCSSUnit_System_Font || + fontVariantPosition->GetUnit() != eCSSUnit_System_Font))) { + // This can't be represented as a shorthand. + return; + } + systemFont->AppendToString(eCSSProperty__x_system_font, aValue, + aSerialization); + } else { + // properties reset by this shorthand property to their + // initial values but not represented in its syntax + if (stretch->GetUnit() != eCSSUnit_Enumerated || + stretch->GetIntValue() != NS_STYLE_FONT_STRETCH_NORMAL || + sizeAdjust->GetUnit() != eCSSUnit_None || + featureSettings->GetUnit() != eCSSUnit_Normal || + languageOverride->GetUnit() != eCSSUnit_Normal || + (fontFeaturesEnabled && + (fontKerning->GetIntValue() != NS_FONT_KERNING_AUTO || + fontSynthesis->GetUnit() != eCSSUnit_Enumerated || + fontSynthesis->GetIntValue() != + (NS_FONT_SYNTHESIS_WEIGHT | NS_FONT_SYNTHESIS_STYLE) || + fontVariantAlternates->GetUnit() != eCSSUnit_Normal || + fontVariantCaps->GetUnit() != eCSSUnit_Normal || + fontVariantEastAsian->GetUnit() != eCSSUnit_Normal || + fontVariantLigatures->GetUnit() != eCSSUnit_Normal || + fontVariantNumeric->GetUnit() != eCSSUnit_Normal || + fontVariantPosition->GetUnit() != eCSSUnit_Normal))) { + return; + } + + if (style->GetUnit() != eCSSUnit_Enumerated || + style->GetIntValue() != NS_FONT_STYLE_NORMAL) { + style->AppendToString(eCSSProperty_font_style, aValue, + aSerialization); + aValue.Append(char16_t(' ')); + } + if (variant->GetUnit() != eCSSUnit_Enumerated || + variant->GetIntValue() != NS_FONT_VARIANT_NORMAL) { + variant->AppendToString(eCSSProperty_font_variant, aValue, + aSerialization); + aValue.Append(char16_t(' ')); + } + if (weight->GetUnit() != eCSSUnit_Enumerated || + weight->GetIntValue() != NS_FONT_WEIGHT_NORMAL) { + weight->AppendToString(eCSSProperty_font_weight, aValue, + aSerialization); + aValue.Append(char16_t(' ')); + } + size->AppendToString(eCSSProperty_font_size, aValue, aSerialization); + if (lh->GetUnit() != eCSSUnit_Normal) { + aValue.Append(char16_t('/')); + lh->AppendToString(eCSSProperty_line_height, aValue, aSerialization); + } + aValue.Append(char16_t(' ')); + family->AppendToString(eCSSProperty_font_family, aValue, + aSerialization); + } + break; + } + case eCSSProperty_list_style: + if (AppendValueToString(eCSSProperty_list_style_type, aValue, + aSerialization)) { + aValue.Append(char16_t(' ')); + } + if (AppendValueToString(eCSSProperty_list_style_position, aValue, + aSerialization)) { + aValue.Append(char16_t(' ')); + } + AppendValueToString(eCSSProperty_list_style_image, aValue, + aSerialization); + break; + case eCSSProperty_overflow: { + const nsCSSValue &xValue = + *data->ValueFor(eCSSProperty_overflow_x); + const nsCSSValue &yValue = + *data->ValueFor(eCSSProperty_overflow_y); + if (xValue == yValue) + xValue.AppendToString(eCSSProperty_overflow_x, aValue, aSerialization); + break; + } + case eCSSProperty_text_decoration: { + // If text-decoration-color or text-decoration-style isn't initial value, + // we cannot serialize the text-decoration shorthand value. + const nsCSSValue *decorationColor = + data->ValueFor(eCSSProperty_text_decoration_color); + const nsCSSValue *decorationStyle = + data->ValueFor(eCSSProperty_text_decoration_style); + + NS_ABORT_IF_FALSE(decorationStyle->GetUnit() == eCSSUnit_Enumerated, + nsPrintfCString("bad text-decoration-style unit %d", + decorationStyle->GetUnit()).get()); + + if (decorationColor->GetUnit() != eCSSUnit_Enumerated || + decorationColor->GetIntValue() != NS_STYLE_COLOR_MOZ_USE_TEXT_COLOR || + decorationStyle->GetIntValue() != + NS_STYLE_TEXT_DECORATION_STYLE_SOLID) { + return; + } + + AppendValueToString(eCSSProperty_text_decoration_line, aValue, + aSerialization); + break; + } + case eCSSProperty_transition: { + const nsCSSValue *transProp = + data->ValueFor(eCSSProperty_transition_property); + const nsCSSValue *transDuration = + data->ValueFor(eCSSProperty_transition_duration); + const nsCSSValue *transTiming = + data->ValueFor(eCSSProperty_transition_timing_function); + const nsCSSValue *transDelay = + data->ValueFor(eCSSProperty_transition_delay); + + NS_ABORT_IF_FALSE(transDuration->GetUnit() == eCSSUnit_List || + transDuration->GetUnit() == eCSSUnit_ListDep, + nsPrintfCString("bad t-duration unit %d", + transDuration->GetUnit()).get()); + NS_ABORT_IF_FALSE(transTiming->GetUnit() == eCSSUnit_List || + transTiming->GetUnit() == eCSSUnit_ListDep, + nsPrintfCString("bad t-timing unit %d", + transTiming->GetUnit()).get()); + NS_ABORT_IF_FALSE(transDelay->GetUnit() == eCSSUnit_List || + transDelay->GetUnit() == eCSSUnit_ListDep, + nsPrintfCString("bad t-delay unit %d", + transDelay->GetUnit()).get()); + + const nsCSSValueList* dur = transDuration->GetListValue(); + const nsCSSValueList* tim = transTiming->GetListValue(); + const nsCSSValueList* del = transDelay->GetListValue(); + + if (transProp->GetUnit() == eCSSUnit_None || + transProp->GetUnit() == eCSSUnit_All) { + // If any of the other three lists has more than one element, + // we can't use the shorthand. + if (!dur->mNext && !tim->mNext && !del->mNext) { + transProp->AppendToString(eCSSProperty_transition_property, aValue, + aSerialization); + aValue.Append(char16_t(' ')); + dur->mValue.AppendToString(eCSSProperty_transition_duration,aValue, + aSerialization); + aValue.Append(char16_t(' ')); + tim->mValue.AppendToString(eCSSProperty_transition_timing_function, + aValue, aSerialization); + aValue.Append(char16_t(' ')); + del->mValue.AppendToString(eCSSProperty_transition_delay, aValue, + aSerialization); + aValue.Append(char16_t(' ')); + } else { + aValue.Truncate(); + } + } else { + NS_ABORT_IF_FALSE(transProp->GetUnit() == eCSSUnit_List || + transProp->GetUnit() == eCSSUnit_ListDep, + nsPrintfCString("bad t-prop unit %d", + transProp->GetUnit()).get()); + const nsCSSValueList* pro = transProp->GetListValue(); + for (;;) { + pro->mValue.AppendToString(eCSSProperty_transition_property, + aValue, aSerialization); + aValue.Append(char16_t(' ')); + dur->mValue.AppendToString(eCSSProperty_transition_duration, + aValue, aSerialization); + aValue.Append(char16_t(' ')); + tim->mValue.AppendToString(eCSSProperty_transition_timing_function, + aValue, aSerialization); + aValue.Append(char16_t(' ')); + del->mValue.AppendToString(eCSSProperty_transition_delay, + aValue, aSerialization); + pro = pro->mNext; + dur = dur->mNext; + tim = tim->mNext; + del = del->mNext; + if (!pro || !dur || !tim || !del) { + break; + } + aValue.AppendLiteral(", "); + } + if (pro || dur || tim || del) { + // Lists not all the same length, can't use shorthand. + aValue.Truncate(); + } + } + break; + } + case eCSSProperty_animation: { + const nsCSSProperty* subprops = + nsCSSProps::SubpropertyEntryFor(eCSSProperty_animation); + static const size_t numProps = 7; + NS_ABORT_IF_FALSE(subprops[numProps] == eCSSProperty_UNKNOWN, + "unexpected number of subproperties"); + const nsCSSValue* values[numProps]; + const nsCSSValueList* lists[numProps]; + + for (uint32_t i = 0; i < numProps; ++i) { + values[i] = data->ValueFor(subprops[i]); + NS_ABORT_IF_FALSE(values[i]->GetUnit() == eCSSUnit_List || + values[i]->GetUnit() == eCSSUnit_ListDep, + nsPrintfCString("bad a-duration unit %d", + values[i]->GetUnit()).get()); + lists[i] = values[i]->GetListValue(); + } + + for (;;) { + // We must serialize 'animation-name' last in case it has + // a value that conflicts with one of the other keyword properties. + NS_ABORT_IF_FALSE(subprops[numProps - 1] == + eCSSProperty_animation_name, + "animation-name must be last"); + bool done = false; + for (uint32_t i = 0;;) { + lists[i]->mValue.AppendToString(subprops[i], aValue, aSerialization); + lists[i] = lists[i]->mNext; + if (!lists[i]) { + done = true; + } + if (++i == numProps) { + break; + } + aValue.Append(char16_t(' ')); + } + if (done) { + break; + } + aValue.AppendLiteral(", "); + } + for (uint32_t i = 0; i < numProps; ++i) { + if (lists[i]) { + // Lists not all the same length, can't use shorthand. + aValue.Truncate(); + break; + } + } + break; + } + case eCSSProperty_marker: { + const nsCSSValue &endValue = + *data->ValueFor(eCSSProperty_marker_end); + const nsCSSValue &midValue = + *data->ValueFor(eCSSProperty_marker_mid); + const nsCSSValue &startValue = + *data->ValueFor(eCSSProperty_marker_start); + if (endValue == midValue && midValue == startValue) + AppendValueToString(eCSSProperty_marker_end, aValue, aSerialization); + break; + } + case eCSSProperty__moz_columns: { + // Two values, column-count and column-width, separated by a space. + const nsCSSProperty* subprops = + nsCSSProps::SubpropertyEntryFor(aProperty); + AppendValueToString(subprops[0], aValue, aSerialization); + aValue.Append(char16_t(' ')); + AppendValueToString(subprops[1], aValue, aSerialization); + break; + } + case eCSSProperty_flex: { + // flex-grow, flex-shrink, flex-basis, separated by single space + const nsCSSProperty* subprops = + nsCSSProps::SubpropertyEntryFor(aProperty); + + AppendValueToString(subprops[0], aValue, aSerialization); + aValue.Append(char16_t(' ')); + AppendValueToString(subprops[1], aValue, aSerialization); + aValue.Append(char16_t(' ')); + AppendValueToString(subprops[2], aValue, aSerialization); + break; + } + case eCSSProperty_flex_flow: { + // flex-direction, flex-wrap, separated by single space + const nsCSSProperty* subprops = + nsCSSProps::SubpropertyEntryFor(aProperty); + NS_ABORT_IF_FALSE(subprops[2] == eCSSProperty_UNKNOWN, + "must have exactly two subproperties"); + + AppendValueToString(subprops[0], aValue, aSerialization); + aValue.Append(char16_t(' ')); + AppendValueToString(subprops[1], aValue, aSerialization); + break; + } + case eCSSProperty_grid_row: + case eCSSProperty_grid_column: { + // grid-{row,column}-start, grid-{row,column}-end, separated by a slash + const nsCSSProperty* subprops = + nsCSSProps::SubpropertyEntryFor(aProperty); + NS_ABORT_IF_FALSE(subprops[2] == eCSSProperty_UNKNOWN, + "must have exactly two subproperties"); + + // TODO: should we simplify when possible? + AppendValueToString(subprops[0], aValue, aSerialization); + aValue.AppendLiteral(" / "); + AppendValueToString(subprops[1], aValue, aSerialization); + break; + } + case eCSSProperty_grid_area: { + const nsCSSProperty* subprops = + nsCSSProps::SubpropertyEntryFor(aProperty); + NS_ABORT_IF_FALSE(subprops[4] == eCSSProperty_UNKNOWN, + "must have exactly four subproperties"); + + // TODO: should we simplify when possible? + AppendValueToString(subprops[0], aValue, aSerialization); + aValue.AppendLiteral(" / "); + AppendValueToString(subprops[1], aValue, aSerialization); + aValue.AppendLiteral(" / "); + AppendValueToString(subprops[2], aValue, aSerialization); + aValue.AppendLiteral(" / "); + AppendValueToString(subprops[3], aValue, aSerialization); + break; + } + + // This can express either grid-template-{areas,columns,rows} + // or grid-auto-{flow,columns,rows}, but not both. + case eCSSProperty_grid: { + const nsCSSValue& areasValue = + *data->ValueFor(eCSSProperty_grid_template_areas); + const nsCSSValue& columnsValue = + *data->ValueFor(eCSSProperty_grid_template_columns); + const nsCSSValue& rowsValue = + *data->ValueFor(eCSSProperty_grid_template_rows); + + const nsCSSValue& autoFlowValue = + *data->ValueFor(eCSSProperty_grid_auto_flow); + const nsCSSValue& autoColumnsValue = + *data->ValueFor(eCSSProperty_grid_auto_columns); + const nsCSSValue& autoRowsValue = + *data->ValueFor(eCSSProperty_grid_auto_rows); + + if (areasValue.GetUnit() == eCSSUnit_None && + columnsValue.GetUnit() == eCSSUnit_None && + rowsValue.GetUnit() == eCSSUnit_None) { + AppendValueToString(eCSSProperty_grid_auto_flow, + aValue, aSerialization); + aValue.Append(char16_t(' ')); + AppendValueToString(eCSSProperty_grid_auto_columns, + aValue, aSerialization); + aValue.AppendLiteral(" / "); + AppendValueToString(eCSSProperty_grid_auto_rows, + aValue, aSerialization); + break; + } else if (!(autoFlowValue.GetUnit() == eCSSUnit_Enumerated && + autoFlowValue.GetIntValue() == NS_STYLE_GRID_AUTO_FLOW_NONE && + autoColumnsValue.GetUnit() == eCSSUnit_Auto && + autoRowsValue.GetUnit() == eCSSUnit_Auto)) { + // Not serializable, bail. + return; + } + // Fall through to eCSSProperty_grid_template + } + case eCSSProperty_grid_template: { + const nsCSSValue& areasValue = + *data->ValueFor(eCSSProperty_grid_template_areas); + const nsCSSValue& columnsValue = + *data->ValueFor(eCSSProperty_grid_template_columns); + const nsCSSValue& rowsValue = + *data->ValueFor(eCSSProperty_grid_template_rows); + if (areasValue.GetUnit() == eCSSUnit_None) { + AppendValueToString(eCSSProperty_grid_template_columns, + aValue, aSerialization); + aValue.AppendLiteral(" / "); + AppendValueToString(eCSSProperty_grid_template_rows, + aValue, aSerialization); + break; + } + if (columnsValue.GetUnit() == eCSSUnit_List || + columnsValue.GetUnit() == eCSSUnit_ListDep) { + const nsCSSValueList* columnsItem = columnsValue.GetListValue(); + if (columnsItem->mValue.GetUnit() == eCSSUnit_Enumerated && + columnsItem->mValue.GetIntValue() == NS_STYLE_GRID_TEMPLATE_SUBGRID) { + // We have "grid-template-areas:[something]; grid-template-columns:subgrid" + // which isn't a value that the shorthand can express. Bail. + return; + } + } + if (rowsValue.GetUnit() != eCSSUnit_List && + rowsValue.GetUnit() != eCSSUnit_ListDep) { + // We have "grid-template-areas:[something]; grid-template-rows:none" + // which isn't a value that the shorthand can express. Bail. + return; + } + const nsCSSValueList* rowsItem = rowsValue.GetListValue(); + if (rowsItem->mValue.GetUnit() == eCSSUnit_Enumerated && + rowsItem->mValue.GetIntValue() == NS_STYLE_GRID_TEMPLATE_SUBGRID) { + // We have "grid-template-areas:[something]; grid-template-rows:subgrid" + // which isn't a value that the shorthand can express. Bail. + return; + } + const GridTemplateAreasValue* areas = areasValue.GetGridTemplateAreas(); + uint32_t nRowItems = 0; + while (rowsItem) { + nRowItems++; + rowsItem = rowsItem->mNext; + } + MOZ_ASSERT(nRowItems % 2 == 1, "expected an odd number of items"); + if ((nRowItems - 1) / 2 != areas->NRows()) { + // Not serializable, bail. + return; + } + if (columnsValue.GetUnit() != eCSSUnit_None) { + AppendValueToString(eCSSProperty_grid_template_columns, + aValue, aSerialization); + aValue.AppendLiteral(" / "); + } + rowsItem = rowsValue.GetListValue(); + uint32_t row = 0; + for (;;) { + bool addSpaceSeparator = true; + nsCSSUnit unit = rowsItem->mValue.GetUnit(); + + if (unit == eCSSUnit_Null) { + // Empty or omitted . Serializes to nothing. + addSpaceSeparator = false; // Avoid a double space. + + } else if (unit == eCSSUnit_List || unit == eCSSUnit_ListDep) { + // Non-empty + aValue.AppendLiteral("("); + rowsItem->mValue.AppendToString(eCSSProperty_grid_template_rows, + aValue, aSerialization); + aValue.AppendLiteral(")"); + + } else { + nsStyleUtil::AppendEscapedCSSString(areas->mTemplates[row++], aValue); + aValue.Append(char16_t(' ')); + + // + rowsItem->mValue.AppendToString(eCSSProperty_grid_template_rows, + aValue, aSerialization); + if (rowsItem->mNext && + rowsItem->mNext->mValue.GetUnit() == eCSSUnit_Null && + !rowsItem->mNext->mNext) { + // Break out of the loop early to avoid a trailing space. + break; + } + } + + rowsItem = rowsItem->mNext; + if (!rowsItem) { + break; + } + + if (addSpaceSeparator) { + aValue.Append(char16_t(' ')); + } + } + break; + } + case eCSSProperty__moz_transform: { + // shorthands that are just aliases with different parsing rules + const nsCSSProperty* subprops = + nsCSSProps::SubpropertyEntryFor(aProperty); + NS_ABORT_IF_FALSE(subprops[1] == eCSSProperty_UNKNOWN, + "must have exactly one subproperty"); + AppendValueToString(subprops[0], aValue, aSerialization); + break; + } + case eCSSProperty_all: + // If we got here, then we didn't have all "inherit" or "initial" or + // "unset" values for all of the longhand property components of 'all'. + // There is no other possible value that is valid for all properties, + // so serialize as the empty string. + break; + default: + NS_ABORT_IF_FALSE(false, "no other shorthands"); + break; + } +} + +bool +Declaration::GetValueIsImportant(const nsAString& aProperty) const +{ + nsCSSProperty propID = + nsCSSProps::LookupProperty(aProperty, nsCSSProps::eIgnoreEnabledState); + if (propID == eCSSProperty_UNKNOWN) { + return false; + } + if (propID == eCSSPropertyExtra_variable) { + const nsSubstring& variableName = + Substring(aProperty, CSS_CUSTOM_NAME_PREFIX_LENGTH); + return GetVariableValueIsImportant(variableName); + } + return GetValueIsImportant(propID); +} + +bool +Declaration::GetValueIsImportant(nsCSSProperty aProperty) const +{ + if (!mImportantData) + return false; + + // Calling ValueFor is inefficient, but we can assume '!important' is rare. + + if (!nsCSSProps::IsShorthand(aProperty)) { + return mImportantData->ValueFor(aProperty) != nullptr; + } + + CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aProperty) { + if (*p == eCSSProperty__x_system_font) { + // The system_font subproperty doesn't count. + continue; + } + if (!mImportantData->ValueFor(*p)) { + return false; + } + } + return true; +} + +void +Declaration::AppendPropertyAndValueToString(nsCSSProperty aProperty, + nsAutoString& aValue, + nsAString& aResult) const +{ + NS_ABORT_IF_FALSE(0 <= aProperty && aProperty < eCSSProperty_COUNT, + "property enum out of range"); + NS_ABORT_IF_FALSE((aProperty < eCSSProperty_COUNT_no_shorthands) == + aValue.IsEmpty(), + "aValue should be given for shorthands but not longhands"); + AppendASCIItoUTF16(nsCSSProps::GetStringValue(aProperty), aResult); + aResult.AppendLiteral(": "); + if (aValue.IsEmpty()) + AppendValueToString(aProperty, aResult, nsCSSValue::eNormalized); + else + aResult.Append(aValue); + if (GetValueIsImportant(aProperty)) { + aResult.AppendLiteral(" ! important"); + } + aResult.AppendLiteral("; "); +} + +void +Declaration::AppendVariableAndValueToString(const nsAString& aName, + nsAString& aResult) const +{ + aResult.AppendLiteral("--"); + aResult.Append(aName); + CSSVariableDeclarations::Type type; + nsString value; + bool important; + + if (mImportantVariables && mImportantVariables->Get(aName, type, value)) { + important = true; + } else { + MOZ_ASSERT(mVariables); + MOZ_ASSERT(mVariables->Has(aName)); + mVariables->Get(aName, type, value); + important = false; + } + + switch (type) { + case CSSVariableDeclarations::eTokenStream: + if (value.IsEmpty()) { + aResult.Append(':'); + } else { + aResult.AppendLiteral(": "); + aResult.Append(value); + } + break; + + case CSSVariableDeclarations::eInitial: + aResult.AppendLiteral("initial"); + break; + + case CSSVariableDeclarations::eInherit: + aResult.AppendLiteral("inherit"); + break; + + case CSSVariableDeclarations::eUnset: + aResult.AppendLiteral("unset"); + break; + + default: + MOZ_ASSERT(false, "unexpected variable value type"); + } + + if (important) { + aResult.AppendLiteral("! important"); + } + aResult.AppendLiteral("; "); +} + +void +Declaration::ToString(nsAString& aString) const +{ + // Someone cares about this declaration's contents, so don't let it + // change from under them. See e.g. bug 338679. + SetImmutable(); + + nsCSSCompressedDataBlock *systemFontData = + GetValueIsImportant(eCSSProperty__x_system_font) ? mImportantData : mData; + const nsCSSValue *systemFont = + systemFontData->ValueFor(eCSSProperty__x_system_font); + const bool haveSystemFont = systemFont && + systemFont->GetUnit() != eCSSUnit_None && + systemFont->GetUnit() != eCSSUnit_Null; + bool didSystemFont = false; + + int32_t count = mOrder.Length(); + int32_t index; + nsAutoTArray shorthandsUsed; + for (index = 0; index < count; index++) { + nsCSSProperty property = GetPropertyAt(index); + + if (property == eCSSPropertyExtra_variable) { + uint32_t variableIndex = mOrder[index] - eCSSProperty_COUNT; + AppendVariableAndValueToString(mVariableOrder[variableIndex], aString); + continue; + } + + if (!nsCSSProps::IsEnabled(property)) { + continue; + } + bool doneProperty = false; + + // If we already used this property in a shorthand, skip it. + if (shorthandsUsed.Length() > 0) { + for (const nsCSSProperty *shorthands = + nsCSSProps::ShorthandsContaining(property); + *shorthands != eCSSProperty_UNKNOWN; ++shorthands) { + if (shorthandsUsed.Contains(*shorthands)) { + doneProperty = true; + break; + } + } + if (doneProperty) + continue; + } + + // Try to use this property in a shorthand. + nsAutoString value; + for (const nsCSSProperty *shorthands = + nsCSSProps::ShorthandsContaining(property); + *shorthands != eCSSProperty_UNKNOWN; ++shorthands) { + // ShorthandsContaining returns the shorthands in order from those + // that contain the most subproperties to those that contain the + // least, which is exactly the order we want to test them. + nsCSSProperty shorthand = *shorthands; + + // If GetValue gives us a non-empty string back, we can use that + // value; otherwise it's not possible to use this shorthand. + GetValue(shorthand, value); + if (!value.IsEmpty()) { + AppendPropertyAndValueToString(shorthand, value, aString); + shorthandsUsed.AppendElement(shorthand); + doneProperty = true; + break; + } + + NS_ABORT_IF_FALSE(shorthand != eCSSProperty_font || + *(shorthands + 1) == eCSSProperty_UNKNOWN, + "font should always be the only containing shorthand"); + if (shorthand == eCSSProperty_font) { + if (haveSystemFont && !didSystemFont) { + // Output the shorthand font declaration that we will + // partially override later. But don't add it to + // |shorthandsUsed|, since we will have to override it. + systemFont->AppendToString(eCSSProperty__x_system_font, value, + nsCSSValue::eNormalized); + AppendPropertyAndValueToString(eCSSProperty_font, value, aString); + value.Truncate(); + didSystemFont = true; + } + + // That we output the system font is enough for this property if: + // (1) it's the hidden system font subproperty (which either + // means we output it or we don't have it), or + // (2) its value is the hidden system font value and it matches + // the hidden system font subproperty in importance, and + // we output the system font subproperty. + const nsCSSValue *val = systemFontData->ValueFor(property); + if (property == eCSSProperty__x_system_font || + (haveSystemFont && val && val->GetUnit() == eCSSUnit_System_Font)) { + doneProperty = true; + } + } + } + if (doneProperty) + continue; + + NS_ABORT_IF_FALSE(value.IsEmpty(), "value should be empty now"); + AppendPropertyAndValueToString(property, value, aString); + } + if (! aString.IsEmpty()) { + // if the string is not empty, we have trailing whitespace we + // should remove + aString.Truncate(aString.Length() - 1); + } +} + +#ifdef DEBUG +void +Declaration::List(FILE* out, int32_t aIndent) const +{ + for (int32_t index = aIndent; --index >= 0; ) fputs(" ", out); + + fputs("{ ", out); + nsAutoString s; + ToString(s); + fputs(NS_ConvertUTF16toUTF8(s).get(), out); + fputs("}", out); +} +#endif + +bool +Declaration::GetNthProperty(uint32_t aIndex, nsAString& aReturn) const +{ + aReturn.Truncate(); + if (aIndex < mOrder.Length()) { + nsCSSProperty property = GetPropertyAt(aIndex); + if (property == eCSSPropertyExtra_variable) { + GetCustomPropertyNameAt(aIndex, aReturn); + return true; + } + if (0 <= property) { + AppendASCIItoUTF16(nsCSSProps::GetStringValue(property), aReturn); + return true; + } + } + return false; +} + +void +Declaration::InitializeEmpty() +{ + NS_ABORT_IF_FALSE(!mData && !mImportantData, "already initialized"); + mData = nsCSSCompressedDataBlock::CreateEmptyBlock(); +} + +Declaration* +Declaration::EnsureMutable() +{ + NS_ABORT_IF_FALSE(mData, "should only be called when not expanded"); + if (!IsMutable()) { + return new Declaration(*this); + } else { + return this; + } +} + +size_t +Declaration::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const +{ + size_t n = aMallocSizeOf(this); + n += mOrder.SizeOfExcludingThis(aMallocSizeOf); + n += mData ? mData ->SizeOfIncludingThis(aMallocSizeOf) : 0; + n += mImportantData ? mImportantData->SizeOfIncludingThis(aMallocSizeOf) : 0; + if (mVariables) { + n += mVariables->SizeOfIncludingThis(aMallocSizeOf); + } + if (mImportantVariables) { + n += mImportantVariables->SizeOfIncludingThis(aMallocSizeOf); + } + return n; +} + +bool +Declaration::HasVariableDeclaration(const nsAString& aName) const +{ + return (mVariables && mVariables->Has(aName)) || + (mImportantVariables && mImportantVariables->Has(aName)); +} + +void +Declaration::GetVariableDeclaration(const nsAString& aName, + nsAString& aValue) const +{ + aValue.Truncate(); + + CSSVariableDeclarations::Type type; + nsString value; + + if ((mImportantVariables && mImportantVariables->Get(aName, type, value)) || + (mVariables && mVariables->Get(aName, type, value))) { + switch (type) { + case CSSVariableDeclarations::eTokenStream: + aValue.Append(value); + break; + + case CSSVariableDeclarations::eInitial: + aValue.AppendLiteral("initial"); + break; + + case CSSVariableDeclarations::eInherit: + aValue.AppendLiteral("inherit"); + break; + + case CSSVariableDeclarations::eUnset: + aValue.AppendLiteral("unset"); + break; + + default: + MOZ_ASSERT(false, "unexpected variable value type"); + } + } +} + +void +Declaration::AddVariableDeclaration(const nsAString& aName, + CSSVariableDeclarations::Type aType, + const nsString& aValue, + bool aIsImportant, + bool aOverrideImportant) +{ + MOZ_ASSERT(IsMutable()); + + nsTArray::index_type index = mVariableOrder.IndexOf(aName); + if (index == nsTArray::NoIndex) { + index = mVariableOrder.Length(); + mVariableOrder.AppendElement(aName); + } + + if (!aIsImportant && !aOverrideImportant && + mImportantVariables && mImportantVariables->Has(aName)) { + return; + } + + CSSVariableDeclarations* variables; + if (aIsImportant) { + if (mVariables) { + mVariables->Remove(aName); + } + if (!mImportantVariables) { + mImportantVariables = new CSSVariableDeclarations; + } + variables = mImportantVariables; + } else { + if (mImportantVariables) { + mImportantVariables->Remove(aName); + } + if (!mVariables) { + mVariables = new CSSVariableDeclarations; + } + variables = mVariables; + } + + switch (aType) { + case CSSVariableDeclarations::eTokenStream: + variables->PutTokenStream(aName, aValue); + break; + + case CSSVariableDeclarations::eInitial: + MOZ_ASSERT(aValue.IsEmpty()); + variables->PutInitial(aName); + break; + + case CSSVariableDeclarations::eInherit: + MOZ_ASSERT(aValue.IsEmpty()); + variables->PutInherit(aName); + break; + + case CSSVariableDeclarations::eUnset: + MOZ_ASSERT(aValue.IsEmpty()); + variables->PutUnset(aName); + break; + + default: + MOZ_ASSERT(false, "unexpected aType value"); + } + + uint32_t propertyIndex = index + eCSSProperty_COUNT; + mOrder.RemoveElement(propertyIndex); + mOrder.AppendElement(propertyIndex); +} + +void +Declaration::RemoveVariableDeclaration(const nsAString& aName) +{ + if (mVariables) { + mVariables->Remove(aName); + } + if (mImportantVariables) { + mImportantVariables->Remove(aName); + } + nsTArray::index_type index = mVariableOrder.IndexOf(aName); + if (index != nsTArray::NoIndex) { + mOrder.RemoveElement(index + eCSSProperty_COUNT); + } +} + +bool +Declaration::GetVariableValueIsImportant(const nsAString& aName) const +{ + return mImportantVariables && mImportantVariables->Has(aName); +} + +} // namespace mozilla::css +} // namespace mozilla