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

/* parsing of CSS stylesheets, based on a token stream from the CSS scanner */

#include "mozilla/ArrayUtils.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Move.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/TypedEnumBits.h"

#include <algorithm> // for std::stable_sort
#include <limits> // for std::numeric_limits

#include "nsCSSParser.h"
#include "nsAlgorithm.h"
#include "nsCSSProps.h"
#include "nsCSSKeywords.h"
#include "nsCSSScanner.h"
#include "mozilla/css/ErrorReporter.h"
#include "mozilla/css/Loader.h"
#include "mozilla/css/StyleRule.h"
#include "mozilla/css/ImportRule.h"
#include "nsCSSRules.h"
#include "mozilla/css/NameSpaceRule.h"
#include "nsTArray.h"
#include "mozilla/StyleSheetInlines.h"
#include "mozilla/css/Declaration.h"
#include "nsStyleConsts.h"
#include "nsNetUtil.h"
#include "nsCOMPtr.h"
#include "nsString.h"
#include "nsIAtom.h"
#include "nsColor.h"
#include "nsCSSPseudoClasses.h"
#include "nsCSSPseudoElements.h"
#include "nsCSSAnonBoxes.h"
#include "nsNameSpaceManager.h"
#include "nsXMLNameSpaceMap.h"
#include "nsError.h"
#include "nsIMediaList.h"
#include "nsStyleUtil.h"
#include "nsIPrincipal.h"
#include "nsICSSUnprefixingService.h"
#include "mozilla/Sprintf.h"
#include "nsContentUtils.h"
#include "nsAutoPtr.h"
#include "CSSCalc.h"
#include "nsMediaFeatures.h"
#include "nsLayoutUtils.h"
#include "mozilla/Preferences.h"
#include "nsRuleData.h"
#include "mozilla/CSSVariableValues.h"
#include "mozilla/dom/AnimationEffectReadOnlyBinding.h"
#include "mozilla/dom/URL.h"
#include "gfxFontFamilyList.h"

using namespace mozilla;
using namespace mozilla::css;

typedef nsCSSProps::KTableEntry KTableEntry;

// pref-backed bool values (hooked up in nsCSSParser::Startup)
static bool sOpentypeSVGEnabled;
static bool sWebkitPrefixedAliasesEnabled;
static bool sWebkitDevicePixelRatioEnabled;
static bool sUnprefixingServiceEnabled;
#ifdef NIGHTLY_BUILD
static bool sUnprefixingServiceGloballyWhitelisted;
#endif
static bool sMozGradientsEnabled;
static bool sControlCharVisibility;

const uint32_t
nsCSSProps::kParserVariantTable[eCSSProperty_COUNT_no_shorthands] = {
#define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, \
                 stylestruct_, stylestructoffset_, animtype_)                 \
  parsevariant_,
#define CSS_PROP_LIST_INCLUDE_LOGICAL
#include "nsCSSPropList.h"
#undef CSS_PROP_LIST_INCLUDE_LOGICAL
#undef CSS_PROP
};

// Maximum number of repetitions for the repeat() function
// in the grid-template-rows and grid-template-columns properties,
// to limit high memory usage from small stylesheets.
// Must be a positive integer. Should be large-ish.
#define GRID_TEMPLATE_MAX_REPETITIONS 10000

// End-of-array marker for mask arguments to ParseBitmaskValues
#define MASK_END_VALUE  (-1)

enum class CSSParseResult : int32_t {
  // Parsed something successfully:
  Ok,
  // Did not find what we were looking for, but did not consume any token:
  NotFound,
  // Unexpected token or token value, too late for UngetToken() to be enough:
  Error
};

enum class GridTrackSizeFlags {
  eDefaultTrackSize = 0x0,
  eFixedTrackSize   = 0x1, // parse a <fixed-size> instead of <track-size>
};
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(GridTrackSizeFlags)

enum class GridTrackListFlags {
  eDefaultTrackList  = 0x0, // parse a <track-list>
  eExplicitTrackList = 0x1, // parse an <explicit-track-list> instead
};
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(GridTrackListFlags)

namespace {

// Rule processing function
typedef void (* RuleAppendFunc) (css::Rule* aRule, void* aData);
static void AssignRuleToPointer(css::Rule* aRule, void* aPointer);
static void AppendRuleToSheet(css::Rule* aRule, void* aParser);

struct CSSParserInputState {
  nsCSSScannerPosition mPosition;
  nsCSSToken mToken;
  bool mHavePushBack;
};

static_assert(css::eAuthorSheetFeatures == 0 &&
              css::eUserSheetFeatures == 1 &&
              css::eAgentSheetFeatures == 2,
              "sheet parsing mode constants won't fit "
              "in CSSParserImpl::mParsingMode");

// Your basic top-down recursive descent style parser
// The exposed methods and members of this class are precisely those
// needed by nsCSSParser, far below.
class CSSParserImpl {
public:
  CSSParserImpl();
  ~CSSParserImpl();

  nsresult SetStyleSheet(CSSStyleSheet* aSheet);

  nsIDocument* GetDocument();

  nsresult SetQuirkMode(bool aQuirkMode);

  nsresult SetChildLoader(mozilla::css::Loader* aChildLoader);

  // Clears everything set by the above Set*() functions.
  void Reset();

  nsresult ParseSheet(const nsAString& aInput,
                      nsIURI*          aSheetURI,
                      nsIURI*          aBaseURI,
                      nsIPrincipal*    aSheetPrincipal,
                      uint32_t         aLineNumber,
                      css::LoaderReusableStyleSheets* aReusableSheets);

  already_AddRefed<css::Declaration>
           ParseStyleAttribute(const nsAString&  aAttributeValue,
                               nsIURI*           aDocURL,
                               nsIURI*           aBaseURL,
                               nsIPrincipal*     aNodePrincipal);

  nsresult ParseDeclarations(const nsAString&  aBuffer,
                             nsIURI*           aSheetURL,
                             nsIURI*           aBaseURL,
                             nsIPrincipal*     aSheetPrincipal,
                             css::Declaration* aDeclaration,
                             bool*           aChanged);

  nsresult ParseRule(const nsAString&        aRule,
                     nsIURI*                 aSheetURL,
                     nsIURI*                 aBaseURL,
                     nsIPrincipal*           aSheetPrincipal,
                     css::Rule**             aResult);

  void ParseProperty(const nsCSSPropertyID aPropID,
                     const nsAString& aPropValue,
                     nsIURI* aSheetURL,
                     nsIURI* aBaseURL,
                     nsIPrincipal* aSheetPrincipal,
                     css::Declaration* aDeclaration,
                     bool* aChanged,
                     bool aIsImportant,
                     bool aIsSVGMode);
  void ParseLonghandProperty(const nsCSSPropertyID aPropID,
                             const nsAString& aPropValue,
                             nsIURI* aSheetURL,
                             nsIURI* aBaseURL,
                             nsIPrincipal* aSheetPrincipal,
                             nsCSSValue& aValue);

  bool ParseTransformProperty(const nsAString& aPropValue,
                              bool aDisallowRelativeValues,
                              nsCSSValue& aResult);

  void ParseMediaList(const nsSubstring& aBuffer,
                      nsIURI* aURL, // for error reporting
                      uint32_t aLineNumber, // for error reporting
                      nsMediaList* aMediaList,
                      bool aHTMLMode);

  bool ParseSourceSizeList(const nsAString& aBuffer,
                           nsIURI* aURI, // for error reporting
                           uint32_t aLineNumber, // for error reporting
                           InfallibleTArray< nsAutoPtr<nsMediaQuery> >& aQueries,
                           InfallibleTArray<nsCSSValue>& aValues,
                           bool aHTMLMode);

  void ParseVariable(const nsAString& aVariableName,
                     const nsAString& aPropValue,
                     nsIURI* aSheetURL,
                     nsIURI* aBaseURL,
                     nsIPrincipal* aSheetPrincipal,
                     css::Declaration* aDeclaration,
                     bool* aChanged,
                     bool aIsImportant);

  bool ParseFontFamilyListString(const nsSubstring& aBuffer,
                                 nsIURI* aURL, // for error reporting
                                 uint32_t aLineNumber, // for error reporting
                                 nsCSSValue& aValue);

  bool ParseColorString(const nsSubstring& aBuffer,
                        nsIURI* aURL, // for error reporting
                        uint32_t aLineNumber, // for error reporting
                        nsCSSValue& aValue,
                        bool aSuppressErrors /* false */);

  bool ParseMarginString(const nsSubstring& aBuffer,
                         nsIURI* aURL, // for error reporting
                         uint32_t aLineNumber, // for error reporting
                         nsCSSValue& aValue,
                         bool aSuppressErrors /* false */);

  nsresult ParseSelectorString(const nsSubstring& aSelectorString,
                               nsIURI* aURL, // for error reporting
                               uint32_t aLineNumber, // for error reporting
                               nsCSSSelectorList **aSelectorList);

  already_AddRefed<nsCSSKeyframeRule>
  ParseKeyframeRule(const nsSubstring& aBuffer,
                    nsIURI*            aURL,
                    uint32_t           aLineNumber);

  bool ParseKeyframeSelectorString(const nsSubstring& aSelectorString,
                                   nsIURI* aURL, // for error reporting
                                   uint32_t aLineNumber, // for error reporting
                                   InfallibleTArray<float>& aSelectorList);

  bool EvaluateSupportsDeclaration(const nsAString& aProperty,
                                   const nsAString& aValue,
                                   nsIURI* aDocURL,
                                   nsIURI* aBaseURL,
                                   nsIPrincipal* aDocPrincipal);

  bool EvaluateSupportsCondition(const nsAString& aCondition,
                                 nsIURI* aDocURL,
                                 nsIURI* aBaseURL,
                                 nsIPrincipal* aDocPrincipal);

  bool ParseCounterStyleName(const nsAString& aBuffer,
                             nsIURI* aURL,
                             nsAString& aName);

  bool ParseCounterDescriptor(nsCSSCounterDesc aDescID,
                              const nsAString& aBuffer,
                              nsIURI* aSheetURL,
                              nsIURI* aBaseURL,
                              nsIPrincipal* aSheetPrincipal,
                              nsCSSValue& aValue);

  bool ParseFontFaceDescriptor(nsCSSFontDesc aDescID,
                               const nsAString& aBuffer,
                               nsIURI* aSheetURL,
                               nsIURI* aBaseURL,
                               nsIPrincipal* aSheetPrincipal,
                               nsCSSValue& aValue);

  bool IsValueValidForProperty(const nsCSSPropertyID aPropID,
                               const nsAString& aPropValue);

  typedef nsCSSParser::VariableEnumFunc VariableEnumFunc;

  /**
   * Parses a CSS token stream value and invokes a callback function for each
   * variable reference that is encountered.
   *
   * @param aPropertyValue The CSS token stream value.
   * @param aFunc The callback function to invoke; its parameters are the
   *   variable name found and the aData argument passed in to this function.
   * @param aData User data to pass in to the callback.
   * @return Whether aPropertyValue could be parsed as a valid CSS token stream
   *   value (e.g., without syntactic errors in variable references).
   */
  bool EnumerateVariableReferences(const nsAString& aPropertyValue,
                                   VariableEnumFunc aFunc,
                                   void* aData);

  /**
   * Parses aPropertyValue as a CSS token stream value and resolves any
   * variable references using the variables in aVariables.
   *
   * @param aPropertyValue The CSS token stream value.
   * @param aVariables The set of variable values to use when resolving variable
   *   references.
   * @param aResult Out parameter that gets the resolved value.
   * @param aFirstToken Out parameter that gets the type of the first token in
   *   aResult.
   * @param aLastToken Out parameter that gets the type of the last token in
   *   aResult.
   * @return Whether aResult could be parsed successfully and variable reference
   *   substitution succeeded.
   */
  bool ResolveVariableValue(const nsAString& aPropertyValue,
                            const CSSVariableValues* aVariables,
                            nsString& aResult,
                            nsCSSTokenSerializationType& aFirstToken,
                            nsCSSTokenSerializationType& aLastToken);

  /**
   * Parses a string as a CSS token stream value for particular property,
   * resolving any variable references.  The parsed property value is stored
   * in the specified nsRuleData object.  If aShorthandPropertyID has a value
   * other than eCSSProperty_UNKNOWN, this is the property that will be parsed;
   * otherwise, aPropertyID will be parsed.  Either way, only aPropertyID,
   * a longhand property, will be copied over to the rule data.
   *
   * If the property cannot be parsed, it will be treated as if 'initial' or
   * 'inherit' were specified, for non-inherited and inherited properties
   * respectively.
   *
   * @param aPropertyID The ID of the longhand property whose value is to be
   *   copied to the rule data.
   * @param aShorthandPropertyID The ID of the shorthand property to be parsed.
   *   If a longhand property is to be parsed, aPropertyID is that property,
   *   and aShorthandPropertyID must be eCSSProperty_UNKNOWN.
   * @param aValue The CSS token stream value.
   * @param aVariables The set of variable values to use when resolving variable
   *   references.
   * @param aRuleData The rule data object into which parsed property value for
   *   aPropertyID will be stored.
   */
  void ParsePropertyWithVariableReferences(nsCSSPropertyID aPropertyID,
                                           nsCSSPropertyID aShorthandPropertyID,
                                           const nsAString& aValue,
                                           const CSSVariableValues* aVariables,
                                           nsRuleData* aRuleData,
                                           nsIURI* aDocURL,
                                           nsIURI* aBaseURL,
                                           nsIPrincipal* aDocPrincipal,
                                           CSSStyleSheet* aSheet,
                                           uint32_t aLineNumber,
                                           uint32_t aLineOffset);

  bool AgentRulesEnabled() const {
    return mParsingMode == css::eAgentSheetFeatures;
  }
  bool ChromeRulesEnabled() const {
    return mIsChrome;
  }
  bool UserRulesEnabled() const {
    return mParsingMode == css::eAgentSheetFeatures ||
           mParsingMode == css::eUserSheetFeatures;
  }

  CSSEnabledState EnabledState() const {
    static_assert(int(CSSEnabledState::eForAllContent) == 0,
                  "CSSEnabledState::eForAllContent should be zero for "
                  "this bitfield to work");
    CSSEnabledState enabledState = CSSEnabledState::eForAllContent;
    if (AgentRulesEnabled()) {
      enabledState |= CSSEnabledState::eInUASheets;
    }
    if (mIsChrome) {
      enabledState |= CSSEnabledState::eInChrome;
    }
    return enabledState;
  }

  nsCSSPropertyID LookupEnabledProperty(const nsAString& aProperty) {
    return nsCSSProps::LookupProperty(aProperty, EnabledState());
  }

protected:
  class nsAutoParseCompoundProperty;
  friend class nsAutoParseCompoundProperty;

  class nsAutoFailingSupportsRule;
  friend class nsAutoFailingSupportsRule;

  class nsAutoSuppressErrors;
  friend class nsAutoSuppressErrors;

  void AppendRule(css::Rule* aRule);
  friend void AppendRuleToSheet(css::Rule*, void*); // calls AppendRule

  /**
   * This helper class automatically calls SetParsingCompoundProperty in its
   * constructor and takes care of resetting it to false in its destructor.
   */
  class nsAutoParseCompoundProperty {
    public:
      explicit nsAutoParseCompoundProperty(CSSParserImpl* aParser) : mParser(aParser)
      {
        NS_ASSERTION(!aParser->IsParsingCompoundProperty(),
                     "already parsing compound property");
        NS_ASSERTION(aParser, "Null parser?");
        aParser->SetParsingCompoundProperty(true);
      }

      ~nsAutoParseCompoundProperty()
      {
        mParser->SetParsingCompoundProperty(false);
      }
    private:
      CSSParserImpl* mParser;
  };

  /**
   * This helper class conditionally sets mInFailingSupportsRule to
   * true if aCondition = false, and resets it to its original value in its
   * destructor.  If we are already somewhere within a failing @supports
   * rule, passing in aCondition = true does not change mInFailingSupportsRule.
   */
  class nsAutoFailingSupportsRule {
    public:
      nsAutoFailingSupportsRule(CSSParserImpl* aParser,
                                bool aCondition)
        : mParser(aParser),
          mOriginalValue(aParser->mInFailingSupportsRule)
      {
        if (!aCondition) {
          mParser->mInFailingSupportsRule = true;
        }
      }

      ~nsAutoFailingSupportsRule()
      {
        mParser->mInFailingSupportsRule = mOriginalValue;
      }

    private:
      CSSParserImpl* mParser;
      bool mOriginalValue;
  };

  /**
   * Auto class to set aParser->mSuppressErrors to the specified value
   * and restore it to its original value later.
   */
  class nsAutoSuppressErrors {
    public:
      explicit nsAutoSuppressErrors(CSSParserImpl* aParser,
                                    bool aSuppressErrors = true)
        : mParser(aParser),
          mOriginalValue(aParser->mSuppressErrors)
      {
        mParser->mSuppressErrors = aSuppressErrors;
      }

      ~nsAutoSuppressErrors()
      {
        mParser->mSuppressErrors = mOriginalValue;
      }

    private:
      CSSParserImpl* mParser;
      bool mOriginalValue;
  };

  /**
   * RAII class to set aParser->mInSupportsCondition to true and restore it
   * to false later.
   */
  class MOZ_RAII nsAutoInSupportsCondition
  {
  public:
    explicit nsAutoInSupportsCondition(CSSParserImpl* aParser)
      : mParser(aParser)
    {
      MOZ_ASSERT(!aParser->mInSupportsCondition,
                 "nsAutoInSupportsCondition is not designed to be used "
                 "re-entrantly");
      mParser->mInSupportsCondition = true;
    }

    ~nsAutoInSupportsCondition()
    {
      mParser->mInSupportsCondition = false;
    }

  private:
    CSSParserImpl* const mParser;
  };

  // the caller must hold on to aString until parsing is done
  void InitScanner(nsCSSScanner& aScanner,
                   css::ErrorReporter& aReporter,
                   nsIURI* aSheetURI, nsIURI* aBaseURI,
                   nsIPrincipal* aSheetPrincipal);
  void ReleaseScanner(void);

  /**
   * This is a RAII class which behaves like an "AutoRestore<>" for our parser
   * input state. When instantiated, this class saves the current parser input
   * state (in a CSSParserInputState object), and it restores the parser to
   * that state when destructed, unless "DoNotRestore()" has been called.
  */
  class MOZ_RAII nsAutoCSSParserInputStateRestorer {
    public:
      explicit nsAutoCSSParserInputStateRestorer(CSSParserImpl* aParser
                                                 MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
        : mParser(aParser),
          mShouldRestore(true)
      {
        MOZ_GUARD_OBJECT_NOTIFIER_INIT;
        mParser->SaveInputState(mSavedState);
      }

      void DoNotRestore()
      {
        mShouldRestore = false;
      }

      ~nsAutoCSSParserInputStateRestorer()
      {
        if (mShouldRestore) {
          mParser->RestoreSavedInputState(mSavedState);
        }
      }

    private:
      MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
      CSSParserImpl* mParser;
      CSSParserInputState mSavedState;
      bool mShouldRestore;
  };

  /**
   * This is a RAII class which creates a temporary nsCSSScanner for the given
   * string, and reconfigures aParser to use *that* scanner instead of its
   * existing scanner, until we go out of scope.  (This allows us to rewrite
   * a portion of a stylesheet using a temporary string, and switch to parsing
   * that rewritten section, and then resume parsing the original stylesheet.)
   *
   * aParser must have a non-null nsCSSScanner (which we'll be temporarily
   * replacing) and ErrorReporter (which this class will co-opt for the
   * temporary parser). While we're in scope, we also suppress error reporting,
   * so it doesn't really matter which reporter we use. We suppress reporting
   * because this class is only used with CSS that is synthesized & didn't
   * come directly from an author, and it would be confusing if we reported
   * syntax errors for CSS that an author didn't provide.
   *
   * XXXdholbert we could also change this & report errors, if needed. Might
   * want to customize the error reporting somehow though.
   */
  class MOZ_RAII nsAutoScannerChanger {
    public:
      nsAutoScannerChanger(CSSParserImpl* aParser,
                           const nsAString& aStringToScan
                           MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
        : mParser(aParser),
          mOriginalScanner(aParser->mScanner),
          mStringScanner(aStringToScan, 0),
          mParserStateRestorer(aParser),
          mErrorSuppresser(aParser)
      {
        MOZ_ASSERT(mOriginalScanner,
                   "Shouldn't use nsAutoScannerChanger unless we already "
                   "have a scanner");
        MOZ_GUARD_OBJECT_NOTIFIER_INIT;

        // Set & setup the new scanner:
        mParser->mScanner = &mStringScanner;
        mStringScanner.SetErrorReporter(mParser->mReporter);

        // We might've had push-back on our original scanner (and if we did,
        // that fact is saved via mParserStateRestorer).  But we don't have
        // push-back in mStringScanner, so clear that flag.
        mParser->mHavePushBack = false;
      }

      ~nsAutoScannerChanger()
      {
        // Restore original scanner. All other cleanup is done by RAII members.
        mParser->mScanner = mOriginalScanner;
      }

    private:
      MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
      CSSParserImpl* mParser;
      nsCSSScanner *mOriginalScanner;
      nsCSSScanner mStringScanner;
      nsAutoCSSParserInputStateRestorer mParserStateRestorer;
      nsAutoSuppressErrors mErrorSuppresser;
  };


  bool IsSVGMode() const {
    return mScanner->IsSVGMode();
  }

  /**
   * Saves the current input state, which includes any currently pushed
   * back token, and the current position of the scanner.
   */
  void SaveInputState(CSSParserInputState& aState);

  /**
   * Restores the saved input state by pushing back any saved pushback
   * token and calling RestoreSavedPosition on the scanner.
   */
  void RestoreSavedInputState(const CSSParserInputState& aState);

  bool GetToken(bool aSkipWS);
  void UngetToken();
  bool GetNextTokenLocation(bool aSkipWS, uint32_t *linenum, uint32_t *colnum);
  void AssertNextTokenAt(uint32_t aLine, uint32_t aCol)
  {
    // Beware that this method will call GetToken/UngetToken (in
    // GetNextTokenLocation) in DEBUG builds, but not in non-DEBUG builds.
    DebugOnly<uint32_t> lineAfter, colAfter;
    MOZ_ASSERT(GetNextTokenLocation(true, &lineAfter, &colAfter) &&
               lineAfter == aLine && colAfter == aCol,
               "shouldn't have consumed any tokens");
  }

  bool ExpectSymbol(char16_t aSymbol, bool aSkipWS);
  bool ExpectEndProperty();
  bool CheckEndProperty();
  nsSubstring* NextIdent();

  // returns true when the stop symbol is found, and false for EOF
  bool SkipUntil(char16_t aStopSymbol);
  void SkipUntilOneOf(const char16_t* aStopSymbolChars);
  // For each character in aStopSymbolChars from the end of the array
  // to the start, calls SkipUntil with that character.
  typedef AutoTArray<char16_t, 16> StopSymbolCharStack;
  void SkipUntilAllOf(const StopSymbolCharStack& aStopSymbolChars);
  // returns true if the stop symbol or EOF is found, and false for an
  // unexpected ')', ']' or '}'; this not safe to call outside variable
  // resolution, as it doesn't handle mismatched content
  bool SkipBalancedContentUntil(char16_t aStopSymbol);

  void SkipRuleSet(bool aInsideBraces);
  bool SkipAtRule(bool aInsideBlock);
  bool SkipDeclaration(bool aCheckForBraces);

  void PushGroup(css::GroupRule* aRule);
  void PopGroup();

  bool ParseRuleSet(RuleAppendFunc aAppendFunc, void* aProcessData,
                    bool aInsideBraces = false);
  bool ParseAtRule(RuleAppendFunc aAppendFunc, void* aProcessData,
                   bool aInAtRule);
  bool ParseCharsetRule(RuleAppendFunc aAppendFunc, void* aProcessData);
  bool ParseImportRule(RuleAppendFunc aAppendFunc, void* aProcessData);
  bool ParseURLOrString(nsString& aURL);
  bool GatherMedia(nsMediaList* aMedia, bool aInAtRule);

  enum eMediaQueryType { eMediaQueryNormal,
                         // Parsing an at rule
                         eMediaQueryAtRule,
                         // Attempt to consume a single media-condition and
                         // stop. Note that the spec defines "expression and/or
                         // expression" as one condition but "expression,
                         // expression" as two.
                         eMediaQuerySingleCondition };
  bool ParseMediaQuery(eMediaQueryType aMode, nsMediaQuery **aQuery,
                       bool *aHitStop);
  bool ParseMediaQueryExpression(nsMediaQuery* aQuery);
  void ProcessImport(const nsString& aURLSpec,
                     nsMediaList* aMedia,
                     RuleAppendFunc aAppendFunc,
                     void* aProcessData,
                     uint32_t aLineNumber,
                     uint32_t aColumnNumber);
  bool ParseGroupRule(css::GroupRule* aRule, RuleAppendFunc aAppendFunc,
                      void* aProcessData);
  bool ParseMediaRule(RuleAppendFunc aAppendFunc, void* aProcessData);
  bool ParseMozDocumentRule(RuleAppendFunc aAppendFunc, void* aProcessData);
  bool ParseNameSpaceRule(RuleAppendFunc aAppendFunc, void* aProcessData);
  void ProcessNameSpace(const nsString& aPrefix,
                        const nsString& aURLSpec, RuleAppendFunc aAppendFunc,
                        void* aProcessData,
                        uint32_t aLineNumber, uint32_t aColumnNumber);

  bool ParseFontFaceRule(RuleAppendFunc aAppendFunc, void* aProcessData);
  bool ParseFontFeatureValuesRule(RuleAppendFunc aAppendFunc,
                                  void* aProcessData);
  bool ParseFontFeatureValueSet(nsCSSFontFeatureValuesRule *aRule);
  bool ParseFontDescriptor(nsCSSFontFaceRule* aRule);
  bool ParseFontDescriptorValue(nsCSSFontDesc aDescID,
                                nsCSSValue& aValue);

  bool ParsePageRule(RuleAppendFunc aAppendFunc, void* aProcessData);
  bool ParseKeyframesRule(RuleAppendFunc aAppendFunc, void* aProcessData);
  already_AddRefed<nsCSSKeyframeRule> ParseKeyframeRule();
  bool ParseKeyframeSelectorList(InfallibleTArray<float>& aSelectorList);

  bool ParseSupportsRule(RuleAppendFunc aAppendFunc, void* aProcessData);
  bool ParseSupportsCondition(bool& aConditionMet);
  bool ParseSupportsConditionNegation(bool& aConditionMet);
  bool ParseSupportsConditionInParens(bool& aConditionMet);
  bool ParseSupportsMozBoolPrefName(bool& aConditionMet);
  bool ParseSupportsConditionInParensInsideParens(bool& aConditionMet);
  bool ParseSupportsConditionTerms(bool& aConditionMet);
  enum SupportsConditionTermOperator { eAnd, eOr };
  bool ParseSupportsConditionTermsAfterOperator(
                                       bool& aConditionMet,
                                       SupportsConditionTermOperator aOperator);

  bool ParseCounterStyleRule(RuleAppendFunc aAppendFunc, void* aProcessData);
  bool ParseCounterStyleName(nsAString& aName, bool aForDefinition);
  bool ParseCounterStyleNameValue(nsCSSValue& aValue);
  bool ParseCounterDescriptor(nsCSSCounterStyleRule *aRule);
  bool ParseCounterDescriptorValue(nsCSSCounterDesc aDescID,
                                   nsCSSValue& aValue);
  bool ParseCounterRange(nsCSSValuePair& aPair);

  /**
   * Parses the current input stream for a CSS token stream value and resolves
   * any variable references using the variables in aVariables.
   *
   * @param aVariables The set of variable values to use when resolving variable
   *   references.
   * @param aResult Out parameter that, if the function returns true, will be
   *   replaced with the resolved value.
   * @return Whether aResult could be parsed successfully and variable reference
   *   substitution succeeded.
   */
  bool ResolveValueWithVariableReferences(
                              const CSSVariableValues* aVariables,
                              nsString& aResult,
                              nsCSSTokenSerializationType& aResultFirstToken,
                              nsCSSTokenSerializationType& aResultLastToken);
  // Helper function for ResolveValueWithVariableReferences.
  bool ResolveValueWithVariableReferencesRec(
                             nsString& aResult,
                             nsCSSTokenSerializationType& aResultFirstToken,
                             nsCSSTokenSerializationType& aResultLastToken,
                             const CSSVariableValues* aVariables);

  enum nsSelectorParsingStatus {
    // we have parsed a selector and we saw a token that cannot be
    // part of a selector:
    eSelectorParsingStatus_Done,
    // we should continue parsing the selector:
    eSelectorParsingStatus_Continue,
    // we saw an unexpected token or token value,
    // or we saw end-of-file with an unfinished selector:
    eSelectorParsingStatus_Error
  };
  nsSelectorParsingStatus ParseIDSelector(int32_t&       aDataMask,
                                          nsCSSSelector& aSelector);

  nsSelectorParsingStatus ParseClassSelector(int32_t&       aDataMask,
                                             nsCSSSelector& aSelector);

  // aPseudoElement and aPseudoElementArgs are the location where
  // pseudo-elements (as opposed to pseudo-classes) are stored;
  // pseudo-classes are stored on aSelector.  aPseudoElement and
  // aPseudoElementArgs must be non-null iff !aIsNegated.
  nsSelectorParsingStatus ParsePseudoSelector(int32_t&       aDataMask,
                                              nsCSSSelector& aSelector,
                                              bool           aIsNegated,
                                              nsIAtom**      aPseudoElement,
                                              nsAtomList**   aPseudoElementArgs,
                                              CSSPseudoElementType* aPseudoElementType);

  nsSelectorParsingStatus ParseAttributeSelector(int32_t&       aDataMask,
                                                 nsCSSSelector& aSelector);

  nsSelectorParsingStatus ParseTypeOrUniversalSelector(int32_t&       aDataMask,
                                                       nsCSSSelector& aSelector,
                                                       bool           aIsNegated);

  nsSelectorParsingStatus ParsePseudoClassWithIdentArg(nsCSSSelector& aSelector,
                                                       CSSPseudoClassType aType);

  nsSelectorParsingStatus ParsePseudoClassWithNthPairArg(nsCSSSelector& aSelector,
                                                         CSSPseudoClassType aType);

  nsSelectorParsingStatus ParsePseudoClassWithSelectorListArg(nsCSSSelector& aSelector,
                                                              CSSPseudoClassType aType);

  nsSelectorParsingStatus ParseNegatedSimpleSelector(int32_t&       aDataMask,
                                                     nsCSSSelector& aSelector);

  // If aStopChar is non-zero, the selector list is done when we hit
  // aStopChar.  Otherwise, it's done when we hit EOF.
  bool ParseSelectorList(nsCSSSelectorList*& aListHead,
                           char16_t aStopChar);
  bool ParseSelectorGroup(nsCSSSelectorList*& aListHead);
  bool ParseSelector(nsCSSSelectorList* aList, char16_t aPrevCombinator);

  enum {
    eParseDeclaration_InBraces           = 1 << 0,
    eParseDeclaration_AllowImportant     = 1 << 1,
    // The declaration we're parsing was generated by the CSSUnprefixingService:
    eParseDeclaration_FromUnprefixingSvc = 1 << 2
  };
  enum nsCSSContextType {
    eCSSContext_General,
    eCSSContext_Page
  };

  already_AddRefed<css::Declaration>
    ParseDeclarationBlock(uint32_t aFlags,
                          nsCSSContextType aContext = eCSSContext_General);
  bool ParseDeclaration(css::Declaration* aDeclaration,
                        uint32_t aFlags,
                        bool aMustCallValueAppended,
                        bool* aChanged,
                        nsCSSContextType aContext = eCSSContext_General);

  // A "prefix-aware" wrapper for nsCSSKeywords::LookupKeyword().
  // Use this instead of LookupKeyword() if you might be parsing an unprefixed
  // property (like "display") for which we emulate a vendor-prefixed value
  // (like "-webkit-box").
  nsCSSKeyword LookupKeywordPrefixAware(nsAString& aKeywordStr,
                                        const KTableEntry aKeywordTable[]);

  bool ShouldUseUnprefixingService() const;
  bool ParsePropertyWithUnprefixingService(const nsAString& aPropertyName,
                                           css::Declaration* aDeclaration,
                                           uint32_t aFlags,
                                           bool aMustCallValueAppended,
                                           bool* aChanged,
                                           nsCSSContextType aContext);
  // When we detect a webkit-prefixed gradient expression, this function can be
  // used to parse its body into outparam |aValue|, with the help of the
  // CSSUnprefixingService.
  // Only call if ShouldUseUnprefixingService() returns true.
  bool ParseWebkitPrefixedGradientWithService(nsAString& aPrefixedFuncName,
                                              nsCSSValue& aValue);

  bool ParseProperty(nsCSSPropertyID aPropID);
  bool ParsePropertyByFunction(nsCSSPropertyID aPropID);
  CSSParseResult ParseSingleValueProperty(nsCSSValue& aValue,
                                          nsCSSPropertyID aPropID);
  bool ParseSingleValuePropertyByFunction(nsCSSValue& aValue,
                                          nsCSSPropertyID aPropID);

  // This is similar to ParseSingleValueProperty but only works for
  // properties that are parsed with ParseBoxProperties or
  // ParseGroupedBoxProperty.
  //
  // Only works with variants with the following flags:
  // A, C, H, K, L, N, P, CALC.
  CSSParseResult ParseBoxProperty(nsCSSValue& aValue,
                                  nsCSSPropertyID aPropID);

  enum PriorityParsingStatus {
    ePriority_None,
    ePriority_Important,
    ePriority_Error
  };
  PriorityParsingStatus ParsePriority();

#ifdef MOZ_XUL
  bool ParseTreePseudoElement(nsAtomList **aPseudoElementArgs);
#endif

  // Property specific parsing routines
  bool ParseImageLayers(const nsCSSPropertyID aTable[]);

  struct ImageLayersShorthandParseState {
    nsCSSValue&  mColor;
    nsCSSValueList* mImage;
    nsCSSValuePairList* mRepeat;
    nsCSSValueList* mAttachment;   // A property for background layer only
    nsCSSValueList* mClip;
    nsCSSValueList* mOrigin;
    nsCSSValueList* mPositionX;
    nsCSSValueList* mPositionY;
    nsCSSValuePairList* mSize;
    nsCSSValueList* mComposite;    // A property for mask layer only
    nsCSSValueList* mMode;         // A property for mask layer only
    ImageLayersShorthandParseState(
        nsCSSValue& aColor, nsCSSValueList* aImage, nsCSSValuePairList* aRepeat,
        nsCSSValueList* aAttachment, nsCSSValueList* aClip,
        nsCSSValueList* aOrigin,
        nsCSSValueList* aPositionX, nsCSSValueList* aPositionY,
        nsCSSValuePairList* aSize, nsCSSValueList* aComposite,
        nsCSSValueList* aMode) :
        mColor(aColor), mImage(aImage), mRepeat(aRepeat),
        mAttachment(aAttachment), mClip(aClip), mOrigin(aOrigin),
        mPositionX(aPositionX), mPositionY(aPositionY),
        mSize(aSize), mComposite(aComposite),
        mMode(aMode) {};
  };

  bool IsFunctionTokenValidForImageLayerImage(const nsCSSToken& aToken) const;
  bool ParseImageLayersItem(ImageLayersShorthandParseState& aState,
                            const nsCSSPropertyID aTable[]);

  bool ParseValueList(nsCSSPropertyID aPropID); // a single value prop-id
  bool ParseImageLayerRepeat(nsCSSPropertyID aPropID);
  bool ParseImageLayerRepeatValues(nsCSSValuePair& aValue);
  bool ParseImageLayerPosition(const nsCSSPropertyID aTable[]);
  bool ParseImageLayerPositionCoord(nsCSSPropertyID aPropID, bool aIsHorizontal);

  // ParseBoxPositionValues parses the CSS 2.1 background-position syntax,
  // which is still used by some properties. See ParsePositionValue
  // for the css3-background syntax.
  bool ParseBoxPositionValues(nsCSSValuePair& aOut, bool aAcceptsInherit,
                              bool aAllowExplicitCenter = true); // deprecated

  // ParsePositionValue parses a CSS <position> value, which is used by
  // the 'background-position' property.
  bool ParsePositionValue(nsCSSValue& aOut);
  bool ParsePositionValueSeparateCoords(nsCSSValue& aOutX, nsCSSValue& aOutY);

  bool ParseImageLayerPositionCoordItem(nsCSSValue& aOut, bool aIsHorizontal);
  bool ParseImageLayerSize(nsCSSPropertyID aPropID);
  bool ParseImageLayerSizeValues(nsCSSValuePair& aOut);
  bool ParseBorderColor();
  bool ParseBorderColors(nsCSSPropertyID aProperty);
  void SetBorderImageInitialValues();
  bool ParseBorderImageRepeat(bool aAcceptsInherit);
  // If ParseBorderImageSlice returns false, aConsumedTokens indicates
  // whether or not any tokens were consumed (in other words, was the property
  // in error or just not present).  If ParseBorderImageSlice returns true
  // aConsumedTokens is always true.
  bool ParseBorderImageSlice(bool aAcceptsInherit, bool* aConsumedTokens);
  bool ParseBorderImageWidth(bool aAcceptsInherit);
  bool ParseBorderImageOutset(bool aAcceptsInherit);
  bool ParseBorderImage();
  bool ParseBorderSpacing();
  bool ParseBorderSide(const nsCSSPropertyID aPropIDs[],
                         bool aSetAllSides);
  bool ParseBorderStyle();
  bool ParseBorderWidth();

  bool ParseCalc(nsCSSValue &aValue, uint32_t aVariantMask);
  bool ParseCalcAdditiveExpression(nsCSSValue& aValue,
                                   uint32_t& aVariantMask);
  bool ParseCalcMultiplicativeExpression(nsCSSValue& aValue,
                                         uint32_t& aVariantMask,
                                         bool *aHadFinalWS);
  bool ParseCalcTerm(nsCSSValue& aValue, uint32_t& aVariantMask);
  bool RequireWhitespace();

  // For "flex" shorthand property, defined in CSS Flexbox spec
  bool ParseFlex();
  // For "flex-flow" shorthand property, defined in CSS Flexbox spec
  bool ParseFlexFlow();

  // CSS Grid
  bool ParseGridAutoFlow();

  // Parse a <line-names> expression.
  // If successful, either leave aValue untouched,
  // to indicate that we parsed the empty list,
  // or set it to a eCSSUnit_List of eCSSUnit_Ident.
  //
  // To parse an optional <line-names> (ie. if not finding an open bracket
  // is considered the same as an empty list),
  // treat CSSParseResult::NotFound the same as CSSParseResult::Ok.
  //
  // If aValue is already a eCSSUnit_List, append to that list.
  CSSParseResult ParseGridLineNames(nsCSSValue& aValue);
  bool ParseGridLineNameListRepeat(nsCSSValueList** aTailPtr);
  bool ParseOptionalLineNameListAfterSubgrid(nsCSSValue& aValue);

  CSSParseResult ParseGridTrackBreadth(nsCSSValue& aValue);
  // eFixedTrackSize in aFlags makes it parse a <fixed-size>.
  CSSParseResult ParseGridTrackSize(nsCSSValue& aValue,
    GridTrackSizeFlags aFlags = GridTrackSizeFlags::eDefaultTrackSize);

  bool ParseGridAutoColumnsRows(nsCSSPropertyID aPropID);
  bool ParseGridTrackListRepeat(nsCSSValueList** aTailPtr);
  bool ParseGridTrackRepeatIntro(bool            aForSubgrid,
                                 int32_t*        aRepetitions,
                                 Maybe<int32_t>* aRepeatAutoEnum);

  // Assuming a [ <line-names>? ] has already been parsed,
  // parse the rest of a <track-list>.
  //
  // This exists because [ <line-names>? ] is ambiguous in the 'grid-template'
  // shorthand: it can be either the start of a <track-list> (in
  // a <'grid-template-rows'>) or of the intertwined syntax that sets both
  // grid-template-rows and grid-template-areas.
  //
  // On success, |aValue| will be a list of odd length >= 3,
  // starting with a <line-names> (which is itself a list)
  // and alternating between that and <track-size>.
  bool ParseGridTrackListWithFirstLineNames(nsCSSValue& aValue,
    const nsCSSValue& aFirstLineNames,
    GridTrackListFlags aFlags = GridTrackListFlags::eDefaultTrackList);

  bool ParseGridTrackList(nsCSSPropertyID aPropID,
    GridTrackListFlags aFlags = GridTrackListFlags::eDefaultTrackList);
  bool ParseGridTemplateColumnsRows(nsCSSPropertyID aPropID);

  // |aAreaIndices| is a lookup table to help us parse faster,
  // mapping area names to indices in |aResult.mNamedAreas|.
  bool ParseGridTemplateAreasLine(const nsAutoString& aInput,
                                  css::GridTemplateAreasValue* aResult,
                                  nsDataHashtable<nsStringHashKey, uint32_t>& aAreaIndices);
  bool ParseGridTemplateAreas();
  bool ParseGridTemplateColumnsOrAutoFlow(bool aForGridShorthand);
  bool ParseGridTemplate(bool aForGridShorthand = false);
  bool ParseGridTemplateAfterString(const nsCSSValue& aFirstLineNames);
  bool ParseGrid();
  CSSParseResult ParseGridShorthandAutoProps(int32_t aAutoFlowAxis);
  bool ParseGridLine(nsCSSValue& aValue);
  bool ParseGridColumnRowStartEnd(nsCSSPropertyID aPropID);
  bool ParseGridColumnRow(nsCSSPropertyID aStartPropID,
                          nsCSSPropertyID aEndPropID);
  bool ParseGridArea();
  bool ParseGridGap();

  bool ParseInitialLetter();

  // parsing 'align/justify-items/self' from the css-align spec
  bool ParseAlignJustifyPosition(nsCSSValue& aResult,
                                 const KTableEntry aTable[]);
  bool ParseJustifyItems();
  bool ParseAlignItems();
  bool ParseAlignJustifySelf(nsCSSPropertyID aPropID);
  // parsing 'align/justify-content' from the css-align spec
  bool ParseAlignJustifyContent(nsCSSPropertyID aPropID);
  bool ParsePlaceContent();
  bool ParsePlaceItems();
  bool ParsePlaceSelf();

  // for 'clip' and '-moz-image-region'
  bool ParseRect(nsCSSPropertyID aPropID);
  bool ParseColumns();
  bool ParseContain(nsCSSValue& aValue);
  bool ParseContent();
  bool ParseCounterData(nsCSSPropertyID aPropID);
  bool ParseCursor();
  bool ParseFont();
  bool ParseFontSynthesis(nsCSSValue& aValue);
  bool ParseSingleAlternate(int32_t& aWhichFeature, nsCSSValue& aValue);
  bool ParseFontVariantAlternates(nsCSSValue& aValue);
  bool MergeBitmaskValue(int32_t aNewValue, const int32_t aMasks[],
                         int32_t& aMergedValue);
  bool ParseBitmaskValues(nsCSSValue& aValue,
                          const KTableEntry aKeywordTable[],
                          const int32_t aMasks[]);
  bool ParseFontVariantEastAsian(nsCSSValue& aValue);
  bool ParseFontVariantLigatures(nsCSSValue& aValue);
  bool ParseFontVariantNumeric(nsCSSValue& aValue);
  bool ParseFontVariant();
  bool ParseFontWeight(nsCSSValue& aValue);
  bool ParseOneFamily(nsAString& aFamily, bool& aOneKeyword, bool& aQuoted);
  bool ParseFamily(nsCSSValue& aValue);
  bool ParseFontFeatureSettings(nsCSSValue& aValue);
  bool ParseFontVariationSettings(nsCSSValue& aValue);
  bool ParseFontSrc(nsCSSValue& aValue);
  bool ParseFontSrcFormat(InfallibleTArray<nsCSSValue>& values);
  bool ParseFontRanges(nsCSSValue& aValue);
  bool ParseListStyle();
  bool ParseListStyleType(nsCSSValue& aValue);
  bool ParseMargin();
  bool ParseClipPath(nsCSSValue& aValue);
  bool ParseTransform(bool aIsPrefixed, bool aDisallowRelativeValues = false);
  bool ParseObjectPosition();
  bool ParseOutline();
  bool ParseOverflow();
  bool ParsePadding();
  bool ParseQuotes();
  bool ParseTextAlign(nsCSSValue& aValue,
                      const KTableEntry aTable[]);
  bool ParseTextAlign(nsCSSValue& aValue);
  bool ParseTextAlignLast(nsCSSValue& aValue);
  bool ParseTextDecoration();
  bool ParseTextDecorationLine(nsCSSValue& aValue);
  bool ParseTextEmphasis();
  bool ParseTextEmphasisPosition(nsCSSValue& aValue);
  bool ParseTextEmphasisStyle(nsCSSValue& aValue);
  bool ParseTextCombineUpright(nsCSSValue& aValue);
  bool ParseTextOverflow(nsCSSValue& aValue);
  bool ParseTouchAction(nsCSSValue& aValue);

  bool ParseShadowItem(nsCSSValue& aValue, bool aIsBoxShadow);
  bool ParseShadowList(nsCSSPropertyID aProperty);
  bool ParseShapeOutside(nsCSSValue& aValue);
  bool ParseTransitionProperty();
  bool ParseTransitionTimingFunctionValues(nsCSSValue& aValue);
  bool ParseTransitionTimingFunctionValueComponent(float& aComponent,
                                                     char aStop,
                                                     bool aIsXPoint);
  bool ParseTransitionStepTimingFunctionValues(nsCSSValue& aValue);
  enum ParseAnimationOrTransitionShorthandResult {
    eParseAnimationOrTransitionShorthand_Values,
    eParseAnimationOrTransitionShorthand_Inherit,
    eParseAnimationOrTransitionShorthand_Error
  };
  ParseAnimationOrTransitionShorthandResult
    ParseAnimationOrTransitionShorthand(const nsCSSPropertyID* aProperties,
                                        const nsCSSValue* aInitialValues,
                                        nsCSSValue* aValues,
                                        size_t aNumProperties);
  bool ParseTransition();
  bool ParseAnimation();
  bool ParseWillChange();

  bool ParsePaint(nsCSSPropertyID aPropID);
  bool ParseDasharray();
  bool ParseMarker();
  bool ParsePaintOrder();
  bool ParseAll();
  bool ParseScrollSnapType();
  bool ParseScrollSnapPoints(nsCSSValue& aValue, nsCSSPropertyID aPropID);
  bool ParseScrollSnapDestination(nsCSSValue& aValue);
  bool ParseScrollSnapCoordinate(nsCSSValue& aValue);
  bool ParseWebkitTextStroke();

  /**
   * Parses a variable value from a custom property declaration.
   *
   * @param aType Out parameter into which will be stored the type of variable
   *   value, indicating whether the parsed value was a token stream or one of
   *   the CSS-wide keywords.
   * @param aValue Out parameter into which will be stored the token stream
   *   as a string, if the parsed custom property did take a token stream.
   * @return Whether parsing succeeded.
   */
  bool ParseVariableDeclaration(CSSVariableDeclarations::Type* aType,
                                nsString& aValue);

  /**
   * Parses a CSS variable value.  This could be 'initial', 'inherit', 'unset'
   * or a token stream, which may or may not include variable references.
   *
   * @param aType Out parameter into which the type of the variable value
   *   will be stored.
   * @param aDropBackslash Out parameter indicating whether during variable
   *   value parsing there was a trailing backslash before EOF that must
   *   be dropped when serializing the variable value.
   * @param aImpliedCharacters Out parameter appended to which will be any
   *   characters that were implied when encountering EOF and which
   *   must be included at the end of the serialized variable value.
   * @param aFunc A callback function to invoke when a variable reference
   *   is encountered.  May be null.  Arguments are the variable name
   *   and the aData argument passed in to this function.
   * @param User data to pass in to the callback.
   * @return Whether parsing succeeded.
   */
  bool ParseValueWithVariables(CSSVariableDeclarations::Type* aType,
                               bool* aDropBackslash,
                               nsString& aImpliedCharacters,
                               void (*aFunc)(const nsAString&, void*),
                               void* aData);

  /**
   * Returns whether the scanner dropped a backslash just before EOF.
   */
  bool BackslashDropped();

  /**
   * Calls AppendImpliedEOFCharacters on mScanner.
   */
  void AppendImpliedEOFCharacters(nsAString& aResult);

  // Reused utility parsing routines
  void AppendValue(nsCSSPropertyID aPropID, const nsCSSValue& aValue);
  bool ParseBoxProperties(const nsCSSPropertyID aPropIDs[]);
  bool ParseGroupedBoxProperty(int32_t aVariantMask,
                               nsCSSValue& aValue,
                               uint32_t aRestrictions);
  bool ParseBoxCornerRadius(const nsCSSPropertyID aPropID);
  bool ParseBoxCornerRadiiInternals(nsCSSValue array[]);
  bool ParseBoxCornerRadii(const nsCSSPropertyID aPropIDs[]);

  int32_t ParseChoice(nsCSSValue aValues[],
                      const nsCSSPropertyID aPropIDs[], int32_t aNumIDs);

  CSSParseResult ParseColor(nsCSSValue& aValue);

  template<typename ComponentType>
  bool ParseRGBColor(ComponentType& aR,
                     ComponentType& aG,
                     ComponentType& aB,
                     ComponentType& aA);
  bool ParseHSLColor(float& aHue, float& aSaturation, float& aLightness,
                     float& aOpacity);

  // The ParseColorOpacityAndCloseParen methods below attempt to parse an
  // optional [ separator <alpha-value> ] expression, followed by a
  // close-parenthesis, at the end of a css color function (e.g. "rgba()" or
  // "hsla()"). If these functions simply encounter a close-parenthesis (without
  // any [separator <alpha-value>]), they will still succeed (i.e. return true),
  // with outparam 'aOpacity' set to a default opacity value (fully-opaque).
  //
  // The range of opacity component is [0, 255], and the default opacity value
  // is 255 (fully-opaque) for this function.
  bool ParseColorOpacityAndCloseParen(uint8_t& aOpacity,
                                      char aSeparator);
  // Similar to the previous one, but the range of opacity component is
  // [0.0f, 1.0f] and the default opacity value is 1.0f (fully-opaque).
  bool ParseColorOpacityAndCloseParen(float& aOpacity,
                                      char aSeparator);

  // Parse a <number> color component. The range of color component is [0, 255].
  // If |aSeparator| is provided, this function will also attempt to parse that
  // character after parsing the color component.
  bool ParseColorComponent(uint8_t& aComponent, Maybe<char> aSeparator);
  // Similar to the previous one, but parse a <percentage> color component.
  // The range of color component is [0.0f, 1.0f].
  bool ParseColorComponent(float& aComponent, Maybe<char> aSeparator);

  // Parse a <hue> component.
  //   <hue> = <number> | <angle>
  // The unit of outparam 'aAngle' is degree. Assume the unit to be degree if an
  // unitless <number> is parsed.
  bool ParseHue(float& aAngle);

  bool ParseEnum(nsCSSValue& aValue,
                 const KTableEntry aKeywordTable[]);

  // A special ParseEnum for the CSS Box Alignment properties that have
  // 'baseline' values.  In addition to the keywords in aKeywordTable, it also
  // parses 'first baseline' and 'last baseline' as a single value.
  // (aKeywordTable must contain 'baseline')
  bool ParseAlignEnum(nsCSSValue& aValue, const KTableEntry aKeywordTable[]);

  // Variant parsing methods
  CSSParseResult ParseVariant(nsCSSValue& aValue,
                              uint32_t aVariantMask,
                              const KTableEntry aKeywordTable[]);
  CSSParseResult ParseVariantWithRestrictions(nsCSSValue& aValue,
                                              int32_t aVariantMask,
                                              const KTableEntry aKeywordTable[],
                                              uint32_t aRestrictions);
  CSSParseResult ParseNonNegativeVariant(nsCSSValue& aValue,
                                         int32_t aVariantMask,
                                         const KTableEntry aKeywordTable[]);
  CSSParseResult ParseOneOrLargerVariant(nsCSSValue& aValue,
                                         int32_t aVariantMask,
                                         const KTableEntry aKeywordTable[]);

  // Variant parsing methods that are guaranteed to UngetToken any token
  // consumed on failure
  bool ParseSingleTokenVariant(nsCSSValue& aValue,
                               int32_t aVariantMask,
                               const KTableEntry aKeywordTable[])
  {
    MOZ_ASSERT(!(aVariantMask & VARIANT_MULTIPLE_TOKENS),
               "use ParseVariant for variants in VARIANT_MULTIPLE_TOKENS");
    CSSParseResult result = ParseVariant(aValue, aVariantMask, aKeywordTable);
    MOZ_ASSERT(result != CSSParseResult::Error);
    return result == CSSParseResult::Ok;
  }
  bool ParseSingleTokenVariantWithRestrictions(
      nsCSSValue& aValue,
      int32_t aVariantMask,
      const KTableEntry aKeywordTable[],
      uint32_t aRestrictions)
  {
    MOZ_ASSERT(!(aVariantMask & VARIANT_MULTIPLE_TOKENS),
               "use ParseVariantWithRestrictions for variants in "
               "VARIANT_MULTIPLE_TOKENS");
    CSSParseResult result =
      ParseVariantWithRestrictions(aValue, aVariantMask, aKeywordTable,
                                   aRestrictions);
    MOZ_ASSERT(result != CSSParseResult::Error);
    return result == CSSParseResult::Ok;
  }
  bool ParseSingleTokenNonNegativeVariant(nsCSSValue& aValue,
                                          int32_t aVariantMask,
                                          const KTableEntry aKeywordTable[])
  {
    MOZ_ASSERT(!(aVariantMask & VARIANT_MULTIPLE_TOKENS),
               "use ParseNonNegativeVariant for variants in "
               "VARIANT_MULTIPLE_TOKENS");
    CSSParseResult result =
      ParseNonNegativeVariant(aValue, aVariantMask, aKeywordTable);
    MOZ_ASSERT(result != CSSParseResult::Error);
    return result == CSSParseResult::Ok;
  }
  bool ParseSingleTokenOneOrLargerVariant(nsCSSValue& aValue,
                                          int32_t aVariantMask,
                                          const KTableEntry aKeywordTable[])
  {
    MOZ_ASSERT(!(aVariantMask & VARIANT_MULTIPLE_TOKENS),
               "use ParseOneOrLargerVariant for variants in "
               "VARIANT_MULTIPLE_TOKENS");
    CSSParseResult result =
      ParseOneOrLargerVariant(aValue, aVariantMask, aKeywordTable);
    MOZ_ASSERT(result != CSSParseResult::Error);
    return result == CSSParseResult::Ok;
  }

  // Helpers for some common ParseSingleTokenNonNegativeVariant calls.
  bool ParseNonNegativeInteger(nsCSSValue& aValue)
  {
    return ParseSingleTokenNonNegativeVariant(aValue, VARIANT_INTEGER, nullptr);
  }
  bool ParseNonNegativeNumber(nsCSSValue& aValue)
  {
    return ParseSingleTokenNonNegativeVariant(aValue, VARIANT_NUMBER, nullptr);
  }

  // Helpers for some common ParseSingleTokenOneOrLargerVariant calls.
  bool ParseOneOrLargerInteger(nsCSSValue& aValue)
  {
    return ParseSingleTokenOneOrLargerVariant(aValue, VARIANT_INTEGER, nullptr);
  }
  bool ParseOneOrLargerNumber(nsCSSValue& aValue)
  {
    return ParseSingleTokenOneOrLargerVariant(aValue, VARIANT_NUMBER, nullptr);
  }

  // http://dev.w3.org/csswg/css-values/#custom-idents
  // Parse an identifier that is none of:
  // * a CSS-wide keyword
  // * "default"
  // * a keyword in |aExcludedKeywords|
  // * a keyword in |aPropertyKTable|
  //
  // |aExcludedKeywords| is an array of nsCSSKeyword
  // that ends with a eCSSKeyword_UNKNOWN marker.
  //
  // |aPropertyKTable| can be used if some of the keywords to exclude
  // also appear in an existing nsCSSProps::KTableEntry,
  // to avoid duplicating them.
  bool ParseCustomIdent(nsCSSValue& aValue,
                        const nsAutoString& aIdentValue,
                        const nsCSSKeyword aExcludedKeywords[] = nullptr,
                        const nsCSSProps::KTableEntry aPropertyKTable[] = nullptr);
  bool ParseCounter(nsCSSValue& aValue);
  bool ParseAttr(nsCSSValue& aValue);
  bool ParseSymbols(nsCSSValue& aValue);
  bool SetValueToURL(nsCSSValue& aValue, const nsString& aURL);
  bool TranslateDimension(nsCSSValue& aValue, uint32_t aVariantMask,
                            float aNumber, const nsString& aUnit);
  bool ParseImageOrientation(nsCSSValue& aAngle);
  bool ParseImageRect(nsCSSValue& aImage);
  bool ParseElement(nsCSSValue& aValue);
  bool ParseColorStop(nsCSSValueGradient* aGradient);

  enum GradientParsingFlags {
    eGradient_Repeating    = 1 << 0, // repeating-{linear|radial}-gradient
    eGradient_MozLegacy    = 1 << 1, // -moz-{linear|radial}-gradient
    eGradient_WebkitLegacy = 1 << 2, // -webkit-{linear|radial}-gradient

    // Mask to catch both "legacy" flags:
    eGradient_AnyLegacy = eGradient_MozLegacy | eGradient_WebkitLegacy
  };
  bool ParseLinearGradient(nsCSSValue& aValue, uint8_t aFlags);
  bool ParseRadialGradient(nsCSSValue& aValue, uint8_t aFlags);
  bool IsLegacyGradientLine(const nsCSSTokenType& aType,
                            const nsString& aId);
  bool ParseGradientColorStops(nsCSSValueGradient* aGradient,
                               nsCSSValue& aValue);

  // For the ancient "-webkit-gradient(linear|radial, ...)" syntax:
  bool ParseWebkitGradientPointComponent(nsCSSValue& aComponent,
                                         bool aIsHorizontal);
  bool ParseWebkitGradientPoint(nsCSSValuePair& aPoint);
  bool ParseWebkitGradientRadius(float& aRadius);
  bool ParseWebkitGradientColorStop(nsCSSValueGradient* aGradient);
  bool ParseWebkitGradientColorStops(nsCSSValueGradient* aGradient);
  void FinalizeLinearWebkitGradient(nsCSSValueGradient* aGradient,
                                    const nsCSSValuePair& aStartPoint,
                                    const nsCSSValuePair& aSecondPoint);
  void FinalizeRadialWebkitGradient(nsCSSValueGradient* aGradient,
                                    const nsCSSValuePair& aFirstCenter,
                                    const nsCSSValuePair& aSecondCenter,
                                    const float aFirstRadius,
                                    const float aSecondRadius);
  bool ParseWebkitGradient(nsCSSValue& aValue);

  void SetParsingCompoundProperty(bool aBool) {
    mParsingCompoundProperty = aBool;
  }
  bool IsParsingCompoundProperty(void) const {
    return mParsingCompoundProperty;
  }

  /* Functions for basic shapes */
  bool ParseReferenceBoxAndBasicShape(nsCSSValue& aValue,
                                      const KTableEntry aBoxKeywordTable[]);
  bool ParseBasicShape(nsCSSValue& aValue, bool* aConsumedTokens);
  bool ParsePolygonFunction(nsCSSValue& aValue);
  bool ParseCircleOrEllipseFunction(nsCSSKeyword, nsCSSValue& aValue);
  bool ParseInsetFunction(nsCSSValue& aValue);
  // We parse position values differently for basic-shape, by expanding defaults
  // and replacing keywords with percentages
  bool ParsePositionValueForBasicShape(nsCSSValue& aOut);


  /* Functions for transform Parsing */
  bool ParseSingleTransform(bool aIsPrefixed, bool aDisallowRelativeValues,
                            nsCSSValue& aValue);
  bool ParseFunction(nsCSSKeyword aFunction, const uint32_t aAllowedTypes[],
                     uint32_t aVariantMaskAll, uint16_t aMinElems,
                     uint16_t aMaxElems, nsCSSValue &aValue);
  bool ParseFunctionInternals(const uint32_t aVariantMask[],
                              uint32_t aVariantMaskAll,
                              uint16_t aMinElems,
                              uint16_t aMaxElems,
                              InfallibleTArray<nsCSSValue>& aOutput);

  /* Functions for transform-origin/perspective-origin Parsing */
  bool ParseTransformOrigin(bool aPerspective);

  /* Functions for filter parsing */
  bool ParseFilter();
  bool ParseSingleFilter(nsCSSValue* aValue);
  bool ParseDropShadow(nsCSSValue* aValue);

  /* Find and return the namespace ID associated with aPrefix.
     If aPrefix has not been declared in an @namespace rule, returns
     kNameSpaceID_Unknown. */
  int32_t GetNamespaceIdForPrefix(const nsString& aPrefix);

  /* Find the correct default namespace, and set it on aSelector. */
  void SetDefaultNamespaceOnSelector(nsCSSSelector& aSelector);

  // Current token. The value is valid after calling GetToken and invalidated
  // by UngetToken.
  nsCSSToken mToken;

  // Our scanner.
  nsCSSScanner* mScanner;

  // Our error reporter.
  css::ErrorReporter* mReporter;

  // The URI to be used as a base for relative URIs.
  nsCOMPtr<nsIURI> mBaseURI;

  // The URI to be used as an HTTP "Referer" and for error reporting.
  nsCOMPtr<nsIURI> mSheetURI;

  // The principal of the sheet involved
  nsCOMPtr<nsIPrincipal> mSheetPrincipal;

  // The sheet we're parsing into
  RefPtr<CSSStyleSheet> mSheet;

  // Used for @import rules
  css::Loader* mChildLoader; // not ref counted, it owns us

  // Any sheets we may reuse when parsing an @import.
  css::LoaderReusableStyleSheets* mReusableSheets;

  // Sheet section we're in.  This is used to enforce correct ordering of the
  // various rule types (eg the fact that a @charset rule must come before
  // anything else).  Note that there are checks of similar things in various
  // places in CSSStyleSheet.cpp (e.g in insertRule, RebuildChildList).
  enum nsCSSSection {
    eCSSSection_Charset,
    eCSSSection_Import,
    eCSSSection_NameSpace,
    eCSSSection_General
  };
  nsCSSSection  mSection;

  nsXMLNameSpaceMap *mNameSpaceMap;  // weak, mSheet owns it

  // After an UngetToken is done this flag is true. The next call to
  // GetToken clears the flag.
  bool mHavePushBack : 1;

  // True if we are in quirks mode; false in standards or almost standards mode
  bool          mNavQuirkMode : 1;

  // True when the hashless color quirk applies.
  bool mHashlessColorQuirk : 1;

  // True when the unitless length quirk applies.
  bool mUnitlessLengthQuirk : 1;

  // Controls access to nonstandard style constructs that are not safe
  // for use on the public Web but necessary in UA sheets and/or
  // useful in user sheets.  The only values stored in this field are
  // 0, 1, and 2; three bits are allocated to avoid issues should the
  // enum type be signed.
  css::SheetParsingMode mParsingMode : 3;

  // True if we are in parsing rules for the chrome.
  bool mIsChrome : 1;

  // True if viewport units should be allowed.
  bool mViewportUnitsEnabled : 1;

  // True for parsing media lists for HTML attributes, where we have to
  // ignore CSS comments.
  bool mHTMLMediaMode : 1;

  // This flag is set when parsing a non-box shorthand; it's used to not apply
  // some quirks during shorthand parsing
  bool          mParsingCompoundProperty : 1;

  // True if we are in the middle of parsing an @supports condition.
  // This is used to avoid recording the input stream when variable references
  // are encountered in a property declaration in the @supports condition.
  bool mInSupportsCondition : 1;

  // True if we are somewhere within a @supports rule whose condition is
  // false.
  bool mInFailingSupportsRule : 1;

  // True if we will suppress all parse errors (except unexpected EOFs).
  // This is used to prevent errors for declarations inside a failing
  // @supports rule.
  bool mSuppressErrors : 1;

  // True if any parsing of URL values requires a sheet principal to have
  // been passed in the nsCSSScanner constructor.  This is usually the case.
  // It can be set to false, for example, when we create an nsCSSParser solely
  // to parse a property value to test it for syntactic correctness.  When
  // false, an assertion that mSheetPrincipal is non-null is skipped.  Should
  // not be set to false if any nsCSSValues created during parsing can escape
  // out of the parser.
  bool mSheetPrincipalRequired;

  // This enum helps us track whether we've unprefixed "display: -webkit-box"
  // (treating it as "display: flex") in an earlier declaration within a series
  // of declarations.  (This only impacts behavior when the function
  // "ShouldUseUnprefixingService()" returns true, and that should only happen
  // for a short whitelist of origins.)
  enum WebkitBoxUnprefixState : uint8_t {
    eNotParsingDecls, // We are *not* currently parsing a sequence of
                      // CSS declarations. (default state)

    // The next two enum values indicate that we *are* currently parsing a
    // sequence of declarations (in ParseDeclarations or ParseDeclarationBlock)
    // and...
    eHaveNotUnprefixed, // ...we have not unprefixed 'display:-webkit-box' in
                        // this sequence of CSS declarations.
    eHaveUnprefixed // ...we *have* unprefixed 'display:-webkit-box' earlier in
                    // this sequence of CSS declarations.
  };
  WebkitBoxUnprefixState mWebkitBoxUnprefixState;

  // Stack of rule groups; used for @media and such.
  InfallibleTArray<RefPtr<css::GroupRule> > mGroupStack;

  // During the parsing of a property (which may be a shorthand), the data
  // are stored in |mTempData|.  (It is needed to ensure that parser
  // errors cause the data to be ignored, and to ensure that a
  // non-'!important' declaration does not override an '!important'
  // one.)
  nsCSSExpandedDataBlock mTempData;

  // All data from successfully parsed properties are placed into |mData|.
  nsCSSExpandedDataBlock mData;
  
  // Value to make sure our resolved variable results stay within sane limits.
  const uint32_t MAX_CSS_VAR_LENGTH = 10240;

public:
  // Used from nsCSSParser constructors and destructors
  CSSParserImpl* mNextFree;
};

static void AssignRuleToPointer(css::Rule* aRule, void* aPointer)
{
  css::Rule **pointer = static_cast<css::Rule**>(aPointer);
  NS_ADDREF(*pointer = aRule);
}

static void AppendRuleToSheet(css::Rule* aRule, void* aParser)
{
  CSSParserImpl* parser = (CSSParserImpl*) aParser;
  parser->AppendRule(aRule);
}

#define REPORT_UNEXPECTED(msg_) \
  { if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_); }

#define REPORT_UNEXPECTED_P(msg_, param_) \
  { if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_, param_); }

#define REPORT_UNEXPECTED_P_V(msg_, param_, value_) \
  { if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_, param_, value_); }

#define REPORT_UNEXPECTED_TOKEN(msg_) \
  { if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_, mToken); }

#define REPORT_UNEXPECTED_TOKEN_CHAR(msg_, ch_) \
  { if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_, mToken, ch_); }

#define REPORT_UNEXPECTED_EOF(lf_) \
  mReporter->ReportUnexpectedEOF(#lf_)

#define REPORT_UNEXPECTED_EOF_CHAR(ch_) \
  mReporter->ReportUnexpectedEOF(ch_)

#define OUTPUT_ERROR() \
  mReporter->OutputError()

#define OUTPUT_ERROR_WITH_POSITION(linenum_, lineoff_) \
  mReporter->OutputError(linenum_, lineoff_)

#define CLEAR_ERROR() \
  mReporter->ClearError()

CSSParserImpl::CSSParserImpl()
  : mToken(),
    mScanner(nullptr),
    mReporter(nullptr),
    mChildLoader(nullptr),
    mReusableSheets(nullptr),
    mSection(eCSSSection_Charset),
    mNameSpaceMap(nullptr),
    mHavePushBack(false),
    mNavQuirkMode(false),
    mHashlessColorQuirk(false),
    mUnitlessLengthQuirk(false),
    mParsingMode(css::eAuthorSheetFeatures),
    mIsChrome(false),
    mViewportUnitsEnabled(true),
    mHTMLMediaMode(false),
    mParsingCompoundProperty(false),
    mInSupportsCondition(false),
    mInFailingSupportsRule(false),
    mSuppressErrors(false),
    mSheetPrincipalRequired(true),
    mWebkitBoxUnprefixState(eNotParsingDecls),
    mNextFree(nullptr)
{
}

CSSParserImpl::~CSSParserImpl()
{
  mData.AssertInitialState();
  mTempData.AssertInitialState();
}

nsresult
CSSParserImpl::SetStyleSheet(CSSStyleSheet* aSheet)
{
  if (aSheet != mSheet) {
    // Switch to using the new sheet, if any
    mGroupStack.Clear();
    mSheet = aSheet;
    if (mSheet) {
      mNameSpaceMap = mSheet->GetNameSpaceMap();
    } else {
      mNameSpaceMap = nullptr;
    }
  } else if (mSheet) {
    mNameSpaceMap = mSheet->GetNameSpaceMap();
  }

  return NS_OK;
}

nsIDocument*
CSSParserImpl::GetDocument()
{
  if (!mSheet) {
    return nullptr;
  }
  return mSheet->GetDocument();
}

nsresult
CSSParserImpl::SetQuirkMode(bool aQuirkMode)
{
  mNavQuirkMode = aQuirkMode;
  return NS_OK;
}

nsresult
CSSParserImpl::SetChildLoader(mozilla::css::Loader* aChildLoader)
{
  mChildLoader = aChildLoader;  // not ref counted, it owns us
  return NS_OK;
}

void
CSSParserImpl::Reset()
{
  NS_ASSERTION(!mScanner, "resetting with scanner active");
  SetStyleSheet(nullptr);
  SetQuirkMode(false);
  SetChildLoader(nullptr);
}

void
CSSParserImpl::InitScanner(nsCSSScanner& aScanner,
                           css::ErrorReporter& aReporter,
                           nsIURI* aSheetURI, nsIURI* aBaseURI,
                           nsIPrincipal* aSheetPrincipal)
{
  NS_PRECONDITION(!mHTMLMediaMode, "Bad initial state");
  NS_PRECONDITION(!mParsingCompoundProperty, "Bad initial state");
  NS_PRECONDITION(!mScanner, "already have scanner");

  mScanner = &aScanner;
  mReporter = &aReporter;
  mScanner->SetErrorReporter(mReporter);

  mBaseURI = aBaseURI;
  mSheetURI = aSheetURI;
  mSheetPrincipal = aSheetPrincipal;
  mHavePushBack = false;
}

void
CSSParserImpl::ReleaseScanner()
{
  mScanner = nullptr;
  mReporter = nullptr;
  mBaseURI = nullptr;
  mSheetURI = nullptr;
  mSheetPrincipal = nullptr;
}

nsresult
CSSParserImpl::ParseSheet(const nsAString& aInput,
                          nsIURI*          aSheetURI,
                          nsIURI*          aBaseURI,
                          nsIPrincipal*    aSheetPrincipal,
                          uint32_t         aLineNumber,
                          css::LoaderReusableStyleSheets* aReusableSheets)
{
  NS_PRECONDITION(aSheetPrincipal, "Must have principal here!");
  NS_PRECONDITION(aBaseURI, "need base URI");
  NS_PRECONDITION(aSheetURI, "need sheet URI");
  NS_PRECONDITION(mSheet, "Must have sheet to parse into");
  NS_ENSURE_STATE(mSheet);

#ifdef DEBUG
  nsIURI* uri = mSheet->GetSheetURI();
  bool equal;
  NS_ASSERTION(NS_SUCCEEDED(aSheetURI->Equals(uri, &equal)) && equal,
               "Sheet URI does not match passed URI");
  NS_ASSERTION(NS_SUCCEEDED(mSheet->Principal()->Equals(aSheetPrincipal,
                                                        &equal)) &&
               equal,
               "Sheet principal does not match passed principal");
#endif

  nsCSSScanner scanner(aInput, aLineNumber);
  css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI);
  InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal);

  int32_t ruleCount = mSheet->StyleRuleCount();
  if (0 < ruleCount) {
    const css::Rule* lastRule = mSheet->GetStyleRuleAt(ruleCount - 1);
    if (lastRule) {
      switch (lastRule->GetType()) {
        case css::Rule::CHARSET_RULE:
        case css::Rule::IMPORT_RULE:
          mSection = eCSSSection_Import;
          break;
        case css::Rule::NAMESPACE_RULE:
          mSection = eCSSSection_NameSpace;
          break;
        default:
          mSection = eCSSSection_General;
          break;
      }
    }
  }
  else {
    mSection = eCSSSection_Charset; // sheet is empty, any rules are fair
  }

  mParsingMode = mSheet->ParsingMode();
  mIsChrome = dom::IsChromeURI(aSheetURI);
  mReusableSheets = aReusableSheets;

  nsCSSToken* tk = &mToken;
  for (;;) {
    // Get next non-whitespace token
    if (!GetToken(true)) {
      OUTPUT_ERROR();
      break;
    }
    if (eCSSToken_HTMLComment == tk->mType) {
      continue; // legal here only
    }
    if (eCSSToken_AtKeyword == tk->mType) {
      ParseAtRule(AppendRuleToSheet, this, false);
      continue;
    }
    UngetToken();
    if (ParseRuleSet(AppendRuleToSheet, this)) {
      mSection = eCSSSection_General;
    }
  }
  ReleaseScanner();

  mParsingMode = css::eAuthorSheetFeatures;
  mIsChrome = false;
  mReusableSheets = nullptr;

  return NS_OK;
}

/**
 * Determines whether the identifier contained in the given string is a
 * vendor-specific identifier, as described in CSS 2.1 section 4.1.2.1.
 */
static bool
NonMozillaVendorIdentifier(const nsAString& ident)
{
  return (ident.First() == char16_t('-') &&
          !StringBeginsWith(ident, NS_LITERAL_STRING("-moz-"))) ||
         ident.First() == char16_t('_');

}

already_AddRefed<css::Declaration>
CSSParserImpl::ParseStyleAttribute(const nsAString& aAttributeValue,
                                   nsIURI*          aDocURI,
                                   nsIURI*          aBaseURI,
                                   nsIPrincipal*    aNodePrincipal)
{
  NS_PRECONDITION(aNodePrincipal, "Must have principal here!");
  NS_PRECONDITION(aBaseURI, "need base URI");

  // XXX line number?
  nsCSSScanner scanner(aAttributeValue, 0);
  css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aDocURI);
  InitScanner(scanner, reporter, aDocURI, aBaseURI, aNodePrincipal);

  mSection = eCSSSection_General;

  uint32_t parseFlags = eParseDeclaration_AllowImportant;

  RefPtr<css::Declaration> declaration = ParseDeclarationBlock(parseFlags);

  ReleaseScanner();

  return declaration.forget();
}

nsresult
CSSParserImpl::ParseDeclarations(const nsAString&  aBuffer,
                                 nsIURI*           aSheetURI,
                                 nsIURI*           aBaseURI,
                                 nsIPrincipal*     aSheetPrincipal,
                                 css::Declaration* aDeclaration,
                                 bool*           aChanged)
{
  *aChanged = false;

  NS_PRECONDITION(aSheetPrincipal, "Must have principal here!");

  nsCSSScanner scanner(aBuffer, 0);
  css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI);
  InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal);

  MOZ_ASSERT(mWebkitBoxUnprefixState == eNotParsingDecls,
             "Someone forgot to clear mWebkitBoxUnprefixState!");
  AutoRestore<WebkitBoxUnprefixState> autoRestore(mWebkitBoxUnprefixState);
  mWebkitBoxUnprefixState = eHaveNotUnprefixed;

  mSection = eCSSSection_General;

  mData.AssertInitialState();
  aDeclaration->ClearData();
  // We could check if it was already empty, but...
  *aChanged = true;

  for (;;) {
    // If we cleared the old decl, then we want to be calling
    // ValueAppended as we parse.
    if (!ParseDeclaration(aDeclaration, eParseDeclaration_AllowImportant,
                          true, aChanged)) {
      if (!SkipDeclaration(false)) {
        break;
      }
    }
  }

  aDeclaration->CompressFrom(&mData);
  ReleaseScanner();
  return NS_OK;
}

nsresult
CSSParserImpl::ParseRule(const nsAString&        aRule,
                         nsIURI*                 aSheetURI,
                         nsIURI*                 aBaseURI,
                         nsIPrincipal*           aSheetPrincipal,
                         css::Rule**             aResult)
{
  NS_PRECONDITION(aSheetPrincipal, "Must have principal here!");
  NS_PRECONDITION(aBaseURI, "need base URI");

  *aResult = nullptr;

  nsCSSScanner scanner(aRule, 0);
  css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI);
  InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal);

  mSection = eCSSSection_Charset; // callers are responsible for rejecting invalid rules.

  nsCSSToken* tk = &mToken;
  // Get first non-whitespace token
  nsresult rv = NS_OK;
  if (!GetToken(true)) {
    REPORT_UNEXPECTED(PEParseRuleWSOnly);
    OUTPUT_ERROR();
    rv = NS_ERROR_DOM_SYNTAX_ERR;
  } else {
    if (eCSSToken_AtKeyword == tk->mType) {
      // FIXME: perhaps aInsideBlock should be true when we are?
      ParseAtRule(AssignRuleToPointer, aResult, false);
    } else {
      UngetToken();
      ParseRuleSet(AssignRuleToPointer, aResult);
    }

    if (*aResult && GetToken(true)) {
      // garbage after rule
      REPORT_UNEXPECTED_TOKEN(PERuleTrailing);
      NS_RELEASE(*aResult);
    }

    if (!*aResult) {
      rv = NS_ERROR_DOM_SYNTAX_ERR;
      OUTPUT_ERROR();
    }
  }

  ReleaseScanner();
  return rv;
}

void
CSSParserImpl::ParseLonghandProperty(const nsCSSPropertyID aPropID,
                                     const nsAString& aPropValue,
                                     nsIURI* aSheetURL,
                                     nsIURI* aBaseURL,
                                     nsIPrincipal* aSheetPrincipal,
                                     nsCSSValue& aValue)
{
  MOZ_ASSERT(aPropID < eCSSProperty_COUNT_no_shorthands,
             "ParseLonghandProperty must only take a longhand property");

  RefPtr<css::Declaration> declaration = new css::Declaration;
  declaration->InitializeEmpty();

  bool changed;
  ParseProperty(aPropID, aPropValue, aSheetURL, aBaseURL, aSheetPrincipal,
                declaration, &changed,
                /* aIsImportant */ false,
                /* aIsSVGMode */ false);

  if (changed) {
    aValue = *declaration->GetNormalBlock()->ValueFor(aPropID);
  } else {
    aValue.Reset();
  }
}

bool
CSSParserImpl::ParseTransformProperty(const nsAString& aPropValue,
                                      bool aDisallowRelativeValues,
                                      nsCSSValue& aValue)
{
  RefPtr<css::Declaration> declaration = new css::Declaration();
  declaration->InitializeEmpty();

  mData.AssertInitialState();
  mTempData.AssertInitialState();

  nsCSSScanner scanner(aPropValue, 0);
  css::ErrorReporter reporter(scanner, mSheet, mChildLoader, nullptr);
  InitScanner(scanner, reporter, nullptr, nullptr, nullptr);

  bool parsedOK = ParseTransform(false, aDisallowRelativeValues);
  // We should now be at EOF
  if (parsedOK && GetToken(true)) {
    parsedOK = false;
  }

  bool changed = false;
  if (parsedOK) {
    declaration->ExpandTo(&mData);
    changed = mData.TransferFromBlock(mTempData, eCSSProperty_transform,
                                      EnabledState(), false,
                                      true, false, declaration,
                                      GetDocument());
    declaration->CompressFrom(&mData);
  }

  if (changed) {
    aValue = *declaration->GetNormalBlock()->ValueFor(eCSSProperty_transform);
  } else {
    aValue.Reset();
  }

  ReleaseScanner();

  return parsedOK;
}

void
CSSParserImpl::ParseProperty(const nsCSSPropertyID aPropID,
                             const nsAString& aPropValue,
                             nsIURI* aSheetURI,
                             nsIURI* aBaseURI,
                             nsIPrincipal* aSheetPrincipal,
                             css::Declaration* aDeclaration,
                             bool* aChanged,
                             bool aIsImportant,
                             bool aIsSVGMode)
{
  NS_PRECONDITION(aSheetPrincipal, "Must have principal here!");
  NS_PRECONDITION(aBaseURI, "need base URI");
  NS_PRECONDITION(aDeclaration, "Need declaration to parse into!");
  MOZ_ASSERT(aPropID != eCSSPropertyExtra_variable);

  mData.AssertInitialState();
  mTempData.AssertInitialState();
  aDeclaration->AssertMutable();

  nsCSSScanner scanner(aPropValue, 0);
  css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI);
  InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal);
  mSection = eCSSSection_General;
  scanner.SetSVGMode(aIsSVGMode);

  *aChanged = false;

  // Check for unknown or preffed off properties
  if (eCSSProperty_UNKNOWN == aPropID ||
      !nsCSSProps::IsEnabled(aPropID, EnabledState())) {
    NS_ConvertASCIItoUTF16 propName(nsCSSProps::GetStringValue(aPropID));
    REPORT_UNEXPECTED_P(PEUnknownProperty, propName);
    REPORT_UNEXPECTED(PEDeclDropped);
    OUTPUT_ERROR();
    ReleaseScanner();
    return;
  }

  bool parsedOK = ParseProperty(aPropID);
  // We should now be at EOF
  if (parsedOK && GetToken(true)) {
    REPORT_UNEXPECTED_TOKEN(PEExpectEndValue);
    parsedOK = false;
  }

  if (!parsedOK) {
    NS_ConvertASCIItoUTF16 propName(nsCSSProps::GetStringValue(aPropID));
    REPORT_UNEXPECTED_P(PEValueParsingError, propName);
    REPORT_UNEXPECTED(PEDeclDropped);
    OUTPUT_ERROR();
    mTempData.ClearProperty(aPropID);
  } else {

    // We know we don't need to force a ValueAppended call for the new
    // value.  So if we are not processing a shorthand, and there's
    // already a value for this property in the declaration at the
    // same importance level, then we can just copy our parsed value
    // directly into the declaration without going through the whole
    // expand/compress thing.
    if (!aDeclaration->TryReplaceValue(aPropID, aIsImportant, mTempData,
                                       aChanged)) {
      // Do it the slow way
      aDeclaration->ExpandTo(&mData);
      *aChanged = mData.TransferFromBlock(mTempData, aPropID,
                                          EnabledState(), aIsImportant,
                                          true, false, aDeclaration,
                                          GetDocument());
      aDeclaration->CompressFrom(&mData);
    }
    CLEAR_ERROR();
  }

  mTempData.AssertInitialState();

  ReleaseScanner();
}

void
CSSParserImpl::ParseVariable(const nsAString& aVariableName,
                             const nsAString& aPropValue,
                             nsIURI* aSheetURI,
                             nsIURI* aBaseURI,
                             nsIPrincipal* aSheetPrincipal,
                             css::Declaration* aDeclaration,
                             bool* aChanged,
                             bool aIsImportant)
{
  NS_PRECONDITION(aSheetPrincipal, "Must have principal here!");
  NS_PRECONDITION(aBaseURI, "need base URI");
  NS_PRECONDITION(aDeclaration, "Need declaration to parse into!");
  NS_PRECONDITION(nsLayoutUtils::CSSVariablesEnabled(),
                  "expected Variables to be enabled");

  mData.AssertInitialState();
  mTempData.AssertInitialState();
  aDeclaration->AssertMutable();

  nsCSSScanner scanner(aPropValue, 0);
  css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI);
  InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal);
  mSection = eCSSSection_General;

  *aChanged = false;

  CSSVariableDeclarations::Type variableType;
  nsString variableValue;

  bool parsedOK = ParseVariableDeclaration(&variableType, variableValue);

  // We should now be at EOF
  if (parsedOK && GetToken(true)) {
    REPORT_UNEXPECTED_TOKEN(PEExpectEndValue);
    parsedOK = false;
  }

  if (!parsedOK) {
    REPORT_UNEXPECTED_P(PEValueParsingError, NS_LITERAL_STRING("--") +
                                             aVariableName);
    REPORT_UNEXPECTED(PEDeclDropped);
    OUTPUT_ERROR();
  } else {
    CLEAR_ERROR();
    aDeclaration->AddVariable(aVariableName, variableType,
                              variableValue, aIsImportant, true);
    *aChanged = true;
  }

  mTempData.AssertInitialState();

  ReleaseScanner();
}

void
CSSParserImpl::ParseMediaList(const nsSubstring& aBuffer,
                              nsIURI* aURI, // for error reporting
                              uint32_t aLineNumber, // for error reporting
                              nsMediaList* aMediaList,
                              bool aHTMLMode)
{
  // XXX Are there cases where the caller wants to keep what it already
  // has in case of parser error?  If GatherMedia ever changes to return
  // a value other than true, we probably should avoid modifying aMediaList.
  aMediaList->Clear();

  // fake base URI since media lists don't have URIs in them
  nsCSSScanner scanner(aBuffer, aLineNumber);
  css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
  InitScanner(scanner, reporter, aURI, aURI, nullptr);

  mHTMLMediaMode = aHTMLMode;

    // XXXldb We need to make the scanner not skip CSS comments!  (Or
    // should we?)

  // For aHTMLMode, we used to follow the parsing rules in
  // http://www.w3.org/TR/1999/REC-html401-19991224/types.html#type-media-descriptors
  // which wouldn't work for media queries since they remove all but the
  // first word.  However, they're changed in
  // http://www.whatwg.org/specs/web-apps/current-work/multipage/section-document.html#media2
  // (as of 2008-05-29) which says that the media attribute just points
  // to a media query.  (The main substative difference is the relative
  // precedence of commas and paretheses.)

  DebugOnly<bool> parsedOK = GatherMedia(aMediaList, false);
  NS_ASSERTION(parsedOK, "GatherMedia returned false; we probably want to avoid "
                         "trashing aMediaList");

  CLEAR_ERROR();
  ReleaseScanner();
  mHTMLMediaMode = false;
}

//  <source-size-list> = <source-size>#?
//  <source-size> = <media-condition>? <length>
bool
CSSParserImpl::ParseSourceSizeList(const nsAString& aBuffer,
                                   nsIURI* aURI, // for error reporting
                                   uint32_t aLineNumber, // for error reporting
                                   InfallibleTArray< nsAutoPtr<nsMediaQuery> >& aQueries,
                                   InfallibleTArray<nsCSSValue>& aValues,
                                   bool aHTMLMode)
{
  aQueries.Clear();
  aValues.Clear();

  // fake base URI since media value lists don't have URIs in them
  nsCSSScanner scanner(aBuffer, aLineNumber);
  css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
  InitScanner(scanner, reporter, aURI, aURI, nullptr);

  // See ParseMediaList comment about HTML mode
  mHTMLMediaMode = aHTMLMode;

  // https://html.spec.whatwg.org/multipage/embedded-content.html#parse-a-sizes-attribute
  bool hitEnd = false;
  do {
    bool hitError = false;
    // Parse single <media-condition> <source-size-value>
    do {
      nsAutoPtr<nsMediaQuery> query;
      nsCSSValue value;

      bool hitStop;
      if (!ParseMediaQuery(eMediaQuerySingleCondition, getter_Transfers(query),
                           &hitStop)) {
        NS_ASSERTION(!hitStop, "should return true when hit stop");
        hitError = true;
        break;
      }

      if (!query) {
        REPORT_UNEXPECTED_EOF(PEParseSourceSizeListEOF);
        NS_ASSERTION(hitStop,
                     "should return hitStop or an error if returning no query");
        hitError = true;
        break;
      }

      if (hitStop) {
        // Empty conditions (e.g. just a bare value) should be treated as always
        // matching (a query with no expressions fails to match, so a negated one
        // always matches.)
        query->SetNegated();
      }

      // https://html.spec.whatwg.org/multipage/embedded-content.html#source-size-value
      // Percentages are not allowed in a <source-size-value>, to avoid
      // confusion about what it would be relative to.
      if (ParseNonNegativeVariant(value, VARIANT_LCALC, nullptr) !=
          CSSParseResult::Ok) {
        hitError = true;
        break;
      }

      if (GetToken(true)) {
        if (!mToken.IsSymbol(',')) {
          REPORT_UNEXPECTED_TOKEN(PEParseSourceSizeListNotComma);
          hitError = true;
          break;
        }
      } else {
        hitEnd = true;
      }

      aQueries.AppendElement(query.forget());
      aValues.AppendElement(value);
    } while(0);

    if (hitError) {
      OUTPUT_ERROR();

      // Per spec, we just skip the current entry if there was a parse error.
      // Jumps to next entry of <source-size-list> which is a comma-separated list.
      if (!SkipUntil(',')) {
        hitEnd = true;
      }
    }
  } while (!hitEnd);

  CLEAR_ERROR();
  ReleaseScanner();
  mHTMLMediaMode = false;

  return !aQueries.IsEmpty();
}

bool
CSSParserImpl::ParseColorString(const nsSubstring& aBuffer,
                                nsIURI* aURI, // for error reporting
                                uint32_t aLineNumber, // for error reporting
                                nsCSSValue& aValue,
                                bool aSuppressErrors /* false */)
{
  nsCSSScanner scanner(aBuffer, aLineNumber);
  css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
  InitScanner(scanner, reporter, aURI, aURI, nullptr);

  nsAutoSuppressErrors suppressErrors(this, aSuppressErrors);

  // Parse a color, and check that there's nothing else after it.
  bool colorParsed = ParseColor(aValue) == CSSParseResult::Ok &&
                     !GetToken(true);

  if (aSuppressErrors) {
    CLEAR_ERROR();
  } else {
    OUTPUT_ERROR();
  }

  ReleaseScanner();
  return colorParsed;
}

bool
CSSParserImpl::ParseMarginString(const nsSubstring& aBuffer,
                                 nsIURI* aURI, // for error reporting
                                 uint32_t aLineNumber, // for error reporting
                                 nsCSSValue& aValue,
                                 bool aSuppressErrors /* false */)
{
  nsCSSScanner scanner(aBuffer, aLineNumber);
  css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
  InitScanner(scanner, reporter, aURI, aURI, nullptr);

  nsAutoSuppressErrors suppressErrors(this, aSuppressErrors);

  // Parse a margin, and check that there's nothing else after it.
  bool marginParsed = ParseGroupedBoxProperty(VARIANT_LP, aValue, 0) && !GetToken(true);

  if (aSuppressErrors) {
    CLEAR_ERROR();
  } else {
    OUTPUT_ERROR();
  }

  ReleaseScanner();
  return marginParsed;
}

bool
CSSParserImpl::ParseFontFamilyListString(const nsSubstring& aBuffer,
                                         nsIURI* aURI, // for error reporting
                                         uint32_t aLineNumber, // for error reporting
                                         nsCSSValue& aValue)
{
  nsCSSScanner scanner(aBuffer, aLineNumber);
  css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
  InitScanner(scanner, reporter, aURI, aURI, nullptr);

  // Parse a font family list, and check that there's nothing else after it.
  bool familyParsed = ParseFamily(aValue) && !GetToken(true);
  OUTPUT_ERROR();
  ReleaseScanner();
  return familyParsed;
}

nsresult
CSSParserImpl::ParseSelectorString(const nsSubstring& aSelectorString,
                                   nsIURI* aURI, // for error reporting
                                   uint32_t aLineNumber, // for error reporting
                                   nsCSSSelectorList **aSelectorList)
{
  nsCSSScanner scanner(aSelectorString, aLineNumber);
  css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
  InitScanner(scanner, reporter, aURI, aURI, nullptr);

  bool success = ParseSelectorList(*aSelectorList, char16_t(0));

  // We deliberately do not call OUTPUT_ERROR here, because all our
  // callers map a failure return to a JS exception, and if that JS
  // exception is caught, people don't want to see parser diagnostics;
  // see e.g. http://bugs.jquery.com/ticket/7535
  // It would be nice to be able to save the parser diagnostics into
  // the exception, so that if it _isn't_ caught we can report them
  // along with the usual uncaught-exception message, but we don't
  // have any way to do that at present; see bug 631621.
  CLEAR_ERROR();
  ReleaseScanner();

  if (success) {
    NS_ASSERTION(*aSelectorList, "Should have list!");
    return NS_OK;
  }

  NS_ASSERTION(!*aSelectorList, "Shouldn't have list!");

  return NS_ERROR_DOM_SYNTAX_ERR;
}


already_AddRefed<nsCSSKeyframeRule>
CSSParserImpl::ParseKeyframeRule(const nsSubstring&  aBuffer,
                                 nsIURI*             aURI,
                                 uint32_t            aLineNumber)
{
  nsCSSScanner scanner(aBuffer, aLineNumber);
  css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
  InitScanner(scanner, reporter, aURI, aURI, nullptr);

  RefPtr<nsCSSKeyframeRule> result = ParseKeyframeRule();
  if (GetToken(true)) {
    // extra garbage at the end
    result = nullptr;
  }

  OUTPUT_ERROR();
  ReleaseScanner();

  return result.forget();
}

bool
CSSParserImpl::ParseKeyframeSelectorString(const nsSubstring& aSelectorString,
                                           nsIURI* aURI, // for error reporting
                                           uint32_t aLineNumber, // for error reporting
                                           InfallibleTArray<float>& aSelectorList)
{
  MOZ_ASSERT(aSelectorList.IsEmpty(), "given list should start empty");

  nsCSSScanner scanner(aSelectorString, aLineNumber);
  css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI);
  InitScanner(scanner, reporter, aURI, aURI, nullptr);

  bool success = ParseKeyframeSelectorList(aSelectorList) &&
                 // must consume entire input string
                 !GetToken(true);

  OUTPUT_ERROR();
  ReleaseScanner();

  if (success) {
    NS_ASSERTION(!aSelectorList.IsEmpty(), "should not be empty");
  } else {
    aSelectorList.Clear();
  }

  return success;
}

bool
CSSParserImpl::EvaluateSupportsDeclaration(const nsAString& aProperty,
                                           const nsAString& aValue,
                                           nsIURI* aDocURL,
                                           nsIURI* aBaseURL,
                                           nsIPrincipal* aDocPrincipal)
{
  nsCSSPropertyID propID = LookupEnabledProperty(aProperty);
  if (propID == eCSSProperty_UNKNOWN) {
    return false;
  }

  nsCSSScanner scanner(aValue, 0);
  css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aDocURL);
  InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal);
  nsAutoSuppressErrors suppressErrors(this);

  bool parsedOK;

  if (propID == eCSSPropertyExtra_variable) {
    MOZ_ASSERT(Substring(aProperty, 0,
                         CSS_CUSTOM_NAME_PREFIX_LENGTH).EqualsLiteral("--"));
    const nsDependentSubstring varName =
      Substring(aProperty, CSS_CUSTOM_NAME_PREFIX_LENGTH);  // remove '--'
    CSSVariableDeclarations::Type variableType;
    nsString variableValue;
    parsedOK = ParseVariableDeclaration(&variableType, variableValue) &&
               !GetToken(true);
  } else {
    parsedOK = ParseProperty(propID) && !GetToken(true);

    mTempData.ClearProperty(propID);
    mTempData.AssertInitialState();
  }

  CLEAR_ERROR();
  ReleaseScanner();

  return parsedOK;
}

bool
CSSParserImpl::EvaluateSupportsCondition(const nsAString& aDeclaration,
                                         nsIURI* aDocURL,
                                         nsIURI* aBaseURL,
                                         nsIPrincipal* aDocPrincipal)
{
  nsCSSScanner scanner(aDeclaration, 0);
  css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aDocURL);
  InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal);
  nsAutoSuppressErrors suppressErrors(this);

  bool conditionMet;
  bool parsedOK = ParseSupportsCondition(conditionMet) && !GetToken(true);

  CLEAR_ERROR();
  ReleaseScanner();

  return parsedOK && conditionMet;
}

bool
CSSParserImpl::EnumerateVariableReferences(const nsAString& aPropertyValue,
                                           VariableEnumFunc aFunc,
                                           void* aData)
{
  nsCSSScanner scanner(aPropertyValue, 0);
  css::ErrorReporter reporter(scanner, nullptr, nullptr, nullptr);
  InitScanner(scanner, reporter, nullptr, nullptr, nullptr);
  nsAutoSuppressErrors suppressErrors(this);

  CSSVariableDeclarations::Type type;
  bool dropBackslash;
  nsString impliedCharacters;
  bool result = ParseValueWithVariables(&type, &dropBackslash,
                                        impliedCharacters, aFunc, aData) &&
                !GetToken(true);

  ReleaseScanner();

  return result;
}

static bool
SeparatorRequiredBetweenTokens(nsCSSTokenSerializationType aToken1,
                               nsCSSTokenSerializationType aToken2)
{
  // The two lines marked with (*) do not correspond to entries in
  // the table in the css-syntax spec but which we need to handle,
  // as we treat them as whole tokens.
  switch (aToken1) {
    case eCSSTokenSerialization_Ident:
      return aToken2 == eCSSTokenSerialization_Ident ||
             aToken2 == eCSSTokenSerialization_Function ||
             aToken2 == eCSSTokenSerialization_URL_or_BadURL ||
             aToken2 == eCSSTokenSerialization_Symbol_Minus ||
             aToken2 == eCSSTokenSerialization_Number ||
             aToken2 == eCSSTokenSerialization_Percentage ||
             aToken2 == eCSSTokenSerialization_Dimension ||
             aToken2 == eCSSTokenSerialization_URange ||
             aToken2 == eCSSTokenSerialization_CDC ||
             aToken2 == eCSSTokenSerialization_Symbol_OpenParen;
    case eCSSTokenSerialization_AtKeyword_or_Hash:
    case eCSSTokenSerialization_Dimension:
      return aToken2 == eCSSTokenSerialization_Ident ||
             aToken2 == eCSSTokenSerialization_Function ||
             aToken2 == eCSSTokenSerialization_URL_or_BadURL ||
             aToken2 == eCSSTokenSerialization_Symbol_Minus ||
             aToken2 == eCSSTokenSerialization_Number ||
             aToken2 == eCSSTokenSerialization_Percentage ||
             aToken2 == eCSSTokenSerialization_Dimension ||
             aToken2 == eCSSTokenSerialization_URange ||
             aToken2 == eCSSTokenSerialization_CDC;
    case eCSSTokenSerialization_Symbol_Hash:
    case eCSSTokenSerialization_Symbol_Minus:
      return aToken2 == eCSSTokenSerialization_Ident ||
             aToken2 == eCSSTokenSerialization_Function ||
             aToken2 == eCSSTokenSerialization_URL_or_BadURL ||
             aToken2 == eCSSTokenSerialization_Symbol_Minus ||
             aToken2 == eCSSTokenSerialization_Number ||
             aToken2 == eCSSTokenSerialization_Percentage ||
             aToken2 == eCSSTokenSerialization_Dimension ||
             aToken2 == eCSSTokenSerialization_URange;
    case eCSSTokenSerialization_Number:
      return aToken2 == eCSSTokenSerialization_Ident ||
             aToken2 == eCSSTokenSerialization_Function ||
             aToken2 == eCSSTokenSerialization_URL_or_BadURL ||
             aToken2 == eCSSTokenSerialization_Number ||
             aToken2 == eCSSTokenSerialization_Percentage ||
             aToken2 == eCSSTokenSerialization_Dimension ||
             aToken2 == eCSSTokenSerialization_URange;
    case eCSSTokenSerialization_Symbol_At:
      return aToken2 == eCSSTokenSerialization_Ident ||
             aToken2 == eCSSTokenSerialization_Function ||
             aToken2 == eCSSTokenSerialization_URL_or_BadURL ||
             aToken2 == eCSSTokenSerialization_Symbol_Minus ||
             aToken2 == eCSSTokenSerialization_URange;
    case eCSSTokenSerialization_URange:
      return aToken2 == eCSSTokenSerialization_Ident ||
             aToken2 == eCSSTokenSerialization_Function ||
             aToken2 == eCSSTokenSerialization_Number ||
             aToken2 == eCSSTokenSerialization_Percentage ||
             aToken2 == eCSSTokenSerialization_Dimension ||
             aToken2 == eCSSTokenSerialization_Symbol_Question;
    case eCSSTokenSerialization_Symbol_Dot_or_Plus:
      return aToken2 == eCSSTokenSerialization_Number ||
             aToken2 == eCSSTokenSerialization_Percentage ||
             aToken2 == eCSSTokenSerialization_Dimension;
    case eCSSTokenSerialization_Symbol_Assorted:
    case eCSSTokenSerialization_Symbol_Asterisk:
      return aToken2 == eCSSTokenSerialization_Symbol_Equals;
    case eCSSTokenSerialization_Symbol_Bar:
      return aToken2 == eCSSTokenSerialization_Symbol_Equals ||
             aToken2 == eCSSTokenSerialization_Symbol_Bar ||
             aToken2 == eCSSTokenSerialization_DashMatch;              // (*)
    case eCSSTokenSerialization_Symbol_Slash:
      return aToken2 == eCSSTokenSerialization_Symbol_Asterisk ||
             aToken2 == eCSSTokenSerialization_ContainsMatch;          // (*)
    default:
      MOZ_ASSERT(aToken1 == eCSSTokenSerialization_Nothing ||
                 aToken1 == eCSSTokenSerialization_Whitespace ||
                 aToken1 == eCSSTokenSerialization_Percentage ||
                 aToken1 == eCSSTokenSerialization_URL_or_BadURL ||
                 aToken1 == eCSSTokenSerialization_Function ||
                 aToken1 == eCSSTokenSerialization_CDC ||
                 aToken1 == eCSSTokenSerialization_Symbol_OpenParen ||
                 aToken1 == eCSSTokenSerialization_Symbol_Question ||
                 aToken1 == eCSSTokenSerialization_Symbol_Assorted ||
                 aToken1 == eCSSTokenSerialization_Symbol_Asterisk ||
                 aToken1 == eCSSTokenSerialization_Symbol_Equals ||
                 aToken1 == eCSSTokenSerialization_Symbol_Bar ||
                 aToken1 == eCSSTokenSerialization_Symbol_Slash ||
                 aToken1 == eCSSTokenSerialization_Other,
                 "unexpected nsCSSTokenSerializationType value");
      return false;
  }
}

/**
 * Appends aValue to aResult, possibly inserting an empty CSS
 * comment between the two to ensure that tokens from both strings
 * remain separated.
 */
static void
AppendTokens(nsAString& aResult,
             nsCSSTokenSerializationType& aResultFirstToken,
             nsCSSTokenSerializationType& aResultLastToken,
             nsCSSTokenSerializationType aValueFirstToken,
             nsCSSTokenSerializationType aValueLastToken,
             const nsAString& aValue)
{
  if (SeparatorRequiredBetweenTokens(aResultLastToken, aValueFirstToken)) {
    aResult.AppendLiteral("/**/");
  }
  aResult.Append(aValue);
  if (aResultFirstToken == eCSSTokenSerialization_Nothing) {
    aResultFirstToken = aValueFirstToken;
  }
  if (aValueLastToken != eCSSTokenSerialization_Nothing) {
    aResultLastToken = aValueLastToken;
  }
}

/**
 * Stops the given scanner recording, and appends the recorded result
 * to aResult, possibly inserting an empty CSS comment between the two to
 * ensure that tokens from both strings remain separated.
 */
static void
StopRecordingAndAppendTokens(nsString& aResult,
                             nsCSSTokenSerializationType& aResultFirstToken,
                             nsCSSTokenSerializationType& aResultLastToken,
                             nsCSSTokenSerializationType aValueFirstToken,
                             nsCSSTokenSerializationType aValueLastToken,
                             nsCSSScanner* aScanner)
{
  if (SeparatorRequiredBetweenTokens(aResultLastToken, aValueFirstToken)) {
    aResult.AppendLiteral("/**/");
  }
  aScanner->StopRecording(aResult);
  if (aResultFirstToken == eCSSTokenSerialization_Nothing) {
    aResultFirstToken = aValueFirstToken;
  }
  if (aValueLastToken != eCSSTokenSerialization_Nothing) {
    aResultLastToken = aValueLastToken;
  }
}

bool
CSSParserImpl::ResolveValueWithVariableReferencesRec(
                                     nsString& aResult,
                                     nsCSSTokenSerializationType& aResultFirstToken,
                                     nsCSSTokenSerializationType& aResultLastToken,
                                     const CSSVariableValues* aVariables)
{
  // This function assumes we are already recording, and will leave the scanner
  // recording when it returns.
  MOZ_ASSERT(mScanner->IsRecording());
  MOZ_ASSERT(aResult.IsEmpty());

  // Stack of closing characters for currently open constructs.
  AutoTArray<char16_t, 16> stack;

  // The resolved value for this ResolveValueWithVariableReferencesRec call.
  nsString value;

  // The length of the scanner's recording before the currently parsed token.
  // This is used so that when we encounter a "var(" token, we can strip
  // it off the end of the recording, regardless of how long the token was.
  // (With escapes, it could be longer than four characters.)
  uint32_t lengthBeforeVar = 0;

  // Tracking the type of token that appears at the start and end of |value|
  // and that appears at the start and end of the scanner recording.  These are
  // used to determine whether we need to insert "/**/" when pasting token
  // streams together.
  nsCSSTokenSerializationType valueFirstToken = eCSSTokenSerialization_Nothing,
                              valueLastToken  = eCSSTokenSerialization_Nothing,
                              recFirstToken   = eCSSTokenSerialization_Nothing,
                              recLastToken    = eCSSTokenSerialization_Nothing;

#define UPDATE_RECORDING_TOKENS(type)                    \
  if (recFirstToken == eCSSTokenSerialization_Nothing) { \
    recFirstToken = type;                                \
  }                                                      \
  recLastToken = type;

  while (GetToken(false)) {
    switch (mToken.mType) {
      case eCSSToken_Symbol: {
        nsCSSTokenSerializationType type = eCSSTokenSerialization_Other;
        if (mToken.mSymbol == '(') {
          stack.AppendElement(')');
          type = eCSSTokenSerialization_Symbol_OpenParen;
        } else if (mToken.mSymbol == '[') {
          stack.AppendElement(']');
        } else if (mToken.mSymbol == '{') {
          stack.AppendElement('}');
        } else if (mToken.mSymbol == ';') {
          if (stack.IsEmpty()) {
            // A ';' that is at the top level of the value or at the top level
            // of a variable reference's fallback is invalid.
            return false;
          }
        } else if (mToken.mSymbol == '!') {
          if (stack.IsEmpty()) {
            // An '!' that is at the top level of the value or at the top level
            // of a variable reference's fallback is invalid.
            return false;
          }
        } else if (mToken.mSymbol == ')' &&
                   stack.IsEmpty()) {
          // We're closing a "var(".
          nsString finalTokens;
          mScanner->StopRecording(finalTokens);
          MOZ_ASSERT(finalTokens[finalTokens.Length() - 1] == ')');
          finalTokens.Truncate(finalTokens.Length() - 1);
          aResult.Append(value);

          AppendTokens(aResult, valueFirstToken, valueLastToken,
                       recFirstToken, recLastToken, finalTokens);

          mScanner->StartRecording();
          UngetToken();
          aResultFirstToken = valueFirstToken;
          aResultLastToken = valueLastToken;
          return true;
        } else if (mToken.mSymbol == ')' ||
                   mToken.mSymbol == ']' ||
                   mToken.mSymbol == '}') {
          if (stack.IsEmpty() ||
              stack.LastElement() != mToken.mSymbol) {
            // A mismatched closing bracket is invalid.
            return false;
          }
          stack.TruncateLength(stack.Length() - 1);
        } else if (mToken.mSymbol == '#') {
          type = eCSSTokenSerialization_Symbol_Hash;
        } else if (mToken.mSymbol == '@') {
          type = eCSSTokenSerialization_Symbol_At;
        } else if (mToken.mSymbol == '.' ||
                   mToken.mSymbol == '+') {
          type = eCSSTokenSerialization_Symbol_Dot_or_Plus;
        } else if (mToken.mSymbol == '-') {
          type = eCSSTokenSerialization_Symbol_Minus;
        } else if (mToken.mSymbol == '?') {
          type = eCSSTokenSerialization_Symbol_Question;
        } else if (mToken.mSymbol == '$' ||
                   mToken.mSymbol == '^' ||
                   mToken.mSymbol == '~') {
          type = eCSSTokenSerialization_Symbol_Assorted;
        } else if (mToken.mSymbol == '=') {
          type = eCSSTokenSerialization_Symbol_Equals;
        } else if (mToken.mSymbol == '|') {
          type = eCSSTokenSerialization_Symbol_Bar;
        } else if (mToken.mSymbol == '/') {
          type = eCSSTokenSerialization_Symbol_Slash;
        } else if (mToken.mSymbol == '*') {
          type = eCSSTokenSerialization_Symbol_Asterisk;
        }
        UPDATE_RECORDING_TOKENS(type);
        break;
      }

      case eCSSToken_Function:
        if (mToken.mIdent.LowerCaseEqualsLiteral("var")) {
          // Save the tokens before the "var(" to our resolved value.
          nsString recording;
          mScanner->StopRecording(recording);
          recording.Truncate(lengthBeforeVar);
          AppendTokens(value, valueFirstToken, valueLastToken,
                       recFirstToken, recLastToken, recording);
          recFirstToken = eCSSTokenSerialization_Nothing;
          recLastToken = eCSSTokenSerialization_Nothing;

          if (!GetToken(true) ||
              mToken.mType != eCSSToken_Ident ||
              !nsCSSProps::IsCustomPropertyName(mToken.mIdent)) {
            // "var(" must be followed by an identifier, and it must be a
            // custom property name.
            return false;
          }

          // Turn the custom property name into a variable name by removing the
          // '--' prefix.
          MOZ_ASSERT(Substring(mToken.mIdent, 0,
                               CSS_CUSTOM_NAME_PREFIX_LENGTH).
                       EqualsLiteral("--"));
          nsDependentString variableName(mToken.mIdent,
                                         CSS_CUSTOM_NAME_PREFIX_LENGTH);

          // Get the value of the identified variable.  Note that we
          // check if the variable value is the empty string, as that means
          // that the variable was invalid at computed value time due to
          // unresolveable variable references or cycles.
          nsString variableValue;
          nsCSSTokenSerializationType varFirstToken, varLastToken;
          bool valid = aVariables->Get(variableName, variableValue,
                                       varFirstToken, varLastToken) &&
                       !variableValue.IsEmpty();

          if (!GetToken(true) ||
              mToken.IsSymbol(')')) {
            mScanner->StartRecording();
            if (!valid) {
              // Invalid variable with no fallback.
              return false;
            }
            // Make sure we are still using sane sizes for value and
            // variableValue, and abort if OOB.
            if (value.Length() > MAX_CSS_VAR_LENGTH ||
                variableValue.Length() > MAX_CSS_VAR_LENGTH) {
              return false;
            }
            // Valid variable with no fallback.
            AppendTokens(value, valueFirstToken, valueLastToken,
                         varFirstToken, varLastToken, variableValue);
          } else if (mToken.IsSymbol(',')) {
            mScanner->StartRecording();
            if (!GetToken(false) ||
                mToken.IsSymbol(')')) {
              // Comma must be followed by at least one fallback token.
              return false;
            }
            UngetToken();
            if (valid) {
              // Valid variable with ignored fallback.
              mScanner->StopRecording();
              AppendTokens(value, valueFirstToken, valueLastToken,
                           varFirstToken, varLastToken, variableValue);
              bool ok = SkipBalancedContentUntil(')');
              mScanner->StartRecording();
              if (!ok) {
                return false;
              }
            } else {
              nsString fallback;
              if (!ResolveValueWithVariableReferencesRec(fallback,
                                                         varFirstToken,
                                                         varLastToken,
                                                         aVariables)) {
                // Fallback value had invalid tokens or an invalid variable reference
                // that itself had no fallback.
                return false;
              }
              AppendTokens(value, valueFirstToken, valueLastToken,
                           varFirstToken, varLastToken, fallback);
              // Now we're either at the pushed back ')' that finished the
              // fallback or at EOF.
              DebugOnly<bool> gotToken = GetToken(false);
              MOZ_ASSERT(!gotToken || mToken.IsSymbol(')'));
            }
          } else {
            // Expected ',' or ')' after the variable name.
            mScanner->StartRecording();
            return false;
          }
        } else {
          stack.AppendElement(')');
          UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Function);
        }
        break;

      case eCSSToken_Bad_String:
      case eCSSToken_Bad_URL:
        return false;

      case eCSSToken_Whitespace:
        UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Whitespace);
        break;

      case eCSSToken_AtKeyword:
      case eCSSToken_Hash:
        UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_AtKeyword_or_Hash);
        break;

      case eCSSToken_Number:
        UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Number);
        break;

      case eCSSToken_Dimension:
        UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Dimension);
        break;

      case eCSSToken_Ident:
        UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Ident);
        break;

      case eCSSToken_Percentage:
        UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Percentage);
        break;

      case eCSSToken_URange:
        UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_URange);
        break;

      case eCSSToken_URL:
        UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_URL_or_BadURL);
        break;

      case eCSSToken_HTMLComment:
        if (mToken.mIdent[0] == '-') {
          UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_CDC);
        } else {
          UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Other);
        }
        break;

      case eCSSToken_Dashmatch:
        UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_DashMatch);
        break;

      case eCSSToken_Containsmatch:
        UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_ContainsMatch);
        break;

      default:
        MOZ_FALLTHROUGH_ASSERT("unexpected token type");
      case eCSSToken_ID:
      case eCSSToken_String:
      case eCSSToken_Includes:
      case eCSSToken_Beginsmatch:
      case eCSSToken_Endsmatch:
        UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Other);
        break;
    }

    lengthBeforeVar = mScanner->RecordingLength();
  }

#undef UPDATE_RECORDING_TOKENS

  aResult.Append(value);
  StopRecordingAndAppendTokens(aResult, valueFirstToken, valueLastToken,
                               recFirstToken, recLastToken, mScanner);

  // Append any implicitly closed brackets.
  if (!stack.IsEmpty()) {
    do {
      aResult.Append(stack.LastElement());
      stack.TruncateLength(stack.Length() - 1);
    } while (!stack.IsEmpty());
    valueLastToken = eCSSTokenSerialization_Other;
  }

  mScanner->StartRecording();
  aResultFirstToken = valueFirstToken;
  aResultLastToken = valueLastToken;
  return true;
}

bool
CSSParserImpl::ResolveValueWithVariableReferences(
                                        const CSSVariableValues* aVariables,
                                        nsString& aResult,
                                        nsCSSTokenSerializationType& aFirstToken,
                                        nsCSSTokenSerializationType& aLastToken)
{
  aResult.Truncate(0);

  // Start recording before we read the first token.
  mScanner->StartRecording();

  if (!GetToken(false)) {
    // Value was empty since we reached EOF.
    mScanner->StopRecording();
    return false;
  }

  UngetToken();

  nsString value;
  nsCSSTokenSerializationType firstToken, lastToken;
  bool ok = ResolveValueWithVariableReferencesRec(value, firstToken, lastToken, aVariables) &&
            !GetToken(true);

  mScanner->StopRecording();

  if (ok) {
    aResult = value;
    aFirstToken = firstToken;
    aLastToken = lastToken;
  }
  return ok;
}

bool
CSSParserImpl::ResolveVariableValue(const nsAString& aPropertyValue,
                                    const CSSVariableValues* aVariables,
                                    nsString& aResult,
                                    nsCSSTokenSerializationType& aFirstToken,
                                    nsCSSTokenSerializationType& aLastToken)
{
  nsCSSScanner scanner(aPropertyValue, 0);

  // At this point, we know that aPropertyValue is syntactically correct
  // for a token stream that has variable references.  We also won't be
  // interpreting any of the stream as we parse it, apart from expanding
  // var() references, so we don't need a base URL etc. or any useful
  // error reporting.
  css::ErrorReporter reporter(scanner, nullptr, nullptr, nullptr);
  InitScanner(scanner, reporter, nullptr, nullptr, nullptr);

  bool valid = ResolveValueWithVariableReferences(aVariables, aResult,
                                                  aFirstToken, aLastToken);

  ReleaseScanner();
  return valid;
}

void
CSSParserImpl::ParsePropertyWithVariableReferences(
                                            nsCSSPropertyID aPropertyID,
                                            nsCSSPropertyID aShorthandPropertyID,
                                            const nsAString& aValue,
                                            const CSSVariableValues* aVariables,
                                            nsRuleData* aRuleData,
                                            nsIURI* aDocURL,
                                            nsIURI* aBaseURL,
                                            nsIPrincipal* aDocPrincipal,
                                            CSSStyleSheet* aSheet,
                                            uint32_t aLineNumber,
                                            uint32_t aLineOffset)
{
  mTempData.AssertInitialState();

  bool valid;
  nsString expandedValue;

  // Resolve any variable references in the property value.
  {
    nsCSSScanner scanner(aValue, 0);
    css::ErrorReporter reporter(scanner, aSheet, mChildLoader, aDocURL);
    InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal);

    nsCSSTokenSerializationType firstToken, lastToken;
    valid = ResolveValueWithVariableReferences(aVariables, expandedValue,
                                               firstToken, lastToken);
    if (!valid) {
      NS_ConvertASCIItoUTF16 propName(nsCSSProps::GetStringValue(aPropertyID));
      REPORT_UNEXPECTED(PEInvalidVariableReference);
      REPORT_UNEXPECTED_P(PEValueParsingError, propName);
      if (nsCSSProps::IsInherited(aPropertyID)) {
        REPORT_UNEXPECTED(PEValueWithVariablesFallbackInherit);
      } else {
        REPORT_UNEXPECTED(PEValueWithVariablesFallbackInitial);
      }
      OUTPUT_ERROR_WITH_POSITION(aLineNumber, aLineOffset);
    }
    ReleaseScanner();
  }

  nsCSSPropertyID propertyToParse =
    aShorthandPropertyID != eCSSProperty_UNKNOWN ? aShorthandPropertyID :
                                                   aPropertyID;

  // Parse the property with that resolved value.
  if (valid) {
    nsCSSScanner scanner(expandedValue, 0);
    css::ErrorReporter reporter(scanner, aSheet, mChildLoader, aDocURL);
    InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal);
    valid = ParseProperty(propertyToParse);
    if (valid && GetToken(true)) {
      REPORT_UNEXPECTED_TOKEN(PEExpectEndValue);
      valid = false;
    }
    if (!valid) {
      NS_ConvertASCIItoUTF16 propName(nsCSSProps::GetStringValue(
                                                              propertyToParse));
      REPORT_UNEXPECTED_P_V(PEValueWithVariablesParsingErrorInValue,
                            propName, expandedValue);
      if (nsCSSProps::IsInherited(aPropertyID)) {
        REPORT_UNEXPECTED(PEValueWithVariablesFallbackInherit);
      } else {
        REPORT_UNEXPECTED(PEValueWithVariablesFallbackInitial);
      }
      OUTPUT_ERROR_WITH_POSITION(aLineNumber, aLineOffset);
    }
    ReleaseScanner();
  }

  // If the property could not be parsed with the resolved value, then we
  // treat it as if the value were 'initial' or 'inherit', depending on whether
  // the property is an inherited property.
  if (!valid) {
    nsCSSValue defaultValue;
    if (nsCSSProps::IsInherited(aPropertyID)) {
      defaultValue.SetInheritValue();
    } else {
      defaultValue.SetInitialValue();
    }
    mTempData.AddLonghandProperty(aPropertyID, defaultValue);
  }

  // Copy the property value into the rule data.
  mTempData.MapRuleInfoInto(aPropertyID, aRuleData);

  mTempData.ClearProperty(propertyToParse);
  mTempData.AssertInitialState();
}

bool
CSSParserImpl::ParseCounterStyleName(const nsAString& aBuffer,
                                     nsIURI* aURL,
                                     nsAString& aName)
{
  nsCSSScanner scanner(aBuffer, 0);
  css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURL);
  InitScanner(scanner, reporter, aURL, aURL, nullptr);

  bool success = ParseCounterStyleName(aName, true) && !GetToken(true);

  OUTPUT_ERROR();
  ReleaseScanner();

  return success;
}

bool
CSSParserImpl::ParseCounterDescriptor(nsCSSCounterDesc aDescID,
                                      const nsAString& aBuffer,
                                      nsIURI* aSheetURL,
                                      nsIURI* aBaseURL,
                                      nsIPrincipal* aSheetPrincipal,
                                      nsCSSValue& aValue)
{
  nsCSSScanner scanner(aBuffer, 0);
  css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURL);
  InitScanner(scanner, reporter, aSheetURL, aBaseURL, aSheetPrincipal);

  bool success = ParseCounterDescriptorValue(aDescID, aValue) &&
                 !GetToken(true);

  OUTPUT_ERROR();
  ReleaseScanner();

  return success;
}

bool
CSSParserImpl::ParseFontFaceDescriptor(nsCSSFontDesc aDescID,
                                       const nsAString& aBuffer,
                                       nsIURI* aSheetURL,
                                       nsIURI* aBaseURL,
                                       nsIPrincipal* aSheetPrincipal,
                                       nsCSSValue& aValue)
{
  nsCSSScanner scanner(aBuffer, 0);
  css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURL);
  InitScanner(scanner, reporter, aSheetURL, aBaseURL, aSheetPrincipal);

  bool success = ParseFontDescriptorValue(aDescID, aValue) &&
                 !GetToken(true);

  OUTPUT_ERROR();
  ReleaseScanner();

  return success;
}

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

bool
CSSParserImpl::GetToken(bool aSkipWS)
{
  if (mHavePushBack) {
    mHavePushBack = false;
    if (!aSkipWS || mToken.mType != eCSSToken_Whitespace) {
      return true;
    }
  }
  return mScanner->Next(mToken, aSkipWS ?
                        eCSSScannerExclude_WhitespaceAndComments :
                        eCSSScannerExclude_Comments);
}

void
CSSParserImpl::UngetToken()
{
  NS_PRECONDITION(!mHavePushBack, "double pushback");
  mHavePushBack = true;
}

bool
CSSParserImpl::GetNextTokenLocation(bool aSkipWS, uint32_t *linenum, uint32_t *colnum)
{
  // Peek at next token so that mScanner updates line and column vals
  if (!GetToken(aSkipWS)) {
    return false;
  }
  UngetToken();
  // The scanner uses one-indexing for line numbers but zero-indexing
  // for column numbers.
  *linenum = mScanner->GetLineNumber();
  *colnum = 1 + mScanner->GetColumnNumber();
  return true;
}

bool
CSSParserImpl::ExpectSymbol(char16_t aSymbol,
                            bool aSkipWS)
{
  if (!GetToken(aSkipWS)) {
    // CSS2.1 specifies that all "open constructs" are to be closed at
    // EOF.  It simplifies higher layers if we claim to have found an
    // ), ], }, or ; if we encounter EOF while looking for one of them.
    // Do still issue a diagnostic, to aid debugging.
    if (aSymbol == ')' || aSymbol == ']' ||
        aSymbol == '}' || aSymbol == ';') {
      REPORT_UNEXPECTED_EOF_CHAR(aSymbol);
      return true;
    }
    else
      return false;
  }
  if (mToken.IsSymbol(aSymbol)) {
    return true;
  }
  UngetToken();
  return false;
}

// Checks to see if we're at the end of a property.  If an error occurs during
// the check, does not signal a parse error.
bool
CSSParserImpl::CheckEndProperty()
{
  if (!GetToken(true)) {
    return true; // properties may end with eof
  }
  if ((eCSSToken_Symbol == mToken.mType) &&
      ((';' == mToken.mSymbol) ||
       ('!' == mToken.mSymbol) ||
       ('}' == mToken.mSymbol) ||
       (')' == mToken.mSymbol))) {
    // XXX need to verify that ! is only followed by "important [;|}]
    // XXX this requires a multi-token pushback buffer
    UngetToken();
    return true;
  }
  UngetToken();
  return false;
}

// Checks if we're at the end of a property, raising an error if we're not.
bool
CSSParserImpl::ExpectEndProperty()
{
  if (CheckEndProperty())
    return true;

  // If we're here, we read something incorrect, so we should report it.
  REPORT_UNEXPECTED_TOKEN(PEExpectEndValue);
  return false;
}

// Parses the priority suffix on a property, which at present may be
// either '!important' or nothing.
CSSParserImpl::PriorityParsingStatus
CSSParserImpl::ParsePriority()
{
  if (!GetToken(true)) {
    return ePriority_None; // properties may end with EOF
  }
  if (!mToken.IsSymbol('!')) {
    UngetToken();
    return ePriority_None; // dunno what it is, but it's not a priority
  }

  if (!GetToken(true)) {
    // EOF is not ok after !
    REPORT_UNEXPECTED_EOF(PEImportantEOF);
    return ePriority_Error;
  }

  if (mToken.mType != eCSSToken_Ident ||
      !mToken.mIdent.LowerCaseEqualsLiteral("important")) {
    REPORT_UNEXPECTED_TOKEN(PEExpectedImportant);
    UngetToken();
    return ePriority_Error;
  }

  return ePriority_Important;
}

nsSubstring*
CSSParserImpl::NextIdent()
{
  // XXX Error reporting?
  if (!GetToken(true)) {
    return nullptr;
  }
  if (eCSSToken_Ident != mToken.mType) {
    UngetToken();
    return nullptr;
  }
  return &mToken.mIdent;
}

bool
CSSParserImpl::SkipAtRule(bool aInsideBlock)
{
  for (;;) {
    if (!GetToken(true)) {
      REPORT_UNEXPECTED_EOF(PESkipAtRuleEOF2);
      return false;
    }
    if (eCSSToken_Symbol == mToken.mType) {
      char16_t symbol = mToken.mSymbol;
      if (symbol == ';') {
        break;
      }
      if (aInsideBlock && symbol == '}') {
        // The closing } doesn't belong to us.
        UngetToken();
        break;
      }
      if (symbol == '{') {
        SkipUntil('}');
        break;
      } else if (symbol == '(') {
        SkipUntil(')');
      } else if (symbol == '[') {
        SkipUntil(']');
      }
    } else if (eCSSToken_Function == mToken.mType ||
               eCSSToken_Bad_URL == mToken.mType) {
      SkipUntil(')');
    }
  }
  return true;
}

bool
CSSParserImpl::ParseAtRule(RuleAppendFunc aAppendFunc,
                           void* aData,
                           bool aInAtRule)
{

  nsCSSSection newSection;
  bool (CSSParserImpl::*parseFunc)(RuleAppendFunc, void*);

  if ((mSection <= eCSSSection_Charset) &&
      (mToken.mIdent.LowerCaseEqualsLiteral("charset"))) {
    parseFunc = &CSSParserImpl::ParseCharsetRule;
    newSection = eCSSSection_Import;  // only one charset allowed

  } else if ((mSection <= eCSSSection_Import) &&
             mToken.mIdent.LowerCaseEqualsLiteral("import")) {
    parseFunc = &CSSParserImpl::ParseImportRule;
    newSection = eCSSSection_Import;

  } else if ((mSection <= eCSSSection_NameSpace) &&
             mToken.mIdent.LowerCaseEqualsLiteral("namespace")) {
    parseFunc = &CSSParserImpl::ParseNameSpaceRule;
    newSection = eCSSSection_NameSpace;

  } else if (mToken.mIdent.LowerCaseEqualsLiteral("media")) {
    parseFunc = &CSSParserImpl::ParseMediaRule;
    newSection = eCSSSection_General;

  } else if (mToken.mIdent.LowerCaseEqualsLiteral("-moz-document")) {
    parseFunc = &CSSParserImpl::ParseMozDocumentRule;
    newSection = eCSSSection_General;

  } else if (mToken.mIdent.LowerCaseEqualsLiteral("font-face")) {
    parseFunc = &CSSParserImpl::ParseFontFaceRule;
    newSection = eCSSSection_General;

  } else if (mToken.mIdent.LowerCaseEqualsLiteral("font-feature-values")) {
    parseFunc = &CSSParserImpl::ParseFontFeatureValuesRule;
    newSection = eCSSSection_General;

  } else if (mToken.mIdent.LowerCaseEqualsLiteral("page")) {
    parseFunc = &CSSParserImpl::ParsePageRule;
    newSection = eCSSSection_General;

  } else if ((nsCSSProps::IsEnabled(eCSSPropertyAlias_MozAnimation,
                                    EnabledState()) &&
              mToken.mIdent.LowerCaseEqualsLiteral("-moz-keyframes")) ||
             (nsCSSProps::IsEnabled(eCSSPropertyAlias_WebkitAnimation) &&
              mToken.mIdent.LowerCaseEqualsLiteral("-webkit-keyframes")) ||
             mToken.mIdent.LowerCaseEqualsLiteral("keyframes")) {
    parseFunc = &CSSParserImpl::ParseKeyframesRule;
    newSection = eCSSSection_General;

  } else if (mToken.mIdent.LowerCaseEqualsLiteral("supports")) {
    parseFunc = &CSSParserImpl::ParseSupportsRule;
    newSection = eCSSSection_General;

  } else if (mToken.mIdent.LowerCaseEqualsLiteral("counter-style")) {
    parseFunc = &CSSParserImpl::ParseCounterStyleRule;
    newSection = eCSSSection_General;

  } else {
    if (!NonMozillaVendorIdentifier(mToken.mIdent)) {
      REPORT_UNEXPECTED_TOKEN(PEUnknownAtRule);
      OUTPUT_ERROR();
    }
    // Skip over unsupported at rule, don't advance section
    return SkipAtRule(aInAtRule);
  }

  // Inside of @-rules, only the rules that can occur anywhere
  // are allowed.
  bool unnestable = aInAtRule && newSection != eCSSSection_General;
  if (unnestable) {
    REPORT_UNEXPECTED_TOKEN(PEGroupRuleNestedAtRule);
  }

  if (unnestable || !(this->*parseFunc)(aAppendFunc, aData)) {
    // Skip over invalid at rule, don't advance section
    OUTPUT_ERROR();
    return SkipAtRule(aInAtRule);
  }

  // Nested @-rules don't affect the top-level rule chain requirement
  if (!aInAtRule) {
    mSection = newSection;
  }

  return true;
}

bool
CSSParserImpl::ParseCharsetRule(RuleAppendFunc aAppendFunc,
                                void* aData)
{
  uint32_t linenum, colnum;
  if (!GetNextTokenLocation(true, &linenum, &colnum) ||
      !GetToken(true)) {
    REPORT_UNEXPECTED_EOF(PECharsetRuleEOF);
    return false;
  }

  if (eCSSToken_String != mToken.mType) {
    UngetToken();
    REPORT_UNEXPECTED_TOKEN(PECharsetRuleNotString);
    return false;
  }

  nsAutoString charset = mToken.mIdent;

  if (!ExpectSymbol(';', true)) {
    return false;
  }

  // It's intentional that we don't create a rule object for @charset rules.

  return true;
}

bool
CSSParserImpl::ParseURLOrString(nsString& aURL)
{
  if (!GetToken(true)) {
    return false;
  }
  if (eCSSToken_String == mToken.mType || eCSSToken_URL == mToken.mType) {
    aURL = mToken.mIdent;
    return true;
  }
  UngetToken();
  return false;
}

bool
CSSParserImpl::ParseMediaQuery(eMediaQueryType aQueryType,
                               nsMediaQuery **aQuery,
                               bool *aHitStop)
{
  *aQuery = nullptr;
  *aHitStop = false;
  bool inAtRule = aQueryType == eMediaQueryAtRule;
  // Attempt to parse a single condition and stop
  bool singleCondition = aQueryType == eMediaQuerySingleCondition;

  // "If the comma-separated list is the empty list it is assumed to
  // specify the media query 'all'."  (css3-mediaqueries, section
  // "Media Queries")
  if (!GetToken(true)) {
    *aHitStop = true;
    // expected termination by EOF
    if (!inAtRule)
      return true;

    // unexpected termination by EOF
    REPORT_UNEXPECTED_EOF(PEGatherMediaEOF);
    return true;
  }

  if (eCSSToken_Symbol == mToken.mType && inAtRule &&
      (mToken.mSymbol == ';' || mToken.mSymbol == '{' || mToken.mSymbol == '}' )) {
    *aHitStop = true;
    UngetToken();
    return true;
  }
  UngetToken();

  nsMediaQuery* query = new nsMediaQuery;
  *aQuery = query;

  if (ExpectSymbol('(', true)) {
    // we got an expression without a media type
    UngetToken(); // so ParseMediaQueryExpression can handle it
    query->SetType(nsGkAtoms::all);
    query->SetTypeOmitted();
    // Just parse the first expression here.
    if (!ParseMediaQueryExpression(query)) {
      OUTPUT_ERROR();
      query->SetHadUnknownExpression();
    }
  } else if (singleCondition) {
    // Since we are only trying to consume a single condition, which precludes
    // media types and not/only, this should be the same as reaching immediate
    // EOF (no condition to parse)
    *aHitStop = true;
    return true;
  } else {
    nsCOMPtr<nsIAtom> mediaType;
    bool gotNotOrOnly = false;
    for (;;) {
      if (!GetToken(true)) {
        REPORT_UNEXPECTED_EOF(PEGatherMediaEOF);
        return false;
      }
      if (eCSSToken_Ident != mToken.mType) {
        REPORT_UNEXPECTED_TOKEN(PEGatherMediaNotIdent);
        UngetToken();
        return false;
      }
      // case insensitive from CSS - must be lower cased
      nsContentUtils::ASCIIToLower(mToken.mIdent);
      mediaType = NS_Atomize(mToken.mIdent);
      if (!gotNotOrOnly && mediaType == nsGkAtoms::_not) {
        gotNotOrOnly = true;
        query->SetNegated();
      } else if (!gotNotOrOnly && mediaType == nsGkAtoms::only) {
        gotNotOrOnly = true;
        query->SetHasOnly();
      } else if (mediaType == nsGkAtoms::_not ||
                 mediaType == nsGkAtoms::only ||
                 mediaType == nsGkAtoms::_and ||
                 mediaType == nsGkAtoms::_or) {
        REPORT_UNEXPECTED_TOKEN(PEGatherMediaReservedMediaType);
        UngetToken();
        return false;
      } else {
        // valid media type
        break;
      }
    }
    query->SetType(mediaType);
  }

  for (;;) {
    if (!GetToken(true)) {
      *aHitStop = true;
      // expected termination by EOF
      if (!inAtRule)
        break;

      // unexpected termination by EOF
      REPORT_UNEXPECTED_EOF(PEGatherMediaEOF);
      break;
    }

    if (eCSSToken_Symbol == mToken.mType && inAtRule &&
        (mToken.mSymbol == ';' || mToken.mSymbol == '{' || mToken.mSymbol == '}')) {
      *aHitStop = true;
      UngetToken();
      break;
    }
    if (!singleCondition &&
        eCSSToken_Symbol == mToken.mType && mToken.mSymbol == ',') {
      // Done with the expressions for this query
      break;
    }
    if (eCSSToken_Ident != mToken.mType ||
        !mToken.mIdent.LowerCaseEqualsLiteral("and")) {
      if (singleCondition) {
        // We have a condition at this point -- if we're not chained to other
        // conditions with and/or, we're done.
        UngetToken();
        break;
      } else {
        REPORT_UNEXPECTED_TOKEN(PEGatherMediaNotComma);
        UngetToken();
        return false;
      }
    }
    if (!ParseMediaQueryExpression(query)) {
      OUTPUT_ERROR();
      query->SetHadUnknownExpression();
    }
  }
  return true;
}

// Returns false only when there is a low-level error in the scanner
// (out-of-memory).
bool
CSSParserImpl::GatherMedia(nsMediaList* aMedia,
                           bool aInAtRule)
{
  eMediaQueryType type = aInAtRule ? eMediaQueryAtRule : eMediaQueryNormal;
  for (;;) {
    nsAutoPtr<nsMediaQuery> query;
    bool hitStop;
    if (!ParseMediaQuery(type, getter_Transfers(query), &hitStop)) {
      NS_ASSERTION(!hitStop, "should return true when hit stop");
      OUTPUT_ERROR();
      if (query) {
        query->SetHadUnknownExpression();
      }
      if (aInAtRule) {
        const char16_t stopChars[] =
          { char16_t(','), char16_t('{'), char16_t(';'), char16_t('}'), char16_t(0) };
        SkipUntilOneOf(stopChars);
      } else {
        SkipUntil(',');
      }
      // Rely on SkipUntilOneOf leaving mToken around as the last token read.
      if (mToken.mType == eCSSToken_Symbol && aInAtRule &&
          (mToken.mSymbol == '{' || mToken.mSymbol == ';'  || mToken.mSymbol == '}')) {
        UngetToken();
        hitStop = true;
      }
    }
    if (query) {
      aMedia->AppendQuery(query);
    }
    if (hitStop) {
      break;
    }
  }
  return true;
}

bool
CSSParserImpl::ParseMediaQueryExpression(nsMediaQuery* aQuery)
{
  if (!ExpectSymbol('(', true)) {
    REPORT_UNEXPECTED_TOKEN(PEMQExpectedExpressionStart);
    return false;
  }
  if (! GetToken(true)) {
    REPORT_UNEXPECTED_EOF(PEMQExpressionEOF);
    return false;
  }
  if (eCSSToken_Ident != mToken.mType) {
    REPORT_UNEXPECTED_TOKEN(PEMQExpectedFeatureName);
    UngetToken();
    SkipUntil(')');
    return false;
  }

  nsMediaExpression *expr = aQuery->NewExpression();

  // case insensitive from CSS - must be lower cased
  nsContentUtils::ASCIIToLower(mToken.mIdent);
  nsDependentString featureString(mToken.mIdent, 0);
  uint8_t satisfiedReqFlags = 0;

  // Strip off "-webkit-" prefix from featureString:
  if (sWebkitPrefixedAliasesEnabled &&
      StringBeginsWith(featureString, NS_LITERAL_STRING("-webkit-"))) {
    satisfiedReqFlags |= nsMediaFeature::eHasWebkitPrefix;
    featureString.Rebind(featureString, 8);
  }
  if (sWebkitDevicePixelRatioEnabled) {
    satisfiedReqFlags |= nsMediaFeature::eWebkitDevicePixelRatioPrefEnabled;
  }

  // Strip off "min-"/"max-" prefix from featureString:
  if (StringBeginsWith(featureString, NS_LITERAL_STRING("min-"))) {
    expr->mRange = nsMediaExpression::eMin;
    featureString.Rebind(featureString, 4);
  } else if (StringBeginsWith(featureString, NS_LITERAL_STRING("max-"))) {
    expr->mRange = nsMediaExpression::eMax;
    featureString.Rebind(featureString, 4);
  } else {
    expr->mRange = nsMediaExpression::eEqual;
  }

  nsCOMPtr<nsIAtom> mediaFeatureAtom = NS_Atomize(featureString);
  const nsMediaFeature *feature = nsMediaFeatures::features;
  for (; feature->mName; ++feature) {
    // See if name matches & all requirement flags are satisfied:
    // (We check requirements by turning off all of the flags that have been
    // satisfied, and then see if the result is 0.)
    if (*(feature->mName) == mediaFeatureAtom &&
        !(feature->mReqFlags & ~satisfiedReqFlags)) {
      break;
    }
  }
  if (!feature->mName ||
      (expr->mRange != nsMediaExpression::eEqual &&
       feature->mRangeType != nsMediaFeature::eMinMaxAllowed)) {
    REPORT_UNEXPECTED_TOKEN(PEMQExpectedFeatureName);
    SkipUntil(')');
    return false;
  }
  expr->mFeature = feature;

  if (!GetToken(true) || mToken.IsSymbol(')')) {
    // Query expressions for any feature can be given without a value.
    // However, min/max prefixes are not allowed.
    if (expr->mRange != nsMediaExpression::eEqual) {
      REPORT_UNEXPECTED(PEMQNoMinMaxWithoutValue);
      return false;
    }
    expr->mValue.Reset();
    return true;
  }

  if (!mToken.IsSymbol(':')) {
    REPORT_UNEXPECTED_TOKEN(PEMQExpectedFeatureNameEnd);
    UngetToken();
    SkipUntil(')');
    return false;
  }

  bool rv = false;
  switch (feature->mValueType) {
    case nsMediaFeature::eLength:
      rv = ParseSingleTokenNonNegativeVariant(expr->mValue, VARIANT_LENGTH,
                                              nullptr);
      break;
    case nsMediaFeature::eInteger:
    case nsMediaFeature::eBoolInteger:
      rv = ParseNonNegativeInteger(expr->mValue);
      // Enforce extra restrictions for eBoolInteger
      if (rv &&
          feature->mValueType == nsMediaFeature::eBoolInteger &&
          expr->mValue.GetIntValue() > 1)
        rv = false;
      break;
    case nsMediaFeature::eFloat:
      rv = ParseNonNegativeNumber(expr->mValue);
      break;
    case nsMediaFeature::eIntRatio:
      {
        // Two integers separated by '/', with optional whitespace on
        // either side of the '/'.
        RefPtr<nsCSSValue::Array> a = nsCSSValue::Array::Create(2);
        expr->mValue.SetArrayValue(a, eCSSUnit_Array);
        // We don't bother with ParseNonNegativeVariant since we have to
        // check for != 0 as well; no need to worry about the UngetToken
        // since we're throwing out up to the next ')' anyway.
        rv = ParseSingleTokenVariant(a->Item(0), VARIANT_INTEGER, nullptr) &&
             a->Item(0).GetIntValue() > 0 &&
             ExpectSymbol('/', true) &&
             ParseSingleTokenVariant(a->Item(1), VARIANT_INTEGER, nullptr) &&
             a->Item(1).GetIntValue() > 0;
      }
      break;
    case nsMediaFeature::eResolution:
      rv = GetToken(true);
      if (!rv)
        break;
      rv = mToken.mType == eCSSToken_Dimension && mToken.mNumber > 0.0f;
      if (!rv) {
        UngetToken();
        break;
      }
      // No worries about whether unitless zero is allowed, since the
      // value must be positive (and we checked that above).
      NS_ASSERTION(!mToken.mIdent.IsEmpty(), "unit lied");
      if (mToken.mIdent.LowerCaseEqualsLiteral("dpi")) {
        expr->mValue.SetFloatValue(mToken.mNumber, eCSSUnit_Inch);
      } else if (mToken.mIdent.LowerCaseEqualsLiteral("dppx")) {
        expr->mValue.SetFloatValue(mToken.mNumber, eCSSUnit_Pixel);
      } else if (mToken.mIdent.LowerCaseEqualsLiteral("dpcm")) {
        expr->mValue.SetFloatValue(mToken.mNumber, eCSSUnit_Centimeter);
      } else {
        rv = false;
      }
      break;
    case nsMediaFeature::eEnumerated:
      rv = ParseSingleTokenVariant(expr->mValue, VARIANT_KEYWORD,
                                   feature->mData.mKeywordTable);
      break;
    case nsMediaFeature::eIdent:
      rv = ParseSingleTokenVariant(expr->mValue, VARIANT_IDENTIFIER, nullptr);
      break;
  }
  if (!rv || !ExpectSymbol(')', true)) {
    REPORT_UNEXPECTED(PEMQExpectedFeatureValue);
    SkipUntil(')');
    return false;
  }

  return true;
}

// Parse a CSS2 import rule: "@import STRING | URL [medium [, medium]]"
bool
CSSParserImpl::ParseImportRule(RuleAppendFunc aAppendFunc, void* aData)
{
  RefPtr<nsMediaList> media = new nsMediaList();

  uint32_t linenum, colnum;
  nsAutoString url;
  if (!GetNextTokenLocation(true, &linenum, &colnum) ||
      !ParseURLOrString(url)) {
    REPORT_UNEXPECTED_TOKEN(PEImportNotURI);
    return false;
  }

  if (!ExpectSymbol(';', true)) {
    if (!GatherMedia(media, true) ||
        !ExpectSymbol(';', true)) {
      REPORT_UNEXPECTED_TOKEN(PEImportUnexpected);
      // don't advance section, simply ignore invalid @import
      return false;
    }

    // Safe to assert this, since we ensured that there is something
    // other than the ';' coming after the @import's url() token.
    NS_ASSERTION(media->Length() != 0, "media list must be nonempty");
  }

  ProcessImport(url, media, aAppendFunc, aData, linenum, colnum);
  return true;
}

void
CSSParserImpl::ProcessImport(const nsString& aURLSpec,
                             nsMediaList* aMedia,
                             RuleAppendFunc aAppendFunc,
                             void* aData,
                             uint32_t aLineNumber,
                             uint32_t aColumnNumber)
{
  RefPtr<css::ImportRule> rule = new css::ImportRule(aMedia, aURLSpec,
                                                       aLineNumber,
                                                       aColumnNumber);
  (*aAppendFunc)(rule, aData);

  // Diagnose bad URIs even if we don't have a child loader.
  nsCOMPtr<nsIURI> url;
  // Charset will be deduced from mBaseURI, which is more or less correct.
  nsresult rv = NS_NewURI(getter_AddRefs(url), aURLSpec, nullptr, mBaseURI);

  if (NS_FAILED(rv)) {
    if (rv == NS_ERROR_MALFORMED_URI) {
      // import url is bad
      REPORT_UNEXPECTED_P(PEImportBadURI, aURLSpec);
      OUTPUT_ERROR();
    }
    return;
  }

  if (mChildLoader) {
    mChildLoader->LoadChildSheet(mSheet, url, aMedia, rule, mReusableSheets);
  }
}

// Parse the {} part of an @media or @-moz-document rule.
bool
CSSParserImpl::ParseGroupRule(css::GroupRule* aRule,
                              RuleAppendFunc aAppendFunc,
                              void* aData)
{
  // XXXbz this could use better error reporting throughout the method
  if (!ExpectSymbol('{', true)) {
    return false;
  }

  // push rule on stack, loop over children
  PushGroup(aRule);
  nsCSSSection holdSection = mSection;
  mSection = eCSSSection_General;

  for (;;) {
    // Get next non-whitespace token
    if (! GetToken(true)) {
      REPORT_UNEXPECTED_EOF(PEGroupRuleEOF2);
      break;
    }
    if (mToken.IsSymbol('}')) { // done!
      UngetToken();
      break;
    }
    if (eCSSToken_AtKeyword == mToken.mType) {
      // Parse for nested rules
      ParseAtRule(aAppendFunc, aData, true);
      continue;
    }
    UngetToken();
    ParseRuleSet(AppendRuleToSheet, this, true);
  }
  PopGroup();

  if (!ExpectSymbol('}', true)) {
    mSection = holdSection;
    return false;
  }
  (*aAppendFunc)(aRule, aData);
  return true;
}

// Parse a CSS2 media rule: "@media medium [, medium] { ... }"
bool
CSSParserImpl::ParseMediaRule(RuleAppendFunc aAppendFunc, void* aData)
{
  RefPtr<nsMediaList> media = new nsMediaList();
  uint32_t linenum, colnum;
  if (GetNextTokenLocation(true, &linenum, &colnum) &&
      GatherMedia(media, true)) {
    // XXXbz this could use better error reporting throughout the method
    RefPtr<css::MediaRule> rule = new css::MediaRule(linenum, colnum);
    // Append first, so when we do SetMedia() the rule
    // knows what its stylesheet is.
    if (ParseGroupRule(rule, aAppendFunc, aData)) {
      rule->SetMedia(media);
      return true;
    }
  }

  return false;
}

// Parse a @-moz-document rule.  This is like an @media rule, but instead
// of a medium it has a nonempty list of items where each item is either
// url(), url-prefix(), or domain().
bool
CSSParserImpl::ParseMozDocumentRule(RuleAppendFunc aAppendFunc, void* aData)
{
  css::DocumentRule::URL *urls = nullptr;
  css::DocumentRule::URL **next = &urls;

  uint32_t linenum, colnum;
  if (!GetNextTokenLocation(true, &linenum, &colnum)) {
    return false;
  }

  do {
    if (!GetToken(true)) {
      REPORT_UNEXPECTED_EOF(PEMozDocRuleEOF);
      delete urls;
      return false;
    }

    if (!(eCSSToken_URL == mToken.mType ||
          (eCSSToken_Function == mToken.mType &&
           (mToken.mIdent.LowerCaseEqualsLiteral("url-prefix") ||
            mToken.mIdent.LowerCaseEqualsLiteral("domain") ||
            mToken.mIdent.LowerCaseEqualsLiteral("regexp"))))) {
      REPORT_UNEXPECTED_TOKEN(PEMozDocRuleBadFunc2);
      UngetToken();
      delete urls;
      return false;
    }
    css::DocumentRule::URL *cur = *next = new css::DocumentRule::URL;
    next = &cur->next;
    if (mToken.mType == eCSSToken_URL) {
      cur->func = css::DocumentRule::eURL;
      CopyUTF16toUTF8(mToken.mIdent, cur->url);
    } else if (mToken.mIdent.LowerCaseEqualsLiteral("regexp")) {
      // regexp() is different from url-prefix() and domain() (but
      // probably the way they *should* have been* in that it requires a
      // string argument, and doesn't try to behave like url().
      cur->func = css::DocumentRule::eRegExp;
      GetToken(true);
      // copy before we know it's valid (but before ExpectSymbol changes
      // mToken.mIdent)
      CopyUTF16toUTF8(mToken.mIdent, cur->url);
      if (eCSSToken_String != mToken.mType || !ExpectSymbol(')', true)) {
        REPORT_UNEXPECTED_TOKEN(PEMozDocRuleNotString);
        SkipUntil(')');
        delete urls;
        return false;
      }
    } else {
      if (mToken.mIdent.LowerCaseEqualsLiteral("url-prefix")) {
        cur->func = css::DocumentRule::eURLPrefix;
      } else if (mToken.mIdent.LowerCaseEqualsLiteral("domain")) {
        cur->func = css::DocumentRule::eDomain;
      }

      NS_ASSERTION(!mHavePushBack, "mustn't have pushback at this point");
      mScanner->NextURL(mToken);
      if (mToken.mType != eCSSToken_URL) {
        REPORT_UNEXPECTED_TOKEN(PEMozDocRuleNotURI);
        SkipUntil(')');
        delete urls;
        return false;
      }

      // We could try to make the URL (as long as it's not domain())
      // canonical and absolute with NS_NewURI and GetSpec, but I'm
      // inclined to think we shouldn't.
      CopyUTF16toUTF8(mToken.mIdent, cur->url);
    }
  } while (ExpectSymbol(',', true));

  RefPtr<css::DocumentRule> rule = new css::DocumentRule(linenum, colnum);
  rule->SetURLs(urls);

  return ParseGroupRule(rule, aAppendFunc, aData);
}

// Parse a CSS3 namespace rule: "@namespace [prefix] STRING | URL;"
bool
CSSParserImpl::ParseNameSpaceRule(RuleAppendFunc aAppendFunc, void* aData)
{
  uint32_t linenum, colnum;
  if (!GetNextTokenLocation(true, &linenum, &colnum) ||
      !GetToken(true)) {
    REPORT_UNEXPECTED_EOF(PEAtNSPrefixEOF);
    return false;
  }

  nsAutoString  prefix;
  nsAutoString  url;

  if (eCSSToken_Ident == mToken.mType) {
    prefix = mToken.mIdent;
    // user-specified identifiers are case-sensitive (bug 416106)
  } else {
    UngetToken();
  }

  if (!ParseURLOrString(url) || !ExpectSymbol(';', true)) {
    if (mHavePushBack) {
      REPORT_UNEXPECTED_TOKEN(PEAtNSUnexpected);
    } else {
      REPORT_UNEXPECTED_EOF(PEAtNSURIEOF);
    }
    return false;
  }

  ProcessNameSpace(prefix, url, aAppendFunc, aData, linenum, colnum);
  return true;
}

void
CSSParserImpl::ProcessNameSpace(const nsString& aPrefix,
                                const nsString& aURLSpec,
                                RuleAppendFunc aAppendFunc,
                                void* aData,
                                uint32_t aLineNumber,
                                uint32_t aColumnNumber)
{
  nsCOMPtr<nsIAtom> prefix;

  if (!aPrefix.IsEmpty()) {
    prefix = NS_Atomize(aPrefix);
  }

  RefPtr<css::NameSpaceRule> rule = new css::NameSpaceRule(prefix, aURLSpec,
                                                             aLineNumber,
                                                             aColumnNumber);
  (*aAppendFunc)(rule, aData);

  // If this was the first namespace rule encountered, it will trigger
  // creation of a namespace map.
  if (!mNameSpaceMap) {
    mNameSpaceMap = mSheet->GetNameSpaceMap();
  }
}

// font-face-rule: '@font-face' '{' font-description '}'
// font-description: font-descriptor+
bool
CSSParserImpl::ParseFontFaceRule(RuleAppendFunc aAppendFunc, void* aData)
{
  uint32_t linenum, colnum;
  if (!GetNextTokenLocation(true, &linenum, &colnum) ||
      !ExpectSymbol('{', true)) {
    REPORT_UNEXPECTED_TOKEN(PEBadFontBlockStart);
    return false;
  }

  RefPtr<nsCSSFontFaceRule> rule(new nsCSSFontFaceRule(linenum, colnum));

  for (;;) {
    if (!GetToken(true)) {
      REPORT_UNEXPECTED_EOF(PEFontFaceEOF);
      break;
    }
    if (mToken.IsSymbol('}')) { // done!
      UngetToken();
      break;
    }

    // ignore extra semicolons
    if (mToken.IsSymbol(';'))
      continue;

    if (!ParseFontDescriptor(rule)) {
      REPORT_UNEXPECTED(PEDeclSkipped);
      OUTPUT_ERROR();
      if (!SkipDeclaration(true))
        break;
    }
  }
  if (!ExpectSymbol('}', true)) {
    REPORT_UNEXPECTED_TOKEN(PEBadFontBlockEnd);
    return false;
  }
  (*aAppendFunc)(rule, aData);
  return true;
}

// font-descriptor: font-family-desc
//                | font-style-desc
//                | font-weight-desc
//                | font-stretch-desc
//                | font-src-desc
//                | unicode-range-desc
//
// All font-*-desc productions follow the pattern
//    IDENT ':' value ';'
//
// On entry to this function, mToken is the IDENT.

bool
CSSParserImpl::ParseFontDescriptor(nsCSSFontFaceRule* aRule)
{
  if (eCSSToken_Ident != mToken.mType) {
    REPORT_UNEXPECTED_TOKEN(PEFontDescExpected);
    return false;
  }

  nsString descName = mToken.mIdent;
  if (!ExpectSymbol(':', true)) {
    REPORT_UNEXPECTED_TOKEN(PEParseDeclarationNoColon);
    OUTPUT_ERROR();
    return false;
  }

  nsCSSFontDesc descID = nsCSSProps::LookupFontDesc(descName);
  nsCSSValue value;

  if (descID == eCSSFontDesc_UNKNOWN ||
      (descID == eCSSFontDesc_Display &&
       !Preferences::GetBool("layout.css.font-display.enabled"))) {
    if (NonMozillaVendorIdentifier(descName)) {
      // silently skip other vendors' extensions
      SkipDeclaration(true);
      return true;
    } else {
      REPORT_UNEXPECTED_P(PEUnknownFontDesc, descName);
      return false;
    }
  }

  if (!ParseFontDescriptorValue(descID, value)) {
    REPORT_UNEXPECTED_P(PEValueParsingError, descName);
    return false;
  }

  // Expect termination by ;, }, or EOF.
  if (GetToken(true)) {
    if (!mToken.IsSymbol(';') &&
        !mToken.IsSymbol('}')) {
      UngetToken();
      REPORT_UNEXPECTED_TOKEN(PEExpectEndValue);
      return false;
    }
    UngetToken();
  }

  aRule->SetDesc(descID, value);
  return true;
}

// @font-feature-values <font-family># {
//   @<feature-type> {
//     <feature-ident> : <feature-index>+;
//     <feature-ident> : <feature-index>+;
//     ...
//   }
//   ...
// }

bool
CSSParserImpl::ParseFontFeatureValuesRule(RuleAppendFunc aAppendFunc,
                                          void* aData)
{
  uint32_t linenum, colnum;
  if (!GetNextTokenLocation(true, &linenum, &colnum)) {
    return false;
  }

  RefPtr<nsCSSFontFeatureValuesRule>
               valuesRule(new nsCSSFontFeatureValuesRule(linenum, colnum));

  // parse family list
  nsCSSValue fontlistValue;

  if (!ParseFamily(fontlistValue) ||
      fontlistValue.GetUnit() != eCSSUnit_FontFamilyList)
  {
    REPORT_UNEXPECTED_TOKEN(PEFFVNoFamily);
    return false;
  }

  // add family to rule
  const FontFamilyList* fontlist = fontlistValue.GetFontFamilyListValue();

  // family list has generic ==> parse error
  if (fontlist->HasGeneric()) {
    REPORT_UNEXPECTED_TOKEN(PEFFVGenericInFamilyList);
    return false;
  }

  valuesRule->SetFamilyList(*fontlist);

  // open brace
  if (!ExpectSymbol('{', true)) {
    REPORT_UNEXPECTED_TOKEN(PEFFVBlockStart);
    return false;
  }

  // list of sets of feature values, each set bound to a specific
  // feature-type (e.g. swash, annotation)
  for (;;) {
    if (!GetToken(true)) {
      REPORT_UNEXPECTED_EOF(PEFFVUnexpectedEOF);
      break;
    }
    if (mToken.IsSymbol('}')) { // done!
      UngetToken();
      break;
    }

    if (!ParseFontFeatureValueSet(valuesRule)) {
      if (!SkipAtRule(false)) {
        break;
      }
    }
  }
  if (!ExpectSymbol('}', true)) {
    REPORT_UNEXPECTED_TOKEN(PEFFVUnexpectedBlockEnd);
    SkipUntil('}');
    return false;
  }

  (*aAppendFunc)(valuesRule, aData);
  return true;
}

#define NUMVALUES_NO_LIMIT  0xFFFF

// parse a single value set containing name-value pairs for a single feature type
//   @<feature-type> { [ <feature-ident> : <feature-index>+ ; ]* }
//   Ex: @swash { flowing: 1; delicate: 2; }
bool
CSSParserImpl::ParseFontFeatureValueSet(nsCSSFontFeatureValuesRule
                                                            *aFeatureValuesRule)
{
  // -- @keyword (e.g. swash, styleset)
  if (eCSSToken_AtKeyword != mToken.mType) {
    REPORT_UNEXPECTED_TOKEN(PEFontFeatureValuesNoAt);
    OUTPUT_ERROR();
    UngetToken();
    return false;
  }

  // which font-specific variant of font-variant-alternates
  int32_t whichVariant;
  nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
  if (keyword == eCSSKeyword_UNKNOWN ||
      !nsCSSProps::FindKeyword(keyword,
                               nsCSSProps::kFontVariantAlternatesFuncsKTable,
                               whichVariant))
  {
    if (!NonMozillaVendorIdentifier(mToken.mIdent)) {
      REPORT_UNEXPECTED_TOKEN(PEFFVUnknownFontVariantPropValue);
      OUTPUT_ERROR();
    }
    UngetToken();
    return false;
  }

  nsAutoString featureType(mToken.mIdent);

  // open brace
  if (!ExpectSymbol('{', true)) {
    REPORT_UNEXPECTED_TOKEN(PEFFVValueSetStart);
    return false;
  }

  // styleset and character-variant can be multi-valued, otherwise single value
  int32_t limitNumValues;

  switch (keyword) {
    case eCSSKeyword_styleset:
      limitNumValues = NUMVALUES_NO_LIMIT;
      break;
    case eCSSKeyword_character_variant:
      limitNumValues = 2;
      break;
    default:
      limitNumValues = 1;
      break;
  }

  // -- ident integer+ [, ident integer+]
  AutoTArray<gfxFontFeatureValueSet::ValueList, 5> values;

  // list of font-feature-values-declaration's
  for (;;) {
    nsAutoString valueId;

    if (!GetToken(true)) {
      REPORT_UNEXPECTED_EOF(PEFFVUnexpectedEOF);
      break;
    }

    // ignore extra semicolons
    if (mToken.IsSymbol(';')) {
      continue;
    }

    // close brace ==> done
    if (mToken.IsSymbol('}')) {
      break;
    }

    // ident
    if (eCSSToken_Ident != mToken.mType) {
      REPORT_UNEXPECTED_TOKEN(PEFFVExpectedIdent);
      if (!SkipDeclaration(true)) {
        break;
      }
      continue;
    }

    valueId.Assign(mToken.mIdent);

    // colon
    if (!ExpectSymbol(':', true)) {
      REPORT_UNEXPECTED_TOKEN(PEParseDeclarationNoColon);
      OUTPUT_ERROR();
      if (!SkipDeclaration(true)) {
        break;
      }
      continue;
    }

    // value list
    AutoTArray<uint32_t,4>   featureSelectors;

    nsCSSValue intValue;
    while (ParseNonNegativeInteger(intValue)) {
      featureSelectors.AppendElement(uint32_t(intValue.GetIntValue()));
    }

    int32_t numValues = featureSelectors.Length();

    if (numValues == 0) {
      REPORT_UNEXPECTED_TOKEN(PEFFVExpectedValue);
      OUTPUT_ERROR();
      if (!SkipDeclaration(true)) {
        break;
      }
      continue;
    }

    if (numValues > limitNumValues) {
      REPORT_UNEXPECTED_P(PEFFVTooManyValues, featureType);
      OUTPUT_ERROR();
      if (!SkipDeclaration(true)) {
        break;
      }
      continue;
    }

    if (!GetToken(true)) {
      REPORT_UNEXPECTED_EOF(PEFFVUnexpectedEOF);
      gfxFontFeatureValueSet::ValueList v(valueId, featureSelectors);
      values.AppendElement(v);
      break;
    }

    // ';' or '}' to end definition
    if (!mToken.IsSymbol(';') && !mToken.IsSymbol('}')) {
      REPORT_UNEXPECTED_TOKEN(PEFFVValueDefinitionTrailing);
      OUTPUT_ERROR();
      if (!SkipDeclaration(true)) {
        break;
      }
      continue;
    }

    gfxFontFeatureValueSet::ValueList v(valueId, featureSelectors);
    values.AppendElement(v);

    if (mToken.IsSymbol('}')) {
      break;
    }
 }

  aFeatureValuesRule->AddValueList(whichVariant, values);
  return true;
}

bool
CSSParserImpl::ParseKeyframesRule(RuleAppendFunc aAppendFunc, void* aData)
{
  uint32_t linenum, colnum;
  if (!GetNextTokenLocation(true, &linenum, &colnum) ||
      !GetToken(true)) {
    REPORT_UNEXPECTED_EOF(PEKeyframeNameEOF);
    return false;
  }

  if (mToken.mType != eCSSToken_Ident) {
    REPORT_UNEXPECTED_TOKEN(PEKeyframeBadName);
    UngetToken();
    return false;
  }
  nsString name(mToken.mIdent);

  if (!ExpectSymbol('{', true)) {
    REPORT_UNEXPECTED_TOKEN(PEKeyframeBrace);
    return false;
  }

  RefPtr<nsCSSKeyframesRule> rule = new nsCSSKeyframesRule(name,
                                                             linenum, colnum);

  while (!ExpectSymbol('}', true)) {
    RefPtr<nsCSSKeyframeRule> kid = ParseKeyframeRule();
    if (kid) {
      rule->AppendStyleRule(kid);
    } else {
      OUTPUT_ERROR();
      SkipRuleSet(true);
    }
  }

  (*aAppendFunc)(rule, aData);
  return true;
}

bool
CSSParserImpl::ParsePageRule(RuleAppendFunc aAppendFunc, void* aData)
{
  uint32_t linenum, colnum;
  if (!GetNextTokenLocation(true, &linenum, &colnum)) {
    return false;
  }

  // TODO: There can be page selectors after @page such as ":first", ":left".
  uint32_t parseFlags = eParseDeclaration_InBraces |
                        eParseDeclaration_AllowImportant;

  // Forbid viewport units in @page rules. See bug 811391.
  MOZ_ASSERT(mViewportUnitsEnabled,
             "Viewport units should be enabled outside of @page rules.");
  mViewportUnitsEnabled = false;
  RefPtr<css::Declaration> declaration =
    ParseDeclarationBlock(parseFlags, eCSSContext_Page);
  mViewportUnitsEnabled = true;

  if (!declaration) {
    return false;
  }

  RefPtr<nsCSSPageRule> rule =
    new nsCSSPageRule(declaration, linenum, colnum);

  (*aAppendFunc)(rule, aData);
  return true;
}

already_AddRefed<nsCSSKeyframeRule>
CSSParserImpl::ParseKeyframeRule()
{
  InfallibleTArray<float> selectorList;
  uint32_t linenum, colnum;
  if (!GetNextTokenLocation(true, &linenum, &colnum) ||
      !ParseKeyframeSelectorList(selectorList)) {
    REPORT_UNEXPECTED(PEBadSelectorKeyframeRuleIgnored);
    return nullptr;
  }

  // Ignore !important in keyframe rules
  uint32_t parseFlags = eParseDeclaration_InBraces;
  RefPtr<css::Declaration> declaration(ParseDeclarationBlock(parseFlags));
  if (!declaration) {
    return nullptr;
  }

  // Takes ownership of declaration
  RefPtr<nsCSSKeyframeRule> rule =
    new nsCSSKeyframeRule(Move(selectorList), declaration.forget(),
                          linenum, colnum);
  return rule.forget();
}

bool
CSSParserImpl::ParseKeyframeSelectorList(InfallibleTArray<float>& aSelectorList)
{
  for (;;) {
    if (!GetToken(true)) {
      // The first time through the loop, this means we got an empty
      // list.  Otherwise, it means we have a trailing comma.
      return false;
    }
    float value;
    switch (mToken.mType) {
      case eCSSToken_Percentage:
        value = mToken.mNumber;
        break;
      case eCSSToken_Ident:
        if (mToken.mIdent.LowerCaseEqualsLiteral("from")) {
          value = 0.0f;
          break;
        }
        if (mToken.mIdent.LowerCaseEqualsLiteral("to")) {
          value = 1.0f;
          break;
        }
        MOZ_FALLTHROUGH;
      default:
        UngetToken();
        // The first time through the loop, this means we got an empty
        // list.  Otherwise, it means we have a trailing comma.
        return false;
    }
    aSelectorList.AppendElement(value);
    if (!ExpectSymbol(',', true)) {
      return true;
    }
  }
}

// supports_rule
//   : "@supports" supports_condition group_rule_body
//   ;
bool
CSSParserImpl::ParseSupportsRule(RuleAppendFunc aAppendFunc, void* aProcessData)
{
  bool conditionMet = false;
  nsString condition;

  mScanner->StartRecording();

  uint32_t linenum, colnum;
  if (!GetNextTokenLocation(true, &linenum, &colnum)) {
    return false;
  }

  bool parsed = ParseSupportsCondition(conditionMet);

  if (!parsed) {
    mScanner->StopRecording();
    return false;
  }

  if (!ExpectSymbol('{', true)) {
    REPORT_UNEXPECTED_TOKEN(PESupportsGroupRuleStart);
    mScanner->StopRecording();
    return false;
  }

  UngetToken();
  mScanner->StopRecording(condition);

  // Remove the "{" that would follow the condition.
  if (condition.Length() != 0) {
    condition.Truncate(condition.Length() - 1);
  }

  // Remove spaces from the start and end of the recorded supports condition.
  condition.Trim(" ", true, true, false);

  // Record whether we are in a failing @supports, so that property parse
  // errors don't get reported.
  nsAutoFailingSupportsRule failing(this, conditionMet);

  RefPtr<css::GroupRule> rule = new CSSSupportsRule(conditionMet, condition,
                                                      linenum, colnum);
  return ParseGroupRule(rule, aAppendFunc, aProcessData);
}

// supports_condition
//   : supports_condition_in_parens supports_condition_terms
//   | supports_condition_negation
//   ;
bool
CSSParserImpl::ParseSupportsCondition(bool& aConditionMet)
{
  nsAutoInSupportsCondition aisc(this);

  if (!GetToken(true)) {
    REPORT_UNEXPECTED_EOF(PESupportsConditionStartEOF2);
    return false;
  }

  UngetToken();

  mScanner->ClearSeenBadToken();

  if (mToken.IsSymbol('(') ||
      mToken.mType == eCSSToken_Function ||
      mToken.mType == eCSSToken_URL ||
      mToken.mType == eCSSToken_Bad_URL) {
    bool result = ParseSupportsConditionInParens(aConditionMet) &&
                  ParseSupportsConditionTerms(aConditionMet) &&
                  !mScanner->SeenBadToken();
    return result;
  }

  if (mToken.mType == eCSSToken_Ident &&
      mToken.mIdent.LowerCaseEqualsLiteral("not")) {
    bool result = ParseSupportsConditionNegation(aConditionMet) &&
                  !mScanner->SeenBadToken();
    return result;
  }

  REPORT_UNEXPECTED_TOKEN(PESupportsConditionExpectedStart);
  return false;
}

// supports_condition_negation
//   : 'not' S+ supports_condition_in_parens
//   ;
bool
CSSParserImpl::ParseSupportsConditionNegation(bool& aConditionMet)
{
  if (!GetToken(true)) {
    REPORT_UNEXPECTED_EOF(PESupportsConditionNotEOF);
    return false;
  }

  if (mToken.mType != eCSSToken_Ident ||
      !mToken.mIdent.LowerCaseEqualsLiteral("not")) {
    REPORT_UNEXPECTED_TOKEN(PESupportsConditionExpectedNot);
    return false;
  }

  if (!RequireWhitespace()) {
    REPORT_UNEXPECTED(PESupportsWhitespaceRequired);
    return false;
  }

  if (ParseSupportsConditionInParens(aConditionMet)) {
    aConditionMet = !aConditionMet;
    return true;
  }

  return false;
}

// supports_condition_in_parens
//   : '(' S* supports_condition_in_parens_inside_parens ')' S*
//   | supports_condition_pref
//   | general_enclosed
//   ;
bool
CSSParserImpl::ParseSupportsConditionInParens(bool& aConditionMet)
{
  if (!GetToken(true)) {
    REPORT_UNEXPECTED_EOF(PESupportsConditionInParensStartEOF);
    return false;
  }

  if (mToken.mType == eCSSToken_URL) {
    aConditionMet = false;
    return true;
  }

  if (AgentRulesEnabled() &&
      mToken.mType == eCSSToken_Function &&
      mToken.mIdent.LowerCaseEqualsLiteral("-moz-bool-pref")) {
    return ParseSupportsMozBoolPrefName(aConditionMet);
  }

  if (mToken.mType == eCSSToken_Function ||
      mToken.mType == eCSSToken_Bad_URL) {
    if (!SkipUntil(')')) {
      REPORT_UNEXPECTED_EOF(PESupportsConditionInParensEOF);
      return false;
    }
    aConditionMet = false;
    return true;
  }

  if (!mToken.IsSymbol('(')) {
    REPORT_UNEXPECTED_TOKEN(PESupportsConditionExpectedOpenParenOrFunction);
    UngetToken();
    return false;
  }

  if (!ParseSupportsConditionInParensInsideParens(aConditionMet)) {
    if (!SkipUntil(')')) {
      REPORT_UNEXPECTED_EOF(PESupportsConditionInParensEOF);
      return false;
    }
    aConditionMet = false;
    return true;
  }

  if (!(ExpectSymbol(')', true))) {
    SkipUntil(')');
    aConditionMet = false;
    return true;
  }

  return true;
}

// supports_condition_pref
//   : '-moz-bool-pref(' bool_pref_name ')'
//   ;
bool
CSSParserImpl::ParseSupportsMozBoolPrefName(bool& aConditionMet)
{
  if (!GetToken(true)) {
    return false;
  }

  if (mToken.mType != eCSSToken_String) {
    SkipUntil(')');
    return false;
  }

  aConditionMet = Preferences::GetBool(
    NS_ConvertUTF16toUTF8(mToken.mIdent).get());

  if (!ExpectSymbol(')', true)) {
    SkipUntil(')');
    return false;
  }

  return true;
}

// supports_condition_in_parens_inside_parens
//   : core_declaration
//   | supports_condition_negation
//   | supports_condition_in_parens supports_condition_terms
//   ;
bool
CSSParserImpl::ParseSupportsConditionInParensInsideParens(bool& aConditionMet)
{
  if (!GetToken(true)) {
    return false;
  }

  if (mToken.mType == eCSSToken_Ident) {
    if (!mToken.mIdent.LowerCaseEqualsLiteral("not")) {
      nsAutoString propertyName = mToken.mIdent;
      if (!ExpectSymbol(':', true)) {
        return false;
      }

      nsCSSPropertyID propID = LookupEnabledProperty(propertyName);
      if (propID == eCSSProperty_UNKNOWN) {
        if (ExpectSymbol(')', true)) {
          UngetToken();
          return false;
        }
        aConditionMet = false;
        SkipUntil(')');
        UngetToken();
      } else if (propID == eCSSPropertyExtra_variable) {
        if (ExpectSymbol(')', false)) {
          UngetToken();
          return false;
        }
        CSSVariableDeclarations::Type variableType;
        nsString variableValue;
        aConditionMet =
          ParseVariableDeclaration(&variableType, variableValue) &&
          ParsePriority() != ePriority_Error;
        if (!aConditionMet) {
          SkipUntil(')');
          UngetToken();
        }
      } else {
        if (ExpectSymbol(')', true)) {
          UngetToken();
          return false;
        }
        aConditionMet = ParseProperty(propID) &&
                        ParsePriority() != ePriority_Error;
        if (!aConditionMet) {
          SkipUntil(')');
          UngetToken();
        }
        mTempData.ClearProperty(propID);
        mTempData.AssertInitialState();
      }
      return true;
    }

    UngetToken();
    return ParseSupportsConditionNegation(aConditionMet);
  }

  UngetToken();
  return ParseSupportsConditionInParens(aConditionMet) &&
         ParseSupportsConditionTerms(aConditionMet);
}

// supports_condition_terms
//   : S+ 'and' supports_condition_terms_after_operator('and')
//   | S+ 'or' supports_condition_terms_after_operator('or')
//   |
//   ;
bool
CSSParserImpl::ParseSupportsConditionTerms(bool& aConditionMet)
{
  if (!RequireWhitespace() || !GetToken(false)) {
    return true;
  }

  if (mToken.mType != eCSSToken_Ident) {
    UngetToken();
    return true;
  }

  if (mToken.mIdent.LowerCaseEqualsLiteral("and")) {
    return ParseSupportsConditionTermsAfterOperator(aConditionMet, eAnd);
  }

  if (mToken.mIdent.LowerCaseEqualsLiteral("or")) {
    return ParseSupportsConditionTermsAfterOperator(aConditionMet, eOr);
  }

  UngetToken();
  return true;
}

// supports_condition_terms_after_operator(operator)
//   : S+ supports_condition_in_parens ( <operator> supports_condition_in_parens )*
//   ;
bool
CSSParserImpl::ParseSupportsConditionTermsAfterOperator(
                         bool& aConditionMet,
                         CSSParserImpl::SupportsConditionTermOperator aOperator)
{
  if (!RequireWhitespace()) {
    REPORT_UNEXPECTED(PESupportsWhitespaceRequired);
    return false;
  }

  const char* token = aOperator == eAnd ? "and" : "or";
  for (;;) {
    bool termConditionMet = false;
    if (!ParseSupportsConditionInParens(termConditionMet)) {
      return false;
    }
    aConditionMet = aOperator == eAnd ? aConditionMet && termConditionMet :
                                        aConditionMet || termConditionMet;

    if (!GetToken(true)) {
      return true;
    }

    if (mToken.mType != eCSSToken_Ident ||
        !mToken.mIdent.LowerCaseEqualsASCII(token)) {
      UngetToken();
      return true;
    }
  }
}

bool
CSSParserImpl::ParseCounterStyleRule(RuleAppendFunc aAppendFunc, void* aData)
{
  nsAutoString name;
  uint32_t linenum, colnum;
  if (!GetNextTokenLocation(true, &linenum, &colnum) ||
      !ParseCounterStyleName(name, true)) {
    REPORT_UNEXPECTED_TOKEN(PECounterStyleNotIdent);
    return false;
  }

  if (!ExpectSymbol('{', true)) {
    REPORT_UNEXPECTED_TOKEN(PECounterStyleBadBlockStart);
    return false;
  }

  RefPtr<nsCSSCounterStyleRule> rule = new nsCSSCounterStyleRule(name,
                                                                   linenum,
                                                                   colnum);
  for (;;) {
    if (!GetToken(true)) {
      REPORT_UNEXPECTED_EOF(PECounterStyleEOF);
      break;
    }
    if (mToken.IsSymbol('}')) {
      break;
    }
    if (mToken.IsSymbol(';')) {
      continue;
    }

    if (!ParseCounterDescriptor(rule)) {
      REPORT_UNEXPECTED(PEDeclSkipped);
      OUTPUT_ERROR();
      if (!SkipDeclaration(true)) {
        REPORT_UNEXPECTED_EOF(PECounterStyleEOF);
        break;
      }
    }
  }

  int32_t system = rule->GetSystem();
  bool isCorrect = false;
  switch (system) {
    case NS_STYLE_COUNTER_SYSTEM_CYCLIC:
    case NS_STYLE_COUNTER_SYSTEM_NUMERIC:
    case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC:
    case NS_STYLE_COUNTER_SYSTEM_SYMBOLIC:
    case NS_STYLE_COUNTER_SYSTEM_FIXED: {
      // check whether symbols is set and the length is sufficient
      const nsCSSValue& symbols = rule->GetDesc(eCSSCounterDesc_Symbols);
      if (symbols.GetUnit() == eCSSUnit_List &&
          nsCSSCounterStyleRule::CheckDescValue(
              system, eCSSCounterDesc_Symbols, symbols)) {
        isCorrect = true;
      }
      break;
    }
    case NS_STYLE_COUNTER_SYSTEM_ADDITIVE: {
      // for additive system, additive-symbols must be set
      const nsCSSValue& symbols =
        rule->GetDesc(eCSSCounterDesc_AdditiveSymbols);
      if (symbols.GetUnit() == eCSSUnit_PairList) {
        isCorrect = true;
      }
      break;
    }
    case NS_STYLE_COUNTER_SYSTEM_EXTENDS: {
      // for extends system, symbols & additive-symbols must not be set
      const nsCSSValue& symbols = rule->GetDesc(eCSSCounterDesc_Symbols);
      const nsCSSValue& additiveSymbols =
        rule->GetDesc(eCSSCounterDesc_AdditiveSymbols);
      if (symbols.GetUnit() == eCSSUnit_Null &&
          additiveSymbols.GetUnit() == eCSSUnit_Null) {
        isCorrect = true;
      }
      break;
    }
    default:
      NS_NOTREACHED("unknown system");
  }

  if (isCorrect) {
    (*aAppendFunc)(rule, aData);
  }
  return true;
}

bool
CSSParserImpl::ParseCounterStyleName(nsAString& aName, bool aForDefinition)
{
  if (!GetToken(true)) {
    return false;
  }

  if (mToken.mType != eCSSToken_Ident) {
    UngetToken();
    return false;
  }

  static const nsCSSKeyword kReservedNames[] = {
    eCSSKeyword_none,
    eCSSKeyword_decimal,
    eCSSKeyword_UNKNOWN
  };

  nsCSSValue value; // we don't actually care about the value
  if (!ParseCustomIdent(value, mToken.mIdent,
                        aForDefinition ? kReservedNames : nullptr)) {
    REPORT_UNEXPECTED_TOKEN(PECounterStyleBadName);
    UngetToken();
    return false;
  }

  aName = mToken.mIdent;
  if (nsCSSProps::IsPredefinedCounterStyle(aName)) {
    ToLowerCase(aName);
  }
  return true;
}

bool
CSSParserImpl::ParseCounterStyleNameValue(nsCSSValue& aValue)
{
  nsString name;
  if (ParseCounterStyleName(name, false)) {
    aValue.SetStringValue(name, eCSSUnit_Ident);
    return true;
  }
  return false;
}

bool
CSSParserImpl::ParseCounterDescriptor(nsCSSCounterStyleRule* aRule)
{
  if (eCSSToken_Ident != mToken.mType) {
    REPORT_UNEXPECTED_TOKEN(PECounterDescExpected);
    return false;
  }

  nsString descName = mToken.mIdent;
  if (!ExpectSymbol(':', true)) {
    REPORT_UNEXPECTED_TOKEN(PEParseDeclarationNoColon);
    OUTPUT_ERROR();
    return false;
  }

  nsCSSCounterDesc descID = nsCSSProps::LookupCounterDesc(descName);
  nsCSSValue value;

  if (descID == eCSSCounterDesc_UNKNOWN) {
    REPORT_UNEXPECTED_P(PEUnknownCounterDesc, descName);
    return false;
  }

  if (!ParseCounterDescriptorValue(descID, value)) {
    REPORT_UNEXPECTED_P(PEValueParsingError, descName);
    return false;
  }

  if (!ExpectEndProperty()) {
    return false;
  }

  aRule->SetDesc(descID, value);
  return true;
}

bool
CSSParserImpl::ParseCounterDescriptorValue(nsCSSCounterDesc aDescID,
                                           nsCSSValue& aValue)
{
  // Should also include VARIANT_IMAGE, but it is not supported currently.
  // See bug 1024179.
  static const int32_t VARIANT_COUNTER_SYMBOL =
    VARIANT_STRING | VARIANT_IDENTIFIER;

  switch (aDescID) {
    case eCSSCounterDesc_System: {
      nsCSSValue system;
      if (!ParseEnum(system, nsCSSProps::kCounterSystemKTable)) {
        return false;
      }
      switch (system.GetIntValue()) {
        case NS_STYLE_COUNTER_SYSTEM_FIXED: {
          nsCSSValue start;
          if (!ParseSingleTokenVariant(start, VARIANT_INTEGER, nullptr)) {
            start.SetIntValue(1, eCSSUnit_Integer);
          }
          aValue.SetPairValue(system, start);
          return true;
        }
        case NS_STYLE_COUNTER_SYSTEM_EXTENDS: {
          nsCSSValue name;
          if (!ParseCounterStyleNameValue(name)) {
            REPORT_UNEXPECTED_TOKEN(PECounterExtendsNotIdent);
            return false;
          }
          aValue.SetPairValue(system, name);
          return true;
        }
        default:
          aValue = system;
          return true;
      }
    }

    case eCSSCounterDesc_Negative: {
      nsCSSValue first, second;
      if (!ParseSingleTokenVariant(first, VARIANT_COUNTER_SYMBOL, nullptr)) {
        return false;
      }
      if (!ParseSingleTokenVariant(second, VARIANT_COUNTER_SYMBOL, nullptr)) {
        aValue = first;
      } else {
        aValue.SetPairValue(first, second);
      }
      return true;
    }

    case eCSSCounterDesc_Prefix:
    case eCSSCounterDesc_Suffix:
      return ParseSingleTokenVariant(aValue, VARIANT_COUNTER_SYMBOL, nullptr);

    case eCSSCounterDesc_Range: {
      if (ParseSingleTokenVariant(aValue, VARIANT_AUTO, nullptr)) {
        return true;
      }
      nsCSSValuePairList* item = aValue.SetPairListValue();
      for (;;) {
        nsCSSValuePair pair;
        if (!ParseCounterRange(pair)) {
          return false;
        }
        item->mXValue = pair.mXValue;
        item->mYValue = pair.mYValue;
        if (!ExpectSymbol(',', true)) {
          return true;
        }
        item->mNext = new nsCSSValuePairList;
        item = item->mNext;
      }
      // should always return in the loop
    }

    case eCSSCounterDesc_Pad: {
      nsCSSValue width, symbol;
      bool hasWidth = ParseNonNegativeInteger(width);
      if (!ParseSingleTokenVariant(symbol, VARIANT_COUNTER_SYMBOL, nullptr) ||
          (!hasWidth && !ParseNonNegativeInteger(width))) {
        return false;
      }
      aValue.SetPairValue(width, symbol);
      return true;
    }

    case eCSSCounterDesc_Fallback:
      return ParseCounterStyleNameValue(aValue);

    case eCSSCounterDesc_Symbols: {
      nsCSSValueList* item = nullptr;
      for (;;) {
        nsCSSValue value;
        if (!ParseSingleTokenVariant(value, VARIANT_COUNTER_SYMBOL, nullptr)) {
          return !!item;
        }
        if (!item) {
          item = aValue.SetListValue();
        } else {
          item->mNext = new nsCSSValueList;
          item = item->mNext;
        }
        item->mValue = value;
      }
      // should always return in the loop
    }

    case eCSSCounterDesc_AdditiveSymbols: {
      nsCSSValuePairList* item = nullptr;
      int32_t lastWeight = -1;
      for (;;) {
        nsCSSValue weight, symbol;
        bool hasWeight = ParseNonNegativeInteger(weight);
        if (!ParseSingleTokenVariant(symbol, VARIANT_COUNTER_SYMBOL, nullptr) ||
            (!hasWeight && !ParseNonNegativeInteger(weight))) {
          return false;
        }
        if (lastWeight != -1 && weight.GetIntValue() >= lastWeight) {
          REPORT_UNEXPECTED(PECounterASWeight);
          return false;
        }
        lastWeight = weight.GetIntValue();
        if (!item) {
          item = aValue.SetPairListValue();
        } else {
          item->mNext = new nsCSSValuePairList;
          item = item->mNext;
        }
        item->mXValue = weight;
        item->mYValue = symbol;
        if (!ExpectSymbol(',', true)) {
          return true;
        }
      }
      // should always return in the loop
    }

    case eCSSCounterDesc_SpeakAs:
      if (ParseSingleTokenVariant(aValue, VARIANT_AUTO | VARIANT_KEYWORD,
                                  nsCSSProps::kCounterSpeakAsKTable)) {
        if (aValue.GetUnit() == eCSSUnit_Enumerated &&
            aValue.GetIntValue() == NS_STYLE_COUNTER_SPEAKAS_SPELL_OUT) {
          // Currently spell-out is not supported, so it is explicitly
          // rejected here rather than parsed as a custom identifier.
          // See bug 1024178.
          return false;
        }
        return true;
      }
      return ParseCounterStyleNameValue(aValue);

    default:
      NS_NOTREACHED("unknown descriptor");
      return false;
  }
}

bool
CSSParserImpl::ParseCounterRange(nsCSSValuePair& aPair)
{
  static const int32_t VARIANT_BOUND = VARIANT_INTEGER | VARIANT_KEYWORD;
  nsCSSValue lower, upper;
  if (!ParseSingleTokenVariant(lower, VARIANT_BOUND,
                               nsCSSProps::kCounterRangeKTable) ||
      !ParseSingleTokenVariant(upper, VARIANT_BOUND,
                               nsCSSProps::kCounterRangeKTable)) {
    return false;
  }
  if (lower.GetUnit() != eCSSUnit_Enumerated &&
      upper.GetUnit() != eCSSUnit_Enumerated &&
      lower.GetIntValue() > upper.GetIntValue()) {
    return false;
  }
  aPair = nsCSSValuePair(lower, upper);
  return true;
}

bool
CSSParserImpl::SkipUntil(char16_t aStopSymbol)
{
  nsCSSToken* tk = &mToken;
  AutoTArray<char16_t, 16> stack;
  stack.AppendElement(aStopSymbol);
  for (;;) {
    if (!GetToken(true)) {
      return false;
    }
    if (eCSSToken_Symbol == tk->mType) {
      char16_t symbol = tk->mSymbol;
      uint32_t stackTopIndex = stack.Length() - 1;
      if (symbol == stack.ElementAt(stackTopIndex)) {
        stack.RemoveElementAt(stackTopIndex);
        if (stackTopIndex == 0) {
          return true;
        }

      // Just handle out-of-memory by parsing incorrectly.  It's
      // highly unlikely we're dealing with a legitimate style sheet
      // anyway.
      } else if ('{' == symbol) {
        stack.AppendElement('}');
      } else if ('[' == symbol) {
        stack.AppendElement(']');
      } else if ('(' == symbol) {
        stack.AppendElement(')');
      }
    } else if (eCSSToken_Function == tk->mType ||
               eCSSToken_Bad_URL == tk->mType) {
      stack.AppendElement(')');
    }
  }
}

bool
CSSParserImpl::SkipBalancedContentUntil(char16_t aStopSymbol)
{
  nsCSSToken* tk = &mToken;
  AutoTArray<char16_t, 16> stack;
  stack.AppendElement(aStopSymbol);
  for (;;) {
    if (!GetToken(true)) {
      return true;
    }
    if (eCSSToken_Symbol == tk->mType) {
      char16_t symbol = tk->mSymbol;
      uint32_t stackTopIndex = stack.Length() - 1;
      if (symbol == stack.ElementAt(stackTopIndex)) {
        stack.RemoveElementAt(stackTopIndex);
        if (stackTopIndex == 0) {
          return true;
        }

      // Just handle out-of-memory by parsing incorrectly.  It's
      // highly unlikely we're dealing with a legitimate style sheet
      // anyway.
      } else if ('{' == symbol) {
        stack.AppendElement('}');
      } else if ('[' == symbol) {
        stack.AppendElement(']');
      } else if ('(' == symbol) {
        stack.AppendElement(')');
      } else if (')' == symbol ||
                 ']' == symbol ||
                 '}' == symbol) {
        UngetToken();
        return false;
      }
    } else if (eCSSToken_Function == tk->mType ||
               eCSSToken_Bad_URL == tk->mType) {
      stack.AppendElement(')');
    }
  }
}

void
CSSParserImpl::SkipUntilOneOf(const char16_t* aStopSymbolChars)
{
  nsCSSToken* tk = &mToken;
  nsDependentString stopSymbolChars(aStopSymbolChars);
  for (;;) {
    if (!GetToken(true)) {
      break;
    }
    if (eCSSToken_Symbol == tk->mType) {
      char16_t symbol = tk->mSymbol;
      if (stopSymbolChars.FindChar(symbol) != -1) {
        break;
      } else if ('{' == symbol) {
        SkipUntil('}');
      } else if ('[' == symbol) {
        SkipUntil(']');
      } else if ('(' == symbol) {
        SkipUntil(')');
      }
    } else if (eCSSToken_Function == tk->mType ||
               eCSSToken_Bad_URL == tk->mType) {
      SkipUntil(')');
    }
  }
}

void
CSSParserImpl::SkipUntilAllOf(const StopSymbolCharStack& aStopSymbolChars)
{
  uint32_t i = aStopSymbolChars.Length();
  while (i--) {
    SkipUntil(aStopSymbolChars[i]);
  }
}

bool
CSSParserImpl::SkipDeclaration(bool aCheckForBraces)
{
  nsCSSToken* tk = &mToken;
  for (;;) {
    if (!GetToken(true)) {
      if (aCheckForBraces) {
        REPORT_UNEXPECTED_EOF(PESkipDeclBraceEOF);
      }
      return false;
    }
    if (eCSSToken_Symbol == tk->mType) {
      char16_t symbol = tk->mSymbol;
      if (';' == symbol) {
        break;
      }
      if (aCheckForBraces) {
        if ('}' == symbol) {
          UngetToken();
          break;
        }
      }
      if ('{' == symbol) {
        SkipUntil('}');
      } else if ('(' == symbol) {
        SkipUntil(')');
      } else if ('[' == symbol) {
        SkipUntil(']');
      }
    } else if (eCSSToken_Function == tk->mType ||
               eCSSToken_Bad_URL == tk->mType) {
      SkipUntil(')');
    }
  }
  return true;
}

void
CSSParserImpl::SkipRuleSet(bool aInsideBraces)
{
  nsCSSToken* tk = &mToken;
  for (;;) {
    if (!GetToken(true)) {
      REPORT_UNEXPECTED_EOF(PESkipRSBraceEOF);
      break;
    }
    if (eCSSToken_Symbol == tk->mType) {
      char16_t symbol = tk->mSymbol;
      if ('}' == symbol && aInsideBraces) {
        // leave block closer for higher-level grammar to consume
        UngetToken();
        break;
      } else if ('{' == symbol) {
        SkipUntil('}');
        break;
      } else if ('(' == symbol) {
        SkipUntil(')');
      } else if ('[' == symbol) {
        SkipUntil(']');
      }
    } else if (eCSSToken_Function == tk->mType ||
               eCSSToken_Bad_URL == tk->mType) {
      SkipUntil(')');
    }
  }
}

void
CSSParserImpl::PushGroup(css::GroupRule* aRule)
{
  mGroupStack.AppendElement(aRule);
}

void
CSSParserImpl::PopGroup()
{
  uint32_t count = mGroupStack.Length();
  if (0 < count) {
    mGroupStack.RemoveElementAt(count - 1);
  }
}

void
CSSParserImpl::AppendRule(css::Rule* aRule)
{
  uint32_t count = mGroupStack.Length();
  if (0 < count) {
    mGroupStack[count - 1]->AppendStyleRule(aRule);
  }
  else {
    mSheet->AppendStyleRule(aRule);
  }
}

bool
CSSParserImpl::ParseRuleSet(RuleAppendFunc aAppendFunc, void* aData,
                            bool aInsideBraces)
{
  // First get the list of selectors for the rule
  nsCSSSelectorList* slist = nullptr;
  uint32_t linenum, colnum;
  if (!GetNextTokenLocation(true, &linenum, &colnum) ||
      !ParseSelectorList(slist, char16_t('{'))) {
    REPORT_UNEXPECTED(PEBadSelectorRSIgnored);
    OUTPUT_ERROR();
    SkipRuleSet(aInsideBraces);
    return false;
  }
  NS_ASSERTION(nullptr != slist, "null selector list");
  CLEAR_ERROR();

  // Next parse the declaration block
  uint32_t parseFlags = eParseDeclaration_InBraces |
                        eParseDeclaration_AllowImportant;
  RefPtr<css::Declaration> declaration = ParseDeclarationBlock(parseFlags);
  if (nullptr == declaration) {
    delete slist;
    return false;
  }

#if 0
  slist->Dump();
  fputs("{\n", stdout);
  declaration->List();
  fputs("}\n", stdout);
#endif

  // Translate the selector list and declaration block into style data

  RefPtr<css::StyleRule> rule = new css::StyleRule(slist, declaration,
                                                     linenum, colnum);
  (*aAppendFunc)(rule, aData);

  return true;
}

bool
CSSParserImpl::ParseSelectorList(nsCSSSelectorList*& aListHead,
                                 char16_t aStopChar)
{
  nsCSSSelectorList* list = nullptr;
  if (! ParseSelectorGroup(list)) {
    // must have at least one selector group
    aListHead = nullptr;
    return false;
  }
  NS_ASSERTION(nullptr != list, "no selector list");
  aListHead = list;

  // After that there must either be a "," or a "{" (the latter if
  // StopChar is nonzero)
  nsCSSToken* tk = &mToken;
  for (;;) {
    if (! GetToken(true)) {
      if (aStopChar == char16_t(0)) {
        return true;
      }

      REPORT_UNEXPECTED_EOF(PESelectorListExtraEOF);
      break;
    }

    if (eCSSToken_Symbol == tk->mType) {
      if (',' == tk->mSymbol) {
        nsCSSSelectorList* newList = nullptr;
        // Another selector group must follow
        if (! ParseSelectorGroup(newList)) {
          break;
        }
        // add new list to the end of the selector list
        list->mNext = newList;
        list = newList;
        continue;
      } else if (aStopChar == tk->mSymbol && aStopChar != char16_t(0)) {
        UngetToken();
        return true;
      }
    }
    REPORT_UNEXPECTED_TOKEN(PESelectorListExtra);
    UngetToken();
    break;
  }

  delete aListHead;
  aListHead = nullptr;
  return false;
}

static bool IsUniversalSelector(const nsCSSSelector& aSelector)
{
  return bool((aSelector.mNameSpace == kNameSpaceID_Unknown) &&
                (aSelector.mLowercaseTag == nullptr) &&
                (aSelector.mIDList == nullptr) &&
                (aSelector.mClassList == nullptr) &&
                (aSelector.mAttrList == nullptr) &&
                (aSelector.mNegations == nullptr) &&
                (aSelector.mPseudoClassList == nullptr));
}

bool
CSSParserImpl::ParseSelectorGroup(nsCSSSelectorList*& aList)
{
  char16_t combinator = 0;
  nsAutoPtr<nsCSSSelectorList> list(new nsCSSSelectorList());

  for (;;) {
    if (!ParseSelector(list, combinator)) {
      return false;
    }

    // Look for a combinator.
    if (!GetToken(false)) {
      break; // EOF ok here
    }

    combinator = char16_t(0);
    if (mToken.mType == eCSSToken_Whitespace) {
      if (!GetToken(true)) {
        break; // EOF ok here
      }
      combinator = char16_t(' ');
    }

    if (mToken.mType != eCSSToken_Symbol) {
      UngetToken(); // not a combinator
    } else {
      char16_t symbol = mToken.mSymbol;
      if (symbol == '+' || symbol == '>' || symbol == '~') {
        combinator = mToken.mSymbol;
      } else {
        UngetToken(); // not a combinator
        if (symbol == ',' || symbol == '{' || symbol == ')') {
          break; // end of selector group
        }
      }
    }

    if (!combinator) {
      REPORT_UNEXPECTED_TOKEN(PESelectorListExtra);
      return false;
    }
  }

  aList = list.forget();
  return true;
}

#define SEL_MASK_NSPACE   0x01
#define SEL_MASK_ELEM     0x02
#define SEL_MASK_ID       0x04
#define SEL_MASK_CLASS    0x08
#define SEL_MASK_ATTRIB   0x10
#define SEL_MASK_PCLASS   0x20
#define SEL_MASK_PELEM    0x40

//
// Parses an ID selector #name
//
CSSParserImpl::nsSelectorParsingStatus
CSSParserImpl::ParseIDSelector(int32_t&       aDataMask,
                               nsCSSSelector& aSelector)
{
  NS_ASSERTION(!mToken.mIdent.IsEmpty(),
               "Empty mIdent in eCSSToken_ID token?");
  aDataMask |= SEL_MASK_ID;
  aSelector.AddID(mToken.mIdent);
  return eSelectorParsingStatus_Continue;
}

//
// Parses a class selector .name
//
CSSParserImpl::nsSelectorParsingStatus
CSSParserImpl::ParseClassSelector(int32_t&       aDataMask,
                                  nsCSSSelector& aSelector)
{
  if (! GetToken(false)) { // get ident
    REPORT_UNEXPECTED_EOF(PEClassSelEOF);
    return eSelectorParsingStatus_Error;
  }
  if (eCSSToken_Ident != mToken.mType) {  // malformed selector
    REPORT_UNEXPECTED_TOKEN(PEClassSelNotIdent);
    UngetToken();
    return eSelectorParsingStatus_Error;
  }
  aDataMask |= SEL_MASK_CLASS;

  aSelector.AddClass(mToken.mIdent);

  return eSelectorParsingStatus_Continue;
}

//
// Parse a type element selector or a universal selector
// namespace|type or namespace|* or *|* or *
//
CSSParserImpl::nsSelectorParsingStatus
CSSParserImpl::ParseTypeOrUniversalSelector(int32_t&       aDataMask,
                                            nsCSSSelector& aSelector,
                                            bool           aIsNegated)
{
  nsAutoString buffer;
  if (mToken.IsSymbol('*')) {  // universal element selector, or universal namespace
    if (ExpectSymbol('|', false)) {  // was namespace
      aDataMask |= SEL_MASK_NSPACE;
      aSelector.SetNameSpace(kNameSpaceID_Unknown); // namespace wildcard

      if (! GetToken(false)) {
        REPORT_UNEXPECTED_EOF(PETypeSelEOF);
        return eSelectorParsingStatus_Error;
      }
      if (eCSSToken_Ident == mToken.mType) {  // element name
        aDataMask |= SEL_MASK_ELEM;

        aSelector.SetTag(mToken.mIdent);
      }
      else if (mToken.IsSymbol('*')) {  // universal selector
        aDataMask |= SEL_MASK_ELEM;
        // don't set tag
      }
      else {
        REPORT_UNEXPECTED_TOKEN(PETypeSelNotType);
        UngetToken();
        return eSelectorParsingStatus_Error;
      }
    }
    else {  // was universal element selector
      SetDefaultNamespaceOnSelector(aSelector);
      aDataMask |= SEL_MASK_ELEM;
      // don't set any tag in the selector
    }
    if (! GetToken(false)) {   // premature eof is ok (here!)
      return eSelectorParsingStatus_Done;
    }
  }
  else if (eCSSToken_Ident == mToken.mType) {    // element name or namespace name
    buffer = mToken.mIdent; // hang on to ident

    if (ExpectSymbol('|', false)) {  // was namespace
      aDataMask |= SEL_MASK_NSPACE;
      int32_t nameSpaceID = GetNamespaceIdForPrefix(buffer);
      if (nameSpaceID == kNameSpaceID_Unknown) {
        return eSelectorParsingStatus_Error;
      }
      aSelector.SetNameSpace(nameSpaceID);

      if (! GetToken(false)) {
        REPORT_UNEXPECTED_EOF(PETypeSelEOF);
        return eSelectorParsingStatus_Error;
      }
      if (eCSSToken_Ident == mToken.mType) {  // element name
        aDataMask |= SEL_MASK_ELEM;
        aSelector.SetTag(mToken.mIdent);
      }
      else if (mToken.IsSymbol('*')) {  // universal selector
        aDataMask |= SEL_MASK_ELEM;
        // don't set tag
      }
      else {
        REPORT_UNEXPECTED_TOKEN(PETypeSelNotType);
        UngetToken();
        return eSelectorParsingStatus_Error;
      }
    }
    else {  // was element name
      SetDefaultNamespaceOnSelector(aSelector);
      aSelector.SetTag(buffer);

      aDataMask |= SEL_MASK_ELEM;
    }
    if (! GetToken(false)) {   // premature eof is ok (here!)
      return eSelectorParsingStatus_Done;
    }
  }
  else if (mToken.IsSymbol('|')) {  // No namespace
    aDataMask |= SEL_MASK_NSPACE;
    aSelector.SetNameSpace(kNameSpaceID_None);  // explicit NO namespace

    // get mandatory tag
    if (! GetToken(false)) {
      REPORT_UNEXPECTED_EOF(PETypeSelEOF);
      return eSelectorParsingStatus_Error;
    }
    if (eCSSToken_Ident == mToken.mType) {  // element name
      aDataMask |= SEL_MASK_ELEM;
      aSelector.SetTag(mToken.mIdent);
    }
    else if (mToken.IsSymbol('*')) {  // universal selector
      aDataMask |= SEL_MASK_ELEM;
      // don't set tag
    }
    else {
      REPORT_UNEXPECTED_TOKEN(PETypeSelNotType);
      UngetToken();
      return eSelectorParsingStatus_Error;
    }
    if (! GetToken(false)) {   // premature eof is ok (here!)
      return eSelectorParsingStatus_Done;
    }
  }
  else {
    SetDefaultNamespaceOnSelector(aSelector);
  }

  if (aIsNegated) {
    // restore last token read in case of a negated type selector
    UngetToken();
  }
  return eSelectorParsingStatus_Continue;
}

//
// Parse attribute selectors [attr], [attr=value], [attr|=value],
// [attr~=value], [attr^=value], [attr$=value] and [attr*=value]
//
CSSParserImpl::nsSelectorParsingStatus
CSSParserImpl::ParseAttributeSelector(int32_t&       aDataMask,
                                      nsCSSSelector& aSelector)
{
  if (! GetToken(true)) { // premature EOF
    REPORT_UNEXPECTED_EOF(PEAttributeNameEOF);
    return eSelectorParsingStatus_Error;
  }

  int32_t nameSpaceID = kNameSpaceID_None;
  nsAutoString  attr;
  if (mToken.IsSymbol('*')) { // wildcard namespace
    nameSpaceID = kNameSpaceID_Unknown;
    if (ExpectSymbol('|', false)) {
      if (! GetToken(false)) { // premature EOF
        REPORT_UNEXPECTED_EOF(PEAttributeNameEOF);
        return eSelectorParsingStatus_Error;
      }
      if (eCSSToken_Ident == mToken.mType) { // attr name
        attr = mToken.mIdent;
      }
      else {
        REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected);
        UngetToken();
        return eSelectorParsingStatus_Error;
       }
    }
    else {
      REPORT_UNEXPECTED_TOKEN(PEAttSelNoBar);
      return eSelectorParsingStatus_Error;
    }
  }
  else if (mToken.IsSymbol('|')) { // NO namespace
    if (! GetToken(false)) { // premature EOF
      REPORT_UNEXPECTED_EOF(PEAttributeNameEOF);
      return eSelectorParsingStatus_Error;
    }
    if (eCSSToken_Ident == mToken.mType) { // attr name
      attr = mToken.mIdent;
    }
    else {
      REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected);
      UngetToken();
      return eSelectorParsingStatus_Error;
    }
  }
  else if (eCSSToken_Ident == mToken.mType) { // attr name or namespace
    attr = mToken.mIdent; // hang on to it
    if (ExpectSymbol('|', false)) {  // was a namespace
      nameSpaceID = GetNamespaceIdForPrefix(attr);
      if (nameSpaceID == kNameSpaceID_Unknown) {
        return eSelectorParsingStatus_Error;
      }
      if (! GetToken(false)) { // premature EOF
        REPORT_UNEXPECTED_EOF(PEAttributeNameEOF);
        return eSelectorParsingStatus_Error;
      }
      if (eCSSToken_Ident == mToken.mType) { // attr name
        attr = mToken.mIdent;
      }
      else {
        REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected);
        UngetToken();
        return eSelectorParsingStatus_Error;
      }
    }
  }
  else {  // malformed
    REPORT_UNEXPECTED_TOKEN(PEAttributeNameOrNamespaceExpected);
    UngetToken();
    return eSelectorParsingStatus_Error;
  }

  bool gotEOF = false;
  if (! GetToken(true)) { // premature EOF
    // Treat this just like we saw a ']', but do still output the
    // warning, similar to what ExpectSymbol does.
    REPORT_UNEXPECTED_EOF(PEAttSelInnerEOF);
    gotEOF = true;
  }
  if (gotEOF ||
      (eCSSToken_Symbol == mToken.mType) ||
      (eCSSToken_Includes == mToken.mType) ||
      (eCSSToken_Dashmatch == mToken.mType) ||
      (eCSSToken_Beginsmatch == mToken.mType) ||
      (eCSSToken_Endsmatch == mToken.mType) ||
      (eCSSToken_Containsmatch == mToken.mType)) {
    uint8_t func;
    // Important: Check the EOF/']' case first, since if gotEOF we
    // don't want to be examining mToken.
    if (gotEOF || ']' == mToken.mSymbol) {
      aDataMask |= SEL_MASK_ATTRIB;
      aSelector.AddAttribute(nameSpaceID, attr);
      func = NS_ATTR_FUNC_SET;
    }
    else if (eCSSToken_Includes == mToken.mType) {
      func = NS_ATTR_FUNC_INCLUDES;
    }
    else if (eCSSToken_Dashmatch == mToken.mType) {
      func = NS_ATTR_FUNC_DASHMATCH;
    }
    else if (eCSSToken_Beginsmatch == mToken.mType) {
      func = NS_ATTR_FUNC_BEGINSMATCH;
    }
    else if (eCSSToken_Endsmatch == mToken.mType) {
      func = NS_ATTR_FUNC_ENDSMATCH;
    }
    else if (eCSSToken_Containsmatch == mToken.mType) {
      func = NS_ATTR_FUNC_CONTAINSMATCH;
    }
    else if ('=' == mToken.mSymbol) {
      func = NS_ATTR_FUNC_EQUALS;
    }
    else {
      REPORT_UNEXPECTED_TOKEN(PEAttSelUnexpected);
      UngetToken(); // bad function
      return eSelectorParsingStatus_Error;
    }
    if (NS_ATTR_FUNC_SET != func) { // get value
      if (! GetToken(true)) { // premature EOF
        REPORT_UNEXPECTED_EOF(PEAttSelValueEOF);
        return eSelectorParsingStatus_Error;
      }
      if ((eCSSToken_Ident == mToken.mType) || (eCSSToken_String == mToken.mType)) {
        nsAutoString  value(mToken.mIdent);
        // Avoid duplicating the eof handling by just not checking for
        // the 'i' annotation if we got eof.
        typedef nsAttrSelector::ValueCaseSensitivity ValueCaseSensitivity;
        ValueCaseSensitivity valueCaseSensitivity =
          ValueCaseSensitivity::CaseSensitive;
        bool eof = !GetToken(true);
        if (!eof) {
          if (eCSSToken_Ident == mToken.mType &&
              mToken.mIdent.LowerCaseEqualsLiteral("i")) {
            valueCaseSensitivity = ValueCaseSensitivity::CaseInsensitive;
            eof = !GetToken(true);
          }
        }
        bool gotClosingBracket;
        if (eof) { // premature EOF
          // Report a warning, but then treat it as a closing bracket.
          REPORT_UNEXPECTED_EOF(PEAttSelCloseEOF);
          gotClosingBracket = true;
        } else {
          gotClosingBracket = mToken.IsSymbol(']');
        }
        if (gotClosingBracket) {
          // For cases when this style sheet is applied to an HTML
          // element in an HTML document, and the attribute selector is
          // for a non-namespaced attribute, then check to see if it's
          // one of the known attributes whose VALUE is
          // case-insensitive.
          if (valueCaseSensitivity != ValueCaseSensitivity::CaseInsensitive &&
              nameSpaceID == kNameSpaceID_None) {
            static const char* caseInsensitiveHTMLAttribute[] = {
              // list based on http://www.w3.org/TR/html4/
              "lang",
              "dir",
              "http-equiv",
              "text",
              "link",
              "vlink",
              "alink",
              "compact",
              "align",
              "frame",
              "rules",
              "valign",
              "scope",
              "axis",
              "nowrap",
              "hreflang",
              "rel",
              "rev",
              "charset",
              "codetype",
              "declare",
              "valuetype",
              "shape",
              "nohref",
              "media",
              "bgcolor",
              "clear",
              "color",
              "face",
              "noshade",
              "noresize",
              "scrolling",
              "target",
              "method",
              "enctype",
              "accept-charset",
              "accept",
              "checked",
              "multiple",
              "selected",
              "disabled",
              "readonly",
              "language",
              "defer",
              "type",
              // additional attributes not in HTML4
              "direction", // marquee
              nullptr
            };
            short i = 0;
            const char* htmlAttr;
            while ((htmlAttr = caseInsensitiveHTMLAttribute[i++])) {
              if (attr.LowerCaseEqualsASCII(htmlAttr)) {
                valueCaseSensitivity = ValueCaseSensitivity::CaseInsensitiveInHTML;
                break;
              }
            }
          }
          aDataMask |= SEL_MASK_ATTRIB;
          aSelector.AddAttribute(nameSpaceID, attr, func, value,
                                 valueCaseSensitivity);
        }
        else {
          REPORT_UNEXPECTED_TOKEN(PEAttSelNoClose);
          UngetToken();
          return eSelectorParsingStatus_Error;
        }
      }
      else {
        REPORT_UNEXPECTED_TOKEN(PEAttSelBadValue);
        UngetToken();
        return eSelectorParsingStatus_Error;
      }
    }
  }
  else {
    REPORT_UNEXPECTED_TOKEN(PEAttSelUnexpected);
    UngetToken(); // bad dog, no biscut!
    return eSelectorParsingStatus_Error;
   }
   return eSelectorParsingStatus_Continue;
}

//
// Parse pseudo-classes and pseudo-elements
//
CSSParserImpl::nsSelectorParsingStatus
CSSParserImpl::ParsePseudoSelector(int32_t&       aDataMask,
                                   nsCSSSelector& aSelector,
                                   bool           aIsNegated,
                                   nsIAtom**      aPseudoElement,
                                   nsAtomList**   aPseudoElementArgs,
                                   CSSPseudoElementType* aPseudoElementType)
{
  NS_ASSERTION(aIsNegated || (aPseudoElement && aPseudoElementArgs),
               "expected location to store pseudo element");
  NS_ASSERTION(!aIsNegated || (!aPseudoElement && !aPseudoElementArgs),
               "negated selectors shouldn't have a place to store "
               "pseudo elements");
  if (! GetToken(false)) { // premature eof
    REPORT_UNEXPECTED_EOF(PEPseudoSelEOF);
    return eSelectorParsingStatus_Error;
  }

  // First, find out whether we are parsing a CSS3 pseudo-element
  bool parsingPseudoElement = false;
  if (mToken.IsSymbol(':')) {
    parsingPseudoElement = true;
    if (! GetToken(false)) { // premature eof
      REPORT_UNEXPECTED_EOF(PEPseudoSelEOF);
      return eSelectorParsingStatus_Error;
    }
  }

  // Do some sanity-checking on the token
  if (eCSSToken_Ident != mToken.mType && eCSSToken_Function != mToken.mType) {
    // malformed selector
    REPORT_UNEXPECTED_TOKEN(PEPseudoSelBadName);
    UngetToken();
    return eSelectorParsingStatus_Error;
  }

  // OK, now we know we have an mIdent.  Atomize it.  All the atoms, for
  // pseudo-classes as well as pseudo-elements, start with a single ':'.
  nsAutoString buffer;
  buffer.Append(char16_t(':'));
  buffer.Append(mToken.mIdent);
  nsContentUtils::ASCIIToLower(buffer);
  nsCOMPtr<nsIAtom> pseudo = NS_Atomize(buffer);

  // stash away some info about this pseudo so we only have to get it once.
  bool isTreePseudo = false;
  CSSEnabledState enabledState = EnabledState();
  CSSPseudoElementType pseudoElementType =
    nsCSSPseudoElements::GetPseudoType(pseudo, enabledState);
  CSSPseudoClassType pseudoClassType =
    nsCSSPseudoClasses::GetPseudoType(pseudo, enabledState);
  bool pseudoClassIsUserAction =
    nsCSSPseudoClasses::IsUserActionPseudoClass(pseudoClassType);

  if (nsCSSAnonBoxes::IsNonElement(pseudo)) {
    // Non-element anonymous boxes should not match any rule.
    REPORT_UNEXPECTED_TOKEN(PEPseudoSelUnknown);
    UngetToken();
    return eSelectorParsingStatus_Error;
  }

  // We currently allow :-moz-placeholder and ::-moz-placeholder and
  // ::placeholder. We have to be a bit stricter regarding the
  // pseudo-element parsing rules.
  if (pseudoElementType == CSSPseudoElementType::placeholder &&
      pseudoClassType == CSSPseudoClassType::mozPlaceholder) {
    if (parsingPseudoElement) {
      pseudoClassType = CSSPseudoClassType::NotPseudo;
    } else {
      pseudoElementType = CSSPseudoElementType::NotPseudo;
    }
  }

#ifdef MOZ_XUL
  isTreePseudo = (pseudoElementType == CSSPseudoElementType::XULTree);
  // If a tree pseudo-element is using the function syntax, it will
  // get isTree set here and will pass the check below that only
  // allows functions if they are in our list of things allowed to be
  // functions.  If it is _not_ using the function syntax, isTree will
  // be false, and it will still pass that check.  So the tree
  // pseudo-elements are allowed to be either functions or not, as
  // desired.
  bool isTree = (eCSSToken_Function == mToken.mType) && isTreePseudo;
#endif
  bool isPseudoElement = (pseudoElementType < CSSPseudoElementType::Count);
  // anonymous boxes are only allowed if they're the tree boxes or we have
  // enabled agent rules
  bool isAnonBox = isTreePseudo ||
    (pseudoElementType == CSSPseudoElementType::AnonBox &&
     AgentRulesEnabled());
  bool isPseudoClass =
    (pseudoClassType != CSSPseudoClassType::NotPseudo);

  NS_ASSERTION(!isPseudoClass ||
               pseudoElementType == CSSPseudoElementType::NotPseudo,
               "Why is this atom both a pseudo-class and a pseudo-element?");
  NS_ASSERTION(isPseudoClass + isPseudoElement + isAnonBox <= 1,
               "Shouldn't be more than one of these");

  if (!isPseudoClass && !isPseudoElement && !isAnonBox) {
    // Not a pseudo-class, not a pseudo-element.... forget it
    REPORT_UNEXPECTED_TOKEN(PEPseudoSelUnknown);
    UngetToken();
    return eSelectorParsingStatus_Error;
  }

  // If it's a function token, it better be on our "ok" list, and if the name
  // is that of a function pseudo it better be a function token
  if ((eCSSToken_Function == mToken.mType) !=
      (
#ifdef MOZ_XUL
       isTree ||
#endif
       CSSPseudoClassType::negation == pseudoClassType ||
       nsCSSPseudoClasses::HasStringArg(pseudoClassType) ||
       nsCSSPseudoClasses::HasNthPairArg(pseudoClassType) ||
       nsCSSPseudoClasses::HasSelectorListArg(pseudoClassType))) {
    // There are no other function pseudos
    REPORT_UNEXPECTED_TOKEN(PEPseudoSelNonFunc);
    UngetToken();
    return eSelectorParsingStatus_Error;
  }

  // If it starts with "::", it better be a pseudo-element
  if (parsingPseudoElement &&
      !isPseudoElement &&
      !isAnonBox) {
    REPORT_UNEXPECTED_TOKEN(PEPseudoSelNotPE);
    UngetToken();
    return eSelectorParsingStatus_Error;
  }

  if (aSelector.IsPseudoElement()) {
    CSSPseudoElementType type = aSelector.PseudoType();
    if (type >= CSSPseudoElementType::Count ||
        !nsCSSPseudoElements::PseudoElementSupportsUserActionState(type)) {
      // We only allow user action pseudo-classes on certain pseudo-elements.
      REPORT_UNEXPECTED_TOKEN(PEPseudoSelNoUserActionPC);
      UngetToken();
      return eSelectorParsingStatus_Error;
    }
    if (!isPseudoClass || !pseudoClassIsUserAction) {
      // CSS 4 Selectors says that pseudo-elements can only be followed by
      // a user action pseudo-class.
      REPORT_UNEXPECTED_TOKEN(PEPseudoClassNotUserAction);
      UngetToken();
      return eSelectorParsingStatus_Error;
    }
  }

  if (!parsingPseudoElement &&
      CSSPseudoClassType::negation == pseudoClassType) {
    if (aIsNegated) { // :not() can't be itself negated
      REPORT_UNEXPECTED_TOKEN(PEPseudoSelDoubleNot);
      UngetToken();
      return eSelectorParsingStatus_Error;
    }
    // CSS 3 Negation pseudo-class takes one simple selector as argument
    nsSelectorParsingStatus parsingStatus =
      ParseNegatedSimpleSelector(aDataMask, aSelector);
    if (eSelectorParsingStatus_Continue != parsingStatus) {
      return parsingStatus;
    }
  }
  else if (!parsingPseudoElement && isPseudoClass) {
    aDataMask |= SEL_MASK_PCLASS;
    if (eCSSToken_Function == mToken.mType) {
      nsSelectorParsingStatus parsingStatus;
      if (nsCSSPseudoClasses::HasStringArg(pseudoClassType)) {
        parsingStatus =
          ParsePseudoClassWithIdentArg(aSelector, pseudoClassType);
      }
      else if (nsCSSPseudoClasses::HasNthPairArg(pseudoClassType)) {
        parsingStatus =
          ParsePseudoClassWithNthPairArg(aSelector, pseudoClassType);
      }
      else {
        MOZ_ASSERT(nsCSSPseudoClasses::HasSelectorListArg(pseudoClassType),
                   "unexpected pseudo with function token");
        parsingStatus = ParsePseudoClassWithSelectorListArg(aSelector,
                                                            pseudoClassType);
      }
      if (eSelectorParsingStatus_Continue != parsingStatus) {
        if (eSelectorParsingStatus_Error == parsingStatus) {
          SkipUntil(')');
        }
        return parsingStatus;
      }
    }
    else {
      aSelector.AddPseudoClass(pseudoClassType);
    }
  }
  else if (isPseudoElement || isAnonBox) {
    // Pseudo-element.  Make some more sanity checks.

    if (aIsNegated) { // pseudo-elements can't be negated
      REPORT_UNEXPECTED_TOKEN(PEPseudoSelPEInNot);
      UngetToken();
      return eSelectorParsingStatus_Error;
    }
    // CSS2 pseudo-elements and -moz-tree-* pseudo-elements are allowed
    // to have a single ':' on them.  Others (CSS3+ pseudo-elements and
    // various -moz-* pseudo-elements) must have |parsingPseudoElement|
    // set.
    if (!parsingPseudoElement &&
        !nsCSSPseudoElements::IsCSS2PseudoElement(pseudo)
#ifdef MOZ_XUL
        && !isTreePseudo
#endif
        ) {
      REPORT_UNEXPECTED_TOKEN(PEPseudoSelNewStyleOnly);
      UngetToken();
      return eSelectorParsingStatus_Error;
    }

    if (0 == (aDataMask & SEL_MASK_PELEM)) {
      aDataMask |= SEL_MASK_PELEM;
      NS_ADDREF(*aPseudoElement = pseudo);
      *aPseudoElementType = pseudoElementType;

#ifdef MOZ_XUL
      if (isTree) {
        // We have encountered a pseudoelement of the form
        // -moz-tree-xxxx(a,b,c).  We parse (a,b,c) and add each
        // item in the list to the pseudoclass list.  They will be pulled
        // from the list later along with the pseudo-element.
        if (!ParseTreePseudoElement(aPseudoElementArgs)) {
          return eSelectorParsingStatus_Error;
        }
      }
#endif

      // Pseudo-elements can only be followed by user action pseudo-classes
      // or be the end of the selector.  So the next non-whitespace token must
      // be ':', '{' or ',' or EOF.
      if (!GetToken(true)) { // premature eof is ok (here!)
        return eSelectorParsingStatus_Done;
      }
      if (parsingPseudoElement && mToken.IsSymbol(':')) {
        UngetToken();
        return eSelectorParsingStatus_Continue;
      }
      if ((mToken.IsSymbol('{') || mToken.IsSymbol(','))) {
        UngetToken();
        return eSelectorParsingStatus_Done;
      }
      REPORT_UNEXPECTED_TOKEN(PEPseudoSelEndOrUserActionPC);
      UngetToken();
      return eSelectorParsingStatus_Error;
    }
    else {  // multiple pseudo elements, not legal
      REPORT_UNEXPECTED_TOKEN(PEPseudoSelMultiplePE);
      UngetToken();
      return eSelectorParsingStatus_Error;
    }
  }
#ifdef DEBUG
  else {
    // We should never end up here.  Indeed, if we ended up here, we know (from
    // the current if/else cascade) that !isPseudoElement and !isAnonBox.  But
    // then due to our earlier check we know that isPseudoClass.  Since we
    // didn't fall into the isPseudoClass case in this cascade, we must have
    // parsingPseudoElement.  But we've already checked the
    // parsingPseudoElement && !isPseudoClass && !isAnonBox case and bailed if
    // it's happened.
    NS_NOTREACHED("How did this happen?");
  }
#endif
  return eSelectorParsingStatus_Continue;
}

//
// Parse the argument of a negation pseudo-class :not()
//
CSSParserImpl::nsSelectorParsingStatus
CSSParserImpl::ParseNegatedSimpleSelector(int32_t&       aDataMask,
                                          nsCSSSelector& aSelector)
{
  if (! GetToken(true)) { // premature eof
    REPORT_UNEXPECTED_EOF(PENegationEOF);
    return eSelectorParsingStatus_Error;
  }

  if (mToken.IsSymbol(')')) {
    REPORT_UNEXPECTED_TOKEN(PENegationBadArg);
    return eSelectorParsingStatus_Error;
  }

  // Create a new nsCSSSelector and add it to the end of
  // aSelector.mNegations.
  // Given the current parsing rules, every selector in mNegations
  // contains only one simple selector (css3 definition) within it.
  // This could easily change in future versions of CSS, and the only
  // thing we need to change to support that is this parsing code and the
  // serialization code for nsCSSSelector.
  nsCSSSelector *newSel = new nsCSSSelector();
  nsCSSSelector* negations = &aSelector;
  while (negations->mNegations) {
    negations = negations->mNegations;
  }
  negations->mNegations = newSel;

  nsSelectorParsingStatus parsingStatus;
  if (eCSSToken_ID == mToken.mType) { // #id
    parsingStatus = ParseIDSelector(aDataMask, *newSel);
  }
  else if (mToken.IsSymbol('.')) {    // .class
    parsingStatus = ParseClassSelector(aDataMask, *newSel);
  }
  else if (mToken.IsSymbol(':')) {    // :pseudo
    parsingStatus = ParsePseudoSelector(aDataMask, *newSel, true,
                                        nullptr, nullptr, nullptr);
  }
  else if (mToken.IsSymbol('[')) {    // [attribute
    parsingStatus = ParseAttributeSelector(aDataMask, *newSel);
    if (eSelectorParsingStatus_Error == parsingStatus) {
      // Skip forward to the matching ']'
      SkipUntil(']');
    }
  }
  else {
    // then it should be a type element or universal selector
    parsingStatus = ParseTypeOrUniversalSelector(aDataMask, *newSel, true);
  }
  if (eSelectorParsingStatus_Error == parsingStatus) {
    REPORT_UNEXPECTED_TOKEN(PENegationBadInner);
    SkipUntil(')');
    return parsingStatus;
  }
  // close the parenthesis
  if (!ExpectSymbol(')', true)) {
    REPORT_UNEXPECTED_TOKEN(PENegationNoClose);
    SkipUntil(')');
    return eSelectorParsingStatus_Error;
  }

  NS_ASSERTION(newSel->mNameSpace == kNameSpaceID_Unknown ||
               (!newSel->mIDList && !newSel->mClassList &&
                !newSel->mPseudoClassList && !newSel->mAttrList),
               "Need to fix the serialization code to deal with this");

  return eSelectorParsingStatus_Continue;
}

//
// Parse the argument of a pseudo-class that has an ident arg
//
CSSParserImpl::nsSelectorParsingStatus
CSSParserImpl::ParsePseudoClassWithIdentArg(nsCSSSelector& aSelector,
                                            CSSPseudoClassType aType)
{
  if (! GetToken(true)) { // premature eof
    REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
    return eSelectorParsingStatus_Error;
  }
  // We expect an identifier with a language abbreviation
  if (eCSSToken_Ident != mToken.mType) {
    REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotIdent);
    UngetToken();
    return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
  }

  // -moz-locale-dir and dir take an identifier argument.  While
  // only 'ltr' and 'rtl' (case-insensitively) will match anything, any
  // other identifier is still valid.
  if (aType == CSSPseudoClassType::mozLocaleDir ||
      aType == CSSPseudoClassType::mozDir ||
      aType == CSSPseudoClassType::dir) {
    nsContentUtils::ASCIIToLower(mToken.mIdent); // case insensitive
  }

  // Add the pseudo with the language parameter
  aSelector.AddPseudoClass(aType, mToken.mIdent.get());

  // close the parenthesis
  if (!ExpectSymbol(')', true)) {
    REPORT_UNEXPECTED_TOKEN(PEPseudoClassNoClose);
    return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
  }

  return eSelectorParsingStatus_Continue;
}

CSSParserImpl::nsSelectorParsingStatus
CSSParserImpl::ParsePseudoClassWithNthPairArg(nsCSSSelector& aSelector,
                                              CSSPseudoClassType aType)
{
  int32_t numbers[2] = { 0, 0 };
  int32_t sign[2] = { 1, 1 };
  bool hasSign[2] = { false, false };
  bool lookForB = true;

  // Follow the whitespace rules as proposed in
  // http://lists.w3.org/Archives/Public/www-style/2008Mar/0121.html

  if (! GetToken(true)) {
    REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
    return eSelectorParsingStatus_Error;
  }

  if (mToken.IsSymbol('+') || mToken.IsSymbol('-')) {
    hasSign[0] = true;
    if (mToken.IsSymbol('-')) {
      sign[0] = -1;
    }
    if (! GetToken(false)) {
      REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
      return eSelectorParsingStatus_Error;
    }
  }

  // A helper function that checks if the token starts with literal string
  // |aStr| using a case-insensitive match.
  auto TokenBeginsWith = [this] (const nsLiteralString& aStr) {
    return StringBeginsWith(mToken.mIdent, aStr,
                            nsASCIICaseInsensitiveStringComparator());
  };

  if (eCSSToken_Ident == mToken.mType || eCSSToken_Dimension == mToken.mType) {
    // The CSS tokenization doesn't handle :nth-child() containing - well:
    //   2n-1 is a dimension
    //   n-1 is an identifier
    // The easiest way to deal with that is to push everything from the
    // minus on back onto the scanner's pushback buffer.
    uint32_t truncAt = 0;
    if (TokenBeginsWith(NS_LITERAL_STRING("n-"))) {
      truncAt = 1;
    } else if (TokenBeginsWith(NS_LITERAL_STRING("-n-")) && !hasSign[0]) {
      truncAt = 2;
    }
    if (truncAt != 0) {
      mScanner->Backup(mToken.mIdent.Length() - truncAt);
      mToken.mIdent.Truncate(truncAt);
    }
  }

  if (eCSSToken_Ident == mToken.mType) {
    if (mToken.mIdent.LowerCaseEqualsLiteral("odd") && !hasSign[0]) {
      numbers[0] = 2;
      numbers[1] = 1;
      lookForB = false;
    }
    else if (mToken.mIdent.LowerCaseEqualsLiteral("even") && !hasSign[0]) {
      numbers[0] = 2;
      numbers[1] = 0;
      lookForB = false;
    }
    else if (mToken.mIdent.LowerCaseEqualsLiteral("n")) {
      numbers[0] = sign[0];
    }
    else if (mToken.mIdent.LowerCaseEqualsLiteral("-n") && !hasSign[0]) {
      numbers[0] = -1;
    }
    else {
      REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
      return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
    }
  }
  else if (eCSSToken_Number == mToken.mType) {
    if (!mToken.mIntegerValid) {
      REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
      return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
    }
    // for +-an case
    if (mToken.mHasSign && hasSign[0]) {
      REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
      return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
    }
    int32_t intValue = mToken.mInteger * sign[0];
    // for -a/**/n case
    if (! GetToken(false)) {
      numbers[1] = intValue;
      lookForB = false;
    }
    else {
      if (eCSSToken_Ident == mToken.mType && mToken.mIdent.LowerCaseEqualsLiteral("n")) {
        numbers[0] = intValue;
      }
      else if (eCSSToken_Ident == mToken.mType && TokenBeginsWith(NS_LITERAL_STRING("n-"))) {
        numbers[0] = intValue;
        mScanner->Backup(mToken.mIdent.Length() - 1);
      }
      else {
        UngetToken();
        numbers[1] = intValue;
        lookForB = false;
      }
    }
  }
  else if (eCSSToken_Dimension == mToken.mType) {
    if (!mToken.mIntegerValid || !mToken.mIdent.LowerCaseEqualsLiteral("n")) {
      REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
      return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
    }
    // for +-an case
    if ( mToken.mHasSign && hasSign[0] ) {
      REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
      return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
    }
    numbers[0] = mToken.mInteger * sign[0];
  }
  // XXX If it's a ')', is that valid?  (as 0n+0)
  else {
    REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
    UngetToken();
    return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
  }

  if (! GetToken(true)) {
    REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
    return eSelectorParsingStatus_Error;
  }
  if (lookForB && !mToken.IsSymbol(')')) {
    // The '+' or '-' sign can optionally be separated by whitespace.
    // If it is separated by whitespace from what follows it, it appears
    // as a separate token rather than part of the number token.
    if (mToken.IsSymbol('+') || mToken.IsSymbol('-')) {
      hasSign[1] = true;
      if (mToken.IsSymbol('-')) {
        sign[1] = -1;
      }
      if (! GetToken(true)) {
        REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
        return eSelectorParsingStatus_Error;
      }
    }
    if (eCSSToken_Number != mToken.mType ||
        !mToken.mIntegerValid || mToken.mHasSign == hasSign[1]) {
      REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
      UngetToken();
      return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
    }
    numbers[1] = mToken.mInteger * sign[1];
    if (! GetToken(true)) {
      REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
      return eSelectorParsingStatus_Error;
    }
  }
  if (!mToken.IsSymbol(')')) {
    REPORT_UNEXPECTED_TOKEN(PEPseudoClassNoClose);
    return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
  }
  aSelector.AddPseudoClass(aType, numbers);
  return eSelectorParsingStatus_Continue;
}

//
// Parse the argument of a pseudo-class that has a selector list argument.
// Such selector lists cannot contain combinators, but can contain
// anything that goes between a pair of combinators.
//
CSSParserImpl::nsSelectorParsingStatus
CSSParserImpl::ParsePseudoClassWithSelectorListArg(nsCSSSelector& aSelector,
                                                   CSSPseudoClassType aType)
{
  nsAutoPtr<nsCSSSelectorList> slist;
  if (! ParseSelectorList(*getter_Transfers(slist), char16_t(')'))) {
    return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
  }

  // Check that none of the selectors in the list have combinators or
  // pseudo-elements.
  for (nsCSSSelectorList *l = slist; l; l = l->mNext) {
    nsCSSSelector *s = l->mSelectors;
    if (s->mNext || s->IsPseudoElement()) {
      return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
    }
  }

  // Add the pseudo with the selector list parameter
  aSelector.AddPseudoClass(aType, slist.forget());

  // close the parenthesis
  if (!ExpectSymbol(')', true)) {
    REPORT_UNEXPECTED_TOKEN(PEPseudoClassNoClose);
    return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
  }

  return eSelectorParsingStatus_Continue;
}


/**
 * This is the format for selectors:
 * operator? [[namespace |]? element_name]? [ ID | class | attrib | pseudo ]*
 */
bool
CSSParserImpl::ParseSelector(nsCSSSelectorList* aList,
                             char16_t aPrevCombinator)
{
  if (! GetToken(true)) {
    REPORT_UNEXPECTED_EOF(PESelectorEOF);
    return false;
  }

  nsCSSSelector* selector = aList->AddSelector(aPrevCombinator);
  nsCOMPtr<nsIAtom> pseudoElement;
  nsAutoPtr<nsAtomList> pseudoElementArgs;
  CSSPseudoElementType pseudoElementType = CSSPseudoElementType::NotPseudo;

  int32_t dataMask = 0;
  nsSelectorParsingStatus parsingStatus =
    ParseTypeOrUniversalSelector(dataMask, *selector, false);

  while (parsingStatus == eSelectorParsingStatus_Continue) {
    if (mToken.IsSymbol(':')) {    // :pseudo
      parsingStatus = ParsePseudoSelector(dataMask, *selector, false,
                                          getter_AddRefs(pseudoElement),
                                          getter_Transfers(pseudoElementArgs),
                                          &pseudoElementType);
      if (pseudoElement &&
          pseudoElementType != CSSPseudoElementType::AnonBox) {
        // Pseudo-elements other than anonymous boxes are represented with
        // a special ':' combinator.

        aList->mWeight += selector->CalcWeight();

        selector = aList->AddSelector(':');

        selector->mLowercaseTag.swap(pseudoElement);
        selector->mClassList = pseudoElementArgs.forget();
        selector->SetPseudoType(pseudoElementType);
      }
    } else if (selector->IsPseudoElement()) {
      // Once we parsed a pseudo-element, we can only parse
      // pseudo-classes (and only a limited set, which
      // ParsePseudoSelector knows how to handle).
      parsingStatus = eSelectorParsingStatus_Done;
      UngetToken();
      break;
    } else if (eCSSToken_ID == mToken.mType) { // #id
      parsingStatus = ParseIDSelector(dataMask, *selector);
    } else if (mToken.IsSymbol('.')) {    // .class
      parsingStatus = ParseClassSelector(dataMask, *selector);
    }
    else if (mToken.IsSymbol('[')) {    // [attribute
      parsingStatus = ParseAttributeSelector(dataMask, *selector);
      if (eSelectorParsingStatus_Error == parsingStatus) {
        SkipUntil(']');
      }
    }
    else {  // not a selector token, we're done
      parsingStatus = eSelectorParsingStatus_Done;
      UngetToken();
      break;
    }

    if (parsingStatus != eSelectorParsingStatus_Continue) {
      break;
    }

    if (! GetToken(false)) { // premature eof is ok (here!)
      parsingStatus = eSelectorParsingStatus_Done;
      break;
    }
  }

  if (parsingStatus == eSelectorParsingStatus_Error) {
    return false;
  }

  if (!dataMask) {
    if (selector->mNext) {
      REPORT_UNEXPECTED(PESelectorGroupExtraCombinator);
    } else {
      REPORT_UNEXPECTED(PESelectorGroupNoSelector);
    }
    return false;
  }

  if (pseudoElementType == CSSPseudoElementType::AnonBox) {
    // We got an anonymous box pseudo-element; it must be the only
    // thing in this selector group.
    if (selector->mNext || !IsUniversalSelector(*selector)) {
      REPORT_UNEXPECTED(PEAnonBoxNotAlone);
      return false;
    }

    // Rewrite the current selector as this pseudo-element.
    // It does not contribute to selector weight.
    selector->mLowercaseTag.swap(pseudoElement);
    selector->mClassList = pseudoElementArgs.forget();
    selector->SetPseudoType(pseudoElementType);
    return true;
  }

  aList->mWeight += selector->CalcWeight();

  return true;
}

already_AddRefed<css::Declaration>
CSSParserImpl::ParseDeclarationBlock(uint32_t aFlags, nsCSSContextType aContext)
{
  bool checkForBraces = (aFlags & eParseDeclaration_InBraces) != 0;

  MOZ_ASSERT(mWebkitBoxUnprefixState == eNotParsingDecls,
             "Someone forgot to clear mWebkitBoxUnprefixState!");
  AutoRestore<WebkitBoxUnprefixState> autoRestore(mWebkitBoxUnprefixState);
  mWebkitBoxUnprefixState = eHaveNotUnprefixed;

  if (checkForBraces) {
    if (!ExpectSymbol('{', true)) {
      REPORT_UNEXPECTED_TOKEN(PEBadDeclBlockStart);
      OUTPUT_ERROR();
      return nullptr;
    }
  }
  RefPtr<css::Declaration> declaration = new css::Declaration();
  mData.AssertInitialState();
  for (;;) {
    bool changed = false;
    if (!ParseDeclaration(declaration, aFlags, true, &changed, aContext)) {
      if (!SkipDeclaration(checkForBraces)) {
        break;
      }
      if (checkForBraces) {
        if (ExpectSymbol('}', true)) {
          break;
        }
      }
      // Since the skipped declaration didn't end the block we parse
      // the next declaration.
    }
  }
  declaration->CompressFrom(&mData);
  return declaration.forget();
}

CSSParseResult
CSSParserImpl::ParseColor(nsCSSValue& aValue)
{
  if (!GetToken(true)) {
    REPORT_UNEXPECTED_EOF(PEColorEOF);
    return CSSParseResult::NotFound;
  }

  nsCSSToken* tk = &mToken;
  nscolor rgba;
  switch (tk->mType) {
    case eCSSToken_ID:
    case eCSSToken_Hash:
      // #rgb, #rrggbb, #rgba, #rrggbbaa
      if (NS_HexToRGBA(tk->mIdent, nsHexColorType::AllowAlpha, &rgba)) {
        nsCSSUnit unit;
        switch (tk->mIdent.Length()) {
          case 3:
            unit = eCSSUnit_ShortHexColor;
            break;
          case 4:
            unit = eCSSUnit_ShortHexColorAlpha;
            break;
          case 6:
            unit = eCSSUnit_HexColor;
            break;
          default:
            MOZ_FALLTHROUGH_ASSERT("unexpected hex color length");
          case 8:
            unit = eCSSUnit_HexColorAlpha;
            break;
        }
        aValue.SetIntegerColorValue(rgba, unit);
        return CSSParseResult::Ok;
      }
      break;

    case eCSSToken_Ident:
      if (NS_ColorNameToRGB(tk->mIdent, &rgba)) {
        aValue.SetStringValue(tk->mIdent, eCSSUnit_Ident);
        return CSSParseResult::Ok;
      }
      else {
        nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(tk->mIdent);
        if (eCSSKeyword_UNKNOWN < keyword) { // known keyword
          int32_t value;
          if (nsCSSProps::FindKeyword(keyword, nsCSSProps::kColorKTable, value)) {
            aValue.SetIntValue(value, eCSSUnit_EnumColor);
            return CSSParseResult::Ok;
          }
        }
      }
      break;
    case eCSSToken_Function: {
      bool isRGB;
      bool isHSL;
      if ((isRGB = mToken.mIdent.LowerCaseEqualsLiteral("rgb")) ||
          mToken.mIdent.LowerCaseEqualsLiteral("rgba")) {
        // rgb() = rgb( <percentage>{3} [ / <alpha-value> ]? ) |
        //         rgb( <number>{3} [ / <alpha-value> ]? ) |
        //         rgb( <percentage>#{3} , <alpha-value>? ) |
        //         rgb( <number>#{3} , <alpha-value>? )
        // <alpha-value> = <number> | <percentage>
        // rgba is an alias of rgb.

        if (GetToken(true)) {
          UngetToken();
        }
        if (mToken.mType == eCSSToken_Number) { // <number>
          uint8_t r, g, b, a;

          if (ParseRGBColor(r, g, b, a)) {
            aValue.SetIntegerColorValue(NS_RGBA(r, g, b, a),
                isRGB ? eCSSUnit_RGBColor : eCSSUnit_RGBAColor);
            return CSSParseResult::Ok;
          }
        } else {  // <percentage>
          float r, g, b, a;

          if (ParseRGBColor(r, g, b, a)) {
            aValue.SetFloatColorValue(r, g, b, a,
                isRGB ? eCSSUnit_PercentageRGBColor : eCSSUnit_PercentageRGBAColor);
            return CSSParseResult::Ok;
          }
        }
        SkipUntil(')');
        return CSSParseResult::Error;
      }
      else if ((isHSL = mToken.mIdent.LowerCaseEqualsLiteral("hsl")) ||
               mToken.mIdent.LowerCaseEqualsLiteral("hsla")) {
        // hsl() = hsl( <hue> <percentage> <percentage> [ / <alpha-value> ]? ) ||
        //         hsl( <hue>, <percentage>, <percentage>, <alpha-value>? )
        // <hue> = <number> | <angle>
        // hsla is an alias of hsl.

        float h, s, l, a;

        if (ParseHSLColor(h, s, l, a)) {
          aValue.SetFloatColorValue(h, s, l, a,
              isHSL ? eCSSUnit_HSLColor : eCSSUnit_HSLAColor);
          return CSSParseResult::Ok;
        }
        SkipUntil(')');
        return CSSParseResult::Error;
      }
      break;
    }
    default:
      break;
  }

  // try 'xxyyzz' without '#' prefix for compatibility with IE and Nav4x (bug 23236 and 45804)
  if (mHashlessColorQuirk) {
    // - If the string starts with 'a-f', the nsCSSScanner builds the
    //   token as a eCSSToken_Ident and we can parse the string as a
    //   'xxyyzz' RGB color.
    // - If it only contains '0-9' digits, the token is a
    //   eCSSToken_Number and it must be converted back to a 6
    //   characters string to be parsed as a RGB color.
    // - If it starts with '0-9' and contains any 'a-f', the token is a
    //   eCSSToken_Dimension, the mNumber part must be converted back to
    //   a string and the mIdent part must be appended to that string so
    //   that the resulting string has 6 characters.
    // Note: This is a hack for Nav compatibility.  Do not attempt to
    // simplify it by hacking into the ncCSSScanner.  This would be very
    // bad.
    nsAutoString str;
    char buffer[20];
    switch (tk->mType) {
      case eCSSToken_Ident:
        str.Assign(tk->mIdent);
        break;

      case eCSSToken_Number:
        if (tk->mIntegerValid) {
          SprintfLiteral(buffer, "%06d", tk->mInteger);
          str.AssignWithConversion(buffer);
        }
        break;

      case eCSSToken_Dimension:
        if (tk->mIdent.Length() <= 6) {
          SprintfLiteral(buffer, "%06.0f", tk->mNumber);
          nsAutoString temp;
          temp.AssignWithConversion(buffer);
          temp.Right(str, 6 - tk->mIdent.Length());
          str.Append(tk->mIdent);
        }
        break;
      default:
        // There is a whole bunch of cases that are
        // not handled by this switch.  Ignore them.
        break;
    }
    // The hashless color quirk does not support 4 & 8 digit colors with alpha.
    if (NS_HexToRGBA(str, nsHexColorType::NoAlpha, &rgba)) {
      aValue.SetIntegerColorValue(rgba, eCSSUnit_HexColor);
      return CSSParseResult::Ok;
    }
  }

  // It's not a color
  REPORT_UNEXPECTED_TOKEN(PEColorNotColor);
  UngetToken();
  return CSSParseResult::NotFound;
}

bool
CSSParserImpl::ParseColorComponent(uint8_t& aComponent, Maybe<char> aSeparator)
{
  if (!GetToken(true)) {
    REPORT_UNEXPECTED_EOF(PEColorComponentEOF);
    return false;
  }

  if (mToken.mType != eCSSToken_Number) {
    REPORT_UNEXPECTED_TOKEN(PEExpectedNumber);
    UngetToken();
    return false;
  }

  float value = mToken.mNumber;

  if (aSeparator && !ExpectSymbol(*aSeparator, true)) {
    REPORT_UNEXPECTED_TOKEN_CHAR(PEColorComponentBadTerm, *aSeparator);
    return false;
  }

  if (value < 0.0f) value = 0.0f;
  if (value > 255.0f) value = 255.0f;

  aComponent = NSToIntRound(value);
  return true;
}

bool
CSSParserImpl::ParseColorComponent(float& aComponent, Maybe<char> aSeparator)
{
  if (!GetToken(true)) {
    REPORT_UNEXPECTED_EOF(PEColorComponentEOF);
    return false;
  }

  if (mToken.mType != eCSSToken_Percentage) {
    REPORT_UNEXPECTED_TOKEN(PEExpectedPercent);
    UngetToken();
    return false;
  }

  float value = mToken.mNumber;

  if (aSeparator && !ExpectSymbol(*aSeparator, true)) {
    REPORT_UNEXPECTED_TOKEN_CHAR(PEColorComponentBadTerm, *aSeparator);
    return false;
  }

  if (value < 0.0f) value = 0.0f;
  if (value > 1.0f) value = 1.0f;

  aComponent = value;
  return true;
}

bool
CSSParserImpl::ParseHue(float& aAngle)
{
  if (!GetToken(true)) {
    REPORT_UNEXPECTED_EOF(PEColorHueEOF);
    return false;
  }

  // <number>
  if (mToken.mType == eCSSToken_Number) {
    aAngle = mToken.mNumber;
    return true;
  }
  UngetToken();

  // <angle>
  nsCSSValue angleValue;
  // The '0' value is handled by <number> parsing, so use VARIANT_ANGLE flag
  // instead of VARIANT_ANGLE_OR_ZERO.
  if (ParseSingleTokenVariant(angleValue, VARIANT_ANGLE, nullptr)) {
    aAngle = angleValue.GetAngleValueInDegrees();
    return true;
  }

  REPORT_UNEXPECTED_TOKEN(PEExpectedNumberOrAngle);
  return false;
}

bool
CSSParserImpl::ParseHSLColor(float& aHue, float& aSaturation, float& aLightness,
                             float& aOpacity)
{
  // comma-less expression:
  // hsl() = hsl( <hue> <saturation> <lightness> [ / <alpha-value> ]? )
  // the expression with comma:
  // hsl() = hsl( <hue>, <saturation>, <lightness>, <alpha-value>? )

  const char commaSeparator = ',';

  // Parse hue.
  // <hue> = <number> | <angle>
  float degreeAngle;
  if (!ParseHue(degreeAngle)) {
    return false;
  }
  aHue = degreeAngle / 360.0f;
  // hue values are wraparound
  aHue = aHue - floor(aHue);
  // Look for a comma separator after "hue" component to determine if the
  // expression is comma-less or not.
  bool hasComma = ExpectSymbol(commaSeparator, true);

  // Parse saturation, lightness and opacity.
  // The saturation and lightness are <percentage>, so reuse the float version
  // of ParseColorComponent function for them. No need to check the separator
  // after 'lightness'. It will be checked in opacity value parsing.
  const char separatorBeforeAlpha = hasComma ? commaSeparator : '/';
  if (ParseColorComponent(aSaturation, hasComma ? Some(commaSeparator) : Nothing()) &&
      ParseColorComponent(aLightness, Nothing()) &&
      ParseColorOpacityAndCloseParen(aOpacity, separatorBeforeAlpha)) {
    return true;
  }

  return false;
}


bool
CSSParserImpl::ParseColorOpacityAndCloseParen(uint8_t& aOpacity,
                                              char aSeparator)
{
  float floatOpacity;
  if (!ParseColorOpacityAndCloseParen(floatOpacity, aSeparator)) {
    return false;
  }

  uint8_t value = nsStyleUtil::FloatToColorComponent(floatOpacity);
  // Need to compare to something slightly larger
  // than 0.5 due to floating point inaccuracies.
  NS_ASSERTION(fabs(255.0f * floatOpacity - value) <= 0.51f,
               "FloatToColorComponent did something weird");

  aOpacity = value;
  return true;
}

bool
CSSParserImpl::ParseColorOpacityAndCloseParen(float& aOpacity,
                                              char aSeparator)
{
  if (ExpectSymbol(')', true)) {
    // The optional [separator <alpha-value>] was omitted, so set the opacity
    // to a fully-opaque value '1.0f' and return success.
    aOpacity = 1.0f;
    return true;
  }

  if (!ExpectSymbol(aSeparator, true)) {
    REPORT_UNEXPECTED_TOKEN_CHAR(PEColorComponentBadTerm, aSeparator);
    return false;
  }

  if (!GetToken(true)) {
    REPORT_UNEXPECTED_EOF(PEColorOpacityEOF);
    return false;
  }

  // eCSSToken_Number or eCSSToken_Percentage.
  if (mToken.mType != eCSSToken_Number && mToken.mType != eCSSToken_Percentage) {
    REPORT_UNEXPECTED_TOKEN(PEExpectedNumberOrPercent);
    UngetToken();
    return false;
  }

  if (!ExpectSymbol(')', true)) {
    REPORT_UNEXPECTED_TOKEN(PEExpectedCloseParen);
    return false;
  }

  if (mToken.mNumber < 0.0f) {
    mToken.mNumber = 0.0f;
  } else if (mToken.mNumber > 1.0f) {
    mToken.mNumber = 1.0f;
  }

  aOpacity = mToken.mNumber;
  return true;
}

template<typename ComponentType>
bool
CSSParserImpl::ParseRGBColor(ComponentType& aR,
                             ComponentType& aG,
                             ComponentType& aB,
                             ComponentType& aA)
{
  // comma-less expression:
  // rgb() = rgb( component{3} [ / <alpha-value> ]? )
  // the expression with comma:
  // rgb() = rgb( component#{3} , <alpha-value>? )

  const char commaSeparator = ',';

  // Parse R.
  if (!ParseColorComponent(aR, Nothing())) {
    return false;
  }
  // Look for a comma separator after "r" component to determine if the
  // expression is comma-less or not.
  bool hasComma = ExpectSymbol(commaSeparator, true);

  // Parse G, B and A.
  // No need to check the separator after 'B'. It will be checked in 'A' value
  // parsing.
  const char separatorBeforeAlpha = hasComma ? commaSeparator : '/';
  if (ParseColorComponent(aG, hasComma ? Some(commaSeparator) : Nothing()) &&
      ParseColorComponent(aB, Nothing()) &&
      ParseColorOpacityAndCloseParen(aA, separatorBeforeAlpha)) {
    return true;
  }

  return false;
}

#ifdef MOZ_XUL
bool
CSSParserImpl::ParseTreePseudoElement(nsAtomList **aPseudoElementArgs)
{
  // The argument to a tree pseudo-element is a sequence of identifiers
  // that are either space- or comma-separated.  (Was the intent to
  // allow only comma-separated?  That's not what was done.)
  nsCSSSelector fakeSelector; // so we can reuse AddPseudoClass

  while (!ExpectSymbol(')', true)) {
    if (!GetToken(true)) {
      return false;
    }
    if (eCSSToken_Ident == mToken.mType) {
      fakeSelector.AddClass(mToken.mIdent);
    }
    else if (!mToken.IsSymbol(',')) {
      UngetToken();
      SkipUntil(')');
      return false;
    }
  }
  *aPseudoElementArgs = fakeSelector.mClassList;
  fakeSelector.mClassList = nullptr;
  return true;
}
#endif

nsCSSKeyword
CSSParserImpl::LookupKeywordPrefixAware(nsAString& aKeywordStr,
                                        const KTableEntry aKeywordTable[])
{
  nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(aKeywordStr);

  if (aKeywordTable == nsCSSProps::kDisplayKTable) {
    // NOTE: This code will be considerably simpler once we can do away with
    // all Unprefixing Service code, in bug 1259348. But for the time being, we
    // have to support two different strategies for handling -webkit-box here:
    // (1) "Native support" (sWebkitPrefixedAliasesEnabled): we assume that
    //     -webkit-box will parse correctly (via an entry in kDisplayKTable),
    //     and we simply make a note that we've parsed it (so that we can we
    //     can give later "-moz-box" styling special handling as noted below).
    // (2) "Unprefixing Service support" (ShouldUseUnprefixingService): we
    //     convert "-webkit-box" directly to modern "flex" (& do the same for
    //     any later "-moz-box" styling).
    //
    // Note that sWebkitPrefixedAliasesEnabled and
    // ShouldUseUnprefixingService() are mutually exlusive, because the latter
    // explicitly defers to the former.
    if ((keyword == eCSSKeyword__webkit_box ||
         keyword == eCSSKeyword__webkit_inline_box)) {
      const bool usingUnprefixingService = ShouldUseUnprefixingService();
      if (sWebkitPrefixedAliasesEnabled || usingUnprefixingService) {
        // Make a note that we're accepting some "-webkit-{inline-}box" styling,
        // so we can give special treatment to subsequent "-moz-{inline}-box".
        // (See special treatment below.)
        if (mWebkitBoxUnprefixState == eHaveNotUnprefixed) {
          mWebkitBoxUnprefixState = eHaveUnprefixed;
        }
        if (usingUnprefixingService) {
          // When we're using the unprefixing service, we treat
          // "display:-webkit-box" as if it were "display:flex"
          // (and "-webkit-inline-box" as "inline-flex").
          return (keyword == eCSSKeyword__webkit_box) ?
            eCSSKeyword_flex : eCSSKeyword_inline_flex;
        }
      }
    }

    // If we've seen "display: -webkit-box" (or "-webkit-inline-box") in an
    // earlier declaration and we honored it, then we have to watch out for
    // later "display: -moz-box" (and "-moz-inline-box") declarations; they're
    // likely just a halfhearted attempt at compatibility, and they actually
    // end up stomping on our emulation of the earlier -webkit-box
    // display-value, via the CSS cascade. To prevent this problem, we treat
    // "display: -moz-box" & "-moz-inline-box" as if they were simply a
    // repetition of the webkit equivalent that we already parsed.
    if (mWebkitBoxUnprefixState == eHaveUnprefixed &&
        (keyword == eCSSKeyword__moz_box ||
         keyword == eCSSKeyword__moz_inline_box)) {
      MOZ_ASSERT(sWebkitPrefixedAliasesEnabled || ShouldUseUnprefixingService(),
                 "mDidUnprefixWebkitBoxInEarlierDecl should only be set if "
                 "we're supporting webkit-prefixed aliases, or if we're using "
                 "the css unprefixing service on this site");
      if (sWebkitPrefixedAliasesEnabled) {
        return (keyword == eCSSKeyword__moz_box) ?
          eCSSKeyword__webkit_box : eCSSKeyword__webkit_inline_box;
      }
      // (If we get here, we're using the Unprefixing Service, which means
      // we're unprefixing all the way to modern flexbox display values.)
      return (keyword == eCSSKeyword__moz_box) ?
        eCSSKeyword_flex : eCSSKeyword_inline_flex;
    }
  }

  return keyword;
}

bool
CSSParserImpl::ShouldUseUnprefixingService() const
{
  if (!sUnprefixingServiceEnabled) {
    // Unprefixing is globally disabled.
    return false;
  }
  if (sWebkitPrefixedAliasesEnabled) {
    // Native webkit-prefix support is enabled, which trumps the unprefixing
    // service for handling prefixed CSS. Don't try to use both at once.
    return false;
  }

#ifdef NIGHTLY_BUILD
  if (sUnprefixingServiceGloballyWhitelisted) {
    // Unprefixing is globally whitelisted,
    // so no need to check mSheetPrincipal.
    return true;
  }
#endif
  // Unprefixing enabled; see if our principal is whitelisted for unprefixing.
  return mSheetPrincipal && mSheetPrincipal->IsOnCSSUnprefixingWhitelist();
}

bool
CSSParserImpl::ParsePropertyWithUnprefixingService(
  const nsAString& aPropertyName,
  css::Declaration* aDeclaration,
  uint32_t aFlags,
  bool aMustCallValueAppended,
  bool* aChanged,
  nsCSSContextType aContext)
{
  MOZ_ASSERT(ShouldUseUnprefixingService(),
             "Caller should've checked ShouldUseUnprefixingService()");

  nsCOMPtr<nsICSSUnprefixingService> unprefixingSvc =
    do_GetService(NS_CSSUNPREFIXINGSERVICE_CONTRACTID);
  NS_ENSURE_TRUE(unprefixingSvc, false);

  // Save the state so we can jump back to this spot if our unprefixing fails
  // (so we can behave as if we didn't even try to unprefix).
  nsAutoCSSParserInputStateRestorer parserStateBeforeTryingToUnprefix(this);

  // Caller has already parsed the first half of the declaration --
  // aPropertyName and the ":".  Now, we record the rest of the CSS declaration
  // (the part after ':') into rightHalfOfDecl.  (This is the property value,
  // plus anything else up to the end of the declaration -- maybe "!important",
  // maybe trailing junk characters, maybe a semicolon, maybe a trailing "}".)
  bool checkForBraces = (aFlags & eParseDeclaration_InBraces) != 0;
  nsAutoString rightHalfOfDecl;
  mScanner->StartRecording();
  SkipDeclaration(checkForBraces);
  mScanner->StopRecording(rightHalfOfDecl);

  // Try to unprefix:
  bool success;
  nsAutoString unprefixedDecl;
  nsresult rv =
    unprefixingSvc->GenerateUnprefixedDeclaration(aPropertyName,
                                                  rightHalfOfDecl,
                                                  unprefixedDecl, &success);
  if (NS_FAILED(rv) || !success) {
    return false;
  }

  // Attempt to parse the unprefixed declaration:
  nsAutoScannerChanger scannerChanger(this, unprefixedDecl);
  success = ParseDeclaration(aDeclaration,
                             aFlags | eParseDeclaration_FromUnprefixingSvc,
                             aMustCallValueAppended, aChanged, aContext);
  if (success) {
    // We succeeded, so we'll leave the parser pointing at the end of
    // the declaration; don't restore it to the pre-recording position.
    parserStateBeforeTryingToUnprefix.DoNotRestore();
  }

  return success;
}

bool
CSSParserImpl::ParseWebkitPrefixedGradientWithService(
  nsAString& aPrefixedFuncName,
  nsCSSValue& aValue)
{
  MOZ_ASSERT(ShouldUseUnprefixingService(),
             "Should only call if we're allowed to use unprefixing service");

  // Record the body of the "-webkit-*gradient" function into a string.
  // Note: we're already just after the opening "(".
  nsAutoString prefixedFuncBody;
  mScanner->StartRecording();
  bool gotCloseParen = SkipUntil(')');
  mScanner->StopRecording(prefixedFuncBody);
  if (gotCloseParen) {
    // Strip off trailing close-paren, so that the value we pass to the
    // unprefixing service is *just* the function-body (no parens).
    prefixedFuncBody.Truncate(prefixedFuncBody.Length() - 1);
  }

  // NOTE: Even if we fail, we'll be leaving the parser's cursor just after
  // the close of the "-webkit-*gradient(...)" expression. This is the same
  // behavior that the other Parse*Gradient functions have in their failure
  // cases -- they call "SkipUntil(')') before returning false. So this is
  // probably what we want.
  nsCOMPtr<nsICSSUnprefixingService> unprefixingSvc =
    do_GetService(NS_CSSUNPREFIXINGSERVICE_CONTRACTID);
  NS_ENSURE_TRUE(unprefixingSvc, false);

  bool success;
  nsAutoString unprefixedFuncName;
  nsAutoString unprefixedFuncBody;
  nsresult rv =
    unprefixingSvc->GenerateUnprefixedGradientValue(aPrefixedFuncName,
                                                    prefixedFuncBody,
                                                    unprefixedFuncName,
                                                    unprefixedFuncBody,
                                                    &success);

  if (NS_FAILED(rv) || !success) {
    return false;
  }

  // JS service thinks it successfully converted the gradient! Now let's try
  // to parse the resulting string.

  // First, add a close-paren if we originally recorded one (so that what we're
  // about to put into the CSS parser is a faithful representation of what it
  // would've seen if it were just parsing the original input stream):
  if (gotCloseParen) {
    unprefixedFuncBody.Append(char16_t(')'));
  }

  nsAutoScannerChanger scannerChanger(this, unprefixedFuncBody);
  if (unprefixedFuncName.EqualsLiteral("linear-gradient")) {
    return ParseLinearGradient(aValue, 0);
  }
  if (unprefixedFuncName.EqualsLiteral("radial-gradient")) {
    return ParseRadialGradient(aValue, 0);
  }

  NS_ERROR("CSSUnprefixingService returned an unrecognized type of "
           "gradient function");

  return false;
}

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

bool
CSSParserImpl::ParseDeclaration(css::Declaration* aDeclaration,
                                uint32_t aFlags,
                                bool aMustCallValueAppended,
                                bool* aChanged,
                                nsCSSContextType aContext)
{
  NS_PRECONDITION(aContext == eCSSContext_General ||
                  aContext == eCSSContext_Page,
                  "Must be page or general context");

  bool checkForBraces = (aFlags & eParseDeclaration_InBraces) != 0;

  mTempData.AssertInitialState();

  // Get property name
  nsCSSToken* tk = &mToken;
  nsAutoString propertyName;
  for (;;) {
    if (!GetToken(true)) {
      if (checkForBraces) {
        REPORT_UNEXPECTED_EOF(PEDeclEndEOF);
      }
      return false;
    }
    if (eCSSToken_Ident == tk->mType) {
      propertyName = tk->mIdent;
      // grab the ident before the ExpectSymbol trashes the token
      if (!ExpectSymbol(':', true)) {
        REPORT_UNEXPECTED_TOKEN(PEParseDeclarationNoColon);
        REPORT_UNEXPECTED(PEDeclDropped);
        OUTPUT_ERROR();
        return false;
      }
      break;
    }
    if (tk->IsSymbol(';')) {
      // dangling semicolons are skipped
      continue;
    }

    if (!tk->IsSymbol('}')) {
      REPORT_UNEXPECTED_TOKEN(PEParseDeclarationDeclExpected);
      REPORT_UNEXPECTED(PEDeclSkipped);
      OUTPUT_ERROR();

      if (eCSSToken_AtKeyword == tk->mType) {
        SkipAtRule(checkForBraces);
        return true;  // Not a declaration, but don't skip until ';'
      }
    }
    // Not a declaration...
    UngetToken();
    return false;
  }

  // Don't report property parse errors if we're inside a failing @supports
  // rule.
  nsAutoSuppressErrors suppressErrors(this, mInFailingSupportsRule);

  // Information about a parsed non-custom property.
  nsCSSPropertyID propID;

  // Information about a parsed custom property.
  CSSVariableDeclarations::Type variableType;
  nsString variableValue;

  // Check if the property name is a custom property.
  bool customProperty = nsLayoutUtils::CSSVariablesEnabled() &&
                        nsCSSProps::IsCustomPropertyName(propertyName) &&
                        aContext == eCSSContext_General;

  if (customProperty) {
    if (!ParseVariableDeclaration(&variableType, variableValue)) {
      REPORT_UNEXPECTED_P(PEValueParsingError, propertyName);
      REPORT_UNEXPECTED(PEDeclDropped);
      OUTPUT_ERROR();
      return false;
    }
  } else {
    // Map property name to its ID.
    propID = LookupEnabledProperty(propertyName);
    if (eCSSProperty_UNKNOWN == propID ||
        eCSSPropertyExtra_variable == propID ||
        (aContext == eCSSContext_Page &&
         !nsCSSProps::PropHasFlags(propID,
                                   CSS_PROPERTY_APPLIES_TO_PAGE_RULE))) { // unknown property
      if (NonMozillaVendorIdentifier(propertyName)) {
        if (!mInSupportsCondition &&
            aContext == eCSSContext_General &&
            !(aFlags & eParseDeclaration_FromUnprefixingSvc) && // no recursion
            ShouldUseUnprefixingService()) {
          if (ParsePropertyWithUnprefixingService(propertyName,
                                                  aDeclaration, aFlags,
                                                  aMustCallValueAppended,
                                                  aChanged, aContext)) {
            return true;
          }
        }
      } else {
        REPORT_UNEXPECTED_P(PEUnknownProperty, propertyName);
        REPORT_UNEXPECTED(PEDeclDropped);
        OUTPUT_ERROR();
      }
      return false;
    }
    // Then parse the property.
    if (!ParseProperty(propID)) {
      // XXX Much better to put stuff in the value parsers instead...
      REPORT_UNEXPECTED_P(PEValueParsingError, propertyName);
      REPORT_UNEXPECTED(PEDeclDropped);
      OUTPUT_ERROR();
      mTempData.ClearProperty(propID);
      mTempData.AssertInitialState();
      return false;
    }
  }

  CLEAR_ERROR();

  // Look for "!important".
  PriorityParsingStatus status;
  if ((aFlags & eParseDeclaration_AllowImportant) != 0) {
    status = ParsePriority();
  } else {
    status = ePriority_None;
  }

  // Look for a semicolon or close brace.
  if (status != ePriority_Error) {
    if (!GetToken(true)) {
      // EOF is always ok
    } else if (mToken.IsSymbol(';')) {
      // semicolon is always ok
    } else if (mToken.IsSymbol('}')) {
      // brace is ok if checkForBraces, but don't eat it
      UngetToken();
      if (!checkForBraces) {
        status = ePriority_Error;
      }
    } else {
      UngetToken();
      status = ePriority_Error;
    }
  }

  if (status == ePriority_Error) {
    if (checkForBraces) {
      REPORT_UNEXPECTED_TOKEN(PEBadDeclOrRuleEnd2);
    } else {
      REPORT_UNEXPECTED_TOKEN(PEBadDeclEnd);
    }
    REPORT_UNEXPECTED(PEDeclDropped);
    OUTPUT_ERROR();
    if (!customProperty) {
      mTempData.ClearProperty(propID);
    }
    mTempData.AssertInitialState();
    return false;
  }

  if (customProperty) {
    MOZ_ASSERT(Substring(propertyName, 0,
                         CSS_CUSTOM_NAME_PREFIX_LENGTH).EqualsLiteral("--"));
    // remove '--'
    nsDependentString varName(propertyName, CSS_CUSTOM_NAME_PREFIX_LENGTH);
    aDeclaration->AddVariable(varName, variableType, variableValue,
                              status == ePriority_Important, false);
  } else {
    *aChanged |= mData.TransferFromBlock(mTempData, propID, EnabledState(),
                                         status == ePriority_Important,
                                         false, aMustCallValueAppended,
                                         aDeclaration, GetDocument());
  }

  return true;
}

static const nsCSSPropertyID kBorderTopIDs[] = {
  eCSSProperty_border_top_width,
  eCSSProperty_border_top_style,
  eCSSProperty_border_top_color
};
static const nsCSSPropertyID kBorderRightIDs[] = {
  eCSSProperty_border_right_width,
  eCSSProperty_border_right_style,
  eCSSProperty_border_right_color
};
static const nsCSSPropertyID kBorderBottomIDs[] = {
  eCSSProperty_border_bottom_width,
  eCSSProperty_border_bottom_style,
  eCSSProperty_border_bottom_color
};
static const nsCSSPropertyID kBorderLeftIDs[] = {
  eCSSProperty_border_left_width,
  eCSSProperty_border_left_style,
  eCSSProperty_border_left_color
};
static const nsCSSPropertyID kBorderInlineStartIDs[] = {
  eCSSProperty_border_inline_start_width,
  eCSSProperty_border_inline_start_style,
  eCSSProperty_border_inline_start_color
};
static const nsCSSPropertyID kBorderInlineEndIDs[] = {
  eCSSProperty_border_inline_end_width,
  eCSSProperty_border_inline_end_style,
  eCSSProperty_border_inline_end_color
};
static const nsCSSPropertyID kBorderBlockStartIDs[] = {
  eCSSProperty_border_block_start_width,
  eCSSProperty_border_block_start_style,
  eCSSProperty_border_block_start_color
};
static const nsCSSPropertyID kBorderBlockEndIDs[] = {
  eCSSProperty_border_block_end_width,
  eCSSProperty_border_block_end_style,
  eCSSProperty_border_block_end_color
};
static const nsCSSPropertyID kColumnRuleIDs[] = {
  eCSSProperty_column_rule_width,
  eCSSProperty_column_rule_style,
  eCSSProperty_column_rule_color
};

bool
CSSParserImpl::ParseEnum(nsCSSValue& aValue,
                         const KTableEntry aKeywordTable[])
{
  nsSubstring* ident = NextIdent();
  if (nullptr == ident) {
    return false;
  }
  nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(*ident);
  if (eCSSKeyword_UNKNOWN < keyword) {
    int32_t value;
    if (nsCSSProps::FindKeyword(keyword, aKeywordTable, value)) {
      aValue.SetIntValue(value, eCSSUnit_Enumerated);
      return true;
    }
  }

  // Put the unknown identifier back and return
  UngetToken();
  return false;
}

bool
CSSParserImpl::ParseAlignEnum(nsCSSValue& aValue,
                              const KTableEntry aKeywordTable[])
{
  MOZ_ASSERT(nsCSSProps::ValueToKeywordEnum(NS_STYLE_ALIGN_BASELINE,
                                            aKeywordTable) !=
               eCSSKeyword_UNKNOWN,
             "Please use ParseEnum instead");
  nsSubstring* ident = NextIdent();
  if (!ident) {
    return false;
  }
  nsCSSKeyword baselinePrefix = eCSSKeyword_first;
  nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(*ident);
  if (keyword == eCSSKeyword_first || keyword == eCSSKeyword_last) {
    baselinePrefix = keyword;
    ident = NextIdent();
    if (!ident) {
      return false;
    }
    keyword = nsCSSKeywords::LookupKeyword(*ident);
  }
  if (eCSSKeyword_UNKNOWN < keyword) {
    int32_t value;
    if (nsCSSProps::FindKeyword(keyword, aKeywordTable, value)) {
      if (baselinePrefix == eCSSKeyword_last &&
          keyword == eCSSKeyword_baseline) {
        value = NS_STYLE_ALIGN_LAST_BASELINE;
      }
      aValue.SetIntValue(value, eCSSUnit_Enumerated);
      return true;
    }
  }

  // Put the unknown identifier back and return
  UngetToken();
  return false;
}

struct UnitInfo {
  char name[6];  // needs to be long enough for the longest unit, with
                 // terminating null.
  uint32_t length;
  nsCSSUnit unit;
  int32_t type;
};

#define STR_WITH_LEN(_str) \
  _str, sizeof(_str) - 1

const UnitInfo UnitData[] = {
  { STR_WITH_LEN("px"), eCSSUnit_Pixel, VARIANT_LENGTH },
  { STR_WITH_LEN("em"), eCSSUnit_EM, VARIANT_LENGTH },
  { STR_WITH_LEN("ex"), eCSSUnit_XHeight, VARIANT_LENGTH },
  { STR_WITH_LEN("pt"), eCSSUnit_Point, VARIANT_LENGTH },
  { STR_WITH_LEN("in"), eCSSUnit_Inch, VARIANT_LENGTH },
  { STR_WITH_LEN("cm"), eCSSUnit_Centimeter, VARIANT_LENGTH },
  { STR_WITH_LEN("ch"), eCSSUnit_Char, VARIANT_LENGTH },
  { STR_WITH_LEN("rem"), eCSSUnit_RootEM, VARIANT_LENGTH },
  { STR_WITH_LEN("mm"), eCSSUnit_Millimeter, VARIANT_LENGTH },
  { STR_WITH_LEN("mozmm"), eCSSUnit_PhysicalMillimeter, VARIANT_LENGTH },
  { STR_WITH_LEN("vw"), eCSSUnit_ViewportWidth, VARIANT_LENGTH },
  { STR_WITH_LEN("vh"), eCSSUnit_ViewportHeight, VARIANT_LENGTH },
  { STR_WITH_LEN("vmin"), eCSSUnit_ViewportMin, VARIANT_LENGTH },
  { STR_WITH_LEN("vmax"), eCSSUnit_ViewportMax, VARIANT_LENGTH },
  { STR_WITH_LEN("pc"), eCSSUnit_Pica, VARIANT_LENGTH },
  { STR_WITH_LEN("q"), eCSSUnit_Quarter, VARIANT_LENGTH },
  { STR_WITH_LEN("deg"), eCSSUnit_Degree, VARIANT_ANGLE },
  { STR_WITH_LEN("grad"), eCSSUnit_Grad, VARIANT_ANGLE },
  { STR_WITH_LEN("rad"), eCSSUnit_Radian, VARIANT_ANGLE },
  { STR_WITH_LEN("turn"), eCSSUnit_Turn, VARIANT_ANGLE },
  { STR_WITH_LEN("hz"), eCSSUnit_Hertz, VARIANT_FREQUENCY },
  { STR_WITH_LEN("khz"), eCSSUnit_Kilohertz, VARIANT_FREQUENCY },
  { STR_WITH_LEN("s"), eCSSUnit_Seconds, VARIANT_TIME },
  { STR_WITH_LEN("ms"), eCSSUnit_Milliseconds, VARIANT_TIME }
};

#undef STR_WITH_LEN

bool
CSSParserImpl::TranslateDimension(nsCSSValue& aValue,
                                  uint32_t aVariantMask,
                                  float aNumber,
                                  const nsString& aUnit)
{
  nsCSSUnit units;
  int32_t   type = 0;
  if (!aUnit.IsEmpty()) {
    uint32_t i;
    for (i = 0; i < ArrayLength(UnitData); ++i) {
      if (aUnit.LowerCaseEqualsASCII(UnitData[i].name,
                                     UnitData[i].length)) {
        units = UnitData[i].unit;
        type = UnitData[i].type;
        break;
      }
    }

    if (i == ArrayLength(UnitData)) {
      // Unknown unit
      return false;
    }

    if (!mViewportUnitsEnabled &&
        (eCSSUnit_ViewportWidth == units  ||
         eCSSUnit_ViewportHeight == units ||
         eCSSUnit_ViewportMin == units    ||
         eCSSUnit_ViewportMax == units)) {
      // Viewport units aren't allowed right now, probably because we're
      // inside an @page declaration. Fail.
      return false;
    }

    if ((VARIANT_ABSOLUTE_DIMENSION & aVariantMask) != 0 &&
        !nsCSSValue::IsPixelLengthUnit(units)) {
      return false;
    }
  } else {
    // Must be a zero number...
    NS_ASSERTION(0 == aNumber, "numbers without units must be 0");
    if ((VARIANT_LENGTH & aVariantMask) != 0) {
      units = eCSSUnit_Pixel;
      type = VARIANT_LENGTH;
    }
    else if ((VARIANT_ANGLE & aVariantMask) != 0) {
      NS_ASSERTION(aVariantMask & VARIANT_ZERO_ANGLE,
                   "must have allowed zero angle");
      units = eCSSUnit_Degree;
      type = VARIANT_ANGLE;
    }
    else {
      NS_ERROR("Variant mask does not include dimension; why were we called?");
      return false;
    }
  }
  if ((type & aVariantMask) != 0) {
    aValue.SetFloatValue(aNumber, units);
    return true;
  }
  return false;
}

// Note that this does include VARIANT_CALC, which is numeric.  This is
// because calc() parsing, as proposed, drops range restrictions inside
// the calc() expression and clamps the result of the calculation to the
// range.
#define VARIANT_ALL_NONNUMERIC \
  VARIANT_KEYWORD | \
  VARIANT_COLOR | \
  VARIANT_URL | \
  VARIANT_STRING | \
  VARIANT_COUNTER | \
  VARIANT_ATTR | \
  VARIANT_IDENTIFIER | \
  VARIANT_IDENTIFIER_NO_INHERIT | \
  VARIANT_AUTO | \
  VARIANT_INHERIT | \
  VARIANT_NONE | \
  VARIANT_NORMAL | \
  VARIANT_SYSFONT | \
  VARIANT_GRADIENT | \
  VARIANT_TIMING_FUNCTION | \
  VARIANT_ALL | \
  VARIANT_CALC | \
  VARIANT_OPENTYPE_SVG_KEYWORD

CSSParseResult
CSSParserImpl::ParseVariantWithRestrictions(nsCSSValue& aValue,
                                            int32_t aVariantMask,
                                            const KTableEntry aKeywordTable[],
                                            uint32_t aRestrictions)
{
  switch (aRestrictions) {
    default:
      MOZ_FALLTHROUGH_ASSERT("should not be reached");
    case 0:
      return ParseVariant(aValue, aVariantMask, aKeywordTable);
    case CSS_PROPERTY_VALUE_NONNEGATIVE:
      return ParseNonNegativeVariant(aValue, aVariantMask, aKeywordTable);
    case CSS_PROPERTY_VALUE_AT_LEAST_ONE:
      return ParseOneOrLargerVariant(aValue, aVariantMask, aKeywordTable);
  }
}

// Note that callers passing VARIANT_CALC in aVariantMask will get
// full-range parsing inside the calc() expression, and the code that
// computes the calc will be required to clamp the resulting value to an
// appropriate range.
CSSParseResult
CSSParserImpl::ParseNonNegativeVariant(nsCSSValue& aValue,
                                       int32_t aVariantMask,
                                       const KTableEntry aKeywordTable[])
{
  // The variant mask must only contain non-numeric variants or the ones
  // that we specifically handle.
  MOZ_ASSERT((aVariantMask & ~(VARIANT_ALL_NONNUMERIC |
                               VARIANT_NUMBER |
                               VARIANT_LENGTH |
                               VARIANT_PERCENT |
                               VARIANT_INTEGER)) == 0,
             "need to update code below to handle additional variants");

  CSSParseResult result = ParseVariant(aValue, aVariantMask, aKeywordTable);
  if (result == CSSParseResult::Ok) {
    if (eCSSUnit_Number == aValue.GetUnit() ||
        aValue.IsLengthUnit()){
      if (aValue.GetFloatValue() < 0) {
        UngetToken();
        return CSSParseResult::NotFound;
      }
    }
    else if (aValue.GetUnit() == eCSSUnit_Percent) {
      if (aValue.GetPercentValue() < 0) {
        UngetToken();
        return CSSParseResult::NotFound;
      }
    } else if (aValue.GetUnit() == eCSSUnit_Integer) {
      if (aValue.GetIntValue() < 0) {
        UngetToken();
        return CSSParseResult::NotFound;
      }
    }
  }
  return result;
}

// Note that callers passing VARIANT_CALC in aVariantMask will get
// full-range parsing inside the calc() expression, and the code that
// computes the calc will be required to clamp the resulting value to an
// appropriate range.
CSSParseResult
CSSParserImpl::ParseOneOrLargerVariant(nsCSSValue& aValue,
                                       int32_t aVariantMask,
                                       const KTableEntry aKeywordTable[])
{
  // The variant mask must only contain non-numeric variants or the ones
  // that we specifically handle.
  MOZ_ASSERT((aVariantMask & ~(VARIANT_ALL_NONNUMERIC |
                               VARIANT_NUMBER |
                               VARIANT_INTEGER)) == 0,
             "need to update code below to handle additional variants");

  CSSParseResult result = ParseVariant(aValue, aVariantMask, aKeywordTable);
  if (result == CSSParseResult::Ok) {
    if (aValue.GetUnit() == eCSSUnit_Integer) {
      if (aValue.GetIntValue() < 1) {
        UngetToken();
        return CSSParseResult::NotFound;
      }
    } else if (eCSSUnit_Number == aValue.GetUnit()) {
      if (aValue.GetFloatValue() < 1.0f) {
        UngetToken();
        return CSSParseResult::NotFound;
      }
    }
  }
  return result;
}

static bool
IsCSSTokenCalcFunction(const nsCSSToken& aToken)
{
  return aToken.mType == eCSSToken_Function &&
         (aToken.mIdent.LowerCaseEqualsLiteral("calc") ||
          aToken.mIdent.LowerCaseEqualsLiteral("-moz-calc"));
}

// Assigns to aValue iff it returns CSSParseResult::Ok.
CSSParseResult
CSSParserImpl::ParseVariant(nsCSSValue& aValue,
                            uint32_t aVariantMask,
                            const KTableEntry aKeywordTable[])
{
  NS_ASSERTION(!(mHashlessColorQuirk && (aVariantMask & VARIANT_COLOR)) ||
               !(aVariantMask & VARIANT_NUMBER),
               "can't distinguish colors from numbers");
  NS_ASSERTION(!(mHashlessColorQuirk && (aVariantMask & VARIANT_COLOR)) ||
               !(mUnitlessLengthQuirk && (aVariantMask & VARIANT_LENGTH)),
               "can't distinguish colors from lengths");
  NS_ASSERTION(!(mUnitlessLengthQuirk && (aVariantMask & VARIANT_LENGTH)) ||
               !(aVariantMask & VARIANT_NUMBER),
               "can't distinguish lengths from numbers");
  MOZ_ASSERT(!(aVariantMask & VARIANT_IDENTIFIER) ||
             !(aVariantMask & VARIANT_IDENTIFIER_NO_INHERIT),
             "must not set both VARIANT_IDENTIFIER and "
             "VARIANT_IDENTIFIER_NO_INHERIT");

  uint32_t lineBefore, colBefore;
  if (!GetNextTokenLocation(true, &lineBefore, &colBefore) ||
      !GetToken(true)) {
    // Must be at EOF.
    return CSSParseResult::NotFound;
  }

  nsCSSToken* tk = &mToken;
  if (((aVariantMask & (VARIANT_AHK | VARIANT_NORMAL | VARIANT_NONE | VARIANT_ALL)) != 0) &&
      (eCSSToken_Ident == tk->mType)) {
    nsCSSKeyword keyword = LookupKeywordPrefixAware(tk->mIdent,
                                                    aKeywordTable);

    if (eCSSKeyword_UNKNOWN < keyword) { // known keyword
      if ((aVariantMask & VARIANT_AUTO) != 0) {
        if (eCSSKeyword_auto == keyword) {
          aValue.SetAutoValue();
          return CSSParseResult::Ok;
        }
      }
      if ((aVariantMask & VARIANT_INHERIT) != 0) {
        // XXX Should we check IsParsingCompoundProperty, or do all
        // callers handle it?  (Not all callers set it, though, since
        // they want the quirks that are disabled by setting it.)

        // IMPORTANT: If new keywords are added here,
        // they probably need to be added in ParseCustomIdent as well.
        if (eCSSKeyword_inherit == keyword) {
          aValue.SetInheritValue();
          return CSSParseResult::Ok;
        }
        else if (eCSSKeyword_initial == keyword) {
          aValue.SetInitialValue();
          return CSSParseResult::Ok;
        }
        else if (eCSSKeyword_unset == keyword &&
                 nsLayoutUtils::UnsetValueEnabled()) {
          aValue.SetUnsetValue();
          return CSSParseResult::Ok;
        }
      }
      if ((aVariantMask & VARIANT_NONE) != 0) {
        if (eCSSKeyword_none == keyword) {
          aValue.SetNoneValue();
          return CSSParseResult::Ok;
        }
      }
      if ((aVariantMask & VARIANT_ALL) != 0) {
        if (eCSSKeyword_all == keyword) {
          aValue.SetAllValue();
          return CSSParseResult::Ok;
        }
      }
      if ((aVariantMask & VARIANT_NORMAL) != 0) {
        if (eCSSKeyword_normal == keyword) {
          aValue.SetNormalValue();
          return CSSParseResult::Ok;
        }
      }
      if ((aVariantMask & VARIANT_SYSFONT) != 0) {
        if (eCSSKeyword__moz_use_system_font == keyword &&
            !IsParsingCompoundProperty()) {
          aValue.SetSystemFontValue();
          return CSSParseResult::Ok;
        }
      }
      if ((aVariantMask & VARIANT_OPENTYPE_SVG_KEYWORD) != 0) {
        if (sOpentypeSVGEnabled) {
          aVariantMask |= VARIANT_KEYWORD;
        }
      }
      if ((aVariantMask & VARIANT_KEYWORD) != 0) {
        int32_t value;
        if (nsCSSProps::FindKeyword(keyword, aKeywordTable, value)) {
          aValue.SetIntValue(value, eCSSUnit_Enumerated);
          return CSSParseResult::Ok;
        }
      }
    }
  }
  // Check VARIANT_NUMBER and VARIANT_INTEGER before VARIANT_LENGTH or
  // VARIANT_ZERO_ANGLE.
  if (((aVariantMask & VARIANT_NUMBER) != 0) &&
      (eCSSToken_Number == tk->mType)) {
    aValue.SetFloatValue(tk->mNumber, eCSSUnit_Number);
    return CSSParseResult::Ok;
  }
  if (((aVariantMask & VARIANT_INTEGER) != 0) &&
      (eCSSToken_Number == tk->mType) && tk->mIntegerValid) {
    aValue.SetIntValue(tk->mInteger, eCSSUnit_Integer);
    return CSSParseResult::Ok;
  }
  if (((aVariantMask & (VARIANT_LENGTH | VARIANT_ANGLE |
                        VARIANT_FREQUENCY | VARIANT_TIME)) != 0 &&
       eCSSToken_Dimension == tk->mType) ||
      ((aVariantMask & (VARIANT_LENGTH | VARIANT_ZERO_ANGLE)) != 0 &&
       eCSSToken_Number == tk->mType &&
       tk->mNumber == 0.0f)) {
    if ((aVariantMask & VARIANT_NONNEGATIVE_DIMENSION) != 0 &&
        tk->mNumber < 0.0) {
        UngetToken();
        AssertNextTokenAt(lineBefore, colBefore);
        return CSSParseResult::NotFound;
    }
    if (TranslateDimension(aValue, aVariantMask, tk->mNumber, tk->mIdent)) {
      return CSSParseResult::Ok;
    }
    // Put the token back; we didn't parse it, so we shouldn't consume it
    UngetToken();
    AssertNextTokenAt(lineBefore, colBefore);
    return CSSParseResult::NotFound;
  }
  if (((aVariantMask & VARIANT_PERCENT) != 0) &&
      (eCSSToken_Percentage == tk->mType)) {
    aValue.SetPercentValue(tk->mNumber);
    return CSSParseResult::Ok;
  }
  if (mUnitlessLengthQuirk) { // NONSTANDARD: Nav interprets unitless numbers as px
    if (((aVariantMask & VARIANT_LENGTH) != 0) &&
        (eCSSToken_Number == tk->mType)) {
      aValue.SetFloatValue(tk->mNumber, eCSSUnit_Pixel);
      return CSSParseResult::Ok;
    }
  }

  if (IsSVGMode() && !IsParsingCompoundProperty()) {
    // STANDARD: SVG Spec states that lengths and coordinates can be unitless
    // in which case they default to user-units (1 px = 1 user unit)
    if (((aVariantMask & VARIANT_LENGTH) != 0) &&
        (eCSSToken_Number == tk->mType)) {
      aValue.SetFloatValue(tk->mNumber, eCSSUnit_Pixel);
      return CSSParseResult::Ok;
    }
  }

  if (((aVariantMask & VARIANT_URL) != 0) &&
      eCSSToken_URL == tk->mType) {
    SetValueToURL(aValue, tk->mIdent);
    return CSSParseResult::Ok;
  }
  if ((aVariantMask & VARIANT_GRADIENT) != 0 &&
      eCSSToken_Function == tk->mType) {
    // a generated gradient
    nsDependentString tmp(tk->mIdent, 0);
    uint8_t gradientFlags = 0;
    if (sMozGradientsEnabled &&
        StringBeginsWith(tmp, NS_LITERAL_STRING("-moz-"))) {
      tmp.Rebind(tmp, 5);
      gradientFlags |= eGradient_MozLegacy;
    } else if (sWebkitPrefixedAliasesEnabled &&
               StringBeginsWith(tmp, NS_LITERAL_STRING("-webkit-"))) {
      tmp.Rebind(tmp, 8);
      gradientFlags |= eGradient_WebkitLegacy;
    }
    if (StringBeginsWith(tmp, NS_LITERAL_STRING("repeating-"))) {
      tmp.Rebind(tmp, 10);
      gradientFlags |= eGradient_Repeating;
    }

    if (tmp.LowerCaseEqualsLiteral("linear-gradient")) {
      if (!ParseLinearGradient(aValue, gradientFlags)) {
        return CSSParseResult::Error;
      }
      return CSSParseResult::Ok;
    }
    if (tmp.LowerCaseEqualsLiteral("radial-gradient")) {
      if (!ParseRadialGradient(aValue, gradientFlags)) {
        return CSSParseResult::Error;
      }
      return CSSParseResult::Ok;
    }
    if ((gradientFlags == eGradient_WebkitLegacy) &&
        tmp.LowerCaseEqualsLiteral("gradient")) {
      // Note: we check gradientFlags using '==' to select *exactly*
      // eGradient_WebkitLegacy -- and exclude eGradient_Repeating -- because
      // we don't want to accept -webkit-repeating-gradient() expressions.
      // (This is not a recognized syntax.)
      if (!ParseWebkitGradient(aValue)) {
        return CSSParseResult::Error;
      }
      return CSSParseResult::Ok;
    }

    if (ShouldUseUnprefixingService() &&
        !gradientFlags &&
        StringBeginsWith(tmp, NS_LITERAL_STRING("-webkit-"))) {
      // Copy 'tmp' into a string on the stack, since as soon as we
      // start parsing, its backing store (in "tk") will be overwritten
      nsAutoString prefixedFuncName(tmp);
      if (!ParseWebkitPrefixedGradientWithService(prefixedFuncName, aValue)) {
        return CSSParseResult::Error;
      }
      return CSSParseResult::Ok;
    }
  }
  if ((aVariantMask & VARIANT_IMAGE_RECT) != 0 &&
      eCSSToken_Function == tk->mType &&
      tk->mIdent.LowerCaseEqualsLiteral("-moz-image-rect")) {
    if (!ParseImageRect(aValue)) {
      return CSSParseResult::Error;
    }
    return CSSParseResult::Ok;
  }
  if ((aVariantMask & VARIANT_ELEMENT) != 0 &&
      eCSSToken_Function == tk->mType &&
      tk->mIdent.LowerCaseEqualsLiteral("-moz-element")) {
    if (!ParseElement(aValue)) {
      return CSSParseResult::Error;
    }
    return CSSParseResult::Ok;
  }
  if ((aVariantMask & VARIANT_COLOR) != 0) {
    if (mHashlessColorQuirk || // NONSTANDARD: Nav interprets 'xxyyzz' values even without '#' prefix
        (eCSSToken_ID == tk->mType) ||
        (eCSSToken_Hash == tk->mType) ||
        (eCSSToken_Ident == tk->mType) ||
        ((eCSSToken_Function == tk->mType) &&
         (tk->mIdent.LowerCaseEqualsLiteral("rgb") ||
          tk->mIdent.LowerCaseEqualsLiteral("hsl") ||
          tk->mIdent.LowerCaseEqualsLiteral("rgba") ||
          tk->mIdent.LowerCaseEqualsLiteral("hsla"))))
    {
      // Put token back so that parse color can get it
      UngetToken();
      return ParseColor(aValue);
    }
  }
  if (((aVariantMask & VARIANT_STRING) != 0) &&
      (eCSSToken_String == tk->mType)) {
    nsAutoString  buffer;
    buffer.Append(tk->mIdent);
    aValue.SetStringValue(buffer, eCSSUnit_String);
    return CSSParseResult::Ok;
  }
  if (((aVariantMask &
        (VARIANT_IDENTIFIER | VARIANT_IDENTIFIER_NO_INHERIT)) != 0) &&
      (eCSSToken_Ident == tk->mType) &&
      ((aVariantMask & VARIANT_IDENTIFIER) != 0 ||
       !(tk->mIdent.LowerCaseEqualsLiteral("inherit") ||
         tk->mIdent.LowerCaseEqualsLiteral("initial") ||
         (tk->mIdent.LowerCaseEqualsLiteral("unset") &&
          nsLayoutUtils::UnsetValueEnabled())))) {
    aValue.SetStringValue(tk->mIdent, eCSSUnit_Ident);
    return CSSParseResult::Ok;
  }
  if (((aVariantMask & VARIANT_COUNTER) != 0) &&
      (eCSSToken_Function == tk->mType) &&
      (tk->mIdent.LowerCaseEqualsLiteral("counter") ||
       tk->mIdent.LowerCaseEqualsLiteral("counters"))) {
    if (!ParseCounter(aValue)) {
      return CSSParseResult::Error;
    }
    return CSSParseResult::Ok;
  }
  if (((aVariantMask & VARIANT_ATTR) != 0) &&
      (eCSSToken_Function == tk->mType) &&
      tk->mIdent.LowerCaseEqualsLiteral("attr")) {
    if (!ParseAttr(aValue)) {
      SkipUntil(')');
      return CSSParseResult::Error;
    }
    return CSSParseResult::Ok;
  }
  if (((aVariantMask & VARIANT_TIMING_FUNCTION) != 0) &&
      (eCSSToken_Function == tk->mType)) {
    if (tk->mIdent.LowerCaseEqualsLiteral("cubic-bezier")) {
      if (!ParseTransitionTimingFunctionValues(aValue)) {
        SkipUntil(')');
        return CSSParseResult::Error;
      }
      return CSSParseResult::Ok;
    }
    if (tk->mIdent.LowerCaseEqualsLiteral("steps")) {
      if (!ParseTransitionStepTimingFunctionValues(aValue)) {
        SkipUntil(')');
        return CSSParseResult::Error;
      }
      return CSSParseResult::Ok;
    }
  }
  if ((aVariantMask & VARIANT_CALC) &&
      IsCSSTokenCalcFunction(*tk)) {
    // calc() currently allows only lengths and percents and number inside it.
    // And note that in current implementation, number cannot be mixed with
    // length and percent.
    if (!ParseCalc(aValue, aVariantMask & VARIANT_LPN)) {
      return CSSParseResult::Error;
    }
    return CSSParseResult::Ok;
  }

  UngetToken();
  AssertNextTokenAt(lineBefore, colBefore);
  return CSSParseResult::NotFound;
}

bool
CSSParserImpl::ParseCustomIdent(nsCSSValue& aValue,
                                const nsAutoString& aIdentValue,
                                const nsCSSKeyword aExcludedKeywords[],
                                const nsCSSProps::KTableEntry aPropertyKTable[])
{
  nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(aIdentValue);
  if (keyword == eCSSKeyword_UNKNOWN) {
    // Fast path for identifiers that are not known CSS keywords:
    aValue.SetStringValue(mToken.mIdent, eCSSUnit_Ident);
    return true;
  }
  if (keyword == eCSSKeyword_inherit ||
      keyword == eCSSKeyword_initial ||
      keyword == eCSSKeyword_unset ||
      keyword == eCSSKeyword_default ||
      (aPropertyKTable &&
        nsCSSProps::FindIndexOfKeyword(keyword, aPropertyKTable) >= 0)) {
    return false;
  }
  if (aExcludedKeywords) {
    for (uint32_t i = 0;; i++) {
      nsCSSKeyword excludedKeyword = aExcludedKeywords[i];
      if (excludedKeyword == eCSSKeyword_UNKNOWN) {
        break;
      }
      if (excludedKeyword == keyword) {
        return false;
      }
    }
  }
  aValue.SetStringValue(mToken.mIdent, eCSSUnit_Ident);
  return true;
}

bool
CSSParserImpl::ParseCounter(nsCSSValue& aValue)
{
  nsCSSUnit unit = (mToken.mIdent.LowerCaseEqualsLiteral("counter") ?
                    eCSSUnit_Counter : eCSSUnit_Counters);

  // A non-iterative for loop to break out when an error occurs.
  for (;;) {
    if (!GetToken(true)) {
      break;
    }
    if (eCSSToken_Ident != mToken.mType) {
      UngetToken();
      break;
    }

    RefPtr<nsCSSValue::Array> val =
      nsCSSValue::Array::Create(unit == eCSSUnit_Counter ? 2 : 3);

    val->Item(0).SetStringValue(mToken.mIdent, eCSSUnit_Ident);

    if (eCSSUnit_Counters == unit) {
      // must have a comma and then a separator string
      if (!ExpectSymbol(',', true) || !GetToken(true)) {
        break;
      }
      if (eCSSToken_String != mToken.mType) {
        UngetToken();
        break;
      }
      val->Item(1).SetStringValue(mToken.mIdent, eCSSUnit_String);
    }

    // get optional type
    int32_t typeItem = eCSSUnit_Counters == unit ? 2 : 1;
    nsCSSValue& type = val->Item(typeItem);
    if (ExpectSymbol(',', true)) {
      if (!ParseCounterStyleNameValue(type) && !ParseSymbols(type)) {
        break;
      }
    } else {
      type.SetStringValue(NS_LITERAL_STRING("decimal"), eCSSUnit_Ident);
    }

    if (!ExpectSymbol(')', true)) {
      break;
    }

    aValue.SetArrayValue(val, unit);
    return true;
  }

  SkipUntil(')');
  return false;
}

bool
CSSParserImpl::ParseAttr(nsCSSValue& aValue)
{
  if (!GetToken(true)) {
    return false;
  }

  nsAutoString attr;
  if (eCSSToken_Ident == mToken.mType) {  // attr name or namespace
    nsAutoString  holdIdent(mToken.mIdent);
    if (ExpectSymbol('|', false)) {  // namespace
      int32_t nameSpaceID = GetNamespaceIdForPrefix(holdIdent);
      if (nameSpaceID == kNameSpaceID_Unknown) {
        return false;
      }
      attr.AppendInt(nameSpaceID, 10);
      attr.Append(char16_t('|'));
      if (! GetToken(false)) {
        REPORT_UNEXPECTED_EOF(PEAttributeNameEOF);
        return false;
      }
      if (eCSSToken_Ident == mToken.mType) {
        attr.Append(mToken.mIdent);
      }
      else {
        REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected);
        UngetToken();
        return false;
      }
    }
    else {  // no namespace
      attr = holdIdent;
    }
  }
  else if (mToken.IsSymbol('*')) {  // namespace wildcard
    // Wildcard namespace makes no sense here and is not allowed
    REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected);
    UngetToken();
    return false;
  }
  else if (mToken.IsSymbol('|')) {  // explicit NO namespace
    if (! GetToken(false)) {
      REPORT_UNEXPECTED_EOF(PEAttributeNameEOF);
      return false;
    }
    if (eCSSToken_Ident == mToken.mType) {
      attr.Append(mToken.mIdent);
    }
    else {
      REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected);
      UngetToken();
      return false;
    }
  }
  else {
    REPORT_UNEXPECTED_TOKEN(PEAttributeNameOrNamespaceExpected);
    UngetToken();
    return false;
  }
  if (!ExpectSymbol(')', true)) {
    return false;
  }
  aValue.SetStringValue(attr, eCSSUnit_Attr);
  return true;
}

bool
CSSParserImpl::ParseSymbols(nsCSSValue& aValue)
{
  if (!GetToken(true)) {
    return false;
  }
  if (mToken.mType != eCSSToken_Function &&
      !mToken.mIdent.LowerCaseEqualsLiteral("symbols")) {
    UngetToken();
    return false;
  }

  RefPtr<nsCSSValue::Array> params = nsCSSValue::Array::Create(2);
  nsCSSValue& type = params->Item(0);
  nsCSSValue& symbols = params->Item(1);

  if (!ParseEnum(type, nsCSSProps::kCounterSymbolsSystemKTable)) {
    type.SetIntValue(NS_STYLE_COUNTER_SYSTEM_SYMBOLIC, eCSSUnit_Enumerated);
  }

  bool first = true;
  nsCSSValueList* item = symbols.SetListValue();
  for (;;) {
    // FIXME Should also include VARIANT_IMAGE. See bug 1071436.
    if (!ParseSingleTokenVariant(item->mValue, VARIANT_STRING, nullptr)) {
      break;
    }
    if (ExpectSymbol(')', true)) {
      if (first) {
        switch (type.GetIntValue()) {
          case NS_STYLE_COUNTER_SYSTEM_NUMERIC:
          case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC:
            // require at least two symbols
            return false;
        }
      }
      aValue.SetArrayValue(params, eCSSUnit_Symbols);
      return true;
    }
    item->mNext = new nsCSSValueList;
    item = item->mNext;
    first = false;
  }

  SkipUntil(')');
  return false;
}

bool
CSSParserImpl::SetValueToURL(nsCSSValue& aValue, const nsString& aURL)
{
  if (!mSheetPrincipal) {
    if (!mSheetPrincipalRequired) {
      /* Pretend to succeed.  */
      return true;
    }

    NS_NOTREACHED("Codepaths that expect to parse URLs MUST pass in an "
                  "origin principal");
    return false;
  }

  RefPtr<nsStringBuffer> buffer(nsCSSValue::BufferFromString(aURL));

  // Note: urlVal retains its own reference to |buffer|.
  mozilla::css::URLValue *urlVal =
    new mozilla::css::URLValue(buffer, mBaseURI, mSheetURI, mSheetPrincipal);
  aValue.SetURLValue(urlVal);
  return true;
}

/**
 * Parse the image-orientation property, which has the grammar:
 * <angle> flip? | flip | from-image
 */
bool
CSSParserImpl::ParseImageOrientation(nsCSSValue& aValue)
{
  if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT, nullptr)) {
    // 'inherit', 'initial' and 'unset' must be alone
    return true;
  }

  // Check for an angle with optional 'flip'.
  nsCSSValue angle;
  if (ParseSingleTokenVariant(angle, VARIANT_ANGLE, nullptr)) {
    nsCSSValue flip;

    if (ParseSingleTokenVariant(flip, VARIANT_KEYWORD,
                                nsCSSProps::kImageOrientationFlipKTable)) {
      RefPtr<nsCSSValue::Array> array = nsCSSValue::Array::Create(2);
      array->Item(0) = angle;
      array->Item(1) = flip;
      aValue.SetArrayValue(array, eCSSUnit_Array);
    } else {
      aValue = angle;
    }

    return true;
  }

  // The remaining possibilities (bare 'flip' and 'from-image') are both
  // keywords, so we can handle them at the same time.
  nsCSSValue keyword;
  if (ParseSingleTokenVariant(keyword, VARIANT_KEYWORD,
                              nsCSSProps::kImageOrientationKTable)) {
    aValue = keyword;
    return true;
  }

  // All possibilities failed.
  return false;
}

/**
 * Parse the arguments of -moz-image-rect() function.
 * -moz-image-rect(<uri>, <top>, <right>, <bottom>, <left>)
 */
bool
CSSParserImpl::ParseImageRect(nsCSSValue& aImage)
{
  // A non-iterative for loop to break out when an error occurs.
  for (;;) {
    nsCSSValue newFunction;
    static const uint32_t kNumArgs = 5;
    nsCSSValue::Array* func =
      newFunction.InitFunction(eCSSKeyword__moz_image_rect, kNumArgs);

    // func->Item(0) is reserved for the function name.
    nsCSSValue& url    = func->Item(1);
    nsCSSValue& top    = func->Item(2);
    nsCSSValue& right  = func->Item(3);
    nsCSSValue& bottom = func->Item(4);
    nsCSSValue& left   = func->Item(5);

    nsAutoString urlString;
    if (!ParseURLOrString(urlString) ||
        !SetValueToURL(url, urlString) ||
        !ExpectSymbol(',', true)) {
      break;
    }

    static const int32_t VARIANT_SIDE = VARIANT_NUMBER | VARIANT_PERCENT;
    if (!ParseSingleTokenNonNegativeVariant(top, VARIANT_SIDE, nullptr) ||
        !ExpectSymbol(',', true) ||
        !ParseSingleTokenNonNegativeVariant(right, VARIANT_SIDE, nullptr) ||
        !ExpectSymbol(',', true) ||
        !ParseSingleTokenNonNegativeVariant(bottom, VARIANT_SIDE, nullptr) ||
        !ExpectSymbol(',', true) ||
        !ParseSingleTokenNonNegativeVariant(left, VARIANT_SIDE, nullptr) ||
        !ExpectSymbol(')', true))
      break;

    aImage = newFunction;
    return true;
  }

  SkipUntil(')');
  return false;
}

// <element>: -moz-element(# <element_id> )
bool
CSSParserImpl::ParseElement(nsCSSValue& aValue)
{
  // A non-iterative for loop to break out when an error occurs.
  for (;;) {
    if (!GetToken(true))
      break;

    if (mToken.mType == eCSSToken_ID) {
      aValue.SetStringValue(mToken.mIdent, eCSSUnit_Element);
    } else {
      UngetToken();
      break;
    }

    if (!ExpectSymbol(')', true))
      break;

    return true;
  }

  // If we detect a syntax error, we must match the opening parenthesis of the
  // function with the closing parenthesis and skip all the tokens in between.
  SkipUntil(')');
  return false;
}

// flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
bool
CSSParserImpl::ParseFlex()
{
  // First check for inherit / initial / unset
  nsCSSValue tmpVal;
  if (ParseSingleTokenVariant(tmpVal, VARIANT_INHERIT, nullptr)) {
    AppendValue(eCSSProperty_flex_grow, tmpVal);
    AppendValue(eCSSProperty_flex_shrink, tmpVal);
    AppendValue(eCSSProperty_flex_basis, tmpVal);
    return true;
  }

  // Next, check for 'none' == '0 0 auto'
  if (ParseSingleTokenVariant(tmpVal, VARIANT_NONE, nullptr)) {
    AppendValue(eCSSProperty_flex_grow, nsCSSValue(0.0f, eCSSUnit_Number));
    AppendValue(eCSSProperty_flex_shrink, nsCSSValue(0.0f, eCSSUnit_Number));
    AppendValue(eCSSProperty_flex_basis, nsCSSValue(eCSSUnit_Auto));
    return true;
  }

  // OK, try parsing our value as individual per-subproperty components:
  //   [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]

  // Each subproperty has a default value that it takes when it's omitted in a
  // "flex" shorthand value. These default values are *only* for the shorthand
  // syntax -- they're distinct from the subproperties' own initial values.  We
  // start with each subproperty at its default, as if we had "flex: 1 1 0%".
  nsCSSValue flexGrow(1.0f, eCSSUnit_Number);
  nsCSSValue flexShrink(1.0f, eCSSUnit_Number);
  nsCSSValue flexBasis(0.0f, eCSSUnit_Percent);

  // OVERVIEW OF PARSING STRATEGY:
  // =============================
  // a) Parse the first component as either flex-basis or flex-grow.
  // b) If it wasn't flex-grow, parse the _next_ component as flex-grow.
  // c) Now we've just parsed flex-grow -- so try parsing the next thing as
  //    flex-shrink.
  // d) Finally: If we didn't get flex-basis at the beginning, try to parse
  //    it now, at the end.
  //
  // More details in each section below.

  uint32_t flexBasisVariantMask =
    (nsCSSProps::ParserVariant(eCSSProperty_flex_basis) & ~(VARIANT_INHERIT));

  // (a) Parse first component. It can be either be a 'flex-basis' value or a
  // 'flex-grow' value, so we use the flex-basis-specific variant mask, along
  //  with VARIANT_NUMBER to accept 'flex-grow' values.
  //
  // NOTE: if we encounter unitless 0 here, we *must* interpret it as a
  // 'flex-grow' value (a number), *not* as a 'flex-basis' value (a length).
  // Conveniently, that's the behavior this combined variant-mask gives us --
  // it'll treat unitless 0 as a number. The flexbox spec requires this:
  // "a unitless zero that is not already preceded by two flex factors must be
  //  interpreted as a flex factor.
  if (ParseNonNegativeVariant(tmpVal, flexBasisVariantMask | VARIANT_NUMBER,
                              nsCSSProps::kWidthKTable) != CSSParseResult::Ok) {
    // First component was not a valid flex-basis or flex-grow value. Fail.
    return false;
  }

  // Record what we just parsed as either flex-basis or flex-grow:
  bool wasFirstComponentFlexBasis = (tmpVal.GetUnit() != eCSSUnit_Number);
  (wasFirstComponentFlexBasis ? flexBasis : flexGrow) = tmpVal;

  // (b) If we didn't get flex-grow yet, parse _next_ component as flex-grow.
  bool doneParsing = false;
  if (wasFirstComponentFlexBasis) {
    if (ParseNonNegativeNumber(tmpVal)) {
      flexGrow = tmpVal;
    } else {
      // Failed to parse anything after our flex-basis -- that's fine. We can
      // skip the remaining parsing.
      doneParsing = true;
    }
  }

  if (!doneParsing) {
    // (c) OK -- the last thing we parsed was flex-grow, so look for a
    //     flex-shrink in the next position.
    if (ParseNonNegativeNumber(tmpVal)) {
      flexShrink = tmpVal;
    }

    // d) Finally: If we didn't get flex-basis at the beginning, try to parse
    //    it now, at the end.
    //
    // NOTE: If we encounter unitless 0 in this final position, we'll parse it
    // as a 'flex-basis' value.  That's OK, because we know it must have
    // been "preceded by 2 flex factors" (justification below), which gets us
    // out of the spec's requirement of otherwise having to treat unitless 0
    // as a flex factor.
    //
    // JUSTIFICATION: How do we know that a unitless 0 here must have been
    // preceded by 2 flex factors? Well, suppose we had a unitless 0 that
    // was preceded by only 1 flex factor.  Then, we would have already
    // accepted this unitless 0 as the 'flex-shrink' value, up above (since
    // it's a valid flex-shrink value), and we'd have moved on to the next
    // token (if any). And of course, if we instead had a unitless 0 preceded
    // by *no* flex factors (if it were the first token), we would've already
    // parsed it in our very first call to ParseNonNegativeVariant().  So, any
    // unitless 0 encountered here *must* have been preceded by 2 flex factors.
    if (!wasFirstComponentFlexBasis) {
      CSSParseResult result =
        ParseNonNegativeVariant(tmpVal, flexBasisVariantMask,
                                nsCSSProps::kWidthKTable);
      if (result == CSSParseResult::Error) {
        return false;
      } else if (result == CSSParseResult::Ok) {
        flexBasis = tmpVal;
      }
    }
  }

  AppendValue(eCSSProperty_flex_grow,   flexGrow);
  AppendValue(eCSSProperty_flex_shrink, flexShrink);
  AppendValue(eCSSProperty_flex_basis,  flexBasis);

  return true;
}

// flex-flow: <flex-direction> || <flex-wrap>
bool
CSSParserImpl::ParseFlexFlow()
{
  static const nsCSSPropertyID kFlexFlowSubprops[] = {
    eCSSProperty_flex_direction,
    eCSSProperty_flex_wrap
  };
  const size_t numProps = MOZ_ARRAY_LENGTH(kFlexFlowSubprops);
  nsCSSValue values[numProps];

  int32_t found = ParseChoice(values, kFlexFlowSubprops, numProps);

  // Bail if we didn't successfully parse anything
  if (found < 1) {
    return false;
  }

  // If either property didn't get an explicit value, use its initial value.
  if ((found & 1) == 0) {
    values[0].SetIntValue(NS_STYLE_FLEX_DIRECTION_ROW, eCSSUnit_Enumerated);
  }
  if ((found & 2) == 0) {
    values[1].SetIntValue(NS_STYLE_FLEX_WRAP_NOWRAP, eCSSUnit_Enumerated);
  }

  // Store these values and declare success!
  for (size_t i = 0; i < numProps; i++) {
    AppendValue(kFlexFlowSubprops[i], values[i]);
  }
  return true;
}

bool
CSSParserImpl::ParseGridAutoFlow()
{
  nsCSSValue value;
  if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
    AppendValue(eCSSProperty_grid_auto_flow, value);
    return true;
  }

  static const int32_t mask[] = {
    NS_STYLE_GRID_AUTO_FLOW_ROW | NS_STYLE_GRID_AUTO_FLOW_COLUMN,
    MASK_END_VALUE
  };
  if (!ParseBitmaskValues(value, nsCSSProps::kGridAutoFlowKTable, mask)) {
    return false;
  }
  int32_t bitField = value.GetIntValue();

  // If neither row nor column is provided, row is assumed.
  if (!(bitField & (NS_STYLE_GRID_AUTO_FLOW_ROW |
                    NS_STYLE_GRID_AUTO_FLOW_COLUMN))) {
    value.SetIntValue(bitField | NS_STYLE_GRID_AUTO_FLOW_ROW,
                      eCSSUnit_Enumerated);
  }

  AppendValue(eCSSProperty_grid_auto_flow, value);
  return true;
}

static const nsCSSKeyword kGridLineKeywords[] = {
  eCSSKeyword_span,
  eCSSKeyword_UNKNOWN  // End-of-array marker
};

CSSParseResult
CSSParserImpl::ParseGridLineNames(nsCSSValue& aValue)
{
  if (!ExpectSymbol('[', true)) {
    return CSSParseResult::NotFound;
  }
  if (!GetToken(true) || mToken.IsSymbol(']')) {
    return CSSParseResult::Ok;
  }
  // 'return' so far leaves aValue untouched, to represent an empty list.

  nsCSSValueList* item;
  if (aValue.GetUnit() == eCSSUnit_List) {
    // Find the end of an existing list.
    // The grid-template shorthand uses this, at most once for a given list.

    // NOTE: we could avoid this traversal by somehow keeping around
    // a pointer to the last item from the previous call.
    // It's not yet clear if this is worth the additional code complexity.
    item = aValue.GetListValue();
    while (item->mNext) {
      item = item->mNext;
    }
    item->mNext = new nsCSSValueList;
    item = item->mNext;
  } else {
    MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Null, "Unexpected unit");
    item = aValue.SetListValue();
  }
  for (;;) {
    if (!(eCSSToken_Ident == mToken.mType &&
          ParseCustomIdent(item->mValue, mToken.mIdent, kGridLineKeywords))) {
      UngetToken();
      SkipUntil(']');
      return CSSParseResult::Error;
    }
    if (!GetToken(true) || mToken.IsSymbol(']')) {
      return CSSParseResult::Ok;
    }
    item->mNext = new nsCSSValueList;
    item = item->mNext;
  }
}

// Assuming the 'repeat(' function token has already been consumed,
// parse the rest of repeat(<positive-integer> | auto-fill, <line-names>+)
// Append to the linked list whose end is given by |aTailPtr|,
// and update |aTailPtr| to point to the new end of the list.
bool
CSSParserImpl::ParseGridLineNameListRepeat(nsCSSValueList** aTailPtr)
{
  int32_t repetitions;
  Maybe<int32_t> repeatAutoEnum;
  if (!ParseGridTrackRepeatIntro(true, &repetitions, &repeatAutoEnum)) {
    return false;
  }
  if (repeatAutoEnum.isSome()) {
    // Parse exactly one <line-names>.
    nsCSSValue listValue;
    nsCSSValueList* list = listValue.SetListValue();
    if (ParseGridLineNames(list->mValue) != CSSParseResult::Ok) {
      return false;
    }
    if (!ExpectSymbol(')', true)) {
      return false;
    }
    // Instead of hooking up this list into the flat name list as usual,
    // we create a pair(Int, List) where the first value is the auto-fill
    // keyword and the second is the name list to repeat.
    nsCSSValue kwd;
    kwd.SetIntValue(repeatAutoEnum.value(), eCSSUnit_Enumerated);
    *aTailPtr = (*aTailPtr)->mNext = new nsCSSValueList;
    (*aTailPtr)->mValue.SetPairValue(kwd, listValue);
    return true;
  }

  // Parse at least one <line-names>
  nsCSSValueList* tail = *aTailPtr;
  do {
    tail->mNext = new nsCSSValueList;
    tail = tail->mNext;
    if (ParseGridLineNames(tail->mValue) != CSSParseResult::Ok) {
      return false;
    }
  } while (!ExpectSymbol(')', true));
  nsCSSValueList* firstRepeatedItem = (*aTailPtr)->mNext;
  nsCSSValueList* lastRepeatedItem = tail;

  // Our repeated items are already in the target list once,
  // so they need to be repeated |repetitions - 1| more times.
  MOZ_ASSERT(repetitions > 0, "Expected positive repetitions");
  while (--repetitions) {
    nsCSSValueList* repeatedItem = firstRepeatedItem;
    for (;;) {
      tail->mNext = new nsCSSValueList;
      tail = tail->mNext;
      tail->mValue = repeatedItem->mValue;
      if (repeatedItem == lastRepeatedItem) {
        break;
      }
      repeatedItem = repeatedItem->mNext;
    }
  }
  *aTailPtr = tail;
  return true;
}

// Assuming a 'subgrid' keyword was already consumed, parse <line-name-list>?
bool
CSSParserImpl::ParseOptionalLineNameListAfterSubgrid(nsCSSValue& aValue)
{
  nsCSSValueList* item = aValue.SetListValue();
  // This marker distinguishes the value from a <track-list>.
  item->mValue.SetIntValue(NS_STYLE_GRID_TEMPLATE_SUBGRID,
                           eCSSUnit_Enumerated);
  bool haveRepeatAuto = false;
  for (;;) {
    // First try to parse <name-repeat>, i.e.
    // repeat(<positive-integer> | auto-fill, <line-names>+)
    if (!GetToken(true)) {
      return true;
    }
    if (mToken.mType == eCSSToken_Function &&
        mToken.mIdent.LowerCaseEqualsLiteral("repeat")) {
      nsCSSValueList* startOfRepeat = item;
      if (!ParseGridLineNameListRepeat(&item)) {
        SkipUntil(')');
        return false;
      }
      if (startOfRepeat->mNext->mValue.GetUnit() == eCSSUnit_Pair) {
        if (haveRepeatAuto) {
          REPORT_UNEXPECTED(PEMoreThanOneGridRepeatAutoFillInNameList);
          return false;
        }
        haveRepeatAuto = true;
      }
    } else {
      UngetToken();

      // This was not a repeat() function. Try to parse <line-names>.
      nsCSSValue lineNames;
      CSSParseResult result = ParseGridLineNames(lineNames);
      if (result == CSSParseResult::NotFound) {
        return true;
      }
      if (result == CSSParseResult::Error) {
        return false;
      }
      item->mNext = new nsCSSValueList;
      item = item->mNext;
      item->mValue = lineNames;
    }
  }
}

CSSParseResult
CSSParserImpl::ParseGridTrackBreadth(nsCSSValue& aValue)
{
  CSSParseResult result = ParseNonNegativeVariant(aValue,
                            VARIANT_AUTO | VARIANT_LPCALC | VARIANT_KEYWORD,
                            nsCSSProps::kGridTrackBreadthKTable);

  if (result == CSSParseResult::Ok ||
      result == CSSParseResult::Error) {
    return result;
  }

  // Attempt to parse <flex> (a dimension with the "fr" unit).
  if (!GetToken(true)) {
    return CSSParseResult::NotFound;
  }
  if (!(eCSSToken_Dimension == mToken.mType &&
        mToken.mIdent.LowerCaseEqualsLiteral("fr") &&
        mToken.mNumber >= 0)) {
    UngetToken();
    return CSSParseResult::NotFound;
  }
  aValue.SetFloatValue(mToken.mNumber, eCSSUnit_FlexFraction);
  return CSSParseResult::Ok;
}

// Parse a <track-size>, or <fixed-size> when aFlags has eFixedTrackSize.
CSSParseResult
CSSParserImpl::ParseGridTrackSize(nsCSSValue& aValue,
                                  GridTrackSizeFlags aFlags)
{
  const bool requireFixedSize =
    !!(aFlags & GridTrackSizeFlags::eFixedTrackSize);
  // Attempt to parse a single <track-breadth>.
  CSSParseResult result = ParseGridTrackBreadth(aValue);
  if (requireFixedSize && result == CSSParseResult::Ok &&
      !aValue.IsLengthPercentCalcUnit()) {
    result = CSSParseResult::Error;
  }
  if (result == CSSParseResult::Error) {
    return result;
  }
  if (result == CSSParseResult::Ok) {
    if (aValue.GetUnit() == eCSSUnit_FlexFraction) {
      // Single value <flex> is represented internally as minmax(auto, <flex>).
      nsCSSValue minmax;
      nsCSSValue::Array* func = minmax.InitFunction(eCSSKeyword_minmax, 2);
      func->Item(1).SetAutoValue();
      func->Item(2) = aValue;
      aValue = minmax;
    }
    return result;
  }

  // Attempt to parse a minmax() or fit-content() function.
  if (!GetToken(true)) {
    return CSSParseResult::NotFound;
  }
  if (eCSSToken_Function != mToken.mType) {
    UngetToken();
    return CSSParseResult::NotFound;
  }
  if (mToken.mIdent.LowerCaseEqualsLiteral("fit-content")) {
    if (requireFixedSize) {
      UngetToken();
      return CSSParseResult::Error;
    }
    nsCSSValue::Array* func = aValue.InitFunction(eCSSKeyword_fit_content, 1);
    if (ParseGridTrackBreadth(func->Item(1)) == CSSParseResult::Ok &&
        func->Item(1).IsLengthPercentCalcUnit() &&
        ExpectSymbol(')', true)) {
      return CSSParseResult::Ok;
    }
    SkipUntil(')');
    return CSSParseResult::Error;
  }
  if (!mToken.mIdent.LowerCaseEqualsLiteral("minmax")) {
    UngetToken();
    return CSSParseResult::NotFound;
  }
  nsCSSValue::Array* func = aValue.InitFunction(eCSSKeyword_minmax, 2);
  if (ParseGridTrackBreadth(func->Item(1)) == CSSParseResult::Ok &&
      ExpectSymbol(',', true) &&
      ParseGridTrackBreadth(func->Item(2)) == CSSParseResult::Ok &&
      ExpectSymbol(')', true)) {
    if (requireFixedSize &&
        !func->Item(1).IsLengthPercentCalcUnit() &&
        !func->Item(2).IsLengthPercentCalcUnit()) {
      return CSSParseResult::Error;
    }
    // Reject <flex> min-sizing.
    if (func->Item(1).GetUnit() == eCSSUnit_FlexFraction) {
      return CSSParseResult::Error;
    }
    return CSSParseResult::Ok;
  }
  SkipUntil(')');
  return CSSParseResult::Error;
}

bool
CSSParserImpl::ParseGridAutoColumnsRows(nsCSSPropertyID aPropID)
{
  nsCSSValue value;
  if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr) ||
      ParseGridTrackSize(value) == CSSParseResult::Ok) {
    AppendValue(aPropID, value);
    return true;
  }
  return false;
}

bool
CSSParserImpl::ParseGridTrackListWithFirstLineNames(nsCSSValue& aValue,
                                                    const nsCSSValue& aFirstLineNames,
                                                    GridTrackListFlags aFlags)
{
  nsCSSValueList* firstLineNamesItem = aValue.SetListValue();
  firstLineNamesItem->mValue = aFirstLineNames;

  // This function is trying to parse <track-list>, which is
  //   [ <line-names>? [ <track-size> | <repeat()> ] ]+ <line-names>?
  // and we're already past the first "<line-names>?".
  // If aFlags contains eExplicitTrackList then <repeat()> is disallowed.
  //
  // Each iteration of the following loop attempts to parse either a
  // repeat() or a <track-size> expression, and then an (optional)
  // <line-names> expression.
  //
  // The only successful exit point from this loop is the ::NotFound
  // case after ParseGridTrackSize(); i.e. we'll greedily parse
  // repeat()/<track-size> until we can't find one.
  nsCSSValueList* item = firstLineNamesItem;
  bool haveRepeatAuto = false;
  for (;;) {
    // First try to parse repeat()
    if (!GetToken(true)) {
      break;
    }
    if (!(aFlags & GridTrackListFlags::eExplicitTrackList) &&
        mToken.mType == eCSSToken_Function &&
        mToken.mIdent.LowerCaseEqualsLiteral("repeat")) {
      nsCSSValueList* startOfRepeat = item;
      if (!ParseGridTrackListRepeat(&item)) {
        SkipUntil(')');
        return false;
      }
      auto firstRepeat = startOfRepeat->mNext;
      if (firstRepeat->mValue.GetUnit() == eCSSUnit_Pair) {
        if (haveRepeatAuto) {
          REPORT_UNEXPECTED(PEMoreThanOneGridRepeatAutoFillFitInTrackList);
          return false;
        }
        haveRepeatAuto = true;
        // We're parsing an <auto-track-list>, which requires that all tracks
        // are <fixed-size>, so we need to check the ones we've parsed already.
        for (nsCSSValueList* list = firstLineNamesItem->mNext;
             list != firstRepeat; list = list->mNext) {
          if (list->mValue.GetUnit() == eCSSUnit_Function) {
            nsCSSValue::Array* func = list->mValue.GetArrayValue();
            auto funcName = func->Item(0).GetKeywordValue();
            if (funcName == eCSSKeyword_minmax) {
              if (!func->Item(1).IsLengthPercentCalcUnit() &&
                  !func->Item(2).IsLengthPercentCalcUnit()) {
                return false;
              }
            } else {
              MOZ_ASSERT(funcName == eCSSKeyword_fit_content,
                         "Expected minmax() or fit-content() function");
              return false; // fit-content() is not a <fixed-size>
            }
          } else if (!list->mValue.IsLengthPercentCalcUnit()) {
            return false;
          }
          list = list->mNext; // skip line names
        }
      }
    } else {
      UngetToken();

      // Not a repeat() function; try to parse <track-size> | <fixed-size>.
      nsCSSValue trackSize;
      GridTrackSizeFlags flags = haveRepeatAuto
        ? GridTrackSizeFlags::eFixedTrackSize
        : GridTrackSizeFlags::eDefaultTrackSize;
      CSSParseResult result = ParseGridTrackSize(trackSize, flags);
      if (result == CSSParseResult::Error) {
        return false;
      }
      if (result == CSSParseResult::NotFound) {
        // What we've parsed so far is a valid <track-list>
        // (modulo the "at least one <track-size>" check below.)
        // Stop here.
        break;
      }
      item->mNext = new nsCSSValueList;
      item = item->mNext;
      item->mValue = trackSize;

      item->mNext = new nsCSSValueList;
      item = item->mNext;
    }
    if (ParseGridLineNames(item->mValue) == CSSParseResult::Error) {
      return false;
    }
  }

  // Require at least one <track-size>.
  if (item == firstLineNamesItem) {
    return false;
  }

  MOZ_ASSERT(aValue.GetListValue() &&
             aValue.GetListValue()->mNext &&
             aValue.GetListValue()->mNext->mNext,
             "<track-list> should have a minimum length of 3");
  return true;
}

// Takes ownership of |aSecond|
static void
ConcatLineNames(nsCSSValue& aFirst, nsCSSValue& aSecond)
{
  if (aSecond.GetUnit() == eCSSUnit_Null) {
    // Nothing to do.
    return;
  }
  if (aFirst.GetUnit() == eCSSUnit_Null) {
    // Empty or omitted <line-names>. Replace it.
    aFirst = aSecond;
    return;
  }

  // Join the two <line-names> lists.
  nsCSSValueList* source = aSecond.GetListValue();
  nsCSSValueList* target = aFirst.GetListValue();
  // Find the end:
  while (target->mNext) {
    target = target->mNext;
  }
  // Copy the first name. We can't take ownership of it
  // as it'll be destroyed when |aSecond| goes out of scope.
  target->mNext = new nsCSSValueList;
  target = target->mNext;
  target->mValue = source->mValue;
  // Move the rest of the linked list.
  target->mNext = source->mNext;
  source->mNext = nullptr;
}

// Assuming the 'repeat(' function token has already been consumed,
// parse "repeat( <positive-integer> | auto-fill | auto-fit ,"
// (or "repeat( <positive-integer> | auto-fill ," when aForSubgrid is true)
// and stop after the comma.  Return true when parsing succeeds,
// with aRepetitions set to the number of repetitions and aRepeatAutoEnum set
// to an enum value for auto-fill | auto-fit (it's not set at all when
// <positive-integer> was parsed).
bool
CSSParserImpl::ParseGridTrackRepeatIntro(bool            aForSubgrid,
                                         int32_t*        aRepetitions,
                                         Maybe<int32_t>* aRepeatAutoEnum)
{
  if (!GetToken(true)) {
    return false;
  }
  if (mToken.mType == eCSSToken_Ident) {
    if (mToken.mIdent.LowerCaseEqualsLiteral("auto-fill")) {
      aRepeatAutoEnum->emplace(NS_STYLE_GRID_REPEAT_AUTO_FILL);
    } else if (!aForSubgrid &&
               mToken.mIdent.LowerCaseEqualsLiteral("auto-fit")) {
      aRepeatAutoEnum->emplace(NS_STYLE_GRID_REPEAT_AUTO_FIT);
    } else {
      return false;
    }
    *aRepetitions = 1;
  } else if (mToken.mType == eCSSToken_Number) {
    if (!(mToken.mIntegerValid &&
          mToken.mInteger > 0)) {
      return false;
    }
    *aRepetitions = std::min(mToken.mInteger, GRID_TEMPLATE_MAX_REPETITIONS);
  } else {
    return false;
  }

  if (!ExpectSymbol(',', true)) {
    return false;
  }
  return true;
}

// Assuming the 'repeat(' function token has already been consumed,
// parse the rest of
// repeat( <positive-integer> | auto-fill | auto-fit ,
//         [ <line-names>? <track-size> ]+ <line-names>? )
// Append to the linked list whose end is given by |aTailPtr|,
// and update |aTailPtr| to point to the new end of the list.
// Note: only one <track-size> is allowed for auto-fill/fit
bool
CSSParserImpl::ParseGridTrackListRepeat(nsCSSValueList** aTailPtr)
{
  int32_t repetitions;
  Maybe<int32_t> repeatAutoEnum;
  if (!ParseGridTrackRepeatIntro(false, &repetitions, &repeatAutoEnum)) {
    return false;
  }

  // Parse [ <line-names>? <track-size> ]+ <line-names>?
  // but keep the first and last <line-names> separate
  // because they'll need to be joined.
  // http://dev.w3.org/csswg/css-grid/#repeat-notation
  nsCSSValue firstLineNames;
  nsCSSValue trackSize;
  nsCSSValue lastLineNames;
  // Optional
  if (ParseGridLineNames(firstLineNames) == CSSParseResult::Error) {
    return false;
  }
  // Required
  GridTrackSizeFlags flags = repeatAutoEnum.isSome()
    ? GridTrackSizeFlags::eFixedTrackSize
    : GridTrackSizeFlags::eDefaultTrackSize;
  if (ParseGridTrackSize(trackSize, flags) != CSSParseResult::Ok) {
    return false;
  }
  // Use nsAutoPtr to free the list in case of early return.
  nsAutoPtr<nsCSSValueList> firstTrackSizeItemAuto(new nsCSSValueList);
  firstTrackSizeItemAuto->mValue = trackSize;

  nsCSSValueList* item = firstTrackSizeItemAuto;
  for (;;) {
    // Optional
    if (ParseGridLineNames(lastLineNames) == CSSParseResult::Error) {
      return false;
    }

    if (ExpectSymbol(')', true)) {
      break;
    }

    // <auto-repeat> only accepts a single track size:
    // <line-names>? <fixed-size> <line-names>?
    if (repeatAutoEnum.isSome()) {
      REPORT_UNEXPECTED(PEMoreThanOneGridRepeatTrackSize);
      return false;
    }

    // Required
    if (ParseGridTrackSize(trackSize) != CSSParseResult::Ok) {
      return false;
    }

    item->mNext = new nsCSSValueList;
    item = item->mNext;
    item->mValue = lastLineNames;
    // Do not append to this list at the next iteration.
    lastLineNames.Reset();

    item->mNext = new nsCSSValueList;
    item = item->mNext;
    item->mValue = trackSize;
  }
  nsCSSValueList* lastTrackSizeItem = item;

  // [ <line-names>? <track-size> ]+ <line-names>?  is now parsed into:
  // * firstLineNames: the first <line-names>
  // * a linked list of odd length >= 1, from firstTrackSizeItem
  //   (the first <track-size>) to lastTrackSizeItem (the last),
  //   with the <line-names> sublists in between
  // * lastLineNames: the last <line-names>

  if (repeatAutoEnum.isSome()) {
    // Instead of hooking up this list into the flat track/name list as usual,
    // we create a pair(Int, List) where the first value is the auto-fill/fit
    // keyword and the second is the list to repeat.  There are three items
    // in this list, the first is the list of line names before the track size,
    // the second item is the track size, and the last item is the list of line
    // names after the track size.  Note that the line names are NOT merged
    // with any line names before/after the repeat() itself.
    nsCSSValue listValue;
    nsCSSValueList* list = listValue.SetListValue();
    list->mValue = firstLineNames;
    list = list->mNext = new nsCSSValueList;
    list->mValue = trackSize;
    list = list->mNext = new nsCSSValueList;
    list->mValue = lastLineNames;
    nsCSSValue kwd;
    kwd.SetIntValue(repeatAutoEnum.value(), eCSSUnit_Enumerated);
    *aTailPtr = (*aTailPtr)->mNext = new nsCSSValueList;
    (*aTailPtr)->mValue.SetPairValue(kwd, listValue);
    // Append an empty list since the caller expects that to represent the names
    // that follows the repeat() function.
    *aTailPtr = (*aTailPtr)->mNext = new nsCSSValueList;
    return true;
  }

  // Join the last and first <line-names> (in that order.)
  // For example, repeat(3, (a) 100px (b) 200px (c)) results in
  // (a) 100px (b) 200px (c a) 100px (b) 200px (c a) 100px (b) 200px (c)
  // This is (c a).
  // Make deep copies: the originals will be moved.
  nsCSSValue joinerLineNames;
  {
    nsCSSValueList* target = nullptr;
    if (lastLineNames.GetUnit() != eCSSUnit_Null) {
      target = joinerLineNames.SetListValue();
      nsCSSValueList* source = lastLineNames.GetListValue();
      for (;;) {
        target->mValue = source->mValue;
        source = source->mNext;
        if (!source) {
          break;
        }
        target->mNext = new nsCSSValueList;
        target = target->mNext;
      }
    }

    if (firstLineNames.GetUnit() != eCSSUnit_Null) {
      if (target) {
        target->mNext = new nsCSSValueList;
        target = target->mNext;
      } else {
        target = joinerLineNames.SetListValue();
      }
      nsCSSValueList* source = firstLineNames.GetListValue();
      for (;;) {
        target->mValue = source->mValue;
        source = source->mNext;
        if (!source) {
          break;
        }
        target->mNext = new nsCSSValueList;
        target = target->mNext;
      }
    }
  }

  // Join our first <line-names> with the one before repeat().
  // (a) repeat(1, (b) 20px) expands to (a b) 20px
  nsCSSValueList* previousItemBeforeRepeat = *aTailPtr;
  ConcatLineNames(previousItemBeforeRepeat->mValue, firstLineNames);

  // Move our linked list
  // (first to last <track-size>, with the <line-names> sublists in between).
  // This is the first repetition.
  NS_ASSERTION(previousItemBeforeRepeat->mNext == nullptr,
               "Expected the end of a linked list");
  previousItemBeforeRepeat->mNext = firstTrackSizeItemAuto.forget();
  nsCSSValueList* firstTrackSizeItem = previousItemBeforeRepeat->mNext;
  nsCSSValueList* tail = lastTrackSizeItem;

  // Repeat |repetitions - 1| more times:
  // * the joiner <line-names>
  // * the linked list
  //   (first to last <track-size>, with the <line-names> sublists in between)
  MOZ_ASSERT(repetitions > 0, "Expected positive repetitions");
  while (--repetitions) {
    tail->mNext = new nsCSSValueList;
    tail = tail->mNext;
    tail->mValue = joinerLineNames;

    nsCSSValueList* repeatedItem = firstTrackSizeItem;
    for (;;) {
      tail->mNext = new nsCSSValueList;
      tail = tail->mNext;
      tail->mValue = repeatedItem->mValue;
      if (repeatedItem == lastTrackSizeItem) {
        break;
      }
      repeatedItem = repeatedItem->mNext;
    }
  }

  // Finally, move our last <line-names>.
  // Any <line-names> immediately after repeat() will append to it.
  tail->mNext = new nsCSSValueList;
  tail = tail->mNext;
  tail->mValue = lastLineNames;

  *aTailPtr = tail;
  return true;
}

bool
CSSParserImpl::ParseGridTrackList(nsCSSPropertyID aPropID,
                                  GridTrackListFlags aFlags)
{
  nsCSSValue value;
  nsCSSValue firstLineNames;
  if (ParseGridLineNames(firstLineNames) == CSSParseResult::Error ||
      !ParseGridTrackListWithFirstLineNames(value, firstLineNames, aFlags)) {
    return false;
  }
  AppendValue(aPropID, value);
  return true;
}

bool
CSSParserImpl::ParseGridTemplateColumnsRows(nsCSSPropertyID aPropID)
{
  nsCSSValue value;
  if (ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE, nullptr)) {
    AppendValue(aPropID, value);
    return true;
  }

  nsSubstring* ident = NextIdent();
  if (ident) {
    if (ident->LowerCaseEqualsLiteral("subgrid")) {
      if (!nsLayoutUtils::IsGridTemplateSubgridValueEnabled()) {
        REPORT_UNEXPECTED(PESubgridNotSupported);
        return false;
      }
      if (!ParseOptionalLineNameListAfterSubgrid(value)) {
        return false;
      }
      AppendValue(aPropID, value);
      return true;
    }
    UngetToken();
  }

  return ParseGridTrackList(aPropID);
}

bool
CSSParserImpl::ParseGridTemplateAreasLine(const nsAutoString& aInput,
                                          css::GridTemplateAreasValue* aAreas,
                                          nsDataHashtable<nsStringHashKey, uint32_t>& aAreaIndices)
{
  aAreas->mTemplates.AppendElement(mToken.mIdent);

  nsCSSGridTemplateAreaScanner scanner(aInput);
  nsCSSGridTemplateAreaToken token;
  css::GridNamedArea* currentArea = nullptr;
  uint32_t row = aAreas->NRows();
  // Column numbers starts at 1, but we might not have any, eg
  // grid-template-areas:""; which will result in mNColumns == 0.
  uint32_t column = 0;
  while (scanner.Next(token)) {
    ++column;
    if (token.isTrash) {
      return false;
    }
    if (currentArea) {
      if (token.mName == currentArea->mName) {
        if (currentArea->mRowStart == row) {
          // Next column in the first row of this named area.
          currentArea->mColumnEnd++;
        }
        continue;
      }
      // We're exiting |currentArea|, so currentArea is ending at |column|.
      // Make sure that this is consistent with currentArea on previous rows:
      if (currentArea->mColumnEnd != column) {
        NS_ASSERTION(currentArea->mRowStart != row,
                     "Inconsistent column end for the first row of a named area.");
        // Not a rectangle
        return false;
      }
      currentArea = nullptr;
    }
    if (!token.mName.IsEmpty()) {
      // Named cell that doesn't have a cell with the same name on its left.

      // Check if this is the continuation of an existing named area:
      uint32_t index;
      if (aAreaIndices.Get(token.mName, &index)) {
        MOZ_ASSERT(index < aAreas->mNamedAreas.Length(),
                   "Invalid aAreaIndices hash table");
        currentArea = &aAreas->mNamedAreas[index];
        if (currentArea->mColumnStart != column ||
            currentArea->mRowEnd != row) {
          // Existing named area, but not forming a rectangle
          return false;
        }
        // Next row of an existing named area
        currentArea->mRowEnd++;
      } else {
        // New named area
        aAreaIndices.Put(token.mName, aAreas->mNamedAreas.Length());
        currentArea = aAreas->mNamedAreas.AppendElement();
        currentArea->mName = token.mName;
        // For column or row N (starting at 1),
        // the start line is N, the end line is N + 1
        currentArea->mColumnStart = column;
        currentArea->mColumnEnd = column + 1;
        currentArea->mRowStart = row;
        currentArea->mRowEnd = row + 1;
      }
    }
  }
  if (currentArea && currentArea->mColumnEnd != column + 1) {
    NS_ASSERTION(currentArea->mRowStart != row,
                 "Inconsistent column end for the first row of a named area.");
    // Not a rectangle
    return false;
  }

  // On the first row, set the number of columns
  // that grid-template-areas contributes to the explicit grid.
  // On other rows, check that the number of columns is consistent
  // between rows.
  if (row == 1) {
    aAreas->mNColumns = column;
  } else if (aAreas->mNColumns != column) {
    return false;
  }
  return true;
}

bool
CSSParserImpl::ParseGridTemplateAreas()
{
  nsCSSValue value;
  if (ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE, nullptr)) {
    AppendValue(eCSSProperty_grid_template_areas, value);
    return true;
  }

  RefPtr<css::GridTemplateAreasValue> areas =
    new css::GridTemplateAreasValue();
  nsDataHashtable<nsStringHashKey, uint32_t> areaIndices;
  for (;;) {
    if (!GetToken(true)) {
      break;
    }
    if (eCSSToken_String != mToken.mType) {
      UngetToken();
      break;
    }
    if (!ParseGridTemplateAreasLine(mToken.mIdent, areas, areaIndices)) {
      return false;
    }
  }

  if (areas->NRows() == 0) {
    return false;
  }

  AppendValue(eCSSProperty_grid_template_areas, nsCSSValue(areas));
  return true;
}

// [ auto-flow && dense? ] <'grid-auto-columns'>? |
// <'grid-template-columns'>
bool
CSSParserImpl::ParseGridTemplateColumnsOrAutoFlow(bool aForGridShorthand)
{
  if (aForGridShorthand) {
    auto res = ParseGridShorthandAutoProps(NS_STYLE_GRID_AUTO_FLOW_COLUMN);
    if (res == CSSParseResult::Error) {
      return false;
    }
    if (res == CSSParseResult::Ok) {
      nsCSSValue value(eCSSUnit_None);
      AppendValue(eCSSProperty_grid_template_columns, value);
      return true;
    }
  }
  return ParseGridTemplateColumnsRows(eCSSProperty_grid_template_columns);
}

bool
CSSParserImpl::ParseGridTemplate(bool aForGridShorthand)
{
  // none |
  // subgrid |
  // <'grid-template-rows'> / <'grid-template-columns'> |
  // [ <line-names>? <string> <track-size>? <line-names>? ]+ [ / <explicit-track-list>]?
  // or additionally when aForGridShorthand is true:
  // <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>?
  nsCSSValue value;
  if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
    AppendValue(eCSSProperty_grid_template_areas, value);
    AppendValue(eCSSProperty_grid_template_rows, value);
    AppendValue(eCSSProperty_grid_template_columns, value);
    return true;
  }

  // 'none' can appear either by itself,
  // or as the beginning of <'grid-template-rows'> / <'grid-template-columns'>
  if (ParseSingleTokenVariant(value, VARIANT_NONE, nullptr)) {
    AppendValue(eCSSProperty_grid_template_rows, value);
    AppendValue(eCSSProperty_grid_template_areas, value);
    if (ExpectSymbol('/', true)) {
      return ParseGridTemplateColumnsOrAutoFlow(aForGridShorthand);
    }
    AppendValue(eCSSProperty_grid_template_columns, value);
    return true;
  }

  // 'subgrid' can appear either by itself,
  // or as the beginning of <'grid-template-rows'> / <'grid-template-columns'>
  nsSubstring* ident = NextIdent();
  if (ident) {
    if (ident->LowerCaseEqualsLiteral("subgrid")) {
      if (!nsLayoutUtils::IsGridTemplateSubgridValueEnabled()) {
        REPORT_UNEXPECTED(PESubgridNotSupported);
        return false;
      }
      if (!ParseOptionalLineNameListAfterSubgrid(value)) {
        return false;
      }
      AppendValue(eCSSProperty_grid_template_rows, value);
      AppendValue(eCSSProperty_grid_template_areas, nsCSSValue(eCSSUnit_None));
      if (ExpectSymbol('/', true)) {
        return ParseGridTemplateColumnsOrAutoFlow(aForGridShorthand);
      }
      if (value.GetListValue()->mNext) {
        // Non-empty <line-name-list> after 'subgrid'.
        // This is only valid as part of <'grid-template-rows'>,
        // which must be followed by a slash.
        return false;
      }
      // 'subgrid' by itself sets both grid-template-rows/columns.
      AppendValue(eCSSProperty_grid_template_columns, value);
      return true;
    }
    UngetToken();
  }

  // [ <line-names>? ] here is ambiguous:
  // it can be either the start of a <track-list> (in a <'grid-template-rows'>),
  // or the start of [ <line-names>? <string> <track-size>? <line-names>? ]+
  nsCSSValue firstLineNames;
  if (ParseGridLineNames(firstLineNames) == CSSParseResult::Error ||
      !GetToken(true)) {
    return false;
  }
  if (mToken.mType == eCSSToken_String) {
    // It's the [ <line-names>? <string> <track-size>? <line-names>? ]+ case.
    if (!ParseGridTemplateAfterString(firstLineNames)) {
      return false;
    }
    // Parse an optional [ / <explicit-track-list> ] as the columns value.
    if (ExpectSymbol('/', true)) {
      return ParseGridTrackList(eCSSProperty_grid_template_columns,
                                GridTrackListFlags::eExplicitTrackList);
    }
    value.SetNoneValue(); // absent means 'none'
    AppendValue(eCSSProperty_grid_template_columns, value);
    return true;
  }
  UngetToken();

  // Finish parsing <'grid-template-rows'> with the |firstLineNames| we have,
  // and then parse a mandatory [ / <'grid-template-columns'> ].
  if (!ParseGridTrackListWithFirstLineNames(value, firstLineNames) ||
      !ExpectSymbol('/', true)) {
    return false;
  }
  AppendValue(eCSSProperty_grid_template_rows, value);
  value.SetNoneValue();
  AppendValue(eCSSProperty_grid_template_areas, value);
  return ParseGridTemplateColumnsOrAutoFlow(aForGridShorthand);
}

// Helper for parsing the 'grid-template' shorthand:
// Parse [ <line-names>? <string> <track-size>? <line-names>? ]+
// with a <line-names>? already consumed, stored in |aFirstLineNames|,
// and the current token a <string>
bool
CSSParserImpl::ParseGridTemplateAfterString(const nsCSSValue& aFirstLineNames)
{
  MOZ_ASSERT(mToken.mType == eCSSToken_String,
             "ParseGridTemplateAfterString called with a non-string token");

  nsCSSValue rowsValue;
  RefPtr<css::GridTemplateAreasValue> areas =
    new css::GridTemplateAreasValue();
  nsDataHashtable<nsStringHashKey, uint32_t> areaIndices;
  nsCSSValueList* rowsItem = rowsValue.SetListValue();
  rowsItem->mValue = aFirstLineNames;

  for (;;) {
    if (!ParseGridTemplateAreasLine(mToken.mIdent, areas, areaIndices)) {
      return false;
    }

    rowsItem->mNext = new nsCSSValueList;
    rowsItem = rowsItem->mNext;
    CSSParseResult result = ParseGridTrackSize(rowsItem->mValue);
    if (result == CSSParseResult::Error) {
      return false;
    }
    if (result == CSSParseResult::NotFound) {
      rowsItem->mValue.SetAutoValue();
    }

    rowsItem->mNext = new nsCSSValueList;
    rowsItem = rowsItem->mNext;
    result = ParseGridLineNames(rowsItem->mValue);
    if (result == CSSParseResult::Error) {
      return false;
    }
    if (result == CSSParseResult::Ok) {
      // Append to the same list as the previous call to ParseGridLineNames.
      result = ParseGridLineNames(rowsItem->mValue);
      if (result == CSSParseResult::Error) {
        return false;
      }
      if (result == CSSParseResult::Ok) {
        // Parsed <line-name> twice.
        // The property value can not end here, we expect a string next.
        if (!GetToken(true)) {
          return false;
        }
        if (eCSSToken_String != mToken.mType) {
          UngetToken();
          return false;
        }
        continue;
      }
    }

    // Did not find a <line-names>.
    // Next, we expect either a string or the end of the property value.
    if (!GetToken(true)) {
      break;
    }
    if (eCSSToken_String != mToken.mType) {
      UngetToken();
      break;
    }
  }

  AppendValue(eCSSProperty_grid_template_areas, nsCSSValue(areas));
  AppendValue(eCSSProperty_grid_template_rows, rowsValue);
  return true;
}

// <'grid-template'> |
// <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>? |
// [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'>
bool
CSSParserImpl::ParseGrid()
{
  nsCSSValue value;
  if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
    for (const nsCSSPropertyID* subprops =
           nsCSSProps::SubpropertyEntryFor(eCSSProperty_grid);
         *subprops != eCSSProperty_UNKNOWN; ++subprops) {
      AppendValue(*subprops, value);
    }
    return true;
  }

  // https://drafts.csswg.org/css-grid/#grid-shorthand
  // "Also, the gutter properties are reset by this shorthand,
  //  even though they can't be set by it."
  value.SetFloatValue(0.0f, eCSSUnit_Pixel);
  AppendValue(eCSSProperty_grid_row_gap, value);
  AppendValue(eCSSProperty_grid_column_gap, value);

  // [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'>
  auto res = ParseGridShorthandAutoProps(NS_STYLE_GRID_AUTO_FLOW_ROW);
  if (res == CSSParseResult::Error) {
    return false;
  }
  if (res == CSSParseResult::Ok) {
    value.SetAutoValue();
    AppendValue(eCSSProperty_grid_auto_columns, value);
    nsCSSValue none(eCSSUnit_None);
    AppendValue(eCSSProperty_grid_template_areas, none);
    AppendValue(eCSSProperty_grid_template_rows, none);
    if (!ExpectSymbol('/', true)) {
      return false;
    }
    return ParseGridTemplateColumnsRows(eCSSProperty_grid_template_columns);
  }

  // Set remaining subproperties that might not be set by ParseGridTemplate to
  // their initial values and then parse <'grid-template'> |
  // <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>? .
  value.SetIntValue(NS_STYLE_GRID_AUTO_FLOW_ROW, eCSSUnit_Enumerated);
  AppendValue(eCSSProperty_grid_auto_flow, value);
  value.SetAutoValue();
  AppendValue(eCSSProperty_grid_auto_rows, value);
  AppendValue(eCSSProperty_grid_auto_columns, value);
  return ParseGridTemplate(true);
}

// Parse [ auto-flow && dense? ] <'grid-auto-[rows|columns]'>? for the 'grid'
// shorthand.  If aAutoFlowAxis == NS_STYLE_GRID_AUTO_FLOW_ROW then we're
// parsing row values, otherwise column values.
CSSParseResult
CSSParserImpl::ParseGridShorthandAutoProps(int32_t aAutoFlowAxis)
{
  MOZ_ASSERT(aAutoFlowAxis == NS_STYLE_GRID_AUTO_FLOW_ROW ||
             aAutoFlowAxis == NS_STYLE_GRID_AUTO_FLOW_COLUMN);
  if (!GetToken(true)) {
    return CSSParseResult::NotFound;
  }
  // [ auto-flow && dense? ]
  int32_t autoFlowValue = 0;
  if (mToken.mType == eCSSToken_Ident) {
    nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
    if (keyword == eCSSKeyword_auto_flow) {
      autoFlowValue = aAutoFlowAxis;
      if (GetToken(true)) {
        if (mToken.mType == eCSSToken_Ident &&
            nsCSSKeywords::LookupKeyword(mToken.mIdent) == eCSSKeyword_dense) {
          autoFlowValue |= NS_STYLE_GRID_AUTO_FLOW_DENSE;
        } else {
          UngetToken();
        }
      }
    } else if (keyword == eCSSKeyword_dense) {
      if (!GetToken(true)) {
        return CSSParseResult::Error;
      }
      if (mToken.mType != eCSSToken_Ident ||
          nsCSSKeywords::LookupKeyword(mToken.mIdent) != eCSSKeyword_auto_flow) {
        UngetToken();
        return CSSParseResult::Error;
      }
      autoFlowValue = aAutoFlowAxis | NS_STYLE_GRID_AUTO_FLOW_DENSE;
    }
  }
  if (autoFlowValue) {
    nsCSSValue value;
    value.SetIntValue(autoFlowValue, eCSSUnit_Enumerated);
    AppendValue(eCSSProperty_grid_auto_flow, value);
  } else {
    UngetToken();
    return CSSParseResult::NotFound;
  }

  // <'grid-auto-[rows|columns]'>?
  nsCSSValue autoTrackValue;
  CSSParseResult result = ParseGridTrackSize(autoTrackValue);
  if (result == CSSParseResult::Error) {
    return result;
  }
  if (result == CSSParseResult::NotFound) {
    autoTrackValue.SetAutoValue();
  }
  AppendValue(aAutoFlowAxis == NS_STYLE_GRID_AUTO_FLOW_ROW ?
                eCSSProperty_grid_auto_rows : eCSSProperty_grid_auto_columns,
              autoTrackValue);
  return CSSParseResult::Ok;
}

// Parse a <grid-line>.
// If successful, set aValue to eCSSUnit_Auto,
// or a eCSSUnit_List containing, in that order:
//
// * An optional eCSSUnit_Enumerated marking a "span" keyword.
// * An optional eCSSUnit_Integer
// * An optional eCSSUnit_Ident
//
// At least one of eCSSUnit_Integer or eCSSUnit_Ident is present.
bool
CSSParserImpl::ParseGridLine(nsCSSValue& aValue)
{
  //  <grid-line> =
  //    auto |
  //    <custom-ident> |
  //    [ <integer> && <custom-ident>? ] |
  //    [ span && [ <integer> || <custom-ident> ] ]
  //
  // Syntactically, this simplifies to:
  //
  //  <grid-line> =
  //    auto |
  //    [ span? && [ <integer> || <custom-ident> ] ]

  if (ParseSingleTokenVariant(aValue, VARIANT_AUTO, nullptr)) {
    return true;
  }

  bool hasSpan = false;
  bool hasIdent = false;
  Maybe<int32_t> integer;
  nsCSSValue ident;

#ifdef MOZ_VALGRIND
  // Make the contained value be defined even though we really want a
  // Nothing here.  This works around an otherwise difficult to avoid
  // Memcheck false positive when this is compiled by gcc-5.3 -O2.
  // See bug 1301856.
  integer.emplace(0);
  integer.reset();
#endif

  if (!GetToken(true)) {
    return false;
  }
  if (mToken.mType == eCSSToken_Ident &&
      mToken.mIdent.LowerCaseEqualsLiteral("span")) {
    hasSpan = true;
    if (!GetToken(true)) {
      return false;
    }
  }

  do {
    if (!hasIdent &&
        mToken.mType == eCSSToken_Ident &&
        ParseCustomIdent(ident, mToken.mIdent, kGridLineKeywords)) {
      hasIdent = true;
    } else if (integer.isNothing() &&
               mToken.mType == eCSSToken_Number &&
               mToken.mIntegerValid &&
               mToken.mInteger != 0) {
      integer.emplace(mToken.mInteger);
    } else {
      UngetToken();
      break;
    }
  } while (!(integer.isSome() && hasIdent) && GetToken(true));

  // Require at least one of <integer> or <custom-ident>
  if (!(integer.isSome() || hasIdent)) {
    return false;
  }

  if (!hasSpan && GetToken(true)) {
    if (mToken.mType == eCSSToken_Ident &&
        mToken.mIdent.LowerCaseEqualsLiteral("span")) {
      hasSpan = true;
    } else {
      UngetToken();
    }
  }

  nsCSSValueList* item = aValue.SetListValue();
  if (hasSpan) {
    // Given "span", a negative <integer> is invalid.
    if (integer.isSome() && integer.ref() < 0) {
      return false;
    }
    // '1' here is a dummy value.
    // The mere presence of eCSSUnit_Enumerated indicates a "span" keyword.
    item->mValue.SetIntValue(1, eCSSUnit_Enumerated);
    item->mNext = new nsCSSValueList;
    item = item->mNext;
  }
  if (integer.isSome()) {
    item->mValue.SetIntValue(integer.ref(), eCSSUnit_Integer);
    if (hasIdent) {
      item->mNext = new nsCSSValueList;
      item = item->mNext;
    }
  }
  if (hasIdent) {
    item->mValue = ident;
  }
  return true;
}

bool
CSSParserImpl::ParseGridColumnRowStartEnd(nsCSSPropertyID aPropID)
{
  nsCSSValue value;
  if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr) ||
      ParseGridLine(value)) {
    AppendValue(aPropID, value);
    return true;
  }
  return false;
}

// If |aFallback| is a List containing a single Ident, set |aValue| to that.
// Otherwise, set |aValue| to Auto.
// Used with |aFallback| from ParseGridLine()
static void
HandleGridLineFallback(const nsCSSValue& aFallback, nsCSSValue& aValue)
{
  if (aFallback.GetUnit() == eCSSUnit_List &&
      aFallback.GetListValue()->mValue.GetUnit() == eCSSUnit_Ident &&
      !aFallback.GetListValue()->mNext) {
    aValue = aFallback;
  } else {
    aValue.SetAutoValue();
  }
}

bool
CSSParserImpl::ParseGridColumnRow(nsCSSPropertyID aStartPropID,
                                  nsCSSPropertyID aEndPropID)
{
  nsCSSValue value;
  nsCSSValue secondValue;
  if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
    AppendValue(aStartPropID, value);
    AppendValue(aEndPropID, value);
    return true;
  }

  if (!ParseGridLine(value)) {
    return false;
  }
  if (GetToken(true)) {
    if (mToken.IsSymbol('/')) {
      if (ParseGridLine(secondValue)) {
        AppendValue(aStartPropID, value);
        AppendValue(aEndPropID, secondValue);
        return true;
      } else {
        return false;
      }
    }
    UngetToken();
  }

  // A single <custom-ident> is repeated to both properties,
  // anything else sets the grid-{column,row}-end property to 'auto'.
  HandleGridLineFallback(value, secondValue);

  AppendValue(aStartPropID, value);
  AppendValue(aEndPropID, secondValue);
  return true;
}

bool
CSSParserImpl::ParseGridArea()
{
  nsCSSValue values[4];
  if (ParseSingleTokenVariant(values[0], VARIANT_INHERIT, nullptr)) {
    AppendValue(eCSSProperty_grid_row_start, values[0]);
    AppendValue(eCSSProperty_grid_column_start, values[0]);
    AppendValue(eCSSProperty_grid_row_end, values[0]);
    AppendValue(eCSSProperty_grid_column_end, values[0]);
    return true;
  }

  int32_t i = 0;
  for (;;) {
    if (!ParseGridLine(values[i])) {
      return false;
    }
    if (++i == 4 || !GetToken(true)) {
      break;
    }
    if (!mToken.IsSymbol('/')) {
      UngetToken();
      break;
    }
  }

  MOZ_ASSERT(i >= 1, "should have parsed at least one grid-line (or returned)");
  if (i < 2) {
    HandleGridLineFallback(values[0], values[1]);
  }
  if (i < 3) {
    HandleGridLineFallback(values[0], values[2]);
  }
  if (i < 4) {
    HandleGridLineFallback(values[1], values[3]);
  }

  AppendValue(eCSSProperty_grid_row_start, values[0]);
  AppendValue(eCSSProperty_grid_column_start, values[1]);
  AppendValue(eCSSProperty_grid_row_end, values[2]);
  AppendValue(eCSSProperty_grid_column_end, values[3]);
  return true;
}

bool
CSSParserImpl::ParseGridGap()
{
  nsCSSValue first;
  if (ParseSingleTokenVariant(first, VARIANT_INHERIT, nullptr)) {
    AppendValue(eCSSProperty_grid_row_gap, first);
    AppendValue(eCSSProperty_grid_column_gap, first);
    return true;
  }
  if (ParseNonNegativeVariant(first, VARIANT_LPCALC, nullptr) !=
        CSSParseResult::Ok) {
    return false;
  }
  nsCSSValue second;
  auto result = ParseNonNegativeVariant(second, VARIANT_LPCALC, nullptr);
  if (result == CSSParseResult::Error) {
    return false;
  }
  AppendValue(eCSSProperty_grid_row_gap, first);
  AppendValue(eCSSProperty_grid_column_gap,
              result == CSSParseResult::NotFound ? first : second);
  return true;
}

// normal | [<number> <integer>?]
bool
CSSParserImpl::ParseInitialLetter()
{
  nsCSSValue value;
  // 'inherit', 'initial', 'unset', 'none', and 'normal' must be alone
  if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NORMAL,
                               nullptr)) {
    nsCSSValue first, second;
    if (!ParseOneOrLargerNumber(first)) {
      return false;
    }

    if (!ParseOneOrLargerInteger(second)) {
      AppendValue(eCSSProperty_initial_letter, first);
      return true;
    } else {
      RefPtr<nsCSSValue::Array> val = nsCSSValue::Array::Create(2);
      val->Item(0) = first;
      val->Item(1) = second;
      value.SetArrayValue(val, eCSSUnit_Array);
    }
  }
  AppendValue(eCSSProperty_initial_letter, value);
  return true;
}

// [ $aTable && <overflow-position>? ] ?
// $aTable is for <content-position> or <self-position>
bool
CSSParserImpl::ParseAlignJustifyPosition(nsCSSValue& aResult,
                                         const KTableEntry aTable[])
{
  nsCSSValue pos, overflowPos;
  int32_t value = 0;
  if (ParseEnum(pos, aTable)) {
    value = pos.GetIntValue();
    if (ParseEnum(overflowPos, nsCSSProps::kAlignOverflowPosition)) {
      value |= overflowPos.GetIntValue();
    }
    aResult.SetIntValue(value, eCSSUnit_Enumerated);
    return true;
  }
  if (ParseEnum(overflowPos, nsCSSProps::kAlignOverflowPosition)) {
    if (ParseEnum(pos, aTable)) {
      aResult.SetIntValue(pos.GetIntValue() | overflowPos.GetIntValue(),
                          eCSSUnit_Enumerated);
      return true;
    }
    return false; // <overflow-position> must be followed by a value in $table
  }
  return true;
}

// auto | normal | stretch | <baseline-position> |
// [ <self-position> && <overflow-position>? ] |
// [ legacy && [ left | right | center ] ]
bool
CSSParserImpl::ParseJustifyItems()
{
  nsCSSValue value;
  if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
    if (MOZ_UNLIKELY(ParseEnum(value, nsCSSProps::kAlignLegacy))) {
      nsCSSValue legacy;
      if (!ParseEnum(legacy, nsCSSProps::kAlignLegacyPosition)) {
        return false; // leading 'legacy' not followed by 'left' etc is an error
      }
      value.SetIntValue(value.GetIntValue() | legacy.GetIntValue(),
                        eCSSUnit_Enumerated);
    } else {
      if (!ParseAlignEnum(value, nsCSSProps::kAlignAutoNormalStretchBaseline)) {
        if (!ParseAlignJustifyPosition(value, nsCSSProps::kAlignSelfPosition) ||
            value.GetUnit() == eCSSUnit_Null) {
          return false;
        }
        // check for a trailing 'legacy' after 'left' etc
        auto val = value.GetIntValue();
        if (val == NS_STYLE_JUSTIFY_CENTER ||
            val == NS_STYLE_JUSTIFY_LEFT   ||
            val == NS_STYLE_JUSTIFY_RIGHT) {
          nsCSSValue legacy;
          if (ParseEnum(legacy, nsCSSProps::kAlignLegacy)) {
            value.SetIntValue(val | legacy.GetIntValue(), eCSSUnit_Enumerated);
          }
        }
      }
    }
  }
  AppendValue(eCSSProperty_justify_items, value);
  return true;
}

// normal | stretch | <baseline-position> |
// [ <overflow-position>? && <self-position> ]
bool
CSSParserImpl::ParseAlignItems()
{
  nsCSSValue value;
  if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
    if (!ParseAlignEnum(value, nsCSSProps::kAlignNormalStretchBaseline)) {
      if (!ParseAlignJustifyPosition(value, nsCSSProps::kAlignSelfPosition) ||
          value.GetUnit() == eCSSUnit_Null) {
        return false;
      }
    }
  }
  AppendValue(eCSSProperty_align_items, value);
  return true;
}

// auto | normal | stretch | <baseline-position> |
// [ <overflow-position>? && <self-position> ]
bool
CSSParserImpl::ParseAlignJustifySelf(nsCSSPropertyID aPropID)
{
  nsCSSValue value;
  if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
    if (!ParseAlignEnum(value, nsCSSProps::kAlignAutoNormalStretchBaseline)) {
      if (!ParseAlignJustifyPosition(value, nsCSSProps::kAlignSelfPosition) ||
          value.GetUnit() == eCSSUnit_Null) {
        return false;
      }
    }
  }
  AppendValue(aPropID, value);
  return true;
}

// normal | <baseline-position> | [ <content-distribution> ||
//   [ <overflow-position>? && <content-position> ] ]
// (the part after the || is called <*-position> below)
bool
CSSParserImpl::ParseAlignJustifyContent(nsCSSPropertyID aPropID)
{
  nsCSSValue value;
  if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
    if (!ParseAlignEnum(value, nsCSSProps::kAlignNormalBaseline)) {
      nsCSSValue fallbackValue;
      if (!ParseEnum(value, nsCSSProps::kAlignContentDistribution)) {
        if (!ParseAlignJustifyPosition(fallbackValue,
                                       nsCSSProps::kAlignContentPosition) ||
            fallbackValue.GetUnit() == eCSSUnit_Null) {
          return false;
        }
        // optional <content-distribution> after <*-position> ...
        if (!ParseEnum(value, nsCSSProps::kAlignContentDistribution)) {
          // ... is missing so the <*-position> is the value, not the fallback
          value = fallbackValue;
          fallbackValue.Reset();
        }
      } else {
        // any optional <*-position> is a fallback value
        if (!ParseAlignJustifyPosition(fallbackValue,
                                       nsCSSProps::kAlignContentPosition)) {
          return false;
        }
      }
      if (fallbackValue.GetUnit() != eCSSUnit_Null) {
        auto fallback = fallbackValue.GetIntValue();
        value.SetIntValue(value.GetIntValue() |
                            (fallback << NS_STYLE_ALIGN_ALL_SHIFT),
                          eCSSUnit_Enumerated);
      }
    }
  }
  AppendValue(aPropID, value);
  return true;
}

// place-content: [ normal | <baseline-position> | <content-distribution> |
//                  <content-position> ]{1,2}
bool
CSSParserImpl::ParsePlaceContent()
{
  nsCSSValue first;
  if (ParseSingleTokenVariant(first, VARIANT_INHERIT, nullptr)) {
    AppendValue(eCSSProperty_align_content, first);
    AppendValue(eCSSProperty_justify_content, first);
    return true;
  }
  if (!ParseAlignEnum(first, nsCSSProps::kAlignNormalBaseline) &&
      !ParseEnum(first, nsCSSProps::kAlignContentDistribution) &&
      !ParseEnum(first, nsCSSProps::kAlignContentPosition)) {
    return false;
  }
  AppendValue(eCSSProperty_align_content, first);
  nsCSSValue second;
  if (!ParseAlignEnum(second, nsCSSProps::kAlignNormalBaseline) &&
      !ParseEnum(second, nsCSSProps::kAlignContentDistribution) &&
      !ParseEnum(second, nsCSSProps::kAlignContentPosition)) {
    AppendValue(eCSSProperty_justify_content, first);
  } else {
    AppendValue(eCSSProperty_justify_content, second);
  }
  return true;
}

// place-items:  <x> [ auto | <x> ]?
// <x> = [ normal | stretch | <baseline-position> | <self-position> ]
bool
CSSParserImpl::ParsePlaceItems()
{
  nsCSSValue first;
  if (ParseSingleTokenVariant(first, VARIANT_INHERIT, nullptr)) {
    AppendValue(eCSSProperty_align_items, first);
    AppendValue(eCSSProperty_justify_items, first);
    return true;
  }
  if (!ParseAlignEnum(first, nsCSSProps::kAlignNormalStretchBaseline) &&
      !ParseEnum(first, nsCSSProps::kAlignSelfPosition)) {
    return false;
  }
  AppendValue(eCSSProperty_align_items, first);
  nsCSSValue second;
  // Note: 'auto' is valid for justify-items, but not align-items.
  if (!ParseAlignEnum(second, nsCSSProps::kAlignAutoNormalStretchBaseline) &&
      !ParseEnum(second, nsCSSProps::kAlignSelfPosition)) {
    AppendValue(eCSSProperty_justify_items, first);
  } else {
    AppendValue(eCSSProperty_justify_items, second);
  }
  return true;
}

// place-self: [ auto | normal | stretch | <baseline-position> |
//               <self-position> ]{1,2}
bool
CSSParserImpl::ParsePlaceSelf()
{
  nsCSSValue first;
  if (ParseSingleTokenVariant(first, VARIANT_INHERIT, nullptr)) {
    AppendValue(eCSSProperty_align_self, first);
    AppendValue(eCSSProperty_justify_self, first);
    return true;
  }
  if (!ParseAlignEnum(first, nsCSSProps::kAlignAutoNormalStretchBaseline) &&
      !ParseEnum(first, nsCSSProps::kAlignSelfPosition)) {
    return false;
  }
  AppendValue(eCSSProperty_align_self, first);
  nsCSSValue second;
  if (!ParseAlignEnum(second, nsCSSProps::kAlignAutoNormalStretchBaseline) &&
      !ParseEnum(second, nsCSSProps::kAlignSelfPosition)) {
    AppendValue(eCSSProperty_justify_self, first);
  } else {
    AppendValue(eCSSProperty_justify_self, second);
  }
  return true;
}

// <color-stop> : <color> [ <percentage> | <length> ]?
bool
CSSParserImpl::ParseColorStop(nsCSSValueGradient* aGradient)
{
  nsCSSValueGradientStop* stop = aGradient->mStops.AppendElement();
  CSSParseResult result = ParseVariant(stop->mColor, VARIANT_COLOR, nullptr);
  if (result == CSSParseResult::Error) {
    return false;
  } else if (result == CSSParseResult::NotFound) {
    stop->mIsInterpolationHint = true;
  }

  // Stop positions do not have to fall between the starting-point and
  // ending-point, so we don't use ParseNonNegativeVariant.
  result = ParseVariant(stop->mLocation, VARIANT_LP | VARIANT_CALC, nullptr);
  if (result == CSSParseResult::Error) {
    return false;
  } else if (result == CSSParseResult::NotFound) {
    if (stop->mIsInterpolationHint) {
      return false;
    }
    stop->mLocation.SetNoneValue();
  }
  return true;
}

// Helper for ParseLinearGradient -- returns true iff aPosition represents a
// box-position value which was parsed with only edge keywords.
// e.g. "left top", or "bottom", but not "left 10px"
//
// (NOTE: Even though callers may want to exclude explicit "center", we still
// need to allow for _CENTER here, because omitted position-values (e.g. the
// x-component of a value like "top") will have been parsed as being *implicit*
// center. The correct way to disallow *explicit* center is to pass "false" for
// ParseBoxPositionValues()'s "aAllowExplicitCenter" parameter, before you
// call this function.)
static bool
IsBoxPositionStrictlyEdgeKeywords(nsCSSValuePair& aPosition)
{
  const nsCSSValue& xValue = aPosition.mXValue;
  const nsCSSValue& yValue = aPosition.mYValue;
  return (xValue.GetUnit() == eCSSUnit_Enumerated &&
          (xValue.GetIntValue() & (NS_STYLE_IMAGELAYER_POSITION_LEFT |
                                   NS_STYLE_IMAGELAYER_POSITION_CENTER |
                                   NS_STYLE_IMAGELAYER_POSITION_RIGHT)) &&
          yValue.GetUnit() == eCSSUnit_Enumerated &&
          (yValue.GetIntValue() & (NS_STYLE_IMAGELAYER_POSITION_TOP |
                                   NS_STYLE_IMAGELAYER_POSITION_CENTER |
                                   NS_STYLE_IMAGELAYER_POSITION_BOTTOM)));
}

// <gradient>
//    : linear-gradient( <linear-gradient-line>? <color-stops> ')'
//    | radial-gradient( <radial-gradient-line>? <color-stops> ')'
//
// <linear-gradient-line> : [ to [left | right] || [top | bottom] ] ,
//                        | <legacy-gradient-line>
// <radial-gradient-line> : [ <shape> || <size> ] [ at <position> ]? ,
//                        | [ at <position> ] ,
//                        | <legacy-gradient-line>? <legacy-shape-size>?
// <shape> : circle | ellipse
// <size> : closest-side | closest-corner | farthest-side | farthest-corner
//        | <length> | [<length> | <percentage>]{2}
//
// <legacy-gradient-line> : [ <position> || <angle>] ,
//
// <legacy-shape-size> : [ <shape> || <legacy-size> ] ,
// <legacy-size> : closest-side | closest-corner | farthest-side
//               | farthest-corner | contain | cover
//
// <color-stops> : <color-stop> , <color-stop> [, <color-stop>]*
bool
CSSParserImpl::ParseLinearGradient(nsCSSValue& aValue,
                                   uint8_t aFlags)
{
  RefPtr<nsCSSValueGradient> cssGradient
    = new nsCSSValueGradient(false, aFlags & eGradient_Repeating);

  if (!GetToken(true)) {
    return false;
  }

  // Check for "to" syntax (but not if parsing a -webkit-linear-gradient)
  if (!(aFlags & eGradient_WebkitLegacy) &&
      mToken.mType == eCSSToken_Ident &&
      mToken.mIdent.LowerCaseEqualsLiteral("to")) {

    // "to" syntax doesn't allow explicit "center"
    if (!ParseBoxPositionValues(cssGradient->mBgPos, false, false)) {
      SkipUntil(')');
      return false;
    }

    // [ to [left | right] || [top | bottom] ] ,
    if (!IsBoxPositionStrictlyEdgeKeywords(cssGradient->mBgPos)) {
      SkipUntil(')');
      return false;
    }

    if (!ExpectSymbol(',', true)) {
      SkipUntil(')');
      return false;
    }

    return ParseGradientColorStops(cssGradient, aValue);
  }

  if (!(aFlags & eGradient_AnyLegacy)) {
    // We're parsing an unprefixed linear-gradient, and we tried & failed to
    // parse a 'to' token above. Put the token back & try to re-parse our
    // expression as <angle>? <color-stop-list>
    UngetToken();

    // <angle> ,
    if (ParseSingleTokenVariant(cssGradient->mAngle,
                                VARIANT_ANGLE_OR_ZERO, nullptr) &&
        !ExpectSymbol(',', true)) {
      SkipUntil(')');
      return false;
    }

    return ParseGradientColorStops(cssGradient, aValue);
  }

  // If we get here, we're parsing a prefixed linear-gradient expression.  Put
  // back the first token (which we may have checked for "to" above) and try to
  // parse expression as <legacy-gradient-line>? <color-stop-list>
  bool haveGradientLine = IsLegacyGradientLine(mToken.mType, mToken.mIdent);
  UngetToken();

  if (haveGradientLine) {
    // Parse a <legacy-gradient-line>
    cssGradient->mIsLegacySyntax = true;
    // In -webkit-linear-gradient expressions (handled below), we need to accept
    // unitless 0 for angles, to match WebKit/Blink.
    int32_t angleFlags = (aFlags & eGradient_WebkitLegacy) ?
      VARIANT_ANGLE | VARIANT_ZERO_ANGLE :
      VARIANT_ANGLE;

    bool haveAngle =
      ParseSingleTokenVariant(cssGradient->mAngle, angleFlags, nullptr);

    // If we got an angle, we might now have a comma, ending the gradient-line.
    bool haveAngleComma = haveAngle && ExpectSymbol(',', true);

    // If we're webkit-prefixed & didn't get an angle,
    // OR if we're moz-prefixed & didn't get an angle+comma,
    // then proceed to parse a box-position.
    if (((aFlags & eGradient_WebkitLegacy) && !haveAngle) ||
        ((aFlags & eGradient_MozLegacy) && !haveAngleComma)) {
      // (Note: 3rd arg controls whether the "center" keyword is allowed.
      // -moz-linear-gradient allows it; -webkit-linear-gradient does not.)
      if (!ParseBoxPositionValues(cssGradient->mBgPos, false,
                                  (aFlags & eGradient_MozLegacy))) {
        SkipUntil(')');
        return false;
      }

      // -webkit-linear-gradient only supports edge keywords here.
      if ((aFlags & eGradient_WebkitLegacy) &&
          !IsBoxPositionStrictlyEdgeKeywords(cssGradient->mBgPos)) {
        SkipUntil(')');
        return false;
      }

      if (!ExpectSymbol(',', true) &&
          // If we didn't already get an angle, and we're not -webkit prefixed,
          // we can parse an angle+comma now.  Otherwise it's an error.
          (haveAngle ||
           (aFlags & eGradient_WebkitLegacy) ||
           !ParseSingleTokenVariant(cssGradient->mAngle, VARIANT_ANGLE,
                                    nullptr) ||
           // now we better have a comma
           !ExpectSymbol(',', true))) {
        SkipUntil(')');
        return false;
      }
    }
  }

  return ParseGradientColorStops(cssGradient, aValue);
}

bool
CSSParserImpl::ParseRadialGradient(nsCSSValue& aValue,
                                   uint8_t aFlags)
{
  RefPtr<nsCSSValueGradient> cssGradient
    = new nsCSSValueGradient(true, aFlags & eGradient_Repeating);

  // [ <shape> || <size> ]
  bool haveShape =
    ParseSingleTokenVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD,
                            nsCSSProps::kRadialGradientShapeKTable);

  bool haveSize =
    ParseSingleTokenVariant(cssGradient->GetRadialSize(), VARIANT_KEYWORD,
                            (aFlags & eGradient_AnyLegacy) ?
                            nsCSSProps::kRadialGradientLegacySizeKTable :
                            nsCSSProps::kRadialGradientSizeKTable);
  if (haveSize) {
    if (!haveShape) {
      // <size> <shape>
      haveShape =
        ParseSingleTokenVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD,
                                nsCSSProps::kRadialGradientShapeKTable);
    }
  } else if (!(aFlags & eGradient_AnyLegacy)) {
    // Save RadialShape before parsing RadiusX because RadialShape and
    // RadiusX share the storage.
    int32_t shape =
      cssGradient->GetRadialShape().GetUnit() == eCSSUnit_Enumerated ?
      cssGradient->GetRadialShape().GetIntValue() : -1;
    // <length> | [<length> | <percentage>]{2}
    cssGradient->mIsExplicitSize = true;
    haveSize =
      ParseSingleTokenNonNegativeVariant(cssGradient->GetRadiusX(), VARIANT_LP,
                                         nullptr);
    if (!haveSize) {
      // It was not an explicit size after all.
      // Note that ParseNonNegativeVariant may have put something
      // invalid into our storage, but only in the case where it was
      // rejected only for being negative.  Since this means the token
      // was a length or a percentage, we know it's not valid syntax
      // (which must be a comma, the 'at' keyword, or a color), so we
      // know this value will be dropped.  This means it doesn't matter
      // that we have something invalid in our storage.
      cssGradient->mIsExplicitSize = false;
    } else {
      // vertical extent is optional
      bool haveYSize =
        ParseSingleTokenNonNegativeVariant(cssGradient->GetRadiusY(),
                                           VARIANT_LP, nullptr);
      if (!haveShape) {
        nsCSSValue shapeValue;
        haveShape =
          ParseSingleTokenVariant(shapeValue, VARIANT_KEYWORD,
                                  nsCSSProps::kRadialGradientShapeKTable);
        if (haveShape) {
          shape = shapeValue.GetIntValue();
        }
      }
      if (haveYSize
            ? shape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR
            : cssGradient->GetRadiusX().GetUnit() == eCSSUnit_Percent ||
              shape == NS_STYLE_GRADIENT_SHAPE_ELLIPTICAL) {
        SkipUntil(')');
        return false;
      }
    }
  }

  if ((haveShape || haveSize) && ExpectSymbol(',', true)) {
    // [ <shape> || <size> ] ,
    return ParseGradientColorStops(cssGradient, aValue);
  }

  if (!GetToken(true)) {
    return false;
  }

  if (!(aFlags & eGradient_AnyLegacy)) {
    if (mToken.mType == eCSSToken_Ident &&
        mToken.mIdent.LowerCaseEqualsLiteral("at")) {
      // [ <shape> || <size> ]? at <position> ,
      if (!ParseBoxPositionValues(cssGradient->mBgPos, false) ||
          !ExpectSymbol(',', true)) {
        SkipUntil(')');
        return false;
      }

      return ParseGradientColorStops(cssGradient, aValue);
    }

    // <color-stops> only
    UngetToken();
    return ParseGradientColorStops(cssGradient, aValue);
  }
  MOZ_ASSERT(!cssGradient->mIsExplicitSize);

  nsCSSTokenType ty = mToken.mType;
  nsString id = mToken.mIdent;
  UngetToken();

  // <legacy-gradient-line>
  bool haveGradientLine = false;
  // if we already encountered a shape or size,
  // we can not have a gradient-line in legacy syntax
  if (!haveShape && !haveSize) {
      haveGradientLine = IsLegacyGradientLine(ty, id);
  }
  if (haveGradientLine) {
    // Note: -webkit-radial-gradient() doesn't accept angles.
    bool haveAngle = (aFlags & eGradient_WebkitLegacy)
      ? false
      : ParseSingleTokenVariant(cssGradient->mAngle, VARIANT_ANGLE, nullptr);

    // If we got an angle, we might now have a comma, ending the gradient-line
    if (!haveAngle || !ExpectSymbol(',', true)) {
      if (!ParseBoxPositionValues(cssGradient->mBgPos, false)) {
        SkipUntil(')');
        return false;
      }

      if (!ExpectSymbol(',', true) &&
          // If we didn't already get an angle, and we're not -webkit prefixed,
          // can parse an angle+comma now.  Otherwise it's an error.
          (haveAngle ||
           (aFlags & eGradient_WebkitLegacy) ||
           !ParseSingleTokenVariant(cssGradient->mAngle, VARIANT_ANGLE,
                                    nullptr) ||
           // now we better have a comma
           !ExpectSymbol(',', true))) {
        SkipUntil(')');
        return false;
      }
    }

    if (cssGradient->mAngle.GetUnit() != eCSSUnit_None) {
      cssGradient->mIsLegacySyntax = true;
    }
  }

  // radial gradients might have a shape and size here for legacy syntax
  if (!haveShape && !haveSize) {
    haveShape =
      ParseSingleTokenVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD,
                              nsCSSProps::kRadialGradientShapeKTable);
    haveSize =
      ParseSingleTokenVariant(cssGradient->GetRadialSize(), VARIANT_KEYWORD,
                              nsCSSProps::kRadialGradientLegacySizeKTable);

    // could be in either order
    if (!haveShape) {
      haveShape =
        ParseSingleTokenVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD,
                                nsCSSProps::kRadialGradientShapeKTable);
    }
  }

  if ((haveShape || haveSize) && !ExpectSymbol(',', true)) {
    SkipUntil(')');
    return false;
  }

  return ParseGradientColorStops(cssGradient, aValue);
}

bool
CSSParserImpl::IsLegacyGradientLine(const nsCSSTokenType& aType,
                                    const nsString& aId)
{
  // N.B. ParseBoxPositionValues is not guaranteed to put back
  // everything it scanned if it fails, so we must only call it
  // if there is no alternative to consuming a <box-position>.
  // ParseVariant, as used here, will either succeed and consume
  // a single token, or fail and consume none, so we can be more
  // cavalier about calling it.

  bool haveGradientLine = false;
  switch (aType) {
  case eCSSToken_Percentage:
  case eCSSToken_Number:
  case eCSSToken_Dimension:
    haveGradientLine = true;
    break;

  case eCSSToken_Function:
    if (aId.LowerCaseEqualsLiteral("calc") ||
        aId.LowerCaseEqualsLiteral("-moz-calc")) {
      haveGradientLine = true;
      break;
    }
    MOZ_FALLTHROUGH;
  case eCSSToken_ID:
  case eCSSToken_Hash:
    // this is a color
    break;

  case eCSSToken_Ident: {
    // This is only a gradient line if it's a box position keyword.
    nsCSSKeyword kw = nsCSSKeywords::LookupKeyword(aId);
    int32_t junk;
    if (kw != eCSSKeyword_UNKNOWN &&
        nsCSSProps::FindKeyword(kw, nsCSSProps::kImageLayerPositionKTable,
                                junk)) {
      haveGradientLine = true;
    }
    break;
  }

  default:
    // error
    break;
  }

  return haveGradientLine;
}

bool
CSSParserImpl::ParseGradientColorStops(nsCSSValueGradient* aGradient,
                                       nsCSSValue& aValue)
{
  // At least two color stops are required
  if (!ParseColorStop(aGradient) ||
      !ExpectSymbol(',', true) ||
      !ParseColorStop(aGradient)) {
    SkipUntil(')');
    return false;
  }

  // Additional color stops
  while (ExpectSymbol(',', true)) {
    if (!ParseColorStop(aGradient)) {
      SkipUntil(')');
      return false;
    }
  }

  if (!ExpectSymbol(')', true)) {
    SkipUntil(')');
    return false;
  }

  // Check if interpolation hints are in the correct location
  bool previousPointWasInterpolationHint = true;
  for (size_t x = 0; x < aGradient->mStops.Length(); x++) {
    bool isInterpolationHint = aGradient->mStops[x].mIsInterpolationHint;
    if (isInterpolationHint && previousPointWasInterpolationHint) {
      return false;
    }
    previousPointWasInterpolationHint = isInterpolationHint;
  }

  if (previousPointWasInterpolationHint) {
    return false;
  }

  aValue.SetGradientValue(aGradient);
  return true;
}

// Parses the x or y component of a -webkit-gradient() <point> expression.
// See ParseWebkitGradientPoint() documentation for more.
bool
CSSParserImpl::ParseWebkitGradientPointComponent(nsCSSValue& aComponent,
                                                 bool aIsHorizontal)
{
  if (!GetToken(true)) {
    return false;
  }

  // Keyword tables to use for keyword-matching
  // (Keyword order is important; we assume the index can be multiplied by 50%
  // to convert to a percent-valued component.)
  static const nsCSSKeyword kHorizKeywords[] = {
    eCSSKeyword_left,   //   0%
    eCSSKeyword_center, //  50%
    eCSSKeyword_right   // 100%
  };
  static const nsCSSKeyword kVertKeywords[] = {
    eCSSKeyword_top,     //   0%
    eCSSKeyword_center,  //  50%
    eCSSKeyword_bottom   // 100%
  };
  static const size_t kNumKeywords = MOZ_ARRAY_LENGTH(kHorizKeywords);
  static_assert(kNumKeywords == MOZ_ARRAY_LENGTH(kVertKeywords),
                "Horizontal & vertical keyword tables must have same count");

  // Try to parse the component as a number, or a percent, or a
  // keyword-converted-to-percent.
  if (mToken.mType == eCSSToken_Number) {
    aComponent.SetFloatValue(mToken.mNumber, eCSSUnit_Pixel);
  } else if (mToken.mType == eCSSToken_Percentage) {
    aComponent.SetPercentValue(mToken.mNumber);
  } else if (mToken.mType == eCSSToken_Ident) {
    nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
    if (keyword == eCSSKeyword_UNKNOWN) {
      return false;
    }
    // Choose our keyword table:
    const nsCSSKeyword* kwTable = aIsHorizontal ? kHorizKeywords : kVertKeywords;
    // Convert keyword to percent value (0%, 50%, or 100%)
    bool didAcceptKeyword = false;
    for (size_t i = 0; i < kNumKeywords; i++) {
      if (keyword == kwTable[i]) {
        // 0%, 50%, or 100%:
        aComponent.SetPercentValue(i * 0.5);
        didAcceptKeyword = true;
        break;
      }
    }
    if (!didAcceptKeyword) {
      return false;
    }
  } else {
    // Unrecognized token type. Put it back. (It might be a closing-paren of an
    // invalid -webkit-gradient(...) expression, and we need to be sure caller
    // can see it & stops parsing at that point.)
    UngetToken();
    return false;
  }

  MOZ_ASSERT(aComponent.GetUnit() == eCSSUnit_Pixel ||
             aComponent.GetUnit() == eCSSUnit_Percent,
             "If we get here, we should've successfully parsed a number (as a "
             "pixel length), a percent, or a keyword (converted to percent)");
  return true;
}

// This function parses a "<point>" expression for -webkit-gradient(...)
// Quoting https://www.webkit.org/blog/175/introducing-css-gradients/ :
//   "A point is a pair of space-separated values.
//    The syntax supports numbers, percentages or
//    the keywords top, bottom, left and right
//    for point values."
//
// Two additional notes:
//  - WebKit also accepts the "center" keyword (not listed in the text above).
//  - WebKit only accepts horizontal-flavored keywords (left/center/right) in
//    the first ("x") component, and vertical-flavored keywords
//    (top/center/bottom) in the second ("y") component. (This is different
//    from the standard gradient syntax, which accepts both orderings, e.g.
//    "top left" as well as "left top".)
bool
CSSParserImpl::ParseWebkitGradientPoint(nsCSSValuePair& aPoint)
{
  return ParseWebkitGradientPointComponent(aPoint.mXValue, true) &&
    ParseWebkitGradientPointComponent(aPoint.mYValue, false);
}

// Parse the next token as a <number> (for a <radius> in a -webkit-gradient
// expresison).  Returns true on success; returns false & puts back
// whatever it parsed on failure.
bool
CSSParserImpl::ParseWebkitGradientRadius(float& aRadius)
{
  if (!GetToken(true)) {
    return false;
  }

  if (mToken.mType != eCSSToken_Number) {
    UngetToken();
    return false;
  }

  aRadius = mToken.mNumber;
  return true;
}

// Parse one of:
//  color-stop(number|percent, color)
//  from(color)
//  to(color)
//
// Quoting https://www.webkit.org/blog/175/introducing-css-gradients/ :
//   A stop is a function, color-stop, that takes two arguments, the stop value
//   (either a percentage or a number between 0 and 1.0), and a color (any
//   valid CSS color). In addition the shorthand functions from and to are
//   supported. These functions only require a color argument and are
//   equivalent to color-stop(0, ...) and color-stop(1.0, …) respectively.
bool
CSSParserImpl::ParseWebkitGradientColorStop(nsCSSValueGradient* aGradient)
{
  MOZ_ASSERT(aGradient, "null gradient");

  if (!GetToken(true)) {
    return false;
  }

  // We're expecting color-stop(...), from(...), or to(...) which are all
  // functions. Bail if we got anything else.
  if (mToken.mType != eCSSToken_Function) {
    UngetToken();
    return false;
  }

  nsCSSValueGradientStop* stop = aGradient->mStops.AppendElement();

  // Parse color-stop location (or infer it, for shorthands "from"/"to"):
  if (mToken.mIdent.LowerCaseEqualsLiteral("color-stop")) {
    // Parse stop location, followed by comma.
    if (!ParseSingleTokenVariant(stop->mLocation,
                                 VARIANT_NUMBER | VARIANT_PERCENT,
                                 nullptr) ||
        !ExpectSymbol(',', true)) {
      SkipUntil(')'); // Skip to end of color-stop(...) expression.
      return false;
    }

    // If we got a <number>, convert it to percentage for consistency:
    if (stop->mLocation.GetUnit() == eCSSUnit_Number) {
      stop->mLocation.SetPercentValue(stop->mLocation.GetFloatValue());
    }
  } else if (mToken.mIdent.LowerCaseEqualsLiteral("from")) {
    // Shorthand for color-stop(0%, ...)
    stop->mLocation.SetPercentValue(0.0f);
  } else if (mToken.mIdent.LowerCaseEqualsLiteral("to")) {
    // Shorthand for color-stop(100%, ...)
    stop->mLocation.SetPercentValue(1.0f);
  } else {
    // Unrecognized function name (invalid for a -webkit-gradient color stop).
    UngetToken();
    return false;
  }

  CSSParseResult result = ParseVariant(stop->mColor, VARIANT_COLOR, nullptr);
  if (result != CSSParseResult::Ok ||
      (stop->mColor.GetUnit() == eCSSUnit_EnumColor &&
       stop->mColor.GetIntValue() == NS_COLOR_CURRENTCOLOR)) {
    // Parse failure, or parsed "currentColor" which is forbidden in
    // -webkit-gradient for some reason.
    SkipUntil(')');
    return false;
  }

  // Parse color-stop function close-paren
  if (!ExpectSymbol(')', true)) {
    SkipUntil(')');
    return false;
  }

  MOZ_ASSERT(stop->mLocation.GetUnit() == eCSSUnit_Percent,
             "Should produce only percent-valued stop-locations. "
             "(Caller depends on this when sorting color stops.)");

  return true;
}

// Comparatison function to use for sorting -webkit-gradient() stops by
// location. This function assumes stops have percent-valued locations (and
// CSSParserImpl::ParseWebkitGradientColorStop should enforce this).
static bool
IsColorStopPctLocationLessThan(const nsCSSValueGradientStop& aStop1,
                               const nsCSSValueGradientStop& aStop2) {
  return (aStop1.mLocation.GetPercentValue() <
          aStop2.mLocation.GetPercentValue());
}

// This function parses a list of comma-separated color-stops for a
// -webkit-gradient(...) expression, and then pads & sorts the list as-needed.
bool
CSSParserImpl::ParseWebkitGradientColorStops(nsCSSValueGradient* aGradient)
{
  MOZ_ASSERT(aGradient, "null gradient");

  // Parse any number of ", <color-stop>" expressions. (0 or more)
  // Note: This is different from unprefixed gradient syntax, which
  // requires at least 2 stops.
  while (ExpectSymbol(',', true)) {
    if (!ParseWebkitGradientColorStop(aGradient)) {
      return false;
    }
  }

  // Pad up to 2 stops as-needed:
  // (Modern gradient expressions are required to have at least 2 stops, so we
  // depend on this internally -- e.g. we have an assertion about this in
  // nsCSSRendering.cpp. -webkit-gradient syntax allows 0 stops or 1 stop,
  // though, so we just pad up to 2 stops in this case).

  // If we have no stops, pad with transparent-black:
  if (aGradient->mStops.IsEmpty()) {
    nsCSSValueGradientStop* stop1 = aGradient->mStops.AppendElement();
    stop1->mColor.SetIntegerColorValue(NS_RGBA(0, 0, 0, 0),
                                       eCSSUnit_RGBAColor);
    stop1->mLocation.SetPercentValue(0.0f);

    nsCSSValueGradientStop* stop2 = aGradient->mStops.AppendElement();
    stop2->mColor.SetIntegerColorValue(NS_RGBA(0, 0, 0, 0),
                                       eCSSUnit_RGBAColor);
    stop2->mLocation.SetPercentValue(1.0f);
  } else if (aGradient->mStops.Length() == 1) {
    // Copy whatever the author provided in the first stop:
    nsCSSValueGradientStop* stop = aGradient->mStops.AppendElement();
    *stop = aGradient->mStops[0];
  } else {
    // We have >2 stops. Sort them in order of increasing location.
    std::stable_sort(aGradient->mStops.begin(),
                     aGradient->mStops.end(),
                     IsColorStopPctLocationLessThan);
  }
  return true;
}

// Compares aStartCoord to aEndCoord, and returns true iff they share the same
// unit (both pixel, or both percent) and aStartCoord is larger.
static bool
IsWebkitGradientCoordLarger(const nsCSSValue& aStartCoord,
                            const nsCSSValue& aEndCoord)
{
  if (aStartCoord.GetUnit() == eCSSUnit_Percent &&
      aEndCoord.GetUnit() == eCSSUnit_Percent) {
    return aStartCoord.GetPercentValue() > aEndCoord.GetPercentValue();
  }

  if (aStartCoord.GetUnit() == eCSSUnit_Pixel &&
      aEndCoord.GetUnit() == eCSSUnit_Pixel) {
    return aStartCoord.GetFloatValue() > aEndCoord.GetFloatValue();
  }

  // We can't compare them, since their units differ. Returning false suggests
  // that aEndCoord is larger, which is probably a decent guess anyway.
  return false;
}

// Finalize our internal representation of a -webkit-gradient(linear, ...)
// expression, given the parsed points.  (The parsed color stops
// should already be hanging off of the passed-in nsCSSValueGradient.)
//
// Note: linear gradients progress along a line between two points.  The
// -webkit-gradient(linear, ...) syntax lets the author precisely specify the
// starting and ending point. However, our internal gradient structures
// only store one point, and the other point is implicitly its reflection
// across the painted area's center. (The legacy -moz-linear-gradient syntax
// also lets us store an angle.)
//
// In this function, we try to go from the two-point representation to an
// equivalent or approximately-equivalent one-point representation.
void
CSSParserImpl::FinalizeLinearWebkitGradient(nsCSSValueGradient* aGradient,
                                            const nsCSSValuePair& aStartPoint,
                                            const nsCSSValuePair& aEndPoint)
{
  MOZ_ASSERT(!aGradient->mIsRadial, "passed-in gradient must be linear");

  // If the start & end points have the same Y-coordinate, then we can treat
  // this as a horizontal gradient progressing towards the center of the left
  // or right side.
  if (aStartPoint.mYValue == aEndPoint.mYValue) {
    aGradient->mBgPos.mYValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_CENTER,
                                          eCSSUnit_Enumerated);
    if (IsWebkitGradientCoordLarger(aStartPoint.mXValue, aEndPoint.mXValue)) {
      aGradient->mBgPos.mXValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_LEFT,
                                            eCSSUnit_Enumerated);
    } else {
      aGradient->mBgPos.mXValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_RIGHT,
                                            eCSSUnit_Enumerated);
    }
    return;
  }

  // If the start & end points have the same X-coordinate, then we can treat
  // this as a horizontal gradient progressing towards the center of the top
  // or bottom side.
  if (aStartPoint.mXValue == aEndPoint.mXValue) {
    aGradient->mBgPos.mXValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_CENTER,
                                          eCSSUnit_Enumerated);
    if (IsWebkitGradientCoordLarger(aStartPoint.mYValue, aEndPoint.mYValue)) {
      aGradient->mBgPos.mYValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_TOP,
                                            eCSSUnit_Enumerated);
    } else {
      aGradient->mBgPos.mYValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_BOTTOM,
                                            eCSSUnit_Enumerated);
    }
    return;
  }

  // OK, the gradient is angled, which means we likely can't represent it
  // exactly in |aGradient|, without doing analysis on the two points to
  // extract an angle (which we might not be able to do depending on the units
  // used).  For now, we'll just do something really basic -- just use the
  // first point as if it were the starting point in a legacy
  // -moz-linear-gradient() expression. That way, the rendered gradient will
  // progress from this first point, towards the center of the covered element,
  // to a reflected end point on the far side. Note that we have to use
  // mIsLegacySyntax=true for this to work, because standardized (non-legacy)
  // gradients place some restrictions on the reference point [namely, that it
  // use percent units & be on the border of the element].
  aGradient->mIsLegacySyntax = true;
  aGradient->mBgPos = aStartPoint;
}

// Finalize our internal representation of a -webkit-gradient(radial, ...)
// expression, given the parsed points & radii.  (The parsed color-stops
// should already be hanging off of the passed-in nsCSSValueGradient).
void
CSSParserImpl::FinalizeRadialWebkitGradient(nsCSSValueGradient* aGradient,
                                            const nsCSSValuePair& aFirstCenter,
                                            const nsCSSValuePair& aSecondCenter,
                                            const float aFirstRadius,
                                            const float aSecondRadius)
{
  MOZ_ASSERT(aGradient->mIsRadial, "passed-in gradient must be radial");

  // NOTE: -webkit-gradient(radial, ...) has *two arbitrary circles*, with the
  // gradient stretching between the circles' edges.  In contrast, the standard
  // syntax (and hence our data structures) can only represent *one* circle,
  // with the gradient going from its center to its edge.  To bridge this gap
  // in expressiveness, we'll just see which of our two circles is smaller, and
  // we'll treat that circle as if it were zero-sized and located at the center
  // of the larger circle. Then, we'll be able to use the same data structures
  // that we use for the standard radial-gradient syntax.
  if (aSecondRadius >= aFirstRadius) {
    // Second circle is larger.
    aGradient->mBgPos = aSecondCenter;
    aGradient->mIsExplicitSize = true;
    aGradient->GetRadiusX().SetFloatValue(aSecondRadius, eCSSUnit_Pixel);
    return;
  }

  // First circle is larger, so we'll have it be the outer circle.
  aGradient->mBgPos = aFirstCenter;
  aGradient->mIsExplicitSize = true;
  aGradient->GetRadiusX().SetFloatValue(aFirstRadius, eCSSUnit_Pixel);

  // For this to work properly (with the earlier color stops attached to the
  // first circle), we need to also reverse the color-stop list, so that
  // e.g. the author's "from" color is attached to the outer edge (the first
  // circle), rather than attached to the center (the collapsed second circle).
  std::reverse(aGradient->mStops.begin(), aGradient->mStops.end());

  // And now invert the stop locations:
  for (nsCSSValueGradientStop& colorStop : aGradient->mStops) {
    float origLocation = colorStop.mLocation.GetPercentValue();
    colorStop.mLocation.SetPercentValue(1.0f - origLocation);
  }
}

bool
CSSParserImpl::ParseWebkitGradient(nsCSSValue& aValue)
{
  // Parse type of gradient
  if (!GetToken(true)) {
    return false;
  }

  if (mToken.mType != eCSSToken_Ident) {
    UngetToken(); // Important; the token might be ")", which we're about to
                  // seek to.
    SkipUntil(')');
    return false;
  }

  bool isRadial;
  if (mToken.mIdent.LowerCaseEqualsLiteral("radial")) {
    isRadial = true;
  } else if (mToken.mIdent.LowerCaseEqualsLiteral("linear")) {
    isRadial = false;
  } else {
    // Unrecognized gradient type.
    SkipUntil(')');
    return false;
  }

  // Parse a comma + first point:
  nsCSSValuePair firstPoint;
  if (!ExpectSymbol(',', true) ||
      !ParseWebkitGradientPoint(firstPoint)) {
    SkipUntil(')');
    return false;
  }

  // If radial, parse comma + first radius:
  float firstRadius;
  if (isRadial) {
    if (!ExpectSymbol(',', true) ||
        !ParseWebkitGradientRadius(firstRadius)) {
      SkipUntil(')');
      return false;
    }
  }

  // Parse a comma + second point:
  nsCSSValuePair secondPoint;
  if (!ExpectSymbol(',', true) ||
      !ParseWebkitGradientPoint(secondPoint)) {
    SkipUntil(')');
    return false;
  }

  // If radial, parse comma + second radius:
  float secondRadius;
  if (isRadial) {
    if (!ExpectSymbol(',', true) ||
        !ParseWebkitGradientRadius(secondRadius)) {
      SkipUntil(')');
      return false;
    }
  }

  // Construct a nsCSSValueGradient object, and parse color stops into it:
  RefPtr<nsCSSValueGradient> cssGradient =
    new nsCSSValueGradient(isRadial, false /* aIsRepeating */);

  if (!ParseWebkitGradientColorStops(cssGradient) ||
      !ExpectSymbol(')', true)) {
    // Failed to parse color-stops, or found trailing junk between them & ')'.
    SkipUntil(')');
    return false;
  }

  // Finish building cssGradient, based on our parsed positioning/sizing info:
  if (isRadial) {
    FinalizeRadialWebkitGradient(cssGradient, firstPoint, secondPoint,
                           firstRadius, secondRadius);
  } else {
    FinalizeLinearWebkitGradient(cssGradient, firstPoint, secondPoint);
  }

  aValue.SetGradientValue(cssGradient);
  return true;
}

bool
CSSParserImpl::ParseWebkitTextStroke()
{
  static const nsCSSPropertyID kWebkitTextStrokeIDs[] = {
    eCSSProperty__webkit_text_stroke_width,
    eCSSProperty__webkit_text_stroke_color
  };

  const size_t numProps = MOZ_ARRAY_LENGTH(kWebkitTextStrokeIDs);
  nsCSSValue values[numProps];

  int32_t found = ParseChoice(values, kWebkitTextStrokeIDs, numProps);
  if (found < 1) {
    return false;
  }

  if (!(found & 1)) { // Provide default -webkit-text-stroke-width
    values[0].SetFloatValue(0, eCSSUnit_Pixel);
  }

  if (!(found & 2)) { // Provide default -webkit-text-stroke-color
    values[1].SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor);
  }

  for (size_t index = 0; index < numProps; ++index) {
    AppendValue(kWebkitTextStrokeIDs[index], values[index]);
  }

  return true;
}

  int32_t
CSSParserImpl::ParseChoice(nsCSSValue aValues[],
                           const nsCSSPropertyID aPropIDs[], int32_t aNumIDs)
{
  int32_t found = 0;
  nsAutoParseCompoundProperty compound(this);

  int32_t loop;
  for (loop = 0; loop < aNumIDs; loop++) {
    // Try each property parser in order
    int32_t hadFound = found;
    int32_t index;
    for (index = 0; index < aNumIDs; index++) {
      int32_t bit = 1 << index;
      if ((found & bit) == 0) {
        CSSParseResult result =
          ParseSingleValueProperty(aValues[index], aPropIDs[index]);
        if (result == CSSParseResult::Error) {
          return -1;
        }
        if (result == CSSParseResult::Ok) {
          found |= bit;
          // It's more efficient to break since it will reset |hadFound|
          // to |found|.  Furthermore, ParseListStyle depends on our going
          // through the properties in order for each value..
          break;
        }
      }
    }
    if (found == hadFound) {  // found nothing new
      break;
    }
  }
  if (0 < found) {
    if (1 == found) { // only first property
      if (eCSSUnit_Inherit == aValues[0].GetUnit()) { // one inherit, all inherit
        for (loop = 1; loop < aNumIDs; loop++) {
          aValues[loop].SetInheritValue();
        }
        found = ((1 << aNumIDs) - 1);
      }
      else if (eCSSUnit_Initial == aValues[0].GetUnit()) { // one initial, all initial
        for (loop = 1; loop < aNumIDs; loop++) {
          aValues[loop].SetInitialValue();
        }
        found = ((1 << aNumIDs) - 1);
      }
      else if (eCSSUnit_Unset == aValues[0].GetUnit()) { // one unset, all unset
        for (loop = 1; loop < aNumIDs; loop++) {
          aValues[loop].SetUnsetValue();
        }
        found = ((1 << aNumIDs) - 1);
      }
    }
    else {  // more than one value, verify no inherits, initials or unsets
      for (loop = 0; loop < aNumIDs; loop++) {
        if (eCSSUnit_Inherit == aValues[loop].GetUnit()) {
          found = -1;
          break;
        }
        else if (eCSSUnit_Initial == aValues[loop].GetUnit()) {
          found = -1;
          break;
        }
        else if (eCSSUnit_Unset == aValues[loop].GetUnit()) {
          found = -1;
          break;
        }
      }
    }
  }
  return found;
}

void
CSSParserImpl::AppendValue(nsCSSPropertyID aPropID, const nsCSSValue& aValue)
{
  mTempData.AddLonghandProperty(aPropID, aValue);
}

/**
 * Parse a "box" property. Box properties have 1 to 4 values. When less
 * than 4 values are provided a standard mapping is used to replicate
 * existing values.
 */
bool
CSSParserImpl::ParseBoxProperties(const nsCSSPropertyID aPropIDs[])
{
  // Get up to four values for the property
  int32_t count = 0;
  nsCSSRect result;
  NS_FOR_CSS_SIDES (index) {
    CSSParseResult parseResult =
      ParseBoxProperty(result.*(nsCSSRect::sides[index]), aPropIDs[index]);
    if (parseResult == CSSParseResult::NotFound) {
      break;
    }
    if (parseResult == CSSParseResult::Error) {
      return false;
    }
    count++;
  }
  if (count == 0) {
    return false;
  }

  if (1 < count) { // verify no more than single inherit, initial or unset
    NS_FOR_CSS_SIDES (index) {
      nsCSSUnit unit = (result.*(nsCSSRect::sides[index])).GetUnit();
      if (eCSSUnit_Inherit == unit ||
          eCSSUnit_Initial == unit ||
          eCSSUnit_Unset == unit) {
        return false;
      }
    }
  }

  // Provide missing values by replicating some of the values found
  switch (count) {
    case 1: // Make right == top
      result.mRight = result.mTop;
      MOZ_FALLTHROUGH;
    case 2: // Make bottom == top
      result.mBottom = result.mTop;
      MOZ_FALLTHROUGH;
    case 3: // Make left == right
      result.mLeft = result.mRight;
  }

  NS_FOR_CSS_SIDES (index) {
    AppendValue(aPropIDs[index], result.*(nsCSSRect::sides[index]));
  }
  return true;
}

// Similar to ParseBoxProperties, except there is only one property
// with the result as its value, not four.
bool
CSSParserImpl::ParseGroupedBoxProperty(int32_t aVariantMask,
                                       /** outparam */ nsCSSValue& aValue,
                                       uint32_t aRestrictions)
{
  nsCSSRect& result = aValue.SetRectValue();

  int32_t count = 0;
  NS_FOR_CSS_SIDES (index) {
    CSSParseResult parseResult =
      ParseVariantWithRestrictions(result.*(nsCSSRect::sides[index]),
                                   aVariantMask, nullptr,
                                   aRestrictions);
    if (parseResult == CSSParseResult::NotFound) {
      break;
    }
    if (parseResult == CSSParseResult::Error) {
      return false;
    }
    count++;
  }

  if (count == 0) {
    return false;
  }

  // Provide missing values by replicating some of the values found
  switch (count) {
    case 1: // Make right == top
      result.mRight = result.mTop;
      MOZ_FALLTHROUGH;
    case 2: // Make bottom == top
      result.mBottom = result.mTop;
      MOZ_FALLTHROUGH;
    case 3: // Make left == right
      result.mLeft = result.mRight;
  }

  return true;
}

bool
CSSParserImpl::ParseBoxCornerRadius(nsCSSPropertyID aPropID)
{
  nsCSSValue dimenX, dimenY;
  // required first value
  if (ParseNonNegativeVariant(dimenX, VARIANT_HLP | VARIANT_CALC, nullptr) !=
      CSSParseResult::Ok) {
    return false;
  }

  // optional second value (forbidden if first value is inherit/initial/unset)
  if (dimenX.GetUnit() != eCSSUnit_Inherit &&
      dimenX.GetUnit() != eCSSUnit_Initial &&
      dimenX.GetUnit() != eCSSUnit_Unset) {
    if (ParseNonNegativeVariant(dimenY, VARIANT_LP | VARIANT_CALC, nullptr) ==
        CSSParseResult::Error) {
      return false;
    }
  }

  if (dimenX == dimenY || dimenY.GetUnit() == eCSSUnit_Null) {
    AppendValue(aPropID, dimenX);
  } else {
    nsCSSValue value;
    value.SetPairValue(dimenX, dimenY);
    AppendValue(aPropID, value);
  }
  return true;
}

bool
CSSParserImpl::ParseBoxCornerRadiiInternals(nsCSSValue array[])
{
  // Rectangles are used as scratch storage.
  // top => top-left, right => top-right,
  // bottom => bottom-right, left => bottom-left.
  nsCSSRect dimenX, dimenY;
  int32_t countX = 0, countY = 0;

  NS_FOR_CSS_SIDES (side) {
    CSSParseResult result =
      ParseNonNegativeVariant(dimenX.*nsCSSRect::sides[side],
                              (side > 0 ? 0 : VARIANT_INHERIT) |
                                VARIANT_LP | VARIANT_CALC,
                              nullptr);
    if (result == CSSParseResult::Error) {
      return false;
    } else if (result == CSSParseResult::NotFound) {
      break;
    }
    countX++;
  }
  if (countX == 0)
    return false;

  if (ExpectSymbol('/', true)) {
    NS_FOR_CSS_SIDES (side) {
      CSSParseResult result =
        ParseNonNegativeVariant(dimenY.*nsCSSRect::sides[side],
                                VARIANT_LP | VARIANT_CALC, nullptr);
      if (result == CSSParseResult::Error) {
        return false;
      } else if (result == CSSParseResult::NotFound) {
        break;
      }
      countY++;
    }
    if (countY == 0)
      return false;
  }

  // if 'initial', 'inherit' or 'unset' was used, it must be the only value
  if (countX > 1 || countY > 0) {
    nsCSSUnit unit = dimenX.mTop.GetUnit();
    if (eCSSUnit_Inherit == unit ||
        eCSSUnit_Initial == unit ||
        eCSSUnit_Unset == unit)
      return false;
  }

  // if we have no Y-values, use the X-values
  if (countY == 0) {
    dimenY = dimenX;
    countY = countX;
  }

  // Provide missing values by replicating some of the values found
  switch (countX) {
    case 1: // Make top-right same as top-left
      dimenX.mRight = dimenX.mTop;
      MOZ_FALLTHROUGH;
    case 2: // Make bottom-right same as top-left
      dimenX.mBottom = dimenX.mTop;
      MOZ_FALLTHROUGH;
    case 3: // Make bottom-left same as top-right
      dimenX.mLeft = dimenX.mRight;
  }

  switch (countY) {
    case 1: // Make top-right same as top-left
      dimenY.mRight = dimenY.mTop;
      MOZ_FALLTHROUGH;
    case 2: // Make bottom-right same as top-left
      dimenY.mBottom = dimenY.mTop;
      MOZ_FALLTHROUGH;
    case 3: // Make bottom-left same as top-right
      dimenY.mLeft = dimenY.mRight;
  }

  NS_FOR_CSS_SIDES(side) {
    nsCSSValue& x = dimenX.*nsCSSRect::sides[side];
    nsCSSValue& y = dimenY.*nsCSSRect::sides[side];

    if (x == y) {
      array[side] = x;
    } else {
      nsCSSValue pair;
      pair.SetPairValue(x, y);
      array[side] = pair;
    }
  }
  return true;
}

bool
CSSParserImpl::ParseBoxCornerRadii(const nsCSSPropertyID aPropIDs[])
{
  nsCSSValue value[4];
  if (!ParseBoxCornerRadiiInternals(value)) {
    return false;
  }

  NS_FOR_CSS_SIDES(side) {
    AppendValue(aPropIDs[side], value[side]);
  }
  return true;
}

// These must be in CSS order (top,right,bottom,left) for indexing to work
static const nsCSSPropertyID kBorderStyleIDs[] = {
  eCSSProperty_border_top_style,
  eCSSProperty_border_right_style,
  eCSSProperty_border_bottom_style,
  eCSSProperty_border_left_style
};
static const nsCSSPropertyID kBorderWidthIDs[] = {
  eCSSProperty_border_top_width,
  eCSSProperty_border_right_width,
  eCSSProperty_border_bottom_width,
  eCSSProperty_border_left_width
};
static const nsCSSPropertyID kBorderColorIDs[] = {
  eCSSProperty_border_top_color,
  eCSSProperty_border_right_color,
  eCSSProperty_border_bottom_color,
  eCSSProperty_border_left_color
};
static const nsCSSPropertyID kBorderRadiusIDs[] = {
  eCSSProperty_border_top_left_radius,
  eCSSProperty_border_top_right_radius,
  eCSSProperty_border_bottom_right_radius,
  eCSSProperty_border_bottom_left_radius
};
static const nsCSSPropertyID kOutlineRadiusIDs[] = {
  eCSSProperty__moz_outline_radius_topLeft,
  eCSSProperty__moz_outline_radius_topRight,
  eCSSProperty__moz_outline_radius_bottomRight,
  eCSSProperty__moz_outline_radius_bottomLeft
};

void
CSSParserImpl::SaveInputState(CSSParserInputState& aState)
{
  aState.mToken = mToken;
  aState.mHavePushBack = mHavePushBack;
  mScanner->SavePosition(aState.mPosition);
}

void
CSSParserImpl::RestoreSavedInputState(const CSSParserInputState& aState)
{
  mToken = aState.mToken;
  mHavePushBack = aState.mHavePushBack;
  mScanner->RestoreSavedPosition(aState.mPosition);
}

bool
CSSParserImpl::ParseProperty(nsCSSPropertyID aPropID)
{
  // Can't use AutoRestore<bool> because it's a bitfield.
  MOZ_ASSERT(!mHashlessColorQuirk,
             "hashless color quirk should not be set");
  MOZ_ASSERT(!mUnitlessLengthQuirk,
             "unitless length quirk should not be set");
  MOZ_ASSERT(aPropID != eCSSPropertyExtra_variable);

  if (mNavQuirkMode) {
    mHashlessColorQuirk =
      nsCSSProps::PropHasFlags(aPropID, CSS_PROPERTY_HASHLESS_COLOR_QUIRK);
    mUnitlessLengthQuirk =
      nsCSSProps::PropHasFlags(aPropID, CSS_PROPERTY_UNITLESS_LENGTH_QUIRK);
  }

  // Save the current input state so that we can restore it later if we
  // have to re-parse the property value as a variable-reference-containing
  // token stream.
  CSSParserInputState stateBeforeProperty;
  SaveInputState(stateBeforeProperty);
  mScanner->ClearSeenVariableReference();

  NS_ASSERTION(aPropID < eCSSProperty_COUNT, "index out of range");
  bool allowVariables = true;
  bool result;
  switch (nsCSSProps::PropertyParseType(aPropID)) {
    case CSS_PROPERTY_PARSE_INACCESSIBLE: {
      // The user can't use these
      REPORT_UNEXPECTED(PEInaccessibleProperty2);
      allowVariables = false;
      result = false;
      break;
    }
    case CSS_PROPERTY_PARSE_FUNCTION: {
      result = ParsePropertyByFunction(aPropID);
      break;
    }
    case CSS_PROPERTY_PARSE_VALUE: {
      result = false;
      nsCSSValue value;
      if (ParseSingleValueProperty(value, aPropID) == CSSParseResult::Ok) {
        AppendValue(aPropID, value);
        result = true;
      }
      // XXX Report errors?
      break;
    }
    case CSS_PROPERTY_PARSE_VALUE_LIST: {
      result = ParseValueList(aPropID);
      break;
    }
    default: {
      result = false;
      allowVariables = false;
      MOZ_ASSERT(false,
                 "Property's flags field in nsCSSPropList.h is missing "
                 "one of the CSS_PROPERTY_PARSE_* constants");
      break;
    }
  }

  if (result) {
    // We need to call ExpectEndProperty() to decide whether to reparse
    // with variables.  This is needed because the property parsing may
    // have stopped upon finding a variable (e.g., 'margin: 1px var(a)')
    // in a way that future variable substitutions will be valid, or
    // because it parsed everything that's possible but we still want to
    // act as though the property contains variables even though we know
    // the substitution will never work (e.g., for 'margin: 1px 2px 3px
    // 4px 5px var(a)').
    //
    // It would be nice to find a better solution here
    // (and for the SkipUntilOneOf below), though, that doesn't depend
    // on using what we don't accept for doing parsing correctly.
    if (!ExpectEndProperty()) {
      result = false;
    }
  }

  bool seenVariable = mScanner->SeenVariableReference() ||
    (stateBeforeProperty.mHavePushBack &&
     stateBeforeProperty.mToken.mType == eCSSToken_Function &&
     stateBeforeProperty.mToken.mIdent.LowerCaseEqualsLiteral("var"));
  bool parseAsTokenStream;

  if (!result && allowVariables) {
    parseAsTokenStream = true;
    if (!seenVariable) {
      // We might have stopped parsing the property before its end and before
      // finding a variable reference.  Keep checking until the end of the
      // property.
      CSSParserInputState stateAtError;
      SaveInputState(stateAtError);

      const char16_t stopChars[] = { ';', '!', '}', ')', 0 };
      SkipUntilOneOf(stopChars);
      UngetToken();
      parseAsTokenStream = mScanner->SeenVariableReference();

      if (!parseAsTokenStream) {
        // If we parsed to the end of the propery and didn't find any variable
        // references, then the real position we want to report the error at
        // is |stateAtError|.
        RestoreSavedInputState(stateAtError);
      }
    }
  } else {
    parseAsTokenStream = false;
  }

  if (parseAsTokenStream) {
    // Go back to the start of the property value and parse it to make sure
    // its variable references are syntactically valid and is otherwise
    // balanced.
    RestoreSavedInputState(stateBeforeProperty);

    if (!mInSupportsCondition) {
      mScanner->StartRecording();
    }

    CSSVariableDeclarations::Type type;
    bool dropBackslash;
    nsString impliedCharacters;
    nsCSSValue value;
    if (ParseValueWithVariables(&type, &dropBackslash, impliedCharacters,
                                nullptr, nullptr)) {
      MOZ_ASSERT(type == CSSVariableDeclarations::eTokenStream,
                 "a non-custom property reparsed since it contained variable "
                 "references should not have been 'initial' or 'inherit'");

      nsString propertyValue;

      if (!mInSupportsCondition) {
        // If we are in an @supports condition, we don't need to store the
        // actual token stream on the nsCSSValue.
        mScanner->StopRecording(propertyValue);
        if (dropBackslash) {
          MOZ_ASSERT(!propertyValue.IsEmpty() &&
                     propertyValue[propertyValue.Length() - 1] == '\\');
          propertyValue.Truncate(propertyValue.Length() - 1);
        }
        propertyValue.Append(impliedCharacters);
      }

      if (mHavePushBack) {
        // If we came to the end of a property value that had a variable
        // reference and a token was pushed back, then it would have been
        // ended by '!', ')', ';', ']' or '}'.  We should remove it from the
        // recorded property value.
        MOZ_ASSERT(mToken.IsSymbol('!') ||
                   mToken.IsSymbol(')') ||
                   mToken.IsSymbol(';') ||
                   mToken.IsSymbol(']') ||
                   mToken.IsSymbol('}'));
        if (!mInSupportsCondition) {
          MOZ_ASSERT(!propertyValue.IsEmpty());
          MOZ_ASSERT(propertyValue[propertyValue.Length() - 1] ==
                     mToken.mSymbol);
          propertyValue.Truncate(propertyValue.Length() - 1);
        }
      }

      if (!mInSupportsCondition) {
        if (nsCSSProps::IsShorthand(aPropID)) {
          // If this is a shorthand property, we store the token stream on each
          // of its corresponding longhand properties.
          CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aPropID, EnabledState()) {
            nsCSSValueTokenStream* tokenStream = new nsCSSValueTokenStream;
            tokenStream->mPropertyID = *p;
            tokenStream->mShorthandPropertyID = aPropID;
            tokenStream->mTokenStream = propertyValue;
            tokenStream->mBaseURI = mBaseURI;
            tokenStream->mSheetURI = mSheetURI;
            tokenStream->mSheetPrincipal = mSheetPrincipal;
            // XXX Should store sheet here (see bug 952338).
            // tokenStream->mSheet = mSheet;
            tokenStream->mLineNumber = stateBeforeProperty.mPosition.LineNumber();
            tokenStream->mLineOffset = stateBeforeProperty.mPosition.LineOffset();
            value.SetTokenStreamValue(tokenStream);
            AppendValue(*p, value);
          }
        } else {
          nsCSSValueTokenStream* tokenStream = new nsCSSValueTokenStream;
          tokenStream->mPropertyID = aPropID;
          tokenStream->mTokenStream = propertyValue;
          tokenStream->mBaseURI = mBaseURI;
          tokenStream->mSheetURI = mSheetURI;
          tokenStream->mSheetPrincipal = mSheetPrincipal;
          // XXX Should store sheet here (see bug 952338).
          // tokenStream->mSheet = mSheet;
          tokenStream->mLineNumber = stateBeforeProperty.mPosition.LineNumber();
          tokenStream->mLineOffset = stateBeforeProperty.mPosition.LineOffset();
          value.SetTokenStreamValue(tokenStream);
          AppendValue(aPropID, value);
        }
      }
      result = true;
    } else {
      if (!mInSupportsCondition) {
        mScanner->StopRecording();
      }
    }
  }

  if (mNavQuirkMode) {
    mHashlessColorQuirk = false;
    mUnitlessLengthQuirk = false;
  }

  return result;
}

bool
CSSParserImpl::ParsePropertyByFunction(nsCSSPropertyID aPropID)
{
  switch (aPropID) {  // handle shorthand or multiple properties
  case eCSSProperty_place_content:
    return ParsePlaceContent();
  case eCSSProperty_place_items:
    return ParsePlaceItems();
  case eCSSProperty_place_self:
    return ParsePlaceSelf();
  case eCSSProperty_background:
    return ParseImageLayers(nsStyleImageLayers::kBackgroundLayerTable);
  case eCSSProperty_background_repeat:
    return ParseImageLayerRepeat(eCSSProperty_background_repeat);
  case eCSSProperty_background_position:
    return ParseImageLayerPosition(nsStyleImageLayers::kBackgroundLayerTable);
  case eCSSProperty_background_position_x:
  case eCSSProperty_background_position_y:
    return ParseImageLayerPositionCoord(aPropID,
               aPropID == eCSSProperty_background_position_x);
  case eCSSProperty_background_size:
    return ParseImageLayerSize(eCSSProperty_background_size);
  case eCSSProperty_border:
    return ParseBorderSide(kBorderTopIDs, true);
  case eCSSProperty_border_color:
    return ParseBorderColor();
  case eCSSProperty_border_spacing:
    return ParseBorderSpacing();
  case eCSSProperty_border_style:
    return ParseBorderStyle();
  case eCSSProperty_border_block_end:
    return ParseBorderSide(kBorderBlockEndIDs, false);
  case eCSSProperty_border_block_start:
    return ParseBorderSide(kBorderBlockStartIDs, false);
  case eCSSProperty_border_bottom:
    return ParseBorderSide(kBorderBottomIDs, false);
  case eCSSProperty_border_inline_end:
    return ParseBorderSide(kBorderInlineEndIDs, false);
  case eCSSProperty_border_inline_start:
    return ParseBorderSide(kBorderInlineStartIDs, false);
  case eCSSProperty_border_left:
    return ParseBorderSide(kBorderLeftIDs, false);
  case eCSSProperty_border_right:
    return ParseBorderSide(kBorderRightIDs, false);
  case eCSSProperty_border_top:
    return ParseBorderSide(kBorderTopIDs, false);
  case eCSSProperty_border_bottom_colors:
  case eCSSProperty_border_left_colors:
  case eCSSProperty_border_right_colors:
  case eCSSProperty_border_top_colors:
    return ParseBorderColors(aPropID);
  case eCSSProperty_border_image_slice:
    return ParseBorderImageSlice(true, nullptr);
  case eCSSProperty_border_image_width:
    return ParseBorderImageWidth(true);
  case eCSSProperty_border_image_outset:
    return ParseBorderImageOutset(true);
  case eCSSProperty_border_image_repeat:
    return ParseBorderImageRepeat(true);
  case eCSSProperty_border_image:
    return ParseBorderImage();
  case eCSSProperty_border_width:
    return ParseBorderWidth();
  case eCSSProperty_border_radius:
    return ParseBoxCornerRadii(kBorderRadiusIDs);
  case eCSSProperty__moz_outline_radius:
    return ParseBoxCornerRadii(kOutlineRadiusIDs);

  case eCSSProperty_border_top_left_radius:
  case eCSSProperty_border_top_right_radius:
  case eCSSProperty_border_bottom_right_radius:
  case eCSSProperty_border_bottom_left_radius:
  case eCSSProperty__moz_outline_radius_topLeft:
  case eCSSProperty__moz_outline_radius_topRight:
  case eCSSProperty__moz_outline_radius_bottomRight:
  case eCSSProperty__moz_outline_radius_bottomLeft:
    return ParseBoxCornerRadius(aPropID);

  case eCSSProperty_box_shadow:
  case eCSSProperty_text_shadow:
    return ParseShadowList(aPropID);

  case eCSSProperty_clip:
    return ParseRect(eCSSProperty_clip);
  case eCSSProperty_columns:
    return ParseColumns();
  case eCSSProperty_column_rule:
    return ParseBorderSide(kColumnRuleIDs, false);
  case eCSSProperty_content:
    return ParseContent();
  case eCSSProperty_counter_increment:
  case eCSSProperty_counter_reset:
    return ParseCounterData(aPropID);
  case eCSSProperty_cursor:
    return ParseCursor();
  case eCSSProperty_filter:
    return ParseFilter();
  case eCSSProperty_flex:
    return ParseFlex();
  case eCSSProperty_flex_flow:
    return ParseFlexFlow();
  case eCSSProperty_font:
    return ParseFont();
  case eCSSProperty_font_variant:
    return ParseFontVariant();
  case eCSSProperty_grid_auto_flow:
    return ParseGridAutoFlow();
  case eCSSProperty_grid_auto_columns:
  case eCSSProperty_grid_auto_rows:
    return ParseGridAutoColumnsRows(aPropID);
  case eCSSProperty_grid_template_areas:
    return ParseGridTemplateAreas();
  case eCSSProperty_grid_template_columns:
  case eCSSProperty_grid_template_rows:
    return ParseGridTemplateColumnsRows(aPropID);
  case eCSSProperty_grid_template:
    return ParseGridTemplate();
  case eCSSProperty_grid:
    return ParseGrid();
  case eCSSProperty_grid_column_start:
  case eCSSProperty_grid_column_end:
  case eCSSProperty_grid_row_start:
  case eCSSProperty_grid_row_end:
    return ParseGridColumnRowStartEnd(aPropID);
  case eCSSProperty_grid_column:
    return ParseGridColumnRow(eCSSProperty_grid_column_start,
                              eCSSProperty_grid_column_end);
  case eCSSProperty_grid_row:
    return ParseGridColumnRow(eCSSProperty_grid_row_start,
                              eCSSProperty_grid_row_end);
  case eCSSProperty_grid_area:
    return ParseGridArea();
  case eCSSProperty_grid_gap:
    return ParseGridGap();
  case eCSSProperty_image_region:
    return ParseRect(eCSSProperty_image_region);
  case eCSSProperty_align_content:
  case eCSSProperty_justify_content:
    return ParseAlignJustifyContent(aPropID);
  case eCSSProperty_align_items:
    return ParseAlignItems();
  case eCSSProperty_align_self:
  case eCSSProperty_justify_self:
    return ParseAlignJustifySelf(aPropID);
  case eCSSProperty_initial_letter:
    return ParseInitialLetter();
  case eCSSProperty_justify_items:
    return ParseJustifyItems();
  case eCSSProperty_list_style:
    return ParseListStyle();
  case eCSSProperty_margin:
    return ParseMargin();
  case eCSSProperty_object_position:
    return ParseObjectPosition();
  case eCSSProperty_outline:
    return ParseOutline();
  case eCSSProperty_overflow:
    return ParseOverflow();
  case eCSSProperty_padding:
    return ParsePadding();
  case eCSSProperty_quotes:
    return ParseQuotes();
  case eCSSProperty_text_decoration:
    return ParseTextDecoration();
  case eCSSProperty_text_emphasis:
    return ParseTextEmphasis();
  case eCSSProperty_will_change:
    return ParseWillChange();
  case eCSSProperty_transform:
    return ParseTransform(false);
  case eCSSProperty__moz_transform:
    return ParseTransform(true);
  case eCSSProperty_transform_origin:
    return ParseTransformOrigin(false);
  case eCSSProperty_perspective_origin:
    return ParseTransformOrigin(true);
  case eCSSProperty_transition:
    return ParseTransition();
  case eCSSProperty_animation:
    return ParseAnimation();
  case eCSSProperty_transition_property:
    return ParseTransitionProperty();
  case eCSSProperty_fill:
  case eCSSProperty_stroke:
    return ParsePaint(aPropID);
  case eCSSProperty_stroke_dasharray:
    return ParseDasharray();
  case eCSSProperty_marker:
    return ParseMarker();
  case eCSSProperty_paint_order:
    return ParsePaintOrder();
  case eCSSProperty_scroll_snap_type:
    return ParseScrollSnapType();
  case eCSSProperty_mask:
    return ParseImageLayers(nsStyleImageLayers::kMaskLayerTable);
  case eCSSProperty_mask_repeat:
    return ParseImageLayerRepeat(eCSSProperty_mask_repeat);
  case eCSSProperty_mask_position:
    return ParseImageLayerPosition(nsStyleImageLayers::kMaskLayerTable);
  case eCSSProperty_mask_position_x:
  case eCSSProperty_mask_position_y:
    return ParseImageLayerPositionCoord(aPropID,
               aPropID == eCSSProperty_mask_position_x);
  case eCSSProperty_mask_size:
    return ParseImageLayerSize(eCSSProperty_mask_size);
  case eCSSProperty__webkit_text_stroke:
    return ParseWebkitTextStroke();
  case eCSSProperty_all:
    return ParseAll();
  default:
    MOZ_ASSERT(false, "should not be called");
    return false;
  }
}

// Bits used in determining which background position info we have
#define BG_CENTER  NS_STYLE_IMAGELAYER_POSITION_CENTER
#define BG_TOP     NS_STYLE_IMAGELAYER_POSITION_TOP
#define BG_BOTTOM  NS_STYLE_IMAGELAYER_POSITION_BOTTOM
#define BG_LEFT    NS_STYLE_IMAGELAYER_POSITION_LEFT
#define BG_RIGHT   NS_STYLE_IMAGELAYER_POSITION_RIGHT
#define BG_CTB    (BG_CENTER | BG_TOP | BG_BOTTOM)
#define BG_TB     (BG_TOP | BG_BOTTOM)
#define BG_CLR    (BG_CENTER | BG_LEFT | BG_RIGHT)
#define BG_LR     (BG_LEFT | BG_RIGHT)

CSSParseResult
CSSParserImpl::ParseBoxProperty(nsCSSValue& aValue,
                                nsCSSPropertyID aPropID)
{
  if (aPropID < 0 || aPropID >= eCSSProperty_COUNT_no_shorthands) {
    MOZ_ASSERT(false, "must only be called for longhand properties");
    return CSSParseResult::NotFound;
  }

  MOZ_ASSERT(!nsCSSProps::PropHasFlags(aPropID,
                                       CSS_PROPERTY_VALUE_PARSER_FUNCTION),
             "must only be called for non-function-parsed properties");

  uint32_t variant = nsCSSProps::ParserVariant(aPropID);
  if (variant == 0) {
    MOZ_ASSERT(false, "must only be called for variant-parsed properties");
    return CSSParseResult::NotFound;
  }

  if (variant & ~(VARIANT_AHKLP | VARIANT_COLOR | VARIANT_CALC)) {
    MOZ_ASSERT(false, "must only be called for properties that take certain "
                      "variants");
    return CSSParseResult::NotFound;
  }

  const KTableEntry* kwtable = nsCSSProps::kKeywordTableTable[aPropID];
  uint32_t restrictions = nsCSSProps::ValueRestrictions(aPropID);

  return ParseVariantWithRestrictions(aValue, variant, kwtable, restrictions);
}

bool
CSSParserImpl::ParseSingleValuePropertyByFunction(nsCSSValue& aValue,
                                                  nsCSSPropertyID aPropID)
{
  switch (aPropID) {
    case eCSSProperty_clip_path:
      return ParseClipPath(aValue);
    case eCSSProperty_contain:
      return ParseContain(aValue);
    case eCSSProperty_font_family:
      return ParseFamily(aValue);
    case eCSSProperty_font_synthesis:
      return ParseFontSynthesis(aValue);
    case eCSSProperty_font_variant_alternates:
      return ParseFontVariantAlternates(aValue);
    case eCSSProperty_font_variant_east_asian:
      return ParseFontVariantEastAsian(aValue);
    case eCSSProperty_font_variant_ligatures:
      return ParseFontVariantLigatures(aValue);
    case eCSSProperty_font_variant_numeric:
      return ParseFontVariantNumeric(aValue);
    case eCSSProperty_font_feature_settings:
      return ParseFontFeatureSettings(aValue);
    case eCSSProperty_font_variation_settings:
      return ParseFontVariationSettings(aValue);
    case eCSSProperty_font_weight:
      return ParseFontWeight(aValue);
    case eCSSProperty_image_orientation:
      return ParseImageOrientation(aValue);
    case eCSSProperty_list_style_type:
      return ParseListStyleType(aValue);
    case eCSSProperty_scroll_snap_points_x:
      return ParseScrollSnapPoints(aValue, eCSSProperty_scroll_snap_points_x);
    case eCSSProperty_scroll_snap_points_y:
      return ParseScrollSnapPoints(aValue, eCSSProperty_scroll_snap_points_y);
    case eCSSProperty_scroll_snap_destination:
      return ParseScrollSnapDestination(aValue);
    case eCSSProperty_scroll_snap_coordinate:
      return ParseScrollSnapCoordinate(aValue);
    case eCSSProperty_shape_outside:
      return ParseShapeOutside(aValue);
    case eCSSProperty_text_align:
      return ParseTextAlign(aValue);
    case eCSSProperty_text_align_last:
      return ParseTextAlignLast(aValue);
    case eCSSProperty_text_decoration_line:
      return ParseTextDecorationLine(aValue);
    case eCSSProperty_text_combine_upright:
      return ParseTextCombineUpright(aValue);
    case eCSSProperty_text_emphasis_position:
      return ParseTextEmphasisPosition(aValue);
    case eCSSProperty_text_emphasis_style:
      return ParseTextEmphasisStyle(aValue);
    case eCSSProperty_text_overflow:
      return ParseTextOverflow(aValue);
    case eCSSProperty_touch_action:
      return ParseTouchAction(aValue);
    default:
      MOZ_ASSERT(false, "should not reach here");
      return false;
  }
}

CSSParseResult
CSSParserImpl::ParseSingleValueProperty(nsCSSValue& aValue,
                                        nsCSSPropertyID aPropID)
{
  if (aPropID == eCSSPropertyExtra_x_none_value) {
    return ParseVariant(aValue, VARIANT_NONE | VARIANT_INHERIT, nullptr);
  }

  if (aPropID == eCSSPropertyExtra_x_auto_value) {
    return ParseVariant(aValue, VARIANT_AUTO | VARIANT_INHERIT, nullptr);
  }

  if (aPropID < 0 || aPropID >= eCSSProperty_COUNT_no_shorthands) {
    MOZ_ASSERT(false, "not a single value property");
    return CSSParseResult::NotFound;
  }

  if (nsCSSProps::PropHasFlags(aPropID, CSS_PROPERTY_VALUE_PARSER_FUNCTION)) {
    uint32_t lineBefore, colBefore;
    if (!GetNextTokenLocation(true, &lineBefore, &colBefore)) {
      // We're at EOF before parsing.
      return CSSParseResult::NotFound;
    }

    if (ParseSingleValuePropertyByFunction(aValue, aPropID)) {
      return CSSParseResult::Ok;
    }

    uint32_t lineAfter, colAfter;
    if (!GetNextTokenLocation(true, &lineAfter, &colAfter) ||
        lineAfter != lineBefore ||
        colAfter != colBefore) {
      // Any single token value that was invalid will have been pushed back,
      // so GetNextTokenLocation encountering EOF means we failed while
      // parsing a multi-token value.
      return CSSParseResult::Error;
    }

    return CSSParseResult::NotFound;
  }

  uint32_t variant = nsCSSProps::ParserVariant(aPropID);
  if (variant == 0) {
    MOZ_ASSERT(false, "not a single value property");
    return CSSParseResult::NotFound;
  }

  const KTableEntry* kwtable = nsCSSProps::kKeywordTableTable[aPropID];
  uint32_t restrictions = nsCSSProps::ValueRestrictions(aPropID);
  return ParseVariantWithRestrictions(aValue, variant, kwtable, restrictions);
}

// font-descriptor: descriptor ':' value ';'
// caller has advanced mToken to point at the descriptor
bool
CSSParserImpl::ParseFontDescriptorValue(nsCSSFontDesc aDescID,
                                        nsCSSValue& aValue)
{
  switch (aDescID) {
    // These four are similar to the properties of the same name,
    // possibly with more restrictions on the values they can take.
  case eCSSFontDesc_Family: {
    nsCSSValue value;
    if (!ParseFamily(value) ||
        value.GetUnit() != eCSSUnit_FontFamilyList)
      return false;

    // name can only be a single, non-generic name
    const FontFamilyList* f = value.GetFontFamilyListValue();
    const nsTArray<FontFamilyName>& fontlist = f->GetFontlist();

    if (fontlist.Length() != 1 || !fontlist[0].IsNamed()) {
      return false;
    }

    aValue.SetStringValue(fontlist[0].mName, eCSSUnit_String);
    return true;
  }

  case eCSSFontDesc_Style:
    // property is VARIANT_HMK|VARIANT_SYSFONT
    return ParseSingleTokenVariant(aValue, VARIANT_KEYWORD | VARIANT_NORMAL,
                                   nsCSSProps::kFontStyleKTable);

  case eCSSFontDesc_Display:
    return ParseSingleTokenVariant(aValue, VARIANT_KEYWORD,
                                   nsCSSProps::kFontDisplayKTable);

  case eCSSFontDesc_Weight:
    return (ParseFontWeight(aValue) &&
            aValue.GetUnit() != eCSSUnit_Inherit &&
            aValue.GetUnit() != eCSSUnit_Initial &&
            aValue.GetUnit() != eCSSUnit_Unset &&
            (aValue.GetUnit() != eCSSUnit_Enumerated ||
             (aValue.GetIntValue() != NS_STYLE_FONT_WEIGHT_BOLDER &&
              aValue.GetIntValue() != NS_STYLE_FONT_WEIGHT_LIGHTER)));

  case eCSSFontDesc_Stretch:
    // property is VARIANT_HK|VARIANT_SYSFONT
    return ParseSingleTokenVariant(aValue, VARIANT_KEYWORD,
                                   nsCSSProps::kFontStretchKTable);

    // These two are unique to @font-face and have their own special grammar.
  case eCSSFontDesc_Src:
    return ParseFontSrc(aValue);

  case eCSSFontDesc_UnicodeRange:
    return ParseFontRanges(aValue);

  case eCSSFontDesc_FontFeatureSettings:
    return ParseFontFeatureSettings(aValue);

  case eCSSFontDesc_FontLanguageOverride:
    return ParseSingleTokenVariant(aValue, VARIANT_NORMAL | VARIANT_STRING,
                                   nullptr);

  case eCSSFontDesc_UNKNOWN:
  case eCSSFontDesc_COUNT:
    NS_NOTREACHED("bad nsCSSFontDesc code");
  }
  // explicitly do NOT have a default case to let the compiler
  // help find missing descriptors
  return false;
}

static nsCSSValue
BoxPositionMaskToCSSValue(int32_t aMask, bool isX)
{
  int32_t val = NS_STYLE_IMAGELAYER_POSITION_CENTER;
  if (isX) {
    if (aMask & BG_LEFT) {
      val = NS_STYLE_IMAGELAYER_POSITION_LEFT;
    }
    else if (aMask & BG_RIGHT) {
      val = NS_STYLE_IMAGELAYER_POSITION_RIGHT;
    }
  }
  else {
    if (aMask & BG_TOP) {
      val = NS_STYLE_IMAGELAYER_POSITION_TOP;
    }
    else if (aMask & BG_BOTTOM) {
      val = NS_STYLE_IMAGELAYER_POSITION_BOTTOM;
    }
  }

  return nsCSSValue(val, eCSSUnit_Enumerated);
}

bool
CSSParserImpl::ParseImageLayers(const nsCSSPropertyID aTable[])
{
  nsAutoParseCompoundProperty compound(this);

  // background-color can only be set once, so it's not a list.
  nsCSSValue color;

  // Check first for inherit/initial/unset.
  if (ParseSingleTokenVariant(color, VARIANT_INHERIT, nullptr)) {
    // must be alone
    for (const nsCSSPropertyID* subprops =
           nsCSSProps::SubpropertyEntryFor(aTable[nsStyleImageLayers::shorthand]);
         *subprops != eCSSProperty_UNKNOWN; ++subprops) {
      AppendValue(*subprops, color);
    }
    return true;
  }

  nsCSSValue image, repeat, attachment, clip, origin, positionX, positionY, size,
             composite, maskMode;
  ImageLayersShorthandParseState state(color, image.SetListValue(),
                                       repeat.SetPairListValue(),
                                       attachment.SetListValue(), clip.SetListValue(),
                                       origin.SetListValue(),
                                       positionX.SetListValue(), positionY.SetListValue(),
                                       size.SetPairListValue(), composite.SetListValue(),
                                       maskMode.SetListValue());

  for (;;) {
    if (!ParseImageLayersItem(state, aTable)) {
      return false;
    }

    // If we saw a color, this must be the last item.
    if (color.GetUnit() != eCSSUnit_Null) {
      MOZ_ASSERT(aTable[nsStyleImageLayers::color] != eCSSProperty_UNKNOWN);
      break;
    }

    // If there's a comma, expect another item.
    if (!ExpectSymbol(',', true)) {
      break;
    }

#define APPENDNEXT(propID_, propMember_, propType_) \
  if (aTable[propID_] != eCSSProperty_UNKNOWN) { \
    propMember_->mNext = new propType_; \
    propMember_ = propMember_->mNext; \
  }
    // Chain another entry on all the lists.
    APPENDNEXT(nsStyleImageLayers::image, state.mImage,
               nsCSSValueList);
    APPENDNEXT(nsStyleImageLayers::repeat, state.mRepeat,
               nsCSSValuePairList);
    APPENDNEXT(nsStyleImageLayers::clip, state.mClip,
               nsCSSValueList);
    APPENDNEXT(nsStyleImageLayers::origin, state.mOrigin,
               nsCSSValueList);
    APPENDNEXT(nsStyleImageLayers::positionX, state.mPositionX,
               nsCSSValueList);
    APPENDNEXT(nsStyleImageLayers::positionY, state.mPositionY,
               nsCSSValueList);
    APPENDNEXT(nsStyleImageLayers::size, state.mSize,
               nsCSSValuePairList);
    APPENDNEXT(nsStyleImageLayers::attachment, state.mAttachment,
               nsCSSValueList);
    APPENDNEXT(nsStyleImageLayers::maskMode, state.mMode,
               nsCSSValueList);
    APPENDNEXT(nsStyleImageLayers::composite, state.mComposite,
               nsCSSValueList);
#undef APPENDNEXT
  }

  // If we get to this point without seeing a color, provide a default.
 if (aTable[nsStyleImageLayers::color] != eCSSProperty_UNKNOWN) {
    if (color.GetUnit() == eCSSUnit_Null) {
      color.SetIntegerColorValue(NS_RGBA(0,0,0,0), eCSSUnit_RGBAColor);
    }
  }

#define APPENDVALUE(propID_, propValue_) \
  if (propID_ != eCSSProperty_UNKNOWN) { \
    AppendValue(propID_,  propValue_); \
  }

  APPENDVALUE(aTable[nsStyleImageLayers::image],      image);
  APPENDVALUE(aTable[nsStyleImageLayers::repeat],     repeat);
  APPENDVALUE(aTable[nsStyleImageLayers::clip],       clip);
  APPENDVALUE(aTable[nsStyleImageLayers::origin],     origin);
  APPENDVALUE(aTable[nsStyleImageLayers::positionX],  positionX);
  APPENDVALUE(aTable[nsStyleImageLayers::positionY],  positionY);
  APPENDVALUE(aTable[nsStyleImageLayers::size],       size);
  APPENDVALUE(aTable[nsStyleImageLayers::color],      color);
  APPENDVALUE(aTable[nsStyleImageLayers::attachment], attachment);
  APPENDVALUE(aTable[nsStyleImageLayers::maskMode],   maskMode);
  APPENDVALUE(aTable[nsStyleImageLayers::composite],  composite);

#undef APPENDVALUE

  return true;
}

// Helper for ParseImageLayersItem. Returns true if the passed-in nsCSSToken is
// a function which is accepted for background-image.
bool
CSSParserImpl::IsFunctionTokenValidForImageLayerImage(
  const nsCSSToken& aToken) const
{
  MOZ_ASSERT(aToken.mType == eCSSToken_Function,
             "Should only be called for function-typed tokens");

  const nsAString& funcName = aToken.mIdent;

  return funcName.LowerCaseEqualsLiteral("linear-gradient") ||
    funcName.LowerCaseEqualsLiteral("radial-gradient") ||
    funcName.LowerCaseEqualsLiteral("repeating-linear-gradient") ||
    funcName.LowerCaseEqualsLiteral("repeating-radial-gradient") ||
    funcName.LowerCaseEqualsLiteral("-moz-linear-gradient") ||
    funcName.LowerCaseEqualsLiteral("-moz-radial-gradient") ||
    funcName.LowerCaseEqualsLiteral("-moz-repeating-linear-gradient") ||
    funcName.LowerCaseEqualsLiteral("-moz-repeating-radial-gradient") ||
    funcName.LowerCaseEqualsLiteral("-moz-image-rect") ||
    funcName.LowerCaseEqualsLiteral("-moz-element") ||
    ((sWebkitPrefixedAliasesEnabled || ShouldUseUnprefixingService()) &&
     (funcName.LowerCaseEqualsLiteral("-webkit-gradient") ||
      funcName.LowerCaseEqualsLiteral("-webkit-linear-gradient") ||
      funcName.LowerCaseEqualsLiteral("-webkit-radial-gradient") ||
      funcName.LowerCaseEqualsLiteral("-webkit-repeating-linear-gradient") ||
      funcName.LowerCaseEqualsLiteral("-webkit-repeating-radial-gradient")));
}

// Parse one item of the background shorthand property.
bool
CSSParserImpl::ParseImageLayersItem(
  CSSParserImpl::ImageLayersShorthandParseState& aState,
  const nsCSSPropertyID aTable[])
{
  // Fill in the values that the shorthand will set if we don't find
  // other values.
  aState.mImage->mValue.SetNoneValue();
  aState.mAttachment->mValue.SetIntValue(NS_STYLE_IMAGELAYER_ATTACHMENT_SCROLL,
                                         eCSSUnit_Enumerated);
  aState.mClip->mValue.SetIntValue(StyleGeometryBox::Border,
                                   eCSSUnit_Enumerated);

  aState.mRepeat->mXValue.SetIntValue(NS_STYLE_IMAGELAYER_REPEAT_REPEAT,
                                      eCSSUnit_Enumerated);
  aState.mRepeat->mYValue.Reset();

  RefPtr<nsCSSValue::Array> positionXArr = nsCSSValue::Array::Create(2);
  RefPtr<nsCSSValue::Array> positionYArr = nsCSSValue::Array::Create(2);
  aState.mPositionX->mValue.SetArrayValue(positionXArr, eCSSUnit_Array);
  aState.mPositionY->mValue.SetArrayValue(positionYArr, eCSSUnit_Array);

  if (eCSSProperty_mask == aTable[nsStyleImageLayers::shorthand]) {
    aState.mOrigin->mValue.SetIntValue(StyleGeometryBox::Border,
                                       eCSSUnit_Enumerated);
  } else {
    aState.mOrigin->mValue.SetIntValue(StyleGeometryBox::Padding,
                                       eCSSUnit_Enumerated);
  }
  positionXArr->Item(1).SetPercentValue(0.0f);
  positionYArr->Item(1).SetPercentValue(0.0f);

  aState.mSize->mXValue.SetAutoValue();
  aState.mSize->mYValue.SetAutoValue();
  aState.mComposite->mValue.SetIntValue(NS_STYLE_MASK_COMPOSITE_ADD,
                                        eCSSUnit_Enumerated);
  aState.mMode->mValue.SetIntValue(NS_STYLE_MASK_MODE_MATCH_SOURCE,
                                   eCSSUnit_Enumerated);
  bool haveColor = false,
       haveImage = false,
       haveRepeat = false,
       haveAttach = false,
       havePositionAndSize = false,
       haveOrigin = false,
       haveComposite = false,
       haveMode = false,
       haveSomething = false;

  const KTableEntry* originTable =
    nsCSSProps::kKeywordTableTable[aTable[nsStyleImageLayers::origin]];
  while (GetToken(true)) {
    nsCSSTokenType tt = mToken.mType;
    UngetToken(); // ...but we'll still cheat and use mToken
    if (tt == eCSSToken_Symbol) {
      // ExpectEndProperty only looks for symbols, and nothing else will
      // show up as one.
      break;
    }

    if (tt == eCSSToken_Ident) {
      nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
      int32_t dummy;
      if (keyword == eCSSKeyword_inherit ||
          keyword == eCSSKeyword_initial ||
          keyword == eCSSKeyword_unset) {
        return false;
      } else if (keyword == eCSSKeyword_none) {
        if (haveImage)
          return false;
        haveImage = true;
        if (ParseSingleValueProperty(aState.mImage->mValue,
                                     aTable[nsStyleImageLayers::image]) !=
            CSSParseResult::Ok) {
          NS_NOTREACHED("should be able to parse");
          return false;
        }
      } else if (aTable[nsStyleImageLayers::attachment] !=
                   eCSSProperty_UNKNOWN &&
                 nsCSSProps::FindKeyword(keyword,
                   nsCSSProps::kImageLayerAttachmentKTable, dummy)) {
        if (haveAttach)
          return false;
        haveAttach = true;
        if (ParseSingleValueProperty(aState.mAttachment->mValue,
                                     aTable[nsStyleImageLayers::attachment]) !=
            CSSParseResult::Ok) {
          NS_NOTREACHED("should be able to parse");
          return false;
        }
      } else if (nsCSSProps::FindKeyword(keyword,
                   nsCSSProps::kImageLayerRepeatKTable, dummy)) {
        if (haveRepeat)
          return false;
        haveRepeat = true;
        nsCSSValuePair scratch;
        if (!ParseImageLayerRepeatValues(scratch)) {
          NS_NOTREACHED("should be able to parse");
          return false;
        }
        aState.mRepeat->mXValue = scratch.mXValue;
        aState.mRepeat->mYValue = scratch.mYValue;
      } else if (nsCSSProps::FindKeyword(keyword,
                   nsCSSProps::kImageLayerPositionKTable, dummy)) {
        if (havePositionAndSize)
          return false;
        havePositionAndSize = true;

        if (!ParsePositionValueSeparateCoords(aState.mPositionX->mValue,
                                              aState.mPositionY->mValue)) {
          return false;
        }
        if (ExpectSymbol('/', true)) {
          nsCSSValuePair scratch;
          if (!ParseImageLayerSizeValues(scratch)) {
            return false;
          }
          aState.mSize->mXValue = scratch.mXValue;
          aState.mSize->mYValue = scratch.mYValue;
        }
      } else if (nsCSSProps::FindKeyword(keyword, originTable, dummy)) {
        if (haveOrigin)
          return false;
        haveOrigin = true;
        if (ParseSingleValueProperty(aState.mOrigin->mValue,
                                     aTable[nsStyleImageLayers::origin]) !=
            CSSParseResult::Ok) {
          NS_NOTREACHED("should be able to parse");
          return false;
        }

        // The spec allows a second box value (for background-clip),
        // immediately following the first one (for background-origin).

#ifdef DEBUG
        const KTableEntry* clipTable =
          nsCSSProps::kKeywordTableTable[aTable[nsStyleImageLayers::clip]];
        for (size_t i = 0; originTable[i].mValue != -1; i++) {
          // For each keyword & value in kOriginKTable, ensure that
          // kBackgroundKTable has a matching entry at the same position.
          MOZ_ASSERT(originTable[i].mKeyword == clipTable[i].mKeyword);
          MOZ_ASSERT(originTable[i].mValue == clipTable[i].mValue);
        }
#endif
        CSSParseResult result =
          ParseSingleValueProperty(aState.mClip->mValue,
                                   aTable[nsStyleImageLayers::clip]);
        MOZ_ASSERT(result != CSSParseResult::Error,
                   "how can failing to parse a single background-clip value "
                   "consume tokens?");
        if (result == CSSParseResult::NotFound) {
          // When exactly one <box> value is set, it is used for both
          // 'background-origin' and 'background-clip'.
          // See assertions above showing these values are compatible.
          aState.mClip->mValue = aState.mOrigin->mValue;
        }
      } else if (aTable[nsStyleImageLayers::composite] != eCSSProperty_UNKNOWN &&
                 nsCSSProps::FindKeyword(keyword,
                   nsCSSProps::kImageLayerCompositeKTable, dummy)) {
        if (haveComposite)
          return false;
        haveComposite = true;
        if (ParseSingleValueProperty(aState.mComposite->mValue,
                                     aTable[nsStyleImageLayers::composite]) !=
            CSSParseResult::Ok) {
          NS_NOTREACHED("should be able to parse");
          return false;
        }
      } else if (aTable[nsStyleImageLayers::maskMode] != eCSSProperty_UNKNOWN &&
                 nsCSSProps::FindKeyword(keyword,
                   nsCSSProps::kImageLayerModeKTable, dummy)) {
        if (haveMode)
          return false;
        haveMode = true;
        if (ParseSingleValueProperty(aState.mMode->mValue,
                                     aTable[nsStyleImageLayers::maskMode]) !=
            CSSParseResult::Ok) {
          NS_NOTREACHED("should be able to parse");
          return false;
        }
      } else if (aTable[nsStyleImageLayers::color] != eCSSProperty_UNKNOWN) {
        if (haveColor)
          return false;
        haveColor = true;
        if (ParseSingleValueProperty(aState.mColor,
                                     aTable[nsStyleImageLayers::color]) !=
                                       CSSParseResult::Ok) {
          return false;
        }
      } else {
        return false;
      }
    } else if (tt == eCSSToken_URL ||
               (tt == eCSSToken_Function &&
                IsFunctionTokenValidForImageLayerImage(mToken))) {
      if (haveImage)
        return false;
      haveImage = true;
      if (ParseSingleValueProperty(aState.mImage->mValue,
                                   aTable[nsStyleImageLayers::image]) !=
          CSSParseResult::Ok) {
        return false;
      }
    } else if (tt == eCSSToken_Dimension ||
               tt == eCSSToken_Number ||
               tt == eCSSToken_Percentage ||
               (tt == eCSSToken_Function &&
                (mToken.mIdent.LowerCaseEqualsLiteral("calc") ||
                 mToken.mIdent.LowerCaseEqualsLiteral("-moz-calc")))) {
      if (havePositionAndSize)
        return false;
      havePositionAndSize = true;
      if (!ParsePositionValueSeparateCoords(aState.mPositionX->mValue,
                                            aState.mPositionY->mValue)) {
        return false;
      }
      if (ExpectSymbol('/', true)) {
        nsCSSValuePair scratch;
        if (!ParseImageLayerSizeValues(scratch)) {
          return false;
        }
        aState.mSize->mXValue = scratch.mXValue;
        aState.mSize->mYValue = scratch.mYValue;
      }
    } else if (aTable[nsStyleImageLayers::color] != eCSSProperty_UNKNOWN) {
      if (haveColor)
        return false;
      haveColor = true;
      // Note: This parses 'inherit', 'initial' and 'unset', but
      // we've already checked for them, so it's ok.
      if (ParseSingleValueProperty(aState.mColor,
                                   aTable[nsStyleImageLayers::color]) !=
                                     CSSParseResult::Ok) {
        return false;
      }
    } else {
      return false;
    }

    haveSomething = true;
  }

  return haveSomething;
}

// This function is very similar to ParseScrollSnapCoordinate,
// ParseImageLayerPosition, and ParseImageLayersSize.
bool
CSSParserImpl::ParseValueList(nsCSSPropertyID aPropID)
{
  // aPropID is a single value prop-id
  nsCSSValue value;
  // 'initial', 'inherit' and 'unset' stand alone, no list permitted.
  if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
    nsCSSValueList* item = value.SetListValue();
    for (;;) {
      if (ParseSingleValueProperty(item->mValue, aPropID) !=
          CSSParseResult::Ok) {
        return false;
      }
      if (!ExpectSymbol(',', true)) {
        break;
      }
      item->mNext = new nsCSSValueList;
      item = item->mNext;
    }
  }
  AppendValue(aPropID, value);
  return true;
}

bool
CSSParserImpl::ParseImageLayerRepeat(nsCSSPropertyID aPropID)
{
  nsCSSValue value;
  // 'initial', 'inherit' and 'unset' stand alone, no list permitted.
  if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
    nsCSSValuePair valuePair;
    if (!ParseImageLayerRepeatValues(valuePair)) {
      return false;
    }
    nsCSSValuePairList* item = value.SetPairListValue();
    for (;;) {
      item->mXValue = valuePair.mXValue;
      item->mYValue = valuePair.mYValue;
      if (!ExpectSymbol(',', true)) {
        break;
      }
      if (!ParseImageLayerRepeatValues(valuePair)) {
        return false;
      }
      item->mNext = new nsCSSValuePairList;
      item = item->mNext;
    }
  }

  AppendValue(aPropID, value);
  return true;
}

bool
CSSParserImpl::ParseImageLayerRepeatValues(nsCSSValuePair& aValue)
{
  nsCSSValue& xValue = aValue.mXValue;
  nsCSSValue& yValue = aValue.mYValue;

  if (ParseEnum(xValue, nsCSSProps::kImageLayerRepeatKTable)) {
    int32_t value = xValue.GetIntValue();
    // For single values set yValue as eCSSUnit_Null.
    if (value == NS_STYLE_IMAGELAYER_REPEAT_REPEAT_X ||
        value == NS_STYLE_IMAGELAYER_REPEAT_REPEAT_Y ||
        !ParseEnum(yValue, nsCSSProps::kImageLayerRepeatPartKTable)) {
      // the caller will fail cases like "repeat-x no-repeat"
      // by expecting a list separator or an end property.
      yValue.Reset();
    }
    return true;
  }

  return false;
}

bool
CSSParserImpl::ParseImageLayerPosition(const nsCSSPropertyID aTable[])
{
  // 'initial', 'inherit' and 'unset' stand alone, no list permitted.
  nsCSSValue position;
  if (ParseSingleTokenVariant(position, VARIANT_INHERIT, nullptr)) {
    AppendValue(aTable[nsStyleImageLayers::positionX], position);
    AppendValue(aTable[nsStyleImageLayers::positionY], position);
    return true;
  }

  nsCSSValue itemValueX;
  nsCSSValue itemValueY;
  if (!ParsePositionValueSeparateCoords(itemValueX, itemValueY)) {
    return false;
  }

  nsCSSValue valueX;
  nsCSSValue valueY;
  nsCSSValueList* itemX = valueX.SetListValue();
  nsCSSValueList* itemY = valueY.SetListValue();
  for (;;) {
    itemX->mValue = itemValueX;
    itemY->mValue = itemValueY;
    if (!ExpectSymbol(',', true)) {
      break;
    }
    if (!ParsePositionValueSeparateCoords(itemValueX, itemValueY)) {
      return false;
    }
    itemX->mNext = new nsCSSValueList;
    itemY->mNext = new nsCSSValueList;
    itemX = itemX->mNext;
    itemY = itemY->mNext;
  }
  AppendValue(aTable[nsStyleImageLayers::positionX], valueX);
  AppendValue(aTable[nsStyleImageLayers::positionY], valueY);
  return true;
}

bool
CSSParserImpl::ParseImageLayerPositionCoord(nsCSSPropertyID aPropID, bool aIsHorizontal)
{
  nsCSSValue value;
  // 'initial', 'inherit' and 'unset' stand alone, no list permitted.
  if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
    nsCSSValue itemValue;
    if (!ParseImageLayerPositionCoordItem(itemValue, aIsHorizontal)) {
      return false;
    }
    nsCSSValueList* item = value.SetListValue();
    for (;;) {
      item->mValue = itemValue;
      if (!ExpectSymbol(',', true)) {
        break;
      }
      if (!ParseImageLayerPositionCoordItem(itemValue, aIsHorizontal)) {
        return false;
      }
      item->mNext = new nsCSSValueList;
      item = item->mNext;
    }
  }
  AppendValue(aPropID, value);
  return true;
}

/**
 * BoxPositionMaskToCSSValue and ParseBoxPositionValues are used
 * for parsing the CSS 2.1 background-position syntax (which has at
 * most two values).  (Compare to the css3-background syntax which
 * takes up to four values.)  Some current CSS specifications that
 * use background-position-like syntax still use this old syntax.
 **
 * Parses two values that correspond to positions in a box.  These can be
 * values corresponding to percentages of the box, raw offsets, or keywords
 * like "top," "left center," etc.
 *
 * @param aOut The nsCSSValuePair in which to place the result.
 * @param aAcceptsInherit If true, 'inherit', 'initial' and 'unset' are
 *   legal values
 * @param aAllowExplicitCenter If true, 'center' is a legal value
 * @return Whether or not the operation succeeded.
 */
bool CSSParserImpl::ParseBoxPositionValues(nsCSSValuePair &aOut,
                                           bool aAcceptsInherit,
                                           bool aAllowExplicitCenter)
{
  // First try a percentage or a length value
  nsCSSValue &xValue = aOut.mXValue,
             &yValue = aOut.mYValue;
  int32_t variantMask =
    (aAcceptsInherit ? VARIANT_INHERIT : 0) | VARIANT_LP | VARIANT_CALC;
  CSSParseResult result = ParseVariant(xValue, variantMask, nullptr);
  if (result == CSSParseResult::Error) {
    return false;
  } else if (result == CSSParseResult::Ok) {
    if (eCSSUnit_Inherit == xValue.GetUnit() ||
        eCSSUnit_Initial == xValue.GetUnit() ||
        eCSSUnit_Unset == xValue.GetUnit()) {  // both are inherit, initial or unset
      yValue = xValue;
      return true;
    }
    // We have one percentage/length/calc. Get the optional second
    // percentage/length/calc/keyword.
    result = ParseVariant(yValue, VARIANT_LP | VARIANT_CALC, nullptr);
    if (result == CSSParseResult::Error) {
      return false;
    } else if (result == CSSParseResult::Ok) {
      // We have two numbers
      return true;
    }

    if (ParseEnum(yValue, nsCSSProps::kImageLayerPositionKTable)) {
      int32_t yVal = yValue.GetIntValue();
      if (!(yVal & BG_CTB)) {
        // The second keyword can only be 'center', 'top', or 'bottom'
        return false;
      }
      yValue = BoxPositionMaskToCSSValue(yVal, false);
      return true;
    }

    // If only one percentage or length value is given, it sets the
    // horizontal position only, and the vertical position will be 50%.
    yValue.SetPercentValue(0.5f);
    return true;
  }

  // Now try keywords. We do this manually to allow for the first
  // appearance of "center" to apply to the either the x or y
  // position (it's ambiguous so we have to disambiguate). Each
  // allowed keyword value is assigned it's own bit. We don't allow
  // any duplicate keywords other than center. We try to get two
  // keywords but it's okay if there is only one.
  int32_t mask = 0;
  if (ParseEnum(xValue, nsCSSProps::kImageLayerPositionKTable)) {
    int32_t bit = xValue.GetIntValue();
    mask |= bit;
    if (ParseEnum(xValue, nsCSSProps::kImageLayerPositionKTable)) {
      bit = xValue.GetIntValue();
      if (mask & (bit & ~BG_CENTER)) {
        // Only the 'center' keyword can be duplicated.
        return false;
      }
      mask |= bit;
    }
    else {
      // Only one keyword.  See if we have a length, percentage, or calc.
      result = ParseVariant(yValue, VARIANT_LP | VARIANT_CALC, nullptr);
      if (result == CSSParseResult::Error) {
        return false;
      } else if (result == CSSParseResult::Ok) {
        if (!(mask & BG_CLR)) {
          // The first keyword can only be 'center', 'left', or 'right'
          return false;
        }

        xValue = BoxPositionMaskToCSSValue(mask, true);
        return true;
      }
    }
  }

  // Check for bad input. Bad input consists of no matching keywords,
  // or pairs of x keywords or pairs of y keywords.
  if ((mask == 0) || (mask == (BG_TOP | BG_BOTTOM)) ||
      (mask == (BG_LEFT | BG_RIGHT)) ||
      (!aAllowExplicitCenter && (mask & BG_CENTER))) {
    return false;
  }

  // Create style values
  xValue = BoxPositionMaskToCSSValue(mask, true);
  yValue = BoxPositionMaskToCSSValue(mask, false);
  return true;
}

// Parses a CSS <position> value, for e.g. the 'background-position' property.
// Spec reference: http://www.w3.org/TR/css3-background/#ltpositiongt
// Invariants:
//  - Always produces a four-value array on a successful parse.
//  - The values are: X edge, X offset, Y edge, Y offset.
//  - Edges are always keywords or null.
//  - A |center| edge will not have an offset.
bool
CSSParserImpl::ParsePositionValue(nsCSSValue& aOut)
{
  RefPtr<nsCSSValue::Array> value = nsCSSValue::Array::Create(4);
  aOut.SetArrayValue(value, eCSSUnit_Array);

  // The following clarifies organisation of the array.
  nsCSSValue &xEdge   = value->Item(0),
             &xOffset = value->Item(1),
             &yEdge   = value->Item(2),
             &yOffset = value->Item(3);

  // Parse all the values into the array.
  uint32_t valueCount = 0;
  for (int32_t i = 0; i < 4; i++) {
    CSSParseResult result =
      ParseVariant(value->Item(i), VARIANT_LPCALC | VARIANT_KEYWORD,
                   nsCSSProps::kImageLayerPositionKTable);
    if (result == CSSParseResult::Error) {
      return false;
    } else if (result == CSSParseResult::NotFound) {
      break;
    }
    ++valueCount;
  }

  switch (valueCount) {
    case 4:
      // "If three or four values are given, then each <percentage> or <length>
      // represents an offset and must be preceded by a keyword, which specifies
      // from which edge the offset is given."
      if (eCSSUnit_Enumerated != xEdge.GetUnit() ||
          BG_CENTER == xEdge.GetIntValue() ||
          eCSSUnit_Enumerated == xOffset.GetUnit() ||
          eCSSUnit_Enumerated != yEdge.GetUnit() ||
          BG_CENTER == yEdge.GetIntValue() ||
          eCSSUnit_Enumerated == yOffset.GetUnit()) {
        return false;
      }
      break;
    case 3:
      // "If three or four values are given, then each <percentage> or<length>
      // represents an offset and must be preceded by a keyword, which specifies
      // from which edge the offset is given." ... "If three values are given,
      // the missing offset is assumed to be zero."
      if (eCSSUnit_Enumerated != value->Item(1).GetUnit()) {
        // keyword offset keyword
        // Second value is non-keyword, thus first value must be a non-center
        // keyword.
        if (eCSSUnit_Enumerated != value->Item(0).GetUnit() ||
            BG_CENTER == value->Item(0).GetIntValue()) {
          return false;
        }

        // Remaining value must be a keyword.
        if (eCSSUnit_Enumerated != value->Item(2).GetUnit()) {
          return false;
        }

        yOffset.Reset(); // Everything else is in the correct position.
      } else if (eCSSUnit_Enumerated != value->Item(2).GetUnit()) {
        // keyword keyword offset
        // Third value is non-keyword, thus second value must be non-center
        // keyword.
        if (BG_CENTER == value->Item(1).GetIntValue()) {
          return false;
        }

        // Remaining value must be a keyword.
        if (eCSSUnit_Enumerated != value->Item(0).GetUnit()) {
          return false;
        }

        // Move the values to the correct position in the array.
        value->Item(3) = value->Item(2); // yOffset
        value->Item(2) = value->Item(1); // yEdge
        value->Item(1).Reset(); // xOffset
      } else {
        return false;
      }
      break;
    case 2:
      // "If two values are given and at least one value is not a keyword, then
      // the first value represents the horizontal position (or offset) and the
      // second represents the vertical position (or offset)"
      if (eCSSUnit_Enumerated == value->Item(0).GetUnit()) {
        if (eCSSUnit_Enumerated == value->Item(1).GetUnit()) {
          // keyword keyword
          value->Item(2) = value->Item(1); // move yEdge to correct position
          xOffset.Reset();
          yOffset.Reset();
        } else {
          // keyword offset
          // First value must represent horizontal position.
          if ((BG_TOP | BG_BOTTOM) & value->Item(0).GetIntValue()) {
            return false;
          }
          value->Item(3) = value->Item(1); // move yOffset to correct position
          xOffset.Reset();
          yEdge.Reset();
        }
      } else {
        if (eCSSUnit_Enumerated == value->Item(1).GetUnit()) {
          // offset keyword
          // Second value must represent vertical position.
          if ((BG_LEFT | BG_RIGHT) & value->Item(1).GetIntValue()) {
            return false;
          }
          value->Item(2) = value->Item(1); // move yEdge to correct position
          value->Item(1) = value->Item(0); // move xOffset to correct position
          xEdge.Reset();
          yOffset.Reset();
        } else {
          // offset offset
          value->Item(3) = value->Item(1); // move yOffset to correct position
          value->Item(1) = value->Item(0); // move xOffset to correct position
          xEdge.Reset();
          yEdge.Reset();
        }
      }
      break;
    case 1:
      // "If only one value is specified, the second value is assumed to be
      // center."
      if (eCSSUnit_Enumerated == value->Item(0).GetUnit()) {
        xOffset.Reset();
      } else {
        value->Item(1) = value->Item(0); // move xOffset to correct position
        xEdge.Reset();
      }
      yEdge.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_CENTER, eCSSUnit_Enumerated);
      yOffset.Reset();
      break;
    default:
      return false;
  }

  // For compatibility with CSS2.1 code the edges can be unspecified.
  // Unspecified edges are recorded as nullptr.
  NS_ASSERTION((eCSSUnit_Enumerated == xEdge.GetUnit()  ||
                eCSSUnit_Null       == xEdge.GetUnit()) &&
               (eCSSUnit_Enumerated == yEdge.GetUnit()  ||
                eCSSUnit_Null       == yEdge.GetUnit()) &&
                eCSSUnit_Enumerated != xOffset.GetUnit()  &&
                eCSSUnit_Enumerated != yOffset.GetUnit(),
                "Unexpected units");

  // Keywords in first and second pairs can not both be vertical or
  // horizontal keywords. (eg. left right, bottom top). Additionally,
  // non-center keyword can not be duplicated (eg. left left).
  int32_t xEdgeEnum =
          xEdge.GetUnit() == eCSSUnit_Enumerated ? xEdge.GetIntValue() : 0;
  int32_t yEdgeEnum =
          yEdge.GetUnit() == eCSSUnit_Enumerated ? yEdge.GetIntValue() : 0;
  if ((xEdgeEnum | yEdgeEnum) == (BG_LEFT | BG_RIGHT) ||
      (xEdgeEnum | yEdgeEnum) == (BG_TOP | BG_BOTTOM) ||
      (xEdgeEnum & yEdgeEnum & ~BG_CENTER)) {
    return false;
  }

  // The values could be in an order that is different than expected.
  // eg. x contains vertical information, y contains horizontal information.
  // Swap if incorrect order.
  if (xEdgeEnum & (BG_TOP | BG_BOTTOM) ||
      yEdgeEnum & (BG_LEFT | BG_RIGHT)) {
    nsCSSValue swapEdge = xEdge;
    nsCSSValue swapOffset = xOffset;
    xEdge = yEdge;
    xOffset = yOffset;
    yEdge = swapEdge;
    yOffset = swapOffset;
  }

  return true;
}

static void
AdjustEdgeOffsetPairForBasicShape(nsCSSValue& aEdge,
                                  nsCSSValue& aOffset,
                                  uint8_t aDefaultEdge)
{
  // 0 length offsets are 0%
  if (aOffset.IsLengthUnit() && aOffset.GetFloatValue() == 0.0) {
    aOffset.SetPercentValue(0);
  }

  // Default edge is top/left in the 4-value case
  // In case of 1 or 0 values, the default is center,
  // but ParsePositionValue already handles this case
  if (eCSSUnit_Null == aEdge.GetUnit()) {
    aEdge.SetIntValue(aDefaultEdge, eCSSUnit_Enumerated);
  }
  // Default offset is 0%
  if (eCSSUnit_Null == aOffset.GetUnit()) {
    aOffset.SetPercentValue(0.0);
  }
  if (eCSSUnit_Enumerated == aEdge.GetUnit() &&
      eCSSUnit_Percent == aOffset.GetUnit()) {
    switch (aEdge.GetIntValue()) {
      case NS_STYLE_IMAGELAYER_POSITION_CENTER:
        aEdge.SetIntValue(aDefaultEdge, eCSSUnit_Enumerated);
        MOZ_ASSERT(aOffset.GetPercentValue() == 0.0,
                   "center cannot be used with an offset");
        aOffset.SetPercentValue(0.5);
        break;
      case NS_STYLE_IMAGELAYER_POSITION_BOTTOM:
        MOZ_ASSERT(aDefaultEdge == NS_STYLE_IMAGELAYER_POSITION_TOP);
        aEdge.SetIntValue(aDefaultEdge, eCSSUnit_Enumerated);
        aOffset.SetPercentValue(1 - aOffset.GetPercentValue());
        break;
      case NS_STYLE_IMAGELAYER_POSITION_RIGHT:
        MOZ_ASSERT(aDefaultEdge == NS_STYLE_IMAGELAYER_POSITION_LEFT);
        aEdge.SetIntValue(aDefaultEdge, eCSSUnit_Enumerated);
        aOffset.SetPercentValue(1 - aOffset.GetPercentValue());
    }
  }
}

// https://drafts.csswg.org/css-shapes/#basic-shape-serialization
// We set values to defaults while parsing for basic shapes
// Invariants:
//  - Always produces a four-value array on a successful parse.
//  - The values are: X edge, X offset, Y edge, Y offset
//  - Edges are always keywords (not including center)
//  - Offsets are nonnull
//  - Percentage offsets have keywords folded into them,
//    so "bottom 40%" or "right 20%" will not exist.
bool
CSSParserImpl::ParsePositionValueForBasicShape(nsCSSValue& aOut)
{
  if (!ParsePositionValue(aOut)) {
    return false;
  }
  nsCSSValue::Array* value = aOut.GetArrayValue();
  nsCSSValue& xEdge   = value->Item(0);
  nsCSSValue& xOffset = value->Item(1);
  nsCSSValue& yEdge   = value->Item(2);
  nsCSSValue& yOffset = value->Item(3);
  // A keyword edge + percent offset pair can be contracted
  // into the percentage with the default value in the edge.
  // Offset lengths which are 0 can also be rewritten as 0%
  AdjustEdgeOffsetPairForBasicShape(xEdge, xOffset,
                                    NS_STYLE_IMAGELAYER_POSITION_LEFT);
  AdjustEdgeOffsetPairForBasicShape(yEdge, yOffset,
                                    NS_STYLE_IMAGELAYER_POSITION_TOP);
  return true;
}

bool
CSSParserImpl::ParsePositionValueSeparateCoords(nsCSSValue& aOutX, nsCSSValue& aOutY)
{
  nsCSSValue scratch;
  if (!ParsePositionValue(scratch)) {
    return false;
  }

  // Separate the four values into two pairs of two values for X and Y.
  RefPtr<nsCSSValue::Array> valueX = nsCSSValue::Array::Create(2);
  RefPtr<nsCSSValue::Array> valueY = nsCSSValue::Array::Create(2);
  aOutX.SetArrayValue(valueX, eCSSUnit_Array);
  aOutY.SetArrayValue(valueY, eCSSUnit_Array);

  RefPtr<nsCSSValue::Array> value = scratch.GetArrayValue();
  valueX->Item(0) = value->Item(0);
  valueX->Item(1) = value->Item(1);
  valueY->Item(0) = value->Item(2);
  valueY->Item(1) = value->Item(3);
  return true;
}

// Parses one item in a list of values for the 'background-position-x' or
// 'background-position-y' property. Does not support the start/end keywords.
// Spec reference: https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-x
bool
CSSParserImpl::ParseImageLayerPositionCoordItem(nsCSSValue& aOut, bool aIsHorizontal)
{
  RefPtr<nsCSSValue::Array> value = nsCSSValue::Array::Create(2);
  aOut.SetArrayValue(value, eCSSUnit_Array);

  nsCSSValue &edge   = value->Item(0),
             &offset = value->Item(1);

  nsCSSValue edgeOrOffset;
  CSSParseResult result =
    ParseVariant(edgeOrOffset, VARIANT_LPCALC | VARIANT_KEYWORD,
                 nsCSSProps::kImageLayerPositionKTable);
  if (result != CSSParseResult::Ok) {
    return false;
  }

  if (edgeOrOffset.GetUnit() == eCSSUnit_Enumerated) {
    edge = edgeOrOffset;

    // The edge can be followed by an optional offset.
    result = ParseVariant(offset, VARIANT_LPCALC, nullptr);
    if (result == CSSParseResult::Error) {
      return false;
    }
  } else {
    offset = edgeOrOffset;
  }

  // Keywords for horizontal properties cannot be vertical keywords, and
  // keywords for vertical properties cannot be horizontal keywords.
  // Also, if an offset is specified, the edge cannot be center.
  int32_t edgeEnum =
          edge.GetUnit() == eCSSUnit_Enumerated ? edge.GetIntValue() : 0;
  int32_t allowedKeywords =
    (aIsHorizontal ? (BG_LEFT | BG_RIGHT) : (BG_TOP | BG_BOTTOM)) |
    (offset.GetUnit() == eCSSUnit_Null ? BG_CENTER : 0);
  if (edgeEnum & ~allowedKeywords) {
    return false;
  }

  NS_ASSERTION((eCSSUnit_Enumerated == edge.GetUnit() ||
                eCSSUnit_Null       == edge.GetUnit()) &&
               eCSSUnit_Enumerated != offset.GetUnit(),
               "Unexpected units");

  return true;
}

// This function is very similar to ParseScrollSnapCoordinate,
// ParseImageLayers, and ParseImageLayerPosition.
bool
CSSParserImpl::ParseImageLayerSize(nsCSSPropertyID aPropID)
{
  nsCSSValue value;
  // 'initial', 'inherit' and 'unset' stand alone, no list permitted.
  if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
    nsCSSValuePair valuePair;
    if (!ParseImageLayerSizeValues(valuePair)) {
      return false;
    }
    nsCSSValuePairList* item = value.SetPairListValue();
    for (;;) {
      item->mXValue = valuePair.mXValue;
      item->mYValue = valuePair.mYValue;
      if (!ExpectSymbol(',', true)) {
        break;
      }
      if (!ParseImageLayerSizeValues(valuePair)) {
        return false;
      }
      item->mNext = new nsCSSValuePairList;
      item = item->mNext;
    }
  }
  AppendValue(aPropID, value);
  return true;
}

/**
 * Parses two values that correspond to lengths for the background-size
 * property.  These can be one or two lengths (or the 'auto' keyword) or
 * percentages corresponding to the element's dimensions or the single keywords
 * 'contain' or 'cover'.  'initial', 'inherit' and 'unset' must be handled by
 * the caller if desired.
 *
 * @param aOut The nsCSSValuePair in which to place the result.
 * @return Whether or not the operation succeeded.
 */
#define BG_SIZE_VARIANT (VARIANT_LP | VARIANT_AUTO | VARIANT_CALC)
bool CSSParserImpl::ParseImageLayerSizeValues(nsCSSValuePair &aOut)
{
  // First try a percentage or a length value
  nsCSSValue &xValue = aOut.mXValue,
             &yValue = aOut.mYValue;
  CSSParseResult result =
    ParseNonNegativeVariant(xValue, BG_SIZE_VARIANT, nullptr);
  if (result == CSSParseResult::Error) {
    return false;
  } else if (result == CSSParseResult::Ok) {
    // We have one percentage/length/calc/auto. Get the optional second
    // percentage/length/calc/keyword.
    result = ParseNonNegativeVariant(yValue, BG_SIZE_VARIANT, nullptr);
    if (result == CSSParseResult::Error) {
      return false;
    } else if (result == CSSParseResult::Ok) {
      // We have a second percentage/length/calc/auto.
      return true;
    }

    // If only one percentage or length value is given, it sets the
    // horizontal size only, and the vertical size will be as if by 'auto'.
    yValue.SetAutoValue();
    return true;
  }

  // Now address 'contain' and 'cover'.
  if (!ParseEnum(xValue, nsCSSProps::kImageLayerSizeKTable))
    return false;
  yValue.Reset();
  return true;
}

#undef BG_SIZE_VARIANT

bool
CSSParserImpl::ParseBorderColor()
{
  return ParseBoxProperties(kBorderColorIDs);
}

void
CSSParserImpl::SetBorderImageInitialValues()
{
  // border-image-source: none
  nsCSSValue source;
  source.SetNoneValue();
  AppendValue(eCSSProperty_border_image_source, source);

  // border-image-slice: 100%
  nsCSSValue sliceBoxValue;
  nsCSSRect& sliceBox = sliceBoxValue.SetRectValue();
  sliceBox.SetAllSidesTo(nsCSSValue(1.0f, eCSSUnit_Percent));
  nsCSSValue slice;
  nsCSSValueList* sliceList = slice.SetListValue();
  sliceList->mValue = sliceBoxValue;
  AppendValue(eCSSProperty_border_image_slice, slice);

  // border-image-width: 1
  nsCSSValue width;
  nsCSSRect& widthBox = width.SetRectValue();
  widthBox.SetAllSidesTo(nsCSSValue(1.0f, eCSSUnit_Number));
  AppendValue(eCSSProperty_border_image_width, width);

  // border-image-outset: 0
  nsCSSValue outset;
  nsCSSRect& outsetBox = outset.SetRectValue();
  outsetBox.SetAllSidesTo(nsCSSValue(0.0f, eCSSUnit_Number));
  AppendValue(eCSSProperty_border_image_outset, outset);

  // border-image-repeat: repeat
  nsCSSValue repeat;
  nsCSSValuePair repeatPair;
  repeatPair.SetBothValuesTo(nsCSSValue(NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH,
                                        eCSSUnit_Enumerated));
  repeat.SetPairValue(&repeatPair);
  AppendValue(eCSSProperty_border_image_repeat, repeat);
}

bool
CSSParserImpl::ParseBorderImageSlice(bool aAcceptsInherit,
                                     bool* aConsumedTokens)
{
  // border-image-slice: initial | [<number>|<percentage>]{1,4} && fill?
  nsCSSValue value;

  if (aConsumedTokens) {
    *aConsumedTokens = true;
  }

  if (aAcceptsInherit &&
      ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
    // Keywords "inherit", "initial" and "unset" can not be mixed, so we
    // are done.
    AppendValue(eCSSProperty_border_image_slice, value);
    return true;
  }

  // Try parsing "fill" value.
  nsCSSValue imageSliceFillValue;
  bool hasFill = ParseEnum(imageSliceFillValue,
                           nsCSSProps::kBorderImageSliceKTable);

  // Parse the box dimensions.
  nsCSSValue imageSliceBoxValue;
  if (!ParseGroupedBoxProperty(VARIANT_PN, imageSliceBoxValue,
                               CSS_PROPERTY_VALUE_NONNEGATIVE)) {
    if (!hasFill && aConsumedTokens) {
      *aConsumedTokens = false;
    }

    return false;
  }

  // Try parsing "fill" keyword again if the first time failed because keyword
  // and slice dimensions can be in any order.
  if (!hasFill) {
    hasFill = ParseEnum(imageSliceFillValue,
                        nsCSSProps::kBorderImageSliceKTable);
  }

  nsCSSValueList* borderImageSlice = value.SetListValue();
  // Put the box value into the list.
  borderImageSlice->mValue = imageSliceBoxValue;

  if (hasFill) {
    // Put the "fill" value into the list.
    borderImageSlice->mNext = new nsCSSValueList;
    borderImageSlice->mNext->mValue = imageSliceFillValue;
  }

  AppendValue(eCSSProperty_border_image_slice, value);
  return true;
}

bool
CSSParserImpl::ParseBorderImageWidth(bool aAcceptsInherit)
{
  // border-image-width: initial | [<length>|<number>|<percentage>|auto]{1,4}
  nsCSSValue value;

  if (aAcceptsInherit &&
      ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
    // Keywords "inherit", "initial" and "unset" can not be mixed, so we
    // are done.
    AppendValue(eCSSProperty_border_image_width, value);
    return true;
  }

  // Parse the box dimensions.
  if (!ParseGroupedBoxProperty(VARIANT_ALPN, value, CSS_PROPERTY_VALUE_NONNEGATIVE)) {
    return false;
  }

  AppendValue(eCSSProperty_border_image_width, value);
  return true;
}

bool
CSSParserImpl::ParseBorderImageOutset(bool aAcceptsInherit)
{
  // border-image-outset: initial | [<length>|<number>]{1,4}
  nsCSSValue value;

  if (aAcceptsInherit &&
      ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
    // Keywords "inherit", "initial" and "unset" can not be mixed, so we
    // are done.
    AppendValue(eCSSProperty_border_image_outset, value);
    return true;
  }

  // Parse the box dimensions.
  if (!ParseGroupedBoxProperty(VARIANT_LN, value, CSS_PROPERTY_VALUE_NONNEGATIVE)) {
    return false;
  }

  AppendValue(eCSSProperty_border_image_outset, value);
  return true;
}

bool
CSSParserImpl::ParseBorderImageRepeat(bool aAcceptsInherit)
{
  nsCSSValue value;
  if (aAcceptsInherit &&
      ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
    // Keywords "inherit", "initial" and "unset" can not be mixed, so we
    // are done.
    AppendValue(eCSSProperty_border_image_repeat, value);
    return true;
  }

  nsCSSValuePair result;
  if (!ParseEnum(result.mXValue, nsCSSProps::kBorderImageRepeatKTable)) {
    return false;
  }

  // optional second keyword, defaults to first
  if (!ParseEnum(result.mYValue, nsCSSProps::kBorderImageRepeatKTable)) {
    result.mYValue = result.mXValue;
  }

  value.SetPairValue(&result);
  AppendValue(eCSSProperty_border_image_repeat, value);
  return true;
}

bool
CSSParserImpl::ParseBorderImage()
{
  nsAutoParseCompoundProperty compound(this);

  // border-image: inherit | initial |
  // <border-image-source> ||
  // <border-image-slice>
  //   [ / <border-image-width> |
  //     / <border-image-width>? / <border-image-outset> ]? ||
  // <border-image-repeat>

  nsCSSValue value;
  if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
    AppendValue(eCSSProperty_border_image_source, value);
    AppendValue(eCSSProperty_border_image_slice, value);
    AppendValue(eCSSProperty_border_image_width, value);
    AppendValue(eCSSProperty_border_image_outset, value);
    AppendValue(eCSSProperty_border_image_repeat, value);
    // Keywords "inherit", "initial" and "unset" can't be mixed, so we are done.
    return true;
  }

  // No empty property.
  if (CheckEndProperty()) {
    return false;
  }

  // Shorthand properties are required to set everything they can.
  SetBorderImageInitialValues();

  bool foundSource = false;
  bool foundSliceWidthOutset = false;
  bool foundRepeat = false;

  // This loop is used to handle the parsing of border-image properties which
  // can appear in any order.
  nsCSSValue imageSourceValue;
  while (!CheckEndProperty()) {
    // <border-image-source>
    if (!foundSource) {
      CSSParseResult result =
        ParseVariant(imageSourceValue, VARIANT_IMAGE, nullptr);
      if (result == CSSParseResult::Error) {
        return false;
      } else if (result == CSSParseResult::Ok) {
        AppendValue(eCSSProperty_border_image_source, imageSourceValue);
        foundSource = true;
        continue;
      }
    }

    // <border-image-slice>
    // ParseBorderImageSlice is weird.  It may consume tokens and then return
    // false, because it parses a property with two required components that
    // can appear in either order.  Since the tokens that were consumed cannot
    // parse as anything else we care about, this isn't a problem.
    if (!foundSliceWidthOutset) {
      bool sliceConsumedTokens = false;
      if (ParseBorderImageSlice(false, &sliceConsumedTokens)) {
        foundSliceWidthOutset = true;

        // [ / <border-image-width>?
        if (ExpectSymbol('/', true)) {
          bool foundBorderImageWidth = ParseBorderImageWidth(false);

          // [ / <border-image-outset>
          if (ExpectSymbol('/', true)) {
            if (!ParseBorderImageOutset(false)) {
              return false;
            }
          } else if (!foundBorderImageWidth) {
            // If this part has an trailing slash, the whole declaration is
            // invalid.
            return false;
          }
        }

        continue;
      } else {
        // If we consumed some tokens for <border-image-slice> but did not
        // successfully parse it, we have an error.
        if (sliceConsumedTokens) {
          return false;
        }
      }
    }

    // <border-image-repeat>
    if (!foundRepeat && ParseBorderImageRepeat(false)) {
      foundRepeat = true;
      continue;
    }

    return false;
  }

  return true;
}

bool
CSSParserImpl::ParseBorderSpacing()
{
  nsCSSValue xValue, yValue;
  if (ParseNonNegativeVariant(xValue, VARIANT_HL | VARIANT_CALC, nullptr) !=
      CSSParseResult::Ok) {
    return false;
  }

  // If we have one length, get the optional second length.
  // set the second value equal to the first.
  if (xValue.IsLengthUnit() || xValue.IsCalcUnit()) {
    if (ParseNonNegativeVariant(yValue, VARIANT_LENGTH | VARIANT_CALC,
                                nullptr) == CSSParseResult::Error) {
      return false;
    }
  }

  if (yValue == xValue || yValue.GetUnit() == eCSSUnit_Null) {
    AppendValue(eCSSProperty_border_spacing, xValue);
  } else {
    nsCSSValue pair;
    pair.SetPairValue(xValue, yValue);
    AppendValue(eCSSProperty_border_spacing, pair);
  }
  return true;
}

bool
CSSParserImpl::ParseBorderSide(const nsCSSPropertyID aPropIDs[],
                               bool aSetAllSides)
{
  const int32_t numProps = 3;
  nsCSSValue  values[numProps];

  int32_t found = ParseChoice(values, aPropIDs, numProps);
  if (found < 1) {
    return false;
  }

  if ((found & 1) == 0) { // Provide default border-width
    values[0].SetIntValue(NS_STYLE_BORDER_WIDTH_MEDIUM, eCSSUnit_Enumerated);
  }
  if ((found & 2) == 0) { // Provide default border-style
    values[1].SetIntValue(NS_STYLE_BORDER_STYLE_NONE, eCSSUnit_Enumerated);
  }
  if ((found & 4) == 0) { // text color will be used
    values[2].SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor);
  }

  if (aSetAllSides) {
    // Parsing "border" shorthand; set all four sides to the same thing
    for (int32_t index = 0; index < 4; index++) {
      NS_ASSERTION(numProps == 3, "This code needs updating");
      AppendValue(kBorderWidthIDs[index], values[0]);
      AppendValue(kBorderStyleIDs[index], values[1]);
      AppendValue(kBorderColorIDs[index], values[2]);
    }

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

    // Set the other properties that the border shorthand sets to their
    // initial values.
    nsCSSValue extraValue;
    switch (values[0].GetUnit()) {
    case eCSSUnit_Inherit:
    case eCSSUnit_Initial:
    case eCSSUnit_Unset:
      extraValue = values[0];
      // Set value of border-image properties to initial/inherit/unset
      AppendValue(eCSSProperty_border_image_source, extraValue);
      AppendValue(eCSSProperty_border_image_slice, extraValue);
      AppendValue(eCSSProperty_border_image_width, extraValue);
      AppendValue(eCSSProperty_border_image_outset, extraValue);
      AppendValue(eCSSProperty_border_image_repeat, extraValue);
      break;
    default:
      extraValue.SetNoneValue();
      SetBorderImageInitialValues();
      break;
    }
    NS_FOR_CSS_SIDES(side) {
      AppendValue(kBorderColorsProps[side], extraValue);
    }
  }
  else {
    // Just set our one side
    for (int32_t index = 0; index < numProps; index++) {
      AppendValue(aPropIDs[index], values[index]);
    }
  }
  return true;
}

bool
CSSParserImpl::ParseBorderStyle()
{
  return ParseBoxProperties(kBorderStyleIDs);
}

bool
CSSParserImpl::ParseBorderWidth()
{
  return ParseBoxProperties(kBorderWidthIDs);
}

bool
CSSParserImpl::ParseBorderColors(nsCSSPropertyID aProperty)
{
  nsCSSValue value;
  // 'inherit', 'initial', 'unset' and 'none' are only allowed on their own
  if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE,
                               nullptr)) {
    nsCSSValueList *cur = value.SetListValue();
    for (;;) {
      if (ParseVariant(cur->mValue, VARIANT_COLOR, nullptr) !=
          CSSParseResult::Ok) {
        return false;
      }
      if (CheckEndProperty()) {
        break;
      }
      cur->mNext = new nsCSSValueList;
      cur = cur->mNext;
    }
  }
  AppendValue(aProperty, value);
  return true;
}

// Parse the top level of a calc() expression.
bool
CSSParserImpl::ParseCalc(nsCSSValue &aValue, uint32_t aVariantMask)
{
  // Parsing calc expressions requires, in a number of cases, looking
  // for a token that is *either* a value of the property or a number.
  // This can be done without lookahead when we assume that the property
  // values cannot themselves be numbers.
  MOZ_ASSERT(aVariantMask != 0, "unexpected variant mask");

  bool oldUnitlessLengthQuirk = mUnitlessLengthQuirk;
  mUnitlessLengthQuirk = false;

  // One-iteration loop so we can break to the error-handling case.
  do {
    // The toplevel of a calc() is always an nsCSSValue::Array of length 1.
    RefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(1);

    if (!ParseCalcAdditiveExpression(arr->Item(0), aVariantMask))
      break;

    if (!ExpectSymbol(')', true))
      break;

    aValue.SetArrayValue(arr, eCSSUnit_Calc);
    mUnitlessLengthQuirk = oldUnitlessLengthQuirk;
    return true;
  } while (false);

  SkipUntil(')');
  mUnitlessLengthQuirk = oldUnitlessLengthQuirk;
  return false;
}

// We optimize away the <value-expression> production given that
// ParseVariant consumes initial whitespace and we call
// ExpectSymbol(')') with true for aSkipWS.
//  * If aVariantMask is VARIANT_NUMBER, this function parses the
//    <number-additive-expression> production.
//  * If aVariantMask does not contain VARIANT_NUMBER, this function
//    parses the <value-additive-expression> production.
//  * Otherwise (VARIANT_NUMBER and other bits) this function parses
//    whichever one of the productions matches ***and modifies
//    aVariantMask*** to reflect which one it has parsed by either
//    removing VARIANT_NUMBER or removing all other bits.
// It does so iteratively, but builds the correct recursive
// data structure.
bool
CSSParserImpl::ParseCalcAdditiveExpression(nsCSSValue& aValue,
                                           uint32_t& aVariantMask)
{
  MOZ_ASSERT(aVariantMask != 0, "unexpected variant mask");
  nsCSSValue *storage = &aValue;
  for (;;) {
    bool haveWS;
    if (!ParseCalcMultiplicativeExpression(*storage, aVariantMask, &haveWS))
      return false;

    if (!haveWS || !GetToken(false))
      return true;
    nsCSSUnit unit;
    if (mToken.IsSymbol('+')) {
      unit = eCSSUnit_Calc_Plus;
    } else if (mToken.IsSymbol('-')) {
      unit = eCSSUnit_Calc_Minus;
    } else {
      UngetToken();
      return true;
    }
    if (!RequireWhitespace())
      return false;

    RefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(2);
    arr->Item(0) = aValue;
    storage = &arr->Item(1);
    aValue.SetArrayValue(arr, unit);
  }
}

struct ReduceNumberCalcOps : public mozilla::css::BasicFloatCalcOps,
                             public mozilla::css::CSSValueInputCalcOps
{
  result_type ComputeLeafValue(const nsCSSValue& aValue)
  {
    MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Number, "unexpected unit");
    return aValue.GetFloatValue();
  }

  float ComputeNumber(const nsCSSValue& aValue)
  {
    return mozilla::css::ComputeCalc(aValue, *this);
  }
};

//  * If aVariantMask is VARIANT_NUMBER, this function parses the
//    <number-multiplicative-expression> production.
//  * If aVariantMask does not contain VARIANT_NUMBER, this function
//    parses the <value-multiplicative-expression> production.
//  * Otherwise (VARIANT_NUMBER and other bits) this function parses
//    whichever one of the productions matches ***and modifies
//    aVariantMask*** to reflect which one it has parsed by either
//    removing VARIANT_NUMBER or removing all other bits.
// It does so iteratively, but builds the correct recursive data
// structure.
// This function always consumes *trailing* whitespace when it returns
// true; whether there was any such whitespace is returned in the
// aHadFinalWS parameter.
bool
CSSParserImpl::ParseCalcMultiplicativeExpression(nsCSSValue& aValue,
                                                 uint32_t& aVariantMask,
                                                 bool *aHadFinalWS)
{
  MOZ_ASSERT(aVariantMask != 0, "unexpected variant mask");
  bool gotValue = false; // already got the part with the unit
  bool afterDivision = false;

  nsCSSValue *storage = &aValue;
  for (;;) {
    uint32_t variantMask;
    if (afterDivision || gotValue) {
      variantMask = VARIANT_NUMBER;
    } else {
      variantMask = aVariantMask | VARIANT_NUMBER;
    }
    if (!ParseCalcTerm(*storage, variantMask))
      return false;
    MOZ_ASSERT(variantMask != 0,
               "ParseCalcTerm did not set variantMask appropriately");
    MOZ_ASSERT(!(variantMask & VARIANT_NUMBER) ||
               !(variantMask & ~int32_t(VARIANT_NUMBER)),
               "ParseCalcTerm did not set variantMask appropriately");

    if (variantMask & VARIANT_NUMBER) {
      // Simplify the value immediately so we can check for division by
      // zero.
      ReduceNumberCalcOps ops;
      float number = mozilla::css::ComputeCalc(*storage, ops);
      if (number == 0.0 && afterDivision)
        return false;
      storage->SetFloatValue(number, eCSSUnit_Number);
    } else {
      gotValue = true;

      if (storage != &aValue) {
        // Simplify any numbers in the Times_L position (which are
        // not simplified by the check above).
        MOZ_ASSERT(storage == &aValue.GetArrayValue()->Item(1),
                   "unexpected relationship to current storage");
        nsCSSValue &leftValue = aValue.GetArrayValue()->Item(0);
        ReduceNumberCalcOps ops;
        float number = mozilla::css::ComputeCalc(leftValue, ops);
        leftValue.SetFloatValue(number, eCSSUnit_Number);
      }
    }

    bool hadWS = RequireWhitespace();
    if (!GetToken(false)) {
      *aHadFinalWS = hadWS;
      break;
    }
    nsCSSUnit unit;
    if (mToken.IsSymbol('*')) {
      unit = gotValue ? eCSSUnit_Calc_Times_R : eCSSUnit_Calc_Times_L;
      afterDivision = false;
    } else if (mToken.IsSymbol('/')) {
      unit = eCSSUnit_Calc_Divided;
      afterDivision = true;
    } else {
      UngetToken();
      *aHadFinalWS = hadWS;
      break;
    }

    RefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(2);
    arr->Item(0) = aValue;
    storage = &arr->Item(1);
    aValue.SetArrayValue(arr, unit);
  }

  // Adjust aVariantMask (see comments above function) to reflect which
  // option we took.
  if (aVariantMask & VARIANT_NUMBER) {
    if (gotValue) {
      aVariantMask &= ~int32_t(VARIANT_NUMBER);
    } else {
      aVariantMask = VARIANT_NUMBER;
    }
  } else {
    if (!gotValue) {
      // We had to find a value, but we didn't.
      return false;
    }
  }

  return true;
}

//  * If aVariantMask is VARIANT_NUMBER, this function parses the
//    <number-term> production.
//  * If aVariantMask does not contain VARIANT_NUMBER, this function
//    parses the <value-term> production.
//  * Otherwise (VARIANT_NUMBER and other bits) this function parses
//    whichever one of the productions matches ***and modifies
//    aVariantMask*** to reflect which one it has parsed by either
//    removing VARIANT_NUMBER or removing all other bits.
bool
CSSParserImpl::ParseCalcTerm(nsCSSValue& aValue, uint32_t& aVariantMask)
{
  MOZ_ASSERT(aVariantMask != 0, "unexpected variant mask");
  if (!GetToken(true))
    return false;
  // Either an additive expression in parentheses...
  if (mToken.IsSymbol('(') ||
      // Treat nested calc() as plain parenthesis.
      IsCSSTokenCalcFunction(mToken)) {
    if (!ParseCalcAdditiveExpression(aValue, aVariantMask) ||
        !ExpectSymbol(')', true)) {
      SkipUntil(')');
      return false;
    }
    return true;
  }
  // ... or just a value
  UngetToken();
  // Always pass VARIANT_NUMBER to ParseVariant so that unitless zero
  // always gets picked up
  if (ParseVariant(aValue, aVariantMask | VARIANT_NUMBER, nullptr) !=
      CSSParseResult::Ok) {
    return false;
  }
  // ...and do the VARIANT_NUMBER check ourselves.
  if (!(aVariantMask & VARIANT_NUMBER) && aValue.GetUnit() == eCSSUnit_Number) {
    return false;
  }
  // If we did the value parsing, we need to adjust aVariantMask to
  // reflect which option we took (see above).
  if (aVariantMask & VARIANT_NUMBER) {
    if (aValue.GetUnit() == eCSSUnit_Number) {
      aVariantMask = VARIANT_NUMBER;
    } else {
      aVariantMask &= ~int32_t(VARIANT_NUMBER);
    }
  }
  return true;
}

// This function consumes all consecutive whitespace and returns whether
// there was any.
bool
CSSParserImpl::RequireWhitespace()
{
  if (!GetToken(false))
    return false;
  if (mToken.mType != eCSSToken_Whitespace) {
    UngetToken();
    return false;
  }
  // Skip any additional whitespace tokens.
  if (GetToken(true)) {
    UngetToken();
  }
  return true;
}

bool
CSSParserImpl::ParseRect(nsCSSPropertyID aPropID)
{
  nsCSSValue val;
  if (ParseSingleTokenVariant(val, VARIANT_INHERIT | VARIANT_AUTO, nullptr)) {
    AppendValue(aPropID, val);
    return true;
  }

  if (! GetToken(true)) {
    return false;
  }

  if (mToken.mType == eCSSToken_Function &&
      mToken.mIdent.LowerCaseEqualsLiteral("rect")) {
    nsCSSRect& rect = val.SetRectValue();
    bool useCommas;
    NS_FOR_CSS_SIDES(side) {
      if (!ParseSingleTokenVariant(rect.*(nsCSSRect::sides[side]),
                                   VARIANT_AL, nullptr)) {
        return false;
      }
      if (side == 0) {
        useCommas = ExpectSymbol(',', true);
      } else if (useCommas && side < 3) {
        // Skip optional commas between elements, but only if the first
        // separator was a comma.
        if (!ExpectSymbol(',', true)) {
          return false;
        }
      }
    }
    if (!ExpectSymbol(')', true)) {
      return false;
    }
  } else {
    UngetToken();
    return false;
  }

  AppendValue(aPropID, val);
  return true;
}

bool
CSSParserImpl::ParseColumns()
{
  // We use a similar "fake value" hack to ParseListStyle, because
  // "auto" is acceptable for both column-count and column-width.
  // If the fake "auto" value is found, and one of the real values isn't,
  // that means the fake auto value is meant for the real value we didn't
  // find.
  static const nsCSSPropertyID columnIDs[] = {
    eCSSPropertyExtra_x_auto_value,
    eCSSProperty_column_count,
    eCSSProperty_column_width
  };
  const int32_t numProps = MOZ_ARRAY_LENGTH(columnIDs);

  nsCSSValue values[numProps];
  int32_t found = ParseChoice(values, columnIDs, numProps);
  if (found < 1) {
    return false;
  }
  if ((found & (1|2|4)) == (1|2|4) &&
      values[0].GetUnit() ==  eCSSUnit_Auto) {
    // We filled all 3 values, which is invalid
    return false;
  }

  if ((found & 2) == 0) {
    // Provide auto column-count
    values[1].SetAutoValue();
  }
  if ((found & 4) == 0) {
    // Provide auto column-width
    values[2].SetAutoValue();
  }

  // Start at index 1 to skip the fake auto value.
  for (int32_t index = 1; index < numProps; index++) {
    AppendValue(columnIDs[index], values[index]);
  }
  return true;
}

#define VARIANT_CONTENT (VARIANT_STRING | VARIANT_URL | VARIANT_COUNTER | VARIANT_ATTR | \
                         VARIANT_KEYWORD)
bool
CSSParserImpl::ParseContent()
{
  // We need to divide the 'content' keywords into two classes for
  // ParseVariant's sake, so we can't just use nsCSSProps::kContentKTable.
  static const KTableEntry kContentListKWs[] = {
    { eCSSKeyword_open_quote, NS_STYLE_CONTENT_OPEN_QUOTE },
    { eCSSKeyword_close_quote, NS_STYLE_CONTENT_CLOSE_QUOTE },
    { eCSSKeyword_no_open_quote, NS_STYLE_CONTENT_NO_OPEN_QUOTE },
    { eCSSKeyword_no_close_quote, NS_STYLE_CONTENT_NO_CLOSE_QUOTE },
    { eCSSKeyword_UNKNOWN, -1 }
  };

  static const KTableEntry kContentSolitaryKWs[] = {
    { eCSSKeyword__moz_alt_content, NS_STYLE_CONTENT_ALT_CONTENT },
    { eCSSKeyword_UNKNOWN, -1 }
  };

  // Verify that these two lists add up to the size of
  // nsCSSProps::kContentKTable.
  MOZ_ASSERT(nsCSSProps::kContentKTable[
               ArrayLength(kContentListKWs) +
               ArrayLength(kContentSolitaryKWs) - 2].mKeyword ==
                 eCSSKeyword_UNKNOWN &&
             nsCSSProps::kContentKTable[
               ArrayLength(kContentListKWs) +
               ArrayLength(kContentSolitaryKWs) - 2].mValue == -1,
             "content keyword tables out of sync");

  nsCSSValue value;
  // 'inherit', 'initial', 'unset', 'normal', 'none', and 'alt-content' must
  // be alone
  if (!ParseSingleTokenVariant(value, VARIANT_HMK | VARIANT_NONE,
                               kContentSolitaryKWs)) {
    nsCSSValueList* cur = value.SetListValue();
    for (;;) {
      if (ParseVariant(cur->mValue, VARIANT_CONTENT, kContentListKWs) !=
          CSSParseResult::Ok) {
        return false;
      }
      if (CheckEndProperty()) {
        break;
      }
      cur->mNext = new nsCSSValueList;
      cur = cur->mNext;
    }
  }
  AppendValue(eCSSProperty_content, value);
  return true;
}

bool
CSSParserImpl::ParseCounterData(nsCSSPropertyID aPropID)
{
  static const nsCSSKeyword kCounterDataKTable[] = {
    eCSSKeyword_none,
    eCSSKeyword_UNKNOWN
  };
  nsCSSValue value;
  if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE,
                               nullptr)) {
    if (!GetToken(true)) {
      return false;
    }
    if (mToken.mType != eCSSToken_Ident) {
      UngetToken();
      return false;
    }

    nsCSSValuePairList *cur = value.SetPairListValue();
    for (;;) {
      if (!ParseCustomIdent(cur->mXValue, mToken.mIdent, kCounterDataKTable)) {
        return false;
      }
      if (!GetToken(true)) {
        break;
      }
      if (mToken.mType == eCSSToken_Number && mToken.mIntegerValid) {
        cur->mYValue.SetIntValue(mToken.mInteger, eCSSUnit_Integer);
      } else {
        UngetToken();
      }
      if (!GetToken(true)) {
        break;
      }
      if (mToken.mType != eCSSToken_Ident) {
        UngetToken();
        break;
      }
      cur->mNext = new nsCSSValuePairList;
      cur = cur->mNext;
    }
  }
  AppendValue(aPropID, value);
  return true;
}

bool
CSSParserImpl::ParseCursor()
{
  nsCSSValue value;
  // 'inherit', 'initial' and 'unset' must be alone
  if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
    nsCSSValueList* cur = value.SetListValue();
    for (;;) {
      if (!ParseSingleTokenVariant(cur->mValue, VARIANT_UK,
                                   nsCSSProps::kCursorKTable)) {
        return false;
      }
      if (cur->mValue.GetUnit() != eCSSUnit_URL) { // keyword must be last
        break;
      }

      // We have a URL, so make a value array with three values.
      RefPtr<nsCSSValue::Array> val = nsCSSValue::Array::Create(3);
      val->Item(0) = cur->mValue;

      // Parse optional x and y position of cursor hotspot (css3-ui).
      if (ParseSingleTokenVariant(val->Item(1), VARIANT_NUMBER, nullptr)) {
        // If we have one number, we must have two.
        if (!ParseSingleTokenVariant(val->Item(2), VARIANT_NUMBER, nullptr)) {
          return false;
        }
      }
      cur->mValue.SetArrayValue(val, eCSSUnit_Array);

      if (!ExpectSymbol(',', true)) { // url must not be last
        return false;
      }
      cur->mNext = new nsCSSValueList;
      cur = cur->mNext;
    }
  }
  AppendValue(eCSSProperty_cursor, value);
  return true;
}


bool
CSSParserImpl::ParseFont()
{
  nsCSSValue  family;
  if (ParseSingleTokenVariant(family, VARIANT_HK, nsCSSProps::kFontKTable)) {
    if (eCSSUnit_Inherit == family.GetUnit() ||
        eCSSUnit_Initial == family.GetUnit() ||
        eCSSUnit_Unset == family.GetUnit()) {
      AppendValue(eCSSProperty__x_system_font, nsCSSValue(eCSSUnit_None));
      AppendValue(eCSSProperty_font_family, family);
      AppendValue(eCSSProperty_font_style, family);
      AppendValue(eCSSProperty_font_weight, family);
      AppendValue(eCSSProperty_font_size, family);
      AppendValue(eCSSProperty_line_height, family);
      AppendValue(eCSSProperty_font_stretch, family);
      AppendValue(eCSSProperty_font_size_adjust, family);
      AppendValue(eCSSProperty_font_feature_settings, family);
      AppendValue(eCSSProperty_font_language_override, family);
      AppendValue(eCSSProperty_font_kerning, family);
      AppendValue(eCSSProperty_font_synthesis, family);
      AppendValue(eCSSProperty_font_variant_alternates, family);
      AppendValue(eCSSProperty_font_variant_caps, family);
      AppendValue(eCSSProperty_font_variant_east_asian, family);
      AppendValue(eCSSProperty_font_variant_ligatures, family);
      AppendValue(eCSSProperty_font_variant_numeric, family);
      AppendValue(eCSSProperty_font_variant_position, family);
    }
    else {
      AppendValue(eCSSProperty__x_system_font, family);
      nsCSSValue systemFont(eCSSUnit_System_Font);
      AppendValue(eCSSProperty_font_family, systemFont);
      AppendValue(eCSSProperty_font_style, systemFont);
      AppendValue(eCSSProperty_font_weight, systemFont);
      AppendValue(eCSSProperty_font_size, systemFont);
      AppendValue(eCSSProperty_line_height, systemFont);
      AppendValue(eCSSProperty_font_stretch, systemFont);
      AppendValue(eCSSProperty_font_size_adjust, systemFont);
      AppendValue(eCSSProperty_font_feature_settings, systemFont);
      AppendValue(eCSSProperty_font_language_override, systemFont);
      AppendValue(eCSSProperty_font_kerning, systemFont);
      AppendValue(eCSSProperty_font_synthesis, systemFont);
      AppendValue(eCSSProperty_font_variant_alternates, systemFont);
      AppendValue(eCSSProperty_font_variant_caps, systemFont);
      AppendValue(eCSSProperty_font_variant_east_asian, systemFont);
      AppendValue(eCSSProperty_font_variant_ligatures, systemFont);
      AppendValue(eCSSProperty_font_variant_numeric, systemFont);
      AppendValue(eCSSProperty_font_variant_position, systemFont);
    }
    return true;
  }

  // Get optional font-style, font-variant, font-weight, font-stretch
  // (in any order)

  // Indexes into fontIDs[] and values[] arrays.
  const int kFontStyleIndex = 0;
  const int kFontVariantIndex = 1;
  const int kFontWeightIndex = 2;
  const int kFontStretchIndex = 3;

  // The order of the initializers here must match the order of the indexes
  // defined above!
  static const nsCSSPropertyID fontIDs[] = {
    eCSSProperty_font_style,
    eCSSProperty_font_variant_caps,
    eCSSProperty_font_weight,
    eCSSProperty_font_stretch
  };

  const int32_t numProps = MOZ_ARRAY_LENGTH(fontIDs);
  nsCSSValue  values[numProps];
  int32_t found = ParseChoice(values, fontIDs, numProps);
  if (found < 0 ||
      eCSSUnit_Inherit == values[kFontStyleIndex].GetUnit() ||
      eCSSUnit_Initial == values[kFontStyleIndex].GetUnit() ||
      eCSSUnit_Unset == values[kFontStyleIndex].GetUnit()) { // illegal data
    return false;
  }
  if ((found & (1 << kFontStyleIndex)) == 0) {
    // Provide default font-style
    values[kFontStyleIndex].SetIntValue(NS_FONT_STYLE_NORMAL,
                                        eCSSUnit_Enumerated);
  }
  if ((found & (1 << kFontVariantIndex)) == 0) {
    // Provide default font-variant
    values[kFontVariantIndex].SetNormalValue();
  } else {
    if (values[kFontVariantIndex].GetUnit() == eCSSUnit_Enumerated &&
        values[kFontVariantIndex].GetIntValue() !=
        NS_FONT_VARIANT_CAPS_SMALLCAPS) {
      // only normal or small-caps is allowed in font shorthand
      // this also assumes other values for font-variant-caps never overlap
      // possible values for style or weight
      return false;
    }
  }
  if ((found & (1 << kFontWeightIndex)) == 0) {
    // Provide default font-weight
    values[kFontWeightIndex].SetIntValue(NS_FONT_WEIGHT_NORMAL,
                                         eCSSUnit_Enumerated);
  }
  if ((found & (1 << kFontStretchIndex)) == 0) {
    // Provide default font-stretch
    values[kFontStretchIndex].SetIntValue(NS_FONT_STRETCH_NORMAL,
                                          eCSSUnit_Enumerated);
  }

  // Get mandatory font-size
  nsCSSValue  size;
  if (!ParseSingleTokenNonNegativeVariant(size, VARIANT_KEYWORD | VARIANT_LP,
                                          nsCSSProps::kFontSizeKTable)) {
    return false;
  }

  // Get optional "/" line-height
  nsCSSValue  lineHeight;
  if (ExpectSymbol('/', true)) {
    if (ParseNonNegativeVariant(lineHeight,
                                VARIANT_NUMBER | VARIANT_LP |
                                  VARIANT_NORMAL | VARIANT_CALC,
                                nullptr) != CSSParseResult::Ok) {
      return false;
    }
  }
  else {
    lineHeight.SetNormalValue();
  }

  // Get final mandatory font-family
  nsAutoParseCompoundProperty compound(this);
  if (ParseFamily(family)) {
    if (eCSSUnit_Inherit != family.GetUnit() &&
        eCSSUnit_Initial != family.GetUnit() &&
        eCSSUnit_Unset != family.GetUnit()) {
      AppendValue(eCSSProperty__x_system_font, nsCSSValue(eCSSUnit_None));
      AppendValue(eCSSProperty_font_family, family);
      AppendValue(eCSSProperty_font_style, values[kFontStyleIndex]);
      AppendValue(eCSSProperty_font_variant_caps, values[kFontVariantIndex]);
      AppendValue(eCSSProperty_font_weight, values[kFontWeightIndex]);
      AppendValue(eCSSProperty_font_size, size);
      AppendValue(eCSSProperty_line_height, lineHeight);
      AppendValue(eCSSProperty_font_stretch, values[kFontStretchIndex]);
      AppendValue(eCSSProperty_font_size_adjust, nsCSSValue(eCSSUnit_None));
      AppendValue(eCSSProperty_font_feature_settings, nsCSSValue(eCSSUnit_Normal));
      AppendValue(eCSSProperty_font_language_override, nsCSSValue(eCSSUnit_Normal));
      AppendValue(eCSSProperty_font_kerning,
                  nsCSSValue(NS_FONT_KERNING_AUTO, eCSSUnit_Enumerated));
      AppendValue(eCSSProperty_font_synthesis,
                  nsCSSValue(NS_FONT_SYNTHESIS_WEIGHT | NS_FONT_SYNTHESIS_STYLE,
                             eCSSUnit_Enumerated));
      AppendValue(eCSSProperty_font_variant_alternates,
                  nsCSSValue(eCSSUnit_Normal));
      AppendValue(eCSSProperty_font_variant_east_asian,
                  nsCSSValue(eCSSUnit_Normal));
      AppendValue(eCSSProperty_font_variant_ligatures,
                  nsCSSValue(eCSSUnit_Normal));
      AppendValue(eCSSProperty_font_variant_numeric,
                  nsCSSValue(eCSSUnit_Normal));
      AppendValue(eCSSProperty_font_variant_position,
                  nsCSSValue(eCSSUnit_Normal));
      return true;
    }
  }
  return false;
}

bool
CSSParserImpl::ParseFontSynthesis(nsCSSValue& aValue)
{
  if (!ParseSingleTokenVariant(aValue, VARIANT_HK | VARIANT_NONE,
                               nsCSSProps::kFontSynthesisKTable)) {
    return false;
  }

  // first value 'none' ==> done
  if (eCSSUnit_None == aValue.GetUnit() ||
      eCSSUnit_Initial == aValue.GetUnit() ||
      eCSSUnit_Inherit == aValue.GetUnit() ||
      eCSSUnit_Unset == aValue.GetUnit())
  {
    return true;
  }

  // look for a second value
  int32_t intValue = aValue.GetIntValue();
  nsCSSValue nextValue;

  if (ParseEnum(nextValue, nsCSSProps::kFontSynthesisKTable)) {
    int32_t nextIntValue = nextValue.GetIntValue();
    if (nextIntValue & intValue) {
      return false;
    }
    aValue.SetIntValue(nextIntValue | intValue, eCSSUnit_Enumerated);
  }

  return true;
}

// font-variant-alternates allows for a combination of multiple
// simple enumerated values and functional values.  Functional values have
// parameter lists with one or more idents which are later resolved
// based on values defined in @font-feature-value rules.
//
// font-variant-alternates: swash(flowing) historical-forms styleset(alt-g, alt-m);
//
// So for this the nsCSSValue is set to a pair value, with one
// value for a bitmask of both simple and functional property values
// and another value containing a ValuePairList with lists of idents
// for each functional property value.
//
// pairValue
//   o intValue
//       NS_FONT_VARIANT_ALTERNATES_SWASH |
//       NS_FONT_VARIANT_ALTERNATES_STYLESET
//   o valuePairList, each element with
//     - intValue - indicates which alternate
//     - string or valueList of strings
//
// Note: when only 'historical-forms' is specified, there are no
// functional values to store, in which case the valuePairList is a
// single element dummy list.  In all other cases, the length of the
// list will match the number of functional values.

#define MAX_ALLOWED_FEATURES 512

static uint16_t
MaxElementsForAlternateType(nsCSSKeyword keyword)
{
  uint16_t maxElems = 1;
  if (keyword == eCSSKeyword_styleset ||
      keyword == eCSSKeyword_character_variant) {
    maxElems = MAX_ALLOWED_FEATURES;
  }
  return maxElems;
}

bool
CSSParserImpl::ParseSingleAlternate(int32_t& aWhichFeature,
                                    nsCSSValue& aValue)
{
  if (!GetToken(true)) {
    return false;
  }

  bool isIdent = (mToken.mType == eCSSToken_Ident);
  if (mToken.mType != eCSSToken_Function && !isIdent) {
    UngetToken();
    return false;
  }

  // ident ==> simple enumerated prop val (e.g. historical-forms)
  // function ==> e.g. swash(flowing) styleset(alt-g, alt-m)

  nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
  if (!(eCSSKeyword_UNKNOWN < keyword &&
        nsCSSProps::FindKeyword(keyword,
                                (isIdent ?
                                 nsCSSProps::kFontVariantAlternatesKTable :
                                 nsCSSProps::kFontVariantAlternatesFuncsKTable),
                                aWhichFeature)))
  {
    // failed, pop token
    UngetToken();
    return false;
  }

  if (isIdent) {
    aValue.SetIntValue(aWhichFeature, eCSSUnit_Enumerated);
    return true;
  }

  return ParseFunction(keyword, nullptr, VARIANT_IDENTIFIER,
                       1, MaxElementsForAlternateType(keyword), aValue);
}

bool
CSSParserImpl::ParseFontVariantAlternates(nsCSSValue& aValue)
{
  if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NORMAL,
                              nullptr)) {
    return true;
  }

  // iterate through parameters
  nsCSSValue listValue;
  int32_t feature, featureFlags = 0;

  // if no functional values, this may be a list with a single, unused element
  listValue.SetListValue();

  nsCSSValueList* list = nullptr;
  nsCSSValue value;
  while (ParseSingleAlternate(feature, value)) {

    // check to make sure value not already set
    if (feature == 0 ||
        feature & featureFlags) {
      return false;
    }

    featureFlags |= feature;

    // if function, need to add to the list of functions
    if (value.GetUnit() == eCSSUnit_Function) {
      if (!list) {
        list = listValue.GetListValue();
      } else {
        list->mNext = new nsCSSValueList;
        list = list->mNext;
      }
      list->mValue = value;
    }
  }

  if (featureFlags == 0) {
    // ParseSingleAlternate failed the first time through the loop.
    return false;
  }

  nsCSSValue featureValue;
  featureValue.SetIntValue(featureFlags, eCSSUnit_Enumerated);
  aValue.SetPairValue(featureValue, listValue);

  return true;
}

bool
CSSParserImpl::MergeBitmaskValue(int32_t aNewValue,
                                 const int32_t aMasks[],
                                 int32_t& aMergedValue)
{
  // check to make sure value not already set
  if (aNewValue & aMergedValue) {
    return false;
  }

  const int32_t *m = aMasks;
  int32_t c = 0;

  while (*m != MASK_END_VALUE) {
    if (*m & aNewValue) {
      c = aMergedValue & *m;
      break;
    }
    m++;
  }

  if (c) {
    return false;
  }

  aMergedValue |= aNewValue;
  return true;
}

// aMasks - array of masks for mutually-exclusive property values,
//          e.g. proportial-nums, tabular-nums

bool
CSSParserImpl::ParseBitmaskValues(nsCSSValue& aValue,
                                  const KTableEntry aKeywordTable[],
                                  const int32_t aMasks[])
{
  // Parse at least one keyword
  if (!ParseEnum(aValue, aKeywordTable)) {
    return false;
  }

  // look for more values
  nsCSSValue nextValue;
  int32_t mergedValue = aValue.GetIntValue();

  while (ParseEnum(nextValue, aKeywordTable))
  {
    if (!MergeBitmaskValue(nextValue.GetIntValue(), aMasks, mergedValue)) {
      return false;
    }
  }

  aValue.SetIntValue(mergedValue, eCSSUnit_Enumerated);

  return true;
}

static const int32_t maskEastAsian[] = {
  NS_FONT_VARIANT_EAST_ASIAN_VARIANT_MASK,
  NS_FONT_VARIANT_EAST_ASIAN_WIDTH_MASK,
  MASK_END_VALUE
};

bool
CSSParserImpl::ParseFontVariantEastAsian(nsCSSValue& aValue)
{
  if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NORMAL,
                              nullptr)) {
    return true;
  }

  NS_ASSERTION(maskEastAsian[ArrayLength(maskEastAsian) - 1] ==
                 MASK_END_VALUE,
               "incorrectly terminated array");

  return ParseBitmaskValues(aValue, nsCSSProps::kFontVariantEastAsianKTable,
                            maskEastAsian);
}

bool
CSSParserImpl::ParseContain(nsCSSValue& aValue)
{
  if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NONE,
                              nullptr)) {
    return true;
  }
  static const int32_t maskContain[] = { MASK_END_VALUE };
  if (!ParseBitmaskValues(aValue, nsCSSProps::kContainKTable, maskContain)) {
    return false;
  }
  if (aValue.GetIntValue() & NS_STYLE_CONTAIN_STRICT) {
    if (aValue.GetIntValue() != NS_STYLE_CONTAIN_STRICT) {
      // Disallow any other keywords in combination with 'strict'.
      return false;
    }
    // Strict implies layout, style, and paint.
    // However, for serialization purposes, we keep the strict bit around.
    aValue.SetIntValue(NS_STYLE_CONTAIN_STRICT |
        NS_STYLE_CONTAIN_ALL_BITS, eCSSUnit_Enumerated);
  }
  return true;
}

static const int32_t maskLigatures[] = {
  NS_FONT_VARIANT_LIGATURES_COMMON_MASK,
  NS_FONT_VARIANT_LIGATURES_DISCRETIONARY_MASK,
  NS_FONT_VARIANT_LIGATURES_HISTORICAL_MASK,
  NS_FONT_VARIANT_LIGATURES_CONTEXTUAL_MASK,
  MASK_END_VALUE
};

bool
CSSParserImpl::ParseFontVariantLigatures(nsCSSValue& aValue)
{
  if (ParseSingleTokenVariant(aValue,
                              VARIANT_INHERIT | VARIANT_NORMAL | VARIANT_NONE,
                              nullptr)) {
    return true;
  }

  NS_ASSERTION(maskLigatures[ArrayLength(maskLigatures) - 1] ==
                 MASK_END_VALUE,
               "incorrectly terminated array");

  return ParseBitmaskValues(aValue, nsCSSProps::kFontVariantLigaturesKTable,
                            maskLigatures);
}

static const int32_t maskNumeric[] = {
  NS_FONT_VARIANT_NUMERIC_FIGURE_MASK,
  NS_FONT_VARIANT_NUMERIC_SPACING_MASK,
  NS_FONT_VARIANT_NUMERIC_FRACTION_MASK,
  MASK_END_VALUE
};

bool
CSSParserImpl::ParseFontVariantNumeric(nsCSSValue& aValue)
{
  if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NORMAL,
                              nullptr)) {
    return true;
  }

  NS_ASSERTION(maskNumeric[ArrayLength(maskNumeric) - 1] ==
                 MASK_END_VALUE,
               "incorrectly terminated array");

  return ParseBitmaskValues(aValue, nsCSSProps::kFontVariantNumericKTable,
                            maskNumeric);
}

bool
CSSParserImpl::ParseFontVariant()
{
  // parse single values - normal/inherit/none
  nsCSSValue value;
  nsCSSValue normal(eCSSUnit_Normal);

  if (ParseSingleTokenVariant(value,
                              VARIANT_INHERIT | VARIANT_NORMAL | VARIANT_NONE,
                              nullptr)) {
    AppendValue(eCSSProperty_font_variant_ligatures, value);
    if (eCSSUnit_None == value.GetUnit()) {
      // 'none' applies the value 'normal' to all properties other
      // than 'font-variant-ligatures'
      value.SetNormalValue();
    }
    AppendValue(eCSSProperty_font_variant_alternates, value);
    AppendValue(eCSSProperty_font_variant_caps, value);
    AppendValue(eCSSProperty_font_variant_east_asian, value);
    AppendValue(eCSSProperty_font_variant_numeric, value);
    AppendValue(eCSSProperty_font_variant_position, value);
    return true;
  }

  // set each of the individual subproperties
  int32_t altFeatures = 0, capsFeatures = 0, eastAsianFeatures = 0,
          ligFeatures = 0, numericFeatures = 0, posFeatures = 0;
  nsCSSValue altListValue;
  nsCSSValueList* altList = nullptr;

  // if no functional values, this may be a list with a single, unused element
  altListValue.SetListValue();

  bool foundValid = false; // found at least one proper value
  while (GetToken(true)) {
    // only an ident or a function at this point
    bool isFunction = (mToken.mType == eCSSToken_Function);
    if (mToken.mType != eCSSToken_Ident && !isFunction) {
      UngetToken();
      break;
    }

    nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
    if (keyword == eCSSKeyword_UNKNOWN) {
      UngetToken();
      return false;
    }

    int32_t feature;

    // function? ==> font-variant-alternates
    if (isFunction) {
      if (!nsCSSProps::FindKeyword(keyword,
                                   nsCSSProps::kFontVariantAlternatesFuncsKTable,
                                   feature) ||
          (feature & altFeatures)) {
        UngetToken();
        return false;
      }

      altFeatures |= feature;
      nsCSSValue funcValue;
      if (!ParseFunction(keyword, nullptr, VARIANT_IDENTIFIER, 1,
                         MaxElementsForAlternateType(keyword), funcValue) ||
          funcValue.GetUnit() != eCSSUnit_Function) {
        UngetToken();
        return false;
      }

      if (!altList) {
        altList = altListValue.GetListValue();
      } else {
        altList->mNext = new nsCSSValueList;
        altList = altList->mNext;
      }
      altList->mValue = funcValue;
    } else if (nsCSSProps::FindKeyword(keyword,
                                       nsCSSProps::kFontVariantCapsKTable,
                                       feature)) {
      if (capsFeatures != 0) {
        // multiple values for font-variant-caps
        UngetToken();
        return false;
      }
      capsFeatures = feature;
    } else if (nsCSSProps::FindKeyword(keyword,
                                       nsCSSProps::kFontVariantAlternatesKTable,
                                       feature)) {
      if (feature & altFeatures) {
        // same value repeated
        UngetToken();
        return false;
      }
      altFeatures |= feature;
    } else if (nsCSSProps::FindKeyword(keyword,
                                       nsCSSProps::kFontVariantEastAsianKTable,
                                       feature)) {
      if (!MergeBitmaskValue(feature, maskEastAsian, eastAsianFeatures)) {
        // multiple mutually exclusive values
        UngetToken();
        return false;
      }
    } else if (nsCSSProps::FindKeyword(keyword,
                                       nsCSSProps::kFontVariantLigaturesKTable,
                                       feature)) {
      if (keyword == eCSSKeyword_none ||
          !MergeBitmaskValue(feature, maskLigatures, ligFeatures)) {
        // none or multiple mutually exclusive values
        UngetToken();
        return false;
      }
    } else if (nsCSSProps::FindKeyword(keyword,
                                       nsCSSProps::kFontVariantNumericKTable,
                                       feature)) {
      if (!MergeBitmaskValue(feature, maskNumeric, numericFeatures)) {
        // multiple mutually exclusive values
        UngetToken();
        return false;
      }
    } else if (nsCSSProps::FindKeyword(keyword,
                                       nsCSSProps::kFontVariantPositionKTable,
                                       feature)) {
      if (posFeatures != 0) {
        // multiple values for font-variant-caps
        UngetToken();
        return false;
      }
      posFeatures = feature;
    } else {
      // bogus keyword, bail...
      UngetToken();
      return false;
    }

    foundValid = true;
  }

  if (!foundValid) {
    return false;
  }

  if (altFeatures) {
    nsCSSValue featureValue;
    featureValue.SetIntValue(altFeatures, eCSSUnit_Enumerated);
    value.SetPairValue(featureValue, altListValue);
    AppendValue(eCSSProperty_font_variant_alternates, value);
  } else {
    AppendValue(eCSSProperty_font_variant_alternates, normal);
  }

  if (capsFeatures) {
    value.SetIntValue(capsFeatures, eCSSUnit_Enumerated);
    AppendValue(eCSSProperty_font_variant_caps, value);
  } else {
    AppendValue(eCSSProperty_font_variant_caps, normal);
  }

  if (eastAsianFeatures) {
    value.SetIntValue(eastAsianFeatures, eCSSUnit_Enumerated);
    AppendValue(eCSSProperty_font_variant_east_asian, value);
  } else {
    AppendValue(eCSSProperty_font_variant_east_asian, normal);
  }

  if (ligFeatures) {
    value.SetIntValue(ligFeatures, eCSSUnit_Enumerated);
    AppendValue(eCSSProperty_font_variant_ligatures, value);
  } else {
    AppendValue(eCSSProperty_font_variant_ligatures, normal);
  }

  if (numericFeatures) {
    value.SetIntValue(numericFeatures, eCSSUnit_Enumerated);
    AppendValue(eCSSProperty_font_variant_numeric, value);
  } else {
    AppendValue(eCSSProperty_font_variant_numeric, normal);
  }

  if (posFeatures) {
    value.SetIntValue(posFeatures, eCSSUnit_Enumerated);
    AppendValue(eCSSProperty_font_variant_position, value);
  } else {
    AppendValue(eCSSProperty_font_variant_position, normal);
  }

  return true;
}

bool
CSSParserImpl::ParseFontWeight(nsCSSValue& aValue)
{
  if (ParseSingleTokenVariant(aValue, VARIANT_HKI | VARIANT_SYSFONT,
                              nsCSSProps::kFontWeightKTable)) {
    if (eCSSUnit_Integer == aValue.GetUnit()) { // ensure unit value
      int32_t intValue = aValue.GetIntValue();
      if ((100 <= intValue) &&
          (intValue <= 900) &&
          (0 == (intValue % 100))) {
        return true;
      } else {
        UngetToken();
        return false;
      }
    }
    return true;
  }
  return false;
}

bool
CSSParserImpl::ParseOneFamily(nsAString& aFamily,
                              bool& aOneKeyword,
                              bool& aQuoted)
{
  if (!GetToken(true))
    return false;

  nsCSSToken* tk = &mToken;

  aOneKeyword = false;
  aQuoted = false;
  if (eCSSToken_Ident == tk->mType) {
    aOneKeyword = true;
    aFamily.Append(tk->mIdent);
    for (;;) {
      if (!GetToken(false))
        break;

      if (eCSSToken_Ident == tk->mType) {
        aOneKeyword = false;
        // We had at least another keyword before.
        // "If a sequence of identifiers is given as a font family name,
        //  the computed value is the name converted to a string by joining
        //  all the identifiers in the sequence by single spaces."
        // -- CSS 2.1, section 15.3
        // Whitespace tokens do not actually matter,
        // identifier tokens can be separated by comments.
        aFamily.Append(char16_t(' '));
        aFamily.Append(tk->mIdent);
      } else if (eCSSToken_Whitespace != tk->mType) {
        UngetToken();
        break;
      }
    }
    return true;

  } else if (eCSSToken_String == tk->mType) {
    aQuoted = true;
    aFamily.Append(tk->mIdent); // XXX What if it had escaped quotes?
    return true;

  } else {
    UngetToken();
    return false;
  }
}


static bool
AppendGeneric(nsCSSKeyword aKeyword, FontFamilyList *aFamilyList)
{
  switch (aKeyword) {
    case eCSSKeyword_serif:
      aFamilyList->Append(FontFamilyName(eFamily_serif));
      return true;
    case eCSSKeyword_sans_serif:
      aFamilyList->Append(FontFamilyName(eFamily_sans_serif));
      return true;
    case eCSSKeyword_monospace:
      aFamilyList->Append(FontFamilyName(eFamily_monospace));
      return true;
    case eCSSKeyword_cursive:
      aFamilyList->Append(FontFamilyName(eFamily_cursive));
      return true;
    case eCSSKeyword_fantasy:
      aFamilyList->Append(FontFamilyName(eFamily_fantasy));
      return true;
    case eCSSKeyword__moz_fixed:
      aFamilyList->Append(FontFamilyName(eFamily_moz_fixed));
      return true;
    default:
      break;
  }

  return false;
}

bool
CSSParserImpl::ParseFamily(nsCSSValue& aValue)
{
  RefPtr<css::FontFamilyListRefCnt> familyList =
    new css::FontFamilyListRefCnt();
  nsAutoString family;
  bool single, quoted;

  // keywords only have meaning in the first position
  if (!ParseOneFamily(family, single, quoted))
    return false;

  // check for keywords, but only when keywords appear by themselves
  // i.e. not in compounds such as font-family: default blah;
  bool foundGeneric = false;
  if (single) {
    nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(family);
    switch (keyword) {
      case eCSSKeyword_inherit:
        aValue.SetInheritValue();
        return true;
      case eCSSKeyword_default:
        // 605231 - don't parse unquoted 'default' reserved keyword
        return false;
      case eCSSKeyword_initial:
        aValue.SetInitialValue();
        return true;
      case eCSSKeyword_unset:
        if (nsLayoutUtils::UnsetValueEnabled()) {
          aValue.SetUnsetValue();
          return true;
        }
        break;
      case eCSSKeyword__moz_use_system_font:
        if (!IsParsingCompoundProperty()) {
          aValue.SetSystemFontValue();
          return true;
        }
        break;
      default:
        foundGeneric = AppendGeneric(keyword, familyList);
    }
  }

  if (!foundGeneric) {
    familyList->Append(
      FontFamilyName(family, (quoted ? eQuotedName : eUnquotedName)));
  }

  for (;;) {
    if (!ExpectSymbol(',', true))
      break;

    nsAutoString nextFamily;
    if (!ParseOneFamily(nextFamily, single, quoted))
      return false;

    // at this point unquoted keywords are not allowed
    // as font family names but can appear within names
    foundGeneric = false;
    if (single) {
      nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(nextFamily);
      switch (keyword) {
        case eCSSKeyword_inherit:
        case eCSSKeyword_initial:
        case eCSSKeyword_default:
        case eCSSKeyword__moz_use_system_font:
          return false;
        case eCSSKeyword_unset:
          if (nsLayoutUtils::UnsetValueEnabled()) {
            return false;
          }
          break;
        default:
          foundGeneric = AppendGeneric(keyword, familyList);
          break;
      }
    }

    if (!foundGeneric) {
      familyList->Append(
        FontFamilyName(nextFamily, (quoted ? eQuotedName : eUnquotedName)));
    }
  }

  if (familyList->IsEmpty()) {
    return false;
  }

  aValue.SetFontFamilyListValue(familyList);
  return true;
}

// src: ( uri-src | local-src ) (',' ( uri-src | local-src ) )*
// uri-src: uri [ 'format(' string ( ',' string )* ')' ]
// local-src: 'local(' ( string | ident ) ')'

bool
CSSParserImpl::ParseFontSrc(nsCSSValue& aValue)
{
  // could we maybe turn nsCSSValue::Array into InfallibleTArray<nsCSSValue>?
  InfallibleTArray<nsCSSValue> values;
  nsCSSValue cur;
  for (;;) {
    if (!GetToken(true))
      break;

    if (mToken.mType == eCSSToken_URL) {
      SetValueToURL(cur, mToken.mIdent);
      values.AppendElement(cur);
      if (!ParseFontSrcFormat(values))
        return false;

    } else if (mToken.mType == eCSSToken_Function &&
               mToken.mIdent.LowerCaseEqualsLiteral("local")) {
      // css3-fonts does not specify a formal grammar for local().
      // The text permits both unquoted identifiers and quoted
      // strings.  We resolve this ambiguity in the spec by
      // assuming that the appropriate production is a single
      // <family-name>, possibly surrounded by whitespace.

      nsAutoString family;
      bool single, quoted;
      if (!ParseOneFamily(family, single, quoted)) {
        SkipUntil(')');
        return false;
      }
      if (!ExpectSymbol(')', true)) {
        SkipUntil(')');
        return false;
      }

      // reject generics
      if (single) {
        nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(family);
        switch (keyword) {
          case eCSSKeyword_serif:
          case eCSSKeyword_sans_serif:
          case eCSSKeyword_monospace:
          case eCSSKeyword_cursive:
          case eCSSKeyword_fantasy:
          case eCSSKeyword__moz_fixed:
            return false;
          default:
            break;
        }
      }

      cur.SetStringValue(family, eCSSUnit_Local_Font);
      values.AppendElement(cur);
    } else {
      // We don't know what to do with this token; unget it and error out
      UngetToken();
      return false;
    }

    if (!ExpectSymbol(',', true))
      break;
  }

  if (values.Length() == 0)
    return false;

  RefPtr<nsCSSValue::Array> srcVals
    = nsCSSValue::Array::Create(values.Length());

  uint32_t i;
  for (i = 0; i < values.Length(); i++)
    srcVals->Item(i) = values[i];
  aValue.SetArrayValue(srcVals, eCSSUnit_Array);
  return true;
}

bool
CSSParserImpl::ParseFontSrcFormat(InfallibleTArray<nsCSSValue> & values)
{
  if (!GetToken(true))
    return true; // EOF harmless here
  if (mToken.mType != eCSSToken_Function ||
      !mToken.mIdent.LowerCaseEqualsLiteral("format")) {
    UngetToken();
    return true;
  }

  do {
    if (!GetToken(true))
      return false; // EOF - no need for SkipUntil

    if (mToken.mType != eCSSToken_String) {
      UngetToken();
      SkipUntil(')');
      return false;
    }

    nsCSSValue cur(mToken.mIdent, eCSSUnit_Font_Format);
    values.AppendElement(cur);
  } while (ExpectSymbol(',', true));

  if (!ExpectSymbol(')', true)) {
    SkipUntil(')');
    return false;
  }

  return true;
}

// font-ranges: urange ( ',' urange )*
bool
CSSParserImpl::ParseFontRanges(nsCSSValue& aValue)
{
  InfallibleTArray<uint32_t> ranges;
  for (;;) {
    if (!GetToken(true))
      break;

    if (mToken.mType != eCSSToken_URange) {
      UngetToken();
      break;
    }

    // An invalid range token is a parsing error, causing the entire
    // descriptor to be ignored.
    if (!mToken.mIntegerValid)
      return false;

    uint32_t low = mToken.mInteger;
    uint32_t high = mToken.mInteger2;

    // A range that descends, or a range that is entirely outside the
    // current range of Unicode (U+0-10FFFF) is ignored, but does not
    // invalidate the descriptor.  A range that straddles the high end
    // is clipped.
    if (low <= 0x10FFFF && low <= high) {
      if (high > 0x10FFFF)
        high = 0x10FFFF;

      ranges.AppendElement(low);
      ranges.AppendElement(high);
    }
    if (!ExpectSymbol(',', true))
      break;
  }

  if (ranges.Length() == 0)
    return false;

  RefPtr<nsCSSValue::Array> srcVals
    = nsCSSValue::Array::Create(ranges.Length());

  for (uint32_t i = 0; i < ranges.Length(); i++)
    srcVals->Item(i).SetIntValue(ranges[i], eCSSUnit_Integer);
  aValue.SetArrayValue(srcVals, eCSSUnit_Array);
  return true;
}

// font-feature-settings: normal | <feature-tag-value> [, <feature-tag-value>]*
// <feature-tag-value> = <string> [ <integer> | on | off ]?

// minimum - "tagx", "tagy", "tagz"
// edge error case - "tagx" on 1, "tagx" "tagy", "tagx" -1, "tagx" big

// pair value is always x = string, y = int

// font feature tags must be four ASCII characters
#define FEATURE_TAG_LENGTH   4

static bool
ValidFontFeatureTag(const nsString& aTag)
{
  if (aTag.Length() != FEATURE_TAG_LENGTH) {
    return false;
  }
  uint32_t i;
  for (i = 0; i < FEATURE_TAG_LENGTH; i++) {
    uint32_t ch = aTag[i];
    if (ch < 0x20 || ch > 0x7e) {
      return false;
    }
  }
  return true;
}

bool
CSSParserImpl::ParseFontFeatureSettings(nsCSSValue& aValue)
{
  if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NORMAL,
                              nullptr)) {
    return true;
  }

  nsCSSValuePairList *cur = aValue.SetPairListValue();
  for (;;) {
    // feature tag
    if (!GetToken(true)) {
      return false;
    }

    if (mToken.mType != eCSSToken_String ||
        !ValidFontFeatureTag(mToken.mIdent)) {
      UngetToken();
      return false;
    }
    cur->mXValue.SetStringValue(mToken.mIdent, eCSSUnit_String);

    if (!GetToken(true)) {
      cur->mYValue.SetIntValue(1, eCSSUnit_Integer);
      break;
    }

    // optional value or on/off keyword
    if (mToken.mType == eCSSToken_Number && mToken.mIntegerValid &&
        mToken.mInteger >= 0) {
      cur->mYValue.SetIntValue(mToken.mInteger, eCSSUnit_Integer);
    } else if (mToken.mType == eCSSToken_Ident &&
               mToken.mIdent.LowerCaseEqualsLiteral("on")) {
      cur->mYValue.SetIntValue(1, eCSSUnit_Integer);
    } else if (mToken.mType == eCSSToken_Ident &&
               mToken.mIdent.LowerCaseEqualsLiteral("off")) {
      cur->mYValue.SetIntValue(0, eCSSUnit_Integer);
    } else {
      // something other than value/on/off, set default value
      cur->mYValue.SetIntValue(1, eCSSUnit_Integer);
      UngetToken();
    }

    if (!ExpectSymbol(',', true)) {
      break;
    }

    cur->mNext = new nsCSSValuePairList;
    cur = cur->mNext;
  }

  return true;
}

bool
CSSParserImpl::ParseFontVariationSettings(nsCSSValue& aValue)
{
  // TODO: Actually implement this.
  
  // This stub is here because websites insist on considering this
  // very hardware-dependent and O.S.-variable low-level font-control
  // as a "critical feature" which it isn't as there is 0 guarantee
  // that font variation settings are supported or honored by any
  // operating system used by the client.
  return true;
}

bool
CSSParserImpl::ParseListStyle()
{
  // 'list-style' can accept 'none' for two different subproperties,
  // 'list-style-type' and 'list-style-image'.  In order to accept
  // 'none' as the value of either but still allow another value for
  // either, we need to ensure that the first 'none' we find gets
  // allocated to a dummy property instead. Since parse function for
  // 'list-style-type' could accept values for 'list-style-position',
  // we put position in front of type.
  static const nsCSSPropertyID listStyleIDs[] = {
    eCSSPropertyExtra_x_none_value,
    eCSSProperty_list_style_position,
    eCSSProperty_list_style_type,
    eCSSProperty_list_style_image
  };

  nsCSSValue values[MOZ_ARRAY_LENGTH(listStyleIDs)];
  int32_t found =
    ParseChoice(values, listStyleIDs, ArrayLength(listStyleIDs));
  if (found < 1) {
    return false;
  }

  if ((found & (1|4|8)) == (1|4|8)) {
    if (values[0].GetUnit() == eCSSUnit_None) {
      // We found a 'none' plus another value for both of
      // 'list-style-type' and 'list-style-image'.  This is a parse
      // error, since the 'none' has to count for at least one of them.
      return false;
    } else {
      NS_ASSERTION(found == (1|2|4|8) && values[0] == values[1] &&
                   values[0] == values[2] && values[0] == values[3],
                   "should be a special value");
    }
  }

  if ((found & 2) == 0) {
    values[1].SetIntValue(NS_STYLE_LIST_STYLE_POSITION_OUTSIDE,
                          eCSSUnit_Enumerated);
  }
  if ((found & 4) == 0) {
    // Provide default values
    nsString type = (found & 1) ?
      NS_LITERAL_STRING("none") : NS_LITERAL_STRING("disc");
    values[2].SetStringValue(type, eCSSUnit_Ident);
  }
  if ((found & 8) == 0) {
    values[3].SetNoneValue();
  }

  // Start at 1 to avoid appending fake value.
  for (uint32_t index = 1; index < ArrayLength(listStyleIDs); ++index) {
    AppendValue(listStyleIDs[index], values[index]);
  }
  return true;
}

bool
CSSParserImpl::ParseListStyleType(nsCSSValue& aValue)
{
  if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_STRING,
                              nullptr)) {
    return true;
  }

  if (ParseCounterStyleNameValue(aValue) || ParseSymbols(aValue)) {
    return true;
  }

  return false;
}

bool
CSSParserImpl::ParseMargin()
{
  static const nsCSSPropertyID kMarginSideIDs[] = {
    eCSSProperty_margin_top,
    eCSSProperty_margin_right,
    eCSSProperty_margin_bottom,
    eCSSProperty_margin_left
  };

  return ParseBoxProperties(kMarginSideIDs);
}

bool
CSSParserImpl::ParseObjectPosition()
{
  nsCSSValue value;
  if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr) &&
      !ParsePositionValue(value)) {
    return false;
  }
  AppendValue(eCSSProperty_object_position, value);
  return true;
}

bool
CSSParserImpl::ParseOutline()
{
  const int32_t numProps = 3;
  static const nsCSSPropertyID kOutlineIDs[] = {
    eCSSProperty_outline_color,
    eCSSProperty_outline_style,
    eCSSProperty_outline_width
  };

  nsCSSValue  values[numProps];
  int32_t found = ParseChoice(values, kOutlineIDs, numProps);
  if (found < 1) {
    return false;
  }

  // Provide default values
  if ((found & 1) == 0) { // Provide default outline-color
    values[0].SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor);
  }
  if ((found & 2) == 0) { // Provide default outline-style
    values[1].SetIntValue(NS_STYLE_BORDER_STYLE_NONE, eCSSUnit_Enumerated);
  }
  if ((found & 4) == 0) { // Provide default outline-width
    values[2].SetIntValue(NS_STYLE_BORDER_WIDTH_MEDIUM, eCSSUnit_Enumerated);
  }

  int32_t index;
  for (index = 0; index < numProps; index++) {
    AppendValue(kOutlineIDs[index], values[index]);
  }
  return true;
}

bool
CSSParserImpl::ParseOverflow()
{
  nsCSSValue overflow;
  if (!ParseSingleTokenVariant(overflow, VARIANT_HK,
                               nsCSSProps::kOverflowKTable)) {
    return false;
  }

  nsCSSValue overflowX(overflow);
  nsCSSValue overflowY(overflow);
  if (eCSSUnit_Enumerated == overflow.GetUnit())
    switch(overflow.GetIntValue()) {
      case NS_STYLE_OVERFLOW_SCROLLBARS_HORIZONTAL:
        overflowX.SetIntValue(NS_STYLE_OVERFLOW_SCROLL, eCSSUnit_Enumerated);
        overflowY.SetIntValue(NS_STYLE_OVERFLOW_HIDDEN, eCSSUnit_Enumerated);
        break;
      case NS_STYLE_OVERFLOW_SCROLLBARS_VERTICAL:
        overflowX.SetIntValue(NS_STYLE_OVERFLOW_HIDDEN, eCSSUnit_Enumerated);
        overflowY.SetIntValue(NS_STYLE_OVERFLOW_SCROLL, eCSSUnit_Enumerated);
        break;
    }
  AppendValue(eCSSProperty_overflow_x, overflowX);
  AppendValue(eCSSProperty_overflow_y, overflowY);
  return true;
}

bool
CSSParserImpl::ParsePadding()
{
  static const nsCSSPropertyID kPaddingSideIDs[] = {
    eCSSProperty_padding_top,
    eCSSProperty_padding_right,
    eCSSProperty_padding_bottom,
    eCSSProperty_padding_left
  };

  return ParseBoxProperties(kPaddingSideIDs);
}

bool
CSSParserImpl::ParseQuotes()
{
  nsCSSValue value;
  if (!ParseSingleTokenVariant(value, VARIANT_HOS, nullptr)) {
    return false;
  }
  if (value.GetUnit() == eCSSUnit_String) {
    nsCSSValue open = value;
    nsCSSValuePairList* quotes = value.SetPairListValue();
    for (;;) {
      quotes->mXValue = open;
      // get mandatory close
      if (!ParseSingleTokenVariant(quotes->mYValue, VARIANT_STRING, nullptr)) {
        return false;
      }
      // look for another open
      if (!ParseSingleTokenVariant(open, VARIANT_STRING, nullptr)) {
        break;
      }
      quotes->mNext = new nsCSSValuePairList;
      quotes = quotes->mNext;
    }
  }
  AppendValue(eCSSProperty_quotes, value);
  return true;
}

bool
CSSParserImpl::ParseTextDecoration()
{
  static const nsCSSPropertyID kTextDecorationIDs[] = {
    eCSSProperty_text_decoration_line,
    eCSSProperty_text_decoration_style,
    eCSSProperty_text_decoration_color
  };
  const int32_t numProps = MOZ_ARRAY_LENGTH(kTextDecorationIDs);
  nsCSSValue values[numProps];

  int32_t found = ParseChoice(values, kTextDecorationIDs, numProps);
  if (found < 1) {
    return false;
  }

  // Provide default values
  if ((found & 1) == 0) { // Provide default text-decoration-line
    values[0].SetIntValue(NS_STYLE_TEXT_DECORATION_LINE_NONE,
                          eCSSUnit_Enumerated);
  }
  if ((found & 2) == 0) { // Provide default text-decoration-style
    values[1].SetIntValue(NS_STYLE_TEXT_DECORATION_STYLE_SOLID,
                          eCSSUnit_Enumerated);
  }
  if ((found & 4) == 0) { // Provide default text-decoration-color
    values[2].SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor);
  }

  for (int32_t index = 0; index < numProps; index++) {
    AppendValue(kTextDecorationIDs[index], values[index]);
  }
  return true;
}

bool
CSSParserImpl::ParseTextEmphasis()
{
  static constexpr nsCSSPropertyID kTextEmphasisIDs[] = {
    eCSSProperty_text_emphasis_style,
    eCSSProperty_text_emphasis_color
  };
  constexpr int32_t numProps = MOZ_ARRAY_LENGTH(kTextEmphasisIDs);
  nsCSSValue values[numProps];

  int32_t found = ParseChoice(values, kTextEmphasisIDs, numProps);
  if (found < 1) {
    return false;
  }

  if (!(found & 1)) { // Provide default text-emphasis-style
    values[0].SetNoneValue();
  }
  if (!(found & 2)) { // Provide default text-emphasis-color
    values[1].SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor);
  }

  for (int32_t index = 0; index < numProps; index++) {
    AppendValue(kTextEmphasisIDs[index], values[index]);
  }
  return true;
}

bool
CSSParserImpl::ParseTextEmphasisPosition(nsCSSValue& aValue)
{
  static_assert((NS_STYLE_TEXT_EMPHASIS_POSITION_OVER ^
                 NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER ^
                 NS_STYLE_TEXT_EMPHASIS_POSITION_LEFT ^
                 NS_STYLE_TEXT_EMPHASIS_POSITION_RIGHT) ==
                (NS_STYLE_TEXT_EMPHASIS_POSITION_OVER |
                 NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER |
                 NS_STYLE_TEXT_EMPHASIS_POSITION_LEFT |
                 NS_STYLE_TEXT_EMPHASIS_POSITION_RIGHT),
                "text-emphasis-position constants should be bitmasks");

  if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT, nullptr)) {
    return true;
  }

  nsCSSValue first, second;
  const auto& kTable = nsCSSProps::kTextEmphasisPositionKTable;
  if (!ParseSingleTokenVariant(first, VARIANT_KEYWORD, kTable) ||
      !ParseSingleTokenVariant(second, VARIANT_KEYWORD, kTable)) {
    return false;
  }

  auto firstValue = first.GetIntValue();
  auto secondValue = second.GetIntValue();
  if ((firstValue == NS_STYLE_TEXT_EMPHASIS_POSITION_OVER ||
      firstValue == NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER) ==
      (secondValue == NS_STYLE_TEXT_EMPHASIS_POSITION_OVER ||
      secondValue == NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER)) {
    return false;
  }

  aValue.SetIntValue(firstValue | secondValue, eCSSUnit_Enumerated);
  return true;
}

bool
CSSParserImpl::ParseTextEmphasisStyle(nsCSSValue& aValue)
{
  static_assert((NS_STYLE_TEXT_EMPHASIS_STYLE_SHAPE_MASK ^
                 NS_STYLE_TEXT_EMPHASIS_STYLE_FILL_MASK) ==
                (NS_STYLE_TEXT_EMPHASIS_STYLE_SHAPE_MASK |
                 NS_STYLE_TEXT_EMPHASIS_STYLE_FILL_MASK),
                "text-emphasis-style shape and fill constants "
                "should not intersect");
  static_assert(NS_STYLE_TEXT_EMPHASIS_STYLE_FILLED == 0,
                "Making 'filled' zero ensures that if neither 'filled' nor "
                "'open' is specified, we compute it to 'filled' per spec");

  if (ParseSingleTokenVariant(aValue, VARIANT_HOS, nullptr)) {
    return true;
  }

  nsCSSValue first, second;
  const auto& fillKTable = nsCSSProps::kTextEmphasisStyleFillKTable;
  const auto& shapeKTable = nsCSSProps::kTextEmphasisStyleShapeKTable;
  if (ParseSingleTokenVariant(first, VARIANT_KEYWORD, fillKTable)) {
    ParseSingleTokenVariant(second, VARIANT_KEYWORD, shapeKTable);
  } else if (ParseSingleTokenVariant(first, VARIANT_KEYWORD, shapeKTable)) {
    ParseSingleTokenVariant(second, VARIANT_KEYWORD, fillKTable);
  } else {
    return false;
  }

  auto value = first.GetIntValue();
  if (second.GetUnit() == eCSSUnit_Enumerated) {
    value |= second.GetIntValue();
  }
  aValue.SetIntValue(value, eCSSUnit_Enumerated);
  return true;
}

bool
CSSParserImpl::ParseTextAlign(nsCSSValue& aValue, const KTableEntry aTable[])
{
  if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT, nullptr)) {
    // 'inherit', 'initial' and 'unset' must be alone
    return true;
  }

  nsCSSValue left;
  if (!ParseSingleTokenVariant(left, VARIANT_KEYWORD, aTable)) {
    return false;
  }

  if (!nsLayoutUtils::IsTextAlignUnsafeValueEnabled()) {
    aValue = left;
    return true;
  }

  nsCSSValue right;
  if (ParseSingleTokenVariant(right, VARIANT_KEYWORD, aTable)) {
    // 'true' must be combined with some other value than 'true'.
    if (left.GetIntValue() == NS_STYLE_TEXT_ALIGN_UNSAFE &&
        right.GetIntValue() == NS_STYLE_TEXT_ALIGN_UNSAFE) {
      return false;
    }
    aValue.SetPairValue(left, right);
  } else {
    // Single value 'true' is not allowed.
    if (left.GetIntValue() == NS_STYLE_TEXT_ALIGN_UNSAFE) {
      return false;
    }
    aValue = left;
  }
  return true;
}

bool
CSSParserImpl::ParseTextAlign(nsCSSValue& aValue)
{
  return ParseTextAlign(aValue, nsCSSProps::kTextAlignKTable);
}

bool
CSSParserImpl::ParseTextAlignLast(nsCSSValue& aValue)
{
  return ParseTextAlign(aValue, nsCSSProps::kTextAlignLastKTable);
}

bool
CSSParserImpl::ParseTextDecorationLine(nsCSSValue& aValue)
{
  static_assert((NS_STYLE_TEXT_DECORATION_LINE_NONE ^
                 NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE ^
                 NS_STYLE_TEXT_DECORATION_LINE_OVERLINE ^
                 NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH ^
                 NS_STYLE_TEXT_DECORATION_LINE_BLINK ^
                 NS_STYLE_TEXT_DECORATION_LINE_PREF_ANCHORS) ==
                (NS_STYLE_TEXT_DECORATION_LINE_NONE |
                 NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE |
                 NS_STYLE_TEXT_DECORATION_LINE_OVERLINE |
                 NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH |
                 NS_STYLE_TEXT_DECORATION_LINE_BLINK |
                 NS_STYLE_TEXT_DECORATION_LINE_PREF_ANCHORS),
                "text decoration constants need to be bitmasks");
  if (ParseSingleTokenVariant(aValue, VARIANT_HK,
                              nsCSSProps::kTextDecorationLineKTable)) {
    if (eCSSUnit_Enumerated == aValue.GetUnit()) {
      int32_t intValue = aValue.GetIntValue();
      if (intValue != NS_STYLE_TEXT_DECORATION_LINE_NONE) {
        // look for more keywords
        nsCSSValue  keyword;
        int32_t index;
        for (index = 0; index < 3; index++) {
          if (ParseEnum(keyword, nsCSSProps::kTextDecorationLineKTable)) {
            int32_t newValue = keyword.GetIntValue();
            if (newValue == NS_STYLE_TEXT_DECORATION_LINE_NONE ||
                newValue & intValue) {
              // 'none' keyword in conjuction with others is not allowed, and
              // duplicate keyword is not allowed.
              return false;
            }
            intValue |= newValue;
          }
          else {
            break;
          }
        }
        aValue.SetIntValue(intValue, eCSSUnit_Enumerated);
      }
    }
    return true;
  }
  return false;
}

bool
CSSParserImpl::ParseTextOverflow(nsCSSValue& aValue)
{
  if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT, nullptr)) {
    // 'inherit', 'initial' and 'unset' must be alone
    return true;
  }

  nsCSSValue left;
  if (!ParseSingleTokenVariant(left, VARIANT_KEYWORD | VARIANT_STRING,
                               nsCSSProps::kTextOverflowKTable))
    return false;

  nsCSSValue right;
  if (ParseSingleTokenVariant(right, VARIANT_KEYWORD | VARIANT_STRING,
                              nsCSSProps::kTextOverflowKTable))
    aValue.SetPairValue(left, right);
  else {
    aValue = left;
  }
  return true;
}

bool
CSSParserImpl::ParseTouchAction(nsCSSValue& aValue)
{
  // Avaliable values of property touch-action:
  // auto | none | [pan-x || pan-y] | manipulation

  if (!ParseSingleTokenVariant(aValue, VARIANT_HK,
                               nsCSSProps::kTouchActionKTable)) {
    return false;
  }

  // Auto and None keywords aren't allowed in conjunction with others.
  // Also inherit, initial and unset values are available.
  if (eCSSUnit_Enumerated != aValue.GetUnit()) {
    return true;
  }

  int32_t intValue = aValue.GetIntValue();
  nsCSSValue nextValue;
  if (ParseEnum(nextValue, nsCSSProps::kTouchActionKTable)) {
    int32_t nextIntValue = nextValue.GetIntValue();

    // duplicates aren't allowed.
    if (nextIntValue & intValue) {
      return false;
    }

    // Auto and None and Manipulation is not allowed in conjunction with others.
    if ((intValue | nextIntValue) & (NS_STYLE_TOUCH_ACTION_NONE |
                                     NS_STYLE_TOUCH_ACTION_AUTO |
                                     NS_STYLE_TOUCH_ACTION_MANIPULATION)) {
      return false;
    }

    aValue.SetIntValue(nextIntValue | intValue, eCSSUnit_Enumerated);
  }

  return true;
}

bool
CSSParserImpl::ParseTextCombineUpright(nsCSSValue& aValue)
{
  if (!ParseSingleTokenVariant(aValue, VARIANT_HK,
                               nsCSSProps::kTextCombineUprightKTable)) {
    return false;
  }

  // if 'digits', need to check for an explicit number [2, 3, 4]
  if (eCSSUnit_Enumerated == aValue.GetUnit() &&
      aValue.GetIntValue() == NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_2) {
    if (!nsLayoutUtils::TextCombineUprightDigitsEnabled()) {
      return false;
    }
    if (!GetToken(true)) {
      return true;
    }
    if (mToken.mType == eCSSToken_Number && mToken.mIntegerValid) {
      switch (mToken.mInteger) {
        case 2:  // already set, nothing to do
          break;
        case 3:
          aValue.SetIntValue(NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_3,
                             eCSSUnit_Enumerated);
          break;
        case 4:
          aValue.SetIntValue(NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_4,
                             eCSSUnit_Enumerated);
          break;
        default:
          // invalid digits value
          return false;
      }
    } else {
      UngetToken();
    }
  }
  return true;
}

///////////////////////////////////////////////////////
// transform Parsing Implementation

/* Reads a function list of arguments and consumes the closing parenthesis.
 * Do not call this function directly; it's meant to be called from
 * ParseFunction.
 */
bool
CSSParserImpl::ParseFunctionInternals(const uint32_t aVariantMask[],
                                      uint32_t aVariantMaskAll,
                                      uint16_t aMinElems,
                                      uint16_t aMaxElems,
                                      InfallibleTArray<nsCSSValue> &aOutput)
{
  NS_ASSERTION((aVariantMask && !aVariantMaskAll) ||
               (!aVariantMask && aVariantMaskAll),
               "only one of the two variant mask parameters can be set");

  for (uint16_t index = 0; index < aMaxElems; ++index) {
    nsCSSValue newValue;
    uint32_t m = aVariantMaskAll ? aVariantMaskAll : aVariantMask[index];
    if (ParseVariant(newValue, m, nullptr) != CSSParseResult::Ok) {
      break;
    }

    if (nsCSSValue::IsFloatUnit(newValue.GetUnit())) {
      // Clamp infinity or -infinity values to max float or -max float to avoid
      // calculations with infinity.
      newValue.SetFloatValue(
        mozilla::clamped(newValue.GetFloatValue(),
                         -std::numeric_limits<float>::max(),
                          std::numeric_limits<float>::max()),
        newValue.GetUnit());
    }

    aOutput.AppendElement(newValue);

    if (ExpectSymbol(',', true)) {
      // Move on to the next argument if we see a comma.
      continue;
    }

    if (ExpectSymbol(')', true)) {
      // Make sure we've read enough symbols if we see a closing parenthesis.
      return (index + 1) >= aMinElems;
    }

    // Only a comma or a closing parenthesis is valid after an argument.
    break;
  }

  // If we're here, we've hit an error without seeing a closing parenthesis or
  // we've read too many elements without seeing a closing parenthesis.
  SkipUntil(')');
  return false;
}

/* Parses a function [ input of the form (a [, b]*) ] and stores it
 * as an nsCSSValue that holds a function of the form
 * function-name arg1 arg2 ... argN
 *
 * On error, the return value is false.
 *
 * @param aFunction The name of the function that we're reading.
 * @param aAllowedTypes An array of values corresponding to the legal
 *        types for each element in the function.  The zeroth element in the
 *        array corresponds to the first function parameter, etc.  The length
 *        of this array _must_ be greater than or equal to aMaxElems or the
 *        behavior is undefined.  If not null, aAllowTypesAll must be 0.
 * @param aAllowedTypesAll If set, every element tested for these types
 * @param aMinElems Minimum number of elements to read.  Reading fewer than
 *        this many elements will result in the function failing.
 * @param aMaxElems Maximum number of elements to read.  Reading more than
 *        this many elements will result in the function failing.
 * @param aValue (out) The value that was parsed.
 */
bool
CSSParserImpl::ParseFunction(nsCSSKeyword aFunction,
                             const uint32_t aAllowedTypes[],
                             uint32_t aAllowedTypesAll,
                             uint16_t aMinElems, uint16_t aMaxElems,
                             nsCSSValue &aValue)
{
  NS_ASSERTION((aAllowedTypes && !aAllowedTypesAll) ||
               (!aAllowedTypes && aAllowedTypesAll),
               "only one of the two allowed type parameter can be set");
  typedef InfallibleTArray<nsCSSValue>::size_type arrlen_t;

  /* 2^16 - 2, so that if we have 2^16 - 2 transforms, we have 2^16 - 1
   * elements stored in the the nsCSSValue::Array.
   */
  static const arrlen_t MAX_ALLOWED_ELEMS = 0xFFFE;

  /* Read in a list of values as an array, failing if we can't or if
   * it's out of bounds.
   *
   * We reserve 16 entries in the foundValues array in order to avoid
   * having to resize the array dynamically when parsing some well-formed
   * functions.  The number 16 is coming from the number of arguments that
   * matrix3d() accepts.
   */
  AutoTArray<nsCSSValue, 16> foundValues;
  if (!ParseFunctionInternals(aAllowedTypes, aAllowedTypesAll, aMinElems,
                              aMaxElems, foundValues)) {
    return false;
  }

  /*
   * In case the user has given us more than 2^16 - 2 arguments,
   * we'll truncate them at 2^16 - 2 arguments.
   */
  uint16_t numArgs = std::min(foundValues.Length(), MAX_ALLOWED_ELEMS);
  RefPtr<nsCSSValue::Array> convertedArray =
    aValue.InitFunction(aFunction, numArgs);

  /* Copy things over. */
  for (uint16_t index = 0; index < numArgs; ++index)
    convertedArray->Item(index + 1) = foundValues[static_cast<arrlen_t>(index)];

  /* Return it! */
  return true;
}

/**
 * Given a token, determines the minimum and maximum number of function
 * parameters to read, along with the mask that should be used to read
 * those function parameters.  If the token isn't a transform function,
 * returns an error.
 *
 * @param aToken The token identifying the function.
 * @param aIsPrefixed If true, parse matrices using the matrix syntax
 *   for -moz-transform.
 * @param aDisallowRelativeValues If true, only allow variants that are
 *   numbers or have non-relative dimensions.
 * @param aMinElems [out] The minimum number of elements to read.
 * @param aMaxElems [out] The maximum number of elements to read
 * @param aVariantMask [out] The variant mask to use during parsing
 * @return Whether the information was loaded successfully.
 */
static bool GetFunctionParseInformation(nsCSSKeyword aToken,
                                        bool aIsPrefixed,
                                        bool aDisallowRelativeValues,
                                        uint16_t &aMinElems,
                                        uint16_t &aMaxElems,
                                        const uint32_t *& aVariantMask)
{
/* These types represent the common variant masks that will be used to
   * parse out the individual functions.  The order in the enumeration
   * must match the order in which the masks are declared.
   */
  enum { eLengthPercentCalc,
         eLengthCalc,
         eAbsoluteLengthCalc,
         eTwoLengthPercentCalcs,
         eTwoAbsoluteLengthCalcs,
         eTwoLengthPercentCalcsOneLengthCalc,
         eThreeAbsoluteLengthCalc,
         eAngle,
         eTwoAngles,
         eNumber,
         eNonNegativeLength,
         eNonNegativeAbsoluteLength,
         eTwoNumbers,
         eThreeNumbers,
         eThreeNumbersOneAngle,
         eMatrix,
         eMatrixPrefixed,
         eMatrix3d,
         eMatrix3dPrefixed,
         eNumVariantMasks };
  static const int32_t kMaxElemsPerFunction = 16;
  static const uint32_t kVariantMasks[eNumVariantMasks][kMaxElemsPerFunction] = {
    {VARIANT_LPCALC},
    {VARIANT_LCALC},
    {VARIANT_LB},
    {VARIANT_LPCALC, VARIANT_LPCALC},
    {VARIANT_LBCALC, VARIANT_LBCALC},
    {VARIANT_LPCALC, VARIANT_LPCALC, VARIANT_LCALC},
    {VARIANT_LBCALC, VARIANT_LBCALC, VARIANT_LBCALC},
    {VARIANT_ANGLE_OR_ZERO},
    {VARIANT_ANGLE_OR_ZERO, VARIANT_ANGLE_OR_ZERO},
    {VARIANT_NUMBER},
    {VARIANT_LENGTH|VARIANT_NONNEGATIVE_DIMENSION},
    {VARIANT_LB|VARIANT_NONNEGATIVE_DIMENSION},
    {VARIANT_NUMBER, VARIANT_NUMBER},
    {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER},
    {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_ANGLE_OR_ZERO},
    {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
     VARIANT_NUMBER, VARIANT_NUMBER},
    {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
     VARIANT_LPNCALC, VARIANT_LPNCALC},
    {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
     VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
     VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
     VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER},
    {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
     VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
     VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
     VARIANT_LPNCALC, VARIANT_LPNCALC, VARIANT_LNCALC, VARIANT_NUMBER}};
  // Map from a mask to a congruent mask that excludes relative variants.
  static const int32_t kNonRelativeVariantMap[eNumVariantMasks] = {
    eAbsoluteLengthCalc,
    eAbsoluteLengthCalc,
    eAbsoluteLengthCalc,
    eTwoAbsoluteLengthCalcs,
    eTwoAbsoluteLengthCalcs,
    eThreeAbsoluteLengthCalc,
    eThreeAbsoluteLengthCalc,
    eAngle,
    eTwoAngles,
    eNumber,
    eNonNegativeAbsoluteLength,
    eNonNegativeAbsoluteLength,
    eTwoNumbers,
    eThreeNumbers,
    eThreeNumbersOneAngle,
    eMatrix,
    eMatrix,
    eMatrix3d,
    eMatrix3d };

#ifdef DEBUG
  static const uint8_t kVariantMaskLengths[eNumVariantMasks] =
    {1, 1, 1, 2, 2, 3, 3, 1, 2, 1, 1, 1, 2, 3, 4, 6, 6, 16, 16};
#endif

  int32_t variantIndex = eNumVariantMasks;

  switch (aToken) {
  case eCSSKeyword_translatex:
  case eCSSKeyword_translatey:
    /* Exactly one length or percent. */
    variantIndex = eLengthPercentCalc;
    aMinElems = 1U;
    aMaxElems = 1U;
    break;
  case eCSSKeyword_translatez:
    /* Exactly one length */
    variantIndex = eLengthCalc;
    aMinElems = 1U;
    aMaxElems = 1U;
    break;
  case eCSSKeyword_translate3d:
    /* Exactly two lengthds or percents and a number */
    variantIndex = eTwoLengthPercentCalcsOneLengthCalc;
    aMinElems = 3U;
    aMaxElems = 3U;
    break;
  case eCSSKeyword_scalez:
  case eCSSKeyword_scalex:
  case eCSSKeyword_scaley:
    /* Exactly one scale factor. */
    variantIndex = eNumber;
    aMinElems = 1U;
    aMaxElems = 1U;
    break;
  case eCSSKeyword_scale3d:
    /* Exactly three scale factors. */
    variantIndex = eThreeNumbers;
    aMinElems = 3U;
    aMaxElems = 3U;
    break;
  case eCSSKeyword_rotatex:
  case eCSSKeyword_rotatey:
  case eCSSKeyword_rotate:
  case eCSSKeyword_rotatez:
    /* Exactly one angle. */
    variantIndex = eAngle;
    aMinElems = 1U;
    aMaxElems = 1U;
    break;
  case eCSSKeyword_rotate3d:
    variantIndex = eThreeNumbersOneAngle;
    aMinElems = 4U;
    aMaxElems = 4U;
    break;
  case eCSSKeyword_translate:
    /* One or two lengths or percents. */
    variantIndex = eTwoLengthPercentCalcs;
    aMinElems = 1U;
    aMaxElems = 2U;
    break;
  case eCSSKeyword_skew:
    /* Exactly one or two angles. */
    variantIndex = eTwoAngles;
    aMinElems = 1U;
    aMaxElems = 2U;
    break;
  case eCSSKeyword_scale:
    /* One or two scale factors. */
    variantIndex = eTwoNumbers;
    aMinElems = 1U;
    aMaxElems = 2U;
    break;
  case eCSSKeyword_skewx:
    /* Exactly one angle. */
    variantIndex = eAngle;
    aMinElems = 1U;
    aMaxElems = 1U;
    break;
  case eCSSKeyword_skewy:
    /* Exactly one angle. */
    variantIndex = eAngle;
    aMinElems = 1U;
    aMaxElems = 1U;
    break;
  case eCSSKeyword_matrix:
    /* Six values, all numbers. */
    variantIndex = aIsPrefixed ? eMatrixPrefixed : eMatrix;
    aMinElems = 6U;
    aMaxElems = 6U;
    break;
  case eCSSKeyword_matrix3d:
    /* 16 matrix values, all numbers */
    variantIndex = aIsPrefixed ? eMatrix3dPrefixed : eMatrix3d;
    aMinElems = 16U;
    aMaxElems = 16U;
    break;
  case eCSSKeyword_perspective:
    /* Exactly one scale number. */
    variantIndex = eNonNegativeLength;
    aMinElems = 1U;
    aMaxElems = 1U;
    break;
  default:
    /* Oh dear, we didn't match.  Report an error. */
    return false;
  }

  if (aDisallowRelativeValues) {
    variantIndex = kNonRelativeVariantMap[variantIndex];
  }

  NS_ASSERTION(aMinElems > 0, "Didn't update minimum elements!");
  NS_ASSERTION(aMaxElems > 0, "Didn't update maximum elements!");
  NS_ASSERTION(aMinElems <= aMaxElems, "aMinElems > aMaxElems!");
  NS_ASSERTION(variantIndex >= 0, "Invalid variant mask!");
  NS_ASSERTION(variantIndex < eNumVariantMasks, "Invalid variant mask!");
#ifdef DEBUG
  NS_ASSERTION(aMaxElems <= kVariantMaskLengths[variantIndex],
               "Invalid aMaxElems for this variant mask.");
#endif

  // Convert the index into a mask.
  aVariantMask = kVariantMasks[variantIndex];

  return true;
}

bool CSSParserImpl::ParseWillChange()
{
  nsCSSValue listValue;
  nsCSSValueList* currentListValue = listValue.SetListValue();
  bool first = true;
  for (;;) {
    const uint32_t variantMask = VARIANT_IDENTIFIER |
                                 VARIANT_INHERIT |
                                 VARIANT_NONE |
                                 VARIANT_ALL |
                                 VARIANT_AUTO;
    nsCSSValue value;
    if (!ParseSingleTokenVariant(value, variantMask, nullptr)) {
      return false;
    }

    if (value.GetUnit() == eCSSUnit_None ||
        value.GetUnit() == eCSSUnit_All)
    {
      return false;
    }

    if (value.GetUnit() != eCSSUnit_Ident) {
      if (first) {
        AppendValue(eCSSProperty_will_change, value);
        return true;
      } else {
        return false;
      }
    }

    nsString str;
    value.GetStringValue(str);
    if (str.LowerCaseEqualsLiteral("default") ||
        str.LowerCaseEqualsLiteral("will-change")) {
      return false;
    }

    currentListValue->mValue = value;

    if (!ExpectSymbol(',', true)) {
      break;
    }
    currentListValue->mNext = new nsCSSValueList;
    currentListValue = currentListValue->mNext;
    first = false;
  }

  AppendValue(eCSSProperty_will_change, listValue);
  return true;
}

/* Reads a single transform function from the tokenizer stream, reporting an
 * error if something goes wrong.
 */
bool
CSSParserImpl::ParseSingleTransform(bool aIsPrefixed,
                                    bool aDisallowRelativeValues,
                                    nsCSSValue& aValue)
{
  if (!GetToken(true))
    return false;

  if (mToken.mType != eCSSToken_Function) {
    UngetToken();
    return false;
  }

  const uint32_t* variantMask;
  uint16_t minElems, maxElems;
  nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);

  if (!GetFunctionParseInformation(keyword, aIsPrefixed,
                                   aDisallowRelativeValues,
                                   minElems, maxElems,
                                   variantMask))
    return false;

  return ParseFunction(keyword, variantMask, 0, minElems, maxElems, aValue);
}

/* Parses a transform property list by continuously reading in properties
 * and constructing a matrix from it.
 */
bool
CSSParserImpl::ParseTransform(bool aIsPrefixed, bool aDisallowRelativeValues)
{
  nsCSSValue value;
  // 'inherit', 'initial', 'unset' and 'none' must be alone
  if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE,
                               nullptr)) {
    nsCSSValueSharedList* list = new nsCSSValueSharedList;
    value.SetSharedListValue(list);
    list->mHead = new nsCSSValueList;
    nsCSSValueList* cur = list->mHead;
    for (;;) {
      if (!ParseSingleTransform(aIsPrefixed, aDisallowRelativeValues,
                                cur->mValue)) {
        return false;
      }
      if (CheckEndProperty()) {
        break;
      }
      cur->mNext = new nsCSSValueList;
      cur = cur->mNext;
    }
  }
  AppendValue(eCSSProperty_transform, value);
  return true;
}

/* Reads a polygon function's argument list.
 */
bool
CSSParserImpl::ParsePolygonFunction(nsCSSValue& aValue)
{
  uint16_t numArgs = 1;

  nsCSSValue fillRuleValue;
  if (ParseEnum(fillRuleValue, nsCSSProps::kFillRuleKTable)) {
    numArgs++;

    // The fill-rule must be comma separated from the polygon points.
    if (!ExpectSymbol(',', true)) {
      REPORT_UNEXPECTED_TOKEN(PEExpectedComma);
      SkipUntil(')');
      return false;
    }
  }

  nsCSSValue coordinates;
  nsCSSValuePairList* item = coordinates.SetPairListValue();
  for (;;) {
    nsCSSValue xValue, yValue;
    if (ParseVariant(xValue, VARIANT_LPCALC, nullptr) != CSSParseResult::Ok ||
        ParseVariant(yValue, VARIANT_LPCALC, nullptr) != CSSParseResult::Ok) {
      REPORT_UNEXPECTED_TOKEN(PECoordinatePair);
      SkipUntil(')');
      return false;
    }
    item->mXValue = xValue;
    item->mYValue = yValue;

    // See whether to continue or whether to look for end of function.
    if (!ExpectSymbol(',', true)) {
      // We need to read the closing parenthesis.
      if (!ExpectSymbol(')', true)) {
        REPORT_UNEXPECTED_TOKEN(PEExpectedCloseParen);
        SkipUntil(')');
        return false;
      }
      break;
    }
    item->mNext = new nsCSSValuePairList;
    item = item->mNext;
  }

  RefPtr<nsCSSValue::Array> functionArray =
    aValue.InitFunction(eCSSKeyword_polygon, numArgs);
  functionArray->Item(numArgs) = coordinates;
  if (numArgs > 1) {
    functionArray->Item(1) = fillRuleValue;
  }

  return true;
}

bool
CSSParserImpl::ParseCircleOrEllipseFunction(nsCSSKeyword aKeyword,
                                            nsCSSValue& aValue)
{
  nsCSSValue radiusX, radiusY, position;
  bool hasRadius = false, hasPosition = false;

  int32_t mask = VARIANT_LPCALC | VARIANT_NONNEGATIVE_DIMENSION |
                 VARIANT_KEYWORD;
  CSSParseResult result =
    ParseVariant(radiusX, mask, nsCSSProps::kShapeRadiusKTable);
  if (result == CSSParseResult::Error) {
    return false;
  } else if (result == CSSParseResult::Ok) {
    if (aKeyword == eCSSKeyword_ellipse) {
      if (ParseVariant(radiusY, mask, nsCSSProps::kShapeRadiusKTable) !=
          CSSParseResult::Ok) {
        REPORT_UNEXPECTED_TOKEN(PEExpectedRadius);
        SkipUntil(')');
        return false;
      }
    }
    hasRadius = true;
  }

  if (!ExpectSymbol(')', true)) {
    if (!GetToken(true)) {
      REPORT_UNEXPECTED_EOF(PEPositionEOF);
      return false;
    }

    if (mToken.mType != eCSSToken_Ident ||
        !mToken.mIdent.LowerCaseEqualsLiteral("at") ||
        !ParsePositionValueForBasicShape(position)) {
      REPORT_UNEXPECTED_TOKEN(PEExpectedPosition);
      SkipUntil(')');
      return false;
    }
    if (!ExpectSymbol(')', true)) {
      REPORT_UNEXPECTED_TOKEN(PEExpectedCloseParen);
      SkipUntil(')');
      return false;
    }
    hasPosition = true;
  }

  size_t count = aKeyword == eCSSKeyword_circle ? 2 : 3;
  RefPtr<nsCSSValue::Array> functionArray =
    aValue.InitFunction(aKeyword, count);
  if (hasRadius) {
    functionArray->Item(1) = radiusX;
    if (aKeyword == eCSSKeyword_ellipse) {
      functionArray->Item(2) = radiusY;
    }
  }
  if (hasPosition) {
    functionArray->Item(count) = position;
  }

  return true;
}

bool
CSSParserImpl::ParseInsetFunction(nsCSSValue& aValue)
{
  RefPtr<nsCSSValue::Array> functionArray =
    aValue.InitFunction(eCSSKeyword_inset, 5);

  int count = 0;
  while (count < 4) {
    CSSParseResult result =
      ParseVariant(functionArray->Item(count + 1), VARIANT_LPCALC, nullptr);
    if (result == CSSParseResult::Error) {
      count = 0;
      break;
    } else if (result == CSSParseResult::NotFound) {
      break;
    }
    ++count;
  }

  if (count == 0) {
    REPORT_UNEXPECTED_TOKEN(PEExpectedShapeArg);
    SkipUntil(')');
    return false;
  }

  if (!ExpectSymbol(')', true)) {
    if (!GetToken(true)) {
      NS_NOTREACHED("ExpectSymbol should have returned true");
      return false;
    }

    RefPtr<nsCSSValue::Array> radiusArray = nsCSSValue::Array::Create(4);
    functionArray->Item(5).SetArrayValue(radiusArray, eCSSUnit_Array);
    if (mToken.mType != eCSSToken_Ident ||
        !mToken.mIdent.LowerCaseEqualsLiteral("round") ||
        !ParseBoxCornerRadiiInternals(radiusArray->ItemStorage())) {
      REPORT_UNEXPECTED_TOKEN(PEExpectedRadius);
      SkipUntil(')');
      return false;
    }

    if (!ExpectSymbol(')', true)) {
      REPORT_UNEXPECTED_TOKEN(PEExpectedCloseParen);
      SkipUntil(')');
      return false;
    }
  }

  return true;
}

bool
CSSParserImpl::ParseBasicShape(nsCSSValue& aValue, bool* aConsumedTokens)
{
  if (!GetToken(true)) {
    return false;
  }

  if (mToken.mType != eCSSToken_Function) {
    UngetToken();
    return false;
  }

  // Specific shape function parsing always consumes tokens.
  *aConsumedTokens = true;
  nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
  switch (keyword) {
  case eCSSKeyword_polygon:
    return ParsePolygonFunction(aValue);
  case eCSSKeyword_circle:
  case eCSSKeyword_ellipse:
    return ParseCircleOrEllipseFunction(keyword, aValue);
  case eCSSKeyword_inset:
    return ParseInsetFunction(aValue);
  default:
    return false;
  }
}

bool
CSSParserImpl::ParseReferenceBoxAndBasicShape(
  nsCSSValue& aValue,
  const KTableEntry aBoxKeywordTable[])
{
  nsCSSValue referenceBox;
  bool hasBox = ParseEnum(referenceBox, aBoxKeywordTable);

  const bool boxCameFirst = hasBox;

  nsCSSValue basicShape;
  bool basicShapeConsumedTokens = false;
  bool hasShape = ParseBasicShape(basicShape, &basicShapeConsumedTokens);

  // Parsing wasn't successful if ParseBasicShape consumed tokens but failed
  // or if the token was neither a reference box nor a basic shape.
  if ((!hasShape && basicShapeConsumedTokens) || (!hasBox && !hasShape)) {
    return false;
  }

  // Check if the second argument is a reference box if the first wasn't.
  if (!hasBox) {
    hasBox = ParseEnum(referenceBox, aBoxKeywordTable);
  }

  RefPtr<nsCSSValue::Array> fullValue =
    nsCSSValue::Array::Create((hasBox && hasShape) ? 2 : 1);

  if (hasBox && hasShape) {
    fullValue->Item(boxCameFirst ? 0 : 1) = referenceBox;
    fullValue->Item(boxCameFirst ? 1 : 0) = basicShape;
  } else if (hasBox) {
    fullValue->Item(0) = referenceBox;
  } else {
    MOZ_ASSERT(hasShape, "should've bailed if we got neither box nor shape");
    fullValue->Item(0) = basicShape;
  }

  aValue.SetArrayValue(fullValue, eCSSUnit_Array);
  return true;
}

// Parse a clip-path url to a <clipPath> element or a basic shape.
bool
CSSParserImpl::ParseClipPath(nsCSSValue& aValue)
{
  if (ParseSingleTokenVariant(aValue, VARIANT_HUO, nullptr)) {
    return true;
  }

  if (!nsLayoutUtils::CSSClipPathShapesEnabled()) {
    // With CSS Clip Path Shapes disabled, we should only accept
    // SVG clipPath reference and none.
    REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURL);
    return false;
  }

  return ParseReferenceBoxAndBasicShape(
    aValue, nsCSSProps::kClipPathGeometryBoxKTable);
}

// none | [ <basic-shape> || <shape-box> ] | <image>
bool
CSSParserImpl::ParseShapeOutside(nsCSSValue& aValue)
{
  if (ParseSingleTokenVariant(aValue, VARIANT_HUO, nullptr)) {
    // 'inherit', 'initial', 'unset', 'none', and <image> url must be alone.
    return true;
  }

  return ParseReferenceBoxAndBasicShape(
    aValue, nsCSSProps::kShapeOutsideShapeBoxKTable);
}

bool CSSParserImpl::ParseTransformOrigin(bool aPerspective)
{
  nsCSSValuePair position;
  if (!ParseBoxPositionValues(position, true))
    return false;

  nsCSSPropertyID prop = eCSSProperty_transform_origin;
  if (aPerspective) {
    prop = eCSSProperty_perspective_origin;
  }

  // Unlike many other uses of pairs, this position should always be stored
  // as a pair, even if the values are the same, so it always serializes as
  // a pair, and to keep the computation code simple.
  if (position.mXValue.GetUnit() == eCSSUnit_Inherit ||
      position.mXValue.GetUnit() == eCSSUnit_Initial ||
      position.mXValue.GetUnit() == eCSSUnit_Unset) {
    MOZ_ASSERT(position.mXValue == position.mYValue,
               "inherit/initial/unset only half?");
    AppendValue(prop, position.mXValue);
  } else {
    nsCSSValue value;
    if (aPerspective) {
      value.SetPairValue(position.mXValue, position.mYValue);
    } else {
      nsCSSValue depth;
      CSSParseResult result =
        ParseVariant(depth, VARIANT_LENGTH | VARIANT_CALC, nullptr);
      if (result == CSSParseResult::Error) {
        return false;
      } else if (result == CSSParseResult::NotFound) {
        depth.SetFloatValue(0.0f, eCSSUnit_Pixel);
      }
      value.SetTripletValue(position.mXValue, position.mYValue, depth);
    }

    AppendValue(prop, value);
  }
  return true;
}

/**
 * Reads a drop-shadow value. At the moment the Filter Effects specification
 * just expects one shadow item. Should this ever change to a list of shadow
 * items, use ParseShadowList instead.
 */
bool
CSSParserImpl::ParseDropShadow(nsCSSValue* aValue)
{
  // Use nsCSSValueList to reuse the shadow resolving code in
  // nsRuleNode and nsComputedDOMStyle.
  nsCSSValue shadow;
  nsCSSValueList* cur = shadow.SetListValue();
  if (!ParseShadowItem(cur->mValue, false))
    return false;

  if (!ExpectSymbol(')', true))
    return false;

  nsCSSValue::Array* dropShadow = aValue->InitFunction(eCSSKeyword_drop_shadow, 1);

  // Copy things over.
  dropShadow->Item(1) = shadow;

  return true;
}

/**
 * Reads a single url or filter function from the tokenizer stream, reporting an
 * error if something goes wrong.
 */
bool
CSSParserImpl::ParseSingleFilter(nsCSSValue* aValue)
{
  if (ParseSingleTokenVariant(*aValue, VARIANT_URL, nullptr)) {
    return true;
  }

  if (!nsLayoutUtils::CSSFiltersEnabled()) {
    // With CSS Filters disabled, we should only accept an SVG reference filter.
    REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURL);
    return false;
  }

  if (!GetToken(true)) {
    REPORT_UNEXPECTED_EOF(PEFilterEOF);
    return false;
  }

  if (mToken.mType != eCSSToken_Function) {
    REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURLOrFilterFunction);
    UngetToken();
    return false;
  }

  nsCSSKeyword functionName = nsCSSKeywords::LookupKeyword(mToken.mIdent);
  // Parse drop-shadow independently of the other filter functions
  // because of its more complex characteristics.
  if (functionName == eCSSKeyword_drop_shadow) {
    if (ParseDropShadow(aValue)) {
      return true;
    } else {
      // Unrecognized filter function.
      REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURLOrFilterFunction);
      SkipUntil(')');
      return false;
    }
  }

  // Set up the parsing rules based on the filter function.
  uint32_t variantMask = VARIANT_PN;
  bool rejectNegativeArgument = true;
  bool clampArgumentToOne = false;
  switch (functionName) {
    case eCSSKeyword_blur:
      variantMask = VARIANT_LCALC | VARIANT_NONNEGATIVE_DIMENSION;
      // VARIANT_NONNEGATIVE_DIMENSION will already reject negative lengths.
      rejectNegativeArgument = false;
      break;
    case eCSSKeyword_brightness:
    case eCSSKeyword_contrast:
    case eCSSKeyword_saturate:
      break;
    case eCSSKeyword_grayscale:
    case eCSSKeyword_invert:
    case eCSSKeyword_sepia:
    case eCSSKeyword_opacity:
      clampArgumentToOne = true;
      break;
    case eCSSKeyword_hue_rotate:
      variantMask = VARIANT_ANGLE;
      rejectNegativeArgument = false;
      break;
    default:
      // Unrecognized filter function.
      REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURLOrFilterFunction);
      SkipUntil(')');
      return false;
  }

  // Parse the function.
  uint16_t minElems = 1U;
  uint16_t maxElems = 1U;
  uint32_t allVariants = 0;
  if (!ParseFunction(functionName, &variantMask, allVariants,
                     minElems, maxElems, *aValue)) {
    REPORT_UNEXPECTED(PEFilterFunctionArgumentsParsingError);
    return false;
  }

  // Get the first and only argument to the filter function.
  MOZ_ASSERT(aValue->GetUnit() == eCSSUnit_Function,
             "expected a filter function");
  MOZ_ASSERT(aValue->UnitHasArrayValue(),
             "filter function should be an array");
  MOZ_ASSERT(aValue->GetArrayValue()->Count() == 2,
             "filter function should have exactly one argument");
  nsCSSValue& arg = aValue->GetArrayValue()->Item(1);

  if (rejectNegativeArgument &&
      ((arg.GetUnit() == eCSSUnit_Percent && arg.GetPercentValue() < 0.0f) ||
       (arg.GetUnit() == eCSSUnit_Number && arg.GetFloatValue() < 0.0f))) {
    REPORT_UNEXPECTED(PEExpectedNonnegativeNP);
    return false;
  }

  if (clampArgumentToOne) {
    if (arg.GetUnit() == eCSSUnit_Number &&
        arg.GetFloatValue() > 1.0f) {
      arg.SetFloatValue(1.0f, arg.GetUnit());
    } else if (arg.GetUnit() == eCSSUnit_Percent &&
               arg.GetPercentValue() > 1.0f) {
      arg.SetPercentValue(1.0f);
    }
  }

  return true;
}

/**
 * Parses a filter property value by continuously reading in urls and/or filter
 * functions and constructing a list.
 *
 * When CSS Filters are enabled, the filter property accepts one or more SVG
 * reference filters and/or CSS filter functions.
 * e.g. filter: url(#my-filter-1) blur(3px) url(#my-filter-2) grayscale(50%);
 *
 * When CSS Filters are disabled, the filter property only accepts one SVG
 * reference filter.
 * e.g. filter: url(#my-filter);
 */
bool
CSSParserImpl::ParseFilter()
{
  nsCSSValue value;
  // 'inherit', 'initial', 'unset' and 'none' must be alone
  if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE,
                               nullptr)) {
    nsCSSValueList* cur = value.SetListValue();
    while (cur) {
      if (!ParseSingleFilter(&cur->mValue)) {
        return false;
      }
      if (CheckEndProperty()) {
        break;
      }
      if (!nsLayoutUtils::CSSFiltersEnabled()) {
        // With CSS Filters disabled, we should only accept one SVG reference
        // filter.
        REPORT_UNEXPECTED_TOKEN(PEExpectEndValue);
        return false;
      }
      cur->mNext = new nsCSSValueList;
      cur = cur->mNext;
    }
  }
  AppendValue(eCSSProperty_filter, value);
  return true;
}

bool
CSSParserImpl::ParseTransitionProperty()
{
  nsCSSValue value;
  // 'inherit', 'initial', 'unset' and 'none' must be alone
  if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE,
                               nullptr)) {
    // Accept a list of arbitrary identifiers.  They should be
    // CSS properties, but we want to accept any so that we
    // accept properties that we don't know about yet, e.g.
    // transition-property: invalid-property, left, opacity;
    nsCSSValueList* cur = value.SetListValue();
    for (;;) {
      if (!ParseSingleTokenVariant(cur->mValue,
                                   VARIANT_IDENTIFIER | VARIANT_ALL,
                                   nullptr)) {
        return false;
      }
      if (cur->mValue.GetUnit() == eCSSUnit_Ident) {
        nsDependentString str(cur->mValue.GetStringBufferValue());
        // Exclude 'none', 'inherit', 'initial' and 'unset' according to the
        // same rules as for 'counter-reset' in CSS 2.1.
        if (str.LowerCaseEqualsLiteral("none") ||
            str.LowerCaseEqualsLiteral("inherit") ||
            str.LowerCaseEqualsLiteral("initial") ||
            (str.LowerCaseEqualsLiteral("unset") &&
             nsLayoutUtils::UnsetValueEnabled())) {
          return false;
        }
      }
      if (!ExpectSymbol(',', true)) {
        break;
      }
      cur->mNext = new nsCSSValueList;
      cur = cur->mNext;
    }
  }
  AppendValue(eCSSProperty_transition_property, value);
  return true;
}

bool
CSSParserImpl::ParseTransitionTimingFunctionValues(nsCSSValue& aValue)
{
  NS_ASSERTION(!mHavePushBack &&
               mToken.mType == eCSSToken_Function &&
               mToken.mIdent.LowerCaseEqualsLiteral("cubic-bezier"),
               "unexpected initial state");

  RefPtr<nsCSSValue::Array> val = nsCSSValue::Array::Create(4);

  float x1, x2, y1, y2;
  if (!ParseTransitionTimingFunctionValueComponent(x1, ',', true) ||
      !ParseTransitionTimingFunctionValueComponent(y1, ',', false) ||
      !ParseTransitionTimingFunctionValueComponent(x2, ',', true) ||
      !ParseTransitionTimingFunctionValueComponent(y2, ')', false)) {
    return false;
  }

  val->Item(0).SetFloatValue(x1, eCSSUnit_Number);
  val->Item(1).SetFloatValue(y1, eCSSUnit_Number);
  val->Item(2).SetFloatValue(x2, eCSSUnit_Number);
  val->Item(3).SetFloatValue(y2, eCSSUnit_Number);

  aValue.SetArrayValue(val, eCSSUnit_Cubic_Bezier);

  return true;
}

bool
CSSParserImpl::ParseTransitionTimingFunctionValueComponent(float& aComponent,
                                                           char aStop,
                                                           bool aIsXPoint)
{
  if (!GetToken(true)) {
    return false;
  }
  nsCSSToken* tk = &mToken;
  if (tk->mType == eCSSToken_Number) {
    float num = tk->mNumber;

    // Clamp infinity or -infinity values to max float or -max float to avoid
    // calculations with infinity.
    num = mozilla::clamped(num, -std::numeric_limits<float>::max(),
                                 std::numeric_limits<float>::max());

    // X control point should be inside [0, 1] range.
    if (aIsXPoint && (num < 0.0 || num > 1.0)) {
      return false;
    }
    aComponent = num;
    if (ExpectSymbol(aStop, true)) {
      return true;
    }
  }
  return false;
}

bool
CSSParserImpl::ParseTransitionStepTimingFunctionValues(nsCSSValue& aValue)
{
  NS_ASSERTION(!mHavePushBack &&
               mToken.mType == eCSSToken_Function &&
               mToken.mIdent.LowerCaseEqualsLiteral("steps"),
               "unexpected initial state");

  RefPtr<nsCSSValue::Array> val = nsCSSValue::Array::Create(2);

  if (!ParseSingleTokenOneOrLargerVariant(val->Item(0), VARIANT_INTEGER,
                                          nullptr)) {
    return false;
  }

  int32_t type = -1;  // indicates an implicit end value
  if (ExpectSymbol(',', true)) {
    if (!GetToken(true)) {
      return false;
    }
    if (mToken.mType == eCSSToken_Ident) {
      if (mToken.mIdent.LowerCaseEqualsLiteral("start")) {
        type = NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_START;
      } else if (mToken.mIdent.LowerCaseEqualsLiteral("end")) {
        type = NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_END;
      }
    }
    if (type == -1) {
      UngetToken();
      return false;
    }
  }
  val->Item(1).SetIntValue(type, eCSSUnit_Enumerated);

  if (!ExpectSymbol(')', true)) {
    return false;
  }

  aValue.SetArrayValue(val, eCSSUnit_Steps);
  return true;
}

static nsCSSValueList*
AppendValueToList(nsCSSValue& aContainer,
                  nsCSSValueList* aTail,
                  const nsCSSValue& aValue)
{
  nsCSSValueList* entry;
  if (aContainer.GetUnit() == eCSSUnit_Null) {
    MOZ_ASSERT(!aTail, "should not have an entry");
    entry = aContainer.SetListValue();
  } else {
    MOZ_ASSERT(!aTail->mNext, "should not have a next entry");
    MOZ_ASSERT(aContainer.GetUnit() == eCSSUnit_List, "not a list");
    entry = new nsCSSValueList;
    aTail->mNext = entry;
  }
  entry->mValue = aValue;
  return entry;
}

CSSParserImpl::ParseAnimationOrTransitionShorthandResult
CSSParserImpl::ParseAnimationOrTransitionShorthand(
                 const nsCSSPropertyID* aProperties,
                 const nsCSSValue* aInitialValues,
                 nsCSSValue* aValues,
                 size_t aNumProperties)
{
  nsCSSValue tempValue;
  // first see if 'inherit', 'initial' or 'unset' is specified.  If one is,
  // it can be the only thing specified, so don't attempt to parse any
  // additional properties
  if (ParseSingleTokenVariant(tempValue, VARIANT_INHERIT, nullptr)) {
    for (uint32_t i = 0; i < aNumProperties; ++i) {
      AppendValue(aProperties[i], tempValue);
    }
    return eParseAnimationOrTransitionShorthand_Inherit;
  }

  static const size_t maxNumProperties = 8;
  MOZ_ASSERT(aNumProperties <= maxNumProperties,
             "can't handle this many properties");
  nsCSSValueList *cur[maxNumProperties];
  bool parsedProperty[maxNumProperties];

  for (size_t i = 0; i < aNumProperties; ++i) {
    cur[i] = nullptr;
  }
  bool atEOP = false; // at end of property?
  for (;;) { // loop over comma-separated transitions or animations
    // whether a particular subproperty was specified for this
    // transition or animation
    bool haveAnyProperty = false;
    for (size_t i = 0; i < aNumProperties; ++i) {
      parsedProperty[i] = false;
    }
    for (;;) { // loop over values within a transition or animation
      bool foundProperty = false;
      // check to see if we're at the end of one full transition or
      // animation definition (either because we hit a comma or because
      // we hit the end of the property definition)
      if (ExpectSymbol(',', true))
        break;
      if (CheckEndProperty()) {
        atEOP = true;
        break;
      }

      // else, try to parse the next transition or animation sub-property
      for (uint32_t i = 0; !foundProperty && i < aNumProperties; ++i) {
        if (!parsedProperty[i]) {
          // if we haven't found this property yet, try to parse it
          CSSParseResult result =
            ParseSingleValueProperty(tempValue, aProperties[i]);
          if (result == CSSParseResult::Error) {
            return eParseAnimationOrTransitionShorthand_Error;
          }
          if (result == CSSParseResult::Ok) {
            parsedProperty[i] = true;
            cur[i] = AppendValueToList(aValues[i], cur[i], tempValue);
            foundProperty = true;
            haveAnyProperty = true;
            break; // out of inner loop; continue looking for next sub-property
          }
        }
      }
      if (!foundProperty) {
        // We're not at a ',' or at the end of the property, but we couldn't
        // parse any of the sub-properties, so the declaration is invalid.
        return eParseAnimationOrTransitionShorthand_Error;
      }
    }

    if (!haveAnyProperty) {
      // Got an empty item.
      return eParseAnimationOrTransitionShorthand_Error;
    }

    // We hit the end of the property or the end of one transition
    // or animation definition, add its components to the list.
    for (uint32_t i = 0; i < aNumProperties; ++i) {
      // If all of the subproperties were not explicitly specified, fill
      // in the missing ones with initial values.
      if (!parsedProperty[i]) {
        cur[i] = AppendValueToList(aValues[i], cur[i], aInitialValues[i]);
      }
    }

    if (atEOP)
      break;
    // else we just hit a ',' so continue parsing the next compound transition
  }

  return eParseAnimationOrTransitionShorthand_Values;
}

bool
CSSParserImpl::ParseTransition()
{
  static const nsCSSPropertyID kTransitionProperties[] = {
    eCSSProperty_transition_duration,
    eCSSProperty_transition_timing_function,
    // Must check 'transition-delay' after 'transition-duration', since
    // that's our assumption about what the spec means for the shorthand
    // syntax (the first time given is the duration, and the second
    // given is the delay).
    eCSSProperty_transition_delay,
    // Must check 'transition-property' after
    // 'transition-timing-function' since 'transition-property' accepts
    // any keyword.
    eCSSProperty_transition_property
  };
  static const uint32_t numProps = MOZ_ARRAY_LENGTH(kTransitionProperties);
  // this is a shorthand property that accepts -property, -delay,
  // -duration, and -timing-function with some components missing.
  // there can be multiple transitions, separated with commas

  nsCSSValue initialValues[numProps];
  initialValues[0].SetFloatValue(0.0, eCSSUnit_Seconds);
  initialValues[1].SetIntValue(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE,
                               eCSSUnit_Enumerated);
  initialValues[2].SetFloatValue(0.0, eCSSUnit_Seconds);
  initialValues[3].SetAllValue();

  nsCSSValue values[numProps];

  ParseAnimationOrTransitionShorthandResult spres =
    ParseAnimationOrTransitionShorthand(kTransitionProperties,
                                        initialValues, values, numProps);
  if (spres != eParseAnimationOrTransitionShorthand_Values) {
    return spres != eParseAnimationOrTransitionShorthand_Error;
  }

  // Make two checks on the list for 'transition-property':
  //   + If there is more than one item, then none of the items can be
  //     'none'.
  //   + None of the items can be 'inherit', 'initial' or 'unset'.
  {
    MOZ_ASSERT(kTransitionProperties[3] == eCSSProperty_transition_property,
               "array index mismatch");
    nsCSSValueList *l = values[3].GetListValue();
    bool multipleItems = !!l->mNext;
    do {
      const nsCSSValue& val = l->mValue;
      if (val.GetUnit() == eCSSUnit_None) {
        if (multipleItems) {
          // This is a syntax error.
          return false;
        }

        // Unbox a solitary 'none'.
        values[3].SetNoneValue();
        break;
      }
      if (val.GetUnit() == eCSSUnit_Ident) {
        nsDependentString str(val.GetStringBufferValue());
        if (str.EqualsLiteral("inherit") ||
            str.EqualsLiteral("initial") ||
            (str.EqualsLiteral("unset") &&
             nsLayoutUtils::UnsetValueEnabled())) {
          return false;
        }
      }
    } while ((l = l->mNext));
  }

  // Save all parsed transition sub-properties in mTempData
  for (uint32_t i = 0; i < numProps; ++i) {
    AppendValue(kTransitionProperties[i], values[i]);
  }
  return true;
}

bool
CSSParserImpl::ParseAnimation()
{
  static const nsCSSPropertyID kAnimationProperties[] = {
    eCSSProperty_animation_duration,
    eCSSProperty_animation_timing_function,
    // Must check 'animation-delay' after 'animation-duration', since
    // that's our assumption about what the spec means for the shorthand
    // syntax (the first time given is the duration, and the second
    // given is the delay).
    eCSSProperty_animation_delay,
    eCSSProperty_animation_direction,
    eCSSProperty_animation_fill_mode,
    eCSSProperty_animation_iteration_count,
    eCSSProperty_animation_play_state,
    // Must check 'animation-name' after 'animation-timing-function',
    // 'animation-direction', 'animation-fill-mode',
    // 'animation-iteration-count', and 'animation-play-state' since
    // 'animation-name' accepts any keyword.
    eCSSProperty_animation_name
  };
  static const uint32_t numProps = MOZ_ARRAY_LENGTH(kAnimationProperties);
  // this is a shorthand property that accepts -property, -delay,
  // -duration, and -timing-function with some components missing.
  // there can be multiple animations, separated with commas

  nsCSSValue initialValues[numProps];
  initialValues[0].SetFloatValue(0.0, eCSSUnit_Seconds);
  initialValues[1].SetIntValue(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE,
                               eCSSUnit_Enumerated);
  initialValues[2].SetFloatValue(0.0, eCSSUnit_Seconds);
  initialValues[3].SetIntValue(static_cast<uint32_t>(mozilla::dom::PlaybackDirection::Normal),
                               eCSSUnit_Enumerated);
  initialValues[4].SetIntValue(static_cast<uint32_t>(mozilla::dom::FillMode::None),
                               eCSSUnit_Enumerated);
  initialValues[5].SetFloatValue(1.0f, eCSSUnit_Number);
  initialValues[6].SetIntValue(NS_STYLE_ANIMATION_PLAY_STATE_RUNNING, eCSSUnit_Enumerated);
  initialValues[7].SetNoneValue();

  nsCSSValue values[numProps];

  ParseAnimationOrTransitionShorthandResult spres =
    ParseAnimationOrTransitionShorthand(kAnimationProperties,
                                        initialValues, values, numProps);
  if (spres != eParseAnimationOrTransitionShorthand_Values) {
    return spres != eParseAnimationOrTransitionShorthand_Error;
  }

  // Save all parsed animation sub-properties in mTempData
  for (uint32_t i = 0; i < numProps; ++i) {
    AppendValue(kAnimationProperties[i], values[i]);
  }
  return true;
}

bool
CSSParserImpl::ParseShadowItem(nsCSSValue& aValue, bool aIsBoxShadow)
{
  // A shadow list item is an array, with entries in this sequence:
  enum {
    IndexX,
    IndexY,
    IndexRadius,
    IndexSpread,  // only for box-shadow
    IndexColor,
    IndexInset    // only for box-shadow
  };

  RefPtr<nsCSSValue::Array> val = nsCSSValue::Array::Create(6);

  if (aIsBoxShadow) {
    // Optional inset keyword (ignore errors)
    ParseSingleTokenVariant(val->Item(IndexInset), VARIANT_KEYWORD,
                            nsCSSProps::kBoxShadowTypeKTable);
  }

  nsCSSValue xOrColor;
  bool haveColor = false;
  if (ParseVariant(xOrColor, VARIANT_COLOR | VARIANT_LENGTH | VARIANT_CALC,
                   nullptr) != CSSParseResult::Ok) {
    return false;
  }
  if (xOrColor.IsLengthUnit() || xOrColor.IsCalcUnit()) {
    val->Item(IndexX) = xOrColor;
  } else {
    // Must be a color (as string or color value)
    NS_ASSERTION(xOrColor.GetUnit() == eCSSUnit_Ident ||
                 xOrColor.GetUnit() == eCSSUnit_EnumColor ||
                 xOrColor.IsNumericColorUnit(),
                 "Must be a color value");
    val->Item(IndexColor) = xOrColor;
    haveColor = true;

    // X coordinate mandatory after color
    if (ParseVariant(val->Item(IndexX), VARIANT_LENGTH | VARIANT_CALC,
                     nullptr) != CSSParseResult::Ok) {
      return false;
    }
  }

  // Y coordinate; mandatory
  if (ParseVariant(val->Item(IndexY), VARIANT_LENGTH | VARIANT_CALC,
                   nullptr) != CSSParseResult::Ok) {
    return false;
  }

  // Optional radius. Ignore errors except if they pass a negative
  // value which we must reject. If we use ParseNonNegativeVariant
  // we can't tell the difference between an unspecified radius
  // and a negative radius.
  CSSParseResult result =
    ParseVariant(val->Item(IndexRadius), VARIANT_LENGTH | VARIANT_CALC,
                 nullptr);
  if (result == CSSParseResult::Error) {
    return false;
  } else if (result == CSSParseResult::Ok) {
    if (val->Item(IndexRadius).IsLengthUnit() &&
        val->Item(IndexRadius).GetFloatValue() < 0) {
      return false;
    }
  }

  if (aIsBoxShadow) {
    // Optional spread
    if (ParseVariant(val->Item(IndexSpread), VARIANT_LENGTH | VARIANT_CALC,
                     nullptr) == CSSParseResult::Error) {
      return false;
    }
  }

  if (!haveColor) {
    // Optional color
    if (ParseVariant(val->Item(IndexColor), VARIANT_COLOR, nullptr) ==
        CSSParseResult::Error) {
      return false;
    }
  }

  if (aIsBoxShadow && val->Item(IndexInset).GetUnit() == eCSSUnit_Null) {
    // Optional inset keyword
    ParseSingleTokenVariant(val->Item(IndexInset), VARIANT_KEYWORD,
                            nsCSSProps::kBoxShadowTypeKTable);
  }

  aValue.SetArrayValue(val, eCSSUnit_Array);
  return true;
}

bool
CSSParserImpl::ParseShadowList(nsCSSPropertyID aProperty)
{
  nsAutoParseCompoundProperty compound(this);
  bool isBoxShadow = aProperty == eCSSProperty_box_shadow;

  nsCSSValue value;
  // 'inherit', 'initial', 'unset' and 'none' must be alone
  if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE,
                               nullptr)) {
    nsCSSValueList* cur = value.SetListValue();
    for (;;) {
      if (!ParseShadowItem(cur->mValue, isBoxShadow)) {
        return false;
      }
      if (!ExpectSymbol(',', true)) {
        break;
      }
      cur->mNext = new nsCSSValueList;
      cur = cur->mNext;
    }
  }
  AppendValue(aProperty, value);
  return true;
}

int32_t
CSSParserImpl::GetNamespaceIdForPrefix(const nsString& aPrefix)
{
  NS_PRECONDITION(!aPrefix.IsEmpty(), "Must have a prefix here");

  int32_t nameSpaceID = kNameSpaceID_Unknown;
  if (mNameSpaceMap) {
    // user-specified identifiers are case-sensitive (bug 416106)
    nsCOMPtr<nsIAtom> prefix = NS_Atomize(aPrefix);
    nameSpaceID = mNameSpaceMap->FindNameSpaceID(prefix);
  }
  // else no declared namespaces

  if (nameSpaceID == kNameSpaceID_Unknown) {   // unknown prefix, dump it
    REPORT_UNEXPECTED_P(PEUnknownNamespacePrefix, aPrefix);
  }

  return nameSpaceID;
}

void
CSSParserImpl::SetDefaultNamespaceOnSelector(nsCSSSelector& aSelector)
{
  if (mNameSpaceMap) {
    aSelector.SetNameSpace(mNameSpaceMap->FindNameSpaceID(nullptr));
  } else {
    aSelector.SetNameSpace(kNameSpaceID_Unknown); // wildcard
  }
}

bool
CSSParserImpl::ParsePaint(nsCSSPropertyID aPropID)
{
  nsCSSValue x, y;

  if (ParseVariant(x, VARIANT_HC | VARIANT_NONE | VARIANT_URL |
                      VARIANT_OPENTYPE_SVG_KEYWORD,
                   nsCSSProps::kContextPatternKTable) != CSSParseResult::Ok) {
    return false;
  }

  bool canHaveFallback = x.GetUnit() == eCSSUnit_URL ||
                         x.GetUnit() == eCSSUnit_Enumerated;
  if (canHaveFallback) {
    CSSParseResult result =
      ParseVariant(y, VARIANT_COLOR | VARIANT_NONE, nullptr);
    if (result == CSSParseResult::Error) {
      return false;
    } else if (result == CSSParseResult::NotFound) {
      y.SetNoneValue();
    }
  }

  if (!canHaveFallback) {
    AppendValue(aPropID, x);
  } else {
    nsCSSValue val;
    val.SetPairValue(x, y);
    AppendValue(aPropID, val);
  }
  return true;
}

bool
CSSParserImpl::ParseDasharray()
{
  nsCSSValue value;

  // 'inherit', 'initial', 'unset' and 'none' are only allowed on their own
  if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE |
                                      VARIANT_OPENTYPE_SVG_KEYWORD,
                               nsCSSProps::kStrokeContextValueKTable)) {
    nsCSSValueList *cur = value.SetListValue();
    for (;;) {
      if (!ParseSingleTokenNonNegativeVariant(cur->mValue, VARIANT_LPN,
                                              nullptr)) {
        return false;
      }
      if (CheckEndProperty()) {
        break;
      }
      // skip optional commas between elements
      (void)ExpectSymbol(',', true);

      cur->mNext = new nsCSSValueList;
      cur = cur->mNext;
    }
  }
  AppendValue(eCSSProperty_stroke_dasharray, value);
  return true;
}

bool
CSSParserImpl::ParseMarker()
{
  nsCSSValue marker;
  if (ParseSingleValueProperty(marker, eCSSProperty_marker_end) ==
      CSSParseResult::Ok) {
    AppendValue(eCSSProperty_marker_end, marker);
    AppendValue(eCSSProperty_marker_mid, marker);
    AppendValue(eCSSProperty_marker_start, marker);
    return true;
  }
  return false;
}

bool
CSSParserImpl::ParsePaintOrder()
{
  static_assert
    ((1 << NS_STYLE_PAINT_ORDER_BITWIDTH) > NS_STYLE_PAINT_ORDER_LAST_VALUE,
     "bitfield width insufficient for paint-order constants");

  static const KTableEntry kPaintOrderKTable[] = {
    { eCSSKeyword_normal,  NS_STYLE_PAINT_ORDER_NORMAL },
    { eCSSKeyword_fill,    NS_STYLE_PAINT_ORDER_FILL },
    { eCSSKeyword_stroke,  NS_STYLE_PAINT_ORDER_STROKE },
    { eCSSKeyword_markers, NS_STYLE_PAINT_ORDER_MARKERS },
    { eCSSKeyword_UNKNOWN, -1 }
  };

  static_assert(MOZ_ARRAY_LENGTH(kPaintOrderKTable) ==
                  NS_STYLE_PAINT_ORDER_LAST_VALUE + 2,
                "missing paint-order values in kPaintOrderKTable");

  nsCSSValue value;
  if (!ParseSingleTokenVariant(value, VARIANT_HK, kPaintOrderKTable)) {
    return false;
  }

  uint32_t seen = 0;
  uint32_t order = 0;
  uint32_t position = 0;

  // Ensure that even cast to a signed int32_t when stored in CSSValue,
  // we have enough space for the entire paint-order value.
  static_assert
    (NS_STYLE_PAINT_ORDER_BITWIDTH * NS_STYLE_PAINT_ORDER_LAST_VALUE < 32,
     "seen and order not big enough");

  if (value.GetUnit() == eCSSUnit_Enumerated) {
    uint32_t component = static_cast<uint32_t>(value.GetIntValue());
    if (component != NS_STYLE_PAINT_ORDER_NORMAL) {
      bool parsedOK = true;
      for (;;) {
        if (seen & (1 << component)) {
          // Already seen this component.
          UngetToken();
          parsedOK = false;
          break;
        }
        seen |= (1 << component);
        order |= (component << position);
        position += NS_STYLE_PAINT_ORDER_BITWIDTH;
        if (!ParseEnum(value, kPaintOrderKTable)) {
          break;
        }
        component = value.GetIntValue();
        if (component == NS_STYLE_PAINT_ORDER_NORMAL) {
          // Can't have "normal" in the middle of the list of paint components.
          UngetToken();
          parsedOK = false;
          break;
        }
      }

      // Fill in the remaining paint-order components in the order of their
      // constant values.
      if (parsedOK) {
        for (component = 1;
             component <= NS_STYLE_PAINT_ORDER_LAST_VALUE;
             component++) {
          if (!(seen & (1 << component))) {
            order |= (component << position);
            position += NS_STYLE_PAINT_ORDER_BITWIDTH;
          }
        }
      }
    }

    static_assert(NS_STYLE_PAINT_ORDER_NORMAL == 0,
                  "unexpected value for NS_STYLE_PAINT_ORDER_NORMAL");
    value.SetIntValue(static_cast<int32_t>(order), eCSSUnit_Enumerated);
  }

  AppendValue(eCSSProperty_paint_order, value);
  return true;
}

bool
CSSParserImpl::BackslashDropped()
{
  return mScanner->GetEOFCharacters() &
         nsCSSScanner::eEOFCharacters_DropBackslash;
}

void
CSSParserImpl::AppendImpliedEOFCharacters(nsAString& aResult)
{
  nsCSSScanner::AppendImpliedEOFCharacters(mScanner->GetEOFCharacters(),
                                           aResult);
}

bool
CSSParserImpl::ParseAll()
{
  nsCSSValue value;
  if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) {
    return false;
  }

  // It's unlikely we'll want to use 'all' from within a UA style sheet, so
  // instead of computing the correct EnabledState value we just expand out
  // to all content-visible properties.
  CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, eCSSProperty_all,
                                       CSSEnabledState::eForAllContent) {
    AppendValue(*p, value);
  }
  return true;
}

bool
CSSParserImpl::ParseVariableDeclaration(CSSVariableDeclarations::Type* aType,
                                        nsString& aValue)
{
  CSSVariableDeclarations::Type type;
  nsString variableValue;
  bool dropBackslash;
  nsString impliedCharacters;

  // Record the token stream while parsing a variable value.
  if (!mInSupportsCondition) {
    mScanner->StartRecording();
  }
  if (!ParseValueWithVariables(&type, &dropBackslash, impliedCharacters,
                               nullptr, nullptr)) {
    if (!mInSupportsCondition) {
      mScanner->StopRecording();
    }
    return false;
  }

  if (!mInSupportsCondition) {
    if (type == CSSVariableDeclarations::eTokenStream) {
      // This was indeed a token stream value, so store it in variableValue.
      mScanner->StopRecording(variableValue);
      if (dropBackslash) {
        MOZ_ASSERT(!variableValue.IsEmpty() &&
                   variableValue[variableValue.Length() - 1] == '\\');
        variableValue.Truncate(variableValue.Length() - 1);
      }
      variableValue.Append(impliedCharacters);
    } else {
      // This was either 'inherit' or 'initial'; we don't need the recorded
      // input.
      mScanner->StopRecording();
    }
  }

  if (mHavePushBack && type == CSSVariableDeclarations::eTokenStream) {
    // If we came to the end of a valid variable declaration and a token was
    // pushed back, then it would have been ended by '!', ')', ';', ']' or '}'.
    // We need to remove it from the recorded variable value.
    MOZ_ASSERT(mToken.IsSymbol('!') ||
               mToken.IsSymbol(')') ||
               mToken.IsSymbol(';') ||
               mToken.IsSymbol(']') ||
               mToken.IsSymbol('}'));
    if (!mInSupportsCondition) {
      MOZ_ASSERT(!variableValue.IsEmpty());
      MOZ_ASSERT(variableValue[variableValue.Length() - 1] == mToken.mSymbol);
      variableValue.Truncate(variableValue.Length() - 1);
    }
  }

  *aType = type;
  aValue = variableValue;
  return true;
}

bool
CSSParserImpl::ParseScrollSnapType()
{
  nsCSSValue value;
  if (!ParseSingleTokenVariant(value, VARIANT_HK,
                               nsCSSProps::kScrollSnapTypeKTable)) {
    return false;
  }
  AppendValue(eCSSProperty_scroll_snap_type_x, value);
  AppendValue(eCSSProperty_scroll_snap_type_y, value);
  return true;
}

bool
CSSParserImpl::ParseScrollSnapPoints(nsCSSValue& aValue, nsCSSPropertyID aPropID)
{
  if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NONE,
                              nullptr)) {
    return true;
  }
  if (!GetToken(true)) {
    return false;
  }
  if (mToken.mType == eCSSToken_Function &&
      nsCSSKeywords::LookupKeyword(mToken.mIdent) == eCSSKeyword_repeat) {
    nsCSSValue lengthValue;
    if (ParseNonNegativeVariant(lengthValue,
                                VARIANT_LENGTH | VARIANT_PERCENT | VARIANT_CALC,
                                nullptr) != CSSParseResult::Ok) {
      REPORT_UNEXPECTED(PEExpectedNonnegativeNP);
      SkipUntil(')');
      return false;
    }
    if (!ExpectSymbol(')', true)) {
      REPORT_UNEXPECTED(PEExpectedCloseParen);
      SkipUntil(')');
      return false;
    }
    RefPtr<nsCSSValue::Array> functionArray =
      aValue.InitFunction(eCSSKeyword_repeat, 1);
    functionArray->Item(1) = lengthValue;
    return true;
  }
  UngetToken();
  return false;
}


bool
CSSParserImpl::ParseScrollSnapDestination(nsCSSValue& aValue)
{
  if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT, nullptr)) {
    return true;
  }
  nsCSSValue itemValue;
  if (!ParsePositionValue(aValue)) {
    REPORT_UNEXPECTED_TOKEN(PEExpectedPosition);
    return false;
  }
  return true;
}

// This function is very similar to ParseImageLayerPosition, and ParseImageLayerSize.
bool
CSSParserImpl::ParseScrollSnapCoordinate(nsCSSValue& aValue)
{
  if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NONE,
                              nullptr)) {
    return true;
  }
  nsCSSValue itemValue;
  if (!ParsePositionValue(itemValue)) {
    REPORT_UNEXPECTED_TOKEN(PEExpectedPosition);
    return false;
  }
  nsCSSValueList* item = aValue.SetListValue();
  for (;;) {
    item->mValue = itemValue;
    if (!ExpectSymbol(',', true)) {
      break;
    }
    if (!ParsePositionValue(itemValue)) {
      REPORT_UNEXPECTED_TOKEN(PEExpectedPosition);
      return false;
    }
    item->mNext = new nsCSSValueList;
    item = item->mNext;
  }
  return true;
}

bool
CSSParserImpl::ParseValueWithVariables(CSSVariableDeclarations::Type* aType,
                                       bool* aDropBackslash,
                                       nsString& aImpliedCharacters,
                                       void (*aFunc)(const nsAString&, void*),
                                       void* aData)
{
  // A property value is invalid if it contains variable references and also:
  //
  //   * has unbalanced parens, brackets or braces
  //   * has any BAD_STRING or BAD_URL tokens
  //   * has any ';' or '!' tokens at the top level of a variable reference's
  //     fallback
  //
  // If the property is a custom property (i.e. a variable declaration), then
  // it is also invalid if it consists of no tokens, such as:
  //
  //   --invalid:;
  //
  // Note that is valid for a custom property to have a value that consists
  // solely of white space, such as:
  //
  //   --valid: ;

  // Stack of closing characters for currently open constructs.
  StopSymbolCharStack stack;

  // Indexes into ')' characters in |stack| that correspond to "var(".  This
  // is used to stop parsing when we encounter a '!' or ';' at the top level
  // of a variable reference's fallback.
  AutoTArray<uint32_t, 16> references;

  if (!GetToken(false)) {
    // Variable value was empty since we reached EOF.
    REPORT_UNEXPECTED_EOF(PEVariableEOF);
    return false;
  }

  if (mToken.mType == eCSSToken_Symbol &&
      (mToken.mSymbol == '!' ||
       mToken.mSymbol == ')' ||
       mToken.mSymbol == ';' ||
       mToken.mSymbol == ']' ||
       mToken.mSymbol == '}')) {
    // Variable value was empty since we reached the end of the construct.
    UngetToken();
    REPORT_UNEXPECTED_TOKEN(PEVariableEmpty);
    return false;
  }

  if (mToken.mType == eCSSToken_Whitespace) {
    if (!GetToken(true)) {
      // Variable value was white space only.  This is valid.
      MOZ_ASSERT(!BackslashDropped());
      *aType = CSSVariableDeclarations::eTokenStream;
      *aDropBackslash = false;
      AppendImpliedEOFCharacters(aImpliedCharacters);
      return true;
    }
  }

  // Look for 'initial', 'inherit' or 'unset' as the first non-white space
  // token.
  CSSVariableDeclarations::Type type = CSSVariableDeclarations::eTokenStream;
  if (mToken.mType == eCSSToken_Ident) {
    if (mToken.mIdent.LowerCaseEqualsLiteral("initial")) {
      type = CSSVariableDeclarations::eInitial;
    } else if (mToken.mIdent.LowerCaseEqualsLiteral("inherit")) {
      type = CSSVariableDeclarations::eInherit;
    } else if (mToken.mIdent.LowerCaseEqualsLiteral("unset")) {
      type = CSSVariableDeclarations::eUnset;
    }
  }

  if (type != CSSVariableDeclarations::eTokenStream) {
    if (!GetToken(true)) {
      // Variable value was 'initial' or 'inherit' followed by EOF.
      MOZ_ASSERT(!BackslashDropped());
      *aType = type;
      *aDropBackslash = false;
      AppendImpliedEOFCharacters(aImpliedCharacters);
      return true;
    }
    UngetToken();
    if (mToken.mType == eCSSToken_Symbol &&
        (mToken.mSymbol == '!' ||
         mToken.mSymbol == ')' ||
         mToken.mSymbol == ';' ||
         mToken.mSymbol == ']' ||
         mToken.mSymbol == '}')) {
      // Variable value was 'initial' or 'inherit' followed by the end
      // of the declaration.
      MOZ_ASSERT(!BackslashDropped());
      *aType = type;
      *aDropBackslash = false;
      return true;
    }
  }

  do {
    switch (mToken.mType) {
      case eCSSToken_Symbol:
        if (mToken.mSymbol == '(') {
          stack.AppendElement(')');
        } else if (mToken.mSymbol == '[') {
          stack.AppendElement(']');
        } else if (mToken.mSymbol == '{') {
          stack.AppendElement('}');
        } else if (mToken.mSymbol == ';' ||
                   mToken.mSymbol == '!') {
          if (stack.IsEmpty()) {
            UngetToken();
            MOZ_ASSERT(!BackslashDropped());
            *aType = CSSVariableDeclarations::eTokenStream;
            *aDropBackslash = false;
            return true;
          } else if (!references.IsEmpty() &&
                     references.LastElement() == stack.Length() - 1) {
            REPORT_UNEXPECTED_TOKEN(PEInvalidVariableTokenFallback);
            SkipUntilAllOf(stack);
            return false;
          }
        } else if (mToken.mSymbol == ')' ||
                   mToken.mSymbol == ']' ||
                   mToken.mSymbol == '}') {
          for (;;) {
            if (stack.IsEmpty()) {
              UngetToken();
              MOZ_ASSERT(!BackslashDropped());
              *aType = CSSVariableDeclarations::eTokenStream;
              *aDropBackslash = false;
              return true;
            }
            char16_t c = stack.LastElement();
            stack.TruncateLength(stack.Length() - 1);
            if (!references.IsEmpty() &&
                references.LastElement() == stack.Length()) {
              references.TruncateLength(references.Length() - 1);
            }
            if (mToken.mSymbol == c) {
              break;
            }
          }
        }
        break;

      case eCSSToken_Function:
        if (mToken.mIdent.LowerCaseEqualsLiteral("var")) {
          if (!GetToken(true)) {
            // EOF directly after "var(".
            REPORT_UNEXPECTED_EOF(PEExpectedVariableNameEOF);
            return false;
          }
          if (mToken.mType != eCSSToken_Ident ||
              !nsCSSProps::IsCustomPropertyName(mToken.mIdent)) {
            // There must be an identifier directly after the "var(" and
            // it must be a custom property name.
            UngetToken();
            REPORT_UNEXPECTED_TOKEN(PEExpectedVariableName);
            SkipUntil(')');
            SkipUntilAllOf(stack);
            return false;
          }
          if (aFunc) {
            MOZ_ASSERT(Substring(mToken.mIdent, 0,
                                 CSS_CUSTOM_NAME_PREFIX_LENGTH).
                         EqualsLiteral("--"));
            // remove '--'
            const nsDependentSubstring varName =
              Substring(mToken.mIdent, CSS_CUSTOM_NAME_PREFIX_LENGTH);
            aFunc(varName, aData);
          }
          if (!GetToken(true)) {
            // EOF right after "var(<ident>".
            stack.AppendElement(')');
          } else if (mToken.IsSymbol(',')) {
            // Variable reference with fallback.
            if (!GetToken(false) || mToken.IsSymbol(')')) {
              // Comma must be followed by at least one fallback token.
              REPORT_UNEXPECTED(PEExpectedVariableFallback);
              SkipUntilAllOf(stack);
              return false;
            }
            UngetToken();
            references.AppendElement(stack.Length());
            stack.AppendElement(')');
          } else if (mToken.IsSymbol(')')) {
            // Correctly closed variable reference.
          } else {
            // Malformed variable reference.
            REPORT_UNEXPECTED_TOKEN(PEExpectedVariableCommaOrCloseParen);
            SkipUntil(')');
            SkipUntilAllOf(stack);
            return false;
          }
        } else {
          stack.AppendElement(')');
        }
        break;

      case eCSSToken_Bad_String:
        SkipUntilAllOf(stack);
        return false;

      case eCSSToken_Bad_URL:
        SkipUntil(')');
        SkipUntilAllOf(stack);
        return false;

      default:
        break;
    }
  } while (GetToken(true));

  // Append any implied closing characters.
  *aDropBackslash = BackslashDropped();
  AppendImpliedEOFCharacters(aImpliedCharacters);
  uint32_t i = stack.Length();
  while (i--) {
    aImpliedCharacters.Append(stack[i]);
  }

  *aType = type;
  return true;
}

bool
CSSParserImpl::IsValueValidForProperty(const nsCSSPropertyID aPropID,
                                       const nsAString& aPropValue)
{
  mData.AssertInitialState();
  mTempData.AssertInitialState();

  nsCSSScanner scanner(aPropValue, 0);
  css::ErrorReporter reporter(scanner, mSheet, mChildLoader, nullptr);
  InitScanner(scanner, reporter, nullptr, nullptr, nullptr);

  // We normally would need to pass in a sheet principal to InitScanner,
  // because we might parse a URL value.  However, we will never use the
  // parsed nsCSSValue (and so whether we have a sheet principal or not
  // doesn't really matter), so to avoid failing the assertion in
  // SetValueToURL, we set mSheetPrincipalRequired to false to declare
  // that it's safe to skip the assertion.
  AutoRestore<bool> autoRestore(mSheetPrincipalRequired);
  mSheetPrincipalRequired = false;

  nsAutoSuppressErrors suppressErrors(this);

  mSection = eCSSSection_General;
  scanner.SetSVGMode(false);

  // Check for unknown properties
  if (eCSSProperty_UNKNOWN == aPropID) {
    ReleaseScanner();
    return false;
  }

  // Check that the property and value parse successfully
  bool parsedOK = ParseProperty(aPropID);

  // Check for priority
  parsedOK = parsedOK && ParsePriority() != ePriority_Error;

  // We should now be at EOF
  parsedOK = parsedOK && !GetToken(true);

  mTempData.ClearProperty(aPropID);
  mTempData.AssertInitialState();
  mData.AssertInitialState();

  CLEAR_ERROR();
  ReleaseScanner();

  return parsedOK;
}

} // namespace

// Recycling of parser implementation objects

static CSSParserImpl* gFreeList = nullptr;

/* static */ void
nsCSSParser::Startup()
{
  Preferences::AddBoolVarCache(&sOpentypeSVGEnabled,
                               "gfx.font_rendering.opentype_svg.enabled");
  Preferences::AddBoolVarCache(&sWebkitPrefixedAliasesEnabled,
                               "layout.css.prefixes.webkit");
  Preferences::AddBoolVarCache(&sWebkitDevicePixelRatioEnabled,
                               "layout.css.prefixes.device-pixel-ratio-webkit");
  Preferences::AddBoolVarCache(&sUnprefixingServiceEnabled,
                               "layout.css.unprefixing-service.enabled");
#ifdef NIGHTLY_BUILD
  Preferences::AddBoolVarCache(&sUnprefixingServiceGloballyWhitelisted,
                               "layout.css.unprefixing-service.globally-whitelisted");
#endif
  Preferences::AddBoolVarCache(&sMozGradientsEnabled,
                               "layout.css.prefixes.gradients");
  Preferences::AddBoolVarCache(&sControlCharVisibility,
                               "layout.css.control-characters.visible");
}

nsCSSParser::nsCSSParser(mozilla::css::Loader* aLoader,
                         CSSStyleSheet* aSheet)
{
  CSSParserImpl *impl = gFreeList;
  if (impl) {
    gFreeList = impl->mNextFree;
    impl->mNextFree = nullptr;
  } else {
    impl = new CSSParserImpl();
  }

  if (aLoader) {
    impl->SetChildLoader(aLoader);
    impl->SetQuirkMode(aLoader->GetCompatibilityMode() ==
                       eCompatibility_NavQuirks);
  }
  if (aSheet) {
    impl->SetStyleSheet(aSheet);
  }

  mImpl = static_cast<void*>(impl);
}

nsCSSParser::~nsCSSParser()
{
  CSSParserImpl *impl = static_cast<CSSParserImpl*>(mImpl);
  impl->Reset();
  impl->mNextFree = gFreeList;
  gFreeList = impl;
}

/* static */ void
nsCSSParser::Shutdown()
{
  CSSParserImpl *tofree = gFreeList;
  CSSParserImpl *next;
  while (tofree)
    {
      next = tofree->mNextFree;
      delete tofree;
      tofree = next;
    }
}

// Wrapper methods

nsresult
nsCSSParser::ParseSheet(const nsAString& aInput,
                        nsIURI*          aSheetURI,
                        nsIURI*          aBaseURI,
                        nsIPrincipal*    aSheetPrincipal,
                        uint32_t         aLineNumber,
                        css::LoaderReusableStyleSheets* aReusableSheets)
{
  return static_cast<CSSParserImpl*>(mImpl)->
    ParseSheet(aInput, aSheetURI, aBaseURI, aSheetPrincipal, aLineNumber,
               aReusableSheets);
}

already_AddRefed<css::Declaration>
nsCSSParser::ParseStyleAttribute(const nsAString&  aAttributeValue,
                                 nsIURI*           aDocURI,
                                 nsIURI*           aBaseURI,
                                 nsIPrincipal*     aNodePrincipal)
{
  return static_cast<CSSParserImpl*>(mImpl)->
    ParseStyleAttribute(aAttributeValue, aDocURI, aBaseURI, aNodePrincipal);
}

nsresult
nsCSSParser::ParseDeclarations(const nsAString&  aBuffer,
                               nsIURI*           aSheetURI,
                               nsIURI*           aBaseURI,
                               nsIPrincipal*     aSheetPrincipal,
                               css::Declaration* aDeclaration,
                               bool*           aChanged)
{
  return static_cast<CSSParserImpl*>(mImpl)->
    ParseDeclarations(aBuffer, aSheetURI, aBaseURI, aSheetPrincipal,
                      aDeclaration, aChanged);
}

nsresult
nsCSSParser::ParseRule(const nsAString&        aRule,
                       nsIURI*                 aSheetURI,
                       nsIURI*                 aBaseURI,
                       nsIPrincipal*           aSheetPrincipal,
                       css::Rule**             aResult)
{
  return static_cast<CSSParserImpl*>(mImpl)->
    ParseRule(aRule, aSheetURI, aBaseURI, aSheetPrincipal, aResult);
}

void
nsCSSParser::ParseProperty(const nsCSSPropertyID aPropID,
                           const nsAString&    aPropValue,
                           nsIURI*             aSheetURI,
                           nsIURI*             aBaseURI,
                           nsIPrincipal*       aSheetPrincipal,
                           css::Declaration*   aDeclaration,
                           bool*               aChanged,
                           bool                aIsImportant,
                           bool                aIsSVGMode)
{
  static_cast<CSSParserImpl*>(mImpl)->
    ParseProperty(aPropID, aPropValue, aSheetURI, aBaseURI,
                  aSheetPrincipal, aDeclaration, aChanged,
                  aIsImportant, aIsSVGMode);
}

void
nsCSSParser::ParseLonghandProperty(const nsCSSPropertyID aPropID,
                                   const nsAString&    aPropValue,
                                   nsIURI*             aSheetURI,
                                   nsIURI*             aBaseURI,
                                   nsIPrincipal*       aSheetPrincipal,
                                   nsCSSValue&         aResult)
{
  static_cast<CSSParserImpl*>(mImpl)->
    ParseLonghandProperty(aPropID, aPropValue, aSheetURI, aBaseURI,
                          aSheetPrincipal, aResult);
}

bool
nsCSSParser::ParseTransformProperty(const nsAString& aPropValue,
                                    bool             aDisallowRelativeValues,
                                    nsCSSValue&      aResult)
{
  return static_cast<CSSParserImpl*>(mImpl)->
    ParseTransformProperty(aPropValue, aDisallowRelativeValues, aResult);
}

void
nsCSSParser::ParseVariable(const nsAString&    aVariableName,
                           const nsAString&    aPropValue,
                           nsIURI*             aSheetURI,
                           nsIURI*             aBaseURI,
                           nsIPrincipal*       aSheetPrincipal,
                           css::Declaration*   aDeclaration,
                           bool*               aChanged,
                           bool                aIsImportant)
{
  static_cast<CSSParserImpl*>(mImpl)->
    ParseVariable(aVariableName, aPropValue, aSheetURI, aBaseURI,
                  aSheetPrincipal, aDeclaration, aChanged, aIsImportant);
}

void
nsCSSParser::ParseMediaList(const nsSubstring& aBuffer,
                            nsIURI*            aURI,
                            uint32_t           aLineNumber,
                            nsMediaList*       aMediaList,
                            bool               aHTMLMode)
{
  static_cast<CSSParserImpl*>(mImpl)->
    ParseMediaList(aBuffer, aURI, aLineNumber, aMediaList, aHTMLMode);
}

bool
nsCSSParser::ParseSourceSizeList(const nsAString& aBuffer,
                                 nsIURI* aURI,
                                 uint32_t aLineNumber,
                                 InfallibleTArray< nsAutoPtr<nsMediaQuery> >& aQueries,
                                 InfallibleTArray<nsCSSValue>& aValues,
                                 bool aHTMLMode)
{
  return static_cast<CSSParserImpl*>(mImpl)->
    ParseSourceSizeList(aBuffer, aURI, aLineNumber, aQueries, aValues,
                        aHTMLMode);
}

bool
nsCSSParser::ParseFontFamilyListString(const nsSubstring& aBuffer,
                                       nsIURI*            aURI,
                                       uint32_t           aLineNumber,
                                       nsCSSValue&        aValue)
{
  return static_cast<CSSParserImpl*>(mImpl)->
    ParseFontFamilyListString(aBuffer, aURI, aLineNumber, aValue);
}

bool
nsCSSParser::ParseColorString(const nsSubstring& aBuffer,
                              nsIURI*            aURI,
                              uint32_t           aLineNumber,
                              nsCSSValue&        aValue,
                              bool               aSuppressErrors /* false */)
{
  return static_cast<CSSParserImpl*>(mImpl)->
    ParseColorString(aBuffer, aURI, aLineNumber, aValue, aSuppressErrors);
}

bool
nsCSSParser::ParseMarginString(const nsSubstring& aBuffer,
                               nsIURI*            aURI,
                               uint32_t           aLineNumber,
                               nsCSSValue&        aValue,
                               bool               aSuppressErrors /* false */)
{
  return static_cast<CSSParserImpl*>(mImpl)->
    ParseMarginString(aBuffer, aURI, aLineNumber, aValue, aSuppressErrors);
}

nsresult
nsCSSParser::ParseSelectorString(const nsSubstring&  aSelectorString,
                                 nsIURI*             aURI,
                                 uint32_t            aLineNumber,
                                 nsCSSSelectorList** aSelectorList)
{
  return static_cast<CSSParserImpl*>(mImpl)->
    ParseSelectorString(aSelectorString, aURI, aLineNumber, aSelectorList);
}

already_AddRefed<nsCSSKeyframeRule>
nsCSSParser::ParseKeyframeRule(const nsSubstring& aBuffer,
                               nsIURI*            aURI,
                               uint32_t           aLineNumber)
{
  return static_cast<CSSParserImpl*>(mImpl)->
    ParseKeyframeRule(aBuffer, aURI, aLineNumber);
}

bool
nsCSSParser::ParseKeyframeSelectorString(const nsSubstring& aSelectorString,
                                         nsIURI*            aURI,
                                         uint32_t           aLineNumber,
                                         InfallibleTArray<float>& aSelectorList)
{
  return static_cast<CSSParserImpl*>(mImpl)->
    ParseKeyframeSelectorString(aSelectorString, aURI, aLineNumber,
                                aSelectorList);
}

bool
nsCSSParser::EvaluateSupportsDeclaration(const nsAString& aProperty,
                                         const nsAString& aValue,
                                         nsIURI* aDocURL,
                                         nsIURI* aBaseURL,
                                         nsIPrincipal* aDocPrincipal)
{
  return static_cast<CSSParserImpl*>(mImpl)->
    EvaluateSupportsDeclaration(aProperty, aValue, aDocURL, aBaseURL,
                                aDocPrincipal);
}

bool
nsCSSParser::EvaluateSupportsCondition(const nsAString& aCondition,
                                       nsIURI* aDocURL,
                                       nsIURI* aBaseURL,
                                       nsIPrincipal* aDocPrincipal)
{
  return static_cast<CSSParserImpl*>(mImpl)->
    EvaluateSupportsCondition(aCondition, aDocURL, aBaseURL, aDocPrincipal);
}

bool
nsCSSParser::EnumerateVariableReferences(const nsAString& aPropertyValue,
                                         VariableEnumFunc aFunc,
                                         void* aData)
{
  return static_cast<CSSParserImpl*>(mImpl)->
    EnumerateVariableReferences(aPropertyValue, aFunc, aData);
}

bool
nsCSSParser::ResolveVariableValue(const nsAString& aPropertyValue,
                                  const CSSVariableValues* aVariables,
                                  nsString& aResult,
                                  nsCSSTokenSerializationType& aFirstToken,
                                  nsCSSTokenSerializationType& aLastToken)
{
  return static_cast<CSSParserImpl*>(mImpl)->
    ResolveVariableValue(aPropertyValue, aVariables,
                         aResult, aFirstToken, aLastToken);
}

void
nsCSSParser::ParsePropertyWithVariableReferences(
                                            nsCSSPropertyID aPropertyID,
                                            nsCSSPropertyID aShorthandPropertyID,
                                            const nsAString& aValue,
                                            const CSSVariableValues* aVariables,
                                            nsRuleData* aRuleData,
                                            nsIURI* aDocURL,
                                            nsIURI* aBaseURL,
                                            nsIPrincipal* aDocPrincipal,
                                            CSSStyleSheet* aSheet,
                                            uint32_t aLineNumber,
                                            uint32_t aLineOffset)
{
  static_cast<CSSParserImpl*>(mImpl)->
    ParsePropertyWithVariableReferences(aPropertyID, aShorthandPropertyID,
                                        aValue, aVariables, aRuleData, aDocURL,
                                        aBaseURL, aDocPrincipal, aSheet,
                                        aLineNumber, aLineOffset);
}

bool
nsCSSParser::ParseCounterStyleName(const nsAString& aBuffer,
                                   nsIURI* aURL,
                                   nsAString& aName)
{
  return static_cast<CSSParserImpl*>(mImpl)->
    ParseCounterStyleName(aBuffer, aURL, aName);
}

bool
nsCSSParser::ParseCounterDescriptor(nsCSSCounterDesc aDescID,
                                    const nsAString& aBuffer,
                                    nsIURI* aSheetURL,
                                    nsIURI* aBaseURL,
                                    nsIPrincipal* aSheetPrincipal,
                                    nsCSSValue& aValue)
{
  return static_cast<CSSParserImpl*>(mImpl)->
    ParseCounterDescriptor(aDescID, aBuffer,
                           aSheetURL, aBaseURL, aSheetPrincipal, aValue);
}

bool
nsCSSParser::ParseFontFaceDescriptor(nsCSSFontDesc aDescID,
                                     const nsAString& aBuffer,
                                     nsIURI* aSheetURL,
                                     nsIURI* aBaseURL,
                                     nsIPrincipal* aSheetPrincipal,
                                     nsCSSValue& aValue)
{
  return static_cast<CSSParserImpl*>(mImpl)->
    ParseFontFaceDescriptor(aDescID, aBuffer,
                           aSheetURL, aBaseURL, aSheetPrincipal, aValue);
}

bool
nsCSSParser::IsValueValidForProperty(const nsCSSPropertyID aPropID,
                                     const nsAString&    aPropValue)
{
  return static_cast<CSSParserImpl*>(mImpl)->
    IsValueValidForProperty(aPropID, aPropValue);
}

/* static */
uint8_t
nsCSSParser::ControlCharVisibilityDefault()
{
  return sControlCharVisibility
    ? NS_STYLE_CONTROL_CHARACTER_VISIBILITY_VISIBLE
    : NS_STYLE_CONTROL_CHARACTER_VISIBILITY_HIDDEN;
}