layout/style/nsRuleNode.cpp
author Xidorn Quan <me@upsuper.org>
Sat, 13 May 2017 21:42:23 +1000
changeset 358377 95d6b2b7ed83d1e45895a0b838f1f87f78067a81
parent 357057 2567a5a33eabd81ae9d3dc2303ee8a0fe72667f6
child 359238 c1ca4c3d5273df4d532fd782e6d80f5ca85a8d43
permissions -rw-r--r--
Bug 1363699 part 1 - Make named CounterStyle objects not refcounted. r=heycam This change does the following: * Introduce a new smart pointer called CounterStylePtr which either holds an AnonymousCounterStyle strongly, or a named CounterStyle managed by CounterStyleManager weakly, and use it to replace all RefPtr<CounterStyle> around the codebase. * Rename CounterStyleManager::mCacheTable to mStyles to reflect the fact that it is used to manage all styles, not just for caching. * Add a retired styles list which collect all named CounterStyle evicted from mStyles, and post a PostRefreshObserver to destroy objects in that list after next flush. * Remove helper functions for counter style in nsStyleList and expose mCounterStyle directly, to make code simpler with the new pointer. Reason for adding a new smart pointer type rather than making their AddRef/Release behave like BuiltinCounterStyle is that, it is possible that after a flush, some stale style structs may still be alive. They can contain pointer to destroyed CounterStyle objects. Although the actual content may never be accessed anymore, RefPtr may still access the object for refcounting during destruction. MozReview-Commit-ID: xxegwSDhNb

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

#include <algorithm>
#include <functional>

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

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

#include "nsAlgorithm.h" // for clamped()
#include "nscore.h"
#include "nsCRT.h" // for IsAscii()
#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 an imgRequestProxy based on the specified value in aValue and
// returns 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.
static already_AddRefed<imgRequestProxy>
CreateImageRequest(nsPresContext* aPresContext, const nsCSSValue& aValue)
{
  RefPtr<imgRequestProxy> req =
    aValue.GetPossiblyStaticImageValue(aPresContext->Document(),
                                       aPresContext);
  return req.forget();
}

static already_AddRefed<nsStyleImageRequest>
CreateStyleImageRequest(nsPresContext* aPresContext, const nsCSSValue& aValue,
                        nsStyleImageRequest::Mode aModeFlags =
                          nsStyleImageRequest::Mode::Track)
{
  css::ImageValue* imageValue = aValue.GetImageStructValue();
  ImageTracker* imageTracker =
    (aModeFlags & nsStyleImageRequest::Mode::Track)
    ? aPresContext->Document()->ImageTracker()
    : nullptr;
  RefPtr<imgRequestProxy> proxy = CreateImageRequest(aPresContext, aValue);
  RefPtr<nsStyleImageRequest> request =
    new nsStyleImageRequest(aModeFlags, proxy, imageValue, imageTracker);
  return request.forget();
}

