layout/style/nsCSSParser.cpp
author Zack Weinberg <zackw@panix.com>
Wed, 02 Sep 2015 13:54:30 -0400
changeset 260586 1fc07bdd9aa8d04a50c5f77956638bc452df45c5
parent 260585 f6e98029d1cb3b7f40dd427802f98223f9c2e419
child 260694 3602ae56a2431a542adf9f9169932d7eaf521195
permissions -rw-r--r--
Bug 1035091 part 2: disable @-moz-document in author sheets. r=heycam

/* -*- 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 "nsCSSParser.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/CSSStyleSheet.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 "nsNameSpaceManager.h"
#include "nsXMLNameSpaceMap.h"
#include "nsError.h"
#include "nsIMediaList.h"
#include "nsStyleUtil.h"
#include "nsIPrincipal.h"
#include "nsICSSUnprefixingService.h"
#include "prprf.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/URL.h"
#include "gfxFontFamilyList.h"

using namespace mozilla;

typedef nsCSSProps::KTableValue KTableValue;

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

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-columns and grid-template-rows 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
};

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(eAuthorSheetFeatures == 0 &&
              eUserSheetFeatures == 1 &&
              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,
                      SheetParsingMode aParsingMode);

  nsresult ParseStyleAttribute(const nsAString&  aAttributeValue,
                               nsIURI*           aDocURL,
                               nsIURI*           aBaseURL,
                               nsIPrincipal*     aNodePrincipal,
                               css::StyleRule**  aResult);

  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 nsCSSProperty aPropID,
                     const nsAString& aPropValue,
                     nsIURI* aSheetURL,
                     nsIURI* aBaseURL,
                     nsIPrincipal* aSheetPrincipal,
                     css::Declaration* aDeclaration,
                     bool* aChanged,
                     bool aIsImportant,
                     bool aIsSVGMode);

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

  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 nsCSSProperty 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(nsCSSProperty aPropertyID,
                                           nsCSSProperty 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 == eAgentSheetFeatures;
  }
  bool UserRulesEnabled() const {
    return mParsingMode == eAgentSheetFeatures ||
           mParsingMode == eUserSheetFeatures;
  }

  nsCSSProps::EnabledState PropertyEnabledState() const {
    static_assert(nsCSSProps::eEnabledForAllContent == 0,
                  "nsCSSProps::eEnabledForAllContent should be zero for "
                  "this bitfield to work");
    nsCSSProps::EnabledState enabledState = nsCSSProps::eEnabledForAllContent;
    if (AgentRulesEnabled()) {
      enabledState |= nsCSSProps::eEnabledInUASheets;
    }
    if (mIsChromeOrCertifiedApp) {
      enabledState |= nsCSSProps::eEnabledInChromeOrCertifiedApp;
    }
    return enabledState;
  }

  nsCSSProperty LookupEnabledProperty(const nsAString& aProperty) {
    return nsCSSProps::LookupProperty(aProperty, PropertyEnabledState());
  }

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

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

  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 nsAutoTArray<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 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,
                                              nsCSSPseudoElements::Type* aPseudoElementType);

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

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

  nsSelectorParsingStatus ParsePseudoClassWithIdentArg(nsCSSSelector& aSelector,
                                                       nsCSSPseudoClasses::Type aType);

  nsSelectorParsingStatus ParsePseudoClassWithNthPairArg(nsCSSSelector& aSelector,
                                                         nsCSSPseudoClasses::Type aType);

  nsSelectorParsingStatus ParsePseudoClassWithSelectorListArg(nsCSSSelector& aSelector,
                                                              nsCSSPseudoClasses::Type 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
  };

  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 KTableValue aKeywordTable[]);

  bool ShouldUseUnprefixingService();
  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|. Only call if
  // ShouldUseUnprefixingService() returns true.
  bool ParseWebkitPrefixedGradient(nsAString& aPrefixedFuncName,
                                   nsCSSValue& aValue);

  bool ParseProperty(nsCSSProperty aPropID);
  bool ParsePropertyByFunction(nsCSSProperty aPropID);
  bool ParseSingleValueProperty(nsCSSValue& aValue,
                                nsCSSProperty aPropID);

  // These are similar to ParseSingleValueProperty but only work for
  // properties that are parsed with ParseBoxProperties or
  // ParseGroupedBoxProperty.  Stores in aConsumedTokens whether any tokens
  // were consumed.
  //
  // Only works with variants with the following flags:
  // A, C, H, K, L, N, P, CALC.
  bool ParseBoxPropertyVariant(nsCSSValue& aValue,
                               uint32_t aVariantMask,
                               const KTableValue aKeywordTable[],
                               uint32_t aRestrictions,
                               bool& aConsumedTokens);
  bool ParseBoxProperty(nsCSSValue& aValue,
                        nsCSSProperty aPropID,
                        bool& aConsumedTokens);

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

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

  // Property specific parsing routines
  bool ParseBackground();

  struct BackgroundParseState {
    nsCSSValue&  mColor;
    nsCSSValueList* mImage;
    nsCSSValuePairList* mRepeat;
    nsCSSValueList* mAttachment;
    nsCSSValueList* mClip;
    nsCSSValueList* mOrigin;
    nsCSSValueList* mPosition;
    nsCSSValuePairList* mSize;
    BackgroundParseState(
        nsCSSValue& aColor, nsCSSValueList* aImage, nsCSSValuePairList* aRepeat,
        nsCSSValueList* aAttachment, nsCSSValueList* aClip,
        nsCSSValueList* aOrigin, nsCSSValueList* aPosition,
        nsCSSValuePairList* aSize) :
        mColor(aColor), mImage(aImage), mRepeat(aRepeat),
        mAttachment(aAttachment), mClip(aClip), mOrigin(aOrigin),
        mPosition(aPosition), mSize(aSize) {};
  };

  bool ParseBackgroundItem(BackgroundParseState& aState);

  bool ParseValueList(nsCSSProperty aPropID); // a single value prop-id
  bool ParseBackgroundRepeat();
  bool ParseBackgroundRepeatValues(nsCSSValuePair& aValue);
  bool ParseBackgroundPosition();

  // 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 ParseBackgroundSize();
  bool ParseBackgroundSizeValues(nsCSSValuePair& aOut);
  bool ParseBorderColor();
  bool ParseBorderColors(nsCSSProperty 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 nsCSSProperty aPropIDs[],
                         bool aSetAllSides);
  bool ParseBorderStyle();
  bool ParseBorderWidth();

  bool ParseCalc(nsCSSValue &aValue, int32_t aVariantMask);
  bool ParseCalcAdditiveExpression(nsCSSValue& aValue,
                                     int32_t& aVariantMask);
  bool ParseCalcMultiplicativeExpression(nsCSSValue& aValue,
                                           int32_t& aVariantMask,
                                           bool *aHadFinalWS);
  bool ParseCalcTerm(nsCSSValue& aValue, int32_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);
  bool ParseGridTrackBreadth(nsCSSValue& aValue);
  CSSParseResult ParseGridTrackSize(nsCSSValue& aValue);
  bool ParseGridAutoColumnsRows(nsCSSProperty aPropID);
  bool ParseGridTrackListRepeat(nsCSSValueList** aTailPtr);

  // 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>,
  // 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);
  bool ParseGridTemplateColumnsRows(nsCSSProperty 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 ParseGridTemplate();
  bool ParseGridTemplateAfterSlash(bool aColumnsIsTrackList);
  bool ParseGridTemplateAfterString(const nsCSSValue& aFirstLineNames);
  bool ParseGrid();
  bool ParseGridShorthandAutoProps();
  bool ParseGridLine(nsCSSValue& aValue);
  bool ParseGridColumnRowStartEnd(nsCSSProperty aPropID);
  bool ParseGridColumnRow(nsCSSProperty aStartPropID,
                          nsCSSProperty aEndPropID);
  bool ParseGridArea();

  // for 'clip' and '-moz-image-region'
  bool ParseRect(nsCSSProperty aPropID);
  bool ParseColumns();
  bool ParseContain(nsCSSValue& aValue);
  bool ParseContent();
  bool ParseCounterData(nsCSSProperty 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 KTableValue 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 ParseFontSrc(nsCSSValue& aValue);
  bool ParseFontSrcFormat(InfallibleTArray<nsCSSValue>& values);
  bool ParseFontRanges(nsCSSValue& aValue);
  bool ParseListStyle();
  bool ParseListStyleType(nsCSSValue& aValue);
  bool ParseMargin();
  bool ParseMarks(nsCSSValue& aValue);
  bool ParseClipPath();
  bool ParseTransform(bool aIsPrefixed);
  bool ParseObjectPosition();
  bool ParseOutline();
  bool ParseOverflow();
  bool ParsePadding();
  bool ParseQuotes();
  bool ParseSize();
  bool ParseTextAlign(nsCSSValue& aValue,
                      const KTableValue aTable[]);
  bool ParseTextAlign(nsCSSValue& aValue);
  bool ParseTextAlignLast(nsCSSValue& aValue);
  bool ParseTextDecoration();
  bool ParseTextDecorationLine(nsCSSValue& aValue);
  bool ParseTextCombineUpright(nsCSSValue& aValue);
  bool ParseTextOverflow(nsCSSValue& aValue);
  bool ParseTouchAction(nsCSSValue& aValue);

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

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

  /**
   * 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(nsCSSProperty aPropID, const nsCSSValue& aValue);
  bool ParseBoxProperties(const nsCSSProperty aPropIDs[]);
  bool ParseGroupedBoxProperty(int32_t aVariantMask,
                               nsCSSValue& aValue);
  bool ParseBoxCornerRadius(const nsCSSProperty aPropID);
  bool ParseBoxCornerRadiiInternals(nsCSSValue array[]);
  bool ParseBoxCornerRadii(const nsCSSProperty aPropIDs[]);

  int32_t ParseChoice(nsCSSValue aValues[],
                      const nsCSSProperty aPropIDs[], int32_t aNumIDs);
  bool ParseColor(nsCSSValue& aValue);
  bool ParseNumberColorComponent(uint8_t& aComponent, char aStop);
  bool ParsePercentageColorComponent(float& aComponent, char aStop);
  // ParseHSLColor parses everything starting with the opening '('
  // up through and including the aStop char.
  bool ParseHSLColor(float& aHue, float& aSaturation, float& aLightness,
                     char aStop);
  // ParseColorOpacity will enforce that the color ends with a ')'
  // after the opacity
  bool ParseColorOpacity(uint8_t& aOpacity);
  bool ParseColorOpacity(float& aOpacity);
  bool ParseEnum(nsCSSValue& aValue,
                 const KTableValue aKeywordTable[]);
  bool ParseVariant(nsCSSValue& aValue,
                    int32_t aVariantMask,
                    const KTableValue aKeywordTable[]);
  bool ParseVariantWithRestrictions(nsCSSValue& aValue,
                                    int32_t aVariantMask,
                                    const KTableValue aKeywordTable[],
                                    uint32_t aRestrictions);
  bool ParseNonNegativeVariant(nsCSSValue& aValue,
                               int32_t aVariantMask,
                               const KTableValue aKeywordTable[]);
  bool ParseOneOrLargerVariant(nsCSSValue& aValue,
                               int32_t aVariantMask,
                               const KTableValue aKeywordTable[]);
  bool ParseNonNegativeInteger(nsCSSValue& aValue)
  {
    return ParseNonNegativeVariant(aValue, VARIANT_INTEGER, 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::KTableValue,
  // to avoid duplicating them.
  bool ParseCustomIdent(nsCSSValue& aValue,
                        const nsAutoString& aIdentValue,
                        const nsCSSKeyword aExcludedKeywords[] = nullptr,
                        const nsCSSProps::KTableValue 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, int32_t aVariantMask,
                            float aNumber, const nsString& aUnit);
  bool ParseImageOrientation(nsCSSValue& aAngle);
  bool ParseImageRect(nsCSSValue& aImage);
  bool ParseElement(nsCSSValue& aValue);
  bool ParseColorStop(nsCSSValueGradient* aGradient);
  bool ParseLinearGradient(nsCSSValue& aValue, bool aIsRepeating,
                           bool aIsLegacy);
  bool ParseRadialGradient(nsCSSValue& aValue, bool aIsRepeating,
                           bool aIsLegacy);
  bool IsLegacyGradientLine(const nsCSSTokenType& aType,
                            const nsString& aId);
  bool ParseGradientColorStops(nsCSSValueGradient* aGradient,
                               nsCSSValue& aValue);

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

  /* Functions for basic shapes */
  bool ParseBasicShape(nsCSSValue& aValue, bool* aConsumedTokens);
  bool ParsePolygonFunction(nsCSSValue& aValue);
  bool ParseCircleOrEllipseFunction(nsCSSKeyword, nsCSSValue& aValue);
  bool ParseInsetFunction(nsCSSValue& aValue);

  /* Functions for transform Parsing */
  bool ParseSingleTransform(bool aIsPrefixed, nsCSSValue& aValue);
  bool ParseFunction(nsCSSKeyword aFunction, const int32_t aAllowedTypes[],
                     int32_t aVariantMaskAll, uint16_t aMinElems,
                     uint16_t aMaxElems, nsCSSValue &aValue);
  bool ParseFunctionInternals(const int32_t aVariantMask[],
                              int32_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
  nsRefPtr<CSSStyleSheet> mSheet;

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

  // 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.
  SheetParsingMode mParsingMode : 3;

  // True if we are in parsing rules for Chrome or Certified App content,
  // in which case CSS properties with the
  // CSS_PROPERTY_ALWAYS_ENABLED_IN_CHROME_OR_CERTIFIED_APP
  // flag should be allowed.
  bool mIsChromeOrCertifiedApp : 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<nsRefPtr<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;

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_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),
    mSection(eCSSSection_Charset),
    mNameSpaceMap(nullptr),
    mHavePushBack(false),
    mNavQuirkMode(false),
    mHashlessColorQuirk(false),
    mUnitlessLengthQuirk(false),
    mParsingMode(eAuthorSheetFeatures),
    mIsChromeOrCertifiedApp(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,
                          SheetParsingMode aParsingMode)
{
  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 = aParsingMode;
  mIsChromeOrCertifiedApp =
    dom::IsChromeURI(aSheetURI) ||
    aSheetPrincipal->GetAppStatus() == nsIPrincipal::APP_STATUS_CERTIFIED;

  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 = eAuthorSheetFeatures;
  mIsChromeOrCertifiedApp = false;

  // XXX check for low level errors
  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('_');

}

