/* -*- 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