static void
SetStyleShapeSourceToCSSValue(StyleShapeSource* 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:
  case StyleDisplay::FlowRoot:
    // 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:
    case StyleDisplay::FlowRoot:
      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::MozBox:
      display = StyleDisplay::MozInlineBox;
      break;
    case StyleDisplay::MozStack:
      display = StyleDisplay::MozInlineStack;
      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::FloatCoeffsAlreadyNormalizedOps
{
  // Declare that we have floats as coefficients so that we unambiguously
  // resolve coeff_type (BasicCoordCalcOps and FloatCoeffsAlreadyNormalizedOps
  // both have |typedef float coeff_type|).
  typedef float coeff_type;

  // 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);
}

/* static */
already_AddRefed<nsFontMetrics>
nsRuleNode::GetMetricsFor(nsPresContext* aPresContext,
                          bool aIsVertical,
                          const nsStyleFont* aStyleFont,
                          nscoord aFontSize,
                          bool aUseUserFontSet)
{
  nsFont font = aStyleFont->mFont;
  font.size = aFontSize;
  gfxFont::Orientation orientation
    = aIsVertical ? gfxFont::eVertical : gfxFont::eHorizontal;
  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 */
already_AddRefed<nsFontMetrics>
nsRuleNode::GetMetricsFor(nsPresContext* aPresContext,
                          nsStyleContext* aStyleContext,
                          const nsStyleFont* aStyleFont,
                          nscoord aFontSize, // overrides value from aStyleFont
                          bool aUseUserFontSet)
{
  bool isVertical = false;
  if (aStyleContext) {
    WritingMode wm(aStyleContext);
    if (wm.IsVertical() && !wm.IsSideways()) {
      isVertical = true;
    }
  }
  return nsRuleNode::GetMetricsFor(aPresContext, isVertical, aStyleFont,
                                   aFontSize, aUseUserFontSet);
}

/* static */
void
nsRuleNode::FixupNoneGeneric(nsFont* aFont,
                             const nsPresContext* aPresContext,
                             uint8_t aGenericFontID,
                             const nsFont* aDefaultVariableFont)
{
  bool useDocumentFonts =
    aPresContext->GetCachedBoolPref(kPresContext_UseDocumentFonts);
  if (aGenericFontID == kGenericFont_NONE ||
      (!useDocumentFonts && (aGenericFontID == kGenericFont_cursive ||
                             aGenericFontID == kGenericFont_fantasy))) {
    FontFamilyType defaultGeneric =
      aDefaultVariableFont->fontlist.FirstGeneric();
    MOZ_ASSERT(aDefaultVariableFont->fontlist.Length() == 1 &&
               (defaultGeneric == eFamily_serif ||
                defaultGeneric == eFamily_sans_serif));
    if (defaultGeneric != eFamily_none) {
      if (useDocumentFonts) {
        aFont->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->fontlist.PrioritizeFirstGeneric()) {
          aFont->fontlist.PrependGeneric(defaultGeneric);
        }
      }
    }
  } else {
    aFont->fontlist.SetDefaultFontType(eFamily_none);
  }
}

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.
        // NOTE: We should not call ResolveStyleFor() against the root element
        // to obtain the root style here because it may lead to reentrant call
        // of nsStyleSet::GetContext().
        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 =
        nsRuleNode::GetMetricsFor(aPresContext, aStyleContext, styleFont,
                                  aFontSize, aUseUserFontSet);
      aConditions.SetUncacheable();
      return ScaleCoordRound(aValue, float(fm->XHeight()));
    }
    case eCSSUnit_Char: {
      aPresContext->SetUsesExChUnits(true);
      RefPtr<nsFontMetrics> fm =
        nsRuleNode::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::FloatCoeffsAlreadyNormalizedOps
{
  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();
      DebugOnly<nsresult> rv =
        LookAndFeel::GetColor(colorID, useStandinsForNativeColors, &aResult);
      MOZ_ASSERT(NS_SUCCEEDED(rv),
                 "Unknown enum colors should have been rejected by parser");
      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 (unit == eCSSUnit_Auto) {
    aResult = StyleComplexColor::Auto();
  } else {
    nscolor resultColor;
    if (!SetColor(aValue, aParentColor.mColor, aPresContext,
                  nullptr, resultColor, aConditions)) {
      MOZ_ASSERT_UNREACHABLE("Unknown color value");
      return;
    }
    aResult = StyleComplexColor::FromColor(resultColor);
  }
}

template<UnsetAction UnsetTo>
static Maybe<nscoord>
ComputeLineWidthValue(const nsCSSValue& aValue,
                      const nscoord aParentCoord,
                      const nscoord aInitialCoord,
                      nsStyleContext* aStyleContext,
                      nsPresContext* aPresContext,
                      RuleNodeCacheConditions& aConditions)
{
  nsCSSUnit unit = aValue.GetUnit();
  if (unit == eCSSUnit_Initial ||
      (UnsetTo == eUnsetInitial && unit == eCSSUnit_Unset)) {
    return Some(aInitialCoord);
  } else if (unit == eCSSUnit_Inherit ||
             (UnsetTo == eUnsetInherit && unit == eCSSUnit_Unset)) {
    aConditions.SetUncacheable();
    return Some(aParentCoord);
  } else if (unit == eCSSUnit_Enumerated) {
    NS_ASSERTION(aValue.GetIntValue() == NS_STYLE_BORDER_WIDTH_THIN ||
                 aValue.GetIntValue() == NS_STYLE_BORDER_WIDTH_MEDIUM ||
                 aValue.GetIntValue() == NS_STYLE_BORDER_WIDTH_THICK,
                 "Unexpected line-width keyword!");
    return Some(nsPresContext::GetBorderWidthForKeyword(aValue.GetIntValue()));
  } else if (aValue.IsLengthUnit() ||
             aValue.IsCalcUnit()) {
    nscoord len =
      CalcLength(aValue, aStyleContext, aPresContext, aConditions);
    if (len < 0) {
      NS_ASSERTION(aValue.IsCalcUnit(),
                   "Parser should have rejected negative length!");
      len = 0;
    }
    return Some(len);
  } else {
    NS_ASSERTION(unit == eCSSUnit_Null,
                 "Missing case handling for line-width computing!");
    return Maybe<nscoord>(Nothing());
  }
}

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;
  aResult.mMozLegacySyntax = gradient->mIsMozLegacySyntax;

  // 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) {
    nsPresContext* pc = aStyleContext->PresContext();
    aResult.SetImageRequest(CreateStyleImageRequest(pc, 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();

  nsPresContext* presContext = aStyleContext->PresContext();
  switch (aValue.GetUnit()) {
    case eCSSUnit_Image:
      aResult.SetImageRequest(CreateStyleImageRequest(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, presContext, aStyleContext, *gradient, aConditions);
      aResult.SetGradientData(gradient);
      break;
    }
    case eCSSUnit_Element:
    {
      nsCOMPtr<nsIAtom> atom = NS_Atomize(aValue.GetStringBufferValue());
      aResult.SetElementId(atom.forget());
      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 = 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
      aResult.SetURLValue(do_AddRef(aValue.GetURLStructValue()));
      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(StyleHyphens, None, Auto)
  DEFINE_ENUM_CLASS_SETTER(StyleTextJustify, None, InterCharacter)
  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)
  DEFINE_ENUM_CLASS_SETTER(StyleGeometryBox, BorderBox, ViewBox)
#ifdef MOZ_XUL
  DEFINE_ENUM_CLASS_SETTER(StyleDisplay, None, MozPopup)
#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();
  }
}

AutoCSSValueArray::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::~AutoCSSValueArray()
{
  for (size_t i = 0; i < mCount; ++i) {
    mArray[i].~nsCSSValue();
  }
}

/* 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 (!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;
}

struct SetFontSizeCalcOps : public css::BasicCoordCalcOps,
                            public css::FloatCoeffsAlreadyNormalizedOps
{
  // Declare that we have floats as coefficients so that we unambiguously
  // resolve coeff_type (BasicCoordCalcOps and FloatCoeffsAlreadyNormalizedOps
  // both have |typedef float coeff_type|).
  typedef float coeff_type;

  // 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);
      }

      float factor = (NS_STYLE_FONT_SIZE_LARGER == value) ? 1.2f : (1.0f / 1.2f);

      *aSize = parentSize * factor;

    } 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::ComputeSystemFont(nsFont* aSystemFont, LookAndFeel::FontID aFontID,
                              const nsPresContext* aPresContext,
                              const nsFont* aDefaultVariableFont)
{
  gfxFontStyle fontStyle;
  float devPerCSS =
    (float)nsPresContext::AppUnitsPerCSSPixel() /
    aPresContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom();
  nsAutoString systemFontName;
  if (LookAndFeel::GetFont(aFontID, systemFontName, fontStyle, devPerCSS)) {
    systemFontName.Trim("\"'");
    aSystemFont->fontlist = FontFamilyList(systemFontName, eUnquotedName);
    aSystemFont->fontlist.SetDefaultFontType(eFamily_none);
    aSystemFont->style = fontStyle.style;
    aSystemFont->systemFont = fontStyle.systemFont;
    aSystemFont->weight = fontStyle.weight;
    aSystemFont->stretch = fontStyle.stretch;
    aSystemFont->size =
      NSFloatPixelsToAppUnits(fontStyle.size,
                              aPresContext->DeviceContext()->
                                AppUnitsPerDevPixelAtUnitFullZoom());
    //aSystemFont->langGroup = fontStyle.langGroup;
    aSystemFont->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 (aFontID == LookAndFeel::eFont_Field ||
        aFontID == LookAndFeel::eFont_Button ||
        aFontID == 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
      aSystemFont->size =
        std::max(aDefaultVariableFont->size -
                 nsPresContext::CSSPointsToAppUnits(2), 0);
    }
#endif
  }
}

/* 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()) {
    LookAndFeel::FontID fontID =
      (LookAndFeel::FontID)systemFontValue->GetIntValue();
    ComputeSystemFont(&systemFont, fontID, aPresContext, defaultVariableFont);
  }

  // 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
    nsRuleNode::FixupNoneGeneric(&aFont->mFont, aPresContext,
                                 aGenericFontID, defaultVariableFont);

    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
  SetValue(*aRuleData->ValueForFontSynthesis(),
           aFont->mFont.synthesis, aConditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           aParentFont->mFont.synthesis,
           defaultVariableFont->synthesis,
           Unused, /* none */ 0, Unused, Unused);

  // 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-variation-settings
  const nsCSSValue* variationSettingsValue =
    aRuleData->ValueForFontVariationSettings();

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

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

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

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

    case eCSSUnit_PairList:
    case eCSSUnit_PairListDep:
      ComputeFontVariations(variationSettingsValue->GetPairListValue(),
                            aFont->mFont.fontVariationSettings);
      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 = NO_FONT_LANGUAGE_OVERRIDE;
  } else if (eCSSUnit_System_Font == languageOverrideValue->GetUnit()) {
    aFont->mFont.languageOverride = systemFont.languageOverride;
  } else if (eCSSUnit_String == languageOverrideValue->GetUnit()) {
    nsAutoString lang;
    languageOverrideValue->GetStringValue(lang);
    aFont->mFont.languageOverride = ParseFontLanguageOverride(lang);
  }

  // -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 inline void
