/* -*- 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/Util.h"

#include "mozilla/css/Declaration.h"
#include "nsPrintfCString.h"

namespace mozilla {
namespace css {

// check that we can fit all the CSS properties into a uint8_t
// for the mOrder array - if not, might need to use uint16_t!
MOZ_STATIC_ASSERT(eCSSProperty_COUNT_no_shorthands - 1 <= UINT8_MAX,
                  "CSS longhand property numbers no longer fit in a uint8_t");

Declaration::Declaration()
  : mImmutable(false)
{
  MOZ_COUNT_CTOR(mozilla::css::Declaration);
}

Declaration::Declaration(const Declaration& aCopy)
  : mOrder(aCopy.mOrder),
    mData(aCopy.mData ? aCopy.mData->Clone() : nullptr),
    mImportantData(aCopy.mImportantData
                   ? aCopy.mImportantData->Clone() : nullptr),
    mImmutable(false)
{
  MOZ_COUNT_CTOR(mozilla::css::Declaration);
}

Declaration::~Declaration()
{
  MOZ_COUNT_DTOR(mozilla::css::Declaration);
}

void
Declaration::ValueAppended(nsCSSProperty aProperty)
{
  NS_ABORT_IF_FALSE(!mData && !mImportantData,
                    "should only be called while expanded");
  NS_ABORT_IF_FALSE(!nsCSSProps::IsShorthand(aProperty),
                    "shorthands forbidden");
  // order IS important for CSS, so remove and add to the end
  mOrder.RemoveElement(aProperty);
  mOrder.AppendElement(aProperty);
}

void
Declaration::RemoveProperty(nsCSSProperty aProperty)
{
  nsCSSExpandedDataBlock data;
  ExpandTo(&data);
  NS_ABORT_IF_FALSE(!mData && !mImportantData, "Expand didn't null things out");

  if (nsCSSProps::IsShorthand(aProperty)) {
    CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aProperty) {
      data.ClearLonghandProperty(*p);
      mOrder.RemoveElement(*p);
    }
  } else {
    data.ClearLonghandProperty(aProperty);
    mOrder.RemoveElement(aProperty);
  }

  CompressFrom(&data);
}

bool
Declaration::HasProperty(nsCSSProperty aProperty) const
{
  NS_ABORT_IF_FALSE(0 <= aProperty &&
                    aProperty < eCSSProperty_COUNT_no_shorthands,
                    "property ID out of range");

  nsCSSCompressedDataBlock *data = GetValueIsImportant(aProperty)
                                      ? mImportantData : mData;
  const nsCSSValue *val = data->ValueFor(aProperty);
  return !!val;
}

bool
Declaration::AppendValueToString(nsCSSProperty aProperty,
                                 nsAString& aResult) const
{
  NS_ABORT_IF_FALSE(0 <= aProperty &&
                    aProperty < eCSSProperty_COUNT_no_shorthands,
                    "property ID out of range");

  nsCSSCompressedDataBlock *data = GetValueIsImportant(aProperty)
                                      ? mImportantData : mData;
  const nsCSSValue *val = data->ValueFor(aProperty);
  if (!val) {
    return false;
  }

  val->AppendToString(aProperty, aResult);
  return true;
}

void
Declaration::GetValue(nsCSSProperty aProperty, nsAString& aValue) const
{
  aValue.Truncate(0);

  // simple properties are easy.
  if (!nsCSSProps::IsShorthand(aProperty)) {
    AppendValueToString(aProperty, aValue);
    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' and 'initial' 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.
  uint32_t totalCount = 0, importantCount = 0,
           initialCount = 0, inheritCount = 0;
  CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aProperty) {
    if (*p == eCSSProperty__x_system_font ||
         nsCSSProps::PropHasFlags(*p, CSS_PROPERTY_DIRECTIONAL_SOURCE)) {
      // The system-font subproperty and the *-source properties don't count.
      continue;
    }
    ++totalCount;
    const nsCSSValue *val = mData->ValueFor(*p);
    NS_ABORT_IF_FALSE(!val || !mImportantData || !mImportantData->ValueFor(*p),
                      "can't be in both blocks");
    if (!val && mImportantData) {
      ++importantCount;
      val = mImportantData->ValueFor(*p);
    }
    if (!val) {
      // Case (1) above: some subproperties not specified.
      return;
    }
    if (val->GetUnit() == eCSSUnit_Inherit) {
      ++inheritCount;
    } else if (val->GetUnit() == eCSSUnit_Initial) {
      ++initialCount;
    }
  }
  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);
    return;
  }
  if (inheritCount == totalCount) {
    // Simplify serialization below by serializing inherit up-front.
    nsCSSValue(eCSSUnit_Inherit).AppendToString(eCSSProperty_UNKNOWN, aValue);
    return;
  }
  if (initialCount != 0 || inheritCount != 0) {
    // Case (2): partially initial or inherit.
    return;
  }

  nsCSSCompressedDataBlock *data = importantCount ? mImportantData : mData;
  switch (aProperty) {
    case eCSSProperty_margin: 
    case eCSSProperty_padding: 
    case eCSSProperty_border_color: 
    case eCSSProperty_border_style: 
    case eCSSProperty_border_width: {
      const nsCSSProperty* subprops =
        nsCSSProps::SubpropertyEntryFor(aProperty);
      NS_ABORT_IF_FALSE(nsCSSProps::GetStringValue(subprops[0]).Find("-top") !=
                        kNotFound, "first subprop must be top");
      NS_ABORT_IF_FALSE(nsCSSProps::GetStringValue(subprops[1]).Find("-right") !=
                        kNotFound, "second subprop must be right");
      NS_ABORT_IF_FALSE(nsCSSProps::GetStringValue(subprops[2]).Find("-bottom") !=
                        kNotFound, "third subprop must be bottom");
      NS_ABORT_IF_FALSE(nsCSSProps::GetStringValue(subprops[3]).Find("-left") !=
                        kNotFound, "fourth subprop must be left");
      const nsCSSValue &topValue = *data->ValueFor(subprops[0]);
      const nsCSSValue &rightValue = *data->ValueFor(subprops[1]);
      const nsCSSValue &bottomValue = *data->ValueFor(subprops[2]);
      const nsCSSValue &leftValue = *data->ValueFor(subprops[3]);

      NS_ABORT_IF_FALSE(topValue.GetUnit() != eCSSUnit_Null, "null top");
      topValue.AppendToString(subprops[0], aValue);
      if (topValue != rightValue || topValue != leftValue ||
          topValue != bottomValue) {
        aValue.Append(PRUnichar(' '));
        NS_ABORT_IF_FALSE(rightValue.GetUnit() != eCSSUnit_Null, "null right");
        rightValue.AppendToString(subprops[1], aValue);
        if (topValue != bottomValue || rightValue != leftValue) {
          aValue.Append(PRUnichar(' '));
          NS_ABORT_IF_FALSE(bottomValue.GetUnit() != eCSSUnit_Null, "null bottom");
          bottomValue.AppendToString(subprops[2], aValue);
          if (rightValue != leftValue) {
            aValue.Append(PRUnichar(' '));
            NS_ABORT_IF_FALSE(leftValue.GetUnit() != eCSSUnit_Null, "null left");
            leftValue.AppendToString(subprops[3], aValue);
          }
        }
      }
      break;
    }
    case eCSSProperty_border_radius:
    case eCSSProperty__moz_outline_radius: {
      const nsCSSProperty* subprops =
        nsCSSProps::SubpropertyEntryFor(aProperty);
      const nsCSSValue* vals[4] = {
        data->ValueFor(subprops[0]),
        data->ValueFor(subprops[1]),
        data->ValueFor(subprops[2]),
        data->ValueFor(subprops[3])
      };

      // For compatibility, only write a slash and the y-values
      // if they're not identical to the x-values.
      bool needY = false;
      for (int i = 0; i < 4; i++) {
        if (vals[i]->GetUnit() == eCSSUnit_Pair) {
          needY = true;
          vals[i]->GetPairValue().mXValue.AppendToString(subprops[i], aValue);
        } else {
          vals[i]->AppendToString(subprops[i], aValue);
        }
        if (i < 3)
          aValue.Append(PRUnichar(' '));
      }

      if (needY) {
        aValue.AppendLiteral(" / ");
        for (int i = 0; i < 4; i++) {
          if (vals[i]->GetUnit() == eCSSUnit_Pair) {
            vals[i]->GetPairValue().mYValue.AppendToString(subprops[i], aValue);
          } else {
            vals[i]->AppendToString(subprops[i], aValue);
          }
          if (i < 3)
            aValue.Append(PRUnichar(' '));
        }
      }
      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);

      bool sliceDefault = data->HasDefaultBorderImageSlice();
      bool widthDefault = data->HasDefaultBorderImageWidth();
      bool outsetDefault = data->HasDefaultBorderImageOutset();

      if (!sliceDefault || !widthDefault || !outsetDefault) {
        aValue.Append(PRUnichar(' '));
        AppendValueToString(eCSSProperty_border_image_slice, aValue);
        if (!widthDefault || !outsetDefault) {
          aValue.Append(NS_LITERAL_STRING(" /"));
          if (!widthDefault) {
            aValue.Append(PRUnichar(' '));
            AppendValueToString(eCSSProperty_border_image_width, aValue);
          }
          if (!outsetDefault) {
            aValue.Append(NS_LITERAL_STRING(" / "));
            AppendValueToString(eCSSProperty_border_image_outset, aValue);
          }
        }
      }

      bool repeatDefault = data->HasDefaultBorderImageRepeat();
      if (!repeatDefault) {
        aValue.Append(PRUnichar(' '));
        AppendValueToString(eCSSProperty_border_image_repeat, aValue);
      }
      break;
    }
    case eCSSProperty_border: {
      // If we have a non-default value for any of the properties that
      // this shorthand sets but cannot specify, we have to return the
      // empty string.
      if (data->ValueFor(eCSSProperty_border_image_source)->GetUnit() !=
            eCSSUnit_None ||
          !data->HasDefaultBorderImageSlice() ||
          !data->HasDefaultBorderImageWidth() ||
          !data->HasDefaultBorderImageOutset() ||
          !data->HasDefaultBorderImageRepeat() ||
          data->ValueFor(eCSSProperty_border_top_colors)->GetUnit() !=
            eCSSUnit_None ||
          data->ValueFor(eCSSProperty_border_right_colors)->GetUnit() !=
            eCSSUnit_None ||
          data->ValueFor(eCSSProperty_border_bottom_colors)->GetUnit() !=
            eCSSUnit_None ||
          data->ValueFor(eCSSProperty_border_left_colors)->GetUnit() !=
            eCSSUnit_None) {
        break;
      }

      const nsCSSProperty* subproptables[3] = {
        nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_color),
        nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_style),
        nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_width)
      };
      bool match = true;
      for (const nsCSSProperty** subprops = subproptables,
               **subprops_end = ArrayEnd(subproptables);
           subprops < subprops_end; ++subprops) {
        // Check only the first four subprops in each table, since the
        // others are extras for dimensional box properties.
        const nsCSSValue *firstSide = data->ValueFor((*subprops)[0]);
        for (int32_t side = 1; side < 4; ++side) {
          const nsCSSValue *otherSide =
            data->ValueFor((*subprops)[side]);
          if (*firstSide != *otherSide)
            match = false;
        }
      }
      if (!match) {
        // We can't express what we have in the border shorthand
        break;
      }
      // tweak aProperty and fall through
      aProperty = eCSSProperty_border_top;
    }
    case eCSSProperty_border_top:
    case eCSSProperty_border_right:
    case eCSSProperty_border_bottom:
    case eCSSProperty_border_left:
    case eCSSProperty_border_start:
    case eCSSProperty_border_end:
    case eCSSProperty__moz_column_rule:
    case eCSSProperty_outline: {
      const nsCSSProperty* subprops =
        nsCSSProps::SubpropertyEntryFor(aProperty);
      NS_ABORT_IF_FALSE(StringEndsWith(nsCSSProps::GetStringValue(subprops[2]),
                                       NS_LITERAL_CSTRING("-color")) ||
                        StringEndsWith(nsCSSProps::GetStringValue(subprops[2]),
                                       NS_LITERAL_CSTRING("-color-value")),
                        "third subprop must be the color property");
      const nsCSSValue *colorValue = data->ValueFor(subprops[2]);
      bool isMozUseTextColor =
        colorValue->GetUnit() == eCSSUnit_Enumerated &&
        colorValue->GetIntValue() == NS_STYLE_COLOR_MOZ_USE_TEXT_COLOR;
      if (!AppendValueToString(subprops[0], aValue) ||
          !(aValue.Append(PRUnichar(' ')),
            AppendValueToString(subprops[1], aValue)) ||
          // Don't output a third value when it's -moz-use-text-color.
          !(isMozUseTextColor ||
            (aValue.Append(PRUnichar(' ')),
             AppendValueToString(subprops[2], aValue)))) {
        aValue.Truncate();
      }
      break;
    }
    case eCSSProperty_margin_left:
    case eCSSProperty_margin_right:
    case eCSSProperty_margin_start:
    case eCSSProperty_margin_end:
    case eCSSProperty_padding_left:
    case eCSSProperty_padding_right:
    case eCSSProperty_padding_start:
    case eCSSProperty_padding_end:
    case eCSSProperty_border_left_color:
    case eCSSProperty_border_left_style:
    case eCSSProperty_border_left_width:
    case eCSSProperty_border_right_color:
    case eCSSProperty_border_right_style:
    case eCSSProperty_border_right_width:
    case eCSSProperty_border_start_color:
    case eCSSProperty_border_start_style:
    case eCSSProperty_border_start_width:
    case eCSSProperty_border_end_color:
    case eCSSProperty_border_end_style:
    case eCSSProperty_border_end_width: {
      const nsCSSProperty* subprops =
        nsCSSProps::SubpropertyEntryFor(aProperty);
      NS_ABORT_IF_FALSE(subprops[3] == eCSSProperty_UNKNOWN,
                        "not box property with physical vs. logical cascading");
      AppendValueToString(subprops[0], aValue);
      break;
    }
    case eCSSProperty_background: {
      // We know from above that all subproperties were specified.
      // However, we still can't represent that in the shorthand unless
      // they're all lists of the same length.  So if they're different
      // lengths, we need to bail out.
      // We also need to bail out if an item has background-clip and
      // background-origin that are different and not the default
      // values.  (We omit them if they're both default.)
      const nsCSSValueList *image =
        data->ValueFor(eCSSProperty_background_image)->
        GetListValue();
      const nsCSSValuePairList *repeat =
        data->ValueFor(eCSSProperty_background_repeat)->
        GetPairListValue();
      const nsCSSValueList *attachment =
        data->ValueFor(eCSSProperty_background_attachment)->
        GetListValue();
      const nsCSSValueList *position =
        data->ValueFor(eCSSProperty_background_position)->
        GetListValue();
      const nsCSSValueList *clip =
        data->ValueFor(eCSSProperty_background_clip)->
        GetListValue();
      const nsCSSValueList *origin =
        data->ValueFor(eCSSProperty_background_origin)->
        GetListValue();
      const nsCSSValuePairList *size =
        data->ValueFor(eCSSProperty_background_size)->
        GetPairListValue();
      for (;;) {
        image->mValue.AppendToString(eCSSProperty_background_image, aValue);
        aValue.Append(PRUnichar(' '));
        repeat->mXValue.AppendToString(eCSSProperty_background_repeat, aValue);
        if (repeat->mYValue.GetUnit() != eCSSUnit_Null) {
          repeat->mYValue.AppendToString(eCSSProperty_background_repeat, aValue);
        }
        aValue.Append(PRUnichar(' '));
        attachment->mValue.AppendToString(eCSSProperty_background_attachment,
                                          aValue);
        aValue.Append(PRUnichar(' '));
        position->mValue.AppendToString(eCSSProperty_background_position,
                                        aValue);
        
        if (size->mXValue.GetUnit() != eCSSUnit_Auto ||
            size->mYValue.GetUnit() != eCSSUnit_Auto) {
          aValue.Append(PRUnichar(' '));
          aValue.Append(PRUnichar('/'));
          aValue.Append(PRUnichar(' '));
          size->mXValue.AppendToString(eCSSProperty_background_size, aValue);
          aValue.Append(PRUnichar(' '));
          size->mYValue.AppendToString(eCSSProperty_background_size, aValue);
        }

        NS_ABORT_IF_FALSE(clip->mValue.GetUnit() == eCSSUnit_Enumerated &&
                          origin->mValue.GetUnit() == eCSSUnit_Enumerated,
                          "should not have inherit/initial within list");

        if (clip->mValue.GetIntValue() != NS_STYLE_BG_CLIP_BORDER ||
            origin->mValue.GetIntValue() != NS_STYLE_BG_ORIGIN_PADDING) {
          // The shorthand only has a single clip/origin value which sets
          // both properties.  So if they're different (and non-default),
          // we can't represent the state using the shorthand.
          MOZ_STATIC_ASSERT(NS_STYLE_BG_CLIP_BORDER ==
                            NS_STYLE_BG_ORIGIN_BORDER &&
                            NS_STYLE_BG_CLIP_PADDING ==
                            NS_STYLE_BG_ORIGIN_PADDING &&
                            NS_STYLE_BG_CLIP_CONTENT ==
                            NS_STYLE_BG_ORIGIN_CONTENT,
                            "bg-clip and bg-origin style constants must agree");
          if (clip->mValue != origin->mValue) {
            aValue.Truncate();
            return;
          }

          aValue.Append(PRUnichar(' '));
          clip->mValue.AppendToString(eCSSProperty_background_clip, aValue);
        }

        image = image->mNext;
        repeat = repeat->mNext;
        attachment = attachment->mNext;
        position = position->mNext;
        clip = clip->mNext;
        origin = origin->mNext;
        size = size->mNext;

        if (!image) {
          if (repeat || attachment || position || clip || origin || size) {
            // Uneven length lists, so can't be serialized as shorthand.
            aValue.Truncate();
            return;
          }
          break;
        }
        if (!repeat || !attachment || !position || !clip || !origin || !size) {
          // Uneven length lists, so can't be serialized as shorthand.
          aValue.Truncate();
          return;
        }
        aValue.Append(PRUnichar(','));
        aValue.Append(PRUnichar(' '));
      }

      aValue.Append(PRUnichar(' '));
      AppendValueToString(eCSSProperty_background_color, aValue);
      break;
    }
    case eCSSProperty_font: {
      // systemFont might not be present; the others are guaranteed to be
      // based on the shorthand check at the beginning of the function
      const nsCSSValue *systemFont =
        data->ValueFor(eCSSProperty__x_system_font);
      const nsCSSValue &style =
        *data->ValueFor(eCSSProperty_font_style);
      const nsCSSValue &variant =
        *data->ValueFor(eCSSProperty_font_variant);
      const nsCSSValue &weight =
        *data->ValueFor(eCSSProperty_font_weight);
      const nsCSSValue &size =
        *data->ValueFor(eCSSProperty_font_size);
      const nsCSSValue &lh =
        *data->ValueFor(eCSSProperty_line_height);
      const nsCSSValue &family =
        *data->ValueFor(eCSSProperty_font_family);
      const nsCSSValue &stretch =
        *data->ValueFor(eCSSProperty_font_stretch);
      const nsCSSValue &sizeAdjust =
        *data->ValueFor(eCSSProperty_font_size_adjust);
      const nsCSSValue &featureSettings =
        *data->ValueFor(eCSSProperty_font_feature_settings);
      const nsCSSValue &languageOverride =
        *data->ValueFor(eCSSProperty_font_language_override);

      if (systemFont &&
          systemFont->GetUnit() != eCSSUnit_None &&
          systemFont->GetUnit() != eCSSUnit_Null) {
        if (style.GetUnit() != eCSSUnit_System_Font ||
            variant.GetUnit() != eCSSUnit_System_Font ||
            weight.GetUnit() != eCSSUnit_System_Font ||
            size.GetUnit() != eCSSUnit_System_Font ||
            lh.GetUnit() != eCSSUnit_System_Font ||
            family.GetUnit() != eCSSUnit_System_Font ||
            stretch.GetUnit() != eCSSUnit_System_Font ||
            sizeAdjust.GetUnit() != eCSSUnit_System_Font ||
            featureSettings.GetUnit() != eCSSUnit_System_Font ||
            languageOverride.GetUnit() != eCSSUnit_System_Font) {
          // This can't be represented as a shorthand.
          return;
        }
        systemFont->AppendToString(eCSSProperty__x_system_font, aValue);
      } else {
        // The font-stretch, font-size-adjust,
        // -moz-font-feature-settings, and -moz-font-language-override
        // properties are reset by this shorthand property to their
        // initial values, but can't be represented in its syntax.
        if (stretch.GetUnit() != eCSSUnit_Enumerated ||
            stretch.GetIntValue() != NS_STYLE_FONT_STRETCH_NORMAL ||
            sizeAdjust.GetUnit() != eCSSUnit_None ||
            featureSettings.GetUnit() != eCSSUnit_Normal ||
            languageOverride.GetUnit() != eCSSUnit_Normal) {
          return;
        }

        if (style.GetUnit() != eCSSUnit_Enumerated ||
            style.GetIntValue() != NS_FONT_STYLE_NORMAL) {
          style.AppendToString(eCSSProperty_font_style, aValue);
          aValue.Append(PRUnichar(' '));
        }
        if (variant.GetUnit() != eCSSUnit_Enumerated ||
            variant.GetIntValue() != NS_FONT_VARIANT_NORMAL) {
          variant.AppendToString(eCSSProperty_font_variant, aValue);
          aValue.Append(PRUnichar(' '));
        }
        if (weight.GetUnit() != eCSSUnit_Enumerated ||
            weight.GetIntValue() != NS_FONT_WEIGHT_NORMAL) {
          weight.AppendToString(eCSSProperty_font_weight, aValue);
          aValue.Append(PRUnichar(' '));
        }
        size.AppendToString(eCSSProperty_font_size, aValue);
        if (lh.GetUnit() != eCSSUnit_Normal) {
          aValue.Append(PRUnichar('/'));
          lh.AppendToString(eCSSProperty_line_height, aValue);
        }
        aValue.Append(PRUnichar(' '));
        family.AppendToString(eCSSProperty_font_family, aValue);
      }
      break;
    }
    case eCSSProperty_list_style:
      if (AppendValueToString(eCSSProperty_list_style_type, aValue))
        aValue.Append(PRUnichar(' '));
      if (AppendValueToString(eCSSProperty_list_style_position, aValue))
        aValue.Append(PRUnichar(' '));
      AppendValueToString(eCSSProperty_list_style_image, aValue);
      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);
      break;
    }
    case eCSSProperty_text_decoration: {
      // If text-decoration-color or text-decoration-style isn't initial value,
      // we cannot serialize the text-decoration shorthand value.
      const nsCSSValue &decorationColor =
        *data->ValueFor(eCSSProperty_text_decoration_color);
      const nsCSSValue &decorationStyle =
        *data->ValueFor(eCSSProperty_text_decoration_style);

      NS_ABORT_IF_FALSE(decorationStyle.GetUnit() == eCSSUnit_Enumerated,
                        nsPrintfCString("bad text-decoration-style unit %d",
                                        decorationStyle.GetUnit()).get());

      if (decorationColor.GetUnit() != eCSSUnit_Enumerated ||
          decorationColor.GetIntValue() != NS_STYLE_COLOR_MOZ_USE_TEXT_COLOR ||
          decorationStyle.GetIntValue() !=
            NS_STYLE_TEXT_DECORATION_STYLE_SOLID) {
        return;
      }

      const nsCSSValue &textBlink =
        *data->ValueFor(eCSSProperty_text_blink);
      const nsCSSValue &decorationLine =
        *data->ValueFor(eCSSProperty_text_decoration_line);

      NS_ABORT_IF_FALSE(textBlink.GetUnit() == eCSSUnit_Enumerated,
                        nsPrintfCString("bad text-blink unit %d",
                                        textBlink.GetUnit()).get());
      NS_ABORT_IF_FALSE(decorationLine.GetUnit() == eCSSUnit_Enumerated,
                        nsPrintfCString("bad text-decoration-line unit %d",
                                        decorationLine.GetUnit()).get());

      bool blinkNone = (textBlink.GetIntValue() == NS_STYLE_TEXT_BLINK_NONE);
      bool lineNone =
        (decorationLine.GetIntValue() == NS_STYLE_TEXT_DECORATION_LINE_NONE);

      if (blinkNone && lineNone) {
        AppendValueToString(eCSSProperty_text_decoration_line, aValue);
      } else {
        if (!blinkNone) {
          AppendValueToString(eCSSProperty_text_blink, aValue);
        }
        if (!lineNone) {
          if (!aValue.IsEmpty()) {
            aValue.Append(PRUnichar(' '));
          }
          AppendValueToString(eCSSProperty_text_decoration_line, aValue);
        }
      }
      break;
    }
    case eCSSProperty_transition: {
      const nsCSSValue &transProp =
        *data->ValueFor(eCSSProperty_transition_property);
      const nsCSSValue &transDuration =
        *data->ValueFor(eCSSProperty_transition_duration);
      const nsCSSValue &transTiming =
        *data->ValueFor(eCSSProperty_transition_timing_function);
      const nsCSSValue &transDelay =
        *data->ValueFor(eCSSProperty_transition_delay);

      NS_ABORT_IF_FALSE(transDuration.GetUnit() == eCSSUnit_List ||
                        transDuration.GetUnit() == eCSSUnit_ListDep,
                        nsPrintfCString("bad t-duration unit %d",
                                        transDuration.GetUnit()).get());
      NS_ABORT_IF_FALSE(transTiming.GetUnit() == eCSSUnit_List ||
                        transTiming.GetUnit() == eCSSUnit_ListDep,
                        nsPrintfCString("bad t-timing unit %d",
                                        transTiming.GetUnit()).get());
      NS_ABORT_IF_FALSE(transDelay.GetUnit() == eCSSUnit_List ||
                        transDelay.GetUnit() == eCSSUnit_ListDep,
                        nsPrintfCString("bad t-delay unit %d",
                                        transDelay.GetUnit()).get());

      const nsCSSValueList* dur = transDuration.GetListValue();
      const nsCSSValueList* tim = transTiming.GetListValue();
      const nsCSSValueList* del = transDelay.GetListValue();

      if (transProp.GetUnit() == eCSSUnit_None ||
          transProp.GetUnit() == eCSSUnit_All) {
        // If any of the other three lists has more than one element,
        // we can't use the shorthand.
        if (!dur->mNext && !tim->mNext && !del->mNext) {
          transProp.AppendToString(eCSSProperty_transition_property, aValue);
          aValue.Append(PRUnichar(' '));
          dur->mValue.AppendToString(eCSSProperty_transition_duration,aValue);
          aValue.Append(PRUnichar(' '));
          tim->mValue.AppendToString(eCSSProperty_transition_timing_function,
                                     aValue);
          aValue.Append(PRUnichar(' '));
          del->mValue.AppendToString(eCSSProperty_transition_delay, aValue);
          aValue.Append(PRUnichar(' '));
        } else {
          aValue.Truncate();
        }
      } else {
        NS_ABORT_IF_FALSE(transProp.GetUnit() == eCSSUnit_List ||
                          transProp.GetUnit() == eCSSUnit_ListDep,
                          nsPrintfCString("bad t-prop unit %d",
                                          transProp.GetUnit()).get());
        const nsCSSValueList* pro = transProp.GetListValue();
        for (;;) {
          pro->mValue.AppendToString(eCSSProperty_transition_property,
                                        aValue);
          aValue.Append(PRUnichar(' '));
          dur->mValue.AppendToString(eCSSProperty_transition_duration,
                                        aValue);
          aValue.Append(PRUnichar(' '));
          tim->mValue.AppendToString(eCSSProperty_transition_timing_function,
                                        aValue);
          aValue.Append(PRUnichar(' '));
          del->mValue.AppendToString(eCSSProperty_transition_delay,
                                        aValue);
          pro = pro->mNext;
          dur = dur->mNext;
          tim = tim->mNext;
          del = del->mNext;
          if (!pro || !dur || !tim || !del) {
            break;
          }
          aValue.AppendLiteral(", ");
        }
        if (pro || dur || tim || del) {
          // Lists not all the same length, can't use shorthand.
          aValue.Truncate();
        }
      }
      break;
    }
    case eCSSProperty_animation: {
      const nsCSSProperty* subprops =
        nsCSSProps::SubpropertyEntryFor(eCSSProperty_animation);
      static const size_t numProps = 7;
      NS_ABORT_IF_FALSE(subprops[numProps] == eCSSProperty_UNKNOWN,
                        "unexpected number of subproperties");
      const nsCSSValue* values[numProps];
      const nsCSSValueList* lists[numProps];

      for (uint32_t i = 0; i < numProps; ++i) {
        values[i] = data->ValueFor(subprops[i]);
        NS_ABORT_IF_FALSE(values[i]->GetUnit() == eCSSUnit_List ||
                          values[i]->GetUnit() == eCSSUnit_ListDep,
                          nsPrintfCString("bad a-duration unit %d",
                                          values[i]->GetUnit()).get());
        lists[i] = values[i]->GetListValue();
      }

      for (;;) {
        // We must serialize 'animation-name' last in case it has
        // a value that conflicts with one of the other keyword properties.
        NS_ABORT_IF_FALSE(subprops[numProps - 1] ==
                            eCSSProperty_animation_name,
                          "animation-name must be last");
        bool done = false;
        for (uint32_t i = 0;;) {
          lists[i]->mValue.AppendToString(subprops[i], aValue);
          lists[i] = lists[i]->mNext;
          if (!lists[i]) {
            done = true;
          }
          if (++i == numProps) {
            break;
          }
          aValue.Append(PRUnichar(' '));
        }
        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);
      break;
    }
    case eCSSProperty__moz_columns: {
      // Two values, column-count and column-width, separated by a space.
      const nsCSSProperty* subprops =
        nsCSSProps::SubpropertyEntryFor(aProperty);
      AppendValueToString(subprops[0], aValue);
      aValue.Append(PRUnichar(' '));
      AppendValueToString(subprops[1], aValue);
      break;
    }
#ifdef MOZ_FLEXBOX
    case eCSSProperty_flex: {
      // flex-grow, flex-shrink, flex-basis, separated by single space
      const nsCSSProperty* subprops =
        nsCSSProps::SubpropertyEntryFor(aProperty);

      AppendValueToString(subprops[0], aValue);
      aValue.Append(PRUnichar(' '));
      AppendValueToString(subprops[1], aValue);
      aValue.Append(PRUnichar(' '));
      AppendValueToString(subprops[2], aValue);
      break;
    }
#endif // MOZ_FLEXBOX
    case eCSSProperty__moz_transform: {
      // shorthands that are just aliases with different parsing rules
      const nsCSSProperty* subprops =
        nsCSSProps::SubpropertyEntryFor(aProperty);
      NS_ABORT_IF_FALSE(subprops[1] == eCSSProperty_UNKNOWN,
                        "must have exactly one subproperty");
      AppendValueToString(subprops[0], aValue);
      break;
    }
    default:
      NS_ABORT_IF_FALSE(false, "no other shorthands");
      break;
  }
}

bool
Declaration::GetValueIsImportant(const nsAString& aProperty) const
{
  nsCSSProperty propID = nsCSSProps::LookupProperty(aProperty, nsCSSProps::eAny);
  if (propID == eCSSProperty_UNKNOWN) {
    return false;
  }
  return GetValueIsImportant(propID);
}

bool
Declaration::GetValueIsImportant(nsCSSProperty aProperty) const
{
  if (!mImportantData)
    return false;

  // Calling ValueFor is inefficient, but we can assume '!important' is rare.

  if (!nsCSSProps::IsShorthand(aProperty)) {
    return mImportantData->ValueFor(aProperty) != nullptr;
  }

  CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aProperty) {
    if (*p == eCSSProperty__x_system_font) {
      // The system_font subproperty doesn't count.
      continue;
    }
    if (!mImportantData->ValueFor(*p)) {
      return false;
    }
  }
  return true;
}

void
Declaration::AppendPropertyAndValueToString(nsCSSProperty aProperty,
                                            nsAutoString& aValue,
                                            nsAString& aResult) const
{
  NS_ABORT_IF_FALSE(0 <= aProperty && aProperty < eCSSProperty_COUNT,
                    "property enum out of range");
  NS_ABORT_IF_FALSE((aProperty < eCSSProperty_COUNT_no_shorthands) ==
                    aValue.IsEmpty(),
                    "aValue should be given for shorthands but not longhands");
  AppendASCIItoUTF16(nsCSSProps::GetStringValue(aProperty), aResult);
  aResult.AppendLiteral(": ");
  if (aValue.IsEmpty())
    AppendValueToString(aProperty, aResult);
  else
    aResult.Append(aValue);
  if (GetValueIsImportant(aProperty)) {
    aResult.AppendLiteral(" ! important");
  }
  aResult.AppendLiteral("; ");
}

void
Declaration::ToString(nsAString& aString) const
{
  // Someone cares about this declaration's contents, so don't let it
  // change from under them.  See e.g. bug 338679.
  SetImmutable();

  nsCSSCompressedDataBlock *systemFontData =
    GetValueIsImportant(eCSSProperty__x_system_font) ? mImportantData : mData;
  const nsCSSValue *systemFont =
    systemFontData->ValueFor(eCSSProperty__x_system_font);
  const bool haveSystemFont = systemFont &&
                                systemFont->GetUnit() != eCSSUnit_None &&
                                systemFont->GetUnit() != eCSSUnit_Null;
  bool didSystemFont = false;

  int32_t count = mOrder.Length();
  int32_t index;
  nsAutoTArray<nsCSSProperty, 16> shorthandsUsed;
  for (index = 0; index < count; index++) {
    nsCSSProperty property = OrderValueAt(index);
    bool doneProperty = false;

    // If we already used this property in a shorthand, skip it.
    if (shorthandsUsed.Length() > 0) {
      for (const nsCSSProperty *shorthands =
             nsCSSProps::ShorthandsContaining(property);
           *shorthands != eCSSProperty_UNKNOWN; ++shorthands) {
        if (shorthandsUsed.Contains(*shorthands)) {
          doneProperty = true;
          break;
        }
      }
      if (doneProperty)
        continue;
    }

    // Try to use this property in a shorthand.
    nsAutoString value;
    for (const nsCSSProperty *shorthands =
           nsCSSProps::ShorthandsContaining(property);
         *shorthands != eCSSProperty_UNKNOWN; ++shorthands) {
      // ShorthandsContaining returns the shorthands in order from those
      // that contain the most subproperties to those that contain the
      // least, which is exactly the order we want to test them.
      nsCSSProperty shorthand = *shorthands;

      // If GetValue gives us a non-empty string back, we can use that
      // value; otherwise it's not possible to use this shorthand.
      GetValue(shorthand, value);
      if (!value.IsEmpty()) {
        AppendPropertyAndValueToString(shorthand, value, aString);
        shorthandsUsed.AppendElement(shorthand);
        doneProperty = true;
        break;
      }

      NS_ABORT_IF_FALSE(shorthand != eCSSProperty_font ||
                        *(shorthands + 1) == eCSSProperty_UNKNOWN,
                        "font should always be the only containing shorthand");
      if (shorthand == eCSSProperty_font) {
        if (haveSystemFont && !didSystemFont) {
          // Output the shorthand font declaration that we will
          // partially override later.  But don't add it to
          // |shorthandsUsed|, since we will have to override it.
          systemFont->AppendToString(eCSSProperty__x_system_font, value);
          AppendPropertyAndValueToString(eCSSProperty_font, value, aString);
          value.Truncate();
          didSystemFont = true;
        }

        // That we output the system font is enough for this property if:
        //   (1) it's the hidden system font subproperty (which either
        //       means we output it or we don't have it), or
        //   (2) its value is the hidden system font value and it matches
        //       the hidden system font subproperty in importance, and
        //       we output the system font subproperty.
        const nsCSSValue *val = systemFontData->ValueFor(property);
        if (property == eCSSProperty__x_system_font ||
            (haveSystemFont && val && val->GetUnit() == eCSSUnit_System_Font)) {
          doneProperty = true;
        }
      }
    }
    if (doneProperty)
      continue;

    NS_ABORT_IF_FALSE(value.IsEmpty(), "value should be empty now");
    AppendPropertyAndValueToString(property, value, aString);
  }
  if (! aString.IsEmpty()) {
    // if the string is not empty, we have trailing whitespace we
    // should remove
    aString.Truncate(aString.Length() - 1);
  }
}

#ifdef DEBUG
void
Declaration::List(FILE* out, int32_t aIndent) const
{
  for (int32_t index = aIndent; --index >= 0; ) fputs("  ", out);

  fputs("{ ", out);
  nsAutoString s;
  ToString(s);
  fputs(NS_ConvertUTF16toUTF8(s).get(), out);
  fputs("}", out);
}
#endif

bool
Declaration::GetNthProperty(uint32_t aIndex, nsAString& aReturn) const
{
  aReturn.Truncate();
  if (aIndex < mOrder.Length()) {
    nsCSSProperty property = OrderValueAt(aIndex);
    if (0 <= property) {
      AppendASCIItoUTF16(nsCSSProps::GetStringValue(property), aReturn);
      return true;
    }
  }
  return false;
}

void
Declaration::InitializeEmpty()
{
  NS_ABORT_IF_FALSE(!mData && !mImportantData, "already initialized");
  mData = nsCSSCompressedDataBlock::CreateEmptyBlock();
}

Declaration*
Declaration::EnsureMutable()
{
  NS_ABORT_IF_FALSE(mData, "should only be called when not expanded");
  if (!IsMutable()) {
    return new Declaration(*this);
  } else {
    return this;
  }
}

size_t
Declaration::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const
{
  size_t n = aMallocSizeOf(this);
  n += mOrder.SizeOfExcludingThis(aMallocSizeOf);
  n += mData          ? mData         ->SizeOfIncludingThis(aMallocSizeOf) : 0;
  n += mImportantData ? mImportantData->SizeOfIncludingThis(aMallocSizeOf) : 0;
  return n;
}

} // namespace mozilla::css
} // namespace mozilla
