/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=78: */
/* 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/. */

/*
 * a node in the lexicographic tree of rules that match an element,
 * responsible for converting the rules' information into computed style
 */

#include <algorithm>

#include "mozilla/ArrayUtils.h"
#include "mozilla/Assertions.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Function.h"
#include "mozilla/dom/AnimationEffectReadOnlyBinding.h" // for PlaybackDirection
#include "mozilla/Likely.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/OperatorNewExtensions.h"
#include "mozilla/Unused.h"

#include "mozilla/css/Declaration.h"

#include "nsAlgorithm.h" // for clamped()
#include "nsRuleNode.h"
#include "nscore.h"
#include "nsIWidget.h"
#include "nsIPresShell.h"
#include "nsFontMetrics.h"
#include "gfxFont.h"
#include "nsCSSAnonBoxes.h"
#include "nsCSSPseudoElements.h"
#include "nsThemeConstants.h"
#include "PLDHashTable.h"
#include "nsStyleContext.h"
#include "nsStyleSet.h"
#include "nsStyleStruct.h"
#include "nsSize.h"
#include "nsRuleData.h"
#include "nsIStyleRule.h"
#include "nsBidiUtils.h"
#include "nsStyleStructInlines.h"
#include "nsCSSProps.h"
#include "nsTArray.h"
#include "nsContentUtils.h"
#include "CSSCalc.h"
#include "nsPrintfCString.h"
#include "nsRenderingContext.h"
#include "nsStyleUtil.h"
#include "nsIDocument.h"
#include "prtime.h"
#include "CSSVariableResolver.h"
#include "nsCSSParser.h"
#include "CounterStyleManager.h"
#include "nsCSSPropertyIDSet.h"
#include "mozilla/RuleNodeCacheConditions.h"
#include "nsDeviceContext.h"
#include "nsQueryObject.h"
#include "nsUnicodeProperties.h"

#if defined(_MSC_VER) || defined(__MINGW32__)
#include <malloc.h>
#ifdef _MSC_VER
#define alloca _alloca
#endif
#endif
#ifdef SOLARIS
#include <alloca.h>
#endif

using std::max;
using std::min;
using namespace mozilla;
using namespace mozilla::dom;

namespace mozilla {

enum UnsetAction
{
  eUnsetInitial,
  eUnsetInherit
};

} // namespace mozilla

void*
nsConditionalResetStyleData::GetConditionalStyleData(nsStyleStructID aSID,
                               nsStyleContext* aStyleContext) const
{
  Entry* e = static_cast<Entry*>(mEntries[aSID]);
  MOZ_ASSERT(e, "if mConditionalBits bit is set, we must have at least one "
                "conditional style struct");
  do {
    if (e->mConditions.Matches(aStyleContext)) {
      void* data = e->mStyleStruct;

      // For reset structs with conditions, we cache the data on the
      // style context.
      // Tell the style context that it doesn't own the data
      aStyleContext->AddStyleBit(GetBitForSID(aSID));
      aStyleContext->SetStyle(aSID, data);

      return data;
    }
    e = e->mNext;
  } while (e);
  return nullptr;
}

// Creates and returns an imgRequestProxy based on the specified
// value in aValue.
static imgRequestProxy*
GetImageRequest(nsPresContext* aPresContext, const nsCSSValue& aValue)
{
  return aValue.GetImageValue(aPresContext->Document());
}

// Creates an imgRequestProxy based on the specified value in
// aValue and calls aCallback with it.  If the nsPresContext
// is static (e.g. for printing), then a static request (i.e.
// showing the first frame, without animation) will be created.
// (The expectation is then that aCallback will set the resulting
// imgRequestProxy in a style struct somewhere.)
static void
SetImageRequest(function<void(imgRequestProxy*)> aCallback,
                nsPresContext* aPresContext,
                const nsCSSValue& aValue)
{
  RefPtr<imgRequestProxy> req =
    aValue.GetPossiblyStaticImageValue(aPresContext->Document(),
                                       aPresContext);
  aCallback(req);
}

static void
SetStyleImageRequest(function<void(nsStyleImageRequest*)> aCallback,
                     nsPresContext* aPresContext,
                     const nsCSSValue& aValue,
                     nsStyleImageRequest::Mode aModeFlags =
                       nsStyleImageRequest::Mode::Track)
{
  SetImageRequest([&](imgRequestProxy* aProxy) {
    RefPtr<nsStyleImageRequest> request;
    if (aProxy) {
      css::ImageValue* imageValue = aValue.GetImageStructValue();
      ImageTracker* imageTracker =
        (aModeFlags & nsStyleImageRequest::Mode::Track)
        ? aPresContext->Document()->ImageTracker()
        : nullptr;
      request =
        new nsStyleImageRequest(aModeFlags, aProxy, imageValue, imageTracker);
    }
    aCallback(request);
  }, aPresContext, aValue);
}

template<typename ReferenceBox>
static void
SetStyleShapeSourceToCSSValue(StyleShapeSource<ReferenceBox>* aShapeSource,
                              const nsCSSValue* aValue,
                              nsStyleContext* aStyleContext,
                              nsPresContext* aPresContext,
                              RuleNodeCacheConditions& aConditions);

/* Helper function to convert a CSS <position> specified value into its
 * computed-style form. */
static void
ComputePositionValue(nsStyleContext* aStyleContext,
                     const nsCSSValue& aValue,
                     Position& aComputedValue,
                     RuleNodeCacheConditions& aConditions);

/*
 * For storage of an |nsRuleNode|'s children in a PLDHashTable.
 */

struct ChildrenHashEntry : public PLDHashEntryHdr {
  // key is |mRuleNode->GetKey()|
  nsRuleNode *mRuleNode;
};

/* static */ PLDHashNumber
nsRuleNode::ChildrenHashHashKey(const void *aKey)
{
  const nsRuleNode::Key *key =
    static_cast<const nsRuleNode::Key*>(aKey);
  // Disagreement on importance and level for the same rule is extremely
  // rare, so hash just on the rule.
  return PLDHashTable::HashVoidPtrKeyStub(key->mRule);
}

/* static */ bool
nsRuleNode::ChildrenHashMatchEntry(const PLDHashEntryHdr *aHdr,
                                   const void *aKey)
{
  const ChildrenHashEntry *entry =
    static_cast<const ChildrenHashEntry*>(aHdr);
  const nsRuleNode::Key *key =
    static_cast<const nsRuleNode::Key*>(aKey);
  return entry->mRuleNode->GetKey() == *key;
}

/* static */ const PLDHashTableOps
nsRuleNode::ChildrenHashOps = {
  // It's probably better to allocate the table itself using malloc and
  // free rather than the pres shell's arena because the table doesn't
  // grow very often and the pres shell's arena doesn't recycle very
  // large size allocations.
  ChildrenHashHashKey,
  ChildrenHashMatchEntry,
  PLDHashTable::MoveEntryStub,
  PLDHashTable::ClearEntryStub,
  nullptr
};


// EnsureBlockDisplay:
// Never change display:none or display:contents *ever*, otherwise:
//  - if the display value (argument) is not a block-type
//    then we set it to a valid block display value
//  - For enforcing the floated/positioned element CSS2 rules
//  - We allow the behavior of "list-item" to be customized.
//    CSS21 says that position/float do not convert 'list-item' to 'block',
//    but it explicitly does not define whether 'list-item' should be
//    converted to block *on the root node*. To allow for flexibility
//    (so that we don't have to support a list-item root node), this method
//    lets the caller pick either behavior, using the 'aConvertListItem' arg.
//    Reference: http://www.w3.org/TR/CSS21/visuren.html#dis-pos-flo
/* static */
void
nsRuleNode::EnsureBlockDisplay(StyleDisplay& display,
                               bool aConvertListItem /* = false */)
{
  // see if the display value is already a block
  switch (display) {
  case StyleDisplay::ListItem:
    if (aConvertListItem) {
      display = StyleDisplay::Block;
      break;
    } // else, fall through to share the 'break' for non-changing display vals
    MOZ_FALLTHROUGH;
  case StyleDisplay::None:
  case StyleDisplay::Contents:
    // never change display:none or display:contents *ever*
  case StyleDisplay::Table:
  case StyleDisplay::Block:
  case StyleDisplay::Flex:
  case StyleDisplay::WebkitBox:
  case StyleDisplay::Grid:
    // do not muck with these at all - already blocks
    // This is equivalent to nsStyleDisplay::IsBlockOutside.  (XXX Maybe we
    // should just call that?)
    // This needs to match the check done in
    // nsCSSFrameConstructor::FindMathMLData for <math>.
    break;

  case StyleDisplay::InlineTable:
    // make inline tables into tables
    display = StyleDisplay::Table;
    break;

  case StyleDisplay::InlineFlex:
    // make inline flex containers into flex containers
    display = StyleDisplay::Flex;
    break;

  case StyleDisplay::WebkitInlineBox:
    // make -webkit-inline-box containers into -webkit-box containers
    display = StyleDisplay::WebkitBox;
    break;

  case StyleDisplay::InlineGrid:
    // make inline grid containers into grid containers
    display = StyleDisplay::Grid;
    break;

  default:
    // make it a block
    display = StyleDisplay::Block;
  }
}

// EnsureInlineDisplay:
//  - if the display value (argument) is not an inline type
//    then we set it to a valid inline display value
/* static */
void
nsRuleNode::EnsureInlineDisplay(StyleDisplay& display)
{
  // see if the display value is already inline
  switch (display) {
    case StyleDisplay::Block:
      display = StyleDisplay::InlineBlock;
      break;
    case StyleDisplay::Table:
      display = StyleDisplay::InlineTable;
      break;
    case StyleDisplay::Flex:
      display = StyleDisplay::InlineFlex;
      break;
    case StyleDisplay::WebkitBox:
      display = StyleDisplay::WebkitInlineBox;
      break;
    case StyleDisplay::Grid:
      display = StyleDisplay::InlineGrid;
      break;
    case StyleDisplay::Box:
      display = StyleDisplay::InlineBox;
      break;
    case StyleDisplay::Stack:
      display = StyleDisplay::InlineStack;
      break;
    default:
      break; // Do nothing
  }
}

static nscoord CalcLengthWith(const nsCSSValue& aValue,
                              nscoord aFontSize,
                              const nsStyleFont* aStyleFont,
                              nsStyleContext* aStyleContext,
                              nsPresContext* aPresContext,
                              bool aUseProvidedRootEmSize,
                              bool aUseUserFontSet,
                              RuleNodeCacheConditions& aConditions);

struct CalcLengthCalcOps : public css::BasicCoordCalcOps,
                           public css::NumbersAlreadyNormalizedOps
{
  // All of the parameters to CalcLengthWith except aValue.
  const nscoord mFontSize;
  const nsStyleFont* const mStyleFont;
  nsStyleContext* const mStyleContext;
  nsPresContext* const mPresContext;
  const bool mUseProvidedRootEmSize;
  const bool mUseUserFontSet;
  RuleNodeCacheConditions& mConditions;

  CalcLengthCalcOps(nscoord aFontSize, const nsStyleFont* aStyleFont,
                    nsStyleContext* aStyleContext, nsPresContext* aPresContext,
                    bool aUseProvidedRootEmSize, bool aUseUserFontSet,
                    RuleNodeCacheConditions& aConditions)
    : mFontSize(aFontSize),
      mStyleFont(aStyleFont),
      mStyleContext(aStyleContext),
      mPresContext(aPresContext),
      mUseProvidedRootEmSize(aUseProvidedRootEmSize),
      mUseUserFontSet(aUseUserFontSet),
      mConditions(aConditions)
  {
  }

  result_type ComputeLeafValue(const nsCSSValue& aValue)
  {
    return CalcLengthWith(aValue, mFontSize, mStyleFont,
                          mStyleContext, mPresContext, mUseProvidedRootEmSize,
                          mUseUserFontSet, mConditions);
  }
};

static inline nscoord ScaleCoordRound(const nsCSSValue& aValue, float aFactor)
{
  return NSToCoordRoundWithClamp(aValue.GetFloatValue() * aFactor);
}

static inline nscoord ScaleViewportCoordTrunc(const nsCSSValue& aValue,
                                              nscoord aViewportSize)
{
  // For units (like percentages and viewport units) where authors might
  // repeatedly use a value and expect some multiple of the value to be
  // smaller than a container, we need to use floor rather than round.
  // We need to use division by 100.0 rather than multiplication by 0.1f
  // to avoid introducing error.
  return NSToCoordTruncClamped(aValue.GetFloatValue() *
                               aViewportSize / 100.0f);
}

already_AddRefed<nsFontMetrics>
GetMetricsFor(nsPresContext* aPresContext,
              nsStyleContext* aStyleContext,
              const nsStyleFont* aStyleFont,
              nscoord aFontSize, // overrides value from aStyleFont
              bool aUseUserFontSet)
{
  nsFont font = aStyleFont->mFont;
  font.size = aFontSize;
  gfxFont::Orientation orientation = gfxFont::eHorizontal;
  if (aStyleContext) {
    WritingMode wm(aStyleContext);
    if (wm.IsVertical() && !wm.IsSideways()) {
      orientation = gfxFont::eVertical;
    }
  }
  nsFontMetrics::Params params;
  params.language = aStyleFont->mLanguage;
  params.explicitLanguage = aStyleFont->mExplicitLanguage;
  params.orientation = orientation;
  params.userFontSet =
    aUseUserFontSet ? aPresContext->GetUserFontSet() : nullptr;
  params.textPerf = aPresContext->GetTextPerfMetrics();
  return aPresContext->DeviceContext()->GetMetricsFor(font, params);
}


static nsSize CalcViewportUnitsScale(nsPresContext* aPresContext)
{
  // The caller is making use of viewport units, so notify the pres context
  // that it will need to rebuild the rule tree if the size of the viewport
  // changes.
  aPresContext->SetUsesViewportUnits(true);

  // The default (when we have 'overflow: auto' on the root element, or
  // trivially for 'overflow: hidden' since we never have scrollbars in that
  // case) is to define the scale of the viewport units without considering
  // scrollbars.
  nsSize viewportSize(aPresContext->GetVisibleArea().Size());

  // Check for 'overflow: scroll' styles on the root scroll frame. If we find
  // any, the standard requires us to take scrollbars into account.
  nsIScrollableFrame* scrollFrame =
    aPresContext->PresShell()->GetRootScrollFrameAsScrollable();
  if (scrollFrame) {
    ScrollbarStyles styles(scrollFrame->GetScrollbarStyles());

    if (styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL ||
        styles.mVertical == NS_STYLE_OVERFLOW_SCROLL) {
      // Gather scrollbar size information.
      nsRenderingContext context(
        aPresContext->PresShell()->CreateReferenceRenderingContext());
      nsMargin sizes(scrollFrame->GetDesiredScrollbarSizes(aPresContext, &context));

      if (styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL) {
        // 'overflow-x: scroll' means we must consider the horizontal scrollbar,
        // which affects the scale of viewport height units.
        viewportSize.height -= sizes.TopBottom();
      }

      if (styles.mVertical == NS_STYLE_OVERFLOW_SCROLL) {
        // 'overflow-y: scroll' means we must consider the vertical scrollbar,
        // which affects the scale of viewport width units.
        viewportSize.width -= sizes.LeftRight();
      }
    }
  }

  return viewportSize;
}

// If |aStyleFont| is nullptr, aStyleContext->StyleFont() is used.
//
// In case that |aValue| is rem unit, if |aStyleContext| is null, callers must
// specify a valid |aStyleFont| and |aUseProvidedRootEmSize| must be true so
// that we can get the length from |aStyleFont|.
static nscoord CalcLengthWith(const nsCSSValue& aValue,
                              nscoord aFontSize,
                              const nsStyleFont* aStyleFont,
                              nsStyleContext* aStyleContext,
                              nsPresContext* aPresContext,
                              bool aUseProvidedRootEmSize,
                              // aUseUserFontSet should always be true
                              // except when called from
                              // CalcLengthWithInitialFont.
                              bool aUseUserFontSet,
                              RuleNodeCacheConditions& aConditions)
{
  NS_ASSERTION(aValue.IsLengthUnit() || aValue.IsCalcUnit(),
               "not a length or calc unit");
  NS_ASSERTION(aStyleFont || aStyleContext,
               "Must have style data");
  NS_ASSERTION(aStyleContext || aUseProvidedRootEmSize,
               "Must have style context or specify aUseProvidedRootEmSize");
  NS_ASSERTION(aPresContext, "Must have prescontext");

  if (aValue.IsFixedLengthUnit()) {
    return aValue.GetFixedLength(aPresContext);
  }
  if (aValue.IsPixelLengthUnit()) {
    return aValue.GetPixelLength();
  }
  if (aValue.IsCalcUnit()) {
    // For properties for which lengths are the *only* units accepted in
    // calc(), we can handle calc() here and just compute a final
    // result.  We ensure that we don't get to this code for other
    // properties by not calling CalcLength in those cases:  SetCoord
    // only calls CalcLength for a calc when it is appropriate to do so.
    CalcLengthCalcOps ops(aFontSize, aStyleFont,
                          aStyleContext, aPresContext,
                          aUseProvidedRootEmSize, aUseUserFontSet,
                          aConditions);
    return css::ComputeCalc(aValue, ops);
  }
  switch (aValue.GetUnit()) {
    // nsPresContext::SetVisibleArea and
    // nsPresContext::MediaFeatureValuesChanged handle dynamic changes
    // of the basis for viewport units by rebuilding the rule tree and
    // style context tree.  Not caching them in the rule tree wouldn't
    // be sufficient to handle these changes because we also need a way
    // to get rid of cached values in the style context tree without any
    // changes in specified style.  We can either do this by not caching
    // in the rule tree and then throwing away the style context tree
    // for dynamic viewport size changes, or by allowing caching in the
    // rule tree and using the existing rebuild style data path that
    // throws away the style context and the rule tree.
    // Thus we do cache viewport units in the rule tree.  This allows us
    // to benefit from the performance advantages of the rule tree
    // (e.g., faster dynamic changes on other things, like transforms)
    // and allows us not to need an additional code path, in exchange
    // for an increased cost to dynamic changes to the viewport size
    // when viewport units are in use.
    case eCSSUnit_ViewportWidth: {
      nscoord viewportWidth = CalcViewportUnitsScale(aPresContext).width;
      return ScaleViewportCoordTrunc(aValue, viewportWidth);
    }
    case eCSSUnit_ViewportHeight: {
      nscoord viewportHeight = CalcViewportUnitsScale(aPresContext).height;
      return ScaleViewportCoordTrunc(aValue, viewportHeight);
    }
    case eCSSUnit_ViewportMin: {
      nsSize vuScale(CalcViewportUnitsScale(aPresContext));
      nscoord viewportMin = min(vuScale.width, vuScale.height);
      return ScaleViewportCoordTrunc(aValue, viewportMin);
    }
    case eCSSUnit_ViewportMax: {
      nsSize vuScale(CalcViewportUnitsScale(aPresContext));
      nscoord viewportMax = max(vuScale.width, vuScale.height);
      return ScaleViewportCoordTrunc(aValue, viewportMax);
    }
    // While we could deal with 'rem' units correctly by simply not
    // caching any data that uses them in the rule tree, it's valuable
    // to store them in the rule tree (for faster dynamic changes of
    // other things).  And since the font size of the root element
    // changes rarely, we instead handle dynamic changes to the root
    // element's font size by rebuilding all style data in
    // nsCSSFrameConstructor::RestyleElement.
    case eCSSUnit_RootEM: {
      aPresContext->SetUsesRootEMUnits(true);
      nscoord rootFontSize;

      // NOTE: Be very careful with |styleFont|, since we haven't added any
      // conditions to aConditions or set it to uncacheable yet, so we don't
      // want to introduce any dependencies on aStyleContext's data here.
      const nsStyleFont *styleFont =
        aStyleFont ? aStyleFont : aStyleContext->StyleFont();

      if (aUseProvidedRootEmSize) {
        // We should use the provided aFontSize as the reference length to
        // scale. This only happens when we are calculating font-size or
        // an equivalent (scriptminsize or CalcLengthWithInitialFont) on
        // the root element, in which case aFontSize is already the
        // value we want.
        if (aFontSize == -1) {
          // XXX Should this be styleFont->mSize instead to avoid taking
          // minfontsize prefs into account?
          aFontSize = styleFont->mFont.size;
        }
        rootFontSize = aFontSize;
      } else if (aStyleContext && !aStyleContext->GetParent()) {
        // This is the root element (XXX we don't really know this, but
        // nsRuleNode::SetFont makes the same assumption!), so we should
        // use StyleFont on this context to get the root element's
        // font size.
        rootFontSize = styleFont->mFont.size;
      } else {
        // This is not the root element or we are calculating something other
        // than font size, so rem is relative to the root element's font size.
        // Find the root style context by walking up the style context tree.
        nsStyleContext* rootStyle = aStyleContext;
        while (rootStyle->GetParent()) {
          rootStyle = rootStyle->GetParent();
        }

        const nsStyleFont *rootStyleFont = rootStyle->StyleFont();
        rootFontSize = rootStyleFont->mFont.size;
      }

      return ScaleCoordRound(aValue, float(rootFontSize));
    }
    default:
      // Fall through to the code for units that can't be stored in the
      // rule tree because they depend on font data.
      break;
  }
  // Common code for units that depend on the element's font data and
  // thus can't be stored in the rule tree:
  const nsStyleFont *styleFont =
    aStyleFont ? aStyleFont : aStyleContext->StyleFont();
  if (aFontSize == -1) {
    // XXX Should this be styleFont->mSize instead to avoid taking minfontsize
    // prefs into account?
    aFontSize = styleFont->mFont.size;
  }
  switch (aValue.GetUnit()) {
    case eCSSUnit_EM: {
      if (aValue.GetFloatValue() == 0.0f) {
        // Don't call SetFontSizeDependency for '0em'.
        return 0;
      }
      // CSS2.1 specifies that this unit scales to the computed font
      // size, not the em-width in the font metrics, despite the name.
      aConditions.SetFontSizeDependency(aFontSize);
      return ScaleCoordRound(aValue, float(aFontSize));
    }
    case eCSSUnit_XHeight: {
      aPresContext->SetUsesExChUnits(true);
      RefPtr<nsFontMetrics> fm =
        GetMetricsFor(aPresContext, aStyleContext, styleFont,
                      aFontSize, aUseUserFontSet);
      aConditions.SetUncacheable();
      return ScaleCoordRound(aValue, float(fm->XHeight()));
    }
    case eCSSUnit_Char: {
      aPresContext->SetUsesExChUnits(true);
      RefPtr<nsFontMetrics> fm =
        GetMetricsFor(aPresContext, aStyleContext, styleFont,
                      aFontSize, aUseUserFontSet);
      gfxFloat zeroWidth =
        fm->GetThebesFontGroup()->GetFirstValidFont()->
          GetMetrics(fm->Orientation()).zeroOrAveCharWidth;

      aConditions.SetUncacheable();
      return ScaleCoordRound(aValue, ceil(aPresContext->AppUnitsPerDevPixel() *
                                          zeroWidth));
    }
    default:
      NS_NOTREACHED("unexpected unit");
      break;
  }
  return 0;
}

/* static */ nscoord
nsRuleNode::CalcLength(const nsCSSValue& aValue,
                       nsStyleContext* aStyleContext,
                       nsPresContext* aPresContext,
                       RuleNodeCacheConditions& aConditions)
{
  NS_ASSERTION(aStyleContext, "Must have style data");

  return CalcLengthWith(aValue, -1, nullptr,
                        aStyleContext, aPresContext,
                        false, true, aConditions);
}

/* Inline helper function to redirect requests to CalcLength. */
static inline nscoord CalcLength(const nsCSSValue& aValue,
                                 nsStyleContext* aStyleContext,
                                 nsPresContext* aPresContext,
                                 RuleNodeCacheConditions& aConditions)
{
  return nsRuleNode::CalcLength(aValue, aStyleContext,
                                aPresContext, aConditions);
}

/* static */ nscoord
nsRuleNode::CalcLengthWithInitialFont(nsPresContext* aPresContext,
                                      const nsCSSValue& aValue)
{
  nsStyleFont defaultFont(aPresContext); // FIXME: best language?
  RuleNodeCacheConditions conditions;
  return CalcLengthWith(aValue, -1, &defaultFont,
                        nullptr, aPresContext,
                        true, false, conditions);
}

struct LengthPercentPairCalcOps : public css::NumbersAlreadyNormalizedOps
{
  typedef nsRuleNode::ComputedCalc result_type;

  LengthPercentPairCalcOps(nsStyleContext* aContext,
                           nsPresContext* aPresContext,
                           RuleNodeCacheConditions& aConditions)
    : mContext(aContext),
      mPresContext(aPresContext),
      mConditions(aConditions),
      mHasPercent(false) {}

  nsStyleContext* mContext;
  nsPresContext* mPresContext;
  RuleNodeCacheConditions& mConditions;
  bool mHasPercent;

  result_type ComputeLeafValue(const nsCSSValue& aValue)
  {
    if (aValue.GetUnit() == eCSSUnit_Percent) {
      mHasPercent = true;
      return result_type(0, aValue.GetPercentValue());
    }
    return result_type(CalcLength(aValue, mContext, mPresContext,
                                  mConditions),
                       0.0f);
  }

  result_type
  MergeAdditive(nsCSSUnit aCalcFunction,
                result_type aValue1, result_type aValue2)
  {
    if (aCalcFunction == eCSSUnit_Calc_Plus) {
      return result_type(NSCoordSaturatingAdd(aValue1.mLength,
                                              aValue2.mLength),
                         aValue1.mPercent + aValue2.mPercent);
    }
    MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Minus,
               "min() and max() are not allowed in calc() on transform");
    return result_type(NSCoordSaturatingSubtract(aValue1.mLength,
                                                 aValue2.mLength, 0),
                       aValue1.mPercent - aValue2.mPercent);
  }

  result_type
  MergeMultiplicativeL(nsCSSUnit aCalcFunction,
                       float aValue1, result_type aValue2)
  {
    MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Times_L,
               "unexpected unit");
    return result_type(NSCoordSaturatingMultiply(aValue2.mLength, aValue1),
                       aValue1 * aValue2.mPercent);
  }

  result_type
  MergeMultiplicativeR(nsCSSUnit aCalcFunction,
                       result_type aValue1, float aValue2)
  {
    MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Times_R ||
               aCalcFunction == eCSSUnit_Calc_Divided,
               "unexpected unit");
    if (aCalcFunction == eCSSUnit_Calc_Divided) {
      aValue2 = 1.0f / aValue2;
    }
    return result_type(NSCoordSaturatingMultiply(aValue1.mLength, aValue2),
                       aValue1.mPercent * aValue2);
  }

};

static void
SpecifiedCalcToComputedCalc(const nsCSSValue& aValue, nsStyleCoord& aCoord,
                            nsStyleContext* aStyleContext,
                            RuleNodeCacheConditions& aConditions)
{
  LengthPercentPairCalcOps ops(aStyleContext, aStyleContext->PresContext(),
                               aConditions);
  nsRuleNode::ComputedCalc vals = ComputeCalc(aValue, ops);

  nsStyleCoord::Calc* calcObj = new nsStyleCoord::Calc;

  calcObj->mLength = vals.mLength;
  calcObj->mPercent = vals.mPercent;
  calcObj->mHasPercent = ops.mHasPercent;

  aCoord.SetCalcValue(calcObj);
}

/* static */ nsRuleNode::ComputedCalc
nsRuleNode::SpecifiedCalcToComputedCalc(const nsCSSValue& aValue,
                                        nsStyleContext* aStyleContext,
                                        nsPresContext* aPresContext,
                                        RuleNodeCacheConditions& aConditions)
{
  LengthPercentPairCalcOps ops(aStyleContext, aPresContext,
                               aConditions);
  return ComputeCalc(aValue, ops);
}

// This is our public API for handling calc() expressions that involve
// percentages.
/* static */ nscoord
nsRuleNode::ComputeComputedCalc(const nsStyleCoord& aValue,
                                nscoord aPercentageBasis)
{
  nsStyleCoord::Calc* calc = aValue.GetCalcValue();
  return calc->mLength +
         NSToCoordFloorClamped(aPercentageBasis * calc->mPercent);
}

/* static */ nscoord
nsRuleNode::ComputeCoordPercentCalc(const nsStyleCoord& aCoord,
                                    nscoord aPercentageBasis)
{
  switch (aCoord.GetUnit()) {
    case eStyleUnit_Coord:
      return aCoord.GetCoordValue();
    case eStyleUnit_Percent:
      return NSToCoordFloorClamped(aPercentageBasis * aCoord.GetPercentValue());
    case eStyleUnit_Calc:
      return ComputeComputedCalc(aCoord, aPercentageBasis);
    default:
      MOZ_ASSERT(false, "unexpected unit");
      return 0;
  }
}

/* Given an enumerated value that represents a box position, converts it to
 * a float representing the percentage of the box it corresponds to.  For
 * example, "center" becomes 0.5f.
 *
 * @param aEnumValue The enumerated value.
 * @return The float percent it corresponds to.
 */
static float
GetFloatFromBoxPosition(int32_t aEnumValue)
{
  switch (aEnumValue) {
  case NS_STYLE_IMAGELAYER_POSITION_LEFT:
  case NS_STYLE_IMAGELAYER_POSITION_TOP:
    return 0.0f;
  case NS_STYLE_IMAGELAYER_POSITION_RIGHT:
  case NS_STYLE_IMAGELAYER_POSITION_BOTTOM:
    return 1.0f;
  default:
    MOZ_FALLTHROUGH_ASSERT("unexpected box position value");
  case NS_STYLE_IMAGELAYER_POSITION_CENTER:
    return 0.5f;
  }
}

#define SETCOORD_NORMAL                 0x01   // N
#define SETCOORD_AUTO                   0x02   // A
#define SETCOORD_INHERIT                0x04   // H
#define SETCOORD_PERCENT                0x08   // P
#define SETCOORD_FACTOR                 0x10   // F
#define SETCOORD_LENGTH                 0x20   // L
#define SETCOORD_INTEGER                0x40   // I
#define SETCOORD_ENUMERATED             0x80   // E
#define SETCOORD_NONE                   0x100  // O
#define SETCOORD_INITIAL_ZERO           0x200
#define SETCOORD_INITIAL_AUTO           0x400
#define SETCOORD_INITIAL_NONE           0x800
#define SETCOORD_INITIAL_NORMAL         0x1000
#define SETCOORD_INITIAL_HALF           0x2000
#define SETCOORD_INITIAL_HUNDRED_PCT    0x00004000
#define SETCOORD_INITIAL_FACTOR_ONE     0x00008000
#define SETCOORD_INITIAL_FACTOR_ZERO    0x00010000
#define SETCOORD_CALC_LENGTH_ONLY       0x00020000
#define SETCOORD_CALC_CLAMP_NONNEGATIVE 0x00040000 // modifier for CALC_LENGTH_ONLY
#define SETCOORD_STORE_CALC             0x00080000
#define SETCOORD_BOX_POSITION           0x00100000 // exclusive with _ENUMERATED
#define SETCOORD_ANGLE                  0x00200000
#define SETCOORD_UNSET_INHERIT          0x00400000
#define SETCOORD_UNSET_INITIAL          0x00800000

#define SETCOORD_LP     (SETCOORD_LENGTH | SETCOORD_PERCENT)
#define SETCOORD_LH     (SETCOORD_LENGTH | SETCOORD_INHERIT)
#define SETCOORD_AH     (SETCOORD_AUTO | SETCOORD_INHERIT)
#define SETCOORD_LAH    (SETCOORD_AUTO | SETCOORD_LENGTH | SETCOORD_INHERIT)
#define SETCOORD_LPH    (SETCOORD_LP | SETCOORD_INHERIT)
#define SETCOORD_LPAH   (SETCOORD_LP | SETCOORD_AH)
#define SETCOORD_LPE    (SETCOORD_LP | SETCOORD_ENUMERATED)
#define SETCOORD_LPEH   (SETCOORD_LPE | SETCOORD_INHERIT)
#define SETCOORD_LPAEH  (SETCOORD_LPAH | SETCOORD_ENUMERATED)
#define SETCOORD_LPO    (SETCOORD_LP | SETCOORD_NONE)
#define SETCOORD_LPOH   (SETCOORD_LPH | SETCOORD_NONE)
#define SETCOORD_LPOEH  (SETCOORD_LPOH | SETCOORD_ENUMERATED)
#define SETCOORD_LE     (SETCOORD_LENGTH | SETCOORD_ENUMERATED)
#define SETCOORD_LEH    (SETCOORD_LE | SETCOORD_INHERIT)
#define SETCOORD_IA     (SETCOORD_INTEGER | SETCOORD_AUTO)
#define SETCOORD_LAE    (SETCOORD_LENGTH | SETCOORD_AUTO | SETCOORD_ENUMERATED)

// changes aCoord iff it returns true
static bool SetCoord(const nsCSSValue& aValue, nsStyleCoord& aCoord,
                       const nsStyleCoord& aParentCoord,
                       int32_t aMask, nsStyleContext* aStyleContext,
                       nsPresContext* aPresContext,
                       RuleNodeCacheConditions& aConditions)
{
  bool result = true;
  if (aValue.GetUnit() == eCSSUnit_Null) {
    result = false;
  }
  else if ((((aMask & SETCOORD_LENGTH) != 0) &&
            aValue.IsLengthUnit()) ||
           (((aMask & SETCOORD_CALC_LENGTH_ONLY) != 0) &&
            aValue.IsCalcUnit())) {
    nscoord len = CalcLength(aValue, aStyleContext, aPresContext,
                             aConditions);
    if ((aMask & SETCOORD_CALC_CLAMP_NONNEGATIVE) && len < 0) {
      NS_ASSERTION(aValue.IsCalcUnit(),
                   "parser should have ensured no nonnegative lengths");
      len = 0;
    }
    aCoord.SetCoordValue(len);
  }
  else if (((aMask & SETCOORD_PERCENT) != 0) &&
           (aValue.GetUnit() == eCSSUnit_Percent)) {
    aCoord.SetPercentValue(aValue.GetPercentValue());
  }
  else if (((aMask & SETCOORD_INTEGER) != 0) &&
           (aValue.GetUnit() == eCSSUnit_Integer)) {
    aCoord.SetIntValue(aValue.GetIntValue(), eStyleUnit_Integer);
  }
  else if (((aMask & SETCOORD_ENUMERATED) != 0) &&
           (aValue.GetUnit() == eCSSUnit_Enumerated)) {
    aCoord.SetIntValue(aValue.GetIntValue(), eStyleUnit_Enumerated);
  }
  else if (((aMask & SETCOORD_BOX_POSITION) != 0) &&
           (aValue.GetUnit() == eCSSUnit_Enumerated)) {
    aCoord.SetPercentValue(GetFloatFromBoxPosition(aValue.GetIntValue()));
  }
  else if (((aMask & SETCOORD_AUTO) != 0) &&
           (aValue.GetUnit() == eCSSUnit_Auto)) {
    aCoord.SetAutoValue();
  }
  else if ((((aMask & SETCOORD_INHERIT) != 0) &&
            aValue.GetUnit() == eCSSUnit_Inherit) ||
           (((aMask & SETCOORD_UNSET_INHERIT) != 0) &&
            aValue.GetUnit() == eCSSUnit_Unset)) {
    aCoord = aParentCoord;  // just inherit value from parent
    aConditions.SetUncacheable();
  }
  else if (((aMask & SETCOORD_NORMAL) != 0) &&
           (aValue.GetUnit() == eCSSUnit_Normal)) {
    aCoord.SetNormalValue();
  }
  else if (((aMask & SETCOORD_NONE) != 0) &&
           (aValue.GetUnit() == eCSSUnit_None)) {
    aCoord.SetNoneValue();
  }
  else if (((aMask & SETCOORD_FACTOR) != 0) &&
           (aValue.GetUnit() == eCSSUnit_Number)) {
    aCoord.SetFactorValue(aValue.GetFloatValue());
  }
  else if (((aMask & SETCOORD_STORE_CALC) != 0) &&
           (aValue.IsCalcUnit())) {
    SpecifiedCalcToComputedCalc(aValue, aCoord, aStyleContext,
                                aConditions);
  }
  else if (aValue.GetUnit() == eCSSUnit_Initial ||
           (aValue.GetUnit() == eCSSUnit_Unset &&
            ((aMask & SETCOORD_UNSET_INITIAL) != 0))) {
    if ((aMask & SETCOORD_INITIAL_AUTO) != 0) {
      aCoord.SetAutoValue();
    }
    else if ((aMask & SETCOORD_INITIAL_ZERO) != 0) {
      aCoord.SetCoordValue(0);
    }
    else if ((aMask & SETCOORD_INITIAL_FACTOR_ZERO) != 0) {
      aCoord.SetFactorValue(0.0f);
    }
    else if ((aMask & SETCOORD_INITIAL_NONE) != 0) {
      aCoord.SetNoneValue();
    }
    else if ((aMask & SETCOORD_INITIAL_NORMAL) != 0) {
      aCoord.SetNormalValue();
    }
    else if ((aMask & SETCOORD_INITIAL_HALF) != 0) {
      aCoord.SetPercentValue(0.5f);
    }
    else if ((aMask & SETCOORD_INITIAL_HUNDRED_PCT) != 0) {
      aCoord.SetPercentValue(1.0f);
    }
    else if ((aMask & SETCOORD_INITIAL_FACTOR_ONE) != 0) {
      aCoord.SetFactorValue(1.0f);
    }
    else {
      result = false;  // didn't set anything
    }
  }
  else if ((aMask & SETCOORD_ANGLE) != 0 &&
           (aValue.IsAngularUnit())) {
    nsStyleUnit unit;
    switch (aValue.GetUnit()) {
      case eCSSUnit_Degree: unit = eStyleUnit_Degree; break;
      case eCSSUnit_Grad:   unit = eStyleUnit_Grad; break;
      case eCSSUnit_Radian: unit = eStyleUnit_Radian; break;
      case eCSSUnit_Turn:   unit = eStyleUnit_Turn; break;
      default: NS_NOTREACHED("unrecognized angular unit");
        unit = eStyleUnit_Degree;
    }
    aCoord.SetAngleValue(aValue.GetAngleValue(), unit);
  }
  else {
    result = false;  // didn't set anything
  }
  return result;
}

// This inline function offers a shortcut for SetCoord() by refusing to accept
// SETCOORD_LENGTH, SETCOORD_INHERIT and SETCOORD_UNSET_* masks.
static inline bool SetAbsCoord(const nsCSSValue& aValue,
                                 nsStyleCoord& aCoord,
                                 int32_t aMask)
{
  MOZ_ASSERT((aMask & (SETCOORD_LH | SETCOORD_UNSET_INHERIT |
                       SETCOORD_UNSET_INITIAL)) == 0,
             "does not handle SETCOORD_LENGTH, SETCOORD_INHERIT and "
             "SETCOORD_UNSET_*");

  // The values of the following variables will never be used; so it does not
  // matter what to set.
  const nsStyleCoord dummyParentCoord;
  nsStyleContext* dummyStyleContext = nullptr;
  nsPresContext* dummyPresContext = nullptr;
  RuleNodeCacheConditions dummyCacheKey;

  bool rv = SetCoord(aValue, aCoord, dummyParentCoord, aMask,
                       dummyStyleContext, dummyPresContext,
                       dummyCacheKey);
  MOZ_ASSERT(dummyCacheKey.CacheableWithoutDependencies(),
             "SetCoord() should not modify dummyCacheKey.");

  return rv;
}

/* Given a specified value that might be a pair value, call SetCoord twice,
 * either using each member of the pair, or using the unpaired value twice.
 */
static bool
SetPairCoords(const nsCSSValue& aValue,
              nsStyleCoord& aCoordX, nsStyleCoord& aCoordY,
              const nsStyleCoord& aParentX, const nsStyleCoord& aParentY,
              int32_t aMask, nsStyleContext* aStyleContext,
              nsPresContext* aPresContext, RuleNodeCacheConditions& aConditions)
{
  const nsCSSValue& valX =
    aValue.GetUnit() == eCSSUnit_Pair ? aValue.GetPairValue().mXValue : aValue;
  const nsCSSValue& valY =
    aValue.GetUnit() == eCSSUnit_Pair ? aValue.GetPairValue().mYValue : aValue;

  bool cX = SetCoord(valX, aCoordX, aParentX, aMask, aStyleContext,
                       aPresContext, aConditions);
  mozilla::DebugOnly<bool> cY = SetCoord(valY, aCoordY, aParentY, aMask,
                       aStyleContext, aPresContext, aConditions);
  MOZ_ASSERT(cX == cY, "changed one but not the other");
  return cX;
}

static bool SetColor(const nsCSSValue& aValue, const nscolor aParentColor,
                       nsPresContext* aPresContext, nsStyleContext *aContext,
                       nscolor& aResult, RuleNodeCacheConditions& aConditions)
{
  bool    result = false;
  nsCSSUnit unit = aValue.GetUnit();

  if (aValue.IsNumericColorUnit()) {
    aResult = aValue.GetColorValue();
    result = true;
  }
  else if (eCSSUnit_Ident == unit) {
    nsAutoString  value;
    aValue.GetStringValue(value);
    nscolor rgba;
    if (NS_ColorNameToRGB(value, &rgba)) {
      aResult = rgba;
      result = true;
    }
  }
  else if (eCSSUnit_EnumColor == unit) {
    int32_t intValue = aValue.GetIntValue();
    if (0 <= intValue) {
      LookAndFeel::ColorID colorID = (LookAndFeel::ColorID) intValue;
      bool useStandinsForNativeColors = aPresContext &&
                                        !aPresContext->IsChrome();
      if (NS_SUCCEEDED(LookAndFeel::GetColor(colorID,
                                    useStandinsForNativeColors, &aResult))) {
        result = true;
      }
    }
    else {
      aResult = NS_RGB(0, 0, 0);
      result = false;
      switch (intValue) {
        case NS_COLOR_MOZ_HYPERLINKTEXT:
          if (aPresContext) {
            aResult = aPresContext->DefaultLinkColor();
            result = true;
          }
          break;
        case NS_COLOR_MOZ_VISITEDHYPERLINKTEXT:
          if (aPresContext) {
            aResult = aPresContext->DefaultVisitedLinkColor();
            result = true;
          }
          break;
        case NS_COLOR_MOZ_ACTIVEHYPERLINKTEXT:
          if (aPresContext) {
            aResult = aPresContext->DefaultActiveLinkColor();
            result = true;
          }
          break;
        case NS_COLOR_CURRENTCOLOR:
          // The data computed from this can't be shared in the rule tree
          // because they could be used on a node with a different color
          aConditions.SetUncacheable();
          if (aContext) {
            aResult = aContext->StyleColor()->mColor;
            result = true;
          }
          break;
        case NS_COLOR_MOZ_DEFAULT_COLOR:
          if (aPresContext) {
            aResult = aPresContext->DefaultColor();
            result = true;
          }
          break;
        case NS_COLOR_MOZ_DEFAULT_BACKGROUND_COLOR:
          if (aPresContext) {
            aResult = aPresContext->DefaultBackgroundColor();
            result = true;
          }
          break;
        default:
          NS_NOTREACHED("Should never have an unknown negative colorID.");
          break;
      }
    }
  }
  else if (eCSSUnit_Inherit == unit) {
    aResult = aParentColor;
    result = true;
    aConditions.SetUncacheable();
  }
  else if (eCSSUnit_Enumerated == unit &&
           aValue.GetIntValue() == NS_STYLE_COLOR_INHERIT_FROM_BODY) {
    NS_ASSERTION(aPresContext->CompatibilityMode() == eCompatibility_NavQuirks,
                 "Should only get this value in quirks mode");
    // We just grab the color from the prescontext, and rely on the fact that
    // if the body color ever changes all its descendants will get new style
    // contexts (but NOT necessarily new rulenodes).
    aResult = aPresContext->BodyTextColor();
    result = true;
    aConditions.SetUncacheable();
  }
  return result;
}

template<UnsetAction UnsetTo>
static void
SetComplexColor(const nsCSSValue& aValue,
                const StyleComplexColor& aParentColor,
                const StyleComplexColor& aInitialColor,
                nsPresContext* aPresContext,
                StyleComplexColor& aResult,
                RuleNodeCacheConditions& aConditions)
{
  nsCSSUnit unit = aValue.GetUnit();
  if (unit == eCSSUnit_Null) {
    return;
  }
  if (unit == eCSSUnit_Initial ||
      (UnsetTo == eUnsetInitial && unit == eCSSUnit_Unset)) {
    aResult = aInitialColor;
  } else if (unit == eCSSUnit_Inherit ||
             (UnsetTo == eUnsetInherit && unit == eCSSUnit_Unset)) {
    aConditions.SetUncacheable();
    aResult = aParentColor;
  } else if (unit == eCSSUnit_EnumColor &&
             aValue.GetIntValue() == NS_COLOR_CURRENTCOLOR) {
    aResult = StyleComplexColor::CurrentColor();
  } else if (unit == eCSSUnit_ComplexColor) {
    aResult = aValue.GetStyleComplexColorValue();
  } else {
    if (!SetColor(aValue, aParentColor.mColor, aPresContext,
                  nullptr, aResult.mColor, aConditions)) {
      MOZ_ASSERT_UNREACHABLE("Unknown color value");
      return;
    }
    aResult.mForegroundRatio = 0;
  }
}

static void SetGradientCoord(const nsCSSValue& aValue, nsPresContext* aPresContext,
                             nsStyleContext* aContext, nsStyleCoord& aResult,
                             RuleNodeCacheConditions& aConditions)
{
  // OK to pass bad aParentCoord since we're not passing SETCOORD_INHERIT
  if (!SetCoord(aValue, aResult, nsStyleCoord(),
                SETCOORD_LPO | SETCOORD_BOX_POSITION | SETCOORD_STORE_CALC,
                aContext, aPresContext, aConditions)) {
    NS_NOTREACHED("unexpected unit for gradient anchor point");
    aResult.SetNoneValue();
  }
}

static void SetGradient(const nsCSSValue& aValue, nsPresContext* aPresContext,
                        nsStyleContext* aContext, nsStyleGradient& aResult,
                        RuleNodeCacheConditions& aConditions)
{
  MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Gradient,
             "The given data is not a gradient");

  const nsCSSValueGradient* gradient = aValue.GetGradientValue();

  if (gradient->mIsExplicitSize) {
    SetCoord(gradient->GetRadiusX(), aResult.mRadiusX, nsStyleCoord(),
             SETCOORD_LP | SETCOORD_STORE_CALC,
             aContext, aPresContext, aConditions);
    if (gradient->GetRadiusY().GetUnit() != eCSSUnit_None) {
      SetCoord(gradient->GetRadiusY(), aResult.mRadiusY, nsStyleCoord(),
               SETCOORD_LP | SETCOORD_STORE_CALC,
               aContext, aPresContext, aConditions);
      aResult.mShape = NS_STYLE_GRADIENT_SHAPE_ELLIPTICAL;
    } else {
      aResult.mRadiusY = aResult.mRadiusX;
      aResult.mShape = NS_STYLE_GRADIENT_SHAPE_CIRCULAR;
    }
    aResult.mSize = NS_STYLE_GRADIENT_SIZE_EXPLICIT_SIZE;
  } else if (gradient->mIsRadial) {
    if (gradient->GetRadialShape().GetUnit() == eCSSUnit_Enumerated) {
      aResult.mShape = gradient->GetRadialShape().GetIntValue();
    } else {
      NS_ASSERTION(gradient->GetRadialShape().GetUnit() == eCSSUnit_None,
                   "bad unit for radial shape");
      aResult.mShape = NS_STYLE_GRADIENT_SHAPE_ELLIPTICAL;
    }
    if (gradient->GetRadialSize().GetUnit() == eCSSUnit_Enumerated) {
      aResult.mSize = gradient->GetRadialSize().GetIntValue();
    } else {
      NS_ASSERTION(gradient->GetRadialSize().GetUnit() == eCSSUnit_None,
                   "bad unit for radial shape");
      aResult.mSize = NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER;
    }
  } else {
    NS_ASSERTION(gradient->GetRadialShape().GetUnit() == eCSSUnit_None,
                 "bad unit for linear shape");
    NS_ASSERTION(gradient->GetRadialSize().GetUnit() == eCSSUnit_None,
                 "bad unit for linear size");
    aResult.mShape = NS_STYLE_GRADIENT_SHAPE_LINEAR;
    aResult.mSize = NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER;
  }

  aResult.mLegacySyntax = gradient->mIsLegacySyntax;

  // bg-position
  SetGradientCoord(gradient->mBgPos.mXValue, aPresContext, aContext,
                   aResult.mBgPosX, aConditions);

  SetGradientCoord(gradient->mBgPos.mYValue, aPresContext, aContext,
                   aResult.mBgPosY, aConditions);

  aResult.mRepeating = gradient->mIsRepeating;

  // angle
  const nsStyleCoord dummyParentCoord;
  if (!SetCoord(gradient->mAngle, aResult.mAngle, dummyParentCoord, SETCOORD_ANGLE,
                aContext, aPresContext, aConditions)) {
    NS_ASSERTION(gradient->mAngle.GetUnit() == eCSSUnit_None,
                 "bad unit for gradient angle");
    aResult.mAngle.SetNoneValue();
  }

  // stops
  for (uint32_t i = 0; i < gradient->mStops.Length(); i++) {
    nsStyleGradientStop stop;
    const nsCSSValueGradientStop &valueStop = gradient->mStops[i];

    if (!SetCoord(valueStop.mLocation, stop.mLocation,
                  nsStyleCoord(), SETCOORD_LPO | SETCOORD_STORE_CALC,
                  aContext, aPresContext, aConditions)) {
      NS_NOTREACHED("unexpected unit for gradient stop location");
    }

    stop.mIsInterpolationHint = valueStop.mIsInterpolationHint;

    // inherit is not a valid color for stops, so we pass in a dummy
    // parent color
    NS_ASSERTION(valueStop.mColor.GetUnit() != eCSSUnit_Inherit,
                 "inherit is not a valid color for gradient stops");
    if (!valueStop.mIsInterpolationHint) {
      SetColor(valueStop.mColor, NS_RGB(0, 0, 0), aPresContext,
              aContext, stop.mColor, aConditions);
    } else {
      // Always initialize to the same color so we don't need to worry
      // about comparisons.
      stop.mColor = NS_RGB(0, 0, 0);
    }

    aResult.mStops.AppendElement(stop);
  }
}

// -moz-image-rect(<uri>, <top>, <right>, <bottom>, <left>)
static void SetStyleImageToImageRect(nsStyleContext* aStyleContext,
                                     const nsCSSValue& aValue,
                                     nsStyleImage& aResult)
{
  MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Function &&
             aValue.EqualsFunction(eCSSKeyword__moz_image_rect),
             "the value is not valid -moz-image-rect()");

  nsCSSValue::Array* arr = aValue.GetArrayValue();
  MOZ_ASSERT(arr && arr->Count() == 6, "invalid number of arguments");

  // <uri>
  if (arr->Item(1).GetUnit() == eCSSUnit_Image) {
    SetStyleImageRequest([&](nsStyleImageRequest* req) {
      aResult.SetImageRequest(do_AddRef(req));
    }, aStyleContext->PresContext(), arr->Item(1));
  } else {
    NS_WARNING("nsCSSValue::Image::Image() failed?");
  }

  // <top>, <right>, <bottom>, <left>
  nsStyleSides cropRect;
  NS_FOR_CSS_SIDES(side) {
    nsStyleCoord coord;
    const nsCSSValue& val = arr->Item(2 + side);

#ifdef DEBUG
    bool unitOk =
#endif
      SetAbsCoord(val, coord, SETCOORD_FACTOR | SETCOORD_PERCENT);
    MOZ_ASSERT(unitOk, "Incorrect data structure created by CSS parser");
    cropRect.Set(side, coord);
  }
  aResult.SetCropRect(MakeUnique<nsStyleSides>(cropRect));
}

static void SetStyleImage(nsStyleContext* aStyleContext,
                          const nsCSSValue& aValue,
                          nsStyleImage& aResult,
                          RuleNodeCacheConditions& aConditions)
{
  if (aValue.GetUnit() == eCSSUnit_Null) {
    return;
  }

  aResult.SetNull();

  switch (aValue.GetUnit()) {
    case eCSSUnit_Image:
      SetStyleImageRequest([&](nsStyleImageRequest* req) {
        aResult.SetImageRequest(do_AddRef(req));
      }, aStyleContext->PresContext(), aValue);
      break;
    case eCSSUnit_Function:
      if (aValue.EqualsFunction(eCSSKeyword__moz_image_rect)) {
        SetStyleImageToImageRect(aStyleContext, aValue, aResult);
      } else {
        NS_NOTREACHED("-moz-image-rect() is the only expected function");
      }
      break;
    case eCSSUnit_Gradient:
    {
      nsStyleGradient* gradient = new nsStyleGradient();
      SetGradient(aValue, aStyleContext->PresContext(), aStyleContext,
                  *gradient, aConditions);
      aResult.SetGradientData(gradient);
      break;
    }
    case eCSSUnit_Element:
      aResult.SetElementId(aValue.GetStringBufferValue());
      break;
    case eCSSUnit_Initial:
    case eCSSUnit_Unset:
    case eCSSUnit_None:
      break;
    case eCSSUnit_URL:
    {
#ifdef DEBUG
      // eCSSUnit_URL is expected only if
      // 1. we have eCSSUnit_URL values for if-visited style contexts, which
      //    we can safely treat like 'none'.
      // 2. aValue is a local-ref URL, e.g. url(#foo).
      // 3. aValue is a not a local-ref URL, but it refers to an element in
      //    the current document. For example, the url of the current document
      //    is "http://foo.html" and aValue is url(http://foo.html#foo).
      //
      // We skip image download in TryToStartImageLoadOnValue under #2 and #3,
      // and that's part of reasons we get eCSSUnit_URL instead of
      // eCSSUnit_Image here.

      // Check #2.
      bool isLocalRef = aValue.GetURLStructValue()->IsLocalRef();

      // Check #3.
      bool isEqualExceptRef = false;
      if (!isLocalRef) {
        nsIDocument* currentDoc = aStyleContext->PresContext()->Document();
        nsIURI* docURI = currentDoc->GetDocumentURI();
        nsIURI* imageURI = aValue.GetURLValue();
        imageURI->EqualsExceptRef(docURI, &isEqualExceptRef);
      }

      MOZ_ASSERT(aStyleContext->IsStyleIfVisited() || isEqualExceptRef ||
                 isLocalRef,
                 "unexpected unit; maybe nsCSSValue::Image::Image() failed?");
#endif

      break;
    }
    default:
      MOZ_ASSERT_UNREACHABLE("Unexpected Unit type.");
      break;
  }
}

struct SetEnumValueHelper
{
  template<typename FieldT>
  static void SetIntegerValue(FieldT&, const nsCSSValue&)
  {
    // FIXME Is it possible to turn this assertion into a compilation error?
    MOZ_ASSERT_UNREACHABLE("inappropriate unit");
  }

#define DEFINE_ENUM_CLASS_SETTER(type_, min_, max_) \
  static void SetEnumeratedValue(type_& aField, const nsCSSValue& aValue) \
  { \
    auto value = aValue.GetIntValue(); \
    MOZ_ASSERT(value >= static_cast<decltype(value)>(type_::min_) && \
               value <= static_cast<decltype(value)>(type_::max_), \
               "inappropriate value"); \
    aField = static_cast<type_>(value); \
  }

  DEFINE_ENUM_CLASS_SETTER(StyleBoxAlign, Stretch, End)
  DEFINE_ENUM_CLASS_SETTER(StyleBoxDecorationBreak, Slice, Clone)
  DEFINE_ENUM_CLASS_SETTER(StyleBoxDirection, Normal, Reverse)
  DEFINE_ENUM_CLASS_SETTER(StyleBoxOrient, Horizontal, Vertical)
  DEFINE_ENUM_CLASS_SETTER(StyleBoxPack, Start, Justify)
  DEFINE_ENUM_CLASS_SETTER(StyleBoxSizing, Content, Border)
  DEFINE_ENUM_CLASS_SETTER(StyleClear, None, Both)
  DEFINE_ENUM_CLASS_SETTER(StyleFillRule, Nonzero, Evenodd)
  DEFINE_ENUM_CLASS_SETTER(StyleFloat, None, InlineEnd)
  DEFINE_ENUM_CLASS_SETTER(StyleFloatEdge, ContentBox, MarginBox)
  DEFINE_ENUM_CLASS_SETTER(StyleUserFocus, None, SelectMenu)
  DEFINE_ENUM_CLASS_SETTER(StyleUserSelect, None, MozText)
  DEFINE_ENUM_CLASS_SETTER(StyleUserInput, None, Auto)
  DEFINE_ENUM_CLASS_SETTER(StyleUserModify, ReadOnly, WriteOnly)
  DEFINE_ENUM_CLASS_SETTER(StyleWindowDragging, Default, NoDrag)
  DEFINE_ENUM_CLASS_SETTER(StyleOrient, Inline, Vertical)
#ifdef MOZ_XUL
  DEFINE_ENUM_CLASS_SETTER(StyleDisplay, None, Popup)
#else
  DEFINE_ENUM_CLASS_SETTER(StyleDisplay, None, InlineBox)
#endif

#undef DEF_SET_ENUMERATED_VALUE
};

template<typename FieldT>
struct SetIntegerValueHelper
{
  static void SetIntegerValue(FieldT& aField, const nsCSSValue& aValue)
  {
    aField = aValue.GetIntValue();
  }
  static void SetEnumeratedValue(FieldT& aField, const nsCSSValue& aValue)
  {
    aField = aValue.GetIntValue();
  }
};

template<typename FieldT>
struct SetValueHelper : Conditional<IsEnum<FieldT>::value,
                                    SetEnumValueHelper,
                                    SetIntegerValueHelper<FieldT>>::Type
{
  template<typename ValueT>
  static void SetValue(FieldT& aField, const ValueT& aValue)
  {
    aField = aValue;
  }
  static void SetValue(FieldT&, unused_t)
  {
    // FIXME Is it possible to turn this assertion into a compilation error?
    MOZ_ASSERT_UNREACHABLE("inappropriate unit");
  }
};


// flags for SetValue - align values with SETCOORD_* constants
// where possible

#define SETVAL_INTEGER                0x40   // I
#define SETVAL_ENUMERATED             0x80   // E
#define SETVAL_UNSET_INHERIT          0x00400000
#define SETVAL_UNSET_INITIAL          0x00800000

// no caller cares whether aField was changed or not
template<typename FieldT, typename InitialT,
         typename AutoT, typename NoneT, typename NormalT, typename SysFontT>
static void
SetValue(const nsCSSValue& aValue, FieldT& aField,
         RuleNodeCacheConditions& aConditions, uint32_t aMask,
         FieldT aParentValue,
         InitialT aInitialValue,
         AutoT aAutoValue,
         NoneT aNoneValue,
         NormalT aNormalValue,
         SysFontT aSystemFontValue)
{
  typedef SetValueHelper<FieldT> Helper;

  switch (aValue.GetUnit()) {
  case eCSSUnit_Null:
    return;

    // every caller of SetValue provides inherit and initial
    // alternatives, so we don't require them to say so in the mask
  case eCSSUnit_Inherit:
    aConditions.SetUncacheable();
    aField = aParentValue;
    return;

  case eCSSUnit_Initial:
    Helper::SetValue(aField, aInitialValue);
    return;

    // every caller provides one or other of these alternatives,
    // but they have to say which
  case eCSSUnit_Enumerated:
    if (aMask & SETVAL_ENUMERATED) {
      Helper::SetEnumeratedValue(aField, aValue);
      return;
    }
    break;

  case eCSSUnit_Integer:
    if (aMask & SETVAL_INTEGER) {
      Helper::SetIntegerValue(aField, aValue);
      return;
    }
    break;

    // remaining possibilities in descending order of frequency of use
  case eCSSUnit_Auto:
    Helper::SetValue(aField, aAutoValue);
    return;

  case eCSSUnit_None:
    Helper::SetValue(aField, aNoneValue);
    return;

  case eCSSUnit_Normal:
    Helper::SetValue(aField, aNormalValue);
    return;

  case eCSSUnit_System_Font:
    Helper::SetValue(aField, aSystemFontValue);
    return;

  case eCSSUnit_Unset:
    if (aMask & SETVAL_UNSET_INHERIT) {
      aConditions.SetUncacheable();
      aField = aParentValue;
      return;
    }
    if (aMask & SETVAL_UNSET_INITIAL) {
      Helper::SetValue(aField, aInitialValue);
      return;
    }
    break;

  default:
    break;
  }

  NS_NOTREACHED("SetValue: inappropriate unit");
}

template <typename FieldT, typename T1>
static void
SetValue(const nsCSSValue& aValue, FieldT& aField,
         RuleNodeCacheConditions& aConditions, uint32_t aMask,
         FieldT aParentValue, T1 aInitialValue)
{
  SetValue(aValue, aField, aConditions, aMask, aParentValue,
           aInitialValue, Unused, Unused, Unused, Unused);
}

// flags for SetFactor
#define SETFCT_POSITIVE 0x01        // assert value is >= 0.0f
#define SETFCT_OPACITY  0x02        // clamp value to [0.0f .. 1.0f]
#define SETFCT_NONE     0x04        // allow _None (uses aInitialValue).
#define SETFCT_UNSET_INHERIT  0x00400000
#define SETFCT_UNSET_INITIAL  0x00800000

static void
SetFactor(const nsCSSValue& aValue, float& aField, RuleNodeCacheConditions& aConditions,
          float aParentValue, float aInitialValue, uint32_t aFlags = 0)
{
  switch (aValue.GetUnit()) {
  case eCSSUnit_Null:
    return;

  case eCSSUnit_Number:
    aField = aValue.GetFloatValue();
    if (aFlags & SETFCT_POSITIVE) {
      NS_ASSERTION(aField >= 0.0f, "negative value for positive-only property");
      if (aField < 0.0f)
        aField = 0.0f;
    }
    if (aFlags & SETFCT_OPACITY) {
      if (aField < 0.0f)
        aField = 0.0f;
      if (aField > 1.0f)
        aField = 1.0f;
    }
    return;

  case eCSSUnit_Inherit:
    aConditions.SetUncacheable();
    aField = aParentValue;
    return;

  case eCSSUnit_Initial:
    aField = aInitialValue;
    return;

  case eCSSUnit_None:
    if (aFlags & SETFCT_NONE) {
      aField = aInitialValue;
      return;
    }
    break;

  case eCSSUnit_Unset:
    if (aFlags & SETFCT_UNSET_INHERIT) {
      aConditions.SetUncacheable();
      aField = aParentValue;
      return;
    }
    if (aFlags & SETFCT_UNSET_INITIAL) {
      aField = aInitialValue;
      return;
    }
    break;

  default:
    break;
  }

  NS_NOTREACHED("SetFactor: inappropriate unit");
}

void*
nsRuleNode::operator new(size_t sz, nsPresContext* aPresContext)
{
  // Check the recycle list first.
  return aPresContext->PresShell()->AllocateByObjectID(eArenaObjectID_nsRuleNode, sz);
}

// Overridden to prevent the global delete from being called, since the memory
// came out of an nsIArena instead of the global delete operator's heap.
void
nsRuleNode::Destroy()
{
  // Destroy ourselves.
  this->~nsRuleNode();

  // Don't let the memory be freed, since it will be recycled
  // instead. Don't call the global operator delete.
  mPresContext->PresShell()->FreeByObjectID(eArenaObjectID_nsRuleNode, this);
}

already_AddRefed<nsRuleNode>
nsRuleNode::CreateRootNode(nsPresContext* aPresContext)
{
  return do_AddRef(new (aPresContext)
    nsRuleNode(aPresContext, nullptr, nullptr, SheetType::Unknown, false));
}

nsRuleNode::nsRuleNode(nsPresContext* aContext, nsRuleNode* aParent,
                       nsIStyleRule* aRule, SheetType aLevel,
                       bool aIsImportant)
  : mPresContext(aContext),
    mParent(aParent),
    mRule(aRule),
    mNextSibling(nullptr),
    mDependentBits((uint32_t(aLevel) << NS_RULE_NODE_LEVEL_SHIFT) |
                   (aIsImportant ? NS_RULE_NODE_IS_IMPORTANT : 0)),
    mNoneBits(aParent ? aParent->mNoneBits & NS_RULE_NODE_HAS_ANIMATION_DATA :
                        0),
    mRefCnt(0)
{
  MOZ_ASSERT(aContext);
  MOZ_ASSERT(IsRoot() == !aRule,
             "non-root rule nodes must have a rule");

  mChildren.asVoid = nullptr;
  MOZ_COUNT_CTOR(nsRuleNode);

  NS_ASSERTION(IsRoot() || GetLevel() == aLevel, "not enough bits");
  NS_ASSERTION(IsRoot() || IsImportantRule() == aIsImportant, "yikes");
  MOZ_ASSERT(aContext->StyleSet()->IsGecko(),
             "ServoStyleSets should not have rule nodes");
  aContext->StyleSet()->AsGecko()->RuleNodeUnused(this, /* aMayGC = */ false);

  // nsStyleSet::GetContext depends on there being only one animation
  // rule.
  MOZ_ASSERT(IsRoot() || GetLevel() != SheetType::Animation ||
             mParent->IsRoot() ||
             mParent->GetLevel() != SheetType::Animation,
             "must be only one rule at animation level");
}

nsRuleNode::~nsRuleNode()
{
  MOZ_ASSERT(!HaveChildren());
  MOZ_COUNT_DTOR(nsRuleNode);
  if (mParent) {
    mParent->RemoveChild(this);
  }

  if (mStyleData.mResetData || mStyleData.mInheritedData)
    mStyleData.Destroy(mDependentBits, mPresContext);
}

nsRuleNode*
nsRuleNode::Transition(nsIStyleRule* aRule, SheetType aLevel,
                       bool aIsImportantRule)
{
#ifdef DEBUG
  {
    RefPtr<css::Declaration> declaration(do_QueryObject(aRule));
    MOZ_ASSERT(!declaration || !declaration->IsMutable(),
               "caller must call Declaration::SetImmutable first");
  }
#endif

  nsRuleNode* next = nullptr;
  nsRuleNode::Key key(aRule, aLevel, aIsImportantRule);

  if (HaveChildren() && !ChildrenAreHashed()) {
    int32_t numKids = 0;
    nsRuleNode* curr = ChildrenList();
    while (curr && curr->GetKey() != key) {
      curr = curr->mNextSibling;
      ++numKids;
    }
    if (curr)
      next = curr;
    else if (numKids >= kMaxChildrenInList)
      ConvertChildrenToHash(numKids);
  }

  if (ChildrenAreHashed()) {
    auto entry =
      static_cast<ChildrenHashEntry*>(ChildrenHash()->Add(&key, fallible));
    if (!entry) {
      NS_WARNING("out of memory");
      return this;
    }
    if (entry->mRuleNode)
      next = entry->mRuleNode;
    else {
      next = entry->mRuleNode = new (mPresContext)
        nsRuleNode(mPresContext, this, aRule, aLevel, aIsImportantRule);
    }
  } else if (!next) {
    // Create the new entry in our list.
    next = new (mPresContext)
      nsRuleNode(mPresContext, this, aRule, aLevel, aIsImportantRule);
    next->mNextSibling = ChildrenList();
    SetChildrenList(next);
  }

  return next;
}

nsRuleNode*
nsRuleNode::RuleTree()
{
  nsRuleNode* n = this;
  while (n->mParent) {
    n = n->mParent;
  }
  return n;
}

void nsRuleNode::SetUsedDirectly()
{
  mDependentBits |= NS_RULE_NODE_USED_DIRECTLY;

  // Maintain the invariant that any rule node that is used directly has
  // all structs that live in the rule tree cached (which
  // nsRuleNode::GetStyleData depends on for speed).
  if (mDependentBits & NS_STYLE_INHERIT_MASK) {
    for (nsStyleStructID sid = nsStyleStructID(0); sid < nsStyleStructID_Length;
         sid = nsStyleStructID(sid + 1)) {
      uint32_t bit = nsCachedStyleData::GetBitForSID(sid);
      if (mDependentBits & bit) {
        nsRuleNode *source = mParent;
        while ((source->mDependentBits & bit) && !source->IsUsedDirectly()) {
          source = source->mParent;
        }
        void *data = source->mStyleData.GetStyleData(sid);
        NS_ASSERTION(data, "unexpected null struct");
        mStyleData.SetStyleData(sid, mPresContext, data);
      }
    }
  }
}

void
nsRuleNode::ConvertChildrenToHash(int32_t aNumKids)
{
  NS_ASSERTION(!ChildrenAreHashed() && HaveChildren(),
               "must have a non-empty list of children");
  PLDHashTable *hash = new PLDHashTable(&ChildrenHashOps,
                                        sizeof(ChildrenHashEntry),
                                        aNumKids);
  for (nsRuleNode* curr = ChildrenList(); curr; curr = curr->mNextSibling) {
    Key key = curr->GetKey();
    // This will never fail because of the initial size we gave the table.
    auto entry =
      static_cast<ChildrenHashEntry*>(hash->Add(&key));
    NS_ASSERTION(!entry->mRuleNode, "duplicate entries in list");
    entry->mRuleNode = curr;
  }
  SetChildrenHash(hash);
}

void
nsRuleNode::RemoveChild(nsRuleNode* aNode)
{
  MOZ_ASSERT(HaveChildren());
  if (ChildrenAreHashed()) {
    PLDHashTable* children = ChildrenHash();
    Key key = aNode->GetKey();
    MOZ_ASSERT(children->Search(&key));
    children->Remove(&key);
    if (children->EntryCount() == 0) {
      delete children;
      mChildren.asVoid = nullptr;
    }
  } else {
    // This linear traversal is unfortunate, but we do the same thing when
    // adding nodes. The traversal is bounded by kMaxChildrenInList.
    nsRuleNode** curr = &mChildren.asList;
    while (*curr != aNode) {
      curr = &((*curr)->mNextSibling);
      MOZ_ASSERT(*curr);
    }
    *curr = (*curr)->mNextSibling;

    // If there was one element in the list, this sets mChildren.asList
    // to 0, and HaveChildren() will return false.
  }
}

inline void
nsRuleNode::PropagateNoneBit(uint32_t aBit, nsRuleNode* aHighestNode)
{
  nsRuleNode* curr = this;
  for (;;) {
    NS_ASSERTION(!(curr->mNoneBits & aBit), "propagating too far");
    curr->mNoneBits |= aBit;
    if (curr == aHighestNode)
      break;
    curr = curr->mParent;
  }
}

inline void
nsRuleNode::PropagateDependentBit(nsStyleStructID aSID, nsRuleNode* aHighestNode,
                                  void* aStruct)
{
  NS_ASSERTION(aStruct, "expected struct");

  uint32_t bit = nsCachedStyleData::GetBitForSID(aSID);
  for (nsRuleNode* curr = this; curr != aHighestNode; curr = curr->mParent) {
    if (curr->mDependentBits & bit) {
#ifdef DEBUG
      while (curr != aHighestNode) {
        NS_ASSERTION(curr->mDependentBits & bit, "bit not set");
        curr = curr->mParent;
      }
#endif
      break;
    }

    curr->mDependentBits |= bit;

    if (curr->IsUsedDirectly()) {
      curr->mStyleData.SetStyleData(aSID, mPresContext, aStruct);
    }
  }
}

/* static */ void
nsRuleNode::PropagateGrandancestorBit(nsStyleContext* aContext,
                                      nsStyleContext* aContextInheritedFrom)
{
  MOZ_ASSERT(aContext);
  MOZ_ASSERT(aContextInheritedFrom &&
             aContextInheritedFrom != aContext,
             "aContextInheritedFrom must be an ancestor of aContext");

  for (nsStyleContext* context = aContext->GetParent();
       context != aContextInheritedFrom;
       context = context->GetParent()) {
    if (!context) {
      MOZ_ASSERT(false, "aContextInheritedFrom must be an ancestor of "
                        "aContext's parent");
      break;
    }
    context->AddStyleBit(NS_STYLE_CHILD_USES_GRANDANCESTOR_STYLE);
  }
}

/*
 * The following "Check" functions are used for determining what type of
 * sharing can be used for the data on this rule node.  MORE HERE...
 */

/*
 * a callback function that that can revise the result of
 * CheckSpecifiedProperties before finishing; aResult is the current
 * result, and it returns the revised one.
 */
typedef nsRuleNode::RuleDetail
  (* CheckCallbackFn)(const nsRuleData* aRuleData,
                      nsRuleNode::RuleDetail aResult);

/**
 * @param aValue the value being examined
 * @param aSpecifiedCount to be incremented by one if the value is specified
 * @param aInheritedCount to be incremented by one if the value is set to inherit
 * @param aUnsetCount to be incremented by one if the value is set to unset
 */
inline void
ExamineCSSValue(const nsCSSValue& aValue,
                uint32_t& aSpecifiedCount,
                uint32_t& aInheritedCount,
                uint32_t& aUnsetCount)
{
  if (aValue.GetUnit() != eCSSUnit_Null) {
    ++aSpecifiedCount;
    if (aValue.GetUnit() == eCSSUnit_Inherit) {
      ++aInheritedCount;
    } else if (aValue.GetUnit() == eCSSUnit_Unset) {
      ++aUnsetCount;
    }
  }
}

static nsRuleNode::RuleDetail
CheckFontCallback(const nsRuleData* aRuleData,
                  nsRuleNode::RuleDetail aResult)
{
  // em, ex, percent, 'larger', and 'smaller' values on font-size depend
  // on the parent context's font-size
  // Likewise, 'lighter' and 'bolder' values of 'font-weight', and 'wider'
  // and 'narrower' values of 'font-stretch' depend on the parent.
  const nsCSSValue& size = *aRuleData->ValueForFontSize();
  const nsCSSValue& weight = *aRuleData->ValueForFontWeight();
  if ((size.IsRelativeLengthUnit() && size.GetUnit() != eCSSUnit_RootEM) ||
      size.GetUnit() == eCSSUnit_Percent ||
      (size.GetUnit() == eCSSUnit_Enumerated &&
       (size.GetIntValue() == NS_STYLE_FONT_SIZE_SMALLER ||
        size.GetIntValue() == NS_STYLE_FONT_SIZE_LARGER)) ||
      aRuleData->ValueForScriptLevel()->GetUnit() == eCSSUnit_Integer ||
      (weight.GetUnit() == eCSSUnit_Enumerated &&
       (weight.GetIntValue() == NS_STYLE_FONT_WEIGHT_BOLDER ||
        weight.GetIntValue() == NS_STYLE_FONT_WEIGHT_LIGHTER))) {
    NS_ASSERTION(aResult == nsRuleNode::eRulePartialReset ||
                 aResult == nsRuleNode::eRuleFullReset ||
                 aResult == nsRuleNode::eRulePartialMixed ||
                 aResult == nsRuleNode::eRuleFullMixed,
                 "we know we already have a reset-counted property");
    // Promote reset to mixed since we have something that depends on
    // the parent.  But never promote to inherited since that could
    // cause inheritance of the exact value.
    if (aResult == nsRuleNode::eRulePartialReset)
      aResult = nsRuleNode::eRulePartialMixed;
    else if (aResult == nsRuleNode::eRuleFullReset)
      aResult = nsRuleNode::eRuleFullMixed;
  }

  return aResult;
}

static nsRuleNode::RuleDetail
CheckColorCallback(const nsRuleData* aRuleData,
                   nsRuleNode::RuleDetail aResult)
{
  // currentColor values for color require inheritance
  const nsCSSValue* colorValue = aRuleData->ValueForColor();
  if (colorValue->GetUnit() == eCSSUnit_EnumColor &&
      colorValue->GetIntValue() == NS_COLOR_CURRENTCOLOR) {
    NS_ASSERTION(aResult == nsRuleNode::eRuleFullReset,
                 "we should already be counted as full-reset");
    aResult = nsRuleNode::eRuleFullInherited;
  }

  return aResult;
}

static nsRuleNode::RuleDetail
CheckTextCallback(const nsRuleData* aRuleData,
                  nsRuleNode::RuleDetail aResult)
{
  const nsCSSValue* textAlignValue = aRuleData->ValueForTextAlign();
  if (textAlignValue->GetUnit() == eCSSUnit_Enumerated &&
      (textAlignValue->GetIntValue() ==
        NS_STYLE_TEXT_ALIGN_MOZ_CENTER_OR_INHERIT ||
       textAlignValue->GetIntValue() == NS_STYLE_TEXT_ALIGN_MATCH_PARENT)) {
    // Promote reset to mixed since we have something that depends on
    // the parent.
    if (aResult == nsRuleNode::eRulePartialReset)
      aResult = nsRuleNode::eRulePartialMixed;
    else if (aResult == nsRuleNode::eRuleFullReset)
      aResult = nsRuleNode::eRuleFullMixed;
  }

  return aResult;
}

static nsRuleNode::RuleDetail
CheckVariablesCallback(const nsRuleData* aRuleData,
                       nsRuleNode::RuleDetail aResult)
{
  // We don't actually have any properties on nsStyleVariables, so we do
  // all of the RuleDetail calculation in here.
  if (aRuleData->mVariables) {
    return nsRuleNode::eRulePartialMixed;
  }
  return nsRuleNode::eRuleNone;
}

#define FLAG_DATA_FOR_PROPERTY(name_, id_, method_, flags_, pref_,          \
                               parsevariant_, kwtable_, stylestructoffset_, \
                               animtype_)                                   \
  flags_,

// The order here must match the enums in *CheckCounter in nsCSSProps.cpp.

static const uint32_t gFontFlags[] = {
#define CSS_PROP_FONT FLAG_DATA_FOR_PROPERTY
#include "nsCSSPropList.h"
#undef CSS_PROP_FONT
};

static const uint32_t gDisplayFlags[] = {
#define CSS_PROP_DISPLAY FLAG_DATA_FOR_PROPERTY
#include "nsCSSPropList.h"
#undef CSS_PROP_DISPLAY
};

static const uint32_t gVisibilityFlags[] = {
#define CSS_PROP_VISIBILITY FLAG_DATA_FOR_PROPERTY
#include "nsCSSPropList.h"
#undef CSS_PROP_VISIBILITY
};

static const uint32_t gMarginFlags[] = {
#define CSS_PROP_MARGIN FLAG_DATA_FOR_PROPERTY
#include "nsCSSPropList.h"
#undef CSS_PROP_MARGIN
};

static const uint32_t gBorderFlags[] = {
#define CSS_PROP_BORDER FLAG_DATA_FOR_PROPERTY
#include "nsCSSPropList.h"
#undef CSS_PROP_BORDER
};

static const uint32_t gPaddingFlags[] = {
#define CSS_PROP_PADDING FLAG_DATA_FOR_PROPERTY
#include "nsCSSPropList.h"
#undef CSS_PROP_PADDING
};

static const uint32_t gOutlineFlags[] = {
#define CSS_PROP_OUTLINE FLAG_DATA_FOR_PROPERTY
#include "nsCSSPropList.h"
#undef CSS_PROP_OUTLINE
};

static const uint32_t gListFlags[] = {
#define CSS_PROP_LIST FLAG_DATA_FOR_PROPERTY
#include "nsCSSPropList.h"
#undef CSS_PROP_LIST
};

static const uint32_t gColorFlags[] = {
#define CSS_PROP_COLOR FLAG_DATA_FOR_PROPERTY
#include "nsCSSPropList.h"
#undef CSS_PROP_COLOR
};

static const uint32_t gBackgroundFlags[] = {
#define CSS_PROP_BACKGROUND FLAG_DATA_FOR_PROPERTY
#include "nsCSSPropList.h"
#undef CSS_PROP_BACKGROUND
};

static const uint32_t gPositionFlags[] = {
#define CSS_PROP_POSITION FLAG_DATA_FOR_PROPERTY
#include "nsCSSPropList.h"
#undef CSS_PROP_POSITION
};

static const uint32_t gTableFlags[] = {
#define CSS_PROP_TABLE FLAG_DATA_FOR_PROPERTY
#include "nsCSSPropList.h"
#undef CSS_PROP_TABLE
};

static const uint32_t gTableBorderFlags[] = {
#define CSS_PROP_TABLEBORDER FLAG_DATA_FOR_PROPERTY
#include "nsCSSPropList.h"
#undef CSS_PROP_TABLEBORDER
};

static const uint32_t gContentFlags[] = {
#define CSS_PROP_CONTENT FLAG_DATA_FOR_PROPERTY
#include "nsCSSPropList.h"
#undef CSS_PROP_CONTENT
};

static const uint32_t gTextFlags[] = {
#define CSS_PROP_TEXT FLAG_DATA_FOR_PROPERTY
#include "nsCSSPropList.h"
#undef CSS_PROP_TEXT
};

static const uint32_t gTextResetFlags[] = {
#define CSS_PROP_TEXTRESET FLAG_DATA_FOR_PROPERTY
#include "nsCSSPropList.h"
#undef CSS_PROP_TEXTRESET
};

static const uint32_t gUserInterfaceFlags[] = {
#define CSS_PROP_USERINTERFACE FLAG_DATA_FOR_PROPERTY
#include "nsCSSPropList.h"
#undef CSS_PROP_USERINTERFACE
};

static const uint32_t gUIResetFlags[] = {
#define CSS_PROP_UIRESET FLAG_DATA_FOR_PROPERTY
#include "nsCSSPropList.h"
#undef CSS_PROP_UIRESET
};

static const uint32_t gXULFlags[] = {
#define CSS_PROP_XUL FLAG_DATA_FOR_PROPERTY
#include "nsCSSPropList.h"
#undef CSS_PROP_XUL
};

static const uint32_t gSVGFlags[] = {
#define CSS_PROP_SVG FLAG_DATA_FOR_PROPERTY
#include "nsCSSPropList.h"
#undef CSS_PROP_SVG
};

static const uint32_t gSVGResetFlags[] = {
#define CSS_PROP_SVGRESET FLAG_DATA_FOR_PROPERTY
#include "nsCSSPropList.h"
#undef CSS_PROP_SVGRESET
};

static const uint32_t gColumnFlags[] = {
#define CSS_PROP_COLUMN FLAG_DATA_FOR_PROPERTY
#include "nsCSSPropList.h"
#undef CSS_PROP_COLUMN
};

// There are no properties in nsStyleVariables, but we can't have a
// zero length array.
static const uint32_t gVariablesFlags[] = {
  0,
#define CSS_PROP_VARIABLES FLAG_DATA_FOR_PROPERTY
#include "nsCSSPropList.h"
#undef CSS_PROP_VARIABLES
};
static_assert(sizeof(gVariablesFlags) == sizeof(uint32_t),
              "if nsStyleVariables has properties now you can remove the dummy "
              "gVariablesFlags entry");

static const uint32_t gEffectsFlags[] = {
#define CSS_PROP_EFFECTS FLAG_DATA_FOR_PROPERTY
#include "nsCSSPropList.h"
#undef CSS_PROP_EFFECTS
};

#undef FLAG_DATA_FOR_PROPERTY

static const uint32_t* gFlagsByStruct[] = {

#define STYLE_STRUCT(name, checkdata_cb) \
  g##name##Flags,
#include "nsStyleStructList.h"
#undef STYLE_STRUCT

};

static const CheckCallbackFn gCheckCallbacks[] = {

#define STYLE_STRUCT(name, checkdata_cb) \
  checkdata_cb,
#include "nsStyleStructList.h"
#undef STYLE_STRUCT

};

#ifdef DEBUG
static bool
AreAllMathMLPropertiesUndefined(const nsRuleData* aRuleData)
{
  return
    aRuleData->ValueForScriptLevel()->GetUnit() == eCSSUnit_Null &&
    aRuleData->ValueForScriptSizeMultiplier()->GetUnit() == eCSSUnit_Null &&
    aRuleData->ValueForScriptMinSize()->GetUnit() == eCSSUnit_Null &&
    aRuleData->ValueForMathVariant()->GetUnit() == eCSSUnit_Null &&
    aRuleData->ValueForMathDisplay()->GetUnit() == eCSSUnit_Null;
}
#endif

inline nsRuleNode::RuleDetail
nsRuleNode::CheckSpecifiedProperties(const nsStyleStructID aSID,
                                     const nsRuleData* aRuleData)
{
  // Build a count of the:
  uint32_t total = 0,      // total number of props in the struct
           specified = 0,  // number that were specified for this node
           inherited = 0,  // number that were 'inherit' (and not
                           //   eCSSUnit_Inherit) for this node
           unset = 0;      // number that were 'unset'

  // See comment in nsRuleData.h above mValueOffsets.
  MOZ_ASSERT(aRuleData->mValueOffsets[aSID] == 0,
             "we assume the value offset is zero instead of adding it");
  for (nsCSSValue *values = aRuleData->mValueStorage,
              *values_end = values + nsCSSProps::PropertyCountInStruct(aSID);
       values != values_end; ++values) {
    ++total;
    ExamineCSSValue(*values, specified, inherited, unset);
  }

  if (!nsCachedStyleData::IsReset(aSID)) {
    // For inherited properties, 'unset' means the same as 'inherit'.
    inherited += unset;
    unset = 0;
  }

#if 0
  printf("CheckSpecifiedProperties: SID=%d total=%d spec=%d inh=%d.\n",
         aSID, total, specified, inherited);
#endif

  NS_ASSERTION(aSID != eStyleStruct_Font ||
               mPresContext->Document()->GetMathMLEnabled() ||
               AreAllMathMLPropertiesUndefined(aRuleData),
               "MathML style property was defined even though MathML is disabled");

  /*
   * Return the most specific information we can: prefer None or Full
   * over Partial, and Reset or Inherited over Mixed, since we can
   * optimize based on the edge cases and not the in-between cases.
   */
  nsRuleNode::RuleDetail result;
  if (inherited == total)
    result = eRuleFullInherited;
  else if (specified == total
           // MathML defines 5 properties in Font that will never be set when
           // MathML is not in use. Therefore if all but five
           // properties have been set, and MathML is not enabled, we can treat
           // this as fully specified. Code in nsMathMLElementFactory will
           // rebuild the rule tree and style data when MathML is first enabled
           // (see nsMathMLElement::BindToTree).
           || (aSID == eStyleStruct_Font && specified + 5 == total &&
               !mPresContext->Document()->GetMathMLEnabled())
          ) {
    if (inherited == 0)
      result = eRuleFullReset;
    else
      result = eRuleFullMixed;
  } else if (specified == 0)
    result = eRuleNone;
  else if (specified == inherited)
    result = eRulePartialInherited;
  else if (inherited == 0)
    result = eRulePartialReset;
  else
    result = eRulePartialMixed;

  CheckCallbackFn cb = gCheckCallbacks[aSID];
  if (cb) {
    result = (*cb)(aRuleData, result);
  }

  return result;
}

// If we need to restrict which properties apply to the style context,
// return the bit to check in nsCSSProp's flags table.  Otherwise,
// return 0.
inline uint32_t
GetPseudoRestriction(nsStyleContext *aContext)
{
  // This needs to match nsStyleSet::WalkRestrictionRule.
  uint32_t pseudoRestriction = 0;
  nsIAtom *pseudoType = aContext->GetPseudo();
  if (pseudoType) {
    if (pseudoType == nsCSSPseudoElements::firstLetter) {
      pseudoRestriction = CSS_PROPERTY_APPLIES_TO_FIRST_LETTER;
    } else if (pseudoType == nsCSSPseudoElements::firstLine) {
      pseudoRestriction = CSS_PROPERTY_APPLIES_TO_FIRST_LINE;
    } else if (pseudoType == nsCSSPseudoElements::placeholder) {
      pseudoRestriction = CSS_PROPERTY_APPLIES_TO_PLACEHOLDER;
    }
  }
  return pseudoRestriction;
}

static void
UnsetPropertiesWithoutFlags(const nsStyleStructID aSID,
                            nsRuleData* aRuleData,
                            uint32_t aFlags)
{
  NS_ASSERTION(aFlags != 0, "aFlags must be nonzero");

  const uint32_t *flagData = gFlagsByStruct[aSID];

  // See comment in nsRuleData.h above mValueOffsets.
  MOZ_ASSERT(aRuleData->mValueOffsets[aSID] == 0,
             "we assume the value offset is zero instead of adding it");
  nsCSSValue *values = aRuleData->mValueStorage;

  for (size_t i = 0, i_end = nsCSSProps::PropertyCountInStruct(aSID);
       i != i_end; ++i) {
    if ((flagData[i] & aFlags) != aFlags)
      values[i].Reset();
  }
}

/**
 * We allocate arrays of CSS values with alloca.  (These arrays are a
 * fixed size per style struct, but we don't want to waste the
 * allocation and construction/destruction costs of the big structs when
 * we're handling much smaller ones.)  Since the lifetime of an alloca
 * allocation is the life of the calling function, the caller must call
 * alloca.  However, to ensure that constructors and destructors are
 * balanced, we do the constructor and destructor calling from this RAII
 * class, AutoCSSValueArray.
 */
struct AutoCSSValueArray {
  /**
   * aStorage must be the result of alloca(aCount * sizeof(nsCSSValue))
   */
  AutoCSSValueArray(void* aStorage, size_t aCount) {
    MOZ_ASSERT(size_t(aStorage) % NS_ALIGNMENT_OF(nsCSSValue) == 0,
               "bad alignment from alloca");
    mCount = aCount;
    // Don't use placement new[], since it might store extra data
    // for the count (on Windows!).
    mArray = static_cast<nsCSSValue*>(aStorage);
    for (size_t i = 0; i < mCount; ++i) {
      new (KnownNotNull, mArray + i) nsCSSValue();
    }
  }

  ~AutoCSSValueArray() {
    for (size_t i = 0; i < mCount; ++i) {
      mArray[i].~nsCSSValue();
    }
  }

  nsCSSValue* get() { return mArray; }

private:
  nsCSSValue *mArray;
  size_t mCount;
};

/* static */ bool
nsRuleNode::ResolveVariableReferences(const nsStyleStructID aSID,
                                      nsRuleData* aRuleData,
                                      nsStyleContext* aContext)
{
  MOZ_ASSERT(aSID != eStyleStruct_Variables);
  MOZ_ASSERT(aRuleData->mSIDs & nsCachedStyleData::GetBitForSID(aSID));
  MOZ_ASSERT(aRuleData->mValueOffsets[aSID] == 0);

  nsCSSParser parser;
  bool anyTokenStreams = false;

  // Look at each property in the nsRuleData for the given style struct.
  size_t nprops = nsCSSProps::PropertyCountInStruct(aSID);
  for (nsCSSValue* value = aRuleData->mValueStorage,
                  *values_end = aRuleData->mValueStorage + nprops;
       value != values_end; value++) {
    if (value->GetUnit() != eCSSUnit_TokenStream) {
      continue;
    }

    const CSSVariableValues* variables =
      &aContext->StyleVariables()->mVariables;
    nsCSSValueTokenStream* tokenStream = value->GetTokenStreamValue();

    MOZ_ASSERT(tokenStream->mLevel != SheetType::Count,
               "Token stream should have a defined level");

    AutoRestore<SheetType> saveLevel(aRuleData->mLevel);
    aRuleData->mLevel = tokenStream->mLevel;

    // Note that ParsePropertyWithVariableReferences relies on the fact
    // that the nsCSSValue in aRuleData for the property we are re-parsing
    // is still the token stream value.  When
    // ParsePropertyWithVariableReferences calls
    // nsCSSExpandedDataBlock::MapRuleInfoInto, that function will add
    // the ImageValue that is created into the token stream object's
    // mImageValues table; see the comment above mImageValues for why.

    // XXX Should pass in sheet here (see bug 952338).
    parser.ParsePropertyWithVariableReferences(
        tokenStream->mPropertyID, tokenStream->mShorthandPropertyID,
        tokenStream->mTokenStream, variables, aRuleData,
        tokenStream->mSheetURI, tokenStream->mBaseURI,
        tokenStream->mSheetPrincipal, nullptr,
        tokenStream->mLineNumber, tokenStream->mLineOffset);
    aRuleData->mConditions.SetUncacheable();
    anyTokenStreams = true;
  }

  return anyTokenStreams;
}

const void*
nsRuleNode::WalkRuleTree(const nsStyleStructID aSID,
                         nsStyleContext* aContext)
{
  // use placement new[] on the result of alloca() to allocate a
  // variable-sized stack array, including execution of constructors,
  // and use an RAII class to run the destructors too.
  size_t nprops = nsCSSProps::PropertyCountInStruct(aSID);
  void* dataStorage = alloca(nprops * sizeof(nsCSSValue));
  AutoCSSValueArray dataArray(dataStorage, nprops);

  nsRuleData ruleData(nsCachedStyleData::GetBitForSID(aSID),
                      dataArray.get(), mPresContext, aContext);
  ruleData.mValueOffsets[aSID] = 0;

  // We start at the most specific rule in the tree.
  void* startStruct = nullptr;

  nsRuleNode* ruleNode = this;
  nsRuleNode* highestNode = nullptr; // The highest node in the rule tree
                                    // that has the same properties
                                    // specified for struct |aSID| as
                                    // |this| does.
  nsRuleNode* rootNode = this; // After the loop below, this will be the
                               // highest node that we've walked without
                               // finding cached data on the rule tree.
                               // If we don't find any cached data, it
                               // will be the root.  (XXX misnamed)
  RuleDetail detail = eRuleNone;
  uint32_t bit = nsCachedStyleData::GetBitForSID(aSID);

  while (ruleNode) {
    // See if this rule node has cached the fact that the remaining
    // nodes along this path specify no data whatsoever.
    if (ruleNode->mNoneBits & bit)
      break;

    // If the dependent bit is set on a rule node for this struct, that
    // means its rule won't have any information to add, so skip it.
    // NOTE: If we exit the loop because of the !IsUsedDirectly() check,
    // then we're guaranteed to break immediately afterwards due to a
    // non-null startStruct.
    while ((ruleNode->mDependentBits & bit) && !ruleNode->IsUsedDirectly()) {
      NS_ASSERTION(ruleNode->mStyleData.GetStyleData(aSID) == nullptr,
                   "dependent bit with cached data makes no sense");
      // Climb up to the next rule in the tree (a less specific rule).
      rootNode = ruleNode;
      ruleNode = ruleNode->mParent;
      NS_ASSERTION(!(ruleNode->mNoneBits & bit), "can't have both bits set");
    }

    // Check for cached data after the inner loop above -- otherwise
    // we'll miss it.
    startStruct = ruleNode->mStyleData.GetStyleData(aSID);
    if (startStruct)
      break; // We found a rule with fully specified data.  We don't
             // need to go up the tree any further, since the remainder
             // of this branch has already been computed.

    // Ask the rule to fill in the properties that it specifies.
    nsIStyleRule *rule = ruleNode->mRule;
    if (rule) {
      ruleData.mLevel = ruleNode->GetLevel();
      ruleData.mIsImportantRule = ruleNode->IsImportantRule();
      rule->MapRuleInfoInto(&ruleData);
    }

    // Now we check to see how many properties have been specified by
    // the rules we've examined so far.
    RuleDetail oldDetail = detail;
    detail = CheckSpecifiedProperties(aSID, &ruleData);

    if (oldDetail == eRuleNone && detail != eRuleNone)
      highestNode = ruleNode;

    if (detail == eRuleFullReset ||
        detail == eRuleFullMixed ||
        detail == eRuleFullInherited)
      break; // We don't need to examine any more rules.  All properties
             // have been fully specified.

    // Climb up to the next rule in the tree (a less specific rule).
    rootNode = ruleNode;
    ruleNode = ruleNode->mParent;
  }

  bool recomputeDetail = false;

  // If we are computing a style struct other than nsStyleVariables, and
  // ruleData has any properties with variable references (nsCSSValues of
  // type eCSSUnit_TokenStream), then we need to resolve these.
  if (aSID != eStyleStruct_Variables) {
    // A property's value might have became 'inherit' after resolving
    // variable references.  (This happens when an inherited property
    // fails to parse its resolved value.)  We need to recompute
    // |detail| in case this happened.
    recomputeDetail = ResolveVariableReferences(aSID, &ruleData, aContext);
  }

  // If needed, unset the properties that don't have a flag that allows
  // them to be set for this style context.  (For example, only some
  // properties apply to :first-line and :first-letter.)
  uint32_t pseudoRestriction = GetPseudoRestriction(aContext);
  if (pseudoRestriction) {
    UnsetPropertiesWithoutFlags(aSID, &ruleData, pseudoRestriction);

    // We need to recompute |detail| based on the restrictions we just applied.
    // We can adjust |detail| arbitrarily because of the restriction
    // rule added in nsStyleSet::WalkRestrictionRule.
    recomputeDetail = true;
  }

  if (recomputeDetail) {
    detail = CheckSpecifiedProperties(aSID, &ruleData);
  }

  NS_ASSERTION(!startStruct || (detail != eRuleFullReset &&
                                detail != eRuleFullMixed &&
                                detail != eRuleFullInherited),
               "can't have start struct and be fully specified");

  bool isReset = nsCachedStyleData::IsReset(aSID);
  if (!highestNode)
    highestNode = rootNode;

  MOZ_ASSERT(!(aSID == eStyleStruct_Variables && startStruct),
             "if we start caching Variables structs in the rule tree, then "
             "not forcing detail to eRulePartialMixed just below is no "
             "longer valid");

  if (detail == eRuleNone && isReset) {
    // We specified absolutely no rule information for a reset struct, and we
    // may or may not have found a parent rule in the tree that specified all
    // the rule information.  Regardless, we don't need to use any cache
    // conditions if we cache this struct in the rule tree.
    //
    // Normally ruleData.mConditions would already indicate that the struct
    // is cacheable without conditions if detail is eRuleNone, but because
    // of the UnsetPropertiesWithoutFlags call above, we may have encountered
    // some rules with dependencies, which we then cleared out of ruleData.
    //
    // ruleData.mConditions could also indicate we are not cacheable at all,
    // such as when AnimValuesStyleRule prevents us from caching structs
    // when attempting to apply animations to pseudos.
    //
    // So if we we are uncacheable, we leave it, but if we are cacheable
    // with dependencies, we convert that to cacheable without dependencies.
    if (ruleData.mConditions.CacheableWithDependencies()) {
      MOZ_ASSERT(pseudoRestriction,
                 "should only be cacheable with dependencies if we had a "
                 "pseudo restriction");
      ruleData.mConditions.Clear();
    } else {
      // XXXheycam We shouldn't have `|| GetLevel() == SheetType::Transition`
      // in the assertion condition, but rule nodes created by
      // ResolveStyleByAddingRules don't call SetIsAnimationRule().
      MOZ_ASSERT(ruleData.mConditions.CacheableWithoutDependencies() ||
                 ((HasAnimationData() ||
                   GetLevel() == SheetType::Transition) &&
                  aContext->GetParent() &&
                  aContext->GetParent()->HasPseudoElementData()),
                 "should only be uncacheable if we had an animation rule "
                 "and we're inside a pseudo");
    }
  }

  if (!ruleData.mConditions.CacheableWithoutDependencies() &&
      aSID != eStyleStruct_Variables) {
    // Treat as though some data is specified to avoid the optimizations and
    // force data computation.
    //
    // We don't need to do this for Variables structs since we know those are
    // never cached in the rule tree, and it avoids wasteful computation of a
    // new Variables struct when we have no additional variable declarations,
    // which otherwise could happen when there is an AnimValuesStyleRule
    // (which calls SetUncacheable for style contexts with pseudo data).
    detail = eRulePartialMixed;
  }

  if (detail == eRuleNone && startStruct) {
    // We specified absolutely no rule information, but a parent rule in the tree
    // specified all the rule information.  We set a bit along the branch from our
    // node in the tree to the node that specified the data that tells nodes on that
    // branch that they never need to examine their rules for this particular struct type
    // ever again.
    PropagateDependentBit(aSID, ruleNode, startStruct);
    // For inherited structs, mark the struct (which will be set on
    // the context by our caller) as not being owned by the context.
    if (!isReset) {
      aContext->AddStyleBit(nsCachedStyleData::GetBitForSID(aSID));
    } else if (HasAnimationData()) {
      // If we have animation data, the struct should be cached on the style
      // context so that we can peek the struct.
      // See comment in AnimValuesStyleRule::MapRuleInfoInto.
      StoreStyleOnContext(aContext, aSID, startStruct);
    }

    return startStruct;
  }
  if ((!startStruct && !isReset &&
       (detail == eRuleNone || detail == eRulePartialInherited)) ||
      detail == eRuleFullInherited) {
    // We specified no non-inherited information and neither did any of
    // our parent rules.

    // We set a bit along the branch from the highest node (ruleNode)
    // down to our node (this) indicating that no non-inherited data was
    // specified.  This bit is guaranteed to be set already on the path
    // from the highest node to the root node in the case where
    // (detail == eRuleNone), which is the most common case here.
    // We must check |!isReset| because the Compute*Data functions for
    // reset structs wouldn't handle none bits correctly.
    if (highestNode != this && !isReset)
      PropagateNoneBit(bit, highestNode);

    // All information must necessarily be inherited from our parent style context.
    // In the absence of any computed data in the rule tree and with
    // no rules specified that didn't have values of 'inherit', we should check our parent.
    nsStyleContext* parentContext = aContext->GetParent();
    if (isReset) {
      /* Reset structs don't inherit from first-line. */
      /* See similar code in COMPUTE_START_RESET */
      while (parentContext &&
             parentContext->GetPseudo() == nsCSSPseudoElements::firstLine) {
        parentContext = parentContext->GetParent();
      }
      if (parentContext && parentContext != aContext->GetParent()) {
        PropagateGrandancestorBit(aContext, parentContext);
      }
    }
    if (parentContext) {
      // We have a parent, and so we should just inherit from the parent.
      // Set the inherit bits on our context.  These bits tell the style context that
      // it never has to go back to the rule tree for data.  Instead the style context tree
      // should be walked to find the data.
      const void* parentStruct = parentContext->StyleData(aSID);
      aContext->AddStyleBit(bit); // makes const_cast OK.
      aContext->SetStyle(aSID, const_cast<void*>(parentStruct));
      if (isReset) {
        parentContext->AddStyleBit(NS_STYLE_HAS_CHILD_THAT_USES_RESET_STYLE);
      }
      return parentStruct;
    }
    else
      // We are the root.  In the case of fonts, the default values just
      // come from the pres context.
      return SetDefaultOnRoot(aSID, aContext);
  }

  typedef const void* (nsRuleNode::*ComputeFunc)(void*, const nsRuleData*,
                                                 nsStyleContext*, nsRuleNode*,
                                                 RuleDetail,
                                                 const RuleNodeCacheConditions);
  static const ComputeFunc sComputeFuncs[] = {
#define STYLE_STRUCT(name, checkdata_cb) &nsRuleNode::Compute##name##Data,
#include "nsStyleStructList.h"
#undef STYLE_STRUCT
  };

  // We need to compute the data from the information that the rules specified.
  return (this->*sComputeFuncs[aSID])(startStruct, &ruleData, aContext,
                                      highestNode, detail,
                                      ruleData.mConditions);
}

const void*
nsRuleNode::SetDefaultOnRoot(const nsStyleStructID aSID, nsStyleContext* aContext)
{
  switch (aSID) {
    case eStyleStruct_Font:
    {
      nsStyleFont* fontData = new (mPresContext) nsStyleFont(mPresContext);
      nscoord minimumFontSize = mPresContext->MinFontSize(fontData->mLanguage);

      if (minimumFontSize > 0 && !mPresContext->IsChrome()) {
        fontData->mFont.size = std::max(fontData->mSize, minimumFontSize);
      }
      else {
        fontData->mFont.size = fontData->mSize;
      }
      aContext->SetStyle(eStyleStruct_Font, fontData);
      return fontData;
    }
    case eStyleStruct_Display:
    {
      nsStyleDisplay* disp = new (mPresContext) nsStyleDisplay(mPresContext);
      aContext->SetStyle(eStyleStruct_Display, disp);
      return disp;
    }
    case eStyleStruct_Visibility:
    {
      nsStyleVisibility* vis = new (mPresContext) nsStyleVisibility(mPresContext);
      aContext->SetStyle(eStyleStruct_Visibility, vis);
      return vis;
    }
    case eStyleStruct_Text:
    {
      nsStyleText* text = new (mPresContext) nsStyleText(mPresContext);
      aContext->SetStyle(eStyleStruct_Text, text);
      return text;
    }
    case eStyleStruct_TextReset:
    {
      nsStyleTextReset* text = new (mPresContext) nsStyleTextReset(mPresContext);
      aContext->SetStyle(eStyleStruct_TextReset, text);
      return text;
    }
    case eStyleStruct_Color:
    {
      nsStyleColor* color = new (mPresContext) nsStyleColor(mPresContext);
      aContext->SetStyle(eStyleStruct_Color, color);
      return color;
    }
    case eStyleStruct_Background:
    {
      nsStyleBackground* bg = new (mPresContext) nsStyleBackground(mPresContext);
      aContext->SetStyle(eStyleStruct_Background, bg);
      return bg;
    }
    case eStyleStruct_Margin:
    {
      nsStyleMargin* margin = new (mPresContext) nsStyleMargin(mPresContext);
      aContext->SetStyle(eStyleStruct_Margin, margin);
      return margin;
    }
    case eStyleStruct_Border:
    {
      nsStyleBorder* border = new (mPresContext) nsStyleBorder(mPresContext);
      aContext->SetStyle(eStyleStruct_Border, border);
      return border;
    }
    case eStyleStruct_Padding:
    {
      nsStylePadding* padding = new (mPresContext) nsStylePadding(mPresContext);
      aContext->SetStyle(eStyleStruct_Padding, padding);
      return padding;
    }
    case eStyleStruct_Outline:
    {
      nsStyleOutline* outline = new (mPresContext) nsStyleOutline(mPresContext);
      aContext->SetStyle(eStyleStruct_Outline, outline);
      return outline;
    }
    case eStyleStruct_List:
    {
      nsStyleList* list = new (mPresContext) nsStyleList(mPresContext);
      aContext->SetStyle(eStyleStruct_List, list);
      return list;
    }
    case eStyleStruct_Position:
    {
      nsStylePosition* pos = new (mPresContext) nsStylePosition(mPresContext);
      aContext->SetStyle(eStyleStruct_Position, pos);
      return pos;
    }
    case eStyleStruct_Table:
    {
      nsStyleTable* table = new (mPresContext) nsStyleTable(mPresContext);
      aContext->SetStyle(eStyleStruct_Table, table);
      return table;
    }
    case eStyleStruct_TableBorder:
    {
      nsStyleTableBorder* table = new (mPresContext) nsStyleTableBorder(mPresContext);
      aContext->SetStyle(eStyleStruct_TableBorder, table);
      return table;
    }
    case eStyleStruct_Content:
    {
      nsStyleContent* content = new (mPresContext) nsStyleContent(mPresContext);
      aContext->SetStyle(eStyleStruct_Content, content);
      return content;
    }
    case eStyleStruct_UserInterface:
    {
      nsStyleUserInterface* ui = new (mPresContext) nsStyleUserInterface(mPresContext);
      aContext->SetStyle(eStyleStruct_UserInterface, ui);
      return ui;
    }
    case eStyleStruct_UIReset:
    {
      nsStyleUIReset* ui = new (mPresContext) nsStyleUIReset(mPresContext);
      aContext->SetStyle(eStyleStruct_UIReset, ui);
      return ui;
    }
    case eStyleStruct_XUL:
    {
      nsStyleXUL* xul = new (mPresContext) nsStyleXUL(mPresContext);
      aContext->SetStyle(eStyleStruct_XUL, xul);
      return xul;
    }
    case eStyleStruct_Column:
    {
      nsStyleColumn* column = new (mPresContext) nsStyleColumn(mPresContext);
      aContext->SetStyle(eStyleStruct_Column, column);
      return column;
    }
    case eStyleStruct_SVG:
    {
      nsStyleSVG* svg = new (mPresContext) nsStyleSVG(mPresContext);
      aContext->SetStyle(eStyleStruct_SVG, svg);
      return svg;
    }
    case eStyleStruct_SVGReset:
    {
      nsStyleSVGReset* svgReset = new (mPresContext) nsStyleSVGReset(mPresContext);
      aContext->SetStyle(eStyleStruct_SVGReset, svgReset);
      return svgReset;
    }
    case eStyleStruct_Variables:
    {
      nsStyleVariables* vars = new (mPresContext) nsStyleVariables(mPresContext);
      aContext->SetStyle(eStyleStruct_Variables, vars);
      return vars;
    }
    case eStyleStruct_Effects:
    {
      nsStyleEffects* effects = new (mPresContext) nsStyleEffects(mPresContext);
      aContext->SetStyle(eStyleStruct_Effects, effects);
      return effects;
    }
    default:
      /*
       * unhandled case: nsStyleStructID_Length.
       * last item of nsStyleStructID, to know its length.
       */
      MOZ_ASSERT(false, "unexpected SID");
      return nullptr;
  }
  return nullptr;
}

/**
 * Begin an nsRuleNode::Compute*Data function for an inherited struct.
 *
 * @param type_ The nsStyle* type this function computes.
 * @param data_ Variable (declared here) holding the result of this
 *              function.
 * @param parentdata_ Variable (declared here) holding the parent style
 *                    context's data for this struct.
 */
#define COMPUTE_START_INHERITED(type_, data_, parentdata_)                    \
  NS_ASSERTION(aRuleDetail != eRuleFullInherited,                             \
               "should not have bothered calling Compute*Data");              \
                                                                              \
  nsStyleContext* parentContext = aContext->GetParent();                      \
                                                                              \
  nsStyle##type_* data_ = nullptr;                                            \
  mozilla::Maybe<nsStyle##type_> maybeFakeParentData;                         \
  const nsStyle##type_* parentdata_ = nullptr;                                \
  RuleNodeCacheConditions conditions = aConditions;                           \
                                                                              \
  /* If |conditions.Cacheable()| might be true by the time we're done, we */  \
  /* can't call parentContext->Style##type_() since it could recur into */    \
  /* setting the same struct on the same rule node, causing a leak. */        \
  if (aRuleDetail != eRuleFullReset &&                                        \
      (!aStartStruct || (aRuleDetail != eRulePartialReset &&                  \
                         aRuleDetail != eRuleNone))) {                        \
    if (parentContext) {                                                      \
      parentdata_ = parentContext->Style##type_();                            \
    } else {                                                                  \
      maybeFakeParentData.emplace(mPresContext);                              \
      parentdata_ = maybeFakeParentData.ptr();                                \
    }                                                                         \
  }                                                                           \
  if (eStyleStruct_##type_ == eStyleStruct_Variables)                         \
    /* no need to copy construct an nsStyleVariables, as we will copy */      \
    /* inherited variables (and call SetUncacheable()) in */                  \
    /* ComputeVariablesData */                                                \
    data_ = new (mPresContext) nsStyle##type_(mPresContext);                  \
  else if (aStartStruct)                                                      \
    /* We only need to compute the delta between this computed data and */    \
    /* our computed data. */                                                  \
    data_ = new (mPresContext)                                                \
            nsStyle##type_(*static_cast<nsStyle##type_*>(aStartStruct));      \
  else {                                                                      \
    if (aRuleDetail != eRuleFullMixed && aRuleDetail != eRuleFullReset) {     \
      /* No question. We will have to inherit. Go ahead and init */           \
      /* with inherited vals from parent. */                                  \
      conditions.SetUncacheable();                                            \
      if (parentdata_)                                                        \
        data_ = new (mPresContext) nsStyle##type_(*parentdata_);              \
      else                                                                    \
        data_ = new (mPresContext) nsStyle##type_(mPresContext);              \
    }                                                                         \
    else                                                                      \
      data_ = new (mPresContext) nsStyle##type_(mPresContext);                \
  }                                                                           \
                                                                              \
  if (!parentdata_)                                                           \
    parentdata_ = data_;

/**
 * Begin an nsRuleNode::Compute*Data function for a reset struct.
 *
 * @param type_ The nsStyle* type this function computes.
 * @param data_ Variable (declared here) holding the result of this
 *              function.
 * @param parentdata_ Variable (declared here) holding the parent style
 *                    context's data for this struct.
 */
#define COMPUTE_START_RESET(type_, data_, parentdata_)                        \
  NS_ASSERTION(aRuleDetail != eRuleFullInherited,                             \
               "should not have bothered calling Compute*Data");              \
                                                                              \
  nsStyleContext* parentContext = aContext->GetParent();                      \
  /* Reset structs don't inherit from first-line */                           \
  /* See similar code in WalkRuleTree */                                      \
  while (parentContext &&                                                     \
         parentContext->GetPseudo() == nsCSSPseudoElements::firstLine) {      \
    parentContext = parentContext->GetParent();                               \
  }                                                                           \
                                                                              \
  nsStyle##type_* data_;                                                      \
  if (aStartStruct)                                                           \
    /* We only need to compute the delta between this computed data and */    \
    /* our computed data. */                                                  \
    data_ = new (mPresContext)                                                \
            nsStyle##type_(*static_cast<nsStyle##type_*>(aStartStruct));      \
  else                                                                        \
    data_ = new (mPresContext) nsStyle##type_(mPresContext);                  \
                                                                              \
  /* If |conditions.Cacheable()| might be true by the time we're done, we */  \
  /* can't call parentContext->Style##type_() since it could recur into */    \
  /* setting the same struct on the same rule node, causing a leak. */        \
  mozilla::Maybe<nsStyle##type_> maybeFakeParentData;                         \
  const nsStyle##type_* parentdata_ = data_;                                  \
  if (aRuleDetail != eRuleFullReset &&                                        \
      aRuleDetail != eRulePartialReset &&                                     \
      aRuleDetail != eRuleNone) {                                             \
    if (parentContext) {                                                      \
      parentdata_ = parentContext->Style##type_();                            \
    } else {                                                                  \
      maybeFakeParentData.emplace(mPresContext);                              \
      parentdata_ = maybeFakeParentData.ptr();                                \
    }                                                                         \
  }                                                                           \
  RuleNodeCacheConditions conditions = aConditions;

/**
 * End an nsRuleNode::Compute*Data function for an inherited struct.
 *
 * @param type_ The nsStyle* type this function computes.
 * @param data_ Variable holding the result of this function.
 */
#define COMPUTE_END_INHERITED(type_, data_)                                   \
  NS_POSTCONDITION(!conditions.CacheableWithoutDependencies() ||              \
                   aRuleDetail == eRuleFullReset ||                           \
                   (aStartStruct && aRuleDetail == eRulePartialReset),        \
                   "conditions.CacheableWithoutDependencies() must be false " \
                   "for inherited structs unless all properties have been "   \
                   "specified with values other than inherit");               \
  if (conditions.CacheableWithoutDependencies()) {                            \
    /* We were fully specified and can therefore be cached right on the */    \
    /* rule node. */                                                          \
    if (!aHighestNode->mStyleData.mInheritedData) {                           \
      aHighestNode->mStyleData.mInheritedData =                               \
        new (mPresContext) nsInheritedStyleData;                              \
    }                                                                         \
    NS_ASSERTION(!aHighestNode->mStyleData.mInheritedData->                   \
                   mStyleStructs[eStyleStruct_##type_],                       \
                 "Going to leak style data");                                 \
    aHighestNode->mStyleData.mInheritedData->                                 \
      mStyleStructs[eStyleStruct_##type_] = data_;                            \
    /* Propagate the bit down. */                                             \
    PropagateDependentBit(eStyleStruct_##type_, aHighestNode, data_);         \
    /* Tell the style context that it doesn't own the data */                 \
    aContext->AddStyleBit(NS_STYLE_INHERIT_BIT(type_));                       \
  }                                                                           \
  /* For inherited structs, our caller will cache the data on the */          \
  /* style context */                                                         \
                                                                              \
  return data_;

/**
 * End an nsRuleNode::Compute*Data function for a reset struct.
 *
 * @param type_ The nsStyle* type this function computes.
 * @param data_ Variable holding the result of this function.
 */
#define COMPUTE_END_RESET(type_, data_)                                       \
  NS_POSTCONDITION(!conditions.CacheableWithoutDependencies() ||              \
                   aRuleDetail == eRuleNone ||                                \
                   aRuleDetail == eRulePartialReset ||                        \
                   aRuleDetail == eRuleFullReset,                             \
                   "conditions.CacheableWithoutDependencies() must be false " \
                   "for reset structs if any properties were specified as "   \
                   "inherit");                                                \
  if (conditions.CacheableWithoutDependencies()) {                            \
    /* We were fully specified and can therefore be cached right on the */    \
    /* rule node. */                                                          \
    if (!aHighestNode->mStyleData.mResetData) {                               \
      aHighestNode->mStyleData.mResetData =                                   \
        new (mPresContext) nsConditionalResetStyleData;                       \
    }                                                                         \
    NS_ASSERTION(!aHighestNode->mStyleData.mResetData->                       \
                   GetStyleData(eStyleStruct_##type_),                        \
                 "Going to leak style data");                                 \
    aHighestNode->mStyleData.mResetData->                                     \
      SetStyleData(eStyleStruct_##type_, data_);                              \
    /* Propagate the bit down. */                                             \
    PropagateDependentBit(eStyleStruct_##type_, aHighestNode, data_);         \
    if (HasAnimationData()) {                                                 \
      /* If we have animation data, the struct should be cached on the */     \
      /* style context so that we can peek the struct. */                     \
      /* See comment in AnimValuesStyleRule::MapRuleInfoInto. */              \
      StoreStyleOnContext(aContext, eStyleStruct_##type_, data_);             \
    }                                                                         \
  } else if (conditions.Cacheable()) {                                        \
    if (!mStyleData.mResetData) {                                             \
      mStyleData.mResetData = new (mPresContext) nsConditionalResetStyleData; \
    }                                                                         \
    mStyleData.mResetData->                                                   \
      SetStyleData(eStyleStruct_##type_, mPresContext, data_, conditions);    \
    /* Tell the style context that it doesn't own the data */                 \
    aContext->AddStyleBit(NS_STYLE_INHERIT_BIT(type_));                       \
    aContext->SetStyle(eStyleStruct_##type_, data_);                          \
  } else {                                                                    \
    /* We can't be cached in the rule node.  We have to be put right */       \
    /* on the style context. */                                               \
    aContext->SetStyle(eStyleStruct_##type_, data_);                          \
    if (aContext->GetParent()) {                                              \
      /* This is pessimistic; we could be uncacheable because we had a */     \
      /* relative font-weight, for example, which does not need to defeat */  \
      /* the restyle optimizations in RestyleManager.cpp that look at */      \
      /* NS_STYLE_HAS_CHILD_THAT_USES_RESET_STYLE. */                         \
      aContext->GetParent()->                                                 \
        AddStyleBit(NS_STYLE_HAS_CHILD_THAT_USES_RESET_STYLE);                \
    }                                                                         \
  }                                                                           \
                                                                              \
  return data_;

// This function figures out how much scaling should be suppressed to
// satisfy scriptminsize. This is our attempt to implement
// http://www.w3.org/TR/MathML2/chapter3.html#id.3.3.4.2.2
// This is called after mScriptLevel, mScriptMinSize and mScriptSizeMultiplier
// have been set in aFont.
//
// Here are the invariants we enforce:
// 1) A decrease in size must not reduce the size below minscriptsize.
// 2) An increase in size must not increase the size above the size we would
// have if minscriptsize had not been applied anywhere.
// 3) The scriptlevel-induced size change must between 1.0 and the parent's
// scriptsizemultiplier^(new script level - old script level), as close to the
// latter as possible subject to constraints 1 and 2.
static nscoord
ComputeScriptLevelSize(const nsStyleFont* aFont, const nsStyleFont* aParentFont,
                       nsPresContext* aPresContext, nscoord* aUnconstrainedSize)
{
  int32_t scriptLevelChange =
    aFont->mScriptLevel - aParentFont->mScriptLevel;
  if (scriptLevelChange == 0) {
    *aUnconstrainedSize = aParentFont->mScriptUnconstrainedSize;
    // Constraint #3 says that we cannot change size, and #1 and #2 are always
    // satisfied with no change. It's important this be fast because it covers
    // all non-MathML content.
    return aParentFont->mSize;
  }

  // Compute actual value of minScriptSize
  nscoord minScriptSize = aParentFont->mScriptMinSize;
  if (aFont->mAllowZoom) {
    minScriptSize = nsStyleFont::ZoomText(aPresContext, minScriptSize);
  }

  double scriptLevelScale =
    pow(aParentFont->mScriptSizeMultiplier, scriptLevelChange);
  // Compute the size we would have had if minscriptsize had never been
  // applied, also prevent overflow (bug 413274)
  *aUnconstrainedSize =
    NSToCoordRoundWithClamp(aParentFont->mScriptUnconstrainedSize*scriptLevelScale);
  // Compute the size we could get via scriptlevel change
  nscoord scriptLevelSize =
    NSToCoordRoundWithClamp(aParentFont->mSize*scriptLevelScale);
  if (scriptLevelScale <= 1.0) {
    if (aParentFont->mSize <= minScriptSize) {
      // We can't decrease the font size at all, so just stick to no change
      // (authors are allowed to explicitly set the font size smaller than
      // minscriptsize)
      return aParentFont->mSize;
    }
    // We can decrease, so apply constraint #1
    return std::max(minScriptSize, scriptLevelSize);
  } else {
    // scriptminsize can only make sizes larger than the unconstrained size
    NS_ASSERTION(*aUnconstrainedSize <= scriptLevelSize, "How can this ever happen?");
    // Apply constraint #2
    return std::min(scriptLevelSize, std::max(*aUnconstrainedSize, minScriptSize));
  }
}


/* static */ nscoord
nsRuleNode::CalcFontPointSize(int32_t aHTMLSize, int32_t aBasePointSize,
                              nsPresContext* aPresContext,
                              nsFontSizeType aFontSizeType)
{
#define sFontSizeTableMin  9
#define sFontSizeTableMax 16

// This table seems to be the one used by MacIE5. We hope its adoption in Mozilla
// and eventually in WinIE5.5 will help to establish a standard rendering across
// platforms and browsers. For now, it is used only in Strict mode. More can be read
// in the document written by Todd Farhner at:
// http://style.verso.com/font_size_intervals/altintervals.html
//
  static int32_t sStrictFontSizeTable[sFontSizeTableMax - sFontSizeTableMin + 1][8] =
  {
      { 9,    9,     9,     9,    11,    14,    18,    27},
      { 9,    9,     9,    10,    12,    15,    20,    30},
      { 9,    9,    10,    11,    13,    17,    22,    33},
      { 9,    9,    10,    12,    14,    18,    24,    36},
      { 9,   10,    12,    13,    16,    20,    26,    39},
      { 9,   10,    12,    14,    17,    21,    28,    42},
      { 9,   10,    13,    15,    18,    23,    30,    45},
      { 9,   10,    13,    16,    18,    24,    32,    48}
  };
// HTML       1      2      3      4      5      6      7
// CSS  xxs   xs     s      m      l     xl     xxl
//                          |
//                      user pref
//
//------------------------------------------------------------
//
// This table gives us compatibility with WinNav4 for the default fonts only.
// In WinNav4, the default fonts were:
//
//     Times/12pt ==   Times/16px at 96ppi
//   Courier/10pt == Courier/13px at 96ppi
//
// The 2 lines below marked "anchored" have the exact pixel sizes used by
// WinNav4 for Times/12pt and Courier/10pt at 96ppi. As you can see, the
// HTML size 3 (user pref) for those 2 anchored lines is 13px and 16px.
//
// All values other than the anchored values were filled in by hand, never
// going below 9px, and maintaining a "diagonal" relationship. See for
// example the 13s -- they follow a diagonal line through the table.
//
  static int32_t sQuirksFontSizeTable[sFontSizeTableMax - sFontSizeTableMin + 1][8] =
  {
      { 9,    9,     9,     9,    11,    14,    18,    28 },
      { 9,    9,     9,    10,    12,    15,    20,    31 },
      { 9,    9,     9,    11,    13,    17,    22,    34 },
      { 9,    9,    10,    12,    14,    18,    24,    37 },
      { 9,    9,    10,    13,    16,    20,    26,    40 }, // anchored (13)
      { 9,    9,    11,    14,    17,    21,    28,    42 },
      { 9,   10,    12,    15,    17,    23,    30,    45 },
      { 9,   10,    13,    16,    18,    24,    32,    48 }  // anchored (16)
  };
// HTML       1      2      3      4      5      6      7
// CSS  xxs   xs     s      m      l     xl     xxl
//                          |
//                      user pref

#if 0
//
// These are the exact pixel values used by WinIE5 at 96ppi.
//
      { ?,    8,    11,    12,    13,    16,    21,    32 }, // smallest
      { ?,    9,    12,    13,    16,    21,    27,    40 }, // smaller
      { ?,   10,    13,    16,    18,    24,    32,    48 }, // medium
      { ?,   13,    16,    19,    21,    27,    37,    ?? }, // larger
      { ?,   16,    19,    21,    24,    32,    43,    ?? }  // largest
//
// HTML       1      2      3      4      5      6      7
// CSS  ?     ?      ?      ?      ?      ?      ?      ?
//
// (CSS not tested yet.)
//
#endif

  static int32_t sFontSizeFactors[8] = { 60,75,89,100,120,150,200,300 };

  static int32_t sCSSColumns[7]  = {0, 1, 2, 3, 4, 5, 6}; // xxs...xxl
  static int32_t sHTMLColumns[7] = {1, 2, 3, 4, 5, 6, 7}; // 1...7

  double dFontSize;

  if (aFontSizeType == eFontSize_HTML) {
    aHTMLSize--;    // input as 1-7
  }

  if (aHTMLSize < 0)
    aHTMLSize = 0;
  else if (aHTMLSize > 6)
    aHTMLSize = 6;

  int32_t* column;
  switch (aFontSizeType)
  {
    case eFontSize_HTML: column = sHTMLColumns; break;
    case eFontSize_CSS:  column = sCSSColumns;  break;
  }

  // Make special call specifically for fonts (needed PrintPreview)
  int32_t fontSize = nsPresContext::AppUnitsToIntCSSPixels(aBasePointSize);

  if ((fontSize >= sFontSizeTableMin) && (fontSize <= sFontSizeTableMax))
  {
    int32_t row = fontSize - sFontSizeTableMin;

    if (aPresContext->CompatibilityMode() == eCompatibility_NavQuirks) {
      dFontSize = nsPresContext::CSSPixelsToAppUnits(sQuirksFontSizeTable[row][column[aHTMLSize]]);
    } else {
      dFontSize = nsPresContext::CSSPixelsToAppUnits(sStrictFontSizeTable[row][column[aHTMLSize]]);
    }
  }
  else
  {
    int32_t factor = sFontSizeFactors[column[aHTMLSize]];
    dFontSize = (factor * aBasePointSize) / 100;
  }


  if (1.0 < dFontSize) {
    return (nscoord)dFontSize;
  }
  return (nscoord)1;
}


//------------------------------------------------------------------------------
//
//------------------------------------------------------------------------------

/* static */ nscoord
nsRuleNode::FindNextSmallerFontSize(nscoord aFontSize, int32_t aBasePointSize,
                                    nsPresContext* aPresContext,
                                    nsFontSizeType aFontSizeType)
{
  int32_t index;
  int32_t indexMin;
  int32_t indexMax;
  float relativePosition;
  nscoord smallerSize;
  nscoord indexFontSize = aFontSize; // XXX initialize to quell a spurious gcc3.2 warning
  nscoord smallestIndexFontSize;
  nscoord largestIndexFontSize;
  nscoord smallerIndexFontSize;
  nscoord largerIndexFontSize;

  nscoord onePx = nsPresContext::CSSPixelsToAppUnits(1);

  if (aFontSizeType == eFontSize_HTML) {
    indexMin = 1;
    indexMax = 7;
  } else {
    indexMin = 0;
    indexMax = 6;
  }

  smallestIndexFontSize = CalcFontPointSize(indexMin, aBasePointSize, aPresContext, aFontSizeType);
  largestIndexFontSize = CalcFontPointSize(indexMax, aBasePointSize, aPresContext, aFontSizeType);
  if (aFontSize > smallestIndexFontSize) {
    if (aFontSize < NSToCoordRound(float(largestIndexFontSize) * 1.5)) { // smaller will be in HTML table
      // find largest index smaller than current
      for (index = indexMax; index >= indexMin; index--) {
        indexFontSize = CalcFontPointSize(index, aBasePointSize, aPresContext, aFontSizeType);
        if (indexFontSize < aFontSize)
          break;
      }
      // set up points beyond table for interpolation purposes
      if (indexFontSize == smallestIndexFontSize) {
        smallerIndexFontSize = indexFontSize - onePx;
        largerIndexFontSize = CalcFontPointSize(index+1, aBasePointSize, aPresContext, aFontSizeType);
      } else if (indexFontSize == largestIndexFontSize) {
        smallerIndexFontSize = CalcFontPointSize(index-1, aBasePointSize, aPresContext, aFontSizeType);
        largerIndexFontSize = NSToCoordRound(float(largestIndexFontSize) * 1.5);
      } else {
        smallerIndexFontSize = CalcFontPointSize(index-1, aBasePointSize, aPresContext, aFontSizeType);
        largerIndexFontSize = CalcFontPointSize(index+1, aBasePointSize, aPresContext, aFontSizeType);
      }
      // compute the relative position of the parent size between the two closest indexed sizes
      relativePosition = float(aFontSize - indexFontSize) / float(largerIndexFontSize - indexFontSize);
      // set the new size to have the same relative position between the next smallest two indexed sizes
      smallerSize = smallerIndexFontSize + NSToCoordRound(relativePosition * (indexFontSize - smallerIndexFontSize));
    }
    else {  // larger than HTML table, drop by 33%
      smallerSize = NSToCoordRound(float(aFontSize) / 1.5);
    }
  }
  else { // smaller than HTML table, drop by 1px
    smallerSize = std::max(aFontSize - onePx, onePx);
  }
  return smallerSize;
}

//------------------------------------------------------------------------------
//
//------------------------------------------------------------------------------

/* static */ nscoord
nsRuleNode::FindNextLargerFontSize(nscoord aFontSize, int32_t aBasePointSize,
                                   nsPresContext* aPresContext,
                                   nsFontSizeType aFontSizeType)
{
  int32_t index;
  int32_t indexMin;
  int32_t indexMax;
  float relativePosition;
  nscoord adjustment;
  nscoord largerSize;
  nscoord indexFontSize = aFontSize; // XXX initialize to quell a spurious gcc3.2 warning
  nscoord smallestIndexFontSize;
  nscoord largestIndexFontSize;
  nscoord smallerIndexFontSize;
  nscoord largerIndexFontSize;

  nscoord onePx = nsPresContext::CSSPixelsToAppUnits(1);

  if (aFontSizeType == eFontSize_HTML) {
    indexMin = 1;
    indexMax = 7;
  } else {
    indexMin = 0;
    indexMax = 6;
  }

  smallestIndexFontSize = CalcFontPointSize(indexMin, aBasePointSize, aPresContext, aFontSizeType);
  largestIndexFontSize = CalcFontPointSize(indexMax, aBasePointSize, aPresContext, aFontSizeType);
  if (aFontSize > (smallestIndexFontSize - onePx)) {
    if (aFontSize < largestIndexFontSize) { // larger will be in HTML table
      // find smallest index larger than current
      for (index = indexMin; index <= indexMax; index++) {
        indexFontSize = CalcFontPointSize(index, aBasePointSize, aPresContext, aFontSizeType);
        if (indexFontSize > aFontSize)
          break;
      }
      // set up points beyond table for interpolation purposes
      if (indexFontSize == smallestIndexFontSize) {
        smallerIndexFontSize = indexFontSize - onePx;
        largerIndexFontSize = CalcFontPointSize(index+1, aBasePointSize, aPresContext, aFontSizeType);
      } else if (indexFontSize == largestIndexFontSize) {
        smallerIndexFontSize = CalcFontPointSize(index-1, aBasePointSize, aPresContext, aFontSizeType);
        largerIndexFontSize = NSCoordSaturatingMultiply(largestIndexFontSize, 1.5);
      } else {
        smallerIndexFontSize = CalcFontPointSize(index-1, aBasePointSize, aPresContext, aFontSizeType);
        largerIndexFontSize = CalcFontPointSize(index+1, aBasePointSize, aPresContext, aFontSizeType);
      }
      // compute the relative position of the parent size between the two closest indexed sizes
      relativePosition = float(aFontSize - smallerIndexFontSize) / float(indexFontSize - smallerIndexFontSize);
      // set the new size to have the same relative position between the next largest two indexed sizes
      adjustment = NSCoordSaturatingNonnegativeMultiply(largerIndexFontSize - indexFontSize, relativePosition);
      largerSize = NSCoordSaturatingAdd(indexFontSize, adjustment);
    }
    else {  // larger than HTML table, increase by 50%
      largerSize = NSCoordSaturatingMultiply(aFontSize, 1.5);
    }
  }
  else { // smaller than HTML table, increase by 1px
    largerSize = NSCoordSaturatingAdd(aFontSize, onePx);
  }
  return largerSize;
}

struct SetFontSizeCalcOps : public css::BasicCoordCalcOps,
                            public css::NumbersAlreadyNormalizedOps
{
  // The parameters beyond aValue that we need for CalcLengthWith.
  const nscoord mParentSize;
  const nsStyleFont* const mParentFont;
  nsPresContext* const mPresContext;
  nsStyleContext* const mStyleContext;
  const bool mAtRoot;
  RuleNodeCacheConditions& mConditions;

  SetFontSizeCalcOps(nscoord aParentSize, const nsStyleFont* aParentFont,
                     nsPresContext* aPresContext,
                     nsStyleContext* aStyleContext,
                     bool aAtRoot,
                     RuleNodeCacheConditions& aConditions)
    : mParentSize(aParentSize),
      mParentFont(aParentFont),
      mPresContext(aPresContext),
      mStyleContext(aStyleContext),
      mAtRoot(aAtRoot),
      mConditions(aConditions)
  {
  }

  result_type ComputeLeafValue(const nsCSSValue& aValue)
  {
    nscoord size;
    if (aValue.IsLengthUnit()) {
      // Note that font-based length units use the parent's size
      // unadjusted for scriptlevel changes. A scriptlevel change
      // between us and the parent is simply ignored.
      size = CalcLengthWith(aValue, mParentSize,
                            mParentFont,
                            mStyleContext, mPresContext, mAtRoot,
                            true, mConditions);
      if (!aValue.IsRelativeLengthUnit() && mParentFont->mAllowZoom) {
        size = nsStyleFont::ZoomText(mPresContext, size);
      }
    }
    else if (eCSSUnit_Percent == aValue.GetUnit()) {
      mConditions.SetUncacheable();
      // Note that % units use the parent's size unadjusted for scriptlevel
      // changes. A scriptlevel change between us and the parent is simply
      // ignored.
      // aValue.GetPercentValue() may be negative for, e.g., calc(-50%)
      size = NSCoordSaturatingMultiply(mParentSize, aValue.GetPercentValue());
    } else {
      MOZ_ASSERT(false, "unexpected value");
      size = mParentSize;
    }

    return size;
  }
};

/* static */ void
nsRuleNode::SetFontSize(nsPresContext* aPresContext,
                        nsStyleContext* aContext,
                        const nsRuleData* aRuleData,
                        const nsStyleFont* aFont,
                        const nsStyleFont* aParentFont,
                        nscoord* aSize,
                        const nsFont& aSystemFont,
                        nscoord aParentSize,
                        nscoord aScriptLevelAdjustedParentSize,
                        bool aUsedStartStruct,
                        bool aAtRoot,
                        RuleNodeCacheConditions& aConditions)
{
  // If false, means that *aSize has not been zoomed.  If true, means that
  // *aSize has been zoomed iff aParentFont->mAllowZoom is true.
  bool sizeIsZoomedAccordingToParent = false;

  int32_t baseSize = (int32_t) aPresContext->
    GetDefaultFont(aFont->mGenericID, aFont->mLanguage)->size;
  const nsCSSValue* sizeValue = aRuleData->ValueForFontSize();
  if (eCSSUnit_Enumerated == sizeValue->GetUnit()) {
    int32_t value = sizeValue->GetIntValue();

    if ((NS_STYLE_FONT_SIZE_XXSMALL <= value) &&
        (value <= NS_STYLE_FONT_SIZE_XXLARGE)) {
      *aSize = CalcFontPointSize(value, baseSize,
                       aPresContext, eFontSize_CSS);
    }
    else if (NS_STYLE_FONT_SIZE_XXXLARGE == value) {
      // <font size="7"> is not specified in CSS, so we don't use eFontSize_CSS.
      *aSize = CalcFontPointSize(value, baseSize, aPresContext);
    }
    else if (NS_STYLE_FONT_SIZE_LARGER  == value ||
             NS_STYLE_FONT_SIZE_SMALLER == value) {
      aConditions.SetUncacheable();

      // Un-zoom so we use the tables correctly.  We'll then rezoom due
      // to the |zoom = true| above.
      // Note that relative units here use the parent's size unadjusted
      // for scriptlevel changes. A scriptlevel change between us and the parent
      // is simply ignored.
      nscoord parentSize = aParentSize;
      if (aParentFont->mAllowZoom) {
        parentSize = nsStyleFont::UnZoomText(aPresContext, parentSize);
      }

      if (NS_STYLE_FONT_SIZE_LARGER == value) {
        *aSize = FindNextLargerFontSize(parentSize,
                         baseSize, aPresContext, eFontSize_CSS);

        NS_ASSERTION(*aSize >= parentSize,
                     "FindNextLargerFontSize failed");
      }
      else {
        *aSize = FindNextSmallerFontSize(parentSize,
                         baseSize, aPresContext, eFontSize_CSS);
        NS_ASSERTION(*aSize < parentSize ||
                     parentSize <= nsPresContext::CSSPixelsToAppUnits(1),
                     "FindNextSmallerFontSize failed");
      }
    } else {
      NS_NOTREACHED("unexpected value");
    }
  }
  else if (sizeValue->IsLengthUnit() ||
           sizeValue->GetUnit() == eCSSUnit_Percent ||
           sizeValue->IsCalcUnit()) {
    SetFontSizeCalcOps ops(aParentSize, aParentFont,
                           aPresContext, aContext,
                           aAtRoot,
                           aConditions);
    *aSize = css::ComputeCalc(*sizeValue, ops);
    if (*aSize < 0) {
      MOZ_ASSERT(sizeValue->IsCalcUnit(),
                 "negative lengths and percents should be rejected by parser");
      *aSize = 0;
    }
    // The calc ops will always zoom its result according to the value
    // of aParentFont->mAllowZoom.
    sizeIsZoomedAccordingToParent = true;
  }
  else if (eCSSUnit_System_Font == sizeValue->GetUnit()) {
    // this becomes our cascading size
    *aSize = aSystemFont.size;
  }
  else if (eCSSUnit_Inherit == sizeValue->GetUnit() ||
           eCSSUnit_Unset == sizeValue->GetUnit()) {
    aConditions.SetUncacheable();
    // We apply scriptlevel change for this case, because the default is
    // to inherit and we don't want explicit "inherit" to differ from the
    // default.
    *aSize = aScriptLevelAdjustedParentSize;
    sizeIsZoomedAccordingToParent = true;
  }
  else if (eCSSUnit_Initial == sizeValue->GetUnit()) {
    // The initial value is 'medium', which has magical sizing based on
    // the generic font family, so do that here too.
    *aSize = baseSize;
  } else {
    NS_ASSERTION(eCSSUnit_Null == sizeValue->GetUnit(),
                 "What kind of font-size value is this?");
    // if aUsedStartStruct is true, then every single property in the
    // font struct is being set all at once. This means scriptlevel is not
    // going to have any influence on the font size; there is no need to
    // do anything here.
    if (!aUsedStartStruct && aParentSize != aScriptLevelAdjustedParentSize) {
      // There was no rule affecting the size but the size has been
      // affected by the parent's size via scriptlevel change. So we cannot
      // store the data in the rule tree.
      aConditions.SetUncacheable();
      *aSize = aScriptLevelAdjustedParentSize;
      sizeIsZoomedAccordingToParent = true;
    } else {
      return;
    }
  }

  // We want to zoom the cascaded size so that em-based measurements,
  // line-heights, etc., work.
  bool currentlyZoomed = sizeIsZoomedAccordingToParent &&
                         aParentFont->mAllowZoom;
  if (!currentlyZoomed && aFont->mAllowZoom) {
    *aSize = nsStyleFont::ZoomText(aPresContext, *aSize);
  } else if (currentlyZoomed && !aFont->mAllowZoom) {
    *aSize = nsStyleFont::UnZoomText(aPresContext, *aSize);
  }
}

static int8_t ClampTo8Bit(int32_t aValue) {
  if (aValue < -128)
    return -128;
  if (aValue > 127)
    return 127;
  return int8_t(aValue);
}

/* static */ void
nsRuleNode::SetFont(nsPresContext* aPresContext, nsStyleContext* aContext,
                    uint8_t aGenericFontID, const nsRuleData* aRuleData,
                    const nsStyleFont* aParentFont,
                    nsStyleFont* aFont, bool aUsedStartStruct,
                    RuleNodeCacheConditions& aConditions)
{
  bool atRoot = !aContext->GetParent();

  // -x-text-zoom: none, inherit, initial
  bool allowZoom;
  const nsCSSValue* textZoomValue = aRuleData->ValueForTextZoom();
  if (eCSSUnit_Null != textZoomValue->GetUnit()) {
    if (eCSSUnit_Inherit == textZoomValue->GetUnit()) {
      allowZoom = aParentFont->mAllowZoom;
    } else if (eCSSUnit_None == textZoomValue->GetUnit()) {
      allowZoom = false;
    } else {
      MOZ_ASSERT(eCSSUnit_Initial == textZoomValue->GetUnit(),
                 "unexpected unit");
      allowZoom = true;
    }
    aFont->EnableZoom(aPresContext, allowZoom);
  }

  // mLanguage must be set before before any of the CalcLengthWith calls
  // (direct calls or calls via SetFontSize) for the cases where |aParentFont|
  // is the same as |aFont|.
  //
  // -x-lang: string, inherit
  // This is not a real CSS property, it is an HTML attribute mapped to CSS.
  const nsCSSValue* langValue = aRuleData->ValueForLang();
  if (eCSSUnit_Ident == langValue->GetUnit()) {
    nsAutoString lang;
    langValue->GetStringValue(lang);

    nsContentUtils::ASCIIToLower(lang);
    aFont->mLanguage = NS_Atomize(lang);
    aFont->mExplicitLanguage = true;
  }

  const nsFont* defaultVariableFont =
    aPresContext->GetDefaultFont(kPresContext_DefaultVariableFont_ID,
                                 aFont->mLanguage);

  // -moz-system-font: enum (never inherit!)
  static_assert(
    NS_STYLE_FONT_CAPTION        == LookAndFeel::eFont_Caption &&
    NS_STYLE_FONT_ICON           == LookAndFeel::eFont_Icon &&
    NS_STYLE_FONT_MENU           == LookAndFeel::eFont_Menu &&
    NS_STYLE_FONT_MESSAGE_BOX    == LookAndFeel::eFont_MessageBox &&
    NS_STYLE_FONT_SMALL_CAPTION  == LookAndFeel::eFont_SmallCaption &&
    NS_STYLE_FONT_STATUS_BAR     == LookAndFeel::eFont_StatusBar &&
    NS_STYLE_FONT_WINDOW         == LookAndFeel::eFont_Window &&
    NS_STYLE_FONT_DOCUMENT       == LookAndFeel::eFont_Document &&
    NS_STYLE_FONT_WORKSPACE      == LookAndFeel::eFont_Workspace &&
    NS_STYLE_FONT_DESKTOP        == LookAndFeel::eFont_Desktop &&
    NS_STYLE_FONT_INFO           == LookAndFeel::eFont_Info &&
    NS_STYLE_FONT_DIALOG         == LookAndFeel::eFont_Dialog &&
    NS_STYLE_FONT_BUTTON         == LookAndFeel::eFont_Button &&
    NS_STYLE_FONT_PULL_DOWN_MENU == LookAndFeel::eFont_PullDownMenu &&
    NS_STYLE_FONT_LIST           == LookAndFeel::eFont_List &&
    NS_STYLE_FONT_FIELD          == LookAndFeel::eFont_Field,
    "LookAndFeel.h system-font constants out of sync with nsStyleConsts.h");

  // Fall back to defaultVariableFont.
  nsFont systemFont = *defaultVariableFont;
  const nsCSSValue* systemFontValue = aRuleData->ValueForSystemFont();
  if (eCSSUnit_Enumerated == systemFontValue->GetUnit()) {
    gfxFontStyle fontStyle;
    LookAndFeel::FontID fontID =
      (LookAndFeel::FontID)systemFontValue->GetIntValue();
    float devPerCSS =
      (float)nsPresContext::AppUnitsPerCSSPixel() /
      aPresContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom();
    nsAutoString systemFontName;
    if (LookAndFeel::GetFont(fontID, systemFontName, fontStyle, devPerCSS)) {
      systemFontName.Trim("\"'");
      systemFont.fontlist = FontFamilyList(systemFontName, eUnquotedName);
      systemFont.fontlist.SetDefaultFontType(eFamily_none);
      systemFont.style = fontStyle.style;
      systemFont.systemFont = fontStyle.systemFont;
      systemFont.weight = fontStyle.weight;
      systemFont.stretch = fontStyle.stretch;
      systemFont.size =
        NSFloatPixelsToAppUnits(fontStyle.size,
                                aPresContext->DeviceContext()->
                                  AppUnitsPerDevPixelAtUnitFullZoom());
      //systemFont.langGroup = fontStyle.langGroup;
      systemFont.sizeAdjust = fontStyle.sizeAdjust;

#ifdef XP_WIN
      // XXXldb This platform-specific stuff should be in the
      // LookAndFeel implementation, not here.
      // XXXzw Should we even still *have* this code?  It looks to be making
      // old, probably obsolete assumptions.

      if (fontID == LookAndFeel::eFont_Field ||
          fontID == LookAndFeel::eFont_Button ||
          fontID == LookAndFeel::eFont_List) {
        // As far as I can tell the system default fonts and sizes
        // on MS-Windows for Buttons, Listboxes/Comboxes and Text Fields are
        // all pre-determined and cannot be changed by either the control panel
        // or programmatically.
        // Fields (text fields)
        // Button and Selects (listboxes/comboboxes)
        //    We use whatever font is defined by the system. Which it appears
        //    (and the assumption is) it is always a proportional font. Then we
        //    always use 2 points smaller than what the browser has defined as
        //    the default proportional font.
        // Assumption: system defined font is proportional
        systemFont.size =
          std::max(defaultVariableFont->size -
                 nsPresContext::CSSPointsToAppUnits(2), 0);
      }
#endif
    }
  }

  // font-family: font family list, enum, inherit
  const nsCSSValue* familyValue = aRuleData->ValueForFontFamily();
  NS_ASSERTION(eCSSUnit_Enumerated != familyValue->GetUnit(),
               "system fonts should not be in mFamily anymore");
  if (eCSSUnit_FontFamilyList == familyValue->GetUnit()) {
    // set the correct font if we are using DocumentFonts OR we are overriding for XUL
    // MJA: bug 31816
    bool useDocumentFonts =
      aPresContext->GetCachedBoolPref(kPresContext_UseDocumentFonts);
    if (aGenericFontID == kGenericFont_NONE ||
        (!useDocumentFonts && (aGenericFontID == kGenericFont_cursive ||
                               aGenericFontID == kGenericFont_fantasy))) {
      FontFamilyType defaultGeneric =
        defaultVariableFont->fontlist.FirstGeneric();
      MOZ_ASSERT(defaultVariableFont->fontlist.Length() == 1 &&
                 (defaultGeneric == eFamily_serif ||
                  defaultGeneric == eFamily_sans_serif));
      if (defaultGeneric != eFamily_none) {
        if (useDocumentFonts) {
          aFont->mFont.fontlist.SetDefaultFontType(defaultGeneric);
        } else {
          // Either prioritize the first generic in the list,
          // or (if there isn't one) prepend the default variable font.
          if (!aFont->mFont.fontlist.PrioritizeFirstGeneric()) {
            aFont->mFont.fontlist.PrependGeneric(defaultGeneric);
          }
        }
      }
    } else {
      aFont->mFont.fontlist.SetDefaultFontType(eFamily_none);
    }
    aFont->mFont.systemFont = false;
    // Technically this is redundant with the code below, but it's good
    // to have since we'll still want it once we get rid of
    // SetGenericFont (bug 380915).
    aFont->mGenericID = aGenericFontID;
  }
  else if (eCSSUnit_System_Font == familyValue->GetUnit()) {
    aFont->mFont.fontlist = systemFont.fontlist;
    aFont->mFont.systemFont = true;
    aFont->mGenericID = kGenericFont_NONE;
  }
  else if (eCSSUnit_Inherit == familyValue->GetUnit() ||
           eCSSUnit_Unset == familyValue->GetUnit()) {
    aConditions.SetUncacheable();
    aFont->mFont.fontlist = aParentFont->mFont.fontlist;
    aFont->mFont.systemFont = aParentFont->mFont.systemFont;
    aFont->mGenericID = aParentFont->mGenericID;
  }
  else if (eCSSUnit_Initial == familyValue->GetUnit()) {
    aFont->mFont.fontlist = defaultVariableFont->fontlist;
    aFont->mFont.systemFont = defaultVariableFont->systemFont;
    aFont->mGenericID = kGenericFont_NONE;
  }

  // When we're in the loop in SetGenericFont, we must ensure that we
  // always keep aFont->mFlags set to the correct generic.  But we have
  // to be careful not to touch it when we're called directly from
  // ComputeFontData, because we could have a start struct.
  if (aGenericFontID != kGenericFont_NONE) {
    aFont->mGenericID = aGenericFontID;
  }

  // -moz-math-variant: enum, inherit, initial
  SetValue(*aRuleData->ValueForMathVariant(), aFont->mMathVariant,
           aConditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           aParentFont->mMathVariant, NS_MATHML_MATHVARIANT_NONE);

  // -moz-math-display: enum, inherit, initial
  SetValue(*aRuleData->ValueForMathDisplay(), aFont->mMathDisplay,
           aConditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           aParentFont->mMathDisplay, NS_MATHML_DISPLAYSTYLE_INLINE);

  // font-smoothing: enum, inherit, initial
  SetValue(*aRuleData->ValueForOsxFontSmoothing(),
           aFont->mFont.smoothing, aConditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           aParentFont->mFont.smoothing,
           defaultVariableFont->smoothing);

  // font-style: enum, inherit, initial, -moz-system-font
  if (aFont->mMathVariant != NS_MATHML_MATHVARIANT_NONE) {
    // -moz-math-variant overrides font-style
    aFont->mFont.style = NS_FONT_STYLE_NORMAL;
  } else {
    SetValue(*aRuleData->ValueForFontStyle(),
             aFont->mFont.style, aConditions,
             SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
             aParentFont->mFont.style,
             defaultVariableFont->style,
             Unused, Unused, Unused, systemFont.style);
  }

  // font-weight: int, enum, inherit, initial, -moz-system-font
  // special handling for enum
  const nsCSSValue* weightValue = aRuleData->ValueForFontWeight();
  if (aFont->mMathVariant != NS_MATHML_MATHVARIANT_NONE) {
    // -moz-math-variant overrides font-weight
    aFont->mFont.weight = NS_FONT_WEIGHT_NORMAL;
  } else if (eCSSUnit_Enumerated == weightValue->GetUnit()) {
    int32_t value = weightValue->GetIntValue();
    switch (value) {
      case NS_STYLE_FONT_WEIGHT_NORMAL:
      case NS_STYLE_FONT_WEIGHT_BOLD:
        aFont->mFont.weight = value;
        break;
      case NS_STYLE_FONT_WEIGHT_BOLDER: {
        aConditions.SetUncacheable();
        int32_t inheritedValue = aParentFont->mFont.weight;
        if (inheritedValue <= 300) {
          aFont->mFont.weight = 400;
        } else if (inheritedValue <= 500) {
          aFont->mFont.weight = 700;
        } else {
          aFont->mFont.weight = 900;
        }
        break;
      }
      case NS_STYLE_FONT_WEIGHT_LIGHTER: {
        aConditions.SetUncacheable();
        int32_t inheritedValue = aParentFont->mFont.weight;
        if (inheritedValue < 600) {
          aFont->mFont.weight = 100;
        } else if (inheritedValue < 800) {
          aFont->mFont.weight = 400;
        } else {
          aFont->mFont.weight = 700;
        }
        break;
      }
    }
  } else
    SetValue(*weightValue, aFont->mFont.weight, aConditions,
             SETVAL_INTEGER | SETVAL_UNSET_INHERIT,
             aParentFont->mFont.weight,
             defaultVariableFont->weight,
             Unused, Unused, Unused, systemFont.weight);

  // font-stretch: enum, inherit, initial, -moz-system-font
  SetValue(*aRuleData->ValueForFontStretch(),
           aFont->mFont.stretch, aConditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           aParentFont->mFont.stretch,
           defaultVariableFont->stretch,
           Unused, Unused, Unused, systemFont.stretch);

  // Compute scriptlevel, scriptminsize and scriptsizemultiplier now so
  // they're available for font-size computation.

  // -moz-script-min-size: length
  const nsCSSValue* scriptMinSizeValue = aRuleData->ValueForScriptMinSize();
  if (scriptMinSizeValue->IsLengthUnit()) {
    // scriptminsize in font units (em, ex) has to be interpreted relative
    // to the parent font, or the size definitions are circular and we
    //
    aFont->mScriptMinSize =
      CalcLengthWith(*scriptMinSizeValue, aParentFont->mSize,
                     aParentFont,
                     aContext, aPresContext, atRoot, true /* aUseUserFontSet */,
                     aConditions);
  }

  // -moz-script-size-multiplier: factor, inherit, initial
  SetFactor(*aRuleData->ValueForScriptSizeMultiplier(),
            aFont->mScriptSizeMultiplier,
            aConditions, aParentFont->mScriptSizeMultiplier,
            NS_MATHML_DEFAULT_SCRIPT_SIZE_MULTIPLIER,
            SETFCT_POSITIVE | SETFCT_UNSET_INHERIT);

  // -moz-script-level: integer, number, inherit
  const nsCSSValue* scriptLevelValue = aRuleData->ValueForScriptLevel();
  if (eCSSUnit_Integer == scriptLevelValue->GetUnit()) {
    // "relative"
    aConditions.SetUncacheable();
    aFont->mScriptLevel = ClampTo8Bit(aParentFont->mScriptLevel + scriptLevelValue->GetIntValue());
  }
  else if (eCSSUnit_Number == scriptLevelValue->GetUnit()) {
    // "absolute"
    aFont->mScriptLevel = ClampTo8Bit(int32_t(scriptLevelValue->GetFloatValue()));
  }
  else if (eCSSUnit_Auto == scriptLevelValue->GetUnit()) {
    // auto
    aConditions.SetUncacheable();
    aFont->mScriptLevel = ClampTo8Bit(aParentFont->mScriptLevel +
                                      (aParentFont->mMathDisplay ==
                                       NS_MATHML_DISPLAYSTYLE_INLINE ? 1 : 0));
  }
  else if (eCSSUnit_Inherit == scriptLevelValue->GetUnit() ||
           eCSSUnit_Unset == scriptLevelValue->GetUnit()) {
    aConditions.SetUncacheable();
    aFont->mScriptLevel = aParentFont->mScriptLevel;
  }
  else if (eCSSUnit_Initial == scriptLevelValue->GetUnit()) {
    aFont->mScriptLevel = 0;
  }

  // font-kerning: none, enum, inherit, initial, -moz-system-font
  SetValue(*aRuleData->ValueForFontKerning(),
           aFont->mFont.kerning, aConditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           aParentFont->mFont.kerning,
           defaultVariableFont->kerning,
           Unused, Unused, Unused, systemFont.kerning);

  // font-synthesis: none, enum (bit field), inherit, initial, -moz-system-font
  SetValue(*aRuleData->ValueForFontSynthesis(),
           aFont->mFont.synthesis, aConditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           aParentFont->mFont.synthesis,
           defaultVariableFont->synthesis,
           Unused, /* none */ 0, Unused, systemFont.synthesis);

  // font-variant-alternates: normal, enum (bit field) + functions, inherit,
  //                          initial, -moz-system-font
  const nsCSSValue* variantAlternatesValue =
    aRuleData->ValueForFontVariantAlternates();
  int32_t variantAlternates = 0;

  switch (variantAlternatesValue->GetUnit()) {
  case eCSSUnit_Inherit:
  case eCSSUnit_Unset:
    aFont->mFont.CopyAlternates(aParentFont->mFont);
    aConditions.SetUncacheable();
    break;

  case eCSSUnit_Initial:
  case eCSSUnit_Normal:
    aFont->mFont.variantAlternates = 0;
    aFont->mFont.alternateValues.Clear();
    aFont->mFont.featureValueLookup = nullptr;
    break;

  case eCSSUnit_Pair:
    NS_ASSERTION(variantAlternatesValue->GetPairValue().mXValue.GetUnit() ==
                   eCSSUnit_Enumerated, "strange unit for variantAlternates");
    variantAlternates =
      variantAlternatesValue->GetPairValue().mXValue.GetIntValue();
    aFont->mFont.variantAlternates = variantAlternates;

    if (variantAlternates & NS_FONT_VARIANT_ALTERNATES_FUNCTIONAL_MASK) {
      // fetch the feature lookup object from the styleset
      MOZ_ASSERT(aPresContext->StyleSet()->IsGecko(),
                 "ServoStyleSets should not have rule nodes");
      aFont->mFont.featureValueLookup =
        aPresContext->StyleSet()->AsGecko()->GetFontFeatureValuesLookup();

      NS_ASSERTION(variantAlternatesValue->GetPairValue().mYValue.GetUnit() ==
                   eCSSUnit_List, "function list not a list value");
      nsStyleUtil::ComputeFunctionalAlternates(
        variantAlternatesValue->GetPairValue().mYValue.GetListValue(),
        aFont->mFont.alternateValues);
    }
    break;

  default:
    break;
  }

  // font-variant-caps: normal, enum, inherit, initial, -moz-system-font
  SetValue(*aRuleData->ValueForFontVariantCaps(),
           aFont->mFont.variantCaps, aConditions,
           SETVAL_ENUMERATED |SETVAL_UNSET_INHERIT,
           aParentFont->mFont.variantCaps,
           defaultVariableFont->variantCaps,
           Unused, Unused, /* normal */ 0, systemFont.variantCaps);

  // font-variant-east-asian: normal, enum (bit field), inherit, initial,
  //                          -moz-system-font
  SetValue(*aRuleData->ValueForFontVariantEastAsian(),
           aFont->mFont.variantEastAsian, aConditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           aParentFont->mFont.variantEastAsian,
           defaultVariableFont->variantEastAsian,
           Unused, Unused, /* normal */ 0, systemFont.variantEastAsian);

  // font-variant-ligatures: normal, none, enum (bit field), inherit, initial,
  //                         -moz-system-font
  SetValue(*aRuleData->ValueForFontVariantLigatures(),
           aFont->mFont.variantLigatures, aConditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           aParentFont->mFont.variantLigatures,
           defaultVariableFont->variantLigatures,
           Unused, NS_FONT_VARIANT_LIGATURES_NONE, /* normal */ 0,
           systemFont.variantLigatures);

  // font-variant-numeric: normal, enum (bit field), inherit, initial,
  //                       -moz-system-font
  SetValue(*aRuleData->ValueForFontVariantNumeric(),
           aFont->mFont.variantNumeric, aConditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           aParentFont->mFont.variantNumeric,
           defaultVariableFont->variantNumeric,
           Unused, Unused, /* normal */ 0, systemFont.variantNumeric);

  // font-variant-position: normal, enum, inherit, initial,
  //                        -moz-system-font
  SetValue(*aRuleData->ValueForFontVariantPosition(),
           aFont->mFont.variantPosition, aConditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           aParentFont->mFont.variantPosition,
           defaultVariableFont->variantPosition,
           Unused, Unused, /* normal */ 0, systemFont.variantPosition);

  // font-feature-settings
  const nsCSSValue* featureSettingsValue =
    aRuleData->ValueForFontFeatureSettings();

  switch (featureSettingsValue->GetUnit()) {
  case eCSSUnit_Null:
    break;

  case eCSSUnit_Normal:
  case eCSSUnit_Initial:
    aFont->mFont.fontFeatureSettings.Clear();
    break;

  case eCSSUnit_Inherit:
  case eCSSUnit_Unset:
    aConditions.SetUncacheable();
    aFont->mFont.fontFeatureSettings = aParentFont->mFont.fontFeatureSettings;
    break;

  case eCSSUnit_System_Font:
    aFont->mFont.fontFeatureSettings = systemFont.fontFeatureSettings;
    break;

  case eCSSUnit_PairList:
  case eCSSUnit_PairListDep:
    ComputeFontFeatures(featureSettingsValue->GetPairListValue(),
                        aFont->mFont.fontFeatureSettings);
    break;

  default:
    MOZ_ASSERT(false, "unexpected value unit");
    break;
  }

  // font-language-override
  const nsCSSValue* languageOverrideValue =
    aRuleData->ValueForFontLanguageOverride();
  if (eCSSUnit_Inherit == languageOverrideValue->GetUnit() ||
      eCSSUnit_Unset == languageOverrideValue->GetUnit()) {
    aConditions.SetUncacheable();
    aFont->mFont.languageOverride = aParentFont->mFont.languageOverride;
  } else if (eCSSUnit_Normal == languageOverrideValue->GetUnit() ||
             eCSSUnit_Initial == languageOverrideValue->GetUnit()) {
    aFont->mFont.languageOverride.Truncate();
  } else if (eCSSUnit_System_Font == languageOverrideValue->GetUnit()) {
    aFont->mFont.languageOverride = systemFont.languageOverride;
  } else if (eCSSUnit_String == languageOverrideValue->GetUnit()) {
    languageOverrideValue->GetStringValue(aFont->mFont.languageOverride);
  }

  // -moz-min-font-size-ratio: percent, inherit
  const nsCSSValue* minFontSizeRatio = aRuleData->ValueForMinFontSizeRatio();
  switch (minFontSizeRatio->GetUnit()) {
    case eCSSUnit_Null:
      break;
    case eCSSUnit_Unset:
    case eCSSUnit_Inherit:
      aFont->mMinFontSizeRatio = aParentFont->mMinFontSizeRatio;
      aConditions.SetUncacheable();
      break;
    case eCSSUnit_Initial:
      aFont->mMinFontSizeRatio = 100; // 100%
      break;
    case eCSSUnit_Percent: {
      // While percentages are parsed as floating point numbers, we
      // only store an integer in the range [0, 255] since that's all
      // we need for now.
      float percent = minFontSizeRatio->GetPercentValue() * 100;
      if (percent < 0) {
        percent = 0;
      } else if (percent > 255) {
        percent = 255;
      }
      aFont->mMinFontSizeRatio = uint8_t(percent);
      break;
    }
    default:
      MOZ_ASSERT_UNREACHABLE("Unknown unit for -moz-min-font-size-ratio");
  }

  nscoord scriptLevelAdjustedUnconstrainedParentSize;

  // font-size: enum, length, percent, inherit
  nscoord scriptLevelAdjustedParentSize =
    ComputeScriptLevelSize(aFont, aParentFont, aPresContext,
                           &scriptLevelAdjustedUnconstrainedParentSize);
  NS_ASSERTION(!aUsedStartStruct || aFont->mScriptUnconstrainedSize == aFont->mSize,
               "If we have a start struct, we should have reset everything coming in here");

  // Compute whether we're affected by scriptMinSize *before* calling
  // SetFontSize, since aParentFont might be the same as aFont.  If it
  // is, calling SetFontSize might throw off our calculation.
  bool affectedByScriptMinSize =
    aParentFont->mSize != aParentFont->mScriptUnconstrainedSize ||
    scriptLevelAdjustedParentSize !=
      scriptLevelAdjustedUnconstrainedParentSize;

  SetFontSize(aPresContext, aContext,
              aRuleData, aFont, aParentFont,
              &aFont->mSize,
              systemFont, aParentFont->mSize, scriptLevelAdjustedParentSize,
              aUsedStartStruct, atRoot, aConditions);
  if (!aPresContext->Document()->GetMathMLEnabled()) {
    MOZ_ASSERT(!affectedByScriptMinSize);
    // If MathML is not enabled, we don't need to mark this node as
    // uncacheable.  If it becomes enabled, code in
    // nsMathMLElementFactory will rebuild the rule tree and style data
    // when MathML is first enabled (see nsMathMLElement::BindToTree).
    aFont->mScriptUnconstrainedSize = aFont->mSize;
  } else if (!affectedByScriptMinSize) {
    // Fast path: we have not been affected by scriptminsize so we don't
    // need to call SetFontSize again to compute the
    // scriptminsize-unconstrained size. This is OK even if we have a
    // start struct, because if we have a start struct then 'font-size'
    // was specified and so scriptminsize has no effect.
    aFont->mScriptUnconstrainedSize = aFont->mSize;
    // It's possible we could, in the future, have a different parent,
    // which would lead to a different affectedByScriptMinSize.
    aConditions.SetUncacheable();
  } else {
    // see previous else-if
    aConditions.SetUncacheable();

    // Use a separate conditions object because it might get a
    // *different* font-size dependency.  We can ignore it because we've
    // already called SetUncacheable.
    RuleNodeCacheConditions unconstrainedConditions;

    SetFontSize(aPresContext, aContext,
                aRuleData, aFont, aParentFont,
                &aFont->mScriptUnconstrainedSize,
                systemFont, aParentFont->mScriptUnconstrainedSize,
                scriptLevelAdjustedUnconstrainedParentSize,
                aUsedStartStruct, atRoot, unconstrainedConditions);
  }
  NS_ASSERTION(aFont->mScriptUnconstrainedSize <= aFont->mSize,
               "scriptminsize should never be making things bigger");

  nscoord fontSize = aFont->mSize;

  // enforce the user' specified minimum font-size on the value that we expose
  // (but don't change font-size:0, since that would unhide hidden text)
  if (fontSize > 0) {
    nscoord minFontSize = aPresContext->MinFontSize(aFont->mLanguage);
    if (minFontSize < 0) {
      minFontSize = 0;
    } else {
      minFontSize = (minFontSize * aFont->mMinFontSizeRatio) / 100;
    }
    if (fontSize < minFontSize && !aPresContext->IsChrome()) {
      // override the minimum font-size constraint
      fontSize = minFontSize;
    }
  }
  aFont->mFont.size = fontSize;

  // font-size-adjust: number, none, inherit, initial, -moz-system-font
  const nsCSSValue* sizeAdjustValue = aRuleData->ValueForFontSizeAdjust();
  if (eCSSUnit_System_Font == sizeAdjustValue->GetUnit()) {
    aFont->mFont.sizeAdjust = systemFont.sizeAdjust;
  } else
    SetFactor(*sizeAdjustValue, aFont->mFont.sizeAdjust,
              aConditions, aParentFont->mFont.sizeAdjust, -1.0f,
              SETFCT_NONE | SETFCT_UNSET_INHERIT);
}

/* static */ void
nsRuleNode::ComputeFontFeatures(const nsCSSValuePairList *aFeaturesList,
                                nsTArray<gfxFontFeature>& aFeatureSettings)
{
  aFeatureSettings.Clear();
  for (const nsCSSValuePairList* p = aFeaturesList; p; p = p->mNext) {
    gfxFontFeature feat = {0, 0};

    MOZ_ASSERT(aFeaturesList->mXValue.GetUnit() == eCSSUnit_String,
               "unexpected value unit");

    // tag is a 4-byte ASCII sequence
    nsAutoString tag;
    p->mXValue.GetStringValue(tag);
    if (tag.Length() != 4) {
      continue;
    }
    // parsing validates that these are ASCII chars
    // tags are always big-endian
    feat.mTag = (tag[0] << 24) | (tag[1] << 16) | (tag[2] << 8)  | tag[3];

    // value
    NS_ASSERTION(p->mYValue.GetUnit() == eCSSUnit_Integer,
                 "should have found an integer unit");
    feat.mValue = p->mYValue.GetIntValue();

    aFeatureSettings.AppendElement(feat);
  }
}

// This should die (bug 380915).
//
// SetGenericFont:
//  - backtrack to an ancestor with the same generic font name (possibly
//    up to the root where default values come from the presentation context)
//  - re-apply cascading rules from there without caching intermediate values
/* static */ void
nsRuleNode::SetGenericFont(nsPresContext* aPresContext,
                           nsStyleContext* aContext,
                           uint8_t aGenericFontID,
                           nsStyleFont* aFont)
{
  // walk up the contexts until a context with the desired generic font
  AutoTArray<nsStyleContext*, 8> contextPath;
  contextPath.AppendElement(aContext);
  nsStyleContext* higherContext = aContext->GetParent();
  while (higherContext) {
    if (higherContext->StyleFont()->mGenericID == aGenericFontID) {
      // done walking up the higher contexts
      break;
    }
    contextPath.AppendElement(higherContext);
    higherContext = higherContext->GetParent();
  }

  // re-apply the cascading rules, starting from the higher context

  // If we stopped earlier because we reached the root of the style tree,
  // we will start with the default generic font from the presentation
  // context. Otherwise we start with the higher context.
  const nsFont* defaultFont =
    aPresContext->GetDefaultFont(aGenericFontID, aFont->mLanguage);
  nsStyleFont parentFont(*defaultFont, aPresContext);
  if (higherContext) {
    const nsStyleFont* tmpFont = higherContext->StyleFont();
    parentFont = *tmpFont;
  }
  *aFont = parentFont;

  uint32_t fontBit = nsCachedStyleData::GetBitForSID(eStyleStruct_Font);

  // use placement new[] on the result of alloca() to allocate a
  // variable-sized stack array, including execution of constructors,
  // and use an RAII class to run the destructors too.
  size_t nprops = nsCSSProps::PropertyCountInStruct(eStyleStruct_Font);
  void* dataStorage = alloca(nprops * sizeof(nsCSSValue));

  for (int32_t i = contextPath.Length() - 1; i >= 0; --i) {
    nsStyleContext* context = contextPath[i];
    AutoCSSValueArray dataArray(dataStorage, nprops);

    nsRuleData ruleData(NS_STYLE_INHERIT_BIT(Font), dataArray.get(),
                        aPresContext, context);
    ruleData.mValueOffsets[eStyleStruct_Font] = 0;

    // Trimmed down version of ::WalkRuleTree() to re-apply the style rules
    // Note that we *do* need to do this for our own data, since what is
    // in |fontData| in ComputeFontData is only for the rules below
    // aStartStruct.
    for (nsRuleNode* ruleNode = context->RuleNode(); ruleNode;
         ruleNode = ruleNode->GetParent()) {
      if (ruleNode->mNoneBits & fontBit)
        // no more font rules on this branch, get out
        break;

      nsIStyleRule *rule = ruleNode->GetRule();
      if (rule) {
        ruleData.mLevel = ruleNode->GetLevel();
        ruleData.mIsImportantRule = ruleNode->IsImportantRule();
        rule->MapRuleInfoInto(&ruleData);
      }
    }

    // Compute the delta from the information that the rules specified

    // Avoid unnecessary operations in SetFont().  But we care if it's
    // the final value that we're computing.
    if (i != 0)
      ruleData.ValueForFontFamily()->Reset();

    ResolveVariableReferences(eStyleStruct_Font, &ruleData, aContext);

    RuleNodeCacheConditions dummy;
    nsRuleNode::SetFont(aPresContext, context,
                        aGenericFontID, &ruleData, &parentFont, aFont,
                        false, dummy);

    parentFont = *aFont;
  }

  if (higherContext && contextPath.Length() > 1) {
    // contextPath is a list of all ancestor style contexts, so it must have
    // at least two elements for it to result in a dependency on grandancestor
    // styles.
    PropagateGrandancestorBit(aContext, higherContext);
  }
}

const void*
nsRuleNode::ComputeFontData(void* aStartStruct,
                            const nsRuleData* aRuleData,
                            nsStyleContext* aContext,
                            nsRuleNode* aHighestNode,
                            const RuleDetail aRuleDetail,
                            const RuleNodeCacheConditions aConditions)
{
  COMPUTE_START_INHERITED(Font, font, parentFont)

  // NOTE:  The |aRuleDetail| passed in is a little bit conservative due
  // to the -moz-system-font property.  We really don't need to consider
  // it here in determining whether to cache in the rule tree.  However,
  // we do need to consider it in WalkRuleTree when deciding whether to
  // walk further up the tree.  So this means that when the font struct
  // is fully specified using *longhand* properties (excluding
  // -moz-system-font), we won't cache in the rule tree even though we
  // could.  However, it's pretty unlikely authors will do that
  // (although there is a pretty good chance they'll fully specify it
  // using the 'font' shorthand).

  // Figure out if we are a generic font
  uint8_t generic = kGenericFont_NONE;
  // XXXldb What if we would have had a string if we hadn't been doing
  // the optimization with a non-null aStartStruct?
  const nsCSSValue* familyValue = aRuleData->ValueForFontFamily();
  if (eCSSUnit_FontFamilyList == familyValue->GetUnit()) {
    const FontFamilyList* fontlist = familyValue->GetFontFamilyListValue();
    FontFamilyList& fl = font->mFont.fontlist;
    fl = *fontlist;

    // extract the first generic in the fontlist, if exists
    FontFamilyType fontType = fontlist->FirstGeneric();

    // if only a single generic, set the generic type
    if (fontlist->Length() == 1) {
      switch (fontType) {
        case eFamily_serif:
          generic = kGenericFont_serif;
          break;
        case eFamily_sans_serif:
          generic = kGenericFont_sans_serif;
          break;
        case eFamily_monospace:
          generic = kGenericFont_monospace;
          break;
        case eFamily_cursive:
          generic = kGenericFont_cursive;
          break;
        case eFamily_fantasy:
          generic = kGenericFont_fantasy;
          break;
        case eFamily_moz_fixed:
          generic = kGenericFont_moz_fixed;
          break;
        default:
          break;
      }
    }
  }

  // Now compute our font struct
  if (generic == kGenericFont_NONE) {
    // continue the normal processing
    nsRuleNode::SetFont(mPresContext, aContext, generic,
                        aRuleData, parentFont, font,
                        aStartStruct != nullptr, conditions);
  }
  else {
    // re-calculate the font as a generic font
    conditions.SetUncacheable();
    nsRuleNode::SetGenericFont(mPresContext, aContext, generic,
                               font);
  }

  COMPUTE_END_INHERITED(Font, font)
}

template <typename T>
inline uint32_t ListLength(const T* aList)
{
  uint32_t len = 0;
  while (aList) {
    len++;
    aList = aList->mNext;
  }
  return len;
}

static already_AddRefed<nsCSSShadowArray>
GetShadowData(const nsCSSValueList* aList,
              nsStyleContext* aContext,
              bool aIsBoxShadow,
              nsPresContext* aPresContext,
              RuleNodeCacheConditions& aConditions)
{
  uint32_t arrayLength = ListLength(aList);

  MOZ_ASSERT(arrayLength > 0,
             "Non-null text-shadow list, yet we counted 0 items.");
  RefPtr<nsCSSShadowArray> shadowList =
    new(arrayLength) nsCSSShadowArray(arrayLength);

  if (!shadowList)
    return nullptr;

  nsStyleCoord tempCoord;
  DebugOnly<bool> unitOK;
  for (nsCSSShadowItem* item = shadowList->ShadowAt(0);
       aList;
       aList = aList->mNext, ++item) {
    MOZ_ASSERT(aList->mValue.GetUnit() == eCSSUnit_Array,
               "expecting a plain array value");
    nsCSSValue::Array *arr = aList->mValue.GetArrayValue();
    // OK to pass bad aParentCoord since we're not passing SETCOORD_INHERIT
    unitOK = SetCoord(arr->Item(0), tempCoord, nsStyleCoord(),
                      SETCOORD_LENGTH | SETCOORD_CALC_LENGTH_ONLY,
                      aContext, aPresContext, aConditions);
    NS_ASSERTION(unitOK, "unexpected unit");
    item->mXOffset = tempCoord.GetCoordValue();

    unitOK = SetCoord(arr->Item(1), tempCoord, nsStyleCoord(),
                      SETCOORD_LENGTH | SETCOORD_CALC_LENGTH_ONLY,
                      aContext, aPresContext, aConditions);
    NS_ASSERTION(unitOK, "unexpected unit");
    item->mYOffset = tempCoord.GetCoordValue();

    // Blur radius is optional in the current box-shadow spec
    if (arr->Item(2).GetUnit() != eCSSUnit_Null) {
      unitOK = SetCoord(arr->Item(2), tempCoord, nsStyleCoord(),
                        SETCOORD_LENGTH | SETCOORD_CALC_LENGTH_ONLY |
                          SETCOORD_CALC_CLAMP_NONNEGATIVE,
                        aContext, aPresContext, aConditions);
      NS_ASSERTION(unitOK, "unexpected unit");
      item->mRadius = tempCoord.GetCoordValue();
    } else {
      item->mRadius = 0;
    }

    // Find the spread radius
    if (aIsBoxShadow && arr->Item(3).GetUnit() != eCSSUnit_Null) {
      unitOK = SetCoord(arr->Item(3), tempCoord, nsStyleCoord(),
                        SETCOORD_LENGTH | SETCOORD_CALC_LENGTH_ONLY,
                        aContext, aPresContext, aConditions);
      NS_ASSERTION(unitOK, "unexpected unit");
      item->mSpread = tempCoord.GetCoordValue();
    } else {
      item->mSpread = 0;
    }

    if (arr->Item(4).GetUnit() != eCSSUnit_Null) {
      item->mHasColor = true;
      // 2nd argument can be bogus since inherit is not a valid color
      unitOK = SetColor(arr->Item(4), 0, aPresContext, aContext, item->mColor,
                        aConditions);
      NS_ASSERTION(unitOK, "unexpected unit");
    }

    if (aIsBoxShadow && arr->Item(5).GetUnit() == eCSSUnit_Enumerated) {
      NS_ASSERTION(arr->Item(5).GetIntValue()
                   == uint8_t(StyleBoxShadowType::Inset),
                   "invalid keyword type for box shadow");
      item->mInset = true;
    } else {
      item->mInset = false;
    }
  }

  return shadowList.forget();
}

struct TextEmphasisChars
{
  const char16_t* mFilled;
  const char16_t* mOpen;
};

#define TEXT_EMPHASIS_CHARS_LIST() \
  TEXT_EMPHASIS_CHARS_ITEM(u"", u"", NONE) \
  TEXT_EMPHASIS_CHARS_ITEM(u"\u2022", u"\u25e6", DOT) \
  TEXT_EMPHASIS_CHARS_ITEM(u"\u25cf", u"\u25cb", CIRCLE) \
  TEXT_EMPHASIS_CHARS_ITEM(u"\u25c9", u"\u25ce", DOUBLE_CIRCLE) \
  TEXT_EMPHASIS_CHARS_ITEM(u"\u25b2", u"\u25b3", TRIANGLE) \
  TEXT_EMPHASIS_CHARS_ITEM(u"\ufe45", u"\ufe46", SESAME)

static constexpr TextEmphasisChars kTextEmphasisChars[] =
{
#define TEXT_EMPHASIS_CHARS_ITEM(filled_, open_, type_) \
  { filled_, open_ }, // type_
  TEXT_EMPHASIS_CHARS_LIST()
#undef TEXT_EMPHASIS_CHARS_ITEM
};

#define TEXT_EMPHASIS_CHARS_ITEM(filled_, open_, type_) \
  static_assert(ArrayLength(filled_) <= 2 && \
                ArrayLength(open_) <= 2, \
                "emphasis marks should have no more than one char"); \
  static_assert( \
    *kTextEmphasisChars[NS_STYLE_TEXT_EMPHASIS_STYLE_##type_].mFilled == \
    *filled_, "filled " #type_ " should be " #filled_); \
  static_assert( \
    *kTextEmphasisChars[NS_STYLE_TEXT_EMPHASIS_STYLE_##type_].mOpen == \
    *open_, "open " #type_ " should be " #open_);
TEXT_EMPHASIS_CHARS_LIST()
#undef TEXT_EMPHASIS_CHARS_ITEM

#undef TEXT_EMPHASIS_CHARS_LIST

static void
TruncateStringToSingleGrapheme(nsAString& aStr)
{
  unicode::ClusterIterator iter(aStr.Data(), aStr.Length());
  if (!iter.AtEnd()) {
    iter.Next();
    if (!iter.AtEnd()) {
      // Not mutating the string for common cases helps memory use
      // since we share the buffer from the specified style into the
      // computed style.
      aStr.Truncate(iter - aStr.Data());
    }
  }
}

struct LineHeightCalcObj
{
  float mLineHeight;
  bool mIsNumber;
};

struct SetLineHeightCalcOps : public css::NumbersAlreadyNormalizedOps
{
  typedef LineHeightCalcObj result_type;
  nsStyleContext* const mStyleContext;
  nsPresContext* const mPresContext;
  RuleNodeCacheConditions& mConditions;

  SetLineHeightCalcOps(nsStyleContext* aStyleContext,
                       nsPresContext* aPresContext,
                       RuleNodeCacheConditions& aConditions)
    : mStyleContext(aStyleContext),
      mPresContext(aPresContext),
      mConditions(aConditions)
  {
  }

  result_type
  MergeAdditive(nsCSSUnit aCalcFunction,
                result_type aValue1, result_type aValue2)
  {
    MOZ_ASSERT(aValue1.mIsNumber == aValue2.mIsNumber);

    LineHeightCalcObj result;
    result.mIsNumber = aValue1.mIsNumber;
    if (aCalcFunction == eCSSUnit_Calc_Plus) {
      result.mLineHeight = aValue1.mLineHeight + aValue2.mLineHeight;
      return result;
    }
    MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Minus,
               "unexpected unit");
    result.mLineHeight = aValue1.mLineHeight - aValue2.mLineHeight;
    return result;
  }

  result_type
  MergeMultiplicativeL(nsCSSUnit aCalcFunction,
                       float aValue1, result_type aValue2)
  {
    MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Times_L,
               "unexpected unit");
    LineHeightCalcObj result;
    result.mIsNumber = aValue2.mIsNumber;
    result.mLineHeight = aValue1 * aValue2.mLineHeight;
    return result;
  }

  result_type
  MergeMultiplicativeR(nsCSSUnit aCalcFunction,
                       result_type aValue1, float aValue2)
  {
    LineHeightCalcObj result;
    result.mIsNumber = aValue1.mIsNumber;
    if (aCalcFunction == eCSSUnit_Calc_Times_R) {
      result.mLineHeight = aValue1.mLineHeight * aValue2;
      return result;
    }
    MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Divided,
               "unexpected unit");
    result.mLineHeight = aValue1.mLineHeight / aValue2;
    return result;
  }

  result_type ComputeLeafValue(const nsCSSValue& aValue)
  {
    LineHeightCalcObj result;
    if (aValue.IsLengthUnit()) {
      result.mIsNumber = false;
      result.mLineHeight = CalcLength(aValue, mStyleContext,
                                      mPresContext, mConditions);
    }
    else if (eCSSUnit_Percent == aValue.GetUnit()) {
      mConditions.SetUncacheable();
      result.mIsNumber = false;
      nscoord fontSize = mStyleContext->StyleFont()->mFont.size;
      result.mLineHeight = fontSize * aValue.GetPercentValue();
    }
    else if (eCSSUnit_Number == aValue.GetUnit()) {
      result.mIsNumber = true;
      result.mLineHeight = aValue.GetFloatValue();
    } else {
      MOZ_ASSERT(false, "unexpected value");
      result.mIsNumber = true;
      result.mLineHeight = 1.0f;
    }

    return result;
  }
};

const void*
nsRuleNode::ComputeTextData(void* aStartStruct,
                            const nsRuleData* aRuleData,
                            nsStyleContext* aContext,
                            nsRuleNode* aHighestNode,
                            const RuleDetail aRuleDetail,
                            const RuleNodeCacheConditions aConditions)
{
  COMPUTE_START_INHERITED(Text, text, parentText)

  auto setComplexColor = [&](const nsCSSValue* aValue,
                             StyleComplexColor nsStyleText::* aField) {
    SetComplexColor<eUnsetInherit>(*aValue, parentText->*aField,
                                   StyleComplexColor::CurrentColor(),
                                   mPresContext, text->*aField, conditions);
  };

  // tab-size: integer, inherit
  SetValue(*aRuleData->ValueForTabSize(),
           text->mTabSize, conditions,
           SETVAL_INTEGER | SETVAL_UNSET_INHERIT, parentText->mTabSize,
           NS_STYLE_TABSIZE_INITIAL);

  // letter-spacing: normal, length, inherit
  SetCoord(*aRuleData->ValueForLetterSpacing(),
           text->mLetterSpacing, parentText->mLetterSpacing,
           SETCOORD_LH | SETCOORD_NORMAL | SETCOORD_INITIAL_NORMAL |
             SETCOORD_CALC_LENGTH_ONLY | SETCOORD_UNSET_INHERIT,
           aContext, mPresContext, conditions);

  // text-shadow: none, list, inherit, initial
  const nsCSSValue* textShadowValue = aRuleData->ValueForTextShadow();
  if (textShadowValue->GetUnit() != eCSSUnit_Null) {
    text->mTextShadow = nullptr;

    // Don't need to handle none/initial explicitly: The above assignment
    // takes care of that
    if (textShadowValue->GetUnit() == eCSSUnit_Inherit ||
        textShadowValue->GetUnit() == eCSSUnit_Unset) {
      conditions.SetUncacheable();
      text->mTextShadow = parentText->mTextShadow;
    } else if (textShadowValue->GetUnit() == eCSSUnit_List ||
               textShadowValue->GetUnit() == eCSSUnit_ListDep) {
      // List of arrays
      text->mTextShadow = GetShadowData(textShadowValue->GetListValue(),
                                        aContext, false, mPresContext, conditions);
    }
  }

  // line-height: normal, number, length, percent, calc, inherit
  const nsCSSValue* lineHeightValue = aRuleData->ValueForLineHeight();
  if (eCSSUnit_Percent == lineHeightValue->GetUnit()) {
    conditions.SetUncacheable();
    // Use |mFont.size| to pick up minimum font size.
    text->mLineHeight.SetCoordValue(
        NSToCoordRound(float(aContext->StyleFont()->mFont.size) *
                       lineHeightValue->GetPercentValue()));
  }
  else if (eCSSUnit_Initial == lineHeightValue->GetUnit() ||
           eCSSUnit_System_Font == lineHeightValue->GetUnit()) {
    text->mLineHeight.SetNormalValue();
  }
  else if (eCSSUnit_Calc == lineHeightValue->GetUnit()) {
    SetLineHeightCalcOps ops(aContext, mPresContext, conditions);
    LineHeightCalcObj obj = css::ComputeCalc(*lineHeightValue, ops);
    if (obj.mIsNumber) {
      text->mLineHeight.SetFactorValue(obj.mLineHeight);
    } else {
      text->mLineHeight.SetCoordValue(
        NSToCoordRoundWithClamp(obj.mLineHeight));
    }
  }
  else {
    SetCoord(*lineHeightValue, text->mLineHeight, parentText->mLineHeight,
             SETCOORD_LEH | SETCOORD_FACTOR | SETCOORD_NORMAL |
               SETCOORD_UNSET_INHERIT,
             aContext, mPresContext, conditions);
    if (lineHeightValue->IsLengthUnit() &&
        !lineHeightValue->IsRelativeLengthUnit()) {
      nscoord lh = nsStyleFont::ZoomText(mPresContext,
                                         text->mLineHeight.GetCoordValue());

      conditions.SetUncacheable();
      const nsStyleFont *font = aContext->StyleFont();
      nscoord minimumFontSize = mPresContext->MinFontSize(font->mLanguage);

      if (minimumFontSize > 0 && !mPresContext->IsChrome()) {
        if (font->mSize != 0) {
          lh = nscoord(float(lh) * float(font->mFont.size) / float(font->mSize));
        } else {
          lh = minimumFontSize;
        }
      }
      text->mLineHeight.SetCoordValue(lh);
    }
  }


  // text-align: enum, string, pair(enum|string), inherit, initial
  // NOTE: string is not implemented yet.
  const nsCSSValue* textAlignValue = aRuleData->ValueForTextAlign();
  text->mTextAlignTrue = false;
  if (eCSSUnit_String == textAlignValue->GetUnit()) {
    NS_NOTYETIMPLEMENTED("align string");
  } else if (eCSSUnit_Enumerated == textAlignValue->GetUnit() &&
             NS_STYLE_TEXT_ALIGN_MOZ_CENTER_OR_INHERIT ==
               textAlignValue->GetIntValue()) {
    conditions.SetUncacheable();
    uint8_t parentAlign = parentText->mTextAlign;
    text->mTextAlign = (NS_STYLE_TEXT_ALIGN_START == parentAlign) ?
      NS_STYLE_TEXT_ALIGN_CENTER : parentAlign;
  } else if (eCSSUnit_Enumerated == textAlignValue->GetUnit() &&
             NS_STYLE_TEXT_ALIGN_MATCH_PARENT ==
               textAlignValue->GetIntValue()) {
    conditions.SetUncacheable();
    nsStyleContext* parent = aContext->GetParent();
    if (parent) {
      uint8_t parentAlign = parentText->mTextAlign;
      uint8_t parentDirection = parent->StyleVisibility()->mDirection;
      switch (parentAlign) {
        case NS_STYLE_TEXT_ALIGN_START:
          text->mTextAlign = parentDirection == NS_STYLE_DIRECTION_RTL ?
            NS_STYLE_TEXT_ALIGN_RIGHT : NS_STYLE_TEXT_ALIGN_LEFT;
          break;

        case NS_STYLE_TEXT_ALIGN_END:
          text->mTextAlign = parentDirection == NS_STYLE_DIRECTION_RTL ?
            NS_STYLE_TEXT_ALIGN_LEFT : NS_STYLE_TEXT_ALIGN_RIGHT;
          break;

        default:
          text->mTextAlign = parentAlign;
      }
    }
  } else {
    if (eCSSUnit_Pair == textAlignValue->GetUnit()) {
      // Two values were specified, one must be 'true'.
      text->mTextAlignTrue = true;
      const nsCSSValuePair& textAlignValuePair = textAlignValue->GetPairValue();
      textAlignValue = &textAlignValuePair.mXValue;
      if (eCSSUnit_Enumerated == textAlignValue->GetUnit()) {
        if (textAlignValue->GetIntValue() == NS_STYLE_TEXT_ALIGN_UNSAFE) {
          textAlignValue = &textAlignValuePair.mYValue;
        }
      } else if (eCSSUnit_String == textAlignValue->GetUnit()) {
        NS_NOTYETIMPLEMENTED("align string");
      }
    } else if (eCSSUnit_Inherit == textAlignValue->GetUnit() ||
               eCSSUnit_Unset == textAlignValue->GetUnit()) {
      text->mTextAlignTrue = parentText->mTextAlignTrue;
    }
    SetValue(*textAlignValue, text->mTextAlign, conditions,
             SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
             parentText->mTextAlign,
             NS_STYLE_TEXT_ALIGN_START);
  }

  // text-align-last: enum, pair(enum), inherit, initial
  const nsCSSValue* textAlignLastValue = aRuleData->ValueForTextAlignLast();
  text->mTextAlignLastTrue = false;
  if (eCSSUnit_Pair == textAlignLastValue->GetUnit()) {
    // Two values were specified, one must be 'true'.
    text->mTextAlignLastTrue = true;
    const nsCSSValuePair& textAlignLastValuePair = textAlignLastValue->GetPairValue();
    textAlignLastValue = &textAlignLastValuePair.mXValue;
    if (eCSSUnit_Enumerated == textAlignLastValue->GetUnit()) {
      if (textAlignLastValue->GetIntValue() == NS_STYLE_TEXT_ALIGN_UNSAFE) {
        textAlignLastValue = &textAlignLastValuePair.mYValue;
      }
    }
  } else if (eCSSUnit_Inherit == textAlignLastValue->GetUnit() ||
             eCSSUnit_Unset == textAlignLastValue->GetUnit()) {
    text->mTextAlignLastTrue = parentText->mTextAlignLastTrue;
  }
  SetValue(*textAlignLastValue, text->mTextAlignLast,
           conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentText->mTextAlignLast,
           NS_STYLE_TEXT_ALIGN_AUTO);

  // text-indent: length, percent, calc, inherit, initial
  SetCoord(*aRuleData->ValueForTextIndent(), text->mTextIndent, parentText->mTextIndent,
           SETCOORD_LPH | SETCOORD_INITIAL_ZERO | SETCOORD_STORE_CALC |
             SETCOORD_UNSET_INHERIT,
           aContext, mPresContext, conditions);

  // text-transform: enum, inherit, initial
  SetValue(*aRuleData->ValueForTextTransform(), text->mTextTransform, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentText->mTextTransform,
           NS_STYLE_TEXT_TRANSFORM_NONE);

  // white-space: enum, inherit, initial
  SetValue(*aRuleData->ValueForWhiteSpace(), text->mWhiteSpace, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentText->mWhiteSpace,
           NS_STYLE_WHITESPACE_NORMAL);

  // word-break: enum, inherit, initial
  SetValue(*aRuleData->ValueForWordBreak(), text->mWordBreak, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentText->mWordBreak,
           NS_STYLE_WORDBREAK_NORMAL);

  // word-spacing: normal, length, percent, inherit
  const nsCSSValue* wordSpacingValue = aRuleData->ValueForWordSpacing();
  if (wordSpacingValue->GetUnit() == eCSSUnit_Normal) {
    // Do this so that "normal" computes to 0px, as the CSS 2.1 spec requires.
    text->mWordSpacing.SetCoordValue(0);
  } else {
    SetCoord(*aRuleData->ValueForWordSpacing(),
             text->mWordSpacing, parentText->mWordSpacing,
             SETCOORD_LPH | SETCOORD_INITIAL_ZERO |
               SETCOORD_STORE_CALC | SETCOORD_UNSET_INHERIT,
             aContext, mPresContext, conditions);
  }

  // overflow-wrap: enum, inherit, initial
  SetValue(*aRuleData->ValueForOverflowWrap(), text->mOverflowWrap, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentText->mOverflowWrap,
           NS_STYLE_OVERFLOWWRAP_NORMAL);

  // hyphens: enum, inherit, initial
  SetValue(*aRuleData->ValueForHyphens(), text->mHyphens, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentText->mHyphens,
           NS_STYLE_HYPHENS_MANUAL);

  // ruby-align: enum, inherit, initial
  SetValue(*aRuleData->ValueForRubyAlign(),
           text->mRubyAlign, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentText->mRubyAlign,
           NS_STYLE_RUBY_ALIGN_SPACE_AROUND);

  // ruby-position: enum, inherit, initial
  SetValue(*aRuleData->ValueForRubyPosition(),
           text->mRubyPosition, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentText->mRubyPosition,
           NS_STYLE_RUBY_POSITION_OVER);

  // text-size-adjust: none, auto, inherit, initial
  SetValue(*aRuleData->ValueForTextSizeAdjust(), text->mTextSizeAdjust,
           conditions, SETVAL_UNSET_INHERIT,
           parentText->mTextSizeAdjust,
           /* initial */ NS_STYLE_TEXT_SIZE_ADJUST_AUTO,
           /* auto */ NS_STYLE_TEXT_SIZE_ADJUST_AUTO,
           /* none */ NS_STYLE_TEXT_SIZE_ADJUST_NONE, Unused, Unused);

  // text-combine-upright: enum, inherit, initial
  SetValue(*aRuleData->ValueForTextCombineUpright(),
           text->mTextCombineUpright,
           conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentText->mTextCombineUpright,
           NS_STYLE_TEXT_COMBINE_UPRIGHT_NONE);

  // text-emphasis-color: color, string, inherit, initial
  setComplexColor(aRuleData->ValueForTextEmphasisColor(),
                  &nsStyleText::mTextEmphasisColor);

  // text-emphasis-position: enum, inherit, initial
  SetValue(*aRuleData->ValueForTextEmphasisPosition(),
           text->mTextEmphasisPosition,
           conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentText->mTextEmphasisPosition,
           NS_STYLE_TEXT_EMPHASIS_POSITION_OVER |
           NS_STYLE_TEXT_EMPHASIS_POSITION_RIGHT);

  // text-emphasis-style: string, enum, inherit, initial
  const nsCSSValue* textEmphasisStyleValue =
    aRuleData->ValueForTextEmphasisStyle();
  switch (textEmphasisStyleValue->GetUnit()) {
    case eCSSUnit_Null:
      break;
    case eCSSUnit_Initial:
    case eCSSUnit_None: {
      text->mTextEmphasisStyle = NS_STYLE_TEXT_EMPHASIS_STYLE_NONE;
      text->mTextEmphasisStyleString = u"";
      break;
    }
    case eCSSUnit_Inherit:
    case eCSSUnit_Unset: {
      conditions.SetUncacheable();
      text->mTextEmphasisStyle = parentText->mTextEmphasisStyle;
      text->mTextEmphasisStyleString = parentText->mTextEmphasisStyleString;
      break;
    }
    case eCSSUnit_Enumerated: {
      auto style = textEmphasisStyleValue->GetIntValue();
      // If shape part is not specified, compute it according to the
      // writing-mode. Note that, if the fill part (filled/open) is not
      // specified, we compute it to filled per spec. Since that value
      // is zero, no additional computation is needed. See the assertion
      // in CSSParserImpl::ParseTextEmphasisStyle().
      if (!(style & NS_STYLE_TEXT_EMPHASIS_STYLE_SHAPE_MASK)) {
        conditions.SetUncacheable();
        if (WritingMode(aContext).IsVertical()) {
          style |= NS_STYLE_TEXT_EMPHASIS_STYLE_SESAME;
        } else {
          style |= NS_STYLE_TEXT_EMPHASIS_STYLE_CIRCLE;
        }
      }
      text->mTextEmphasisStyle = style;
      size_t shape = style & NS_STYLE_TEXT_EMPHASIS_STYLE_SHAPE_MASK;
      MOZ_ASSERT(shape > 0 && shape < ArrayLength(kTextEmphasisChars));
      const TextEmphasisChars& chars = kTextEmphasisChars[shape];
      text->mTextEmphasisStyleString =
        (style & NS_STYLE_TEXT_EMPHASIS_STYLE_FILL_MASK) ==
        NS_STYLE_TEXT_EMPHASIS_STYLE_FILLED ? chars.mFilled : chars.mOpen;
      break;
    }
    case eCSSUnit_String: {
      text->mTextEmphasisStyle = NS_STYLE_TEXT_EMPHASIS_STYLE_STRING;
      nsString strValue;
      textEmphasisStyleValue->GetStringValue(strValue);
      TruncateStringToSingleGrapheme(strValue);
      text->mTextEmphasisStyleString = strValue;
      break;
    }
    default:
      MOZ_ASSERT_UNREACHABLE("Unknown value unit type");
  }

  // text-rendering: enum, inherit, initial
  SetValue(*aRuleData->ValueForTextRendering(),
           text->mTextRendering, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentText->mTextRendering,
           NS_STYLE_TEXT_RENDERING_AUTO);

  // -webkit-text-fill-color: color, string, inherit, initial
  setComplexColor(aRuleData->ValueForWebkitTextFillColor(),
                  &nsStyleText::mWebkitTextFillColor);

  // -webkit-text-stroke-color: color, string, inherit, initial
  setComplexColor(aRuleData->ValueForWebkitTextStrokeColor(),
                  &nsStyleText::mWebkitTextStrokeColor);

  // -webkit-text-stroke-width: length, inherit, initial, enum
  const nsCSSValue*
    webkitTextStrokeWidthValue = aRuleData->ValueForWebkitTextStrokeWidth();
  if (webkitTextStrokeWidthValue->GetUnit() == eCSSUnit_Enumerated) {
    NS_ASSERTION(webkitTextStrokeWidthValue->GetIntValue() == NS_STYLE_BORDER_WIDTH_THIN ||
                 webkitTextStrokeWidthValue->GetIntValue() == NS_STYLE_BORDER_WIDTH_MEDIUM ||
                 webkitTextStrokeWidthValue->GetIntValue() == NS_STYLE_BORDER_WIDTH_THICK,
                 "Unexpected enum value");
    text->mWebkitTextStrokeWidth.SetCoordValue(
      mPresContext->GetBorderWidthTable()[webkitTextStrokeWidthValue->GetIntValue()]);
  } else {
    SetCoord(*webkitTextStrokeWidthValue, text->mWebkitTextStrokeWidth,
             parentText->mWebkitTextStrokeWidth,
             SETCOORD_LH | SETCOORD_CALC_LENGTH_ONLY |
               SETCOORD_CALC_CLAMP_NONNEGATIVE |
               SETCOORD_INITIAL_ZERO | SETCOORD_UNSET_INHERIT,
             aContext, mPresContext, conditions);
  }

  // -moz-control-character-visibility: enum, inherit, initial
  SetValue(*aRuleData->ValueForControlCharacterVisibility(),
           text->mControlCharacterVisibility,
           conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentText->mControlCharacterVisibility,
           nsCSSParser::ControlCharVisibilityDefault());

  COMPUTE_END_INHERITED(Text, text)
}

const void*
nsRuleNode::ComputeTextResetData(void* aStartStruct,
                                 const nsRuleData* aRuleData,
                                 nsStyleContext* aContext,
                                 nsRuleNode* aHighestNode,
                                 const RuleDetail aRuleDetail,
                                 const RuleNodeCacheConditions aConditions)
{
  COMPUTE_START_RESET(TextReset, text, parentText)

  // text-decoration-line: enum (bit field), inherit, initial
  const nsCSSValue* decorationLineValue =
    aRuleData->ValueForTextDecorationLine();
  if (eCSSUnit_Enumerated == decorationLineValue->GetUnit()) {
    int32_t td = decorationLineValue->GetIntValue();
    text->mTextDecorationLine = td;
    if (td & NS_STYLE_TEXT_DECORATION_LINE_PREF_ANCHORS) {
      bool underlineLinks =
        mPresContext->GetCachedBoolPref(kPresContext_UnderlineLinks);
      if (underlineLinks) {
        text->mTextDecorationLine |= NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
      }
      else {
        text->mTextDecorationLine &= ~NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
      }
    }
  } else if (eCSSUnit_Inherit == decorationLineValue->GetUnit()) {
    conditions.SetUncacheable();
    text->mTextDecorationLine = parentText->mTextDecorationLine;
  } else if (eCSSUnit_Initial == decorationLineValue->GetUnit() ||
             eCSSUnit_Unset == decorationLineValue->GetUnit()) {
    text->mTextDecorationLine = NS_STYLE_TEXT_DECORATION_LINE_NONE;
  }

  // text-decoration-color: color, string, enum, inherit, initial
  SetComplexColor<eUnsetInitial>(*aRuleData->ValueForTextDecorationColor(),
                                 parentText->mTextDecorationColor,
                                 StyleComplexColor::CurrentColor(),
                                 mPresContext,
                                 text->mTextDecorationColor, conditions);

  // text-decoration-style: enum, inherit, initial
  const nsCSSValue* decorationStyleValue =
    aRuleData->ValueForTextDecorationStyle();
  if (eCSSUnit_Enumerated == decorationStyleValue->GetUnit()) {
    text->mTextDecorationStyle = decorationStyleValue->GetIntValue();
  } else if (eCSSUnit_Inherit == decorationStyleValue->GetUnit()) {
    text->mTextDecorationStyle = parentText->mTextDecorationStyle;
    conditions.SetUncacheable();
  } else if (eCSSUnit_Initial == decorationStyleValue->GetUnit() ||
             eCSSUnit_Unset == decorationStyleValue->GetUnit()) {
    text->mTextDecorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
  }

  // text-overflow: enum, string, pair(enum|string), inherit, initial
  const nsCSSValue* textOverflowValue =
    aRuleData->ValueForTextOverflow();
  if (eCSSUnit_Initial == textOverflowValue->GetUnit() ||
      eCSSUnit_Unset == textOverflowValue->GetUnit()) {
    text->mTextOverflow = nsStyleTextOverflow();
  } else if (eCSSUnit_Inherit == textOverflowValue->GetUnit()) {
    conditions.SetUncacheable();
    text->mTextOverflow = parentText->mTextOverflow;
  } else if (eCSSUnit_Enumerated == textOverflowValue->GetUnit()) {
    // A single enumerated value.
    SetValue(*textOverflowValue, text->mTextOverflow.mRight.mType,
             conditions,
             SETVAL_ENUMERATED, parentText->mTextOverflow.mRight.mType,
             NS_STYLE_TEXT_OVERFLOW_CLIP);
    text->mTextOverflow.mRight.mString.Truncate();
    text->mTextOverflow.mLeft.mType = NS_STYLE_TEXT_OVERFLOW_CLIP;
    text->mTextOverflow.mLeft.mString.Truncate();
    text->mTextOverflow.mLogicalDirections = true;
  } else if (eCSSUnit_String == textOverflowValue->GetUnit()) {
    // A single string value.
    text->mTextOverflow.mRight.mType = NS_STYLE_TEXT_OVERFLOW_STRING;
    textOverflowValue->GetStringValue(text->mTextOverflow.mRight.mString);
    text->mTextOverflow.mLeft.mType = NS_STYLE_TEXT_OVERFLOW_CLIP;
    text->mTextOverflow.mLeft.mString.Truncate();
    text->mTextOverflow.mLogicalDirections = true;
  } else if (eCSSUnit_Pair == textOverflowValue->GetUnit()) {
    // Two values were specified.
    text->mTextOverflow.mLogicalDirections = false;
    const nsCSSValuePair& textOverflowValuePair =
      textOverflowValue->GetPairValue();

    const nsCSSValue *textOverflowLeftValue = &textOverflowValuePair.mXValue;
    if (eCSSUnit_Enumerated == textOverflowLeftValue->GetUnit()) {
      SetValue(*textOverflowLeftValue, text->mTextOverflow.mLeft.mType,
               conditions,
               SETVAL_ENUMERATED, parentText->mTextOverflow.mLeft.mType,
               NS_STYLE_TEXT_OVERFLOW_CLIP);
      text->mTextOverflow.mLeft.mString.Truncate();
    } else if (eCSSUnit_String == textOverflowLeftValue->GetUnit()) {
      textOverflowLeftValue->GetStringValue(text->mTextOverflow.mLeft.mString);
      text->mTextOverflow.mLeft.mType = NS_STYLE_TEXT_OVERFLOW_STRING;
    }

    const nsCSSValue *textOverflowRightValue = &textOverflowValuePair.mYValue;
    if (eCSSUnit_Enumerated == textOverflowRightValue->GetUnit()) {
      SetValue(*textOverflowRightValue, text->mTextOverflow.mRight.mType,
               conditions,
               SETVAL_ENUMERATED, parentText->mTextOverflow.mRight.mType,
               NS_STYLE_TEXT_OVERFLOW_CLIP);
      text->mTextOverflow.mRight.mString.Truncate();
    } else if (eCSSUnit_String == textOverflowRightValue->GetUnit()) {
      textOverflowRightValue->GetStringValue(text->mTextOverflow.mRight.mString);
      text->mTextOverflow.mRight.mType = NS_STYLE_TEXT_OVERFLOW_STRING;
    }
  }

  // unicode-bidi: enum, inherit, initial
  SetValue(*aRuleData->ValueForUnicodeBidi(), text->mUnicodeBidi, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentText->mUnicodeBidi,
           NS_STYLE_UNICODE_BIDI_NORMAL);

  // initial-letter: normal, number, array(number, integer?), initial
  const nsCSSValue* initialLetterValue = aRuleData->ValueForInitialLetter();
  if (initialLetterValue->GetUnit() == eCSSUnit_Null) {
    // We don't want to change anything in this case.
  } else if (initialLetterValue->GetUnit() == eCSSUnit_Inherit) {
    conditions.SetUncacheable();
    text->mInitialLetterSink = parentText->mInitialLetterSink;
    text->mInitialLetterSize = parentText->mInitialLetterSize;
  } else if (initialLetterValue->GetUnit() == eCSSUnit_Initial ||
             initialLetterValue->GetUnit() == eCSSUnit_Unset ||
             initialLetterValue->GetUnit() == eCSSUnit_Normal) {
    // Use invalid values in initial-letter property to mean normal. So we can
    // determine whether it is normal by checking mInitialLetterSink == 0.
    text->mInitialLetterSink = 0;
    text->mInitialLetterSize = 0.0f;
  } else if (initialLetterValue->GetUnit() == eCSSUnit_Array) {
    const nsCSSValue& firstValue = initialLetterValue->GetArrayValue()->Item(0);
    const nsCSSValue& secondValue = initialLetterValue->GetArrayValue()->Item(1);
    MOZ_ASSERT(firstValue.GetUnit() == eCSSUnit_Number &&
               secondValue.GetUnit() == eCSSUnit_Integer,
               "unexpected value unit");
    text->mInitialLetterSize = firstValue.GetFloatValue();
    text->mInitialLetterSink = secondValue.GetIntValue();
  } else if (initialLetterValue->GetUnit() == eCSSUnit_Number) {
    text->mInitialLetterSize = initialLetterValue->GetFloatValue();
    text->mInitialLetterSink = NSToCoordFloorClamped(text->mInitialLetterSize);
  } else {
    MOZ_ASSERT_UNREACHABLE("unknown unit for initial-letter");
  }

  COMPUTE_END_RESET(TextReset, text)
}

const void*
nsRuleNode::ComputeUserInterfaceData(void* aStartStruct,
                                     const nsRuleData* aRuleData,
                                     nsStyleContext* aContext,
                                     nsRuleNode* aHighestNode,
                                     const RuleDetail aRuleDetail,
                                     const RuleNodeCacheConditions aConditions)
{
  COMPUTE_START_INHERITED(UserInterface, ui, parentUI)

  // cursor: enum, url, inherit
  const nsCSSValue* cursorValue = aRuleData->ValueForCursor();
  nsCSSUnit cursorUnit = cursorValue->GetUnit();
  if (cursorUnit != eCSSUnit_Null) {
    ui->mCursorImages.Clear();

    if (cursorUnit == eCSSUnit_Inherit ||
        cursorUnit == eCSSUnit_Unset) {
      conditions.SetUncacheable();
      ui->mCursor = parentUI->mCursor;
      ui->mCursorImages = parentUI->mCursorImages;
    }
    else if (cursorUnit == eCSSUnit_Initial) {
      ui->mCursor = NS_STYLE_CURSOR_AUTO;
    }
    else {
      // The parser will never create a list that is *all* URL values --
      // that's invalid.
      MOZ_ASSERT(cursorUnit == eCSSUnit_List || cursorUnit == eCSSUnit_ListDep,
                 "unrecognized cursor unit");
      const nsCSSValueList* list = cursorValue->GetListValue();
      for ( ; list->mValue.GetUnit() == eCSSUnit_Array; list = list->mNext) {
        nsCSSValue::Array* arr = list->mValue.GetArrayValue();
        imgRequestProxy* req =
          GetImageRequest(aContext->PresContext(), arr->Item(0));
        if (req) {
          nsCursorImage* item = ui->mCursorImages.AppendElement();
          item->SetImage(req);
          if (arr->Item(1).GetUnit() != eCSSUnit_Null) {
            item->mHaveHotspot = true;
            item->mHotspotX = arr->Item(1).GetFloatValue();
            item->mHotspotY = arr->Item(2).GetFloatValue();
          }
        }
      }

      NS_ASSERTION(list, "Must have non-array value at the end");
      NS_ASSERTION(list->mValue.GetUnit() == eCSSUnit_Enumerated,
                   "Unexpected fallback value at end of cursor list");
      ui->mCursor = list->mValue.GetIntValue();
    }
  }

  // user-input: enum, inherit, initial
  SetValue(*aRuleData->ValueForUserInput(),
           ui->mUserInput, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentUI->mUserInput,
           StyleUserInput::Auto);

  // user-modify: enum, inherit, initial
  SetValue(*aRuleData->ValueForUserModify(),
           ui->mUserModify, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentUI->mUserModify,
           StyleUserModify::ReadOnly);

  // user-focus: enum, inherit, initial
  SetValue(*aRuleData->ValueForUserFocus(),
           ui->mUserFocus, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentUI->mUserFocus,
           StyleUserFocus::None);

  // pointer-events: enum, inherit, initial
  SetValue(*aRuleData->ValueForPointerEvents(), ui->mPointerEvents,
           conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentUI->mPointerEvents,
           NS_STYLE_POINTER_EVENTS_AUTO);

  COMPUTE_END_INHERITED(UserInterface, ui)
}

const void*
nsRuleNode::ComputeUIResetData(void* aStartStruct,
                               const nsRuleData* aRuleData,
                               nsStyleContext* aContext,
                               nsRuleNode* aHighestNode,
                               const RuleDetail aRuleDetail,
                               const RuleNodeCacheConditions aConditions)
{
  COMPUTE_START_RESET(UIReset, ui, parentUI)

  // user-select: enum, inherit, initial
  SetValue(*aRuleData->ValueForUserSelect(),
           ui->mUserSelect, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentUI->mUserSelect,
           StyleUserSelect::Auto);

  // ime-mode: enum, inherit, initial
  SetValue(*aRuleData->ValueForImeMode(),
           ui->mIMEMode, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentUI->mIMEMode,
           NS_STYLE_IME_MODE_AUTO);

  // force-broken-image-icons: integer, inherit, initial
  SetValue(*aRuleData->ValueForForceBrokenImageIcon(),
           ui->mForceBrokenImageIcon,
           conditions,
           SETVAL_INTEGER | SETVAL_UNSET_INITIAL,
           parentUI->mForceBrokenImageIcon, 0);

  // -moz-window-dragging: enum, inherit, initial
  SetValue(*aRuleData->ValueForWindowDragging(),
           ui->mWindowDragging, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentUI->mWindowDragging,
           StyleWindowDragging::Default);

  // -moz-window-shadow: enum, inherit, initial
  SetValue(*aRuleData->ValueForWindowShadow(),
           ui->mWindowShadow, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentUI->mWindowShadow,
           NS_STYLE_WINDOW_SHADOW_DEFAULT);

  COMPUTE_END_RESET(UIReset, ui)
}

// Information about each transition or animation property that is
// constant.
struct TransitionPropInfo {
  nsCSSPropertyID property;
  // Location of the count of the property's computed value.
  uint32_t nsStyleDisplay::* sdCount;
};

// Each property's index in this array must match its index in the
// mutable array |transitionPropData| below.
static const TransitionPropInfo transitionPropInfo[4] = {
  { eCSSProperty_transition_delay,
    &nsStyleDisplay::mTransitionDelayCount },
  { eCSSProperty_transition_duration,
    &nsStyleDisplay::mTransitionDurationCount },
  { eCSSProperty_transition_property,
    &nsStyleDisplay::mTransitionPropertyCount },
  { eCSSProperty_transition_timing_function,
    &nsStyleDisplay::mTransitionTimingFunctionCount },
};

// Each property's index in this array must match its index in the
// mutable array |animationPropData| below.
static const TransitionPropInfo animationPropInfo[8] = {
  { eCSSProperty_animation_delay,
    &nsStyleDisplay::mAnimationDelayCount },
  { eCSSProperty_animation_duration,
    &nsStyleDisplay::mAnimationDurationCount },
  { eCSSProperty_animation_name,
    &nsStyleDisplay::mAnimationNameCount },
  { eCSSProperty_animation_timing_function,
    &nsStyleDisplay::mAnimationTimingFunctionCount },
  { eCSSProperty_animation_direction,
    &nsStyleDisplay::mAnimationDirectionCount },
  { eCSSProperty_animation_fill_mode,
    &nsStyleDisplay::mAnimationFillModeCount },
  { eCSSProperty_animation_play_state,
    &nsStyleDisplay::mAnimationPlayStateCount },
  { eCSSProperty_animation_iteration_count,
    &nsStyleDisplay::mAnimationIterationCountCount },
};

// Information about each transition or animation property that changes
// during ComputeDisplayData.
struct TransitionPropData {
  const nsCSSValueList *list;
  nsCSSUnit unit;
  uint32_t num;
};

static uint32_t
CountTransitionProps(const TransitionPropInfo* aInfo,
                     TransitionPropData* aData,
                     size_t aLength,
                     nsStyleDisplay* aDisplay,
                     const nsStyleDisplay* aParentDisplay,
                     const nsRuleData* aRuleData,
                     RuleNodeCacheConditions& aConditions)
{
  // The four transition properties or eight animation properties are
  // stored in nsCSSDisplay in a single array for all properties.  The
  // number of transitions is equal to the number of items in the
  // longest property's value.  Properties that have fewer values than
  // the longest are filled in by repeating the list.  However, this
  // repetition does not extend the computed value of that particular
  // property (for purposes of inheritance, or, in our code, for when
  // other properties are overridden by a more specific rule).

  // But actually, since the spec isn't clear yet, we'll fully compute
  // all of them (so we can switch easily later), but only care about
  // the ones up to the number of items for 'transition-property', per
  // http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html .

  // Transitions are difficult to handle correctly because of this.  For
  // example, we need to handle scenarios such as:
  //  * a more general rule specifies transition-property: a, b, c;
  //  * a more specific rule overrides as transition-property: d;
  //
  // If only the general rule applied, we would fill in the extra
  // properties (duration, delay, etc) with initial values to create 3
  // fully-specified transitions.  But when the more specific rule
  // applies, we should only create a single transition.  In order to do
  // this we need to remember which properties were explicitly specified
  // and which ones were just filled in with initial values to get a
  // fully-specified transition, which we do by remembering the number
  // of values for each property.

  uint32_t numTransitions = 0;
  for (size_t i = 0; i < aLength; ++i) {
    const TransitionPropInfo& info = aInfo[i];
    TransitionPropData& data = aData[i];

    // cache whether any of the properties are specified as 'inherit' so
    // we can use it below

    const nsCSSValue& value = *aRuleData->ValueFor(info.property);
    data.unit = value.GetUnit();
    data.list = (value.GetUnit() == eCSSUnit_List ||
                 value.GetUnit() == eCSSUnit_ListDep)
                  ? value.GetListValue() : nullptr;

    // General algorithm to determine how many total transitions we need
    // to build.  For each property:
    //  - if there is no value specified in for the property in
    //    displayData, use the values from the start struct, but only if
    //    they were explicitly specified
    //  - if there is a value specified for the property in displayData:
    //    - if the value is 'inherit', count the number of values for
    //      that property are specified by the parent, but only those
    //      that were explicitly specified
    //    - otherwise, count the number of values specified in displayData


    // calculate number of elements
    if (data.unit == eCSSUnit_Inherit) {
      data.num = aParentDisplay->*(info.sdCount);
      aConditions.SetUncacheable();
    } else if (data.list) {
      data.num = ListLength(data.list);
    } else {
      data.num = aDisplay->*(info.sdCount);
    }
    if (data.num > numTransitions)
      numTransitions = data.num;
  }

  return numTransitions;
}

/* static */ void
nsRuleNode::ComputeTimingFunction(const nsCSSValue& aValue,
                                  nsTimingFunction& aResult)
{
  switch (aValue.GetUnit()) {
    case eCSSUnit_Enumerated:
      aResult = nsTimingFunction(aValue.GetIntValue());
      break;
    case eCSSUnit_Cubic_Bezier:
      {
        nsCSSValue::Array* array = aValue.GetArrayValue();
        NS_ASSERTION(array && array->Count() == 4,
                     "Need 4 control points");
        aResult = nsTimingFunction(array->Item(0).GetFloatValue(),
                                   array->Item(1).GetFloatValue(),
                                   array->Item(2).GetFloatValue(),
                                   array->Item(3).GetFloatValue());
      }
      break;
    case eCSSUnit_Steps:
      {
        nsCSSValue::Array* array = aValue.GetArrayValue();
        NS_ASSERTION(array && array->Count() == 2,
                     "Need 2 items");
        NS_ASSERTION(array->Item(0).GetUnit() == eCSSUnit_Integer,
                     "unexpected first value");
        NS_ASSERTION(array->Item(1).GetUnit() == eCSSUnit_Enumerated &&
                     (array->Item(1).GetIntValue() ==
                       NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_START ||
                      array->Item(1).GetIntValue() ==
                       NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_END ||
                      array->Item(1).GetIntValue() == -1),
                     "unexpected second value");
        nsTimingFunction::Type type =
          (array->Item(1).GetIntValue() ==
            NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_START) ?
              nsTimingFunction::Type::StepStart :
              nsTimingFunction::Type::StepEnd;
        aResult = nsTimingFunction(type, array->Item(0).GetIntValue());
      }
      break;
    default:
      NS_NOTREACHED("Invalid transition property unit");
  }
}

static uint8_t
GetWillChangeBitFieldFromPropFlags(const nsCSSPropertyID& aProp)
{
  uint8_t willChangeBitField = 0;
  if (nsCSSProps::PropHasFlags(aProp, CSS_PROPERTY_CREATES_STACKING_CONTEXT)) {
    willChangeBitField |= NS_STYLE_WILL_CHANGE_STACKING_CONTEXT;
  }

  if (nsCSSProps::PropHasFlags(aProp, CSS_PROPERTY_FIXPOS_CB)) {
    willChangeBitField |= NS_STYLE_WILL_CHANGE_FIXPOS_CB;
  }

  if (nsCSSProps::PropHasFlags(aProp, CSS_PROPERTY_ABSPOS_CB)) {
    willChangeBitField |= NS_STYLE_WILL_CHANGE_ABSPOS_CB;
  }

  return willChangeBitField;
}

const void*
nsRuleNode::ComputeDisplayData(void* aStartStruct,
                               const nsRuleData* aRuleData,
                               nsStyleContext* aContext,
                               nsRuleNode* aHighestNode,
                               const RuleDetail aRuleDetail,
                               const RuleNodeCacheConditions aConditions)
{
  COMPUTE_START_RESET(Display, display, parentDisplay)

  // We may have ended up with aStartStruct's values of mDisplay and
  // mFloat, but those may not be correct if our style data overrides
  // its position or float properties.  Reset to mOriginalDisplay and
  // mOriginalFloat; if it turns out we still need the display/floats
  // adjustments, we'll do them below.
  display->mDisplay = display->mOriginalDisplay;
  display->mFloat = display->mOriginalFloat;

  // Each property's index in this array must match its index in the
  // const array |transitionPropInfo| above.
  TransitionPropData transitionPropData[4];
  TransitionPropData& delay = transitionPropData[0];
  TransitionPropData& duration = transitionPropData[1];
  TransitionPropData& property = transitionPropData[2];
  TransitionPropData& timingFunction = transitionPropData[3];

#define FOR_ALL_TRANSITION_PROPS(var_) \
                                      for (uint32_t var_ = 0; var_ < 4; ++var_)

  // CSS Transitions
  uint32_t numTransitions =
    CountTransitionProps(transitionPropInfo, transitionPropData,
                         ArrayLength(transitionPropData),
                         display, parentDisplay, aRuleData,
                         conditions);

  display->mTransitions.SetLengthNonZero(numTransitions);

  FOR_ALL_TRANSITION_PROPS(p) {
    const TransitionPropInfo& i = transitionPropInfo[p];
    TransitionPropData& d = transitionPropData[p];

    display->*(i.sdCount) = d.num;
  }

  // Fill in the transitions we just allocated with the appropriate values.
  for (uint32_t i = 0; i < numTransitions; ++i) {
    StyleTransition *transition = &display->mTransitions[i];

    if (i >= delay.num) {
      MOZ_ASSERT(delay.num, "delay.num must be greater than 0");
      transition->SetDelay(display->mTransitions[i % delay.num].GetDelay());
    } else if (delay.unit == eCSSUnit_Inherit) {
      // FIXME (Bug 522599) (for all transition properties): write a test that
      // detects when this was wrong for i >= delay.num if parent had
      // count for this property not equal to length
      MOZ_ASSERT(i < parentDisplay->mTransitionDelayCount,
                 "delay.num computed incorrectly");
      MOZ_ASSERT(!conditions.Cacheable(),
                 "should have made conditions.Cacheable() false above");
      transition->SetDelay(parentDisplay->mTransitions[i].GetDelay());
    } else if (delay.unit == eCSSUnit_Initial ||
               delay.unit == eCSSUnit_Unset) {
      transition->SetDelay(0.0);
    } else if (delay.list) {
      switch (delay.list->mValue.GetUnit()) {
        case eCSSUnit_Seconds:
          transition->SetDelay(PR_MSEC_PER_SEC *
                               delay.list->mValue.GetFloatValue());
          break;
        case eCSSUnit_Milliseconds:
          transition->SetDelay(delay.list->mValue.GetFloatValue());
          break;
        default:
          NS_NOTREACHED("Invalid delay unit");
      }
    }

    if (i >= duration.num) {
      MOZ_ASSERT(duration.num, "duration.num must be greater than 0");
      transition->SetDuration(
        display->mTransitions[i % duration.num].GetDuration());
    } else if (duration.unit == eCSSUnit_Inherit) {
      MOZ_ASSERT(i < parentDisplay->mTransitionDurationCount,
                 "duration.num computed incorrectly");
      MOZ_ASSERT(!conditions.Cacheable(),
                 "should have made conditions.Cacheable() false above");
      transition->SetDuration(parentDisplay->mTransitions[i].GetDuration());
    } else if (duration.unit == eCSSUnit_Initial ||
               duration.unit == eCSSUnit_Unset) {
      transition->SetDuration(0.0);
    } else if (duration.list) {
      switch (duration.list->mValue.GetUnit()) {
        case eCSSUnit_Seconds:
          transition->SetDuration(PR_MSEC_PER_SEC *
                                  duration.list->mValue.GetFloatValue());
          break;
        case eCSSUnit_Milliseconds:
          transition->SetDuration(duration.list->mValue.GetFloatValue());
          break;
        default:
          NS_NOTREACHED("Invalid duration unit");
      }
    }

    if (i >= property.num) {
      MOZ_ASSERT(property.num, "property.num must be greater than 0");
      transition->CopyPropertyFrom(display->mTransitions[i % property.num]);
    } else if (property.unit == eCSSUnit_Inherit) {
      MOZ_ASSERT(i < parentDisplay->mTransitionPropertyCount,
                 "property.num computed incorrectly");
      MOZ_ASSERT(!conditions.Cacheable(),
                 "should have made conditions.Cacheable() false above");
      transition->CopyPropertyFrom(parentDisplay->mTransitions[i]);
    } else if (property.unit == eCSSUnit_Initial ||
               property.unit == eCSSUnit_Unset) {
      transition->SetProperty(eCSSPropertyExtra_all_properties);
    } else if (property.unit == eCSSUnit_None) {
      transition->SetProperty(eCSSPropertyExtra_no_properties);
    } else if (property.list) {
      const nsCSSValue &val = property.list->mValue;

      if (val.GetUnit() == eCSSUnit_Ident) {
        nsDependentString
          propertyStr(property.list->mValue.GetStringBufferValue());
        nsCSSPropertyID prop =
          nsCSSProps::LookupProperty(propertyStr,
                                     CSSEnabledState::eForAllContent);
        if (prop == eCSSProperty_UNKNOWN ||
            prop == eCSSPropertyExtra_variable) {
          transition->SetUnknownProperty(prop, propertyStr);
        } else {
          transition->SetProperty(prop);
        }
      } else {
        MOZ_ASSERT(val.GetUnit() == eCSSUnit_All,
                   "Invalid transition property unit");
        transition->SetProperty(eCSSPropertyExtra_all_properties);
      }
    }

    if (i >= timingFunction.num) {
      MOZ_ASSERT(timingFunction.num,
        "timingFunction.num must be greater than 0");
      transition->SetTimingFunction(
        display->mTransitions[i % timingFunction.num].GetTimingFunction());
    } else if (timingFunction.unit == eCSSUnit_Inherit) {
      MOZ_ASSERT(i < parentDisplay->mTransitionTimingFunctionCount,
                 "timingFunction.num computed incorrectly");
      MOZ_ASSERT(!conditions.Cacheable(),
                 "should have made conditions.Cacheable() false above");
      transition->SetTimingFunction(
        parentDisplay->mTransitions[i].GetTimingFunction());
    } else if (timingFunction.unit == eCSSUnit_Initial ||
               timingFunction.unit == eCSSUnit_Unset) {
      transition->SetTimingFunction(
        nsTimingFunction(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE));
    } else if (timingFunction.list) {
      ComputeTimingFunction(timingFunction.list->mValue,
                            transition->TimingFunctionSlot());
    }

    FOR_ALL_TRANSITION_PROPS(p) {
      const TransitionPropInfo& info = transitionPropInfo[p];
      TransitionPropData& d = transitionPropData[p];

      // if we're at the end of the list, start at the beginning and repeat
      // until we're out of transitions to populate
      if (d.list) {
        d.list = d.list->mNext ? d.list->mNext :
          aRuleData->ValueFor(info.property)->GetListValue();
      }
    }
  }

  // Each property's index in this array must match its index in the
  // const array |animationPropInfo| above.
  TransitionPropData animationPropData[8];
  TransitionPropData& animDelay = animationPropData[0];
  TransitionPropData& animDuration = animationPropData[1];
  TransitionPropData& animName = animationPropData[2];
  TransitionPropData& animTimingFunction = animationPropData[3];
  TransitionPropData& animDirection = animationPropData[4];
  TransitionPropData& animFillMode = animationPropData[5];
  TransitionPropData& animPlayState = animationPropData[6];
  TransitionPropData& animIterationCount = animationPropData[7];

#define FOR_ALL_ANIMATION_PROPS(var_) \
    for (uint32_t var_ = 0; var_ < 8; ++var_)

  // CSS Animations.

  uint32_t numAnimations =
    CountTransitionProps(animationPropInfo, animationPropData,
                         ArrayLength(animationPropData),
                         display, parentDisplay, aRuleData,
                         conditions);

  display->mAnimations.SetLengthNonZero(numAnimations);

  FOR_ALL_ANIMATION_PROPS(p) {
    const TransitionPropInfo& i = animationPropInfo[p];
    TransitionPropData& d = animationPropData[p];

    display->*(i.sdCount) = d.num;
  }

  // Fill in the animations we just allocated with the appropriate values.
  for (uint32_t i = 0; i < numAnimations; ++i) {
    StyleAnimation *animation = &display->mAnimations[i];

    if (i >= animDelay.num) {
      MOZ_ASSERT(animDelay.num, "animDelay.num must be greater than 0");
      animation->SetDelay(display->mAnimations[i % animDelay.num].GetDelay());
    } else if (animDelay.unit == eCSSUnit_Inherit) {
      // FIXME (Bug 522599) (for all animation properties): write a test that
      // detects when this was wrong for i >= animDelay.num if parent had
      // count for this property not equal to length
      MOZ_ASSERT(i < parentDisplay->mAnimationDelayCount,
                 "animDelay.num computed incorrectly");
      MOZ_ASSERT(!conditions.Cacheable(),
                 "should have made conditions.Cacheable() false above");
      animation->SetDelay(parentDisplay->mAnimations[i].GetDelay());
    } else if (animDelay.unit == eCSSUnit_Initial ||
               animDelay.unit == eCSSUnit_Unset) {
      animation->SetDelay(0.0);
    } else if (animDelay.list) {
      switch (animDelay.list->mValue.GetUnit()) {
        case eCSSUnit_Seconds:
          animation->SetDelay(PR_MSEC_PER_SEC *
                              animDelay.list->mValue.GetFloatValue());
          break;
        case eCSSUnit_Milliseconds:
          animation->SetDelay(animDelay.list->mValue.GetFloatValue());
          break;
        default:
          NS_NOTREACHED("Invalid delay unit");
      }
    }

    if (i >= animDuration.num) {
      MOZ_ASSERT(animDuration.num, "animDuration.num must be greater than 0");
      animation->SetDuration(
        display->mAnimations[i % animDuration.num].GetDuration());
    } else if (animDuration.unit == eCSSUnit_Inherit) {
      MOZ_ASSERT(i < parentDisplay->mAnimationDurationCount,
                 "animDuration.num computed incorrectly");
      MOZ_ASSERT(!conditions.Cacheable(),
                 "should have made conditions.Cacheable() false above");
      animation->SetDuration(parentDisplay->mAnimations[i].GetDuration());
    } else if (animDuration.unit == eCSSUnit_Initial ||
               animDuration.unit == eCSSUnit_Unset) {
      animation->SetDuration(0.0);
    } else if (animDuration.list) {
      switch (animDuration.list->mValue.GetUnit()) {
        case eCSSUnit_Seconds:
          animation->SetDuration(PR_MSEC_PER_SEC *
                                 animDuration.list->mValue.GetFloatValue());
          break;
        case eCSSUnit_Milliseconds:
          animation->SetDuration(animDuration.list->mValue.GetFloatValue());
          break;
        default:
          NS_NOTREACHED("Invalid duration unit");
      }
    }

    if (i >= animName.num) {
      MOZ_ASSERT(animName.num, "animName.num must be greater than 0");
      animation->SetName(display->mAnimations[i % animName.num].GetName());
    } else if (animName.unit == eCSSUnit_Inherit) {
      MOZ_ASSERT(i < parentDisplay->mAnimationNameCount,
                 "animName.num computed incorrectly");
      MOZ_ASSERT(!conditions.Cacheable(),
                 "should have made conditions.Cacheable() false above");
      animation->SetName(parentDisplay->mAnimations[i].GetName());
    } else if (animName.unit == eCSSUnit_Initial ||
               animName.unit == eCSSUnit_Unset) {
      animation->SetName(EmptyString());
    } else if (animName.list) {
      switch (animName.list->mValue.GetUnit()) {
        case eCSSUnit_Ident: {
          nsDependentString
            nameStr(animName.list->mValue.GetStringBufferValue());
          animation->SetName(nameStr);
          break;
        }
        case eCSSUnit_None: {
          animation->SetName(EmptyString());
          break;
        }
        default:
          MOZ_ASSERT(false, "Invalid animation-name unit");
      }
    }

    if (i >= animTimingFunction.num) {
      MOZ_ASSERT(animTimingFunction.num,
        "animTimingFunction.num must be greater than 0");
      animation->SetTimingFunction(
        display->mAnimations[i % animTimingFunction.num].GetTimingFunction());
    } else if (animTimingFunction.unit == eCSSUnit_Inherit) {
      MOZ_ASSERT(i < parentDisplay->mAnimationTimingFunctionCount,
                 "animTimingFunction.num computed incorrectly");
      MOZ_ASSERT(!conditions.Cacheable(),
                 "should have made conditions.Cacheable() false above");
      animation->SetTimingFunction(
        parentDisplay->mAnimations[i].GetTimingFunction());
    } else if (animTimingFunction.unit == eCSSUnit_Initial ||
               animTimingFunction.unit == eCSSUnit_Unset) {
      animation->SetTimingFunction(
        nsTimingFunction(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE));
    } else if (animTimingFunction.list) {
      ComputeTimingFunction(animTimingFunction.list->mValue,
                            animation->TimingFunctionSlot());
    }

    if (i >= animDirection.num) {
      MOZ_ASSERT(animDirection.num,
        "animDirection.num must be greater than 0");
      animation->SetDirection(display->mAnimations[i % animDirection.num].GetDirection());
    } else if (animDirection.unit == eCSSUnit_Inherit) {
      MOZ_ASSERT(i < parentDisplay->mAnimationDirectionCount,
                 "animDirection.num computed incorrectly");
      MOZ_ASSERT(!conditions.Cacheable(),
                 "should have made conditions.Cacheable() false above");
      animation->SetDirection(parentDisplay->mAnimations[i].GetDirection());
    } else if (animDirection.unit == eCSSUnit_Initial ||
               animDirection.unit == eCSSUnit_Unset) {
      animation->SetDirection(dom::PlaybackDirection::Normal);
    } else if (animDirection.list) {
      MOZ_ASSERT(animDirection.list->mValue.GetUnit() == eCSSUnit_Enumerated,
                 "Invalid animation-direction unit");

      animation->SetDirection(
          static_cast<dom::PlaybackDirection>(animDirection.list->mValue.GetIntValue()));
    }

    if (i >= animFillMode.num) {
      MOZ_ASSERT(animFillMode.num, "animFillMode.num must be greater than 0");
      animation->SetFillMode(display->mAnimations[i % animFillMode.num].GetFillMode());
    } else if (animFillMode.unit == eCSSUnit_Inherit) {
      MOZ_ASSERT(i < parentDisplay->mAnimationFillModeCount,
                 "animFillMode.num computed incorrectly");
      MOZ_ASSERT(!conditions.Cacheable(),
                 "should have made conditions.Cacheable() false above");
      animation->SetFillMode(parentDisplay->mAnimations[i].GetFillMode());
    } else if (animFillMode.unit == eCSSUnit_Initial ||
               animFillMode.unit == eCSSUnit_Unset) {
      animation->SetFillMode(dom::FillMode::None);
    } else if (animFillMode.list) {
      MOZ_ASSERT(animFillMode.list->mValue.GetUnit() == eCSSUnit_Enumerated,
                 "Invalid animation-fill-mode unit");

      animation->SetFillMode(
          static_cast<dom::FillMode>(animFillMode.list->mValue.GetIntValue()));
    }

    if (i >= animPlayState.num) {
      MOZ_ASSERT(animPlayState.num,
        "animPlayState.num must be greater than 0");
      animation->SetPlayState(display->mAnimations[i % animPlayState.num].GetPlayState());
    } else if (animPlayState.unit == eCSSUnit_Inherit) {
      MOZ_ASSERT(i < parentDisplay->mAnimationPlayStateCount,
                 "animPlayState.num computed incorrectly");
      MOZ_ASSERT(!conditions.Cacheable(),
                 "should have made conditions.Cacheable() false above");
      animation->SetPlayState(parentDisplay->mAnimations[i].GetPlayState());
    } else if (animPlayState.unit == eCSSUnit_Initial ||
               animPlayState.unit == eCSSUnit_Unset) {
      animation->SetPlayState(NS_STYLE_ANIMATION_PLAY_STATE_RUNNING);
    } else if (animPlayState.list) {
      MOZ_ASSERT(animPlayState.list->mValue.GetUnit() == eCSSUnit_Enumerated,
                 "Invalid animation-play-state unit");

      animation->SetPlayState(animPlayState.list->mValue.GetIntValue());
    }

    if (i >= animIterationCount.num) {
      MOZ_ASSERT(animIterationCount.num,
        "animIterationCount.num must be greater than 0");
      animation->SetIterationCount(display->mAnimations[i % animIterationCount.num].GetIterationCount());
    } else if (animIterationCount.unit == eCSSUnit_Inherit) {
      MOZ_ASSERT(i < parentDisplay->mAnimationIterationCountCount,
                 "animIterationCount.num computed incorrectly");
      MOZ_ASSERT(!conditions.Cacheable(),
                 "should have made conditions.Cacheable() false above");
      animation->SetIterationCount(parentDisplay->mAnimations[i].GetIterationCount());
    } else if (animIterationCount.unit == eCSSUnit_Initial ||
               animIterationCount.unit == eCSSUnit_Unset) {
      animation->SetIterationCount(1.0f);
    } else if (animIterationCount.list) {
      switch (animIterationCount.list->mValue.GetUnit()) {
        case eCSSUnit_Enumerated:
          MOZ_ASSERT(animIterationCount.list->mValue.GetIntValue() ==
                       NS_STYLE_ANIMATION_ITERATION_COUNT_INFINITE,
                     "unexpected value");
          animation->SetIterationCount(NS_IEEEPositiveInfinity());
          break;
        case eCSSUnit_Number:
          animation->SetIterationCount(
            animIterationCount.list->mValue.GetFloatValue());
          break;
        default:
          MOZ_ASSERT(false,
                     "unexpected animation-iteration-count unit");
      }
    }

    FOR_ALL_ANIMATION_PROPS(p) {
      const TransitionPropInfo& info = animationPropInfo[p];
      TransitionPropData& d = animationPropData[p];

      // if we're at the end of the list, start at the beginning and repeat
      // until we're out of animations to populate
      if (d.list) {
        d.list = d.list->mNext ? d.list->mNext :
          aRuleData->ValueFor(info.property)->GetListValue();
      }
    }
  }

  // display: enum, inherit, initial
  SetValue(*aRuleData->ValueForDisplay(), display->mDisplay, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentDisplay->mDisplay,
           StyleDisplay::Inline);

  // contain: none, enum, inherit, initial
  SetValue(*aRuleData->ValueForContain(), display->mContain, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentDisplay->mContain,
           NS_STYLE_CONTAIN_NONE, Unused,
           NS_STYLE_CONTAIN_NONE, Unused, Unused);

  // scroll-behavior: enum, inherit, initial
  SetValue(*aRuleData->ValueForScrollBehavior(), display->mScrollBehavior,
           conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentDisplay->mScrollBehavior, NS_STYLE_SCROLL_BEHAVIOR_AUTO);

  // scroll-snap-type-x: none, enum, inherit, initial
  SetValue(*aRuleData->ValueForScrollSnapTypeX(), display->mScrollSnapTypeX,
           conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentDisplay->mScrollSnapTypeX, NS_STYLE_SCROLL_SNAP_TYPE_NONE);

  // scroll-snap-type-y: none, enum, inherit, initial
  SetValue(*aRuleData->ValueForScrollSnapTypeY(), display->mScrollSnapTypeY,
           conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentDisplay->mScrollSnapTypeY, NS_STYLE_SCROLL_SNAP_TYPE_NONE);

  // scroll-snap-points-x: none, inherit, initial
  const nsCSSValue& scrollSnapPointsX = *aRuleData->ValueForScrollSnapPointsX();
  switch (scrollSnapPointsX.GetUnit()) {
    case eCSSUnit_Null:
      break;
    case eCSSUnit_Initial:
    case eCSSUnit_Unset:
    case eCSSUnit_None:
      display->mScrollSnapPointsX.SetNoneValue();
      break;
    case eCSSUnit_Inherit:
      display->mScrollSnapPointsX = parentDisplay->mScrollSnapPointsX;
      conditions.SetUncacheable();
      break;
    case eCSSUnit_Function: {
      nsCSSValue::Array* func = scrollSnapPointsX.GetArrayValue();
      NS_ASSERTION(func->Item(0).GetKeywordValue() == eCSSKeyword_repeat,
                   "Expected repeat(), got another function name");
      nsStyleCoord coord;
      if (SetCoord(func->Item(1), coord, nsStyleCoord(),
                   SETCOORD_LP | SETCOORD_STORE_CALC |
                   SETCOORD_CALC_CLAMP_NONNEGATIVE,
                   aContext, mPresContext, conditions)) {
        NS_ASSERTION(coord.GetUnit() == eStyleUnit_Coord ||
                     coord.GetUnit() == eStyleUnit_Percent ||
                     coord.GetUnit() == eStyleUnit_Calc,
                     "unexpected unit");
        display->mScrollSnapPointsX = coord;
      }
      break;
    }
    default:
      NS_NOTREACHED("unexpected unit");
  }

  // scroll-snap-points-y: none, inherit, initial
  const nsCSSValue& scrollSnapPointsY = *aRuleData->ValueForScrollSnapPointsY();
  switch (scrollSnapPointsY.GetUnit()) {
    case eCSSUnit_Null:
      break;
    case eCSSUnit_Initial:
    case eCSSUnit_Unset:
    case eCSSUnit_None:
      display->mScrollSnapPointsY.SetNoneValue();
      break;
    case eCSSUnit_Inherit:
      display->mScrollSnapPointsY = parentDisplay->mScrollSnapPointsY;
      conditions.SetUncacheable();
      break;
    case eCSSUnit_Function: {
      nsCSSValue::Array* func = scrollSnapPointsY.GetArrayValue();
      NS_ASSERTION(func->Item(0).GetKeywordValue() == eCSSKeyword_repeat,
                   "Expected repeat(), got another function name");
      nsStyleCoord coord;
      if (SetCoord(func->Item(1), coord, nsStyleCoord(),
                   SETCOORD_LP | SETCOORD_STORE_CALC |
                   SETCOORD_CALC_CLAMP_NONNEGATIVE,
                   aContext, mPresContext, conditions)) {
        NS_ASSERTION(coord.GetUnit() == eStyleUnit_Coord ||
                     coord.GetUnit() == eStyleUnit_Percent ||
                     coord.GetUnit() == eStyleUnit_Calc,
                     "unexpected unit");
        display->mScrollSnapPointsY = coord;
      }
      break;
    }
    default:
      NS_NOTREACHED("unexpected unit");
  }

  // scroll-snap-destination: inherit, initial
  const nsCSSValue& snapDestination = *aRuleData->ValueForScrollSnapDestination();
  switch (snapDestination.GetUnit()) {
    case eCSSUnit_Null:
      break;
    case eCSSUnit_Initial:
    case eCSSUnit_Unset:
      display->mScrollSnapDestination.SetInitialZeroValues();
      break;
    case eCSSUnit_Inherit:
      display->mScrollSnapDestination = parentDisplay->mScrollSnapDestination;
      conditions.SetUncacheable();
      break;
    default: {
        ComputePositionValue(aContext, snapDestination,
                             display->mScrollSnapDestination, conditions);
      }
  }

  // scroll-snap-coordinate: none, inherit, initial
  const nsCSSValue& snapCoordinate = *aRuleData->ValueForScrollSnapCoordinate();
  switch (snapCoordinate.GetUnit()) {
    case eCSSUnit_Null:
      break;
    case eCSSUnit_Initial:
    case eCSSUnit_Unset:
    case eCSSUnit_None:
      // Unset and Initial is none, indicated by an empty array
      display->mScrollSnapCoordinate.Clear();
      break;
    case eCSSUnit_Inherit:
      display->mScrollSnapCoordinate = parentDisplay->mScrollSnapCoordinate;
      conditions.SetUncacheable();
      break;
    case eCSSUnit_List: {
      display->mScrollSnapCoordinate.Clear();
      const nsCSSValueList* item = snapCoordinate.GetListValue();
      do {
        NS_ASSERTION(item->mValue.GetUnit() != eCSSUnit_Null &&
                     item->mValue.GetUnit() != eCSSUnit_Inherit &&
                     item->mValue.GetUnit() != eCSSUnit_Initial &&
                     item->mValue.GetUnit() != eCSSUnit_Unset,
                     "unexpected unit");
        Position* pos = display->mScrollSnapCoordinate.AppendElement();
        ComputePositionValue(aContext, item->mValue, *pos, conditions);
        item = item->mNext;
      } while(item);
      break;
    }
    default:
      NS_NOTREACHED("unexpected unit");
  }

  // isolation: enum, inherit, initial
  SetValue(*aRuleData->ValueForIsolation(), display->mIsolation,
           conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentDisplay->mIsolation, NS_STYLE_ISOLATION_AUTO);

  // -moz-top-layer: enum, inherit, initial
  SetValue(*aRuleData->ValueForTopLayer(), display->mTopLayer,
           conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentDisplay->mTopLayer, NS_STYLE_TOP_LAYER_NONE);

  // Backup original display value for calculation of a hypothetical
  // box (CSS2 10.6.4/10.6.5), in addition to getting our style data right later.
  // See ReflowInput::CalculateHypotheticalBox
  display->mOriginalDisplay = display->mDisplay;

  // appearance: enum, inherit, initial
  SetValue(*aRuleData->ValueForAppearance(),
           display->mAppearance, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentDisplay->mAppearance,
           NS_THEME_NONE);

  // binding: url, none, inherit
  const nsCSSValue* bindingValue = aRuleData->ValueForBinding();
  if (eCSSUnit_URL == bindingValue->GetUnit()) {
    mozilla::css::URLValue* url = bindingValue->GetURLStructValue();
    NS_ASSERTION(url, "What's going on here?");

    if (MOZ_LIKELY(url->GetURI())) {
      display->mBinding = url;
    } else {
      display->mBinding = nullptr;
    }
  }
  else if (eCSSUnit_None == bindingValue->GetUnit() ||
           eCSSUnit_Initial == bindingValue->GetUnit() ||
           eCSSUnit_Unset == bindingValue->GetUnit()) {
    display->mBinding = nullptr;
  }
  else if (eCSSUnit_Inherit == bindingValue->GetUnit()) {
    conditions.SetUncacheable();
    display->mBinding = parentDisplay->mBinding;
  }

  // position: enum, inherit, initial
  SetValue(*aRuleData->ValueForPosition(), display->mPosition, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentDisplay->mPosition,
           NS_STYLE_POSITION_STATIC);
  // If an element is put in the top layer, while it is not absolutely
  // positioned, the position value should be computed to 'absolute' per
  // the Fullscreen API spec.
  if (display->mTopLayer != NS_STYLE_TOP_LAYER_NONE &&
      !display->IsAbsolutelyPositionedStyle()) {
    display->mPosition = NS_STYLE_POSITION_ABSOLUTE;
    // We cannot cache this struct because otherwise it may be used as
    // an aStartStruct for some other elements.
    conditions.SetUncacheable();
  }

  // clear: enum, inherit, initial
  SetValue(*aRuleData->ValueForClear(), display->mBreakType, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentDisplay->mBreakType,
           StyleClear::None);

  // temp fix for bug 24000
  // Map 'auto' and 'avoid' to false, and 'always', 'left', and
  // 'right' to true.
  // "A conforming user agent may interpret the values 'left' and
  // 'right' as 'always'." - CSS2.1, section 13.3.1
  const nsCSSValue* breakBeforeValue = aRuleData->ValueForPageBreakBefore();
  if (eCSSUnit_Enumerated == breakBeforeValue->GetUnit()) {
    display->mBreakBefore =
      (NS_STYLE_PAGE_BREAK_AVOID != breakBeforeValue->GetIntValue() &&
       NS_STYLE_PAGE_BREAK_AUTO  != breakBeforeValue->GetIntValue());
  }
  else if (eCSSUnit_Initial == breakBeforeValue->GetUnit() ||
           eCSSUnit_Unset == breakBeforeValue->GetUnit()) {
    display->mBreakBefore = false;
  }
  else if (eCSSUnit_Inherit == breakBeforeValue->GetUnit()) {
    conditions.SetUncacheable();
    display->mBreakBefore = parentDisplay->mBreakBefore;
  }

  const nsCSSValue* breakAfterValue = aRuleData->ValueForPageBreakAfter();
  if (eCSSUnit_Enumerated == breakAfterValue->GetUnit()) {
    display->mBreakAfter =
      (NS_STYLE_PAGE_BREAK_AVOID != breakAfterValue->GetIntValue() &&
       NS_STYLE_PAGE_BREAK_AUTO  != breakAfterValue->GetIntValue());
  }
  else if (eCSSUnit_Initial == breakAfterValue->GetUnit() ||
           eCSSUnit_Unset == breakAfterValue->GetUnit()) {
    display->mBreakAfter = false;
  }
  else if (eCSSUnit_Inherit == breakAfterValue->GetUnit()) {
    conditions.SetUncacheable();
    display->mBreakAfter = parentDisplay->mBreakAfter;
  }
  // end temp fix

  // page-break-inside: enum, inherit, initial
  SetValue(*aRuleData->ValueForPageBreakInside(),
           display->mBreakInside, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentDisplay->mBreakInside,
           NS_STYLE_PAGE_BREAK_AUTO);

  // touch-action: none, auto, enum, inherit, initial
  SetValue(*aRuleData->ValueForTouchAction(), display->mTouchAction,
           conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentDisplay->mTouchAction,
           /* initial */ NS_STYLE_TOUCH_ACTION_AUTO,
           /* auto */ NS_STYLE_TOUCH_ACTION_AUTO,
           /* none */ NS_STYLE_TOUCH_ACTION_NONE, Unused, Unused);

  // float: enum, inherit, initial
  SetValue(*aRuleData->ValueForFloat(),
           display->mFloat, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentDisplay->mFloat,
           StyleFloat::None);
  // Save mFloat in mOriginalFloat in case we need it later
  display->mOriginalFloat = display->mFloat;

  // overflow-x: enum, inherit, initial
  SetValue(*aRuleData->ValueForOverflowX(),
           display->mOverflowX, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentDisplay->mOverflowX,
           NS_STYLE_OVERFLOW_VISIBLE);

  // overflow-y: enum, inherit, initial
  SetValue(*aRuleData->ValueForOverflowY(),
           display->mOverflowY, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentDisplay->mOverflowY,
           NS_STYLE_OVERFLOW_VISIBLE);

  // CSS3 overflow-x and overflow-y require some fixup as well in some
  // cases.  NS_STYLE_OVERFLOW_VISIBLE and NS_STYLE_OVERFLOW_CLIP are
  // meaningful only when used in both dimensions.
  if (display->mOverflowX != display->mOverflowY &&
      (display->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE ||
       display->mOverflowX == NS_STYLE_OVERFLOW_CLIP ||
       display->mOverflowY == NS_STYLE_OVERFLOW_VISIBLE ||
       display->mOverflowY == NS_STYLE_OVERFLOW_CLIP)) {
    // We can't store in the rule tree since a more specific rule might
    // change these conditions.
    conditions.SetUncacheable();

    // NS_STYLE_OVERFLOW_CLIP is a deprecated value, so if it's specified
    // in only one dimension, convert it to NS_STYLE_OVERFLOW_HIDDEN.
    if (display->mOverflowX == NS_STYLE_OVERFLOW_CLIP)
      display->mOverflowX = NS_STYLE_OVERFLOW_HIDDEN;
    if (display->mOverflowY == NS_STYLE_OVERFLOW_CLIP)
      display->mOverflowY = NS_STYLE_OVERFLOW_HIDDEN;

    // If 'visible' is specified but doesn't match the other dimension, it
    // turns into 'auto'.
    if (display->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE)
      display->mOverflowX = NS_STYLE_OVERFLOW_AUTO;
    if (display->mOverflowY == NS_STYLE_OVERFLOW_VISIBLE)
      display->mOverflowY = NS_STYLE_OVERFLOW_AUTO;
  }

  // When 'contain: paint', update overflow from 'visible' to 'clip'.
  if (display->IsContainPaint()) {
    // XXX This actually sets overflow-[x|y] to -moz-hidden-unscrollable.
    if (display->mOverflowX == NS_STYLE_OVERFLOW_VISIBLE) {
      // This uncacheability (and the one below) could be fixed by adding
      // mOriginalOverflowX and mOriginalOverflowY fields, if necessary.
      display->mOverflowX = NS_STYLE_OVERFLOW_CLIP;
      conditions.SetUncacheable();
    }
    if (display->mOverflowY == NS_STYLE_OVERFLOW_VISIBLE) {
      display->mOverflowY = NS_STYLE_OVERFLOW_CLIP;
      conditions.SetUncacheable();
    }
  }

  SetValue(*aRuleData->ValueForOverflowClipBox(), display->mOverflowClipBox,
           conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentDisplay->mOverflowClipBox,
           NS_STYLE_OVERFLOW_CLIP_BOX_PADDING_BOX);

  SetValue(*aRuleData->ValueForResize(), display->mResize, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentDisplay->mResize,
           NS_STYLE_RESIZE_NONE);

  if (display->mDisplay != StyleDisplay::None) {
    // CSS2 9.7 specifies display type corrections dealing with 'float'
    // and 'position'.  Since generated content can't be floated or
    // positioned, we can deal with it here.

    nsIAtom* pseudo = aContext->GetPseudo();
    if (pseudo && display->mDisplay == StyleDisplay::Contents) {
      // We don't want to create frames for anonymous content using a parent
      // frame that is for content above the root of the anon tree.
      // (XXX what we really should check here is not GetPseudo() but if there's
      //  a 'content' property value that implies anon content but we can't
      //  check that here since that's a different struct(?))
      // We might get display:contents to work for CSS_PSEUDO_ELEMENT_CONTAINS_ELEMENTS
      // pseudos (:first-letter etc) in the future, but those have a lot of
      // special handling in frame construction so they are also unsupported
      // for now.
      display->mOriginalDisplay = display->mDisplay = StyleDisplay::Inline;
      conditions.SetUncacheable();
    }

    // Inherit a <fieldset> grid/flex display type into its anon content frame.
    if (pseudo == nsCSSAnonBoxes::fieldsetContent) {
      MOZ_ASSERT(display->mDisplay == StyleDisplay::Block,
                 "forms.css should have set 'display:block'");
      switch (parentDisplay->mDisplay) {
        case StyleDisplay::Grid:
        case StyleDisplay::InlineGrid:
          display->mDisplay = StyleDisplay::Grid;
          conditions.SetUncacheable();
          break;
        case StyleDisplay::Flex:
        case StyleDisplay::InlineFlex:
          display->mDisplay = StyleDisplay::Flex;
          conditions.SetUncacheable();
          break;
        default:
          break; // Do nothing
      }
    }

    if (nsCSSPseudoElements::firstLetter == pseudo) {
      // a non-floating first-letter must be inline
      // XXX this fix can go away once bug 103189 is fixed correctly
      // Note that we reset mOriginalDisplay to enforce the invariant that it equals mDisplay if we're not positioned or floating.
      display->mOriginalDisplay = display->mDisplay = StyleDisplay::Inline;

      // We can't cache the data in the rule tree since if a more specific
      // rule has 'float: left' we'll end up with the wrong 'display'
      // property.
      conditions.SetUncacheable();
    }

    if (display->IsAbsolutelyPositionedStyle()) {
      // 1) if position is 'absolute' or 'fixed' then display must be
      // block-level and float must be 'none'
      EnsureBlockDisplay(display->mDisplay);
      display->mFloat = StyleFloat::None;

      // Note that it's OK to cache this struct in the ruletree
      // because it's fine as-is for any style context that points to
      // it directly, and any use of it as aStartStruct (e.g. if a
      // more specific rule sets "position: static") will use
      // mOriginalDisplay and mOriginalFloat, which we have carefully
      // not changed.
    } else if (display->mFloat != StyleFloat::None) {
      // 2) if float is not none, and display is not none, then we must
      // set a block-level 'display' type per CSS2.1 section 9.7.
      EnsureBlockDisplay(display->mDisplay);

      // Note that it's OK to cache this struct in the ruletree
      // because it's fine as-is for any style context that points to
      // it directly, and any use of it as aStartStruct (e.g. if a
      // more specific rule sets "float: none") will use
      // mOriginalDisplay, which we have carefully not changed.
    }

    if (display->IsContainPaint()) {
      // An element with contain:paint or contain:layout needs to "be a
      // formatting context". For the purposes of the "display" property, that
      // just means we need to promote "display:inline" to "inline-block".
      // XXX We may also need to promote ruby display vals; see bug 1179349.

      // It's okay to cache this change in the rule tree for the same
      // reasons as floats in the previous condition.
      if (display->mDisplay == StyleDisplay::Inline) {
        display->mDisplay = StyleDisplay::InlineBlock;
      }
    }
  }

  /* Convert the nsCSSValueList into an nsTArray<nsTransformFunction *>. */
  const nsCSSValue* transformValue = aRuleData->ValueForTransform();
  switch (transformValue->GetUnit()) {
  case eCSSUnit_Null:
    break;

  case eCSSUnit_Initial:
  case eCSSUnit_Unset:
  case eCSSUnit_None:
    display->mSpecifiedTransform = nullptr;
    break;

  case eCSSUnit_Inherit:
    display->mSpecifiedTransform = parentDisplay->mSpecifiedTransform;
    conditions.SetUncacheable();
    break;

  case eCSSUnit_SharedList: {
    nsCSSValueSharedList* list = transformValue->GetSharedListValue();
    nsCSSValueList* head = list->mHead;
    MOZ_ASSERT(head, "transform list must have at least one item");
    // can get a _None in here from transform animation
    if (head->mValue.GetUnit() == eCSSUnit_None) {
      MOZ_ASSERT(head->mNext == nullptr, "none must be alone");
      display->mSpecifiedTransform = nullptr;
    } else {
      display->mSpecifiedTransform = list;
    }
    break;
  }

  default:
    MOZ_ASSERT(false, "unrecognized transform unit");
  }

  /* Convert the nsCSSValueList into a will-change bitfield for fast lookup */
  const nsCSSValue* willChangeValue = aRuleData->ValueForWillChange();
  switch (willChangeValue->GetUnit()) {
  case eCSSUnit_Null:
    break;

  case eCSSUnit_List:
  case eCSSUnit_ListDep: {
    display->mWillChange.Clear();
    display->mWillChangeBitField = 0;
    for (const nsCSSValueList* item = willChangeValue->GetListValue();
         item; item = item->mNext)
    {
      if (item->mValue.UnitHasStringValue()) {
        nsAutoString buffer;
        item->mValue.GetStringValue(buffer);
        display->mWillChange.AppendElement(buffer);

        if (buffer.EqualsLiteral("transform")) {
          display->mWillChangeBitField |= NS_STYLE_WILL_CHANGE_TRANSFORM;
        }
        if (buffer.EqualsLiteral("opacity")) {
          display->mWillChangeBitField |= NS_STYLE_WILL_CHANGE_OPACITY;
        }
        if (buffer.EqualsLiteral("scroll-position")) {
          display->mWillChangeBitField |= NS_STYLE_WILL_CHANGE_SCROLL;
        }

        nsCSSPropertyID prop =
          nsCSSProps::LookupProperty(buffer, CSSEnabledState::eForAllContent);
        if (prop != eCSSProperty_UNKNOWN &&
            prop != eCSSPropertyExtra_variable) {
          // If the property given is a shorthand, it indicates the expectation
          // for all the longhands the shorthand expands to.
          if (nsCSSProps::IsShorthand(prop)) {
            for (const nsCSSPropertyID* shorthands =
                   nsCSSProps::SubpropertyEntryFor(prop);
                 *shorthands != eCSSProperty_UNKNOWN; ++shorthands) {
              display->mWillChangeBitField |= GetWillChangeBitFieldFromPropFlags(*shorthands);
            }
          } else {
            display->mWillChangeBitField |= GetWillChangeBitFieldFromPropFlags(prop);
          }
        }
      }
    }
    break;
  }

  case eCSSUnit_Inherit:
    display->mWillChange = parentDisplay->mWillChange;
    display->mWillChangeBitField = parentDisplay->mWillChangeBitField;
    conditions.SetUncacheable();
    break;

  case eCSSUnit_Initial:
  case eCSSUnit_Unset:
  case eCSSUnit_Auto:
    display->mWillChange.Clear();
    display->mWillChangeBitField = 0;
    break;

  default:
    MOZ_ASSERT(false, "unrecognized will-change unit");
  }

  // vertical-align: enum, length, percent, calc, inherit
  const nsCSSValue* verticalAlignValue = aRuleData->ValueForVerticalAlign();
  if (!SetCoord(*verticalAlignValue, display->mVerticalAlign,
                parentDisplay->mVerticalAlign,
                SETCOORD_LPH | SETCOORD_ENUMERATED | SETCOORD_STORE_CALC,
                aContext, mPresContext, conditions)) {
    if (eCSSUnit_Initial == verticalAlignValue->GetUnit() ||
        eCSSUnit_Unset == verticalAlignValue->GetUnit()) {
      display->mVerticalAlign.SetIntValue(NS_STYLE_VERTICAL_ALIGN_BASELINE,
                                          eStyleUnit_Enumerated);
    }
  }

  /* Convert -moz-transform-origin. */
  const nsCSSValue* transformOriginValue =
    aRuleData->ValueForTransformOrigin();
  if (transformOriginValue->GetUnit() != eCSSUnit_Null) {
    const nsCSSValue& valX =
      transformOriginValue->GetUnit() == eCSSUnit_Triplet ?
        transformOriginValue->GetTripletValue().mXValue : *transformOriginValue;
    const nsCSSValue& valY =
      transformOriginValue->GetUnit() == eCSSUnit_Triplet ?
        transformOriginValue->GetTripletValue().mYValue : *transformOriginValue;
    const nsCSSValue& valZ =
      transformOriginValue->GetUnit() == eCSSUnit_Triplet ?
        transformOriginValue->GetTripletValue().mZValue : *transformOriginValue;

    mozilla::DebugOnly<bool> cX =
       SetCoord(valX, display->mTransformOrigin[0],
                parentDisplay->mTransformOrigin[0],
                SETCOORD_LPH | SETCOORD_INITIAL_HALF |
                  SETCOORD_BOX_POSITION | SETCOORD_STORE_CALC |
                  SETCOORD_UNSET_INITIAL,
                aContext, mPresContext, conditions);

     mozilla::DebugOnly<bool> cY =
       SetCoord(valY, display->mTransformOrigin[1],
                parentDisplay->mTransformOrigin[1],
                SETCOORD_LPH | SETCOORD_INITIAL_HALF |
                  SETCOORD_BOX_POSITION | SETCOORD_STORE_CALC |
                  SETCOORD_UNSET_INITIAL,
                aContext, mPresContext, conditions);

     if (valZ.GetUnit() == eCSSUnit_Null) {
       // Null for the z component means a 0 translation, not
       // unspecified, as we have already checked the triplet
       // value for Null.
       display->mTransformOrigin[2].SetCoordValue(0);
     } else {
       mozilla::DebugOnly<bool> cZ =
         SetCoord(valZ, display->mTransformOrigin[2],
                  parentDisplay->mTransformOrigin[2],
                  SETCOORD_LH | SETCOORD_INITIAL_ZERO | SETCOORD_STORE_CALC |
                    SETCOORD_UNSET_INITIAL,
                  aContext, mPresContext, conditions);
       MOZ_ASSERT(cY == cZ, "changed one but not the other");
     }
     MOZ_ASSERT(cX == cY, "changed one but not the other");
     NS_ASSERTION(cX, "Malformed -moz-transform-origin parse!");
  }

  const nsCSSValue* perspectiveOriginValue =
    aRuleData->ValueForPerspectiveOrigin();
  if (perspectiveOriginValue->GetUnit() != eCSSUnit_Null) {
    mozilla::DebugOnly<bool> result =
      SetPairCoords(*perspectiveOriginValue,
                    display->mPerspectiveOrigin[0],
                    display->mPerspectiveOrigin[1],
                    parentDisplay->mPerspectiveOrigin[0],
                    parentDisplay->mPerspectiveOrigin[1],
                    SETCOORD_LPH | SETCOORD_INITIAL_HALF |
                      SETCOORD_BOX_POSITION | SETCOORD_STORE_CALC |
                      SETCOORD_UNSET_INITIAL,
                    aContext, mPresContext, conditions);
    NS_ASSERTION(result, "Malformed -moz-perspective-origin parse!");
  }

  SetCoord(*aRuleData->ValueForPerspective(),
           display->mChildPerspective, parentDisplay->mChildPerspective,
           SETCOORD_LAH | SETCOORD_INITIAL_NONE | SETCOORD_NONE |
             SETCOORD_UNSET_INITIAL,
           aContext, mPresContext, conditions);

  SetValue(*aRuleData->ValueForBackfaceVisibility(),
           display->mBackfaceVisibility, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentDisplay->mBackfaceVisibility,
           NS_STYLE_BACKFACE_VISIBILITY_VISIBLE);

  // transform-style: enum, inherit, initial
  SetValue(*aRuleData->ValueForTransformStyle(),
           display->mTransformStyle, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentDisplay->mTransformStyle,
           NS_STYLE_TRANSFORM_STYLE_FLAT);

  // transform-box: enum, inherit, initial
  SetValue(*aRuleData->ValueForTransformBox(),
           display->mTransformBox, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentDisplay->mTransformBox,
           NS_STYLE_TRANSFORM_BOX_BORDER_BOX);

  // orient: enum, inherit, initial
  SetValue(*aRuleData->ValueForOrient(),
           display->mOrient, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentDisplay->mOrient,
           StyleOrient::Inline);

  // shape-outside: none | [ <basic-shape> || <shape-box> ] | <image>
  const nsCSSValue* shapeOutsideValue = aRuleData->ValueForShapeOutside();
  switch (shapeOutsideValue->GetUnit()) {
    case eCSSUnit_Null:
      break;
    case eCSSUnit_None:
    case eCSSUnit_Initial:
    case eCSSUnit_Unset:
      display->mShapeOutside = StyleShapeOutside();
      break;
    case eCSSUnit_Inherit:
      conditions.SetUncacheable();
      display->mShapeOutside = parentDisplay->mShapeOutside;
      break;
    case eCSSUnit_URL: {
      display->mShapeOutside = StyleShapeOutside();
      display->mShapeOutside.SetURL(shapeOutsideValue->GetURLStructValue());
      break;
    }
    case eCSSUnit_Array: {
      display->mShapeOutside = StyleShapeOutside();
      SetStyleShapeSourceToCSSValue(&display->mShapeOutside, shapeOutsideValue,
                                    aContext, mPresContext, conditions);
      break;
    }
    default:
      MOZ_ASSERT_UNREACHABLE("Unrecognized shape-outside unit!");
  }

  COMPUTE_END_RESET(Display, display)
}

const void*
nsRuleNode::ComputeVisibilityData(void* aStartStruct,
                                  const nsRuleData* aRuleData,
                                  nsStyleContext* aContext,
                                  nsRuleNode* aHighestNode,
                                  const RuleDetail aRuleDetail,
                                  const RuleNodeCacheConditions aConditions)
{
  COMPUTE_START_INHERITED(Visibility, visibility, parentVisibility)

  // IMPORTANT: No properties in this struct have lengths in them.  We
  // depend on this since CalcLengthWith can call StyleVisibility()
  // to get the language for resolving fonts!

  // direction: enum, inherit, initial
  SetValue(*aRuleData->ValueForDirection(), visibility->mDirection,
           conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentVisibility->mDirection,
           (GET_BIDI_OPTION_DIRECTION(mPresContext->GetBidi())
            == IBMBIDI_TEXTDIRECTION_RTL)
            ? NS_STYLE_DIRECTION_RTL : NS_STYLE_DIRECTION_LTR);

  // visibility: enum, inherit, initial
  SetValue(*aRuleData->ValueForVisibility(), visibility->mVisible,
           conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentVisibility->mVisible,
           NS_STYLE_VISIBILITY_VISIBLE);

  // image-rendering: enum, inherit
  SetValue(*aRuleData->ValueForImageRendering(),
           visibility->mImageRendering, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentVisibility->mImageRendering,
           NS_STYLE_IMAGE_RENDERING_AUTO);

  // writing-mode: enum, inherit, initial
  SetValue(*aRuleData->ValueForWritingMode(), visibility->mWritingMode,
           conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentVisibility->mWritingMode,
           NS_STYLE_WRITING_MODE_HORIZONTAL_TB);

  // text-orientation: enum, inherit, initial
  SetValue(*aRuleData->ValueForTextOrientation(), visibility->mTextOrientation,
           conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentVisibility->mTextOrientation,
           NS_STYLE_TEXT_ORIENTATION_MIXED);

  // image-orientation: enum, inherit, initial
  const nsCSSValue* orientation = aRuleData->ValueForImageOrientation();
  if (orientation->GetUnit() == eCSSUnit_Inherit ||
      orientation->GetUnit() == eCSSUnit_Unset) {
    conditions.SetUncacheable();
    visibility->mImageOrientation = parentVisibility->mImageOrientation;
  } else if (orientation->GetUnit() == eCSSUnit_Initial) {
    visibility->mImageOrientation = nsStyleImageOrientation();
  } else if (orientation->IsAngularUnit()) {
    double angle = orientation->GetAngleValueInRadians();
    visibility->mImageOrientation =
      nsStyleImageOrientation::CreateAsAngleAndFlip(angle, false);
  } else if (orientation->GetUnit() == eCSSUnit_Array) {
    const nsCSSValue::Array* array = orientation->GetArrayValue();
    MOZ_ASSERT(array->Item(0).IsAngularUnit(),
               "First image-orientation value is not an angle");
    MOZ_ASSERT(array->Item(1).GetUnit() == eCSSUnit_Enumerated &&
               array->Item(1).GetIntValue() == NS_STYLE_IMAGE_ORIENTATION_FLIP,
               "Second image-orientation value is not 'flip'");
    double angle = array->Item(0).GetAngleValueInRadians();
    visibility->mImageOrientation =
      nsStyleImageOrientation::CreateAsAngleAndFlip(angle, true);

  } else if (orientation->GetUnit() == eCSSUnit_Enumerated) {
    switch (orientation->GetIntValue()) {
      case NS_STYLE_IMAGE_ORIENTATION_FLIP:
        visibility->mImageOrientation = nsStyleImageOrientation::CreateAsFlip();
        break;
      case NS_STYLE_IMAGE_ORIENTATION_FROM_IMAGE:
        visibility->mImageOrientation = nsStyleImageOrientation::CreateAsFromImage();
        break;
      default:
        NS_NOTREACHED("Invalid image-orientation enumerated value");
    }
  } else {
    MOZ_ASSERT(orientation->GetUnit() == eCSSUnit_Null, "Should be null unit");
  }

  SetValue(*aRuleData->ValueForColorAdjust(), visibility->mColorAdjust,
           conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentVisibility->mColorAdjust,
           NS_STYLE_COLOR_ADJUST_ECONOMY);

  COMPUTE_END_INHERITED(Visibility, visibility)
}

const void*
nsRuleNode::ComputeColorData(void* aStartStruct,
                             const nsRuleData* aRuleData,
                             nsStyleContext* aContext,
                             nsRuleNode* aHighestNode,
                             const RuleDetail aRuleDetail,
                             const RuleNodeCacheConditions aConditions)
{
  COMPUTE_START_INHERITED(Color, color, parentColor)

  // color: color, string, inherit
  // Special case for currentColor.  According to CSS3, setting color to 'currentColor'
  // should behave as if it is inherited
  const nsCSSValue* colorValue = aRuleData->ValueForColor();
  if ((colorValue->GetUnit() == eCSSUnit_EnumColor &&
       colorValue->GetIntValue() == NS_COLOR_CURRENTCOLOR) ||
      colorValue->GetUnit() == eCSSUnit_Unset) {
    color->mColor = parentColor->mColor;
    conditions.SetUncacheable();
  }
  else if (colorValue->GetUnit() == eCSSUnit_Initial) {
    color->mColor = mPresContext->DefaultColor();
  }
  else {
    SetColor(*colorValue, parentColor->mColor, mPresContext, aContext,
             color->mColor, conditions);
  }

  COMPUTE_END_INHERITED(Color, color)
}

// information about how to compute values for background-* properties
template <class SpecifiedValueItem, class ComputedValueItem>
struct BackgroundItemComputer {
};

template <>
struct BackgroundItemComputer<nsCSSValueList, uint8_t>
{
  static void ComputeValue(nsStyleContext* aStyleContext,
                           const nsCSSValueList* aSpecifiedValue,
                           uint8_t& aComputedValue,
                           RuleNodeCacheConditions& aConditions)
  {
    SetValue(aSpecifiedValue->mValue, aComputedValue, aConditions,
             SETVAL_ENUMERATED, uint8_t(0), 0);
  }
};

template <>
struct BackgroundItemComputer<nsCSSValuePairList, nsStyleImageLayers::Repeat>
{
  static void ComputeValue(nsStyleContext* aStyleContext,
                           const nsCSSValuePairList* aSpecifiedValue,
                           nsStyleImageLayers::Repeat& aComputedValue,
                           RuleNodeCacheConditions& aConditions)
  {
    NS_ASSERTION(aSpecifiedValue->mXValue.GetUnit() == eCSSUnit_Enumerated &&
                 (aSpecifiedValue->mYValue.GetUnit() == eCSSUnit_Enumerated ||
                  aSpecifiedValue->mYValue.GetUnit() == eCSSUnit_Null),
                 "Invalid unit");

    bool hasContraction = true;
    uint8_t value = aSpecifiedValue->mXValue.GetIntValue();
    switch (value) {
    case NS_STYLE_IMAGELAYER_REPEAT_REPEAT_X:
      aComputedValue.mXRepeat = NS_STYLE_IMAGELAYER_REPEAT_REPEAT;
      aComputedValue.mYRepeat = NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT;
      break;
    case NS_STYLE_IMAGELAYER_REPEAT_REPEAT_Y:
      aComputedValue.mXRepeat = NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT;
      aComputedValue.mYRepeat = NS_STYLE_IMAGELAYER_REPEAT_REPEAT;
      break;
    default:
      NS_ASSERTION(value == NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT ||
                   value == NS_STYLE_IMAGELAYER_REPEAT_REPEAT ||
                   value == NS_STYLE_IMAGELAYER_REPEAT_SPACE ||
                   value == NS_STYLE_IMAGELAYER_REPEAT_ROUND, "Unexpected value");
      aComputedValue.mXRepeat = value;
      hasContraction = false;
      break;
    }

    if (hasContraction) {
      NS_ASSERTION(aSpecifiedValue->mYValue.GetUnit() == eCSSUnit_Null,
                   "Invalid unit.");
      return;
    }

    switch (aSpecifiedValue->mYValue.GetUnit()) {
    case eCSSUnit_Null:
      aComputedValue.mYRepeat = aComputedValue.mXRepeat;
      break;
    case eCSSUnit_Enumerated:
      value = aSpecifiedValue->mYValue.GetIntValue();
      NS_ASSERTION(value == NS_STYLE_IMAGELAYER_REPEAT_NO_REPEAT ||
                   value == NS_STYLE_IMAGELAYER_REPEAT_REPEAT ||
                   value == NS_STYLE_IMAGELAYER_REPEAT_SPACE ||
                   value == NS_STYLE_IMAGELAYER_REPEAT_ROUND, "Unexpected value");
      aComputedValue.mYRepeat = value;
      break;
    default:
      NS_NOTREACHED("Unexpected CSS value");
      break;
    }
  }
};

template <>
struct BackgroundItemComputer<nsCSSValueList, nsStyleImage>
{
  static void ComputeValue(nsStyleContext* aStyleContext,
                           const nsCSSValueList* aSpecifiedValue,
                           nsStyleImage& aComputedValue,
                           RuleNodeCacheConditions& aConditions)
  {
    SetStyleImage(aStyleContext, aSpecifiedValue->mValue, aComputedValue,
                  aConditions);
  }
};

template <>
struct BackgroundItemComputer<nsCSSValueList, RefPtr<css::URLValueData>>
{
  static void ComputeValue(nsStyleContext* aStyleContext,
                           const nsCSSValueList* aSpecifiedValue,
                           RefPtr<css::URLValueData>& aComputedValue,
                           RuleNodeCacheConditions& aConditions)
  {
    switch (aSpecifiedValue->mValue.GetUnit()) {
      case eCSSUnit_Null:
        break;
      case eCSSUnit_URL:
        aComputedValue = aSpecifiedValue->mValue.GetURLStructValue();
        break;
      case eCSSUnit_Image:
        aComputedValue = aSpecifiedValue->mValue.GetImageStructValue();
        break;
      default:
        aComputedValue = nullptr;
        break;
    }
  }
};

/* Helper function for ComputePositionValue.
 * This function computes a single PositionCoord from two nsCSSValue objects,
 * which represent an edge and an offset from that edge.
 */
static void
ComputePositionCoord(nsStyleContext* aStyleContext,
                     const nsCSSValue& aEdge,
                     const nsCSSValue& aOffset,
                     Position::Coord* aResult,
                     RuleNodeCacheConditions& aConditions)
{
  if (eCSSUnit_Percent == aOffset.GetUnit()) {
    aResult->mLength = 0;
    aResult->mPercent = aOffset.GetPercentValue();
    aResult->mHasPercent = true;
  } else if (aOffset.IsLengthUnit()) {
    aResult->mLength = CalcLength(aOffset, aStyleContext,
                                  aStyleContext->PresContext(),
                                  aConditions);
    aResult->mPercent = 0.0f;
    aResult->mHasPercent = false;
  } else if (aOffset.IsCalcUnit()) {
    LengthPercentPairCalcOps ops(aStyleContext,
                                 aStyleContext->PresContext(),
                                 aConditions);
    nsRuleNode::ComputedCalc vals = ComputeCalc(aOffset, ops);
    aResult->mLength = vals.mLength;
    aResult->mPercent = vals.mPercent;
    aResult->mHasPercent = ops.mHasPercent;
  } else {
    aResult->mLength = 0;
    aResult->mPercent = 0.0f;
    aResult->mHasPercent = false;
    NS_ASSERTION(aOffset.GetUnit() == eCSSUnit_Null, "unexpected unit");
  }

  if (eCSSUnit_Enumerated == aEdge.GetUnit()) {
    int sign;
    if (aEdge.GetIntValue() & (NS_STYLE_IMAGELAYER_POSITION_BOTTOM |
                               NS_STYLE_IMAGELAYER_POSITION_RIGHT)) {
      sign = -1;
    } else {
      sign = 1;
    }
    aResult->mPercent = GetFloatFromBoxPosition(aEdge.GetIntValue()) +
                        sign * aResult->mPercent;
    aResult->mLength = sign * aResult->mLength;
    aResult->mHasPercent = true;
  } else {
    NS_ASSERTION(eCSSUnit_Null == aEdge.GetUnit(), "unexpected unit");
  }
}

/* Helper function to convert a CSS <position> specified value into its
 * computed-style form. */
static void
ComputePositionValue(nsStyleContext* aStyleContext,
                     const nsCSSValue& aValue,
                     Position& aComputedValue,
                     RuleNodeCacheConditions& aConditions)
{
  NS_ASSERTION(aValue.GetUnit() == eCSSUnit_Array,
               "unexpected unit for CSS <position> value");

  RefPtr<nsCSSValue::Array> positionArray = aValue.GetArrayValue();
  NS_ASSERTION(positionArray->Count() == 4,
               "unexpected number of values in CSS <position> value");

  const nsCSSValue &xEdge   = positionArray->Item(0);
  const nsCSSValue &xOffset = positionArray->Item(1);
  const nsCSSValue &yEdge   = positionArray->Item(2);
  const nsCSSValue &yOffset = positionArray->Item(3);

  NS_ASSERTION((eCSSUnit_Enumerated == xEdge.GetUnit()  ||
                eCSSUnit_Null       == xEdge.GetUnit()) &&
               (eCSSUnit_Enumerated == yEdge.GetUnit()  ||
                eCSSUnit_Null       == yEdge.GetUnit()) &&
               eCSSUnit_Enumerated != xOffset.GetUnit()  &&
               eCSSUnit_Enumerated != yOffset.GetUnit(),
               "Invalid background position");

  ComputePositionCoord(aStyleContext, xEdge, xOffset,
                       &aComputedValue.mXPosition,
                       aConditions);

  ComputePositionCoord(aStyleContext, yEdge, yOffset,
                       &aComputedValue.mYPosition,
                       aConditions);
}

/* Helper function to convert the -x or -y part of a CSS <position> specified
 * value into its computed-style form. */
static void
ComputePositionCoordValue(nsStyleContext* aStyleContext,
                          const nsCSSValue& aValue,
                          Position::Coord& aComputedValue,
                          RuleNodeCacheConditions& aConditions)
{
  NS_ASSERTION(aValue.GetUnit() == eCSSUnit_Array,
               "unexpected unit for position coord value");

  RefPtr<nsCSSValue::Array> positionArray = aValue.GetArrayValue();
  NS_ASSERTION(positionArray->Count() == 2,
               "unexpected number of values, expecting one edge and one offset");

  const nsCSSValue &edge   = positionArray->Item(0);
  const nsCSSValue &offset = positionArray->Item(1);

  NS_ASSERTION((eCSSUnit_Enumerated == edge.GetUnit() ||
                eCSSUnit_Null       == edge.GetUnit()) &&
               eCSSUnit_Enumerated != offset.GetUnit(),
               "Invalid background position");

  ComputePositionCoord(aStyleContext, edge, offset,
                       &aComputedValue,
                       aConditions);
}

struct BackgroundSizeAxis {
  nsCSSValue nsCSSValuePairList::* specified;
  nsStyleImageLayers::Size::Dimension nsStyleImageLayers::Size::* result;
  uint8_t nsStyleImageLayers::Size::* type;
};

static const BackgroundSizeAxis gBGSizeAxes[] = {
  { &nsCSSValuePairList::mXValue,
    &nsStyleImageLayers::Size::mWidth,
    &nsStyleImageLayers::Size::mWidthType },
  { &nsCSSValuePairList::mYValue,
    &nsStyleImageLayers::Size::mHeight,
    &nsStyleImageLayers::Size::mHeightType }
};

template <>
struct BackgroundItemComputer<nsCSSValuePairList, nsStyleImageLayers::Size>
{
  static void ComputeValue(nsStyleContext* aStyleContext,
                           const nsCSSValuePairList* aSpecifiedValue,
                           nsStyleImageLayers::Size& aComputedValue,
                           RuleNodeCacheConditions& aConditions)
  {
    nsStyleImageLayers::Size &size = aComputedValue;
    for (const BackgroundSizeAxis *axis = gBGSizeAxes,
                        *axis_end = ArrayEnd(gBGSizeAxes);
         axis < axis_end; ++axis) {
      const nsCSSValue &specified = aSpecifiedValue->*(axis->specified);
      if (eCSSUnit_Auto == specified.GetUnit()) {
        size.*(axis->type) = nsStyleImageLayers::Size::eAuto;
      }
      else if (eCSSUnit_Enumerated == specified.GetUnit()) {
        static_assert(nsStyleImageLayers::Size::eContain ==
                      NS_STYLE_IMAGELAYER_SIZE_CONTAIN &&
                      nsStyleImageLayers::Size::eCover ==
                      NS_STYLE_IMAGELAYER_SIZE_COVER,
                      "background size constants out of sync");
        MOZ_ASSERT(specified.GetIntValue() == NS_STYLE_IMAGELAYER_SIZE_CONTAIN ||
                   specified.GetIntValue() == NS_STYLE_IMAGELAYER_SIZE_COVER,
                   "invalid enumerated value for size coordinate");
        size.*(axis->type) = specified.GetIntValue();
      }
      else if (eCSSUnit_Null == specified.GetUnit()) {
        MOZ_ASSERT(axis == gBGSizeAxes + 1,
                   "null allowed only as height value, and only "
                   "for contain/cover/initial/inherit");
#ifdef DEBUG
        {
          const nsCSSValue &widthValue = aSpecifiedValue->mXValue;
          MOZ_ASSERT(widthValue.GetUnit() != eCSSUnit_Inherit &&
                     widthValue.GetUnit() != eCSSUnit_Initial &&
                     widthValue.GetUnit() != eCSSUnit_Unset,
                     "initial/inherit/unset should already have been handled");
          MOZ_ASSERT(widthValue.GetUnit() == eCSSUnit_Enumerated &&
                     (widthValue.GetIntValue() == NS_STYLE_IMAGELAYER_SIZE_CONTAIN ||
                      widthValue.GetIntValue() == NS_STYLE_IMAGELAYER_SIZE_COVER),
                     "null height value not corresponding to allowable "
                     "non-null width value");
        }
#endif
        size.*(axis->type) = size.mWidthType;
      }
      else if (eCSSUnit_Percent == specified.GetUnit()) {
        (size.*(axis->result)).mLength = 0;
        (size.*(axis->result)).mPercent = specified.GetPercentValue();
        (size.*(axis->result)).mHasPercent = true;
        size.*(axis->type) = nsStyleImageLayers::Size::eLengthPercentage;
      }
      else if (specified.IsLengthUnit()) {
        (size.*(axis->result)).mLength =
          CalcLength(specified, aStyleContext, aStyleContext->PresContext(),
                     aConditions);
        (size.*(axis->result)).mPercent = 0.0f;
        (size.*(axis->result)).mHasPercent = false;
        size.*(axis->type) = nsStyleImageLayers::Size::eLengthPercentage;
      } else {
        MOZ_ASSERT(specified.IsCalcUnit(), "unexpected unit");
        LengthPercentPairCalcOps ops(aStyleContext,
                                     aStyleContext->PresContext(),
                                     aConditions);
        nsRuleNode::ComputedCalc vals = ComputeCalc(specified, ops);
        (size.*(axis->result)).mLength = vals.mLength;
        (size.*(axis->result)).mPercent = vals.mPercent;
        (size.*(axis->result)).mHasPercent = ops.mHasPercent;
        size.*(axis->type) = nsStyleImageLayers::Size::eLengthPercentage;
      }
    }

    MOZ_ASSERT(size.mWidthType < nsStyleImageLayers::Size::eDimensionType_COUNT,
               "bad width type");
    MOZ_ASSERT(size.mHeightType < nsStyleImageLayers::Size::eDimensionType_COUNT,
               "bad height type");
    MOZ_ASSERT((size.mWidthType != nsStyleImageLayers::Size::eContain &&
                size.mWidthType != nsStyleImageLayers::Size::eCover) ||
               size.mWidthType == size.mHeightType,
               "contain/cover apply to both dimensions or to neither");
  }
};

template <class ComputedValueItem>
static void
SetImageLayerList(nsStyleContext* aStyleContext,
                  const nsCSSValue& aValue,
                  nsStyleAutoArray<nsStyleImageLayers::Layer>& aLayers,
                  const nsStyleAutoArray<nsStyleImageLayers::Layer>& aParentLayers,
                  ComputedValueItem nsStyleImageLayers::Layer::* aResultLocation,
                  ComputedValueItem aInitialValue,
                  uint32_t aParentItemCount,
                  uint32_t& aItemCount,
                  uint32_t& aMaxItemCount,
                  bool& aRebuild,
                  RuleNodeCacheConditions& aConditions)
{
  switch (aValue.GetUnit()) {
  case eCSSUnit_Null:
    break;

  case eCSSUnit_Inherit:
    aRebuild = true;
    aConditions.SetUncacheable();
    aLayers.EnsureLengthAtLeast(aParentItemCount);
    aItemCount = aParentItemCount;
    for (uint32_t i = 0; i < aParentItemCount; ++i) {
      aLayers[i].*aResultLocation = aParentLayers[i].*aResultLocation;
    }
    break;

  case eCSSUnit_Initial:
  case eCSSUnit_Unset:
    aRebuild = true;
    aItemCount = 1;
    aLayers[0].*aResultLocation = aInitialValue;
    break;

  case eCSSUnit_List:
  case eCSSUnit_ListDep: {
    aRebuild = true;
    aItemCount = 0;
    const nsCSSValueList* item = aValue.GetListValue();
    do {
      NS_ASSERTION(item->mValue.GetUnit() != eCSSUnit_Null &&
                   item->mValue.GetUnit() != eCSSUnit_Inherit &&
                   item->mValue.GetUnit() != eCSSUnit_Initial &&
                   item->mValue.GetUnit() != eCSSUnit_Unset,
                   "unexpected unit");
      ++aItemCount;
      aLayers.EnsureLengthAtLeast(aItemCount);
      BackgroundItemComputer<nsCSSValueList, ComputedValueItem>
        ::ComputeValue(aStyleContext, item,
                       aLayers[aItemCount-1].*aResultLocation,
                       aConditions);
      item = item->mNext;
    } while (item);
    break;
  }

  default:
    MOZ_ASSERT(false, "unexpected unit");
  }

  if (aItemCount > aMaxItemCount)
    aMaxItemCount = aItemCount;
}

// The same as SetImageLayerList, but for values stored in
// layer.mPosition.*aResultLocation instead of layer.*aResultLocation.
// This code is duplicated because it would be annoying to make
// SetImageLayerList generic enough to handle both cases.
static void
SetImageLayerPositionCoordList(
                  nsStyleContext* aStyleContext,
                  const nsCSSValue& aValue,
                  nsStyleAutoArray<nsStyleImageLayers::Layer>& aLayers,
                  const nsStyleAutoArray<nsStyleImageLayers::Layer>& aParentLayers,
                  Position::Coord
                      Position::* aResultLocation,
                  Position::Coord aInitialValue,
                  uint32_t aParentItemCount,
                  uint32_t& aItemCount,
                  uint32_t& aMaxItemCount,
                  bool& aRebuild,
                  RuleNodeCacheConditions& aConditions)
{
  switch (aValue.GetUnit()) {
  case eCSSUnit_Null:
    break;

  case eCSSUnit_Inherit:
    aRebuild = true;
    aConditions.SetUncacheable();
    aLayers.EnsureLengthAtLeast(aParentItemCount);
    aItemCount = aParentItemCount;
    for (uint32_t i = 0; i < aParentItemCount; ++i) {
      aLayers[i].mPosition.*aResultLocation = aParentLayers[i].mPosition.*aResultLocation;
    }
    break;

  case eCSSUnit_Initial:
  case eCSSUnit_Unset:
    aRebuild = true;
    aItemCount = 1;
    aLayers[0].mPosition.*aResultLocation = aInitialValue;
    break;

  case eCSSUnit_List:
  case eCSSUnit_ListDep: {
    aRebuild = true;
    aItemCount = 0;
    const nsCSSValueList* item = aValue.GetListValue();
    do {
      NS_ASSERTION(item->mValue.GetUnit() != eCSSUnit_Null &&
                   item->mValue.GetUnit() != eCSSUnit_Inherit &&
                   item->mValue.GetUnit() != eCSSUnit_Initial &&
                   item->mValue.GetUnit() != eCSSUnit_Unset,
                   "unexpected unit");
      ++aItemCount;
      aLayers.EnsureLengthAtLeast(aItemCount);

      ComputePositionCoordValue(aStyleContext, item->mValue,
                                aLayers[aItemCount-1].mPosition.*aResultLocation,
                                aConditions);
      item = item->mNext;
    } while (item);
    break;
  }

  default:
    MOZ_ASSERT(false, "unexpected unit");
  }

  if (aItemCount > aMaxItemCount)
    aMaxItemCount = aItemCount;
}

template <class ComputedValueItem>
static void
SetImageLayerPairList(nsStyleContext* aStyleContext,
                      const nsCSSValue& aValue,
                      nsStyleAutoArray<nsStyleImageLayers::Layer>& aLayers,
                      const nsStyleAutoArray<nsStyleImageLayers::Layer>& aParentLayers,
                      ComputedValueItem nsStyleImageLayers::Layer::*
                                                                aResultLocation,
                      ComputedValueItem aInitialValue,
                      uint32_t aParentItemCount,
                      uint32_t& aItemCount,
                      uint32_t& aMaxItemCount,
                      bool& aRebuild,
                      RuleNodeCacheConditions& aConditions)
{
  switch (aValue.GetUnit()) {
  case eCSSUnit_Null:
    break;

  case eCSSUnit_Inherit:
    aRebuild = true;
    aConditions.SetUncacheable();
    aLayers.EnsureLengthAtLeast(aParentItemCount);
    aItemCount = aParentItemCount;
    for (uint32_t i = 0; i < aParentItemCount; ++i) {
      aLayers[i].*aResultLocation = aParentLayers[i].*aResultLocation;
    }
    break;

  case eCSSUnit_Initial:
  case eCSSUnit_Unset:
    aRebuild = true;
    aItemCount = 1;
    aLayers[0].*aResultLocation = aInitialValue;
    break;

  case eCSSUnit_PairList:
  case eCSSUnit_PairListDep: {
    aRebuild = true;
    aItemCount = 0;
    const nsCSSValuePairList* item = aValue.GetPairListValue();
    do {
      NS_ASSERTION(item->mXValue.GetUnit() != eCSSUnit_Inherit &&
                   item->mXValue.GetUnit() != eCSSUnit_Initial &&
                   item->mXValue.GetUnit() != eCSSUnit_Unset &&
                   item->mYValue.GetUnit() != eCSSUnit_Inherit &&
                   item->mYValue.GetUnit() != eCSSUnit_Initial &&
                   item->mYValue.GetUnit() != eCSSUnit_Unset,
                   "unexpected unit");
      ++aItemCount;
      aLayers.EnsureLengthAtLeast(aItemCount);
      BackgroundItemComputer<nsCSSValuePairList, ComputedValueItem>
        ::ComputeValue(aStyleContext, item,
                       aLayers[aItemCount-1].*aResultLocation,
                       aConditions);
      item = item->mNext;
    } while (item);
    break;
  }

  default:
    MOZ_ASSERT(false, "unexpected unit");
  }

  if (aItemCount > aMaxItemCount)
    aMaxItemCount = aItemCount;
}

template <class ComputedValueItem>
static void
FillImageLayerList(
    nsStyleAutoArray<nsStyleImageLayers::Layer>& aLayers,
    ComputedValueItem nsStyleImageLayers::Layer::* aResultLocation,
    uint32_t aItemCount, uint32_t aFillCount)
{
  NS_PRECONDITION(aFillCount <= aLayers.Length(), "unexpected array length");
  for (uint32_t sourceLayer = 0, destLayer = aItemCount;
       destLayer < aFillCount;
       ++sourceLayer, ++destLayer) {
    aLayers[destLayer].*aResultLocation =
      aLayers[sourceLayer].*aResultLocation;
  }
}

// The same as FillImageLayerList, but for values stored in
// layer.mPosition.*aResultLocation instead of layer.*aResultLocation.
static void
FillImageLayerPositionCoordList(
    nsStyleAutoArray<nsStyleImageLayers::Layer>& aLayers,
    Position::Coord
        Position::* aResultLocation,
    uint32_t aItemCount, uint32_t aFillCount)
{
  NS_PRECONDITION(aFillCount <= aLayers.Length(), "unexpected array length");
  for (uint32_t sourceLayer = 0, destLayer = aItemCount;
       destLayer < aFillCount;
       ++sourceLayer, ++destLayer) {
    aLayers[destLayer].mPosition.*aResultLocation =
      aLayers[sourceLayer].mPosition.*aResultLocation;
  }
}

/* static */
void
nsRuleNode::FillAllBackgroundLists(nsStyleImageLayers& aImage,
                                   uint32_t aMaxItemCount)
{
  // Delete any extra items.  We need to keep layers in which any
  // property was specified.
  aImage.mLayers.TruncateLengthNonZero(aMaxItemCount);

  uint32_t fillCount = aImage.mImageCount;
  FillImageLayerList(aImage.mLayers,
                     &nsStyleImageLayers::Layer::mImage,
                     aImage.mImageCount, fillCount);
  FillImageLayerList(aImage.mLayers,
                     &nsStyleImageLayers::Layer::mRepeat,
                     aImage.mRepeatCount, fillCount);
  FillImageLayerList(aImage.mLayers,
                     &nsStyleImageLayers::Layer::mAttachment,
                     aImage.mAttachmentCount, fillCount);
  FillImageLayerList(aImage.mLayers,
                     &nsStyleImageLayers::Layer::mClip,
                     aImage.mClipCount, fillCount);
  FillImageLayerList(aImage.mLayers,
                     &nsStyleImageLayers::Layer::mBlendMode,
                     aImage.mBlendModeCount, fillCount);
  FillImageLayerList(aImage.mLayers,
                     &nsStyleImageLayers::Layer::mOrigin,
                     aImage.mOriginCount, fillCount);
  FillImageLayerPositionCoordList(aImage.mLayers,
                                  &Position::mXPosition,
                                  aImage.mPositionXCount, fillCount);
  FillImageLayerPositionCoordList(aImage.mLayers,
                                  &Position::mYPosition,
                                  aImage.mPositionYCount, fillCount);
  FillImageLayerList(aImage.mLayers,
                     &nsStyleImageLayers::Layer::mSize,
                     aImage.mSizeCount, fillCount);
}

const void*
nsRuleNode::ComputeBackgroundData(void* aStartStruct,
                                  const nsRuleData* aRuleData,
                                  nsStyleContext* aContext,
                                  nsRuleNode* aHighestNode,
                                  const RuleDetail aRuleDetail,
                                  const RuleNodeCacheConditions aConditions)
{
  COMPUTE_START_RESET(Background, bg, parentBG)

  // background-color: color, string, inherit
  const nsCSSValue* backColorValue = aRuleData->ValueForBackgroundColor();
  if (eCSSUnit_Initial == backColorValue->GetUnit() ||
      eCSSUnit_Unset == backColorValue->GetUnit()) {
    bg->mBackgroundColor = NS_RGBA(0, 0, 0, 0);
  } else if (!SetColor(*backColorValue, parentBG->mBackgroundColor,
                       mPresContext, aContext, bg->mBackgroundColor,
                       conditions)) {
    NS_ASSERTION(eCSSUnit_Null == backColorValue->GetUnit(),
                 "unexpected color unit");
  }

  uint32_t maxItemCount = 1;
  bool rebuild = false;

  // background-image: url (stored as image), none, inherit [list]
  nsStyleImage initialImage;
  SetImageLayerList(aContext, *aRuleData->ValueForBackgroundImage(),
                    bg->mImage.mLayers,
                    parentBG->mImage.mLayers,
                    &nsStyleImageLayers::Layer::mImage,
                    initialImage, parentBG->mImage.mImageCount,
                    bg->mImage.mImageCount,
                    maxItemCount, rebuild, conditions);

  // background-repeat: enum, inherit, initial [pair list]
  nsStyleImageLayers::Repeat initialRepeat;
  initialRepeat.SetInitialValues();
  SetImageLayerPairList(aContext, *aRuleData->ValueForBackgroundRepeat(),
                        bg->mImage.mLayers,
                        parentBG->mImage.mLayers,
                        &nsStyleImageLayers::Layer::mRepeat,
                        initialRepeat, parentBG->mImage.mRepeatCount,
                        bg->mImage.mRepeatCount, maxItemCount, rebuild,
                        conditions);

  // background-attachment: enum, inherit, initial [list]
  SetImageLayerList(aContext, *aRuleData->ValueForBackgroundAttachment(),
                    bg->mImage.mLayers, parentBG->mImage.mLayers,
                    &nsStyleImageLayers::Layer::mAttachment,
                    uint8_t(NS_STYLE_IMAGELAYER_ATTACHMENT_SCROLL),
                    parentBG->mImage.mAttachmentCount,
                    bg->mImage.mAttachmentCount, maxItemCount, rebuild,
                    conditions);

  // background-clip: enum, inherit, initial [list]
  SetImageLayerList(aContext, *aRuleData->ValueForBackgroundClip(),
                    bg->mImage.mLayers,
                    parentBG->mImage.mLayers,
                    &nsStyleImageLayers::Layer::mClip,
                    uint8_t(NS_STYLE_IMAGELAYER_CLIP_BORDER),
                    parentBG->mImage.mClipCount,
                    bg->mImage.mClipCount, maxItemCount, rebuild, conditions);

  // background-blend-mode: enum, inherit, initial [list]
  SetImageLayerList(aContext, *aRuleData->ValueForBackgroundBlendMode(),
                    bg->mImage.mLayers,
                    parentBG->mImage.mLayers,
                    &nsStyleImageLayers::Layer::mBlendMode,
                    uint8_t(NS_STYLE_BLEND_NORMAL),
                    parentBG->mImage.mBlendModeCount,
                    bg->mImage.mBlendModeCount, maxItemCount, rebuild,
                    conditions);

  // background-origin: enum, inherit, initial [list]
  SetImageLayerList(aContext, *aRuleData->ValueForBackgroundOrigin(),
                    bg->mImage.mLayers,
                    parentBG->mImage.mLayers,
                    &nsStyleImageLayers::Layer::mOrigin,
                    uint8_t(NS_STYLE_IMAGELAYER_ORIGIN_PADDING),
                    parentBG->mImage.mOriginCount,
                    bg->mImage.mOriginCount, maxItemCount, rebuild,
                    conditions);

  // background-position-x/y: enum, length, percent (flags), inherit [list]
  Position::Coord initialPositionCoord;
  initialPositionCoord.mPercent = 0.0f;
  initialPositionCoord.mLength = 0;
  initialPositionCoord.mHasPercent = true;

  SetImageLayerPositionCoordList(
                    aContext, *aRuleData->ValueForBackgroundPositionX(),
                    bg->mImage.mLayers,
                    parentBG->mImage.mLayers,
                    &Position::mXPosition,
                    initialPositionCoord, parentBG->mImage.mPositionXCount,
                    bg->mImage.mPositionXCount, maxItemCount, rebuild,
                    conditions);
  SetImageLayerPositionCoordList(
                    aContext, *aRuleData->ValueForBackgroundPositionY(),
                    bg->mImage.mLayers,
                    parentBG->mImage.mLayers,
                    &Position::mYPosition,
                    initialPositionCoord, parentBG->mImage.mPositionYCount,
                    bg->mImage.mPositionYCount, maxItemCount, rebuild,
                    conditions);

  // background-size: enum, length, auto, inherit, initial [pair list]
  nsStyleImageLayers::Size initialSize;
  initialSize.SetInitialValues();
  SetImageLayerPairList(aContext, *aRuleData->ValueForBackgroundSize(),
                        bg->mImage.mLayers,
                        parentBG->mImage.mLayers,
                        &nsStyleImageLayers::Layer::mSize,
                        initialSize, parentBG->mImage.mSizeCount,
                        bg->mImage.mSizeCount, maxItemCount, rebuild,
                        conditions);

  if (rebuild) {
    FillAllBackgroundLists(bg->mImage, maxItemCount);
  }

  COMPUTE_END_RESET(Background, bg)
}

const void*
nsRuleNode::ComputeMarginData(void* aStartStruct,
                              const nsRuleData* aRuleData,
                              nsStyleContext* aContext,
                              nsRuleNode* aHighestNode,
                              const RuleDetail aRuleDetail,
                              const RuleNodeCacheConditions aConditions)
{
  COMPUTE_START_RESET(Margin, margin, parentMargin)

  // margin: length, percent, calc, inherit
  const nsCSSPropertyID* subprops =
    nsCSSProps::SubpropertyEntryFor(eCSSProperty_margin);
  nsStyleCoord coord;
  NS_FOR_CSS_SIDES(side) {
    nsStyleCoord parentCoord = parentMargin->mMargin.Get(side);
    if (SetCoord(*aRuleData->ValueFor(subprops[side]),
                 coord, parentCoord,
                 SETCOORD_LPAH | SETCOORD_INITIAL_ZERO | SETCOORD_STORE_CALC |
                   SETCOORD_UNSET_INITIAL,
                 aContext, mPresContext, conditions)) {
      margin->mMargin.Set(side, coord);
    }
  }

  COMPUTE_END_RESET(Margin, margin)
}

static void
SetBorderImageRect(const nsCSSValue& aValue,
                   /** outparam */ nsCSSRect& aRect)
{
  switch (aValue.GetUnit()) {
  case eCSSUnit_Null:
    aRect.Reset();
    break;
  case eCSSUnit_Rect:
    aRect = aValue.GetRectValue();
    break;
  case eCSSUnit_Inherit:
  case eCSSUnit_Initial:
  case eCSSUnit_Unset:
    aRect.SetAllSidesTo(aValue);
    break;
  default:
    NS_ASSERTION(false, "Unexpected border image value for rect.");
  }
}

static void
SetBorderImagePair(const nsCSSValue& aValue,
                   /** outparam */ nsCSSValuePair& aPair)
{
  switch (aValue.GetUnit()) {
  case eCSSUnit_Null:
    aPair.Reset();
    break;
  case eCSSUnit_Pair:
    aPair = aValue.GetPairValue();
    break;
  case eCSSUnit_Inherit:
  case eCSSUnit_Initial:
  case eCSSUnit_Unset:
    aPair.SetBothValuesTo(aValue);
    break;
  default:
    NS_ASSERTION(false, "Unexpected border image value for pair.");
  }
}

static void
SetBorderImageSlice(const nsCSSValue& aValue,
                    /** outparam */ nsCSSValue& aSlice,
                    /** outparam */ nsCSSValue& aFill)
{
  const nsCSSValueList* valueList;
  switch (aValue.GetUnit()) {
  case eCSSUnit_Null:
    aSlice.Reset();
    aFill.Reset();
    break;
  case eCSSUnit_List:
    // Get slice dimensions.
    valueList = aValue.GetListValue();
    aSlice = valueList->mValue;

    // Get "fill" keyword.
    valueList = valueList->mNext;
    if (valueList) {
      aFill = valueList->mValue;
    } else {
      aFill.SetInitialValue();
    }
    break;
  case eCSSUnit_Inherit:
  case eCSSUnit_Initial:
  case eCSSUnit_Unset:
    aSlice = aValue;
    aFill = aValue;
    break;
  default:
    NS_ASSERTION(false, "Unexpected border image value for pair.");
  }
}

const void*
nsRuleNode::ComputeBorderData(void* aStartStruct,
                              const nsRuleData* aRuleData,
                              nsStyleContext* aContext,
                              nsRuleNode* aHighestNode,
                              const RuleDetail aRuleDetail,
                              const RuleNodeCacheConditions aConditions)
{
  COMPUTE_START_RESET(Border, border, parentBorder)

  // box-decoration-break: enum, inherit, initial
  SetValue(*aRuleData->ValueForBoxDecorationBreak(),
           border->mBoxDecorationBreak, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentBorder->mBoxDecorationBreak,
           StyleBoxDecorationBreak::Slice);

  // border-width, border-*-width: length, enum, inherit
  nsStyleCoord coord;
  {
    const nsCSSPropertyID* subprops =
      nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_width);
    NS_FOR_CSS_SIDES(side) {
      const nsCSSValue& value = *aRuleData->ValueFor(subprops[side]);
      NS_ASSERTION(eCSSUnit_Percent != value.GetUnit(),
                   "Percentage borders not implemented yet "
                   "If implementing, make sure to fix all consumers of "
                   "nsStyleBorder, the IsPercentageAwareChild method, "
                   "the nsAbsoluteContainingBlock::FrameDependsOnContainer "
                   "method, the "
                   "nsLineLayout::IsPercentageAwareReplacedElement method "
                   "and probably some other places");
      if (eCSSUnit_Enumerated == value.GetUnit()) {
        NS_ASSERTION(value.GetIntValue() == NS_STYLE_BORDER_WIDTH_THIN ||
                     value.GetIntValue() == NS_STYLE_BORDER_WIDTH_MEDIUM ||
                     value.GetIntValue() == NS_STYLE_BORDER_WIDTH_THICK,
                     "Unexpected enum value");
        border->SetBorderWidth(side,
                               (mPresContext->GetBorderWidthTable())[value.GetIntValue()]);
      }
      // OK to pass bad aParentCoord since we're not passing SETCOORD_INHERIT
      else if (SetCoord(value, coord, nsStyleCoord(),
                        SETCOORD_LENGTH | SETCOORD_CALC_LENGTH_ONLY,
                        aContext, mPresContext, conditions)) {
        NS_ASSERTION(coord.GetUnit() == eStyleUnit_Coord, "unexpected unit");
        // clamp negative calc() to 0.
        border->SetBorderWidth(side, std::max(coord.GetCoordValue(), 0));
      }
      else if (eCSSUnit_Inherit == value.GetUnit()) {
        conditions.SetUncacheable();
        border->SetBorderWidth(side,
                               parentBorder->GetComputedBorder().Side(side));
      }
      else if (eCSSUnit_Initial == value.GetUnit() ||
               eCSSUnit_Unset == value.GetUnit()) {
        border->SetBorderWidth(side,
          (mPresContext->GetBorderWidthTable())[NS_STYLE_BORDER_WIDTH_MEDIUM]);
      }
      else {
        NS_ASSERTION(eCSSUnit_Null == value.GetUnit(),
                     "missing case handling border width");
      }
    }
  }

  // border-style, border-*-style: enum, inherit
  {
    const nsCSSPropertyID* subprops =
      nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_style);
    NS_FOR_CSS_SIDES(side) {
      const nsCSSValue& value = *aRuleData->ValueFor(subprops[side]);
      nsCSSUnit unit = value.GetUnit();
      MOZ_ASSERT(eCSSUnit_None != unit,
                 "'none' should be handled as enumerated value");
      if (eCSSUnit_Enumerated == unit) {
        border->SetBorderStyle(side, value.GetIntValue());
      }
      else if (eCSSUnit_Initial == unit ||
               eCSSUnit_Unset == unit) {
        border->SetBorderStyle(side, NS_STYLE_BORDER_STYLE_NONE);
      }
      else if (eCSSUnit_Inherit == unit) {
        conditions.SetUncacheable();
        border->SetBorderStyle(side, parentBorder->GetBorderStyle(side));
      }
    }
  }

  // -moz-border-*-colors: color, string, enum, none, inherit/initial
  nscolor borderColor;
  nscolor unused = NS_RGB(0,0,0);

  static const nsCSSPropertyID borderColorsProps[] = {
    eCSSProperty_border_top_colors,
    eCSSProperty_border_right_colors,
    eCSSProperty_border_bottom_colors,
    eCSSProperty_border_left_colors
  };

  NS_FOR_CSS_SIDES(side) {
    const nsCSSValue& value = *aRuleData->ValueFor(borderColorsProps[side]);
    switch (value.GetUnit()) {
    case eCSSUnit_Null:
      break;

    case eCSSUnit_Initial:
    case eCSSUnit_Unset:
    case eCSSUnit_None:
      border->ClearBorderColors(side);
      break;

    case eCSSUnit_Inherit: {
      conditions.SetUncacheable();
      border->ClearBorderColors(side);
      if (parentContext) {
        nsBorderColors *parentColors;
        parentBorder->GetCompositeColors(side, &parentColors);
        if (parentColors) {
          border->EnsureBorderColors();
          border->mBorderColors[side] = parentColors->Clone();
        }
      }
      break;
    }

    case eCSSUnit_List:
    case eCSSUnit_ListDep: {
      // Some composite border color information has been specified for this
      // border side.
      border->EnsureBorderColors();
      border->ClearBorderColors(side);
      const nsCSSValueList* list = value.GetListValue();
      while (list) {
        if (SetColor(list->mValue, unused, mPresContext,
                     aContext, borderColor, conditions))
          border->AppendBorderColor(side, borderColor);
        else {
          NS_NOTREACHED("unexpected item in -moz-border-*-colors list");
        }
        list = list->mNext;
      }
      break;
    }

    default:
      MOZ_ASSERT(false, "unrecognized border color unit");
    }
  }

  // border-color, border-*-color: color, string, enum, inherit
  {
    const nsCSSPropertyID* subprops =
      nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_color);
    NS_FOR_CSS_SIDES(side) {
      SetComplexColor<eUnsetInitial>(*aRuleData->ValueFor(subprops[side]),
                                     parentBorder->mBorderColor[side],
                                     StyleComplexColor::CurrentColor(),
                                     mPresContext,
                                     border->mBorderColor[side], conditions);
    }
  }

  // border-radius: length, percent, inherit
  {
    const nsCSSPropertyID* subprops =
      nsCSSProps::SubpropertyEntryFor(eCSSProperty_border_radius);
    NS_FOR_CSS_FULL_CORNERS(corner) {
      int cx = NS_FULL_TO_HALF_CORNER(corner, false);
      int cy = NS_FULL_TO_HALF_CORNER(corner, true);
      const nsCSSValue& radius = *aRuleData->ValueFor(subprops[corner]);
      nsStyleCoord parentX = parentBorder->mBorderRadius.Get(cx);
      nsStyleCoord parentY = parentBorder->mBorderRadius.Get(cy);
      nsStyleCoord coordX, coordY;

      if (SetPairCoords(radius, coordX, coordY, parentX, parentY,
                        SETCOORD_LPH | SETCOORD_INITIAL_ZERO |
                          SETCOORD_STORE_CALC | SETCOORD_UNSET_INITIAL,
                        aContext, mPresContext, conditions)) {
        border->mBorderRadius.Set(cx, coordX);
        border->mBorderRadius.Set(cy, coordY);
      }
    }
  }

  // float-edge: enum, inherit, initial
  SetValue(*aRuleData->ValueForFloatEdge(),
           border->mFloatEdge, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentBorder->mFloatEdge,
           StyleFloatEdge::ContentBox);

  // border-image-source
  const nsCSSValue* borderImageSource = aRuleData->ValueForBorderImageSource();
  if (borderImageSource->GetUnit() == eCSSUnit_Inherit) {
    conditions.SetUncacheable();
    border->mBorderImageSource = parentBorder->mBorderImageSource;
  } else {
    SetStyleImage(aContext,
                  *borderImageSource,
                  border->mBorderImageSource,
                  conditions);
  }

  nsCSSValue borderImageSliceValue;
  nsCSSValue borderImageSliceFill;
  SetBorderImageSlice(*aRuleData->ValueForBorderImageSlice(),
                      borderImageSliceValue, borderImageSliceFill);

  // border-image-slice: fill
  SetValue(borderImageSliceFill,
           border->mBorderImageFill,
           conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentBorder->mBorderImageFill,
           NS_STYLE_BORDER_IMAGE_SLICE_NOFILL);

  nsCSSRect borderImageSlice;
  SetBorderImageRect(borderImageSliceValue, borderImageSlice);

  nsCSSRect borderImageWidth;
  SetBorderImageRect(*aRuleData->ValueForBorderImageWidth(),
                     borderImageWidth);

  nsCSSRect borderImageOutset;
  SetBorderImageRect(*aRuleData->ValueForBorderImageOutset(),
                     borderImageOutset);

  NS_FOR_CSS_SIDES (side) {
    // border-image-slice
    if (SetCoord(borderImageSlice.*(nsCSSRect::sides[side]), coord,
                 parentBorder->mBorderImageSlice.Get(side),
                 SETCOORD_FACTOR | SETCOORD_PERCENT |
                   SETCOORD_INHERIT | SETCOORD_INITIAL_HUNDRED_PCT |
                   SETCOORD_UNSET_INITIAL,
                 aContext, mPresContext, conditions)) {
      border->mBorderImageSlice.Set(side, coord);
    }

    // border-image-width
    // 'auto' here means "same as slice"
    if (SetCoord(borderImageWidth.*(nsCSSRect::sides[side]), coord,
                 parentBorder->mBorderImageWidth.Get(side),
                 SETCOORD_LPAH | SETCOORD_FACTOR | SETCOORD_INITIAL_FACTOR_ONE |
                   SETCOORD_UNSET_INITIAL,
                 aContext, mPresContext, conditions)) {
      border->mBorderImageWidth.Set(side, coord);
    }

    // border-image-outset
    if (SetCoord(borderImageOutset.*(nsCSSRect::sides[side]), coord,
                 parentBorder->mBorderImageOutset.Get(side),
                 SETCOORD_LENGTH | SETCOORD_FACTOR |
                   SETCOORD_INHERIT | SETCOORD_INITIAL_FACTOR_ZERO |
                   SETCOORD_UNSET_INITIAL,
                 aContext, mPresContext, conditions)) {
      border->mBorderImageOutset.Set(side, coord);
    }
  }

  // border-image-repeat
  nsCSSValuePair borderImageRepeat;
  SetBorderImagePair(*aRuleData->ValueForBorderImageRepeat(),
                     borderImageRepeat);

  SetValue(borderImageRepeat.mXValue,
           border->mBorderImageRepeatH,
           conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentBorder->mBorderImageRepeatH,
           NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH);

  SetValue(borderImageRepeat.mYValue,
           border->mBorderImageRepeatV,
           conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentBorder->mBorderImageRepeatV,
           NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH);

  COMPUTE_END_RESET(Border, border)
}

const void*
nsRuleNode::ComputePaddingData(void* aStartStruct,
                               const nsRuleData* aRuleData,
                               nsStyleContext* aContext,
                               nsRuleNode* aHighestNode,
                               const RuleDetail aRuleDetail,
                               const RuleNodeCacheConditions aConditions)
{
  COMPUTE_START_RESET(Padding, padding, parentPadding)

  // padding: length, percent, calc, inherit
  const nsCSSPropertyID* subprops =
    nsCSSProps::SubpropertyEntryFor(eCSSProperty_padding);
  nsStyleCoord coord;
  NS_FOR_CSS_SIDES(side) {
    nsStyleCoord parentCoord = parentPadding->mPadding.Get(side);
    if (SetCoord(*aRuleData->ValueFor(subprops[side]),
                 coord, parentCoord,
                 SETCOORD_LPH | SETCOORD_INITIAL_ZERO | SETCOORD_STORE_CALC |
                   SETCOORD_UNSET_INITIAL,
                 aContext, mPresContext, conditions)) {
      padding->mPadding.Set(side, coord);
    }
  }

  COMPUTE_END_RESET(Padding, padding)
}

const void*
nsRuleNode::ComputeOutlineData(void* aStartStruct,
                               const nsRuleData* aRuleData,
                               nsStyleContext* aContext,
                               nsRuleNode* aHighestNode,
                               const RuleDetail aRuleDetail,
                               const RuleNodeCacheConditions aConditions)
{
  COMPUTE_START_RESET(Outline, outline, parentOutline)

  // outline-width: length, enum, inherit
  const nsCSSValue* outlineWidthValue = aRuleData->ValueForOutlineWidth();
  if (eCSSUnit_Initial == outlineWidthValue->GetUnit() ||
      eCSSUnit_Unset == outlineWidthValue->GetUnit()) {
    outline->mOutlineWidth =
      nsStyleCoord(NS_STYLE_BORDER_WIDTH_MEDIUM, eStyleUnit_Enumerated);
  }
  else {
    SetCoord(*outlineWidthValue, outline->mOutlineWidth,
             parentOutline->mOutlineWidth,
             SETCOORD_LEH | SETCOORD_CALC_LENGTH_ONLY, aContext,
             mPresContext, conditions);
  }

  // outline-offset: length, inherit
  nsStyleCoord tempCoord;
  const nsCSSValue* outlineOffsetValue = aRuleData->ValueForOutlineOffset();
  if (SetCoord(*outlineOffsetValue, tempCoord,
               nsStyleCoord(parentOutline->mOutlineOffset,
                            nsStyleCoord::CoordConstructor),
               SETCOORD_LH | SETCOORD_INITIAL_ZERO | SETCOORD_CALC_LENGTH_ONLY |
                 SETCOORD_UNSET_INITIAL,
               aContext, mPresContext, conditions)) {
    outline->mOutlineOffset = tempCoord.GetCoordValue();
  } else {
    NS_ASSERTION(outlineOffsetValue->GetUnit() == eCSSUnit_Null,
                 "unexpected unit");
  }

  // outline-color: color, string, enum, inherit
  SetComplexColor<eUnsetInitial>(*aRuleData->ValueForOutlineColor(),
                                 parentOutline->mOutlineColor,
                                 StyleComplexColor::CurrentColor(),
                                 mPresContext,
                                 outline->mOutlineColor, conditions);

  // -moz-outline-radius: length, percent, inherit
  {
    const nsCSSPropertyID* subprops =
      nsCSSProps::SubpropertyEntryFor(eCSSProperty__moz_outline_radius);
    NS_FOR_CSS_FULL_CORNERS(corner) {
      int cx = NS_FULL_TO_HALF_CORNER(corner, false);
      int cy = NS_FULL_TO_HALF_CORNER(corner, true);
      const nsCSSValue& radius = *aRuleData->ValueFor(subprops[corner]);
      nsStyleCoord parentX = parentOutline->mOutlineRadius.Get(cx);
      nsStyleCoord parentY = parentOutline->mOutlineRadius.Get(cy);
      nsStyleCoord coordX, coordY;

      if (SetPairCoords(radius, coordX, coordY, parentX, parentY,
                        SETCOORD_LPH | SETCOORD_INITIAL_ZERO |
                          SETCOORD_STORE_CALC | SETCOORD_UNSET_INITIAL,
                        aContext, mPresContext, conditions)) {
        outline->mOutlineRadius.Set(cx, coordX);
        outline->mOutlineRadius.Set(cy, coordY);
      }
    }
  }

  // outline-style: enum, inherit, initial
  // cannot use SetValue because of SetOutlineStyle
  const nsCSSValue* outlineStyleValue = aRuleData->ValueForOutlineStyle();
  nsCSSUnit unit = outlineStyleValue->GetUnit();
  MOZ_ASSERT(eCSSUnit_None != unit && eCSSUnit_Auto != unit,
             "'none' and 'auto' should be handled as enumerated values");
  if (eCSSUnit_Enumerated == unit) {
    outline->mOutlineStyle = outlineStyleValue->GetIntValue();
  } else if (eCSSUnit_Initial == unit ||
             eCSSUnit_Unset == unit) {
    outline->mOutlineStyle = NS_STYLE_BORDER_STYLE_NONE;
  } else if (eCSSUnit_Inherit == unit) {
    conditions.SetUncacheable();
    outline->mOutlineStyle = parentOutline->mOutlineStyle;
  }

  outline->RecalcData();
  COMPUTE_END_RESET(Outline, outline)
}

const void*
nsRuleNode::ComputeListData(void* aStartStruct,
                            const nsRuleData* aRuleData,
                            nsStyleContext* aContext,
                            nsRuleNode* aHighestNode,
                            const RuleDetail aRuleDetail,
                            const RuleNodeCacheConditions aConditions)
{
  COMPUTE_START_INHERITED(List, list, parentList)

  // quotes: inherit, initial, none, [string string]+
  const nsCSSValue* quotesValue = aRuleData->ValueForQuotes();
  switch (quotesValue->GetUnit()) {
  case eCSSUnit_Null:
    break;
  case eCSSUnit_Inherit:
  case eCSSUnit_Unset:
    conditions.SetUncacheable();
    list->SetQuotesInherit(parentList);
    break;
  case eCSSUnit_Initial:
    list->SetQuotesInitial();
    break;
  case eCSSUnit_None:
    list->SetQuotesNone();
    break;
  case eCSSUnit_PairList:
  case eCSSUnit_PairListDep: {
    const nsCSSValuePairList* ourQuotes = quotesValue->GetPairListValue();

    nsStyleQuoteValues::QuotePairArray quotePairs;
    quotePairs.SetLength(ListLength(ourQuotes));

    size_t index = 0;
    nsAutoString buffer;
    while (ourQuotes) {
      MOZ_ASSERT(ourQuotes->mXValue.GetUnit() == eCSSUnit_String &&
                 ourQuotes->mYValue.GetUnit() == eCSSUnit_String,
                 "improper list contents for quotes");
      quotePairs[index].first  = ourQuotes->mXValue.GetStringValue(buffer);
      quotePairs[index].second = ourQuotes->mYValue.GetStringValue(buffer);
      ++index;
      ourQuotes = ourQuotes->mNext;
    }
    list->SetQuotes(Move(quotePairs));
    break;
  }
  default:
    MOZ_ASSERT(false, "unexpected value unit");
  }

  // list-style-type: string, none, inherit, initial
  const nsCSSValue* typeValue = aRuleData->ValueForListStyleType();
  switch (typeValue->GetUnit()) {
    case eCSSUnit_Unset:
    case eCSSUnit_Inherit: {
      conditions.SetUncacheable();
      list->SetCounterStyle(parentList->GetCounterStyle());
      break;
    }
    case eCSSUnit_Initial:
      list->SetListStyleType(NS_LITERAL_STRING("disc"), mPresContext);
      break;
    case eCSSUnit_Ident: {
      nsString typeIdent;
      typeValue->GetStringValue(typeIdent);
      list->SetListStyleType(typeIdent, mPresContext);
      break;
    }
    case eCSSUnit_String: {
      nsString str;
      typeValue->GetStringValue(str);
      list->SetCounterStyle(new AnonymousCounterStyle(str));
      break;
    }
    case eCSSUnit_Enumerated: {
      // For compatibility with html attribute map.
      // This branch should never be called for value from CSS.
      int32_t intValue = typeValue->GetIntValue();
      nsAutoString name;
      switch (intValue) {
        case NS_STYLE_LIST_STYLE_LOWER_ROMAN:
          name.AssignLiteral(u"lower-roman");
          break;
        case NS_STYLE_LIST_STYLE_UPPER_ROMAN:
          name.AssignLiteral(u"upper-roman");
          break;
        case NS_STYLE_LIST_STYLE_LOWER_ALPHA:
          name.AssignLiteral(u"lower-alpha");
          break;
        case NS_STYLE_LIST_STYLE_UPPER_ALPHA:
          name.AssignLiteral(u"upper-alpha");
          break;
        default:
          CopyASCIItoUTF16(nsCSSProps::ValueToKeyword(
                  intValue, nsCSSProps::kListStyleKTable), name);
          break;
      }
      list->SetListStyleType(name, mPresContext);
      break;
    }
    case eCSSUnit_Symbols:
      list->SetCounterStyle(new AnonymousCounterStyle(typeValue->GetArrayValue()));
      break;
    case eCSSUnit_Null:
      break;
    default:
      NS_NOTREACHED("Unexpected value unit");
  }

  // list-style-image: url, none, inherit
  const nsCSSValue* imageValue = aRuleData->ValueForListStyleImage();
  if (eCSSUnit_Image == imageValue->GetUnit()) {
    SetStyleImageRequest([&](nsStyleImageRequest* req) {
      list->mListStyleImage = req;
    }, mPresContext, *imageValue, nsStyleImageRequest::Mode(0));
  }
  else if (eCSSUnit_None == imageValue->GetUnit() ||
           eCSSUnit_Initial == imageValue->GetUnit()) {
    list->mListStyleImage = nullptr;
  }
  else if (eCSSUnit_Inherit == imageValue->GetUnit() ||
           eCSSUnit_Unset == imageValue->GetUnit()) {
    conditions.SetUncacheable();
    list->mListStyleImage = parentList->mListStyleImage;
  }

  // list-style-position: enum, inherit, initial
  SetValue(*aRuleData->ValueForListStylePosition(),
           list->mListStylePosition, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentList->mListStylePosition,
           NS_STYLE_LIST_STYLE_POSITION_OUTSIDE);

  // image region property: length, auto, inherit
  const nsCSSValue* imageRegionValue = aRuleData->ValueForImageRegion();
  switch (imageRegionValue->GetUnit()) {
  case eCSSUnit_Inherit:
  case eCSSUnit_Unset:
    conditions.SetUncacheable();
    list->mImageRegion = parentList->mImageRegion;
    break;

  case eCSSUnit_Initial:
  case eCSSUnit_Auto:
    list->mImageRegion.SetRect(0,0,0,0);
    break;

  case eCSSUnit_Null:
    break;

  case eCSSUnit_Rect: {
    const nsCSSRect& rgnRect = imageRegionValue->GetRectValue();

    if (rgnRect.mTop.GetUnit() == eCSSUnit_Auto)
      list->mImageRegion.y = 0;
    else if (rgnRect.mTop.IsLengthUnit())
      list->mImageRegion.y =
        CalcLength(rgnRect.mTop, aContext, mPresContext, conditions);

    if (rgnRect.mBottom.GetUnit() == eCSSUnit_Auto)
      list->mImageRegion.height = 0;
    else if (rgnRect.mBottom.IsLengthUnit())
      list->mImageRegion.height =
        CalcLength(rgnRect.mBottom, aContext, mPresContext,
                   conditions) - list->mImageRegion.y;

    if (rgnRect.mLeft.GetUnit() == eCSSUnit_Auto)
      list->mImageRegion.x = 0;
    else if (rgnRect.mLeft.IsLengthUnit())
      list->mImageRegion.x =
        CalcLength(rgnRect.mLeft, aContext, mPresContext, conditions);

    if (rgnRect.mRight.GetUnit() == eCSSUnit_Auto)
      list->mImageRegion.width = 0;
    else if (rgnRect.mRight.IsLengthUnit())
      list->mImageRegion.width =
        CalcLength(rgnRect.mRight, aContext, mPresContext,
                   conditions) - list->mImageRegion.x;
    break;
  }

  default:
    MOZ_ASSERT(false, "unrecognized image-region unit");
  }

  COMPUTE_END_INHERITED(List, list)
}

static void
SetGridTrackBreadth(const nsCSSValue& aValue,
                    nsStyleCoord& aResult,
                    nsStyleContext* aStyleContext,
                    nsPresContext* aPresContext,
                    RuleNodeCacheConditions& aConditions)
{
  nsCSSUnit unit = aValue.GetUnit();
  if (unit == eCSSUnit_FlexFraction) {
    aResult.SetFlexFractionValue(aValue.GetFloatValue());
  } else if (unit == eCSSUnit_Auto) {
    aResult.SetAutoValue();
  } else if (unit == eCSSUnit_None) {
    // For fit-content().
    aResult.SetNoneValue();
  } else {
    MOZ_ASSERT(unit != eCSSUnit_Inherit && unit != eCSSUnit_Unset,
               "Unexpected value that would use dummyParentCoord");
    const nsStyleCoord dummyParentCoord;
    DebugOnly<bool> stored =
      SetCoord(aValue, aResult, dummyParentCoord,
               SETCOORD_LPE | SETCOORD_STORE_CALC,
               aStyleContext, aPresContext, aConditions);
    MOZ_ASSERT(stored, "invalid <track-size> value");
  }
}

static void
SetGridTrackSize(const nsCSSValue& aValue,
                 nsStyleCoord& aResultMin,
                 nsStyleCoord& aResultMax,
                 nsStyleContext* aStyleContext,
                 nsPresContext* aPresContext,
                 RuleNodeCacheConditions& aConditions)
{
  if (aValue.GetUnit() == eCSSUnit_Function) {
    nsCSSValue::Array* func = aValue.GetArrayValue();
    auto funcName = func->Item(0).GetKeywordValue();
    if (funcName == eCSSKeyword_minmax) {
      SetGridTrackBreadth(func->Item(1), aResultMin,
                          aStyleContext, aPresContext, aConditions);
      SetGridTrackBreadth(func->Item(2), aResultMax,
                          aStyleContext, aPresContext, aConditions);
    } else if (funcName == eCSSKeyword_fit_content) {
      // We represent fit-content(L) as 'none' min-sizing and L max-sizing.
      SetGridTrackBreadth(nsCSSValue(eCSSUnit_None), aResultMin,
                          aStyleContext, aPresContext, aConditions);
      SetGridTrackBreadth(func->Item(1), aResultMax,
                          aStyleContext, aPresContext, aConditions);
    } else {
      NS_ERROR("Expected minmax() or fit-content(), got another function name");
    }
  } else {
    // A single <track-breadth>,
    // specifies identical min and max sizing functions.
    SetGridTrackBreadth(aValue, aResultMin,
                        aStyleContext, aPresContext, aConditions);
    aResultMax = aResultMin;
  }
}

static void
SetGridAutoColumnsRows(const nsCSSValue& aValue,
                       nsStyleCoord& aResultMin,
                       nsStyleCoord& aResultMax,
                       const nsStyleCoord& aParentValueMin,
                       const nsStyleCoord& aParentValueMax,
                       nsStyleContext* aStyleContext,
                       nsPresContext* aPresContext,
                       RuleNodeCacheConditions& aConditions)

{
  switch (aValue.GetUnit()) {
  case eCSSUnit_Null:
    break;

  case eCSSUnit_Inherit:
    aConditions.SetUncacheable();
    aResultMin = aParentValueMin;
    aResultMax = aParentValueMax;
    break;

  case eCSSUnit_Initial:
  case eCSSUnit_Unset:
    // The initial value is 'auto',
    // which computes to 'minmax(auto, auto)'.
    // (Explicitly-specified 'auto' values are handled in SetGridTrackSize.)
    aResultMin.SetAutoValue();
    aResultMax.SetAutoValue();
    break;

  default:
    SetGridTrackSize(aValue, aResultMin, aResultMax,
                     aStyleContext, aPresContext, aConditions);
  }
}

static void
AppendGridLineNames(const nsCSSValue& aValue,
                    nsTArray<nsString>& aNameList)
{
  // Compute a <line-names> value
  // Null unit means empty list, nothing more to do.
  if (aValue.GetUnit() != eCSSUnit_Null) {
    const nsCSSValueList* item = aValue.GetListValue();
    do {
      nsString* name = aNameList.AppendElement();
      item->mValue.GetStringValue(*name);
      item = item->mNext;
    } while (item);
  }
}

static void
SetGridTrackList(const nsCSSValue& aValue,
                 nsStyleGridTemplate& aResult,
                 const nsStyleGridTemplate& aParentValue,
                 nsStyleContext* aStyleContext,
                 nsPresContext* aPresContext,
                 RuleNodeCacheConditions& aConditions)

{
  switch (aValue.GetUnit()) {
  case eCSSUnit_Null:
    break;

  case eCSSUnit_Inherit:
    aConditions.SetUncacheable();
    aResult.mIsSubgrid = aParentValue.mIsSubgrid;
    aResult.mLineNameLists = aParentValue.mLineNameLists;
    aResult.mMinTrackSizingFunctions = aParentValue.mMinTrackSizingFunctions;
    aResult.mMaxTrackSizingFunctions = aParentValue.mMaxTrackSizingFunctions;
    aResult.mRepeatAutoLineNameListBefore = aParentValue.mRepeatAutoLineNameListBefore;
    aResult.mRepeatAutoLineNameListAfter = aParentValue.mRepeatAutoLineNameListAfter;
    aResult.mRepeatAutoIndex = aParentValue.mRepeatAutoIndex;
    aResult.mIsAutoFill = aParentValue.mIsAutoFill;
    break;

  case eCSSUnit_Initial:
  case eCSSUnit_Unset:
  case eCSSUnit_None:
    aResult.mIsSubgrid = false;
    aResult.mLineNameLists.Clear();
    aResult.mMinTrackSizingFunctions.Clear();
    aResult.mMaxTrackSizingFunctions.Clear();
    aResult.mRepeatAutoLineNameListBefore.Clear();
    aResult.mRepeatAutoLineNameListAfter.Clear();
    aResult.mRepeatAutoIndex = -1;
    aResult.mIsAutoFill = false;
    break;

  default:
    aResult.mLineNameLists.Clear();
    aResult.mMinTrackSizingFunctions.Clear();
    aResult.mMaxTrackSizingFunctions.Clear();
    aResult.mRepeatAutoLineNameListBefore.Clear();
    aResult.mRepeatAutoLineNameListAfter.Clear();
    aResult.mRepeatAutoIndex = -1;
    aResult.mIsAutoFill = false;
    const nsCSSValueList* item = aValue.GetListValue();
    if (item->mValue.GetUnit() == eCSSUnit_Enumerated &&
        item->mValue.GetIntValue() == NS_STYLE_GRID_TEMPLATE_SUBGRID) {
      // subgrid <line-name-list>?
      aResult.mIsSubgrid = true;
      item = item->mNext;
      for (int32_t i = 0; item && i < nsStyleGridLine::kMaxLine; ++i) {
        if (item->mValue.GetUnit() == eCSSUnit_Pair) {
          // This is a 'auto-fill' <name-repeat> expression.
          const nsCSSValuePair& pair = item->mValue.GetPairValue();
          MOZ_ASSERT(aResult.mRepeatAutoIndex == -1,
                     "can only have one <name-repeat> with auto-fill");
          aResult.mRepeatAutoIndex = i;
          aResult.mIsAutoFill = true;
          MOZ_ASSERT(pair.mXValue.GetIntValue() == NS_STYLE_GRID_REPEAT_AUTO_FILL,
                     "unexpected repeat() enum value for subgrid");
          const nsCSSValueList* list = pair.mYValue.GetListValue();
          AppendGridLineNames(list->mValue, aResult.mRepeatAutoLineNameListBefore);
        } else {
          AppendGridLineNames(item->mValue,
                              *aResult.mLineNameLists.AppendElement());
        }
        item = item->mNext;
      }
    } else {
      // <track-list>
      // The list is expected to have odd number of items, at least 3
      // starting with a <line-names> (sub list of identifiers),
      // and alternating between that and <track-size>.
      aResult.mIsSubgrid = false;
      for (int32_t line = 1;  ; ++line) {
        AppendGridLineNames(item->mValue,
                            *aResult.mLineNameLists.AppendElement());
        item = item->mNext;

        if (!item || line == nsStyleGridLine::kMaxLine) {
          break;
        }

        if (item->mValue.GetUnit() == eCSSUnit_Pair) {
          // This is a 'auto-fill' / 'auto-fit' <auto-repeat> expression.
          const nsCSSValuePair& pair = item->mValue.GetPairValue();
          MOZ_ASSERT(aResult.mRepeatAutoIndex == -1,
                     "can only have one <auto-repeat>");
          aResult.mRepeatAutoIndex = line - 1;
          switch (pair.mXValue.GetIntValue()) {
            case NS_STYLE_GRID_REPEAT_AUTO_FILL:
              aResult.mIsAutoFill = true;
              break;
            case NS_STYLE_GRID_REPEAT_AUTO_FIT:
              aResult.mIsAutoFill = false;
              break;
            default:
              MOZ_ASSERT_UNREACHABLE("unexpected repeat() enum value");
          }
          const nsCSSValueList* list = pair.mYValue.GetListValue();
          AppendGridLineNames(list->mValue, aResult.mRepeatAutoLineNameListBefore);
          list = list->mNext;
          nsStyleCoord& min = *aResult.mMinTrackSizingFunctions.AppendElement();
          nsStyleCoord& max = *aResult.mMaxTrackSizingFunctions.AppendElement();
          SetGridTrackSize(list->mValue, min, max,
                           aStyleContext, aPresContext, aConditions);
          list = list->mNext;
          AppendGridLineNames(list->mValue, aResult.mRepeatAutoLineNameListAfter);
        } else {
          nsStyleCoord& min = *aResult.mMinTrackSizingFunctions.AppendElement();
          nsStyleCoord& max = *aResult.mMaxTrackSizingFunctions.AppendElement();
          SetGridTrackSize(item->mValue, min, max,
                           aStyleContext, aPresContext, aConditions);
        }

        item = item->mNext;
        MOZ_ASSERT(item, "Expected a eCSSUnit_List of odd length");
      }
      MOZ_ASSERT(!aResult.mMinTrackSizingFunctions.IsEmpty() &&
                 aResult.mMinTrackSizingFunctions.Length() ==
                 aResult.mMaxTrackSizingFunctions.Length() &&
                 aResult.mMinTrackSizingFunctions.Length() + 1 ==
                 aResult.mLineNameLists.Length(),
                 "Inconstistent array lengths for nsStyleGridTemplate");
    }
  }
}

static void
SetGridTemplateAreas(const nsCSSValue& aValue,
                     RefPtr<css::GridTemplateAreasValue>* aResult,
                     css::GridTemplateAreasValue* aParentValue,
                     RuleNodeCacheConditions& aConditions)
{
  switch (aValue.GetUnit()) {
  case eCSSUnit_Null:
    break;

  case eCSSUnit_Inherit:
    aConditions.SetUncacheable();
    *aResult = aParentValue;
    break;

  case eCSSUnit_Initial:
  case eCSSUnit_Unset:
  case eCSSUnit_None:
    *aResult = nullptr;
    break;

  default:
    *aResult = aValue.GetGridTemplateAreas();
  }
}

static void
SetGridLine(const nsCSSValue& aValue,
            nsStyleGridLine& aResult,
            const nsStyleGridLine& aParentValue,
            RuleNodeCacheConditions& aConditions)

{
  switch (aValue.GetUnit()) {
  case eCSSUnit_Null:
    break;

  case eCSSUnit_Inherit:
    aConditions.SetUncacheable();
    aResult = aParentValue;
    break;

  case eCSSUnit_Initial:
  case eCSSUnit_Unset:
  case eCSSUnit_Auto:
    aResult.SetAuto();
    break;

  default:
    aResult.SetAuto();  // Reset any existing value.
    const nsCSSValueList* item = aValue.GetListValue();
    do {
      if (item->mValue.GetUnit() == eCSSUnit_Enumerated) {
        aResult.mHasSpan = true;
      } else if (item->mValue.GetUnit() == eCSSUnit_Integer) {
        aResult.mInteger = clamped(item->mValue.GetIntValue(),
                                   nsStyleGridLine::kMinLine,
                                   nsStyleGridLine::kMaxLine);
      } else if (item->mValue.GetUnit() == eCSSUnit_Ident) {
        item->mValue.GetStringValue(aResult.mLineName);
      } else {
        NS_ASSERTION(false, "Unexpected unit");
      }
      item = item->mNext;
    } while (item);
    MOZ_ASSERT(!aResult.IsAuto(),
               "should have set something away from default value");
  }
}

const void*
nsRuleNode::ComputePositionData(void* aStartStruct,
                                const nsRuleData* aRuleData,
                                nsStyleContext* aContext,
                                nsRuleNode* aHighestNode,
                                const RuleDetail aRuleDetail,
                                const RuleNodeCacheConditions aConditions)
{
  COMPUTE_START_RESET(Position, pos, parentPos)

  // box offsets: length, percent, calc, auto, inherit
  static const nsCSSPropertyID offsetProps[] = {
    eCSSProperty_top,
    eCSSProperty_right,
    eCSSProperty_bottom,
    eCSSProperty_left
  };
  nsStyleCoord  coord;
  NS_FOR_CSS_SIDES(side) {
    nsStyleCoord parentCoord = parentPos->mOffset.Get(side);
    if (SetCoord(*aRuleData->ValueFor(offsetProps[side]),
                 coord, parentCoord,
                 SETCOORD_LPAH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC |
                   SETCOORD_UNSET_INITIAL,
                 aContext, mPresContext, conditions)) {
      pos->mOffset.Set(side, coord);
    }
  }

  // We allow the enumerated box size property values -moz-min-content, etc. to
  // be specified on both the {,min-,max-}width properties and the
  // {,min-,max-}height properties, regardless of the writing mode.  This is
  // because the writing mode is not determined until here, at computed value
  // time.  Since we do not support layout behavior of these keywords on the
  // block-axis properties, we turn them into unset if we find them in
  // that case.

  WritingMode wm(aContext);
  bool vertical = wm.IsVertical();

  const nsCSSValue* width = aRuleData->ValueForWidth();
  if (width->GetUnit() == eCSSUnit_Enumerated) {
    conditions.SetWritingModeDependency(wm.GetBits());
  }
  SetCoord(width->GetUnit() == eCSSUnit_Enumerated && vertical ?
             nsCSSValue(eCSSUnit_Unset) : *width,
           pos->mWidth, parentPos->mWidth,
           SETCOORD_LPAEH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC |
             SETCOORD_UNSET_INITIAL,
           aContext, mPresContext, conditions);

  const nsCSSValue* minWidth = aRuleData->ValueForMinWidth();
  if (minWidth->GetUnit() == eCSSUnit_Enumerated) {
    conditions.SetWritingModeDependency(wm.GetBits());
  }
  SetCoord(minWidth->GetUnit() == eCSSUnit_Enumerated && vertical ?
             nsCSSValue(eCSSUnit_Unset) : *minWidth,
           pos->mMinWidth, parentPos->mMinWidth,
           SETCOORD_LPAEH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC |
             SETCOORD_UNSET_INITIAL,
           aContext, mPresContext, conditions);

  const nsCSSValue* maxWidth = aRuleData->ValueForMaxWidth();
  if (maxWidth->GetUnit() == eCSSUnit_Enumerated) {
    conditions.SetWritingModeDependency(wm.GetBits());
  }
  SetCoord(maxWidth->GetUnit() == eCSSUnit_Enumerated && vertical ?
             nsCSSValue(eCSSUnit_Unset) : *maxWidth,
           pos->mMaxWidth, parentPos->mMaxWidth,
           SETCOORD_LPOEH | SETCOORD_INITIAL_NONE | SETCOORD_STORE_CALC |
             SETCOORD_UNSET_INITIAL,
           aContext, mPresContext, conditions);

  const nsCSSValue* height = aRuleData->ValueForHeight();
  if (height->GetUnit() == eCSSUnit_Enumerated) {
    conditions.SetWritingModeDependency(wm.GetBits());
  }
  SetCoord(height->GetUnit() == eCSSUnit_Enumerated && !vertical ?
             nsCSSValue(eCSSUnit_Unset) : *height,
           pos->mHeight, parentPos->mHeight,
           SETCOORD_LPAEH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC |
             SETCOORD_UNSET_INITIAL,
           aContext, mPresContext, conditions);

  const nsCSSValue* minHeight = aRuleData->ValueForMinHeight();
  if (minHeight->GetUnit() == eCSSUnit_Enumerated) {
    conditions.SetWritingModeDependency(wm.GetBits());
  }
  SetCoord(minHeight->GetUnit() == eCSSUnit_Enumerated && !vertical ?
             nsCSSValue(eCSSUnit_Unset) : *minHeight,
           pos->mMinHeight, parentPos->mMinHeight,
           SETCOORD_LPAEH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC |
             SETCOORD_UNSET_INITIAL,
           aContext, mPresContext, conditions);

  const nsCSSValue* maxHeight = aRuleData->ValueForMaxHeight();
  if (maxHeight->GetUnit() == eCSSUnit_Enumerated) {
    conditions.SetWritingModeDependency(wm.GetBits());
  }
  SetCoord(maxHeight->GetUnit() == eCSSUnit_Enumerated && !vertical ?
             nsCSSValue(eCSSUnit_Unset) : *maxHeight,
           pos->mMaxHeight, parentPos->mMaxHeight,
           SETCOORD_LPOEH | SETCOORD_INITIAL_NONE | SETCOORD_STORE_CALC |
             SETCOORD_UNSET_INITIAL,
           aContext, mPresContext, conditions);

  // box-sizing: enum, inherit, initial
  SetValue(*aRuleData->ValueForBoxSizing(),
           pos->mBoxSizing, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentPos->mBoxSizing,
           StyleBoxSizing::Content);

  // align-content: enum, inherit, initial
  SetValue(*aRuleData->ValueForAlignContent(),
           pos->mAlignContent, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentPos->mAlignContent,
           NS_STYLE_ALIGN_NORMAL);

  // align-items: enum, inherit, initial
  SetValue(*aRuleData->ValueForAlignItems(),
           pos->mAlignItems, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentPos->mAlignItems,
           NS_STYLE_ALIGN_NORMAL);

  // align-self: enum, inherit, initial
  SetValue(*aRuleData->ValueForAlignSelf(),
           pos->mAlignSelf, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentPos->mAlignSelf,
           NS_STYLE_ALIGN_AUTO);

  // justify-content: enum, inherit, initial
  SetValue(*aRuleData->ValueForJustifyContent(),
           pos->mJustifyContent, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentPos->mJustifyContent,
           NS_STYLE_JUSTIFY_NORMAL);

  // justify-items: enum, inherit, initial
  const auto& justifyItemsValue = *aRuleData->ValueForJustifyItems();
  if (MOZ_UNLIKELY(justifyItemsValue.GetUnit() == eCSSUnit_Inherit)) {
    if (MOZ_LIKELY(parentContext)) {
      pos->mJustifyItems =
        parentPos->ComputedJustifyItems(parentContext->GetParent());
    } else {
      pos->mJustifyItems = NS_STYLE_JUSTIFY_NORMAL;
    }
    conditions.SetUncacheable();
  } else {
    SetValue(justifyItemsValue,
             pos->mJustifyItems, conditions,
             SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
             parentPos->mJustifyItems, // unused, we handle 'inherit' above
             NS_STYLE_JUSTIFY_AUTO);
  }

  // justify-self: enum, inherit, initial
  SetValue(*aRuleData->ValueForJustifySelf(),
           pos->mJustifySelf, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentPos->mJustifySelf,
           NS_STYLE_JUSTIFY_AUTO);

  // flex-basis: auto, length, percent, enum, calc, inherit, initial
  // (Note: The flags here should match those used for 'width' property above.)
  SetCoord(*aRuleData->ValueForFlexBasis(), pos->mFlexBasis, parentPos->mFlexBasis,
           SETCOORD_LPAEH | SETCOORD_INITIAL_AUTO | SETCOORD_STORE_CALC |
             SETCOORD_UNSET_INITIAL,
           aContext, mPresContext, conditions);

  // flex-direction: enum, inherit, initial
  SetValue(*aRuleData->ValueForFlexDirection(),
           pos->mFlexDirection, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentPos->mFlexDirection,
           NS_STYLE_FLEX_DIRECTION_ROW);

  // flex-grow: float, inherit, initial
  SetFactor(*aRuleData->ValueForFlexGrow(),
            pos->mFlexGrow, conditions,
            parentPos->mFlexGrow, 0.0f,
            SETFCT_UNSET_INITIAL);

  // flex-shrink: float, inherit, initial
  SetFactor(*aRuleData->ValueForFlexShrink(),
            pos->mFlexShrink, conditions,
            parentPos->mFlexShrink, 1.0f,
            SETFCT_UNSET_INITIAL);

  // flex-wrap: enum, inherit, initial
  SetValue(*aRuleData->ValueForFlexWrap(),
           pos->mFlexWrap, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentPos->mFlexWrap,
           NS_STYLE_FLEX_WRAP_NOWRAP);

  // order: integer, inherit, initial
  SetValue(*aRuleData->ValueForOrder(),
           pos->mOrder, conditions,
           SETVAL_INTEGER | SETVAL_UNSET_INITIAL,
           parentPos->mOrder,
           NS_STYLE_ORDER_INITIAL);

  // object-fit: enum, inherit, initial
  SetValue(*aRuleData->ValueForObjectFit(),
           pos->mObjectFit, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentPos->mObjectFit,
           NS_STYLE_OBJECT_FIT_FILL);

  // object-position
  const nsCSSValue& objectPosition = *aRuleData->ValueForObjectPosition();
  switch (objectPosition.GetUnit()) {
    case eCSSUnit_Null:
      break;
    case eCSSUnit_Inherit:
      conditions.SetUncacheable();
      pos->mObjectPosition = parentPos->mObjectPosition;
      break;
    case eCSSUnit_Initial:
    case eCSSUnit_Unset:
      pos->mObjectPosition.SetInitialPercentValues(0.5f);
      break;
    default:
      ComputePositionValue(aContext, objectPosition,
                           pos->mObjectPosition, conditions);
  }

  // grid-auto-flow
  const nsCSSValue& gridAutoFlow = *aRuleData->ValueForGridAutoFlow();
  switch (gridAutoFlow.GetUnit()) {
    case eCSSUnit_Null:
      break;
    case eCSSUnit_Inherit:
      conditions.SetUncacheable();
      pos->mGridAutoFlow = parentPos->mGridAutoFlow;
      break;
    case eCSSUnit_Initial:
    case eCSSUnit_Unset:
      pos->mGridAutoFlow = NS_STYLE_GRID_AUTO_FLOW_ROW;
      break;
    default:
      NS_ASSERTION(gridAutoFlow.GetUnit() == eCSSUnit_Enumerated,
                   "Unexpected unit");
      pos->mGridAutoFlow = gridAutoFlow.GetIntValue();
  }

  // grid-auto-columns
  SetGridAutoColumnsRows(*aRuleData->ValueForGridAutoColumns(),
                         pos->mGridAutoColumnsMin,
                         pos->mGridAutoColumnsMax,
                         parentPos->mGridAutoColumnsMin,
                         parentPos->mGridAutoColumnsMax,
                         aContext, mPresContext, conditions);

  // grid-auto-rows
  SetGridAutoColumnsRows(*aRuleData->ValueForGridAutoRows(),
                         pos->mGridAutoRowsMin,
                         pos->mGridAutoRowsMax,
                         parentPos->mGridAutoRowsMin,
                         parentPos->mGridAutoRowsMax,
                         aContext, mPresContext, conditions);

  // grid-template-columns
  SetGridTrackList(*aRuleData->ValueForGridTemplateColumns(),
                   pos->mGridTemplateColumns, parentPos->mGridTemplateColumns,
                   aContext, mPresContext, conditions);

  // grid-template-rows
  SetGridTrackList(*aRuleData->ValueForGridTemplateRows(),
                   pos->mGridTemplateRows, parentPos->mGridTemplateRows,
                   aContext, mPresContext, conditions);

  // grid-tempate-areas
  SetGridTemplateAreas(*aRuleData->ValueForGridTemplateAreas(),
                       &pos->mGridTemplateAreas,
                       parentPos->mGridTemplateAreas,
                       conditions);

  // grid-column-start
  SetGridLine(*aRuleData->ValueForGridColumnStart(),
              pos->mGridColumnStart,
              parentPos->mGridColumnStart,
              conditions);

  // grid-column-end
  SetGridLine(*aRuleData->ValueForGridColumnEnd(),
              pos->mGridColumnEnd,
              parentPos->mGridColumnEnd,
              conditions);

  // grid-row-start
  SetGridLine(*aRuleData->ValueForGridRowStart(),
              pos->mGridRowStart,
              parentPos->mGridRowStart,
              conditions);

  // grid-row-end
  SetGridLine(*aRuleData->ValueForGridRowEnd(),
              pos->mGridRowEnd,
              parentPos->mGridRowEnd,
              conditions);

  // grid-column-gap
  if (SetCoord(*aRuleData->ValueForGridColumnGap(),
               pos->mGridColumnGap, parentPos->mGridColumnGap,
               SETCOORD_LPH | SETCOORD_INITIAL_ZERO | SETCOORD_STORE_CALC |
               SETCOORD_CALC_CLAMP_NONNEGATIVE | SETCOORD_UNSET_INITIAL,
               aContext, mPresContext, conditions)) {
  } else {
    MOZ_ASSERT(aRuleData->ValueForGridColumnGap()->GetUnit() == eCSSUnit_Null,
               "unexpected unit");
  }

  // grid-row-gap
  if (SetCoord(*aRuleData->ValueForGridRowGap(),
               pos->mGridRowGap, parentPos->mGridRowGap,
               SETCOORD_LPH | SETCOORD_INITIAL_ZERO | SETCOORD_STORE_CALC |
               SETCOORD_CALC_CLAMP_NONNEGATIVE | SETCOORD_UNSET_INITIAL,
               aContext, mPresContext, conditions)) {
  } else {
    MOZ_ASSERT(aRuleData->ValueForGridRowGap()->GetUnit() == eCSSUnit_Null,
               "unexpected unit");
  }

  // z-index
  const nsCSSValue* zIndexValue = aRuleData->ValueForZIndex();
  if (! SetCoord(*zIndexValue, pos->mZIndex, parentPos->mZIndex,
                 SETCOORD_IA | SETCOORD_INITIAL_AUTO | SETCOORD_UNSET_INITIAL,
                 aContext, nullptr, conditions)) {
    if (eCSSUnit_Inherit == zIndexValue->GetUnit()) {
      // handle inherit, because it's ok to inherit 'auto' here
      conditions.SetUncacheable();
      pos->mZIndex = parentPos->mZIndex;
    }
  }

  COMPUTE_END_RESET(Position, pos)
}

const void*
nsRuleNode::ComputeTableData(void* aStartStruct,
                             const nsRuleData* aRuleData,
                             nsStyleContext* aContext,
                             nsRuleNode* aHighestNode,
                             const RuleDetail aRuleDetail,
                             const RuleNodeCacheConditions aConditions)
{
  COMPUTE_START_RESET(Table, table, parentTable)

  // table-layout: enum, inherit, initial
  SetValue(*aRuleData->ValueForTableLayout(),
           table->mLayoutStrategy, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentTable->mLayoutStrategy,
           NS_STYLE_TABLE_LAYOUT_AUTO);

  // span: pixels (not a real CSS prop)
  const nsCSSValue* spanValue = aRuleData->ValueForSpan();
  if (eCSSUnit_Enumerated == spanValue->GetUnit() ||
      eCSSUnit_Integer == spanValue->GetUnit())
    table->mSpan = spanValue->GetIntValue();

  COMPUTE_END_RESET(Table, table)
}

const void*
nsRuleNode::ComputeTableBorderData(void* aStartStruct,
                                   const nsRuleData* aRuleData,
                                   nsStyleContext* aContext,
                                   nsRuleNode* aHighestNode,
                                   const RuleDetail aRuleDetail,
                                   const RuleNodeCacheConditions aConditions)
{
  COMPUTE_START_INHERITED(TableBorder, table, parentTable)

  // border-collapse: enum, inherit, initial
  SetValue(*aRuleData->ValueForBorderCollapse(), table->mBorderCollapse,
           conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentTable->mBorderCollapse,
           NS_STYLE_BORDER_SEPARATE);

  const nsCSSValue* borderSpacingValue = aRuleData->ValueForBorderSpacing();
  // border-spacing: pair(length), inherit
  if (borderSpacingValue->GetUnit() != eCSSUnit_Null) {
    nsStyleCoord parentCol(parentTable->mBorderSpacingCol,
                           nsStyleCoord::CoordConstructor);
    nsStyleCoord parentRow(parentTable->mBorderSpacingRow,
                           nsStyleCoord::CoordConstructor);
    nsStyleCoord coordCol, coordRow;

#ifdef DEBUG
    bool result =
#endif
      SetPairCoords(*borderSpacingValue,
                    coordCol, coordRow, parentCol, parentRow,
                    SETCOORD_LH | SETCOORD_INITIAL_ZERO |
                      SETCOORD_CALC_LENGTH_ONLY |
                      SETCOORD_CALC_CLAMP_NONNEGATIVE | SETCOORD_UNSET_INHERIT,
                    aContext, mPresContext, conditions);
    NS_ASSERTION(result, "malformed table border value");
    table->mBorderSpacingCol = coordCol.GetCoordValue();
    table->mBorderSpacingRow = coordRow.GetCoordValue();
  }

  // caption-side: enum, inherit, initial
  SetValue(*aRuleData->ValueForCaptionSide(),
           table->mCaptionSide, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentTable->mCaptionSide,
           NS_STYLE_CAPTION_SIDE_TOP);

  // empty-cells: enum, inherit, initial
  SetValue(*aRuleData->ValueForEmptyCells(),
           table->mEmptyCells, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentTable->mEmptyCells,
           NS_STYLE_TABLE_EMPTY_CELLS_SHOW);

  COMPUTE_END_INHERITED(TableBorder, table)
}

const void*
nsRuleNode::ComputeContentData(void* aStartStruct,
                               const nsRuleData* aRuleData,
                               nsStyleContext* aContext,
                               nsRuleNode* aHighestNode,
                               const RuleDetail aRuleDetail,
                               const RuleNodeCacheConditions aConditions)
{
  uint32_t count;
  nsAutoString buffer;

  COMPUTE_START_RESET(Content, content, parentContent)

  // content: [string, url, counter, attr, enum]+, normal, none, inherit
  const nsCSSValue* contentValue = aRuleData->ValueForContent();
  switch (contentValue->GetUnit()) {
  case eCSSUnit_Null:
    break;

  case eCSSUnit_Normal:
  case eCSSUnit_None:
  case eCSSUnit_Initial:
  case eCSSUnit_Unset:
    // "normal", "none", "initial" and "unset" all mean no content
    content->AllocateContents(0);
    break;

  case eCSSUnit_Inherit:
    conditions.SetUncacheable();
    count = parentContent->ContentCount();
    content->AllocateContents(count);
    while (0 < count--) {
      content->ContentAt(count) = parentContent->ContentAt(count);
    }
    break;

  case eCSSUnit_Enumerated: {
    MOZ_ASSERT(contentValue->GetIntValue() == NS_STYLE_CONTENT_ALT_CONTENT,
               "unrecognized solitary content keyword");
    content->AllocateContents(1);
    nsStyleContentData& data = content->ContentAt(0);
    data.mType = eStyleContentType_AltContent;
    data.mContent.mString = nullptr;
    break;
  }

  case eCSSUnit_List:
  case eCSSUnit_ListDep: {
    const nsCSSValueList* contentValueList = contentValue->GetListValue();
    count = 0;
    while (contentValueList) {
      count++;
      contentValueList = contentValueList->mNext;
    }
    content->AllocateContents(count);
    const nsAutoString nullStr;
    count = 0;
    contentValueList = contentValue->GetListValue();
    while (contentValueList) {
      const nsCSSValue& value = contentValueList->mValue;
      nsCSSUnit unit = value.GetUnit();
      nsStyleContentType type;
      nsStyleContentData &data = content->ContentAt(count++);
      switch (unit) {
      case eCSSUnit_String:   type = eStyleContentType_String;    break;
      case eCSSUnit_Image:    type = eStyleContentType_Image;     break;
      case eCSSUnit_Attr:     type = eStyleContentType_Attr;      break;
      case eCSSUnit_Counter:  type = eStyleContentType_Counter;   break;
      case eCSSUnit_Counters: type = eStyleContentType_Counters;  break;
      case eCSSUnit_Enumerated:
        switch (value.GetIntValue()) {
        case NS_STYLE_CONTENT_OPEN_QUOTE:
          type = eStyleContentType_OpenQuote;     break;
        case NS_STYLE_CONTENT_CLOSE_QUOTE:
          type = eStyleContentType_CloseQuote;    break;
        case NS_STYLE_CONTENT_NO_OPEN_QUOTE:
          type = eStyleContentType_NoOpenQuote;   break;
        case NS_STYLE_CONTENT_NO_CLOSE_QUOTE:
          type = eStyleContentType_NoCloseQuote;  break;
        default:
          NS_ERROR("bad content value");
          type = eStyleContentType_Uninitialized;
        }
        break;
      default:
        NS_ERROR("bad content type");
        type = eStyleContentType_Uninitialized;
      }
      data.mType = type;
      if (type == eStyleContentType_Image) {
        SetImageRequest([&](imgRequestProxy* req) {
          data.SetImage(req);
        }, mPresContext, value);
      } else if (type <= eStyleContentType_Attr) {
        value.GetStringValue(buffer);
        data.mContent.mString = NS_strdup(buffer.get());
      } else if (type <= eStyleContentType_Counters) {
        data.mContent.mCounters = value.GetArrayValue();
        data.mContent.mCounters->AddRef();
      } else {
        data.mContent.mString = nullptr;
      }
      contentValueList = contentValueList->mNext;
    }
    break;
  }

  default:
    MOZ_ASSERT(false, "unrecognized content unit");
  }

  // counter-increment: [string [int]]+, none, inherit
  const nsCSSValue* counterIncrementValue =
    aRuleData->ValueForCounterIncrement();
  switch (counterIncrementValue->GetUnit()) {
  case eCSSUnit_Null:
    break;

  case eCSSUnit_None:
  case eCSSUnit_Initial:
  case eCSSUnit_Unset:
    content->AllocateCounterIncrements(0);
    break;

  case eCSSUnit_Inherit:
    conditions.SetUncacheable();
    count = parentContent->CounterIncrementCount();
    content->AllocateCounterIncrements(count);
    while (count--) {
      const nsStyleCounterData& data = parentContent->CounterIncrementAt(count);
      content->SetCounterIncrementAt(count, data.mCounter, data.mValue);
    }
    break;

  case eCSSUnit_PairList:
  case eCSSUnit_PairListDep: {
    const nsCSSValuePairList* ourIncrement =
      counterIncrementValue->GetPairListValue();
    MOZ_ASSERT(ourIncrement->mXValue.GetUnit() == eCSSUnit_Ident,
               "unexpected value unit");
    count = ListLength(ourIncrement);
    content->AllocateCounterIncrements(count);

    count = 0;
    for (const nsCSSValuePairList* p = ourIncrement; p; p = p->mNext, count++) {
      int32_t increment;
      if (p->mYValue.GetUnit() == eCSSUnit_Integer) {
        increment = p->mYValue.GetIntValue();
      } else {
        increment = 1;
      }
      p->mXValue.GetStringValue(buffer);
      content->SetCounterIncrementAt(count, buffer, increment);
    }
    break;
  }

  default:
    MOZ_ASSERT(false, "unexpected value unit");
  }

  // counter-reset: [string [int]]+, none, inherit
  const nsCSSValue* counterResetValue = aRuleData->ValueForCounterReset();
  switch (counterResetValue->GetUnit()) {
  case eCSSUnit_Null:
    break;

  case eCSSUnit_None:
  case eCSSUnit_Initial:
  case eCSSUnit_Unset:
    content->AllocateCounterResets(0);
    break;

  case eCSSUnit_Inherit:
    conditions.SetUncacheable();
    count = parentContent->CounterResetCount();
    content->AllocateCounterResets(count);
    while (0 < count--) {
      const nsStyleCounterData& data = parentContent->CounterResetAt(count);
      content->SetCounterResetAt(count, data.mCounter, data.mValue);
    }
    break;

  case eCSSUnit_PairList:
  case eCSSUnit_PairListDep: {
    const nsCSSValuePairList* ourReset =
      counterResetValue->GetPairListValue();
    MOZ_ASSERT(ourReset->mXValue.GetUnit() == eCSSUnit_Ident,
               "unexpected value unit");
    count = ListLength(ourReset);
    content->AllocateCounterResets(count);
    count = 0;
    for (const nsCSSValuePairList* p = ourReset; p; p = p->mNext, count++) {
      int32_t reset;
      if (p->mYValue.GetUnit() == eCSSUnit_Integer) {
        reset = p->mYValue.GetIntValue();
      } else {
        reset = 0;
      }
      p->mXValue.GetStringValue(buffer);
      content->SetCounterResetAt(count, buffer, reset);
    }
    break;
  }

  default:
    MOZ_ASSERT(false, "unexpected value unit");
  }

  // If we ended up with an image, track it.
  for (uint32_t i = 0; i < content->ContentCount(); ++i) {
    if ((content->ContentAt(i).mType == eStyleContentType_Image) &&
        content->ContentAt(i).mContent.mImage) {
      content->ContentAt(i).TrackImage(
          aContext->PresContext()->Document()->ImageTracker());
    }
  }

  COMPUTE_END_RESET(Content, content)
}

const void*
nsRuleNode::ComputeXULData(void* aStartStruct,
                           const nsRuleData* aRuleData,
                           nsStyleContext* aContext,
                           nsRuleNode* aHighestNode,
                           const RuleDetail aRuleDetail,
                           const RuleNodeCacheConditions aConditions)
{
  COMPUTE_START_RESET(XUL, xul, parentXUL)

  // box-align: enum, inherit, initial
  SetValue(*aRuleData->ValueForBoxAlign(),
           xul->mBoxAlign, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentXUL->mBoxAlign,
           StyleBoxAlign::Stretch);

  // box-direction: enum, inherit, initial
  SetValue(*aRuleData->ValueForBoxDirection(),
           xul->mBoxDirection, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentXUL->mBoxDirection,
           StyleBoxDirection::Normal);

  // box-flex: factor, inherit
  SetFactor(*aRuleData->ValueForBoxFlex(),
            xul->mBoxFlex, conditions,
            parentXUL->mBoxFlex, 0.0f,
            SETFCT_UNSET_INITIAL);

  // box-orient: enum, inherit, initial
  SetValue(*aRuleData->ValueForBoxOrient(),
           xul->mBoxOrient, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentXUL->mBoxOrient,
           StyleBoxOrient::Horizontal);

  // box-pack: enum, inherit, initial
  SetValue(*aRuleData->ValueForBoxPack(),
           xul->mBoxPack, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentXUL->mBoxPack,
           StyleBoxPack::Start);

  // box-ordinal-group: integer, inherit, initial
  SetValue(*aRuleData->ValueForBoxOrdinalGroup(),
           xul->mBoxOrdinal, conditions,
           SETVAL_INTEGER | SETVAL_UNSET_INITIAL,
           parentXUL->mBoxOrdinal, 1);

  const nsCSSValue* stackSizingValue = aRuleData->ValueForStackSizing();
  if (eCSSUnit_Inherit == stackSizingValue->GetUnit()) {
    conditions.SetUncacheable();
    xul->mStretchStack = parentXUL->mStretchStack;
  } else if (eCSSUnit_Initial == stackSizingValue->GetUnit() ||
             eCSSUnit_Unset == stackSizingValue->GetUnit()) {
    xul->mStretchStack = true;
  } else if (eCSSUnit_Enumerated == stackSizingValue->GetUnit()) {
    xul->mStretchStack = stackSizingValue->GetIntValue() ==
      NS_STYLE_STACK_SIZING_STRETCH_TO_FIT;
  }

  COMPUTE_END_RESET(XUL, xul)
}

const void*
nsRuleNode::ComputeColumnData(void* aStartStruct,
                              const nsRuleData* aRuleData,
                              nsStyleContext* aContext,
                              nsRuleNode* aHighestNode,
                              const RuleDetail aRuleDetail,
                              const RuleNodeCacheConditions aConditions)
{
  COMPUTE_START_RESET(Column, column, parent)

  // column-width: length, auto, inherit
  SetCoord(*aRuleData->ValueForColumnWidth(),
           column->mColumnWidth, parent->mColumnWidth,
           SETCOORD_LAH | SETCOORD_INITIAL_AUTO |
             SETCOORD_CALC_LENGTH_ONLY | SETCOORD_CALC_CLAMP_NONNEGATIVE |
             SETCOORD_UNSET_INITIAL,
           aContext, mPresContext, conditions);

  // column-gap: length, inherit, normal
  SetCoord(*aRuleData->ValueForColumnGap(),
           column->mColumnGap, parent->mColumnGap,
           SETCOORD_LH | SETCOORD_NORMAL | SETCOORD_INITIAL_NORMAL |
             SETCOORD_CALC_LENGTH_ONLY | SETCOORD_UNSET_INITIAL,
           aContext, mPresContext, conditions);
  // clamp negative calc() to 0
  if (column->mColumnGap.GetUnit() == eStyleUnit_Coord) {
    column->mColumnGap.SetCoordValue(
      std::max(column->mColumnGap.GetCoordValue(), 0));
  }

  // column-count: auto, integer, inherit
  const nsCSSValue* columnCountValue = aRuleData->ValueForColumnCount();
  if (eCSSUnit_Auto == columnCountValue->GetUnit() ||
      eCSSUnit_Initial == columnCountValue->GetUnit() ||
      eCSSUnit_Unset == columnCountValue->GetUnit()) {
    column->mColumnCount = NS_STYLE_COLUMN_COUNT_AUTO;
  } else if (eCSSUnit_Integer == columnCountValue->GetUnit()) {
    column->mColumnCount = columnCountValue->GetIntValue();
    // Max kMaxColumnCount columns - wallpaper for bug 345583.
    column->mColumnCount = std::min(column->mColumnCount,
                                    nsStyleColumn::kMaxColumnCount);
  } else if (eCSSUnit_Inherit == columnCountValue->GetUnit()) {
    conditions.SetUncacheable();
    column->mColumnCount = parent->mColumnCount;
  }

  // column-rule-width: length, enum, inherit
  const nsCSSValue& widthValue = *aRuleData->ValueForColumnRuleWidth();
  if (eCSSUnit_Initial == widthValue.GetUnit() ||
      eCSSUnit_Unset == widthValue.GetUnit()) {
    column->SetColumnRuleWidth(
        (mPresContext->GetBorderWidthTable())[NS_STYLE_BORDER_WIDTH_MEDIUM]);
  }
  else if (eCSSUnit_Enumerated == widthValue.GetUnit()) {
    NS_ASSERTION(widthValue.GetIntValue() == NS_STYLE_BORDER_WIDTH_THIN ||
                 widthValue.GetIntValue() == NS_STYLE_BORDER_WIDTH_MEDIUM ||
                 widthValue.GetIntValue() == NS_STYLE_BORDER_WIDTH_THICK,
                 "Unexpected enum value");
    column->SetColumnRuleWidth(
        (mPresContext->GetBorderWidthTable())[widthValue.GetIntValue()]);
  }
  else if (eCSSUnit_Inherit == widthValue.GetUnit()) {
    column->SetColumnRuleWidth(parent->GetComputedColumnRuleWidth());
    conditions.SetUncacheable();
  }
  else if (widthValue.IsLengthUnit() || widthValue.IsCalcUnit()) {
    nscoord len =
      CalcLength(widthValue, aContext, mPresContext, conditions);
    if (len < 0) {
      // FIXME: This is untested (by test_value_storage.html) for
      // column-rule-width since it gets covered up by the border
      // rounding code.
      NS_ASSERTION(widthValue.IsCalcUnit(),
                   "parser should have rejected negative length");
      len = 0;
    }
    column->SetColumnRuleWidth(len);
  }

  // column-rule-style: enum, inherit
  const nsCSSValue& styleValue = *aRuleData->ValueForColumnRuleStyle();
  MOZ_ASSERT(eCSSUnit_None != styleValue.GetUnit(),
             "'none' should be handled as enumerated value");
  if (eCSSUnit_Enumerated == styleValue.GetUnit()) {
    column->mColumnRuleStyle = styleValue.GetIntValue();
  }
  else if (eCSSUnit_Initial == styleValue.GetUnit() ||
           eCSSUnit_Unset == styleValue.GetUnit()) {
    column->mColumnRuleStyle = NS_STYLE_BORDER_STYLE_NONE;
  }
  else if (eCSSUnit_Inherit == styleValue.GetUnit()) {
    conditions.SetUncacheable();
    column->mColumnRuleStyle = parent->mColumnRuleStyle;
  }

  // column-rule-color: color, inherit
  SetComplexColor<eUnsetInitial>(*aRuleData->ValueForColumnRuleColor(),
                                 parent->mColumnRuleColor,
                                 StyleComplexColor::CurrentColor(),
                                 mPresContext,
                                 column->mColumnRuleColor, conditions);

  // column-fill: enum
  SetValue(*aRuleData->ValueForColumnFill(),
           column->mColumnFill, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parent->mColumnFill,
           NS_STYLE_COLUMN_FILL_BALANCE);

  COMPUTE_END_RESET(Column, column)
}

static void
SetSVGPaint(const nsCSSValue& aValue, const nsStyleSVGPaint& parentPaint,
            nsPresContext* aPresContext, nsStyleContext *aContext,
            nsStyleSVGPaint& aResult, nsStyleSVGPaintType aInitialPaintType,
            RuleNodeCacheConditions& aConditions)
{
  MOZ_ASSERT(aInitialPaintType == eStyleSVGPaintType_None ||
             aInitialPaintType == eStyleSVGPaintType_Color,
             "SetSVGPaint only supports initial values being either 'black' "
             "(represented by eStyleSVGPaintType_Color) or none (by "
             "eStyleSVGPaintType_None)");

  nscolor color;

  if (aValue.GetUnit() == eCSSUnit_Inherit ||
      aValue.GetUnit() == eCSSUnit_Unset) {
    aResult = parentPaint;
    aConditions.SetUncacheable();
  } else if (aValue.GetUnit() == eCSSUnit_None) {
    aResult.SetNone();
  } else if (aValue.GetUnit() == eCSSUnit_Initial) {
    if (aInitialPaintType == eStyleSVGPaintType_None) {
      aResult.SetNone();
    } else {
      aResult.SetColor(NS_RGB(0, 0, 0));
    }
  } else if (SetColor(aValue, NS_RGB(0, 0, 0), aPresContext, aContext,
                      color, aConditions)) {
    aResult.SetColor(color);
  } else if (aValue.GetUnit() == eCSSUnit_Pair) {
    const nsCSSValuePair& pair = aValue.GetPairValue();

    nscolor fallback;
    if (pair.mYValue.GetUnit() == eCSSUnit_None) {
      fallback = NS_RGBA(0, 0, 0, 0);
    } else {
      MOZ_ASSERT(pair.mYValue.GetUnit() != eCSSUnit_Inherit,
                 "cannot inherit fallback colour");
      SetColor(pair.mYValue, NS_RGB(0, 0, 0), aPresContext, aContext,
               fallback, aConditions);
    }

    if (pair.mXValue.GetUnit() == eCSSUnit_URL) {
      aResult.SetPaintServer(pair.mXValue.GetURLStructValue(), fallback);
    } else if (pair.mXValue.GetUnit() == eCSSUnit_Enumerated) {

      switch (pair.mXValue.GetIntValue()) {
      case NS_COLOR_CONTEXT_FILL:
        aResult.SetContextValue(eStyleSVGPaintType_ContextFill, fallback);
        break;
      case NS_COLOR_CONTEXT_STROKE:
        aResult.SetContextValue(eStyleSVGPaintType_ContextStroke, fallback);
        break;
      default:
        NS_NOTREACHED("unknown keyword as paint server value");
      }

    } else {
      NS_NOTREACHED("malformed paint server value");
    }

  } else {
    MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Null,
               "malformed paint server value");
  }
}

static void
SetSVGOpacity(const nsCSSValue& aValue,
              float& aOpacityField, nsStyleSVGOpacitySource& aOpacityTypeField,
              RuleNodeCacheConditions& aConditions,
              float aParentOpacity, nsStyleSVGOpacitySource aParentOpacityType)
{
  if (eCSSUnit_Enumerated == aValue.GetUnit()) {
    switch (aValue.GetIntValue()) {
    case NS_STYLE_CONTEXT_FILL_OPACITY:
      aOpacityTypeField = eStyleSVGOpacitySource_ContextFillOpacity;
      break;
    case NS_STYLE_CONTEXT_STROKE_OPACITY:
      aOpacityTypeField = eStyleSVGOpacitySource_ContextStrokeOpacity;
      break;
    default:
      NS_NOTREACHED("SetSVGOpacity: Unknown keyword");
    }
    // Fall back on fully opaque
    aOpacityField = 1.0f;
  } else if (eCSSUnit_Inherit == aValue.GetUnit() ||
             eCSSUnit_Unset == aValue.GetUnit()) {
    aConditions.SetUncacheable();
    aOpacityField = aParentOpacity;
    aOpacityTypeField = aParentOpacityType;
  } else if (eCSSUnit_Null != aValue.GetUnit()) {
    SetFactor(aValue, aOpacityField, aConditions,
              aParentOpacity, 1.0f, SETFCT_OPACITY);
    aOpacityTypeField = eStyleSVGOpacitySource_Normal;
  }
}

/* static */
void
nsRuleNode::FillAllMaskLists(nsStyleImageLayers& aMask,
                             uint32_t aMaxItemCount)
{

  // Delete any extra items.  We need to keep layers in which any
  // property was specified.
  aMask.mLayers.TruncateLengthNonZero(aMaxItemCount);

  uint32_t fillCount = aMask.mImageCount;

  FillImageLayerList(aMask.mLayers,
                     &nsStyleImageLayers::Layer::mImage,
                     aMask.mImageCount, fillCount);
  FillImageLayerList(aMask.mLayers,
                     &nsStyleImageLayers::Layer::mSourceURI,
                     aMask.mImageCount, fillCount);
  FillImageLayerList(aMask.mLayers,
                     &nsStyleImageLayers::Layer::mRepeat,
                     aMask.mRepeatCount, fillCount);
  FillImageLayerList(aMask.mLayers,
                     &nsStyleImageLayers::Layer::mClip,
                     aMask.mClipCount, fillCount);
  FillImageLayerList(aMask.mLayers,
                     &nsStyleImageLayers::Layer::mOrigin,
                     aMask.mOriginCount, fillCount);
  FillImageLayerPositionCoordList(aMask.mLayers,
                                  &Position::mXPosition,
                                  aMask.mPositionXCount, fillCount);
  FillImageLayerPositionCoordList(aMask.mLayers,
                                  &Position::mYPosition,
                                  aMask.mPositionYCount, fillCount);
  FillImageLayerList(aMask.mLayers,
                     &nsStyleImageLayers::Layer::mSize,
                     aMask.mSizeCount, fillCount);
  FillImageLayerList(aMask.mLayers,
                     &nsStyleImageLayers::Layer::mMaskMode,
                     aMask.mMaskModeCount, fillCount);
  FillImageLayerList(aMask.mLayers,
                     &nsStyleImageLayers::Layer::mComposite,
                     aMask.mCompositeCount, fillCount);
}

const void*
nsRuleNode::ComputeSVGData(void* aStartStruct,
                           const nsRuleData* aRuleData,
                           nsStyleContext* aContext,
                           nsRuleNode* aHighestNode,
                           const RuleDetail aRuleDetail,
                           const RuleNodeCacheConditions aConditions)
{
  COMPUTE_START_INHERITED(SVG, svg, parentSVG)

  // clip-rule: enum, inherit, initial
  SetValue(*aRuleData->ValueForClipRule(),
           svg->mClipRule, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentSVG->mClipRule,
           StyleFillRule::Nonzero);

  // color-interpolation: enum, inherit, initial
  SetValue(*aRuleData->ValueForColorInterpolation(),
           svg->mColorInterpolation, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentSVG->mColorInterpolation,
           NS_STYLE_COLOR_INTERPOLATION_SRGB);

  // color-interpolation-filters: enum, inherit, initial
  SetValue(*aRuleData->ValueForColorInterpolationFilters(),
           svg->mColorInterpolationFilters, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentSVG->mColorInterpolationFilters,
           NS_STYLE_COLOR_INTERPOLATION_LINEARRGB);

  // fill:
  SetSVGPaint(*aRuleData->ValueForFill(),
              parentSVG->mFill, mPresContext, aContext,
              svg->mFill, eStyleSVGPaintType_Color, conditions);

  // fill-opacity: factor, inherit, initial,
  // context-fill-opacity, context-stroke-opacity
  nsStyleSVGOpacitySource contextFillOpacity = svg->FillOpacitySource();
  SetSVGOpacity(*aRuleData->ValueForFillOpacity(),
                svg->mFillOpacity, contextFillOpacity, conditions,
                parentSVG->mFillOpacity, parentSVG->FillOpacitySource());
  svg->SetFillOpacitySource(contextFillOpacity);

  // fill-rule: enum, inherit, initial
  SetValue(*aRuleData->ValueForFillRule(),
           svg->mFillRule, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentSVG->mFillRule,
           StyleFillRule::Nonzero);

  // marker-end: url, none, inherit
  const nsCSSValue* markerEndValue = aRuleData->ValueForMarkerEnd();
  if (eCSSUnit_URL == markerEndValue->GetUnit()) {
    svg->mMarkerEnd = markerEndValue->GetURLStructValue();
  } else if (eCSSUnit_None == markerEndValue->GetUnit() ||
             eCSSUnit_Initial == markerEndValue->GetUnit()) {
    svg->mMarkerEnd = nullptr;
  } else if (eCSSUnit_Inherit == markerEndValue->GetUnit() ||
             eCSSUnit_Unset == markerEndValue->GetUnit()) {
    conditions.SetUncacheable();
    svg->mMarkerEnd = parentSVG->mMarkerEnd;
  }

  // marker-mid: url, none, inherit
  const nsCSSValue* markerMidValue = aRuleData->ValueForMarkerMid();
  if (eCSSUnit_URL == markerMidValue->GetUnit()) {
    svg->mMarkerMid = markerMidValue->GetURLStructValue();
  } else if (eCSSUnit_None == markerMidValue->GetUnit() ||
             eCSSUnit_Initial == markerMidValue->GetUnit()) {
    svg->mMarkerMid = nullptr;
  } else if (eCSSUnit_Inherit == markerMidValue->GetUnit() ||
             eCSSUnit_Unset == markerMidValue->GetUnit()) {
    conditions.SetUncacheable();
    svg->mMarkerMid = parentSVG->mMarkerMid;
  }

  // marker-start: url, none, inherit
  const nsCSSValue* markerStartValue = aRuleData->ValueForMarkerStart();
  if (eCSSUnit_URL == markerStartValue->GetUnit()) {
    svg->mMarkerStart = markerStartValue->GetURLStructValue();
  } else if (eCSSUnit_None == markerStartValue->GetUnit() ||
             eCSSUnit_Initial == markerStartValue->GetUnit()) {
    svg->mMarkerStart = nullptr;
  } else if (eCSSUnit_Inherit == markerStartValue->GetUnit() ||
             eCSSUnit_Unset == markerStartValue->GetUnit()) {
    conditions.SetUncacheable();
    svg->mMarkerStart = parentSVG->mMarkerStart;
  }

  // paint-order: enum (bit field), inherit, initial
  const nsCSSValue* paintOrderValue = aRuleData->ValueForPaintOrder();
  switch (paintOrderValue->GetUnit()) {
    case eCSSUnit_Null:
      break;

    case eCSSUnit_Enumerated:
      static_assert
        (NS_STYLE_PAINT_ORDER_BITWIDTH * NS_STYLE_PAINT_ORDER_LAST_VALUE <= 8,
         "SVGStyleStruct::mPaintOrder not big enough");
      svg->mPaintOrder = static_cast<uint8_t>(paintOrderValue->GetIntValue());
      break;

    case eCSSUnit_Inherit:
    case eCSSUnit_Unset:
      conditions.SetUncacheable();
      svg->mPaintOrder = parentSVG->mPaintOrder;
      break;

    case eCSSUnit_Initial:
      svg->mPaintOrder = NS_STYLE_PAINT_ORDER_NORMAL;
      break;

    default:
      NS_NOTREACHED("unexpected unit");
  }

  // shape-rendering: enum, inherit
  SetValue(*aRuleData->ValueForShapeRendering(),
           svg->mShapeRendering, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentSVG->mShapeRendering,
           NS_STYLE_SHAPE_RENDERING_AUTO);

  // stroke:
  SetSVGPaint(*aRuleData->ValueForStroke(),
              parentSVG->mStroke, mPresContext, aContext,
              svg->mStroke, eStyleSVGPaintType_None, conditions);

  // stroke-dasharray: <dasharray>, none, inherit, context-value
  const nsCSSValue* strokeDasharrayValue = aRuleData->ValueForStrokeDasharray();
  switch (strokeDasharrayValue->GetUnit()) {
  case eCSSUnit_Null:
    break;

  case eCSSUnit_Inherit:
  case eCSSUnit_Unset:
    conditions.SetUncacheable();
    svg->SetStrokeDasharrayFromObject(parentSVG->StrokeDasharrayFromObject());
    svg->mStrokeDasharray = parentSVG->mStrokeDasharray;
    break;

  case eCSSUnit_Enumerated:
    MOZ_ASSERT(strokeDasharrayValue->GetIntValue() ==
                     NS_STYLE_STROKE_PROP_CONTEXT_VALUE,
               "Unknown keyword for stroke-dasharray");
    svg->SetStrokeDasharrayFromObject(true);
    svg->mStrokeDasharray.Clear();
    break;

  case eCSSUnit_Initial:
  case eCSSUnit_None:
    svg->SetStrokeDasharrayFromObject(false);
    svg->mStrokeDasharray.Clear();
    break;

  case eCSSUnit_List:
  case eCSSUnit_ListDep: {
    svg->SetStrokeDasharrayFromObject(false);
    svg->mStrokeDasharray.Clear();

    // count number of values
    const nsCSSValueList *value = strokeDasharrayValue->GetListValue();
    uint32_t strokeDasharrayLength = ListLength(value);

    MOZ_ASSERT(strokeDasharrayLength != 0, "no dasharray items");

    svg->mStrokeDasharray.SetLength(strokeDasharrayLength);

    uint32_t i = 0;
    while (nullptr != value) {
      SetCoord(value->mValue,
               svg->mStrokeDasharray[i++], nsStyleCoord(),
               SETCOORD_LP | SETCOORD_FACTOR,
               aContext, mPresContext, conditions);
      value = value->mNext;
    }
    break;
  }

  default:
    MOZ_ASSERT(false, "unrecognized dasharray unit");
  }

  // stroke-dashoffset: <dashoffset>, inherit
  const nsCSSValue *strokeDashoffsetValue =
    aRuleData->ValueForStrokeDashoffset();
  svg->SetStrokeDashoffsetFromObject(
    strokeDashoffsetValue->GetUnit() == eCSSUnit_Enumerated &&
    strokeDashoffsetValue->GetIntValue() == NS_STYLE_STROKE_PROP_CONTEXT_VALUE);
  if (svg->StrokeDashoffsetFromObject()) {
    svg->mStrokeDashoffset.SetCoordValue(0);
  } else {
    SetCoord(*aRuleData->ValueForStrokeDashoffset(),
             svg->mStrokeDashoffset, parentSVG->mStrokeDashoffset,
             SETCOORD_LPH | SETCOORD_FACTOR | SETCOORD_INITIAL_ZERO |
               SETCOORD_UNSET_INHERIT,
             aContext, mPresContext, conditions);
  }

  // stroke-linecap: enum, inherit, initial
  SetValue(*aRuleData->ValueForStrokeLinecap(),
           svg->mStrokeLinecap, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentSVG->mStrokeLinecap,
           NS_STYLE_STROKE_LINECAP_BUTT);

  // stroke-linejoin: enum, inherit, initial
  SetValue(*aRuleData->ValueForStrokeLinejoin(),
           svg->mStrokeLinejoin, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentSVG->mStrokeLinejoin,
           NS_STYLE_STROKE_LINEJOIN_MITER);

  // stroke-miterlimit: <miterlimit>, inherit
  SetFactor(*aRuleData->ValueForStrokeMiterlimit(),
            svg->mStrokeMiterlimit,
            conditions,
            parentSVG->mStrokeMiterlimit, 4.0f,
            SETFCT_UNSET_INHERIT);

  // stroke-opacity:
  nsStyleSVGOpacitySource contextStrokeOpacity = svg->StrokeOpacitySource();
  SetSVGOpacity(*aRuleData->ValueForStrokeOpacity(),
                svg->mStrokeOpacity, contextStrokeOpacity, conditions,
                parentSVG->mStrokeOpacity, parentSVG->StrokeOpacitySource());
  svg->SetStrokeOpacitySource(contextStrokeOpacity);

  // stroke-width:
  const nsCSSValue* strokeWidthValue = aRuleData->ValueForStrokeWidth();
  switch (strokeWidthValue->GetUnit()) {
  case eCSSUnit_Enumerated:
    MOZ_ASSERT(strokeWidthValue->GetIntValue() ==
                 NS_STYLE_STROKE_PROP_CONTEXT_VALUE,
               "Unrecognized keyword for stroke-width");
    svg->SetStrokeWidthFromObject(true);
    svg->mStrokeWidth.SetCoordValue(nsPresContext::CSSPixelsToAppUnits(1));
    break;

  case eCSSUnit_Initial:
    svg->SetStrokeWidthFromObject(false);
    svg->mStrokeWidth.SetCoordValue(nsPresContext::CSSPixelsToAppUnits(1));
    break;

  default:
    svg->SetStrokeWidthFromObject(false);
    SetCoord(*strokeWidthValue,
             svg->mStrokeWidth, parentSVG->mStrokeWidth,
             SETCOORD_LPH | SETCOORD_FACTOR | SETCOORD_UNSET_INHERIT,
             aContext, mPresContext, conditions);
  }

  // text-anchor: enum, inherit, initial
  SetValue(*aRuleData->ValueForTextAnchor(),
           svg->mTextAnchor, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentSVG->mTextAnchor,
           NS_STYLE_TEXT_ANCHOR_START);

  COMPUTE_END_INHERITED(SVG, svg)
}

static already_AddRefed<StyleBasicShape>
GetStyleBasicShapeFromCSSValue(const nsCSSValue& aValue,
                               nsStyleContext* aStyleContext,
                               nsPresContext* aPresContext,
                               RuleNodeCacheConditions& aConditions)
{
  RefPtr<StyleBasicShape> basicShape;

  nsCSSValue::Array* shapeFunction = aValue.GetArrayValue();
  nsCSSKeyword functionName =
    (nsCSSKeyword)shapeFunction->Item(0).GetIntValue();

  if (functionName == eCSSKeyword_polygon) {
    MOZ_ASSERT(!basicShape, "did not expect value");
    basicShape = new StyleBasicShape(StyleBasicShapeType::Polygon);
    MOZ_ASSERT(shapeFunction->Count() > 1,
               "polygon has wrong number of arguments");
    size_t j = 1;
    if (shapeFunction->Item(j).GetUnit() == eCSSUnit_Enumerated) {
      StyleFillRule rule;
      SetEnumValueHelper::SetEnumeratedValue(rule, shapeFunction->Item(j));
      basicShape->SetFillRule(rule);
      ++j;
    }
    const int32_t mask = SETCOORD_PERCENT | SETCOORD_LENGTH |
      SETCOORD_STORE_CALC;
    const nsCSSValuePairList* curPair =
      shapeFunction->Item(j).GetPairListValue();
    nsTArray<nsStyleCoord>& coordinates = basicShape->Coordinates();
    while (curPair) {
      nsStyleCoord xCoord, yCoord;
      DebugOnly<bool> didSetCoordX = SetCoord(curPair->mXValue, xCoord,
                                              nsStyleCoord(), mask,
                                              aStyleContext, aPresContext,
                                              aConditions);
      coordinates.AppendElement(xCoord);
      MOZ_ASSERT(didSetCoordX, "unexpected x coordinate unit");
      DebugOnly<bool> didSetCoordY = SetCoord(curPair->mYValue, yCoord,
                                              nsStyleCoord(), mask,
                                              aStyleContext, aPresContext,
                                              aConditions);
      coordinates.AppendElement(yCoord);
      MOZ_ASSERT(didSetCoordY, "unexpected y coordinate unit");
      curPair = curPair->mNext;
    }
  } else if (functionName == eCSSKeyword_circle ||
             functionName == eCSSKeyword_ellipse) {
    StyleBasicShapeType type = functionName == eCSSKeyword_circle ?
      StyleBasicShapeType::Circle :
      StyleBasicShapeType::Ellipse;
    MOZ_ASSERT(!basicShape, "did not expect value");
    basicShape = new StyleBasicShape(type);
    const int32_t mask = SETCOORD_PERCENT | SETCOORD_LENGTH |
      SETCOORD_STORE_CALC | SETCOORD_ENUMERATED;
    size_t count = type == StyleBasicShapeType::Circle ? 2 : 3;
    MOZ_ASSERT(shapeFunction->Count() == count + 1,
               "unexpected arguments count");
    MOZ_ASSERT(type == StyleBasicShapeType::Circle ||
               (shapeFunction->Item(1).GetUnit() == eCSSUnit_Null) ==
               (shapeFunction->Item(2).GetUnit() == eCSSUnit_Null),
               "ellipse should have two radii or none");
    for (size_t j = 1; j < count; ++j) {
      const nsCSSValue& val = shapeFunction->Item(j);
      nsStyleCoord radius;
      if (val.GetUnit() != eCSSUnit_Null) {
        DebugOnly<bool> didSetRadius = SetCoord(val, radius,
                                                nsStyleCoord(), mask,
                                                aStyleContext,
                                                aPresContext,
                                                aConditions);
        MOZ_ASSERT(didSetRadius, "unexpected radius unit");
      } else {
        radius.SetIntValue(NS_RADIUS_CLOSEST_SIDE, eStyleUnit_Enumerated);
      }
      basicShape->Coordinates().AppendElement(radius);
    }
    const nsCSSValue& positionVal = shapeFunction->Item(count);
    if (positionVal.GetUnit() == eCSSUnit_Array) {
      ComputePositionValue(aStyleContext, positionVal,
                           basicShape->GetPosition(),
                           aConditions);
    } else {
      MOZ_ASSERT(positionVal.GetUnit() == eCSSUnit_Null,
                 "expected no value");
    }
  } else if (functionName == eCSSKeyword_inset) {
    MOZ_ASSERT(!basicShape, "did not expect value");
    basicShape = new StyleBasicShape(StyleBasicShapeType::Inset);
    MOZ_ASSERT(shapeFunction->Count() == 6,
               "inset function has wrong number of arguments");
    MOZ_ASSERT(shapeFunction->Item(1).GetUnit() != eCSSUnit_Null,
               "no shape arguments defined");
    const int32_t mask = SETCOORD_PERCENT | SETCOORD_LENGTH |
      SETCOORD_STORE_CALC;
    nsTArray<nsStyleCoord>& coords = basicShape->Coordinates();
    for (size_t j = 1; j <= 4; ++j) {
      const nsCSSValue& val = shapeFunction->Item(j);
      nsStyleCoord inset;
      // Fill missing values to get 4 at the end.
      if (val.GetUnit() == eCSSUnit_Null) {
        if (j == 4) {
          inset = coords[1];
        } else {
          MOZ_ASSERT(j != 1, "first argument not specified");
          inset = coords[0];
        }
      } else {
        DebugOnly<bool> didSetInset = SetCoord(val, inset,
                                               nsStyleCoord(), mask,
                                               aStyleContext, aPresContext,
                                               aConditions);
        MOZ_ASSERT(didSetInset, "unexpected inset unit");
      }
      coords.AppendElement(inset);
    }

    nsStyleCorners& insetRadius = basicShape->GetRadius();
    if (shapeFunction->Item(5).GetUnit() == eCSSUnit_Array) {
      nsCSSValue::Array* radiiArray = shapeFunction->Item(5).GetArrayValue();
      NS_FOR_CSS_FULL_CORNERS(corner) {
        int cx = NS_FULL_TO_HALF_CORNER(corner, false);
        int cy = NS_FULL_TO_HALF_CORNER(corner, true);
        const nsCSSValue& radius = radiiArray->Item(corner);
        nsStyleCoord coordX, coordY;
        DebugOnly<bool> didSetRadii = SetPairCoords(radius, coordX, coordY,
                                                    nsStyleCoord(),
                                                    nsStyleCoord(), mask,
                                                    aStyleContext,
                                                    aPresContext,
                                                    aConditions);
        MOZ_ASSERT(didSetRadii, "unexpected radius unit");
        insetRadius.Set(cx, coordX);
        insetRadius.Set(cy, coordY);
      }
    } else {
      MOZ_ASSERT(shapeFunction->Item(5).GetUnit() == eCSSUnit_Null,
                 "unexpected value");
      // Initialize border-radius
      nsStyleCoord zero;
      zero.SetCoordValue(0);
      NS_FOR_CSS_HALF_CORNERS(j) {
        insetRadius.Set(j, zero);
      }
    }
  } else {
    NS_NOTREACHED("unexpected basic shape function");
  }

  return basicShape.forget();
}

template<typename ReferenceBox>
static void
SetStyleShapeSourceToCSSValue(
  StyleShapeSource<ReferenceBox>* aShapeSource,
  const nsCSSValue* aValue,
  nsStyleContext* aStyleContext,
  nsPresContext* aPresContext,
  RuleNodeCacheConditions& aConditions)
{
  MOZ_ASSERT(aValue->GetUnit() == eCSSUnit_Array,
             "expected a basic shape or reference box");

  const nsCSSValue::Array* array = aValue->GetArrayValue();
  MOZ_ASSERT(array->Count() == 1 || array->Count() == 2,
             "Expect one or both of a shape function and a reference box");

  ReferenceBox referenceBox = ReferenceBox::NoBox;
  RefPtr<StyleBasicShape> basicShape;

  for (size_t i = 0; i < array->Count(); ++i) {
    const nsCSSValue& item = array->Item(i);
    if (item.GetUnit() == eCSSUnit_Enumerated) {
      referenceBox = static_cast<ReferenceBox>(item.GetIntValue());
    } else if (item.GetUnit() == eCSSUnit_Function) {
      basicShape = GetStyleBasicShapeFromCSSValue(item, aStyleContext,
                                                  aPresContext, aConditions);
    } else {
      MOZ_ASSERT_UNREACHABLE("Unexpected unit!");
      return;
    }
  }

  if (basicShape) {
    aShapeSource->SetBasicShape(basicShape, referenceBox);
  } else {
    aShapeSource->SetReferenceBox(referenceBox);
  }
}

// Returns true if the nsStyleFilter was successfully set using the nsCSSValue.
static bool
SetStyleFilterToCSSValue(nsStyleFilter* aStyleFilter,
                         const nsCSSValue& aValue,
                         nsStyleContext* aStyleContext,
                         nsPresContext* aPresContext,
                         RuleNodeCacheConditions& aConditions)
{
  nsCSSUnit unit = aValue.GetUnit();
  if (unit == eCSSUnit_URL) {
    return aStyleFilter->SetURL(aValue.GetURLStructValue());
  }

  MOZ_ASSERT(unit == eCSSUnit_Function, "expected a filter function");

  nsCSSValue::Array* filterFunction = aValue.GetArrayValue();
  nsCSSKeyword functionName =
    (nsCSSKeyword)filterFunction->Item(0).GetIntValue();

  int32_t type;
  DebugOnly<bool> foundKeyword =
    nsCSSProps::FindKeyword(functionName,
                            nsCSSProps::kFilterFunctionKTable,
                            type);
  MOZ_ASSERT(foundKeyword, "unknown filter type");
  if (type == NS_STYLE_FILTER_DROP_SHADOW) {
    RefPtr<nsCSSShadowArray> shadowArray = GetShadowData(
      filterFunction->Item(1).GetListValue(),
      aStyleContext,
      false,
      aPresContext,
      aConditions);
    aStyleFilter->SetDropShadow(shadowArray);
    return true;
  }

  int32_t mask = SETCOORD_PERCENT | SETCOORD_FACTOR;
  if (type == NS_STYLE_FILTER_BLUR) {
    mask = SETCOORD_LENGTH |
           SETCOORD_CALC_LENGTH_ONLY |
           SETCOORD_CALC_CLAMP_NONNEGATIVE;
  } else if (type == NS_STYLE_FILTER_HUE_ROTATE) {
    mask = SETCOORD_ANGLE;
  }

  MOZ_ASSERT(filterFunction->Count() == 2,
             "all filter functions should have exactly one argument");

  nsCSSValue& arg = filterFunction->Item(1);
  nsStyleCoord filterParameter;
  DebugOnly<bool> didSetCoord = SetCoord(arg, filterParameter,
                                         nsStyleCoord(), mask,
                                         aStyleContext, aPresContext,
                                         aConditions);
  aStyleFilter->SetFilterParameter(filterParameter, type);
  MOZ_ASSERT(didSetCoord, "unexpected unit");
  return true;
}

const void*
nsRuleNode::ComputeSVGResetData(void* aStartStruct,
                                const nsRuleData* aRuleData,
                                nsStyleContext* aContext,
                                nsRuleNode* aHighestNode,
                                const RuleDetail aRuleDetail,
                                const RuleNodeCacheConditions aConditions)
{
  COMPUTE_START_RESET(SVGReset, svgReset, parentSVGReset)

  // stop-color:
  const nsCSSValue* stopColorValue = aRuleData->ValueForStopColor();
  if (eCSSUnit_Initial == stopColorValue->GetUnit() ||
      eCSSUnit_Unset == stopColorValue->GetUnit()) {
    svgReset->mStopColor = NS_RGB(0, 0, 0);
  } else {
    SetColor(*stopColorValue, parentSVGReset->mStopColor,
             mPresContext, aContext, svgReset->mStopColor, conditions);
  }

  // flood-color:
  const nsCSSValue* floodColorValue = aRuleData->ValueForFloodColor();
  if (eCSSUnit_Initial == floodColorValue->GetUnit() ||
      eCSSUnit_Unset == floodColorValue->GetUnit()) {
    svgReset->mFloodColor = NS_RGB(0, 0, 0);
  } else {
    SetColor(*floodColorValue, parentSVGReset->mFloodColor,
             mPresContext, aContext, svgReset->mFloodColor, conditions);
  }

  // lighting-color:
  const nsCSSValue* lightingColorValue = aRuleData->ValueForLightingColor();
  if (eCSSUnit_Initial == lightingColorValue->GetUnit() ||
      eCSSUnit_Unset == lightingColorValue->GetUnit()) {
    svgReset->mLightingColor = NS_RGB(255, 255, 255);
  } else {
    SetColor(*lightingColorValue, parentSVGReset->mLightingColor,
             mPresContext, aContext, svgReset->mLightingColor,
             conditions);
  }

  // clip-path: url, <basic-shape> || <geometry-box>, none, inherit
  const nsCSSValue* clipPathValue = aRuleData->ValueForClipPath();
  switch (clipPathValue->GetUnit()) {
    case eCSSUnit_Null:
      break;
    case eCSSUnit_None:
    case eCSSUnit_Initial:
    case eCSSUnit_Unset:
      svgReset->mClipPath = StyleClipPath();
      break;
    case eCSSUnit_Inherit:
      conditions.SetUncacheable();
      svgReset->mClipPath = parentSVGReset->mClipPath;
      break;
    case eCSSUnit_URL: {
      svgReset->mClipPath = StyleClipPath();
      svgReset->mClipPath.SetURL(clipPathValue->GetURLStructValue());
      break;
    }
    case eCSSUnit_Array: {
      svgReset->mClipPath = StyleClipPath();
      SetStyleShapeSourceToCSSValue(&svgReset->mClipPath, clipPathValue, aContext,
                                    mPresContext, conditions);
      break;
    }
    default:
      NS_NOTREACHED("unexpected unit");
  }

  // stop-opacity:
  SetFactor(*aRuleData->ValueForStopOpacity(),
            svgReset->mStopOpacity, conditions,
            parentSVGReset->mStopOpacity, 1.0f,
            SETFCT_OPACITY | SETFCT_UNSET_INITIAL);

  // flood-opacity:
  SetFactor(*aRuleData->ValueForFloodOpacity(),
            svgReset->mFloodOpacity, conditions,
            parentSVGReset->mFloodOpacity, 1.0f,
            SETFCT_OPACITY | SETFCT_UNSET_INITIAL);

  // dominant-baseline: enum, inherit, initial
  SetValue(*aRuleData->ValueForDominantBaseline(),
           svgReset->mDominantBaseline,
           conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentSVGReset->mDominantBaseline,
           NS_STYLE_DOMINANT_BASELINE_AUTO);

  // vector-effect: enum, inherit, initial
  SetValue(*aRuleData->ValueForVectorEffect(),
           svgReset->mVectorEffect,
           conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentSVGReset->mVectorEffect,
           NS_STYLE_VECTOR_EFFECT_NONE);

  // mask-type: enum, inherit, initial
  SetValue(*aRuleData->ValueForMaskType(),
           svgReset->mMaskType,
           conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentSVGReset->mMaskType,
           NS_STYLE_MASK_TYPE_LUMINANCE);

#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND
  uint32_t maxItemCount = 1;
  bool rebuild = false;

  // mask-image: none | <url> | <image-list> | <element-reference>  | <gradient>
  nsStyleImage initialImage;
  SetImageLayerList(aContext, *aRuleData->ValueForMaskImage(),
                    svgReset->mMask.mLayers,
                    parentSVGReset->mMask.mLayers,
                    &nsStyleImageLayers::Layer::mImage,
                    initialImage, parentSVGReset->mMask.mImageCount,
                    svgReset->mMask.mImageCount,
                    maxItemCount, rebuild, conditions);
  SetImageLayerList(aContext, *aRuleData->ValueForMaskImage(),
                    svgReset->mMask.mLayers,
                    parentSVGReset->mMask.mLayers,
                    &nsStyleImageLayers::Layer::mSourceURI,
                    RefPtr<css::URLValueData>(),
                    parentSVGReset->mMask.mImageCount,
                    svgReset->mMask.mImageCount,
                    maxItemCount, rebuild, conditions);

  // mask-repeat: enum, inherit, initial [pair list]
  nsStyleImageLayers::Repeat initialRepeat;
  initialRepeat.SetInitialValues();
  SetImageLayerPairList(aContext, *aRuleData->ValueForMaskRepeat(),
                        svgReset->mMask.mLayers,
                        parentSVGReset->mMask.mLayers,
                        &nsStyleImageLayers::Layer::mRepeat,
                        initialRepeat, parentSVGReset->mMask.mRepeatCount,
                        svgReset->mMask.mRepeatCount, maxItemCount, rebuild,
                        conditions);

  // mask-clip: enum, inherit, initial [list]
  SetImageLayerList(aContext, *aRuleData->ValueForMaskClip(),
                    svgReset->mMask.mLayers,
                    parentSVGReset->mMask.mLayers,
                    &nsStyleImageLayers::Layer::mClip,
                    uint8_t(NS_STYLE_IMAGELAYER_CLIP_BORDER),
                    parentSVGReset->mMask.mClipCount,
                    svgReset->mMask.mClipCount, maxItemCount, rebuild,
                    conditions);

  // mask-origin: enum, inherit, initial [list]
  SetImageLayerList(aContext, *aRuleData->ValueForMaskOrigin(),
                    svgReset->mMask.mLayers,
                    parentSVGReset->mMask.mLayers,
                    &nsStyleImageLayers::Layer::mOrigin,
                    uint8_t(NS_STYLE_IMAGELAYER_ORIGIN_BORDER),
                    parentSVGReset->mMask.mOriginCount,
                    svgReset->mMask.mOriginCount, maxItemCount, rebuild,
                    conditions);

  // mask-position-x/y: enum, length, percent (flags), inherit [list]
  Position::Coord initialPositionCoord;
  initialPositionCoord.mPercent = 0.0f;
  initialPositionCoord.mLength = 0;
  initialPositionCoord.mHasPercent = true;

  SetImageLayerPositionCoordList(
                    aContext, *aRuleData->ValueForMaskPositionX(),
                    svgReset->mMask.mLayers,
                    parentSVGReset->mMask.mLayers,
                    &Position::mXPosition,
                    initialPositionCoord, parentSVGReset->mMask.mPositionXCount,
                    svgReset->mMask.mPositionXCount, maxItemCount, rebuild,
                    conditions);
  SetImageLayerPositionCoordList(
                    aContext, *aRuleData->ValueForMaskPositionY(),
                    svgReset->mMask.mLayers,
                    parentSVGReset->mMask.mLayers,
                    &Position::mYPosition,
                    initialPositionCoord, parentSVGReset->mMask.mPositionYCount,
                    svgReset->mMask.mPositionYCount, maxItemCount, rebuild,
                    conditions);

  // mask-size: enum, length, auto, inherit, initial [pair list]
  nsStyleImageLayers::Size initialSize;
  initialSize.SetInitialValues();
  SetImageLayerPairList(aContext, *aRuleData->ValueForMaskSize(),
                        svgReset->mMask.mLayers,
                        parentSVGReset->mMask.mLayers,
                        &nsStyleImageLayers::Layer::mSize,
                        initialSize, parentSVGReset->mMask.mSizeCount,
                        svgReset->mMask.mSizeCount, maxItemCount, rebuild,
                        conditions);

  // mask-mode: enum, inherit, initial [list]
  SetImageLayerList(aContext, *aRuleData->ValueForMaskMode(),
                    svgReset->mMask.mLayers,
                    parentSVGReset->mMask.mLayers,
                    &nsStyleImageLayers::Layer::mMaskMode,
                    uint8_t(NS_STYLE_MASK_MODE_MATCH_SOURCE),
                    parentSVGReset->mMask.mMaskModeCount,
                    svgReset->mMask.mMaskModeCount, maxItemCount, rebuild, conditions);

  // mask-composite: enum, inherit, initial [list]
  SetImageLayerList(aContext, *aRuleData->ValueForMaskComposite(),
                    svgReset->mMask.mLayers,
                    parentSVGReset->mMask.mLayers,
                    &nsStyleImageLayers::Layer::mComposite,
                    uint8_t(NS_STYLE_MASK_COMPOSITE_ADD),
                    parentSVGReset->mMask.mCompositeCount,
                    svgReset->mMask.mCompositeCount, maxItemCount, rebuild, conditions);

  if (rebuild) {
    FillAllBackgroundLists(svgReset->mMask, maxItemCount);
  }
#else
  // mask: none | <url>
  const nsCSSValue* maskValue = aRuleData->ValueForMask();
  if (eCSSUnit_URL == maskValue->GetUnit()) {
    svgReset->mMask.mLayers[0].mSourceURI = maskValue->GetURLStructValue();
  } else if (eCSSUnit_None == maskValue->GetUnit() ||
             eCSSUnit_Initial == maskValue->GetUnit() ||
             eCSSUnit_Unset == maskValue->GetUnit()) {
    svgReset->mMask.mLayers[0].mSourceURI = nullptr;
  } else if (eCSSUnit_Inherit == maskValue->GetUnit()) {
    conditions.SetUncacheable();
    svgReset->mMask.mLayers[0].mSourceURI =
      parentSVGReset->mMask.mLayers[0].mSourceURI;
  }
#endif

  COMPUTE_END_RESET(SVGReset, svgReset)
}

const void*
nsRuleNode::ComputeVariablesData(void* aStartStruct,
                                 const nsRuleData* aRuleData,
                                 nsStyleContext* aContext,
                                 nsRuleNode* aHighestNode,
                                 const RuleDetail aRuleDetail,
                                 const RuleNodeCacheConditions aConditions)
{
  COMPUTE_START_INHERITED(Variables, variables, parentVariables)

  MOZ_ASSERT(aRuleData->mVariables,
             "shouldn't be in ComputeVariablesData if there were no variable "
             "declarations specified");

  CSSVariableResolver resolver(&variables->mVariables);
  resolver.Resolve(&parentVariables->mVariables,
                   aRuleData->mVariables);
  conditions.SetUncacheable();

  COMPUTE_END_INHERITED(Variables, variables)
}

const void*
nsRuleNode::ComputeEffectsData(void* aStartStruct,
                               const nsRuleData* aRuleData,
                               nsStyleContext* aContext,
                               nsRuleNode* aHighestNode,
                               const RuleDetail aRuleDetail,
                               const RuleNodeCacheConditions aConditions)
{
  COMPUTE_START_RESET(Effects, effects, parentEffects)

  // filter: url, none, inherit
  const nsCSSValue* filterValue = aRuleData->ValueForFilter();
  switch (filterValue->GetUnit()) {
    case eCSSUnit_Null:
      break;
    case eCSSUnit_None:
    case eCSSUnit_Initial:
    case eCSSUnit_Unset:
      effects->mFilters.Clear();
      break;
    case eCSSUnit_Inherit:
      conditions.SetUncacheable();
      effects->mFilters = parentEffects->mFilters;
      break;
    case eCSSUnit_List:
    case eCSSUnit_ListDep: {
      effects->mFilters.Clear();
      const nsCSSValueList* cur = filterValue->GetListValue();
      while (cur) {
        nsStyleFilter styleFilter;
        if (!SetStyleFilterToCSSValue(&styleFilter, cur->mValue, aContext,
                                      mPresContext, conditions)) {
          effects->mFilters.Clear();
          break;
        }
        MOZ_ASSERT(styleFilter.GetType() != NS_STYLE_FILTER_NONE,
                   "filter should be set");
        effects->mFilters.AppendElement(styleFilter);
        cur = cur->mNext;
      }
      break;
    }
    default:
      NS_NOTREACHED("unexpected unit");
  }

  // box-shadow: none, list, inherit, initial
  const nsCSSValue* boxShadowValue = aRuleData->ValueForBoxShadow();
  switch (boxShadowValue->GetUnit()) {
  case eCSSUnit_Null:
    break;

  case eCSSUnit_Initial:
  case eCSSUnit_Unset:
  case eCSSUnit_None:
    effects->mBoxShadow = nullptr;
    break;

  case eCSSUnit_Inherit:
    effects->mBoxShadow = parentEffects->mBoxShadow;
    conditions.SetUncacheable();
    break;

  case eCSSUnit_List:
  case eCSSUnit_ListDep:
    effects->mBoxShadow = GetShadowData(boxShadowValue->GetListValue(),
                                        aContext, true, mPresContext, conditions);
    break;

  default:
    MOZ_ASSERT(false, "unrecognized shadow unit");
  }

  // clip property: length, auto, inherit
  const nsCSSValue* clipValue = aRuleData->ValueForClip();
  switch (clipValue->GetUnit()) {
  case eCSSUnit_Inherit:
    conditions.SetUncacheable();
    effects->mClipFlags = parentEffects->mClipFlags;
    effects->mClip = parentEffects->mClip;
    break;

  case eCSSUnit_Initial:
  case eCSSUnit_Unset:
  case eCSSUnit_Auto:
    effects->mClipFlags = NS_STYLE_CLIP_AUTO;
    effects->mClip.SetRect(0,0,0,0);
    break;

  case eCSSUnit_Null:
    break;

  case eCSSUnit_Rect: {
    const nsCSSRect& clipRect = clipValue->GetRectValue();

    effects->mClipFlags = NS_STYLE_CLIP_RECT;

    if (clipRect.mTop.GetUnit() == eCSSUnit_Auto) {
      effects->mClip.y = 0;
      effects->mClipFlags |= NS_STYLE_CLIP_TOP_AUTO;
    }
    else if (clipRect.mTop.IsLengthUnit()) {
      effects->mClip.y = CalcLength(clipRect.mTop, aContext,
                                    mPresContext, conditions);
    }

    if (clipRect.mBottom.GetUnit() == eCSSUnit_Auto) {
      // Setting to NS_MAXSIZE for the 'auto' case ensures that
      // the clip rect is nonempty. It is important that mClip be
      // nonempty if the actual clip rect could be nonempty.
      effects->mClip.height = NS_MAXSIZE;
      effects->mClipFlags |= NS_STYLE_CLIP_BOTTOM_AUTO;
    }
    else if (clipRect.mBottom.IsLengthUnit()) {
      effects->mClip.height = CalcLength(clipRect.mBottom, aContext,
                                         mPresContext, conditions) -
                              effects->mClip.y;
    }

    if (clipRect.mLeft.GetUnit() == eCSSUnit_Auto) {
      effects->mClip.x = 0;
      effects->mClipFlags |= NS_STYLE_CLIP_LEFT_AUTO;
    }
    else if (clipRect.mLeft.IsLengthUnit()) {
      effects->mClip.x = CalcLength(clipRect.mLeft, aContext,
                                    mPresContext, conditions);
    }

    if (clipRect.mRight.GetUnit() == eCSSUnit_Auto) {
      // Setting to NS_MAXSIZE for the 'auto' case ensures that
      // the clip rect is nonempty. It is important that mClip be
      // nonempty if the actual clip rect could be nonempty.
      effects->mClip.width = NS_MAXSIZE;
      effects->mClipFlags |= NS_STYLE_CLIP_RIGHT_AUTO;
    }
    else if (clipRect.mRight.IsLengthUnit()) {
      effects->mClip.width = CalcLength(clipRect.mRight, aContext,
                                        mPresContext, conditions) -
                             effects->mClip.x;
    }
    break;
  }

  default:
    MOZ_ASSERT(false, "unrecognized clip unit");
  }

  // opacity: factor, inherit, initial
  SetFactor(*aRuleData->ValueForOpacity(), effects->mOpacity, conditions,
            parentEffects->mOpacity, 1.0f,
            SETFCT_OPACITY | SETFCT_UNSET_INITIAL);

  // mix-blend-mode: enum, inherit, initial
  SetValue(*aRuleData->ValueForMixBlendMode(), effects->mMixBlendMode,
           conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INITIAL,
           parentEffects->mMixBlendMode, NS_STYLE_BLEND_NORMAL);

  COMPUTE_END_RESET(Effects, effects)
}

const void*
nsRuleNode::GetStyleData(nsStyleStructID aSID,
                         nsStyleContext* aContext,
                         bool aComputeData)
{
  NS_ASSERTION(IsUsedDirectly(),
               "if we ever call this on rule nodes that aren't used "
               "directly, we should adjust handling of mDependentBits "
               "in some way.");
  MOZ_ASSERT(!aContext->GetCachedStyleData(aSID),
             "style context should not have cached data for struct");

  const void *data;

  // Never use cached data for animated style inside a pseudo-element;
  // see comment on cacheability in AnimValuesStyleRule::MapRuleInfoInto.
  if (!(HasAnimationData() && ParentHasPseudoElementData(aContext))) {
    data = mStyleData.GetStyleData(aSID, aContext, aComputeData);
    if (MOZ_LIKELY(data != nullptr)) {
      // For inherited structs, mark the struct (which will be set on
      // the context by our caller) as not being owned by the context.
      if (!nsCachedStyleData::IsReset(aSID)) {
        aContext->AddStyleBit(nsCachedStyleData::GetBitForSID(aSID));
      } else if (HasAnimationData()) {
        // If we have animation data, the struct should be cached on the style
        // context so that we can peek the struct.
        // See comment in AnimValuesStyleRule::MapRuleInfoInto.
        StoreStyleOnContext(aContext, aSID, const_cast<void*>(data));
      }

      return data; // We have a fully specified struct. Just return it.
    }
  }

  if (MOZ_UNLIKELY(!aComputeData))
    return nullptr;

  // Nothing is cached.  We'll have to delve further and examine our rules.
  data = WalkRuleTree(aSID, aContext);

  MOZ_ASSERT(data, "should have aborted on out-of-memory");
  return data;
}

void
nsRuleNode::GetDiscretelyAnimatedCSSValue(nsCSSPropertyID aProperty,
                                          nsCSSValue* aValue)
{
  for (nsRuleNode* node = this; node; node = node->GetParent()) {
    nsIStyleRule* rule = node->GetRule();
    if (!rule) {
      continue;
    }
    if (rule->GetDiscretelyAnimatedCSSValue(aProperty, aValue)) {
      return;
    }
  }
}

/* static */ bool
nsRuleNode::HasAuthorSpecifiedRules(nsStyleContext* aStyleContext,
                                    uint32_t ruleTypeMask,
                                    bool aAuthorColorsAllowed)
{
#ifdef MOZ_STYLO
  if (aStyleContext->StyleSource().IsServoComputedValues()) {
    NS_WARNING("stylo: nsRuleNode::HasAuthorSpecifiedRules not implemented");
    return true;
  }
#endif

  uint32_t inheritBits = 0;
  if (ruleTypeMask & NS_AUTHOR_SPECIFIED_BACKGROUND)
    inheritBits |= NS_STYLE_INHERIT_BIT(Background);

  if (ruleTypeMask & NS_AUTHOR_SPECIFIED_BORDER)
    inheritBits |= NS_STYLE_INHERIT_BIT(Border);

  if (ruleTypeMask & NS_AUTHOR_SPECIFIED_PADDING)
    inheritBits |= NS_STYLE_INHERIT_BIT(Padding);

  if (ruleTypeMask & NS_AUTHOR_SPECIFIED_TEXT_SHADOW)
    inheritBits |= NS_STYLE_INHERIT_BIT(Text);

  // properties in the SIDS, whether or not we care about them
  size_t nprops = 0,
         backgroundOffset, borderOffset, paddingOffset, textShadowOffset;

  // We put the reset properties the start of the nsCSSValue array....

  if (ruleTypeMask & NS_AUTHOR_SPECIFIED_BACKGROUND) {
    backgroundOffset = nprops;
    nprops += nsCSSProps::PropertyCountInStruct(eStyleStruct_Background);
  }

  if (ruleTypeMask & NS_AUTHOR_SPECIFIED_BORDER) {
    borderOffset = nprops;
    nprops += nsCSSProps::PropertyCountInStruct(eStyleStruct_Border);
  }

  if (ruleTypeMask & NS_AUTHOR_SPECIFIED_PADDING) {
    paddingOffset = nprops;
    nprops += nsCSSProps::PropertyCountInStruct(eStyleStruct_Padding);
  }

  // ...and the inherited properties at the end of the array.
  size_t inheritedOffset = nprops;

  if (ruleTypeMask & NS_AUTHOR_SPECIFIED_TEXT_SHADOW) {
    textShadowOffset = nprops;
    nprops += nsCSSProps::PropertyCountInStruct(eStyleStruct_Text);
  }

  void* dataStorage = alloca(nprops * sizeof(nsCSSValue));
  AutoCSSValueArray dataArray(dataStorage, nprops);

  /* We're relying on the use of |aStyleContext| not mutating it! */
  nsRuleData ruleData(inheritBits, dataArray.get(),
                      aStyleContext->PresContext(), aStyleContext);

  if (ruleTypeMask & NS_AUTHOR_SPECIFIED_BACKGROUND) {
    ruleData.mValueOffsets[eStyleStruct_Background] = backgroundOffset;
  }

  if (ruleTypeMask & NS_AUTHOR_SPECIFIED_BORDER) {
    ruleData.mValueOffsets[eStyleStruct_Border] = borderOffset;
  }

  if (ruleTypeMask & NS_AUTHOR_SPECIFIED_PADDING) {
    ruleData.mValueOffsets[eStyleStruct_Padding] = paddingOffset;
  }

  if (ruleTypeMask & NS_AUTHOR_SPECIFIED_TEXT_SHADOW) {
    ruleData.mValueOffsets[eStyleStruct_Text] = textShadowOffset;
  }

  static const nsCSSPropertyID backgroundValues[] = {
    eCSSProperty_background_color,
    eCSSProperty_background_image,
  };

  static const nsCSSPropertyID borderValues[] = {
    eCSSProperty_border_top_color,
    eCSSProperty_border_top_style,
    eCSSProperty_border_top_width,
    eCSSProperty_border_right_color,
    eCSSProperty_border_right_style,
    eCSSProperty_border_right_width,
    eCSSProperty_border_bottom_color,
    eCSSProperty_border_bottom_style,
    eCSSProperty_border_bottom_width,
    eCSSProperty_border_left_color,
    eCSSProperty_border_left_style,
    eCSSProperty_border_left_width,
    eCSSProperty_border_top_left_radius,
    eCSSProperty_border_top_right_radius,
    eCSSProperty_border_bottom_right_radius,
    eCSSProperty_border_bottom_left_radius,
  };

  static const nsCSSPropertyID paddingValues[] = {
    eCSSProperty_padding_top,
    eCSSProperty_padding_right,
    eCSSProperty_padding_bottom,
    eCSSProperty_padding_left,
  };

  static const nsCSSPropertyID textShadowValues[] = {
    eCSSProperty_text_shadow
  };

  // Number of properties we care about
  size_t nValues = 0;

  nsCSSValue* values[MOZ_ARRAY_LENGTH(backgroundValues) +
                     MOZ_ARRAY_LENGTH(borderValues) +
                     MOZ_ARRAY_LENGTH(paddingValues) +
                     MOZ_ARRAY_LENGTH(textShadowValues)];

  nsCSSPropertyID properties[MOZ_ARRAY_LENGTH(backgroundValues) +
                           MOZ_ARRAY_LENGTH(borderValues) +
                           MOZ_ARRAY_LENGTH(paddingValues) +
                           MOZ_ARRAY_LENGTH(textShadowValues)];

  if (ruleTypeMask & NS_AUTHOR_SPECIFIED_BACKGROUND) {
    for (uint32_t i = 0, i_end = ArrayLength(backgroundValues);
         i < i_end; ++i) {
      properties[nValues] = backgroundValues[i];
      values[nValues++] = ruleData.ValueFor(backgroundValues[i]);
    }
  }

  if (ruleTypeMask & NS_AUTHOR_SPECIFIED_BORDER) {
    for (uint32_t i = 0, i_end = ArrayLength(borderValues);
         i < i_end; ++i) {
      properties[nValues] = borderValues[i];
      values[nValues++] = ruleData.ValueFor(borderValues[i]);
    }
  }

  if (ruleTypeMask & NS_AUTHOR_SPECIFIED_PADDING) {
    for (uint32_t i = 0, i_end = ArrayLength(paddingValues);
         i < i_end; ++i) {
      properties[nValues] = paddingValues[i];
      values[nValues++] = ruleData.ValueFor(paddingValues[i]);
    }
  }

  if (ruleTypeMask & NS_AUTHOR_SPECIFIED_TEXT_SHADOW) {
    for (uint32_t i = 0, i_end = ArrayLength(textShadowValues);
         i < i_end; ++i) {
      properties[nValues] = textShadowValues[i];
      values[nValues++] = ruleData.ValueFor(textShadowValues[i]);
    }
  }

  nsStyleContext* styleContext = aStyleContext;

  // We need to be careful not to count styles covered up by user-important or
  // UA-important declarations.  But we do want to catch explicit inherit
  // styling in those and check our parent style context to see whether we have
  // user styling for those properties.  Note that we don't care here about
  // inheritance due to lack of a specified value, since all the properties we
  // care about are reset properties.
  bool haveExplicitUAInherit;
  do {
    haveExplicitUAInherit = false;
    for (nsRuleNode* ruleNode = styleContext->RuleNode(); ruleNode;
         ruleNode = ruleNode->GetParent()) {
      nsIStyleRule *rule = ruleNode->GetRule();
      if (rule) {
        ruleData.mLevel = ruleNode->GetLevel();
        ruleData.mIsImportantRule = ruleNode->IsImportantRule();

        rule->MapRuleInfoInto(&ruleData);

        if (ruleData.mLevel == SheetType::Agent ||
            ruleData.mLevel == SheetType::User) {
          // This is a rule whose effect we want to ignore, so if any of
          // the properties we care about were set, set them to the dummy
          // value that they'll never otherwise get.
          for (uint32_t i = 0; i < nValues; ++i) {
            nsCSSUnit unit = values[i]->GetUnit();
            if (unit != eCSSUnit_Null &&
                unit != eCSSUnit_Dummy &&
                unit != eCSSUnit_DummyInherit) {
              if (unit == eCSSUnit_Inherit ||
                  (i >= inheritedOffset && unit == eCSSUnit_Unset)) {
                haveExplicitUAInherit = true;
                values[i]->SetDummyInheritValue();
              } else {
                values[i]->SetDummyValue();
              }
            }
          }
        } else {
          // If any of the values we care about was set by the above rule,
          // we have author style.
          for (uint32_t i = 0; i < nValues; ++i) {
            if (values[i]->GetUnit() != eCSSUnit_Null &&
                values[i]->GetUnit() != eCSSUnit_Dummy && // see above
                values[i]->GetUnit() != eCSSUnit_DummyInherit) {
              // If author colors are not allowed, only claim to have
              // author-specified rules if we're looking at a non-color
              // property or if we're looking at the background color and it's
              // set to transparent.  Anything else should get set to a dummy
              // value instead.
              if (aAuthorColorsAllowed ||
                  !nsCSSProps::PropHasFlags(properties[i],
                     CSS_PROPERTY_IGNORED_WHEN_COLORS_DISABLED) ||
                  (properties[i] == eCSSProperty_background_color &&
                   !values[i]->IsNonTransparentColor())) {
                return true;
              }

              values[i]->SetDummyValue();
            }
          }
        }
      }
    }

    if (haveExplicitUAInherit) {
      // reset all the eCSSUnit_Null values to eCSSUnit_Dummy (since they're
      // not styled by the author, or by anyone else), and then reset all the
      // eCSSUnit_DummyInherit values to eCSSUnit_Null (so we will be able to
      // detect them being styled by the author) and move up to our parent
      // style context.
      for (uint32_t i = 0; i < nValues; ++i)
        if (values[i]->GetUnit() == eCSSUnit_Null)
          values[i]->SetDummyValue();
      for (uint32_t i = 0; i < nValues; ++i)
        if (values[i]->GetUnit() == eCSSUnit_DummyInherit)
          values[i]->Reset();
      styleContext = styleContext->GetParent();
    }
  } while (haveExplicitUAInherit && styleContext);

  return false;
}

/* static */ void
nsRuleNode::ComputePropertiesOverridingAnimation(
                              const nsTArray<nsCSSPropertyID>& aProperties,
                              nsStyleContext* aStyleContext,
                              nsCSSPropertyIDSet& aPropertiesOverridden)
{
  /*
   * Set up an nsRuleData with all the structs needed for all of the
   * properties in aProperties.
   */
  uint32_t structBits = 0;
  size_t nprops = 0;
  size_t offsets[nsStyleStructID_Length];
  for (size_t propIdx = 0, propEnd = aProperties.Length();
       propIdx < propEnd; ++propIdx) {
    nsCSSPropertyID prop = aProperties[propIdx];
    nsStyleStructID sid = nsCSSProps::kSIDTable[prop];
    uint32_t bit = nsCachedStyleData::GetBitForSID(sid);
    if (!(structBits & bit)) {
      structBits |= bit;
      offsets[sid] = nprops;
      nprops += nsCSSProps::PropertyCountInStruct(sid);
    }
  }

  void* dataStorage = alloca(nprops * sizeof(nsCSSValue));
  AutoCSSValueArray dataArray(dataStorage, nprops);

  // We're relying on the use of |aStyleContext| not mutating it!
  nsRuleData ruleData(structBits, dataArray.get(),
                      aStyleContext->PresContext(), aStyleContext);
  for (nsStyleStructID sid = nsStyleStructID(0);
       sid < nsStyleStructID_Length; sid = nsStyleStructID(sid + 1)) {
    if (structBits & nsCachedStyleData::GetBitForSID(sid)) {
      ruleData.mValueOffsets[sid] = offsets[sid];
    }
  }

  /*
   * Actually walk up the rule tree until we're someplace less
   * specific than animations.
   */
  for (nsRuleNode* ruleNode = aStyleContext->RuleNode(); ruleNode;
       ruleNode = ruleNode->GetParent()) {
    nsIStyleRule *rule = ruleNode->GetRule();
    if (rule) {
      ruleData.mLevel = ruleNode->GetLevel();
      ruleData.mIsImportantRule = ruleNode->IsImportantRule();

      // Transitions are the only non-!important level overriding
      // animations in the cascade ordering.  They also don't actually
      // override animations, since transitions are suppressed when both
      // are present.  And since we might not have called
      // UpdateCascadeResults (which updates when they are suppressed
      // due to the presence of animations for the same element and
      // property) for transitions yet (which will make their
      // MapRuleInfoInto skip the properties that are currently
      // animating), we should skip them explicitly.
      if (ruleData.mLevel == SheetType::Transition) {
        continue;
      }

      if (!ruleData.mIsImportantRule) {
        // We're now equal to or less than the animation level; stop.
        break;
      }

      rule->MapRuleInfoInto(&ruleData);
    }
  }

  /*
   * Fill in which properties were overridden.
   */
  for (size_t propIdx = 0, propEnd = aProperties.Length();
       propIdx < propEnd; ++propIdx) {
    nsCSSPropertyID prop = aProperties[propIdx];
    if (ruleData.ValueFor(prop)->GetUnit() != eCSSUnit_Null) {
      aPropertiesOverridden.AddProperty(prop);
    }
  }
}

/* static */
bool
nsRuleNode::ComputeColor(const nsCSSValue& aValue, nsPresContext* aPresContext,
                         nsStyleContext* aStyleContext, nscolor& aResult)
{
  MOZ_ASSERT(aValue.GetUnit() != eCSSUnit_Inherit,
             "aValue shouldn't have eCSSUnit_Inherit");
  MOZ_ASSERT(aValue.GetUnit() != eCSSUnit_Initial,
             "aValue shouldn't have eCSSUnit_Initial");
  MOZ_ASSERT(aValue.GetUnit() != eCSSUnit_Unset,
             "aValue shouldn't have eCSSUnit_Unset");

  RuleNodeCacheConditions conditions;
  bool ok = SetColor(aValue, NS_RGB(0, 0, 0), aPresContext, aStyleContext,
                     aResult, conditions);
  MOZ_ASSERT(ok || !(aPresContext && aStyleContext));
  return ok;
}

/* static */ bool
nsRuleNode::ParentHasPseudoElementData(nsStyleContext* aContext)
{
  nsStyleContext* parent = aContext->GetParent();
  return parent && parent->HasPseudoElementData();
}

/* static */ void
nsRuleNode::StoreStyleOnContext(nsStyleContext* aContext,
                                nsStyleStructID aSID,
                                void* aStruct)
{
  aContext->AddStyleBit(nsCachedStyleData::GetBitForSID(aSID));
  aContext->SetStyle(aSID, aStruct);
}

#ifdef DEBUG
bool
nsRuleNode::ContextHasCachedData(nsStyleContext* aContext,
                                 nsStyleStructID aSID)
{
  return !!aContext->GetCachedStyleData(aSID);
}
#endif