AssertValidFontTag(const nsString& aString)
{
  // To be valid as a font feature tag, a string MUST be:
  MOZ_ASSERT(aString.Length() == 4 &&              // (1) exactly 4 chars long
             NS_IsAscii(aString.BeginReading()) && // (2) entirely ASCII
             isprint(aString[0]) &&                // (3) all printable chars
             isprint(aString[1]) &&
             isprint(aString[2]) &&
             isprint(aString[3]));
}

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

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

    // tag is a 4-byte ASCII sequence
    nsAutoString tag;
    p->mXValue.GetStringValue(tag);
    AssertValidFontTag(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);
  }
}

/* static */ void
nsRuleNode::ComputeFontVariations(const nsCSSValuePairList* aVariationsList,
                                  nsTArray<gfxFontVariation>& aVariationSettings)
{
  aVariationSettings.Clear();
  for (const nsCSSValuePairList* p = aVariationsList; p; p = p->mNext) {
    gfxFontVariation var;

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

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

    // value
    NS_ASSERTION(p->mYValue.GetUnit() == eCSSUnit_Number,
                 "should have found a number unit");
    var.mValue = p->mYValue.GetFloatValue();

    aVariationSettings.AppendElement(var);
  }
}

// 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)
}

/*static*/ uint32_t
nsRuleNode::ParseFontLanguageOverride(const nsAString& aLangTag)
{
  if (!aLangTag.Length() || aLangTag.Length() > 4) {
    return NO_FONT_LANGUAGE_OVERRIDE;
  }
  uint32_t index, result = 0;
  for (index = 0; index < aLangTag.Length(); ++index) {
    char16_t ch = aLangTag[index];
    if (!nsCRT::IsAscii(ch)) { // valid tags are pure ASCII
      return NO_FONT_LANGUAGE_OVERRIDE;
    }
    result = (result << 8) + ch;
  }
  while (index++ < 4) {
    result = (result << 8) + 0x20;
  }
  return result;
}

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 LengthNumberCalcObj
{
  float mValue;
  bool mIsNumber;
};