nsresult
CSSParserImpl::ParseStyleAttribute(const nsAString& aAttributeValue,
                                   nsIURI*          aDocURI,
                                   nsIURI*          aBaseURI,
                                   nsIPrincipal*    aNodePrincipal,
                                   css::StyleRule** aResult)
{
  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;

  css::Declaration* declaration = ParseDeclarationBlock(parseFlags);
  if (declaration) {
    // Create a style rule for the declaration
    NS_ADDREF(*aResult = new css::StyleRule(nullptr, declaration, 0, 0));
  } else {
    *aResult = nullptr;
  }

  ReleaseScanner();

  // XXX check for low level errors
  return NS_OK;
}

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::ParseProperty(const nsCSSProperty 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) ||
        (AgentRulesEnabled() &&
         nsCSSProps::PropHasFlags(aPropID,
                                  CSS_PROPERTY_ALWAYS_ENABLED_IN_UA_SHEETS)))) {
    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,
                                          PropertyEnabledState(), 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->AddVariableDeclaration(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;

  bool hitError = false;
  for (;;) {
    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();
    }

    if (!ParseNonNegativeVariant(value, VARIANT_LPCALC, nullptr)) {
      hitError = true;
      break;
    }

    aQueries.AppendElement(query.forget());
    aValues.AppendElement(value);

    if (!GetToken(true)) {
      // Expected EOF
      break;
    }

    if (eCSSToken_Symbol != mToken.mType || mToken.mSymbol != ',') {
      REPORT_UNEXPECTED_TOKEN(PEParseSourceSizeListNotComma);
      hitError = true;
      break;
    }
  }

  if (hitError) {
    // Per spec, a parse failure in this list invalidates it
    // entirely. Currently, this grammar is specified standalone and not part of
    // any larger grammar, so it doesn't make sense to try to advance the token
    // beyond it.
    OUTPUT_ERROR();
  }

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

  return !hitError;
}

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) && !GetToken(true);

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

  ReleaseScanner();
  return colorParsed;
}

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

  nsRefPtr<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)
{
  nsCSSProperty 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:
      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_Symbol_Minus:
    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.
  nsAutoTArray<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;
            }
            // 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:
        NS_NOTREACHED("unexpected token type");
        // fall through
      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(
                                            nsCSSProperty aPropertyID,
                                            nsCSSProperty 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();
  }

  nsCSSProperty 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(PEValueWithVariablesParsingError, propName);
      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")) {
    if (UserRulesEnabled()) {
      parseFunc = &CSSParserImpl::ParseMozDocumentRule;
      newSection = eCSSSection_General;
    } else {
      REPORT_UNEXPECTED_TOKEN(PEMozDocumentRuleNotAllowed);
      OUTPUT_ERROR();
      return SkipAtRule(aInAtRule);
    }

  } 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) &&
              mToken.mIdent.LowerCaseEqualsLiteral("-moz-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 = do_GetAtom(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);
  const char16_t *featureString;
  if (StringBeginsWith(mToken.mIdent, NS_LITERAL_STRING("min-"))) {
    expr->mRange = nsMediaExpression::eMin;
    featureString = mToken.mIdent.get() + 4;
  } else if (StringBeginsWith(mToken.mIdent, NS_LITERAL_STRING("max-"))) {
    expr->mRange = nsMediaExpression::eMax;
    featureString = mToken.mIdent.get() + 4;
  } else {
    expr->mRange = nsMediaExpression::eEqual;
    featureString = mToken.mIdent.get();
  }

  nsCOMPtr<nsIAtom> mediaFeatureAtom = do_GetAtom(featureString);
  const nsMediaFeature *feature = nsMediaFeatures::features;
  for (; feature->mName; ++feature) {
    if (*(feature->mName) == mediaFeatureAtom) {
      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 = ParseNonNegativeVariant(expr->mValue, VARIANT_LENGTH, nullptr);
      break;
    case nsMediaFeature::eInteger:
    case nsMediaFeature::eBoolInteger:
      rv = ParseNonNegativeVariant(expr->mValue, VARIANT_INTEGER, nullptr);
      // Enforce extra restrictions for eBoolInteger
      if (rv &&
          feature->mValueType == nsMediaFeature::eBoolInteger &&
          expr->mValue.GetIntValue() > 1)
        rv = false;
      break;
    case nsMediaFeature::eFloat:
      rv = ParseNonNegativeVariant(expr->mValue, VARIANT_NUMBER, nullptr);
      break;
    case nsMediaFeature::eIntRatio:
      {
        // Two integers separated by '/', with optional whitespace on
        // either side of the '/'.
        nsRefPtr<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 = ParseVariant(a->Item(0), VARIANT_INTEGER, nullptr) &&
             a->Item(0).GetIntValue() > 0 &&
             ExpectSymbol('/', true) &&
             ParseVariant(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 = ParseVariant(expr->mValue, VARIANT_KEYWORD,
                        feature->mData.mKeywordTable);
      break;
    case nsMediaFeature::eIdent:
      rv = ParseVariant(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)
{
  nsRefPtr<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)
{
  nsRefPtr<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);
  }
}

// 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)
{
  nsRefPtr<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
    nsRefPtr<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));

  nsRefPtr<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 = do_GetAtom(aPrefix);
  }

  nsRefPtr<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;
  }

  nsRefPtr<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) {
    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;
  }

  nsRefPtr<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+]
  nsAutoTArray<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
    nsAutoTArray<uint32_t,4>   featureSelectors;

    nsCSSValue intValue;
    while (ParseNonNegativeVariant(intValue, VARIANT_INTEGER, nullptr)) {
      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;
  }

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

  while (!ExpectSymbol('}', true)) {
    nsRefPtr<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;
  nsAutoPtr<css::Declaration> declaration(
                                ParseDeclarationBlock(parseFlags,
                                                      eCSSContext_Page));
  mViewportUnitsEnabled = true;

  if (!declaration) {
    return false;
  }

  // Takes ownership of declaration.
  nsRefPtr<nsCSSPageRule> rule = new nsCSSPageRule(Move(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;
  nsAutoPtr<css::Declaration> declaration(ParseDeclarationBlock(parseFlags));
  if (!declaration) {
    return nullptr;
  }

  // Takes ownership of declaration, and steals contents of selectorList.
  nsRefPtr<nsCSSKeyframeRule> rule =
    new nsCSSKeyframeRule(selectorList, Move(declaration), 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;
        }
        // fall through
      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);

  nsRefPtr<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)
{
  mInSupportsCondition = true;

  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();
    mInSupportsCondition = false;
    return result;
  }

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

  REPORT_UNEXPECTED_TOKEN(PESupportsConditionExpectedStart);
  mInSupportsCondition = false;
  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*
//   | 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 (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_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;
      }

      nsCSSProperty 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;
  }

  nsRefPtr<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 (!ParseVariant(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 (!ParseVariant(first, VARIANT_COUNTER_SYMBOL, nullptr)) {
        return false;
      }
      if (!ParseVariant(second, VARIANT_COUNTER_SYMBOL, nullptr)) {
        aValue = first;
      } else {
        aValue.SetPairValue(first, second);
      }
      return true;
    }

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

    case eCSSCounterDesc_Range: {
      if (ParseVariant(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 (!ParseVariant(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 (!ParseVariant(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 (!ParseVariant(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 (ParseVariant(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 (!ParseVariant(lower, VARIANT_BOUND, nsCSSProps::kCounterRangeKTable) ||
      !ParseVariant(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;
  nsAutoTArray<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;
  nsAutoTArray<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;
  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

  nsRefPtr<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);
        bool gotClosingBracket;
        if (! GetToken(true)) { // 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) {
          bool isCaseSensitive = true;

          // 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 (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)) {
                isCaseSensitive = false;
                break;
              }
            }
          }
          aDataMask |= SEL_MASK_ATTRIB;
          aSelector.AddAttribute(nameSpaceID, attr, func, value, isCaseSensitive);
        }
        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,
                                   nsCSSPseudoElements::Type* 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 = do_GetAtom(buffer);

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

  if (!AgentRulesEnabled() &&
      ((pseudoElementType < nsCSSPseudoElements::ePseudo_PseudoElementCount &&
        nsCSSPseudoElements::PseudoElementIsUASheetOnly(pseudoElementType)) ||
       (pseudoClassType != nsCSSPseudoClasses::ePseudoClass_NotPseudoClass &&
        nsCSSPseudoClasses::PseudoClassIsUASheetOnly(pseudoClassType)))) {
    // This pseudo-element or pseudo-class is not exposed to content.
    REPORT_UNEXPECTED_TOKEN(PEPseudoSelUnknown);
    UngetToken();
    return eSelectorParsingStatus_Error;
  }

  // We currently allow :-moz-placeholder and ::-moz-placeholder. We have to
  // be a bit stricter regarding the pseudo-element parsing rules.
  if (pseudoElementType == nsCSSPseudoElements::ePseudo_mozPlaceholder &&
      pseudoClassType == nsCSSPseudoClasses::ePseudoClass_mozPlaceholder) {
    if (parsingPseudoElement) {
      pseudoClassType = nsCSSPseudoClasses::ePseudoClass_NotPseudoClass;
    } else {
      pseudoElementType = nsCSSPseudoElements::ePseudo_NotPseudoElement;
    }
  }

#ifdef MOZ_XUL
  isTreePseudo = (pseudoElementType == nsCSSPseudoElements::ePseudo_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 < nsCSSPseudoElements::ePseudo_PseudoElementCount);
  // anonymous boxes are only allowed if they're the tree boxes or we have
  // enabled agent rules
  bool isAnonBox = isTreePseudo ||
    (pseudoElementType == nsCSSPseudoElements::ePseudo_AnonBox &&
     AgentRulesEnabled());
  bool isPseudoClass =
    (pseudoClassType != nsCSSPseudoClasses::ePseudoClass_NotPseudoClass);

  NS_ASSERTION(!isPseudoClass ||
               pseudoElementType == nsCSSPseudoElements::ePseudo_NotPseudoElement,
               "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
       nsCSSPseudoClasses::ePseudoClass_notPseudo == 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 (!parsingPseudoElement &&
      nsCSSPseudoClasses::ePseudoClass_notPseudo == 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) {
    if (aSelector.IsPseudoElement()) {
      nsCSSPseudoElements::Type type = aSelector.PseudoType();
      if (!nsCSSPseudoElements::PseudoElementSupportsUserActionState(type)) {
        // We only allow user action pseudo-classes on certain pseudo-elements.
        REPORT_UNEXPECTED_TOKEN(PEPseudoSelNoUserActionPC);
        UngetToken();
        return eSelectorParsingStatus_Error;
      }
      if (!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;
      }
    }
    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,
                                            nsCSSPseudoClasses::Type 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 -moz-dir take an identifier argument.  While
  // only 'ltr' and 'rtl' (case-insensitively) will match anything, any
  // other identifier is still valid.
  if (aType == nsCSSPseudoClasses::ePseudoClass_mozLocaleDir ||
      aType == nsCSSPseudoClasses::ePseudoClass_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,
                                              nsCSSPseudoClasses::Type 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;
    }
  }

  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 (StringBeginsWith(mToken.mIdent, NS_LITERAL_STRING("n-"))) {
      truncAt = 1;
    } else if (StringBeginsWith(mToken.mIdent, 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 && StringBeginsWith(mToken.mIdent, 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,
                                                   nsCSSPseudoClasses::Type 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;
  nsCSSPseudoElements::Type pseudoElementType =
    nsCSSPseudoElements::ePseudo_NotPseudoElement;

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

  while (parsingStatus == eSelectorParsingStatus_Continue) {
    if (eCSSToken_ID == mToken.mType) { // #id
      parsingStatus = ParseIDSelector(dataMask, *selector);
    }
    else if (mToken.IsSymbol('.')) {    // .class
      parsingStatus = ParseClassSelector(dataMask, *selector);
    }
    else if (mToken.IsSymbol(':')) {    // :pseudo
      parsingStatus = ParsePseudoSelector(dataMask, *selector, false,
                                          getter_AddRefs(pseudoElement),
                                          getter_Transfers(pseudoElementArgs),
                                          &pseudoElementType);
      if (pseudoElement &&
          pseudoElementType != nsCSSPseudoElements::ePseudo_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 (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 == nsCSSPseudoElements::ePseudo_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;
}

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;
    }
  }
  css::Declaration* declaration = new css::Declaration();
  mData.AssertInitialState();
  for (;;) {
    bool changed;
    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;
}

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

  nsCSSToken* tk = &mToken;
  nscolor rgba;
  switch (tk->mType) {
    case eCSSToken_ID:
    case eCSSToken_Hash:
      // #xxyyzz
      if (NS_HexToRGB(tk->mIdent, &rgba)) {
        MOZ_ASSERT(tk->mIdent.Length() == 3 || tk->mIdent.Length() == 6,
                   "unexpected hex color length");
        nsCSSUnit unit = tk->mIdent.Length() == 3 ?
                           eCSSUnit_ShortHexColor :
                           eCSSUnit_HexColor;
        aValue.SetIntegerColorValue(rgba, unit);
        return true;
      }
      break;

    case eCSSToken_Ident:
      if (NS_ColorNameToRGB(tk->mIdent, &rgba)) {
        aValue.SetStringValue(tk->mIdent, eCSSUnit_Ident);
        return true;
      }
      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 true;
          }
        }
      }
      break;
    case eCSSToken_Function:
      if (mToken.mIdent.LowerCaseEqualsLiteral("rgb")) {
        // rgb ( component , component , component )
        if (GetToken(true)) {
          UngetToken();
        }
        if (mToken.mType == eCSSToken_Number) {
          uint8_t r, g, b;
          if (ParseNumberColorComponent(r, ',') &&
              ParseNumberColorComponent(g, ',') &&
              ParseNumberColorComponent(b, ')')) {