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