struct LengthNumberCalcOps : public css::FloatCoeffsAlreadyNormalizedOps
{
  typedef LengthNumberCalcObj result_type;
  nsStyleContext* const mStyleContext;
  nsPresContext* const mPresContext;
  RuleNodeCacheConditions& mConditions;

  LengthNumberCalcOps(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);

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

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

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

  result_type ComputeLeafValue(const nsCSSValue& aValue)
  {
    LengthNumberCalcObj result;
    if (aValue.IsLengthUnit()) {
      result.mIsNumber = false;
      result.mValue = CalcLength(aValue, mStyleContext,
                                      mPresContext, mConditions);
    }
    else if (eCSSUnit_Number == aValue.GetUnit()) {
      result.mIsNumber = true;
      result.mValue = aValue.GetFloatValue();
    } else {
      MOZ_ASSERT(false, "unexpected value");
      result.mIsNumber = true;
      result.mValue = 1.0f;
    }

    return result;
  }
};

struct SetLineHeightCalcOps : public LengthNumberCalcOps
{
  SetLineHeightCalcOps(nsStyleContext* aStyleContext,
                       nsPresContext* aPresContext,
                       RuleNodeCacheConditions& aConditions)
    : LengthNumberCalcOps(aStyleContext, aPresContext, aConditions)
  {
  }

