diff options
Diffstat (limited to 'layout/style/Declaration.cpp')
-rw-r--r-- | layout/style/Declaration.cpp | 1988 |
1 files changed, 1988 insertions, 0 deletions
diff --git a/layout/style/Declaration.cpp b/layout/style/Declaration.cpp new file mode 100644 index 000000000..c67f6b2a2 --- /dev/null +++ b/layout/style/Declaration.cpp @@ -0,0 +1,1988 @@ +/* -*- 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 { + +NS_IMPL_QUERY_INTERFACE(ImportantStyleData, nsIStyleRule) +NS_IMPL_ADDREF_USING_AGGREGATOR(ImportantStyleData, Declaration()) +NS_IMPL_RELEASE_USING_AGGREGATOR(ImportantStyleData, Declaration()) + +/* virtual */ void +ImportantStyleData::MapRuleInfoInto(nsRuleData* aRuleData) +{ + Declaration()->MapImportantRuleInfoInto(aRuleData); +} + +/* virtual */ bool +ImportantStyleData::MightMapInheritedStyleData() +{ + return Declaration()->MapsImportantInheritedStyleData(); +} + +/* virtual */ bool +ImportantStyleData::GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty, + nsCSSValue* aValue) +{ + return Declaration()->GetDiscretelyAnimatedCSSValue(aProperty, aValue); +} + + +#ifdef DEBUG +/* virtual */ void +ImportantStyleData::List(FILE* out, int32_t aIndent) const +{ + // Indent + nsAutoCString str; + for (int32_t index = aIndent; --index >= 0; ) { + str.AppendLiteral(" "); + } + + str.AppendLiteral("! important rule\n"); + fprintf_stderr(out, "%s", str.get()); +} +#endif + +Declaration::Declaration(const Declaration& aCopy) + : DeclarationBlock(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) +{ +} + +Declaration::~Declaration() +{ +} + +NS_INTERFACE_MAP_BEGIN(Declaration) + if (aIID.Equals(NS_GET_IID(mozilla::css::Declaration))) { + *aInstancePtr = this; + NS_ADDREF_THIS(); + return NS_OK; + } + else + NS_INTERFACE_MAP_ENTRY(nsIStyleRule) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStyleRule) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(Declaration) +NS_IMPL_RELEASE(Declaration) + +/* virtual */ void +Declaration::MapRuleInfoInto(nsRuleData* aRuleData) +{ + MOZ_ASSERT(mData, "must call only while compressed"); + mData->MapRuleInfoInto(aRuleData); + if (mVariables) { + mVariables->MapRuleInfoInto(aRuleData); + } +} + +/* virtual */ bool +Declaration::MightMapInheritedStyleData() +{ + MOZ_ASSERT(mData, "must call only while compressed"); + if (mVariables && mVariables->Count() != 0) { + return true; + } + return mData->HasInheritedStyleData(); +} + +/* virtual */ bool +Declaration::GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty, + nsCSSValue* aValue) +{ + nsCSSCompressedDataBlock* data = GetPropertyIsImportantByID(aProperty) + ? mImportantData : mData; + const nsCSSValue* value = data->ValueFor(aProperty); + if (!value) { + return false; + } + *aValue = *value; + return true; +} + + +bool +Declaration::MapsImportantInheritedStyleData() const +{ + MOZ_ASSERT(mData, "must call only while compressed"); + MOZ_ASSERT(HasImportantData(), "must only be called for Declarations with " + "important data"); + if (mImportantVariables && mImportantVariables->Count() != 0) { + return true; + } + return mImportantData ? mImportantData->HasInheritedStyleData() : false; +} + +void +Declaration::ValueAppended(nsCSSPropertyID aProperty) +{ + MOZ_ASSERT(!mData && !mImportantData, + "should only be called while expanded"); + MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty), + "shorthands forbidden"); + // order IS important for CSS, so remove and add to the end + mOrder.RemoveElement(static_cast<uint32_t>(aProperty)); + mOrder.AppendElement(static_cast<uint32_t>(aProperty)); +} + +template<typename PropFunc, typename CustomPropFunc> +inline void +DispatchPropertyOperation(const nsAString& aProperty, + PropFunc aPropFunc, CustomPropFunc aCustomPropFunc) +{ + nsCSSPropertyID propID = + nsCSSProps::LookupProperty(aProperty, CSSEnabledState::eForAllContent); + if (propID != eCSSProperty_UNKNOWN) { + if (propID != eCSSPropertyExtra_variable) { + aPropFunc(propID); + } else { + aCustomPropFunc(Substring(aProperty, CSS_CUSTOM_NAME_PREFIX_LENGTH)); + } + } +} + +void +Declaration::GetPropertyValue(const nsAString& aProperty, + nsAString& aValue) const +{ + DispatchPropertyOperation(aProperty, + [&](nsCSSPropertyID propID) { GetPropertyValueByID(propID, aValue); }, + [&](const nsAString& name) { GetVariableValue(name, aValue); }); +} + +void +Declaration::GetPropertyValueByID(nsCSSPropertyID aPropID, + nsAString& aValue) const +{ + GetPropertyValueInternal(aPropID, aValue, nsCSSValue::eNormalized); +} + +void +Declaration::GetAuthoredPropertyValue(const nsAString& aProperty, + nsAString& aValue) const +{ + DispatchPropertyOperation(aProperty, + [&](nsCSSPropertyID propID) { + GetPropertyValueInternal(propID, aValue, nsCSSValue::eAuthorSpecified); + }, + [&](const nsAString& name) { GetVariableValue(name, aValue); }); +} + +bool +Declaration::GetPropertyIsImportant(const nsAString& aProperty) const +{ + bool r = false; + DispatchPropertyOperation(aProperty, + [&](nsCSSPropertyID propID) { r = GetPropertyIsImportantByID(propID); }, + [&](const nsAString& name) { r = GetVariableIsImportant(name); }); + return r; +} + +void +Declaration::RemoveProperty(const nsAString& aProperty) +{ + DispatchPropertyOperation(aProperty, + [&](nsCSSPropertyID propID) { RemovePropertyByID(propID); }, + [&](const nsAString& name) { RemoveVariable(name); }); +} + +void +Declaration::RemovePropertyByID(nsCSSPropertyID aProperty) +{ + MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT); + + nsCSSExpandedDataBlock data; + ExpandTo(&data); + MOZ_ASSERT(!mData && !mImportantData, "Expand didn't null things out"); + + if (nsCSSProps::IsShorthand(aProperty)) { + CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aProperty, + CSSEnabledState::eForAllContent) { + data.ClearLonghandProperty(*p); + mOrder.RemoveElement(static_cast<uint32_t>(*p)); + } + } else { + data.ClearLonghandProperty(aProperty); + mOrder.RemoveElement(static_cast<uint32_t>(aProperty)); + } + + CompressFrom(&data); +} + +bool +Declaration::HasProperty(nsCSSPropertyID aProperty) const +{ + MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT_no_shorthands, + "property ID out of range"); + + nsCSSCompressedDataBlock *data = GetPropertyIsImportantByID(aProperty) + ? mImportantData : mData; + const nsCSSValue *val = data->ValueFor(aProperty); + return !!val; +} + +bool +Declaration::AppendValueToString(nsCSSPropertyID aProperty, + nsAString& aResult, + nsCSSValue::Serialization aSerialization) const +{ + MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT_no_shorthands, + "property ID out of range"); + + nsCSSCompressedDataBlock *data = GetPropertyIsImportantByID(aProperty) + ? mImportantData : mData; + const nsCSSValue *val = data->ValueFor(aProperty); + if (!val) { + return false; + } + + val->AppendToString(aProperty, aResult, aSerialization); + return true; +} + +static void +AppendSingleImageLayerPositionValue(const nsCSSValue& aPositionX, + const nsCSSValue& aPositionY, + const nsCSSPropertyID aTable[], + nsAString& aValue, + nsCSSValue::Serialization aSerialization) +{ + // We need to make sure that we don't serialize to an invalid 3-value form. + // The 3-value form is only valid if both edges are present. + const nsCSSValue &xEdge = aPositionX.GetArrayValue()->Item(0); + const nsCSSValue &xOffset = aPositionX.GetArrayValue()->Item(1); + const nsCSSValue &yEdge = aPositionY.GetArrayValue()->Item(0); + const nsCSSValue &yOffset = aPositionY.GetArrayValue()->Item(1); + bool xHasEdge = (eCSSUnit_Enumerated == xEdge.GetUnit()); + bool xHasBoth = xHasEdge && (eCSSUnit_Null != xOffset.GetUnit()); + bool yHasEdge = (eCSSUnit_Enumerated == yEdge.GetUnit()); + bool yHasBoth = yHasEdge && (eCSSUnit_Null != yOffset.GetUnit()); + + if (yHasBoth && !xHasEdge) { + // Output 4-value form by adding the x edge. + aValue.AppendLiteral("left "); + } + aPositionX.AppendToString(aTable[nsStyleImageLayers::positionX], + aValue, aSerialization); + + aValue.Append(char16_t(' ')); + + if (xHasBoth && !yHasEdge) { + // Output 4-value form by adding the y edge. + aValue.AppendLiteral("top "); + } + aPositionY.AppendToString(aTable[nsStyleImageLayers::positionY], + aValue, aSerialization); +} + +void +Declaration::GetImageLayerValue( + nsCSSCompressedDataBlock *data, + nsAString& aValue, + nsCSSValue::Serialization aSerialization, + const nsCSSPropertyID aTable[]) const +{ + // We know from our caller 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.) + + // Common CSS properties for both background & mask layer. + const nsCSSValueList *image = + data->ValueFor(aTable[nsStyleImageLayers::image])->GetListValue(); + const nsCSSValuePairList *repeat = + data->ValueFor(aTable[nsStyleImageLayers::repeat])->GetPairListValue(); + const nsCSSValueList *positionX = + data->ValueFor(aTable[nsStyleImageLayers::positionX])->GetListValue(); + const nsCSSValueList *positionY = + data->ValueFor(aTable[nsStyleImageLayers::positionY])->GetListValue(); + const nsCSSValueList *clip = + data->ValueFor(aTable[nsStyleImageLayers::clip])->GetListValue(); + const nsCSSValueList *origin = + data->ValueFor(aTable[nsStyleImageLayers::origin])->GetListValue(); + const nsCSSValuePairList *size = + data->ValueFor(aTable[nsStyleImageLayers::size])->GetPairListValue(); + + // Background layer property. + const nsCSSValueList *attachment = + (aTable[nsStyleImageLayers::attachment] == eCSSProperty_UNKNOWN)? + nullptr : + data->ValueFor(aTable[nsStyleImageLayers::attachment])->GetListValue(); + + // Mask layer properties. + const nsCSSValueList *composite = + (aTable[nsStyleImageLayers::composite] == eCSSProperty_UNKNOWN)? + nullptr : + data->ValueFor(aTable[nsStyleImageLayers::composite])->GetListValue(); + const nsCSSValueList *mode = + (aTable[nsStyleImageLayers::maskMode] == eCSSProperty_UNKNOWN)? + nullptr : + data->ValueFor(aTable[nsStyleImageLayers::maskMode])->GetListValue(); + + for (;;) { + // Serialize background-color at the beginning of the last item. + if (!image->mNext) { + if (aTable[nsStyleImageLayers::color] != eCSSProperty_UNKNOWN) { + AppendValueToString(aTable[nsStyleImageLayers::color], aValue, + aSerialization); + aValue.Append(char16_t(' ')); + } + } + + image->mValue.AppendToString(aTable[nsStyleImageLayers::image], aValue, + aSerialization); + + aValue.Append(char16_t(' ')); + repeat->mXValue.AppendToString(aTable[nsStyleImageLayers::repeat], aValue, + aSerialization); + if (repeat->mYValue.GetUnit() != eCSSUnit_Null) { + repeat->mYValue.AppendToString(aTable[nsStyleImageLayers::repeat], aValue, + aSerialization); + } + + if (attachment) { + aValue.Append(char16_t(' ')); + attachment->mValue.AppendToString(aTable[nsStyleImageLayers::attachment], + aValue, aSerialization); + } + + aValue.Append(char16_t(' ')); + AppendSingleImageLayerPositionValue(positionX->mValue, positionY->mValue, + aTable, 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(aTable[nsStyleImageLayers::size], aValue, + aSerialization); + aValue.Append(char16_t(' ')); + size->mYValue.AppendToString(aTable[nsStyleImageLayers::size], aValue, + aSerialization); + } + + MOZ_ASSERT(clip->mValue.GetUnit() == eCSSUnit_Enumerated && + origin->mValue.GetUnit() == eCSSUnit_Enumerated, + "should not have inherit/initial within list"); + + int32_t originDefaultValue = + (aTable == nsStyleImageLayers::kBackgroundLayerTable) + ? NS_STYLE_IMAGELAYER_ORIGIN_PADDING : NS_STYLE_IMAGELAYER_ORIGIN_BORDER; + if (clip->mValue.GetIntValue() != NS_STYLE_IMAGELAYER_CLIP_BORDER || + origin->mValue.GetIntValue() != originDefaultValue) { +#ifdef DEBUG + for (size_t i = 0; nsCSSProps::kImageLayerOriginKTable[i].mValue != -1; i++) { + // For each keyword & value in kOriginKTable, ensure that + // kBackgroundKTable has a matching entry at the same position. + MOZ_ASSERT(nsCSSProps::kImageLayerOriginKTable[i].mKeyword == + nsCSSProps::kBackgroundClipKTable[i].mKeyword); + MOZ_ASSERT(nsCSSProps::kImageLayerOriginKTable[i].mValue == + nsCSSProps::kBackgroundClipKTable[i].mValue); + } +#endif + static_assert(NS_STYLE_IMAGELAYER_CLIP_BORDER == + NS_STYLE_IMAGELAYER_ORIGIN_BORDER && + NS_STYLE_IMAGELAYER_CLIP_PADDING == + NS_STYLE_IMAGELAYER_ORIGIN_PADDING && + NS_STYLE_IMAGELAYER_CLIP_CONTENT == + NS_STYLE_IMAGELAYER_ORIGIN_CONTENT, + "mask-clip and mask-origin style constants must agree"); + aValue.Append(char16_t(' ')); + origin->mValue.AppendToString(aTable[nsStyleImageLayers::origin], aValue, + aSerialization); + + if (clip->mValue != origin->mValue) { + aValue.Append(char16_t(' ')); + clip->mValue.AppendToString(aTable[nsStyleImageLayers::clip], aValue, + aSerialization); + } + } + + if (composite) { + aValue.Append(char16_t(' ')); + composite->mValue.AppendToString(aTable[nsStyleImageLayers::composite], + aValue, aSerialization); + } + + if (mode) { + aValue.Append(char16_t(' ')); + mode->mValue.AppendToString(aTable[nsStyleImageLayers::maskMode], + aValue, aSerialization); + } + + image = image->mNext; + repeat = repeat->mNext; + positionX = positionX->mNext; + positionY = positionY->mNext; + clip = clip->mNext; + origin = origin->mNext; + size = size->mNext; + attachment = attachment ? attachment->mNext : nullptr; + composite = composite ? composite->mNext : nullptr; + mode = mode ? mode->mNext : nullptr; + + if (!image) { + // This layer is an background layer + if (aTable == nsStyleImageLayers::kBackgroundLayerTable) { + if (repeat || positionX || positionY || clip || origin || size || + attachment) { + // Uneven length lists, so can't be serialized as shorthand. + aValue.Truncate(); + return; + } + // This layer is an mask layer + } else { +#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND + MOZ_ASSERT(aTable == nsStyleImageLayers::kMaskLayerTable); +#else + MOZ_ASSERT_UNREACHABLE("Should never get here when mask-as-shorthand is disable"); +#endif + if (repeat || positionX || positionY || clip || origin || size || + composite || mode) { + // Uneven length lists, so can't be serialized as shorthand. + aValue.Truncate(); + return; + } + } + break; + } + + // This layer is an background layer + if (aTable == nsStyleImageLayers::kBackgroundLayerTable) { + if (!repeat || !positionX || !positionY || !clip || !origin || !size || + !attachment) { + // Uneven length lists, so can't be serialized as shorthand. + aValue.Truncate(); + return; + } + // This layer is an mask layer + } else { +#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND + MOZ_ASSERT(aTable == nsStyleImageLayers::kMaskLayerTable); +#else + MOZ_ASSERT_UNREACHABLE("Should never get here when mask-as-shorthand is disable"); +#endif + if (!repeat || !positionX || !positionY || !clip || !origin || !size || + !composite || !mode) { + // Uneven length lists, so can't be serialized as shorthand. + aValue.Truncate(); + return; + } + } + aValue.Append(char16_t(',')); + aValue.Append(char16_t(' ')); + } +} + +void +Declaration::GetImageLayerPositionValue( + nsCSSCompressedDataBlock *data, + nsAString& aValue, + nsCSSValue::Serialization aSerialization, + const nsCSSPropertyID aTable[]) const +{ + // 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. + const nsCSSValueList *positionX = + data->ValueFor(aTable[nsStyleImageLayers::positionX])->GetListValue(); + const nsCSSValueList *positionY = + data->ValueFor(aTable[nsStyleImageLayers::positionY])->GetListValue(); + for (;;) { + AppendSingleImageLayerPositionValue(positionX->mValue, positionY->mValue, + aTable, aValue, aSerialization); + positionX = positionX->mNext; + positionY = positionY->mNext; + + if (!positionX || !positionY) { + if (positionX || positionY) { + // Uneven length lists, so can't be serialized as shorthand. + aValue.Truncate(); + } + return; + } + aValue.Append(char16_t(',')); + aValue.Append(char16_t(' ')); + } +} + +void +Declaration::GetPropertyValueInternal( + nsCSSPropertyID 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, nonMatchingTokenStreamCount = 0; + CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aProperty, + CSSEnabledState::eForAllContent) { + if (*p == eCSSProperty__x_system_font) { + // The system-font subproperty doesn't count. + continue; + } + ++totalCount; + const nsCSSValue *val = mData->ValueFor(*p); + MOZ_ASSERT(!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) { + if (val->GetTokenStreamValue()->mShorthandPropertyID == aProperty) { + tokenStream = val; + ++matchingTokenStreamCount; + } else { + ++nonMatchingTokenStreamCount; + } + } + } + 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 || nonMatchingTokenStreamCount != 0) { + // Case (2): partially initial, inherit, unset or token stream. + 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 nsCSSPropertyID* subprops = + nsCSSProps::SubpropertyEntryFor(aProperty); + MOZ_ASSERT(nsCSSProps::GetStringValue(subprops[0]).Find("-top") != + kNotFound, "first subprop must be top"); + MOZ_ASSERT(nsCSSProps::GetStringValue(subprops[1]).Find("-right") != + kNotFound, "second subprop must be right"); + MOZ_ASSERT(nsCSSProps::GetStringValue(subprops[2]).Find("-bottom") != + kNotFound, "third subprop must be bottom"); + MOZ_ASSERT(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]) + }; + nsCSSValue::AppendSidesShorthandToString(subprops, vals, aValue, + aSerialization); + break; + } + case eCSSProperty_border_radius: + case eCSSProperty__moz_outline_radius: { + const nsCSSPropertyID* subprops = + nsCSSProps::SubpropertyEntryFor(aProperty); + const nsCSSValue* vals[4] = { + data->ValueFor(subprops[0]), + data->ValueFor(subprops[1]), + data->ValueFor(subprops[2]), + data->ValueFor(subprops[3]) + }; + nsCSSValue::AppendBasicShapeRadiusToString(subprops, vals, 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.AppendLiteral(" /"); + if (!widthDefault) { + aValue.Append(char16_t(' ')); + AppendValueToString(eCSSProperty_border_image_width, aValue, + aSerialization); + } + if (!outsetDefault) { + aValue.AppendLiteral(" / "); + 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 nsCSSPropertyID* subproptables[3] = { + nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_color), + nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_style), + nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_width) + }; + bool match = true; + for (const nsCSSPropertyID** subprops = subproptables, + **subprops_end = ArrayEnd(subproptables); + subprops < subprops_end; ++subprops) { + 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; + MOZ_FALLTHROUGH; + } + case eCSSProperty_border_top: + case eCSSProperty_border_right: + case eCSSProperty_border_bottom: + case eCSSProperty_border_left: + case eCSSProperty_border_inline_start: + case eCSSProperty_border_inline_end: + case eCSSProperty_border_block_start: + case eCSSProperty_border_block_end: + case eCSSProperty_column_rule: + case eCSSProperty_outline: { + const nsCSSPropertyID* subprops = + nsCSSProps::SubpropertyEntryFor(aProperty); + MOZ_ASSERT(StringEndsWith(nsCSSProps::GetStringValue(subprops[2]), + NS_LITERAL_CSTRING("-color")), + "third subprop must be the color property"); + const nsCSSValue *colorValue = data->ValueFor(subprops[2]); + bool isCurrentColor = + colorValue->GetUnit() == eCSSUnit_EnumColor && + colorValue->GetIntValue() == NS_COLOR_CURRENTCOLOR; + if (!AppendValueToString(subprops[0], aValue, aSerialization) || + !(aValue.Append(char16_t(' ')), + AppendValueToString(subprops[1], aValue, aSerialization)) || + // Don't output a third value when it's currentcolor. + !(isCurrentColor || + (aValue.Append(char16_t(' ')), + AppendValueToString(subprops[2], aValue, aSerialization)))) { + aValue.Truncate(); + } + break; + } + case eCSSProperty_background: { + GetImageLayerValue(data, aValue, aSerialization, + nsStyleImageLayers::kBackgroundLayerTable); + break; + } + case eCSSProperty_background_position: { + GetImageLayerPositionValue(data, aValue, aSerialization, + nsStyleImageLayers::kBackgroundLayerTable); + break; + } +#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND + case eCSSProperty_mask: { + GetImageLayerValue(data, aValue, aSerialization, + nsStyleImageLayers::kMaskLayerTable); + break; + } + case eCSSProperty_mask_position: { + GetImageLayerPositionValue(data, aValue, aSerialization, + nsStyleImageLayers::kMaskLayerTable); + break; + } +#endif + 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 *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 (systemFont && + systemFont->GetUnit() != eCSSUnit_None && + systemFont->GetUnit() != eCSSUnit_Null) { + if (style->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 || + 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 (sizeAdjust->GetUnit() != eCSSUnit_None || + featureSettings->GetUnit() != eCSSUnit_Normal || + languageOverride->GetUnit() != eCSSUnit_Normal || + fontKerning->GetIntValue() != NS_FONT_KERNING_AUTO || + fontSynthesis->GetUnit() != eCSSUnit_Enumerated || + fontSynthesis->GetIntValue() != + (NS_FONT_SYNTHESIS_WEIGHT | NS_FONT_SYNTHESIS_STYLE) || + fontVariantAlternates->GetUnit() != eCSSUnit_Normal || + fontVariantEastAsian->GetUnit() != eCSSUnit_Normal || + fontVariantLigatures->GetUnit() != eCSSUnit_Normal || + fontVariantNumeric->GetUnit() != eCSSUnit_Normal || + fontVariantPosition->GetUnit() != eCSSUnit_Normal) { + return; + } + + // only a normal or small-caps values of font-variant-caps can + // be represented in the font shorthand + if (fontVariantCaps->GetUnit() != eCSSUnit_Normal && + (fontVariantCaps->GetUnit() != eCSSUnit_Enumerated || + fontVariantCaps->GetIntValue() != NS_FONT_VARIANT_CAPS_SMALLCAPS)) { + return; + } + + if (style->GetUnit() != eCSSUnit_Enumerated || + style->GetIntValue() != NS_FONT_STYLE_NORMAL) { + style->AppendToString(eCSSProperty_font_style, aValue, + aSerialization); + aValue.Append(char16_t(' ')); + } + if (fontVariantCaps->GetUnit() != eCSSUnit_Normal) { + fontVariantCaps->AppendToString(eCSSProperty_font_variant_caps, 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(' ')); + } + if (stretch->GetUnit() != eCSSUnit_Enumerated || + stretch->GetIntValue() != NS_FONT_STRETCH_NORMAL) { + stretch->AppendToString(eCSSProperty_font_stretch, 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_font_variant: { + const nsCSSPropertyID *subprops = + nsCSSProps::SubpropertyEntryFor(aProperty); + const nsCSSValue *fontVariantLigatures = + data->ValueFor(eCSSProperty_font_variant_ligatures); + + // all subproperty values normal? system font? + bool normalLigs = true, normalNonLigs = true, systemFont = true, + hasSystem = false; + for (const nsCSSPropertyID *sp = subprops; *sp != eCSSProperty_UNKNOWN; sp++) { + const nsCSSValue *spVal = data->ValueFor(*sp); + bool isNormal = (spVal->GetUnit() == eCSSUnit_Normal); + if (*sp == eCSSProperty_font_variant_ligatures) { + normalLigs = normalLigs && isNormal; + } else { + normalNonLigs = normalNonLigs && isNormal; + } + bool isSystem = (spVal->GetUnit() == eCSSUnit_System_Font); + systemFont = systemFont && isSystem; + hasSystem = hasSystem || isSystem; + } + + bool ligsNone = + fontVariantLigatures->GetUnit() == eCSSUnit_None; + + // normal, none, or system font ==> single value + if ((normalLigs && normalNonLigs) || + (normalNonLigs && ligsNone) || + systemFont) { + fontVariantLigatures->AppendToString(eCSSProperty_font_variant_ligatures, + aValue, + aSerialization); + } else if (ligsNone || hasSystem) { + // ligatures none but other values are non-normal ==> empty + // at least one but not all values are system font ==> empty + return; + } else { + // iterate over and append non-normal values + bool appendSpace = false; + for (const nsCSSPropertyID *sp = subprops; + *sp != eCSSProperty_UNKNOWN; sp++) { + const nsCSSValue *spVal = data->ValueFor(*sp); + if (spVal && spVal->GetUnit() != eCSSUnit_Normal) { + if (appendSpace) { + aValue.Append(char16_t(' ')); + } else { + appendSpace = true; + } + spVal->AppendToString(*sp, aValue, aSerialization); + } + } + } + break; + } + case eCSSProperty_list_style: + if (AppendValueToString(eCSSProperty_list_style_position, aValue, + aSerialization)) { + aValue.Append(char16_t(' ')); + } + if (AppendValueToString(eCSSProperty_list_style_image, aValue, + aSerialization)) { + aValue.Append(char16_t(' ')); + } + AppendValueToString(eCSSProperty_list_style_type, 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: { + const nsCSSValue *decorationColor = + data->ValueFor(eCSSProperty_text_decoration_color); + const nsCSSValue *decorationStyle = + data->ValueFor(eCSSProperty_text_decoration_style); + + MOZ_ASSERT(decorationStyle->GetUnit() == eCSSUnit_Enumerated, + "bad text-decoration-style unit"); + + AppendValueToString(eCSSProperty_text_decoration_line, aValue, + aSerialization); + if (decorationStyle->GetIntValue() != + NS_STYLE_TEXT_DECORATION_STYLE_SOLID) { + aValue.Append(char16_t(' ')); + AppendValueToString(eCSSProperty_text_decoration_style, aValue, + aSerialization); + } + if (decorationColor->GetUnit() != eCSSUnit_EnumColor || + decorationColor->GetIntValue() != NS_COLOR_CURRENTCOLOR) { + aValue.Append(char16_t(' ')); + AppendValueToString(eCSSProperty_text_decoration_color, 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); + + MOZ_ASSERT(transDuration->GetUnit() == eCSSUnit_List || + transDuration->GetUnit() == eCSSUnit_ListDep, + "bad t-duration unit"); + MOZ_ASSERT(transTiming->GetUnit() == eCSSUnit_List || + transTiming->GetUnit() == eCSSUnit_ListDep, + "bad t-timing unit"); + MOZ_ASSERT(transDelay->GetUnit() == eCSSUnit_List || + transDelay->GetUnit() == eCSSUnit_ListDep, + "bad t-delay unit"); + + 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 { + MOZ_ASSERT(transProp->GetUnit() == eCSSUnit_List || + transProp->GetUnit() == eCSSUnit_ListDep, + "bad t-prop unit"); + 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 nsCSSPropertyID* subprops = + nsCSSProps::SubpropertyEntryFor(eCSSProperty_animation); + static const size_t numProps = 8; + MOZ_ASSERT(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]); + MOZ_ASSERT(values[i]->GetUnit() == eCSSUnit_List || + values[i]->GetUnit() == eCSSUnit_ListDep, + "bad a-duration unit"); + 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. + MOZ_ASSERT(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_columns: { + // Two values, column-count and column-width, separated by a space. + const nsCSSPropertyID* 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 nsCSSPropertyID* 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 nsCSSPropertyID* subprops = + nsCSSProps::SubpropertyEntryFor(aProperty); + MOZ_ASSERT(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 nsCSSPropertyID* subprops = + nsCSSProps::SubpropertyEntryFor(aProperty); + MOZ_ASSERT(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 nsCSSPropertyID* subprops = + nsCSSProps::SubpropertyEntryFor(aProperty); + MOZ_ASSERT(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; + } + + // The 'grid' shorthand has 3 different possibilities for syntax: + // #1 <'grid-template'> + // #2 <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>? + // #3 [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'> + case eCSSProperty_grid: { + const nsCSSValue& columnGapValue = + *data->ValueFor(eCSSProperty_grid_column_gap); + if (columnGapValue.GetUnit() != eCSSUnit_Pixel || + columnGapValue.GetFloatValue() != 0.0f) { + return; // Not serializable, bail. + } + const nsCSSValue& rowGapValue = + *data->ValueFor(eCSSProperty_grid_row_gap); + if (rowGapValue.GetUnit() != eCSSUnit_Pixel || + rowGapValue.GetFloatValue() != 0.0f) { + return; // Not serializable, bail. + } + 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); + + // grid-template-rows/areas:none + default grid-auto-columns + + // non-default row grid-auto-flow or grid-auto-rows. + // --> serialize as 'grid' syntax #3. + // (for default grid-auto-flow/rows we prefer to serialize to + // "none ['/' ...]" instead using syntax #2 or #1 below) + if (rowsValue.GetUnit() == eCSSUnit_None && + areasValue.GetUnit() == eCSSUnit_None && + autoColumnsValue.GetUnit() == eCSSUnit_Auto && + autoFlowValue.GetUnit() == eCSSUnit_Enumerated && + (autoFlowValue.GetIntValue() & NS_STYLE_GRID_AUTO_FLOW_ROW) && + (autoFlowValue.GetIntValue() != NS_STYLE_GRID_AUTO_FLOW_ROW || + autoRowsValue.GetUnit() != eCSSUnit_Auto)) { + aValue.AppendLiteral("auto-flow"); + if (autoFlowValue.GetIntValue() & NS_STYLE_GRID_AUTO_FLOW_DENSE) { + aValue.AppendLiteral(" dense"); + } + if (autoRowsValue.GetUnit() != eCSSUnit_Auto) { + aValue.Append(' '); + AppendValueToString(eCSSProperty_grid_auto_rows, + aValue, aSerialization); + } + aValue.AppendLiteral(" / "); + AppendValueToString(eCSSProperty_grid_template_columns, + aValue, aSerialization); + break; + } + + // grid-template-columns/areas:none + column grid-auto-flow + + // default grid-auto-rows. + // --> serialize as 'grid' syntax #2. + if (columnsValue.GetUnit() == eCSSUnit_None && + areasValue.GetUnit() == eCSSUnit_None && + autoRowsValue.GetUnit() == eCSSUnit_Auto && + autoFlowValue.GetUnit() == eCSSUnit_Enumerated && + (autoFlowValue.GetIntValue() & NS_STYLE_GRID_AUTO_FLOW_COLUMN)) { + AppendValueToString(eCSSProperty_grid_template_rows, + aValue, aSerialization); + aValue.AppendLiteral(" / auto-flow "); + if (autoFlowValue.GetIntValue() & NS_STYLE_GRID_AUTO_FLOW_DENSE) { + aValue.AppendLiteral("dense "); + } + AppendValueToString(eCSSProperty_grid_auto_columns, + aValue, aSerialization); + break; + } + + if (!(autoFlowValue.GetUnit() == eCSSUnit_Enumerated && + autoFlowValue.GetIntValue() == NS_STYLE_GRID_AUTO_FLOW_ROW && + autoColumnsValue.GetUnit() == eCSSUnit_Auto && + autoRowsValue.GetUnit() == eCSSUnit_Auto)) { + // Not serializable, bail. + return; + } + // Fall through to eCSSProperty_grid_template (syntax #1) + MOZ_FALLTHROUGH; + } + 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_rows, + aValue, aSerialization); + aValue.AppendLiteral(" / "); + AppendValueToString(eCSSProperty_grid_template_columns, + 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; + } + rowsItem = rowsValue.GetListValue(); + uint32_t row = 0; + for (;;) { + bool addSpaceSeparator = true; + nsCSSUnit unit = rowsItem->mValue.GetUnit(); + + if (unit == eCSSUnit_Null) { + // Empty or omitted <line-names>. Serializes to nothing. + addSpaceSeparator = false; // Avoid a double space. + + } else if (unit == eCSSUnit_List || unit == eCSSUnit_ListDep) { + // Non-empty <line-names> + aValue.Append('['); + rowsItem->mValue.AppendToString(eCSSProperty_grid_template_rows, + aValue, aSerialization); + aValue.Append(']'); + + } else { + nsStyleUtil::AppendEscapedCSSString(areas->mTemplates[row++], aValue); + aValue.Append(char16_t(' ')); + + // <track-size> + if (unit == eCSSUnit_Pair) { + // 'repeat()' isn't allowed with non-default 'grid-template-areas'. + aValue.Truncate(); + return; + } + 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(' ')); + } + } + if (columnsValue.GetUnit() != eCSSUnit_None) { + const nsCSSValueList* colsItem = columnsValue.GetListValue(); + colsItem = colsItem->mNext; // first value is <line-names> + for (; colsItem; colsItem = colsItem->mNext) { + if (colsItem->mValue.GetUnit() == eCSSUnit_Pair) { + // 'repeat()' isn't allowed with non-default 'grid-template-areas'. + aValue.Truncate(); + return; + } + colsItem = colsItem->mNext; // skip <line-names> + } + aValue.AppendLiteral(" / "); + AppendValueToString(eCSSProperty_grid_template_columns, + aValue, aSerialization); + } + break; + } + case eCSSProperty_place_content: + case eCSSProperty_place_items: + case eCSSProperty_place_self: { + const nsCSSPropertyID* subprops = + nsCSSProps::SubpropertyEntryFor(aProperty); + MOZ_ASSERT(subprops[2] == eCSSProperty_UNKNOWN, + "must have exactly two subproperties"); + auto IsSingleValue = [] (const nsCSSValue& aValue) { + switch (aValue.GetUnit()) { + case eCSSUnit_Auto: + case eCSSUnit_Inherit: + case eCSSUnit_Initial: + case eCSSUnit_Unset: + return true; + case eCSSUnit_Enumerated: + // return false if there is a fallback value or <overflow-position> + return aValue.GetIntValue() <= NS_STYLE_JUSTIFY_SPACE_EVENLY; + default: + MOZ_ASSERT_UNREACHABLE("Unexpected unit for CSS Align property val"); + return false; + } + }; + // Each value must be a single value (i.e. no fallback value and no + // <overflow-position>), otherwise it can't be represented as a shorthand + // value. ('first|last baseline' counts as a single value) + const nsCSSValue* align = data->ValueFor(subprops[0]); + const nsCSSValue* justify = data->ValueFor(subprops[1]); + if (!align || !IsSingleValue(*align) || + !justify || !IsSingleValue(*justify)) { + return; // Not serializable, bail. + } + MOZ_FALLTHROUGH; + } + case eCSSProperty_grid_gap: { + const nsCSSPropertyID* subprops = + nsCSSProps::SubpropertyEntryFor(aProperty); + MOZ_ASSERT(subprops[2] == eCSSProperty_UNKNOWN, + "must have exactly two subproperties"); + + nsAutoString val1, val2; + AppendValueToString(subprops[0], val1, aSerialization); + AppendValueToString(subprops[1], val2, aSerialization); + if (val1 == val2) { + aValue.Append(val1); + } else { + aValue.Append(val1); + aValue.Append(' '); + aValue.Append(val2); + } + break; + } + case eCSSProperty_text_emphasis: { + const nsCSSValue* emphasisStyle = + data->ValueFor(eCSSProperty_text_emphasis_style); + const nsCSSValue* emphasisColor = + data->ValueFor(eCSSProperty_text_emphasis_color); + bool isDefaultColor = emphasisColor->GetUnit() == eCSSUnit_EnumColor && + emphasisColor->GetIntValue() == NS_COLOR_CURRENTCOLOR; + + if (emphasisStyle->GetUnit() != eCSSUnit_None || isDefaultColor) { + AppendValueToString(eCSSProperty_text_emphasis_style, + aValue, aSerialization); + if (!isDefaultColor) { + aValue.Append(char16_t(' ')); + } + } + if (!isDefaultColor) { + AppendValueToString(eCSSProperty_text_emphasis_color, + aValue, aSerialization); + } + break; + } + case eCSSProperty__moz_transform: { + // shorthands that are just aliases with different parsing rules + const nsCSSPropertyID* subprops = + nsCSSProps::SubpropertyEntryFor(aProperty); + MOZ_ASSERT(subprops[1] == eCSSProperty_UNKNOWN, + "must have exactly one subproperty"); + AppendValueToString(subprops[0], aValue, aSerialization); + break; + } + case eCSSProperty_scroll_snap_type: { + const nsCSSValue& xValue = + *data->ValueFor(eCSSProperty_scroll_snap_type_x); + const nsCSSValue& yValue = + *data->ValueFor(eCSSProperty_scroll_snap_type_y); + if (xValue == yValue) { + AppendValueToString(eCSSProperty_scroll_snap_type_x, aValue, + aSerialization); + } + // If scroll-snap-type-x and scroll-snap-type-y are not equal, + // we don't have a shorthand that can express. Bail. + break; + } + case eCSSProperty__webkit_text_stroke: { + const nsCSSValue* strokeWidth = + data->ValueFor(eCSSProperty__webkit_text_stroke_width); + const nsCSSValue* strokeColor = + data->ValueFor(eCSSProperty__webkit_text_stroke_color); + bool isDefaultColor = strokeColor->GetUnit() == eCSSUnit_EnumColor && + strokeColor->GetIntValue() == NS_COLOR_CURRENTCOLOR; + + if (strokeWidth->GetUnit() != eCSSUnit_Integer || + strokeWidth->GetIntValue() != 0 || isDefaultColor) { + AppendValueToString(eCSSProperty__webkit_text_stroke_width, + aValue, aSerialization); + if (!isDefaultColor) { + aValue.Append(char16_t(' ')); + } + } + if (!isDefaultColor) { + AppendValueToString(eCSSProperty__webkit_text_stroke_color, + 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: + MOZ_ASSERT(false, "no other shorthands"); + break; + } +} + +bool +Declaration::GetPropertyIsImportantByID(nsCSSPropertyID 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, + CSSEnabledState::eForAllContent) { + 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(nsCSSPropertyID aProperty, + nsAutoString& aValue, + nsAString& aResult) const +{ + MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT, + "property enum out of range"); + MOZ_ASSERT((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 (GetPropertyIsImportantByID(aProperty)) { + aResult.AppendLiteral(" ! important"); + } + aResult.AppendLiteral("; "); +} + +void +Declaration::AppendVariableAndValueToString(const nsAString& aName, + nsAString& aResult) const +{ + nsAutoString localName; + localName.AppendLiteral("--"); + localName.Append(aName); + nsStyleUtil::AppendEscapedCSSIdent(localName, aResult); + 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 = + GetPropertyIsImportantByID(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; + AutoTArray<nsCSSPropertyID, 16> shorthandsUsed; + for (index = 0; index < count; index++) { + nsCSSPropertyID property = GetPropertyAt(index); + + if (property == eCSSPropertyExtra_variable) { + uint32_t variableIndex = mOrder[index] - eCSSProperty_COUNT; + AppendVariableAndValueToString(mVariableOrder[variableIndex], aString); + continue; + } + + if (!nsCSSProps::IsEnabled(property, CSSEnabledState::eForAllContent)) { + continue; + } + bool doneProperty = false; + + // If we already used this property in a shorthand, skip it. + if (shorthandsUsed.Length() > 0) { + for (const nsCSSPropertyID *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 nsCSSPropertyID *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. + nsCSSPropertyID shorthand = *shorthands; + + GetPropertyValueByID(shorthand, value); + + // in the system font case, skip over font-variant shorthand, since all + // subproperties are already dealt with via the font shorthand + if (shorthand == eCSSProperty_font_variant && + value.EqualsLiteral("-moz-use-system-font")) { + continue; + } + + // If GetPropertyValueByID gives us a non-empty string back, we can + // use that value; otherwise it's not possible to use this shorthand. + if (!value.IsEmpty()) { + AppendPropertyAndValueToString(shorthand, value, aString); + shorthandsUsed.AppendElement(shorthand); + doneProperty = true; + break; + } + + 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; + break; + } + } + } + if (doneProperty) + continue; + + MOZ_ASSERT(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 +/* virtual */ void +Declaration::List(FILE* out, int32_t aIndent) const +{ + const Rule* owningRule = GetOwningRule(); + if (owningRule) { + // More useful to print the selector and sheet URI too. + owningRule->List(out, aIndent); + return; + } + + nsAutoCString str; + for (int32_t index = aIndent; --index >= 0; ) { + str.AppendLiteral(" "); + } + + str.AppendLiteral("{ "); + nsAutoString s; + ToString(s); + AppendUTF16toUTF8(s, str); + str.AppendLiteral("}\n"); + fprintf_stderr(out, "%s", str.get()); +} +#endif + +bool +Declaration::GetNthProperty(uint32_t aIndex, nsAString& aReturn) const +{ + aReturn.Truncate(); + if (aIndex < mOrder.Length()) { + nsCSSPropertyID 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() +{ + MOZ_ASSERT(!mData && !mImportantData, "already initialized"); + mData = nsCSSCompressedDataBlock::CreateEmptyBlock(); +} + +size_t +Declaration::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const +{ + size_t n = aMallocSizeOf(this); + n += mOrder.ShallowSizeOfExcludingThis(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; +} + +void +Declaration::GetVariableValue(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::AddVariable(const nsAString& aName, + CSSVariableDeclarations::Type aType, + const nsString& aValue, + bool aIsImportant, + bool aOverrideImportant) +{ + MOZ_ASSERT(IsMutable()); + + nsTArray<nsString>::index_type index = mVariableOrder.IndexOf(aName); + if (index == nsTArray<nsString>::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::RemoveVariable(const nsAString& aName) +{ + if (mVariables) { + mVariables->Remove(aName); + } + if (mImportantVariables) { + mImportantVariables->Remove(aName); + } + nsTArray<nsString>::index_type index = mVariableOrder.IndexOf(aName); + if (index != nsTArray<nsString>::NoIndex) { + mOrder.RemoveElement(index + eCSSProperty_COUNT); + } +} + +bool +Declaration::GetVariableIsImportant(const nsAString& aName) const +{ + return mImportantVariables && mImportantVariables->Has(aName); +} + +} // namespace css +} // namespace mozilla |