  result_type ComputeLeafValue(const nsCSSValue& aValue)
  {
    LengthNumberCalcObj result;
    if (aValue.IsLengthUnit()) {
      result.mIsNumber = false;
      result.mValue = CalcLength(aValue, mStyleContext,
                                      mPresContext, mConditions);
    }
    else if (eCSSUnit_Percent == aValue.GetUnit()) {
      mConditions.SetUncacheable();
      result.mIsNumber = false;
      nscoord fontSize = mStyleContext->StyleFont()->mFont.size;
      result.mValue = fontSize * aValue.GetPercentValue();
    }
    else if (eCSSUnit_Number == aValue.GetUnit()) {
      result.mIsNumber = true;
      result.mValue = aValue.GetFloatValue();
    } else {
      MOZ_ASSERT(false, "unexpected value");
      result.mIsNumber = true;
      result.mValue = 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: number, length, calc, inherit
  const nsCSSValue* tabSizeValue = aRuleData->ValueForTabSize();
  if (tabSizeValue->GetUnit() == eCSSUnit_Initial) {
    text->mTabSize = nsStyleCoord(float(NS_STYLE_TABSIZE_INITIAL), eStyleUnit_Factor);
  } else if (eCSSUnit_Calc == tabSizeValue->GetUnit()) {
    LengthNumberCalcOps ops(aContext, mPresContext, conditions);
    LengthNumberCalcObj obj = css::ComputeCalc(*tabSizeValue, ops);
    float value = obj.mValue < 0 ? 0 : obj.mValue;
    if (obj.mIsNumber) {
      text->mTabSize.SetFactorValue(value);
    } else {
      text->mTabSize.SetCoordValue(
        NSToCoordRoundWithClamp(value));
    }
  } else {
    SetCoord(*tabSizeValue, text->mTabSize, parentText->mTabSize,
             SETCOORD_LH | SETCOORD_FACTOR | SETCOORD_UNSET_INHERIT,
             aContext, mPresContext, conditions);
  }

  // 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);
    LengthNumberCalcObj obj = css::ComputeCalc(*lineHeightValue, ops);
    if (obj.mIsNumber) {
      text->mLineHeight.SetFactorValue(obj.mValue);
    } else {
      text->mLineHeight.SetCoordValue(
        NSToCoordRoundWithClamp(obj.mValue));
    }
  }
  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-justify: enum, inherit, initial
  SetValue(*aRuleData->ValueForTextJustify(), text->mTextJustify, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentText->mTextJustify,
           StyleTextJustify::Auto);

  // 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,
           StyleHyphens::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: enum, inherit, initial
  SetValue(*aRuleData->ValueForTextSizeAdjust(),
           text->mTextSizeAdjust, conditions,
           SETVAL_ENUMERATED | SETVAL_UNSET_INHERIT,
           parentText->mTextSizeAdjust,
           NS_STYLE_TEXT_SIZE_ADJUST_AUTO);

  // 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
  Maybe<nscoord> coord =
    ComputeLineWidthValue<eUnsetInherit>(
      *aRuleData->ValueForWebkitTextStrokeWidth(),
      parentText->mWebkitTextStrokeWidth, 0,
      aContext, mPresContext, conditions);
  if (coord.isSome()) {
    text->mWebkitTextStrokeWidth = *coord;
  }

  // -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()) {
    text->mTextDecorationLine = decorationLineValue->GetIntValue();
  } 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();
        nsCursorImage* item = ui->mCursorImages.AppendElement();
        item->mImage =
          CreateStyleImageRequest(aContext->PresContext(), arr->Item(0),
                                  nsStyleImageRequest::Mode::Discard);
        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);

  // caret-color: auto, color, inherit
  const nsCSSValue* caretColorValue = aRuleData->ValueForCaretColor();
  SetComplexColor<eUnsetInherit>(*caretColorValue,
                                 parentUI->mCaretColor,
                                 StyleComplexColor::Auto(),
                                 mPresContext,
                                 ui->mCaretColor, conditions);

  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;
    case eCSSUnit_Function:
      {
        nsCSSValue::Array* array = aValue.GetArrayValue();
        NS_ASSERTION(array && array->Count() == 2, "Need 2 items");
        NS_ASSERTION(array->Item(0).GetKeywordValue() == eCSSKeyword_frames,
                     "should be frames function");
        NS_ASSERTION(array->Item(1).GetUnit() == eCSSUnit_Integer,
                     "unexpected frames function value");
        aResult = nsTimingFunction(nsTimingFunction::Type::Frames,
                                   array->Item(1).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_String:
        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());