diff options
Diffstat (limited to 'layout/style/nsCSSParser.cpp')
-rw-r--r-- | layout/style/nsCSSParser.cpp | 18324 |
1 files changed, 18324 insertions, 0 deletions
diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp new file mode 100644 index 000000000..1108ce5b5 --- /dev/null +++ b/layout/style/nsCSSParser.cpp @@ -0,0 +1,18324 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=78: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* parsing of CSS stylesheets, based on a token stream from the CSS scanner */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Move.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/TypedEnumBits.h" + +#include <algorithm> // for std::stable_sort +#include <limits> // for std::numeric_limits + +#include "nsCSSParser.h" +#include "nsAlgorithm.h" +#include "nsCSSProps.h" +#include "nsCSSKeywords.h" +#include "nsCSSScanner.h" +#include "mozilla/css/ErrorReporter.h" +#include "mozilla/css/Loader.h" +#include "mozilla/css/StyleRule.h" +#include "mozilla/css/ImportRule.h" +#include "nsCSSRules.h" +#include "mozilla/css/NameSpaceRule.h" +#include "nsTArray.h" +#include "mozilla/StyleSheetInlines.h" +#include "mozilla/css/Declaration.h" +#include "nsStyleConsts.h" +#include "nsNetUtil.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsIAtom.h" +#include "nsColor.h" +#include "nsCSSPseudoClasses.h" +#include "nsCSSPseudoElements.h" +#include "nsCSSAnonBoxes.h" +#include "nsNameSpaceManager.h" +#include "nsXMLNameSpaceMap.h" +#include "nsError.h" +#include "nsIMediaList.h" +#include "nsStyleUtil.h" +#include "nsIPrincipal.h" +#include "nsICSSUnprefixingService.h" +#include "mozilla/Sprintf.h" +#include "nsContentUtils.h" +#include "nsAutoPtr.h" +#include "CSSCalc.h" +#include "nsMediaFeatures.h" +#include "nsLayoutUtils.h" +#include "mozilla/Preferences.h" +#include "nsRuleData.h" +#include "mozilla/CSSVariableValues.h" +#include "mozilla/dom/AnimationEffectReadOnlyBinding.h" +#include "mozilla/dom/URL.h" +#include "gfxFontFamilyList.h" + +using namespace mozilla; +using namespace mozilla::css; + +typedef nsCSSProps::KTableEntry KTableEntry; + +// pref-backed bool values (hooked up in nsCSSParser::Startup) +static bool sOpentypeSVGEnabled; +static bool sWebkitPrefixedAliasesEnabled; +static bool sWebkitDevicePixelRatioEnabled; +static bool sUnprefixingServiceEnabled; +#ifdef NIGHTLY_BUILD +static bool sUnprefixingServiceGloballyWhitelisted; +#endif +static bool sMozGradientsEnabled; +static bool sControlCharVisibility; + +const uint32_t +nsCSSProps::kParserVariantTable[eCSSProperty_COUNT_no_shorthands] = { +#define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, \ + stylestruct_, stylestructoffset_, animtype_) \ + parsevariant_, +#define CSS_PROP_LIST_INCLUDE_LOGICAL +#include "nsCSSPropList.h" +#undef CSS_PROP_LIST_INCLUDE_LOGICAL +#undef CSS_PROP +}; + +// Maximum number of repetitions for the repeat() function +// in the grid-template-rows and grid-template-columns properties, +// to limit high memory usage from small stylesheets. +// Must be a positive integer. Should be large-ish. +#define GRID_TEMPLATE_MAX_REPETITIONS 10000 + +// End-of-array marker for mask arguments to ParseBitmaskValues +#define MASK_END_VALUE (-1) + +enum class CSSParseResult : int32_t { + // Parsed something successfully: + Ok, + // Did not find what we were looking for, but did not consume any token: + NotFound, + // Unexpected token or token value, too late for UngetToken() to be enough: + Error +}; + +enum class GridTrackSizeFlags { + eDefaultTrackSize = 0x0, + eFixedTrackSize = 0x1, // parse a <fixed-size> instead of <track-size> +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(GridTrackSizeFlags) + +enum class GridTrackListFlags { + eDefaultTrackList = 0x0, // parse a <track-list> + eExplicitTrackList = 0x1, // parse an <explicit-track-list> instead +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(GridTrackListFlags) + +namespace { + +// Rule processing function +typedef void (* RuleAppendFunc) (css::Rule* aRule, void* aData); +static void AssignRuleToPointer(css::Rule* aRule, void* aPointer); +static void AppendRuleToSheet(css::Rule* aRule, void* aParser); + +struct CSSParserInputState { + nsCSSScannerPosition mPosition; + nsCSSToken mToken; + bool mHavePushBack; +}; + +static_assert(css::eAuthorSheetFeatures == 0 && + css::eUserSheetFeatures == 1 && + css::eAgentSheetFeatures == 2, + "sheet parsing mode constants won't fit " + "in CSSParserImpl::mParsingMode"); + +// Your basic top-down recursive descent style parser +// The exposed methods and members of this class are precisely those +// needed by nsCSSParser, far below. +class CSSParserImpl { +public: + CSSParserImpl(); + ~CSSParserImpl(); + + nsresult SetStyleSheet(CSSStyleSheet* aSheet); + + nsIDocument* GetDocument(); + + nsresult SetQuirkMode(bool aQuirkMode); + + nsresult SetChildLoader(mozilla::css::Loader* aChildLoader); + + // Clears everything set by the above Set*() functions. + void Reset(); + + nsresult ParseSheet(const nsAString& aInput, + nsIURI* aSheetURI, + nsIURI* aBaseURI, + nsIPrincipal* aSheetPrincipal, + uint32_t aLineNumber, + css::LoaderReusableStyleSheets* aReusableSheets); + + already_AddRefed<css::Declaration> + ParseStyleAttribute(const nsAString& aAttributeValue, + nsIURI* aDocURL, + nsIURI* aBaseURL, + nsIPrincipal* aNodePrincipal); + + nsresult ParseDeclarations(const nsAString& aBuffer, + nsIURI* aSheetURL, + nsIURI* aBaseURL, + nsIPrincipal* aSheetPrincipal, + css::Declaration* aDeclaration, + bool* aChanged); + + nsresult ParseRule(const nsAString& aRule, + nsIURI* aSheetURL, + nsIURI* aBaseURL, + nsIPrincipal* aSheetPrincipal, + css::Rule** aResult); + + void ParseProperty(const nsCSSPropertyID aPropID, + const nsAString& aPropValue, + nsIURI* aSheetURL, + nsIURI* aBaseURL, + nsIPrincipal* aSheetPrincipal, + css::Declaration* aDeclaration, + bool* aChanged, + bool aIsImportant, + bool aIsSVGMode); + void ParseLonghandProperty(const nsCSSPropertyID aPropID, + const nsAString& aPropValue, + nsIURI* aSheetURL, + nsIURI* aBaseURL, + nsIPrincipal* aSheetPrincipal, + nsCSSValue& aValue); + + bool ParseTransformProperty(const nsAString& aPropValue, + bool aDisallowRelativeValues, + nsCSSValue& aResult); + + void ParseMediaList(const nsSubstring& aBuffer, + nsIURI* aURL, // for error reporting + uint32_t aLineNumber, // for error reporting + nsMediaList* aMediaList, + bool aHTMLMode); + + bool ParseSourceSizeList(const nsAString& aBuffer, + nsIURI* aURI, // for error reporting + uint32_t aLineNumber, // for error reporting + InfallibleTArray< nsAutoPtr<nsMediaQuery> >& aQueries, + InfallibleTArray<nsCSSValue>& aValues, + bool aHTMLMode); + + void ParseVariable(const nsAString& aVariableName, + const nsAString& aPropValue, + nsIURI* aSheetURL, + nsIURI* aBaseURL, + nsIPrincipal* aSheetPrincipal, + css::Declaration* aDeclaration, + bool* aChanged, + bool aIsImportant); + + bool ParseFontFamilyListString(const nsSubstring& aBuffer, + nsIURI* aURL, // for error reporting + uint32_t aLineNumber, // for error reporting + nsCSSValue& aValue); + + bool ParseColorString(const nsSubstring& aBuffer, + nsIURI* aURL, // for error reporting + uint32_t aLineNumber, // for error reporting + nsCSSValue& aValue, + bool aSuppressErrors /* false */); + + bool ParseMarginString(const nsSubstring& aBuffer, + nsIURI* aURL, // for error reporting + uint32_t aLineNumber, // for error reporting + nsCSSValue& aValue, + bool aSuppressErrors /* false */); + + nsresult ParseSelectorString(const nsSubstring& aSelectorString, + nsIURI* aURL, // for error reporting + uint32_t aLineNumber, // for error reporting + nsCSSSelectorList **aSelectorList); + + already_AddRefed<nsCSSKeyframeRule> + ParseKeyframeRule(const nsSubstring& aBuffer, + nsIURI* aURL, + uint32_t aLineNumber); + + bool ParseKeyframeSelectorString(const nsSubstring& aSelectorString, + nsIURI* aURL, // for error reporting + uint32_t aLineNumber, // for error reporting + InfallibleTArray<float>& aSelectorList); + + bool EvaluateSupportsDeclaration(const nsAString& aProperty, + const nsAString& aValue, + nsIURI* aDocURL, + nsIURI* aBaseURL, + nsIPrincipal* aDocPrincipal); + + bool EvaluateSupportsCondition(const nsAString& aCondition, + nsIURI* aDocURL, + nsIURI* aBaseURL, + nsIPrincipal* aDocPrincipal); + + bool ParseCounterStyleName(const nsAString& aBuffer, + nsIURI* aURL, + nsAString& aName); + + bool ParseCounterDescriptor(nsCSSCounterDesc aDescID, + const nsAString& aBuffer, + nsIURI* aSheetURL, + nsIURI* aBaseURL, + nsIPrincipal* aSheetPrincipal, + nsCSSValue& aValue); + + bool ParseFontFaceDescriptor(nsCSSFontDesc aDescID, + const nsAString& aBuffer, + nsIURI* aSheetURL, + nsIURI* aBaseURL, + nsIPrincipal* aSheetPrincipal, + nsCSSValue& aValue); + + bool IsValueValidForProperty(const nsCSSPropertyID aPropID, + const nsAString& aPropValue); + + typedef nsCSSParser::VariableEnumFunc VariableEnumFunc; + + /** + * Parses a CSS token stream value and invokes a callback function for each + * variable reference that is encountered. + * + * @param aPropertyValue The CSS token stream value. + * @param aFunc The callback function to invoke; its parameters are the + * variable name found and the aData argument passed in to this function. + * @param aData User data to pass in to the callback. + * @return Whether aPropertyValue could be parsed as a valid CSS token stream + * value (e.g., without syntactic errors in variable references). + */ + bool EnumerateVariableReferences(const nsAString& aPropertyValue, + VariableEnumFunc aFunc, + void* aData); + + /** + * Parses aPropertyValue as a CSS token stream value and resolves any + * variable references using the variables in aVariables. + * + * @param aPropertyValue The CSS token stream value. + * @param aVariables The set of variable values to use when resolving variable + * references. + * @param aResult Out parameter that gets the resolved value. + * @param aFirstToken Out parameter that gets the type of the first token in + * aResult. + * @param aLastToken Out parameter that gets the type of the last token in + * aResult. + * @return Whether aResult could be parsed successfully and variable reference + * substitution succeeded. + */ + bool ResolveVariableValue(const nsAString& aPropertyValue, + const CSSVariableValues* aVariables, + nsString& aResult, + nsCSSTokenSerializationType& aFirstToken, + nsCSSTokenSerializationType& aLastToken); + + /** + * Parses a string as a CSS token stream value for particular property, + * resolving any variable references. The parsed property value is stored + * in the specified nsRuleData object. If aShorthandPropertyID has a value + * other than eCSSProperty_UNKNOWN, this is the property that will be parsed; + * otherwise, aPropertyID will be parsed. Either way, only aPropertyID, + * a longhand property, will be copied over to the rule data. + * + * If the property cannot be parsed, it will be treated as if 'initial' or + * 'inherit' were specified, for non-inherited and inherited properties + * respectively. + * + * @param aPropertyID The ID of the longhand property whose value is to be + * copied to the rule data. + * @param aShorthandPropertyID The ID of the shorthand property to be parsed. + * If a longhand property is to be parsed, aPropertyID is that property, + * and aShorthandPropertyID must be eCSSProperty_UNKNOWN. + * @param aValue The CSS token stream value. + * @param aVariables The set of variable values to use when resolving variable + * references. + * @param aRuleData The rule data object into which parsed property value for + * aPropertyID will be stored. + */ + void ParsePropertyWithVariableReferences(nsCSSPropertyID aPropertyID, + nsCSSPropertyID aShorthandPropertyID, + const nsAString& aValue, + const CSSVariableValues* aVariables, + nsRuleData* aRuleData, + nsIURI* aDocURL, + nsIURI* aBaseURL, + nsIPrincipal* aDocPrincipal, + CSSStyleSheet* aSheet, + uint32_t aLineNumber, + uint32_t aLineOffset); + + bool AgentRulesEnabled() const { + return mParsingMode == css::eAgentSheetFeatures; + } + bool ChromeRulesEnabled() const { + return mIsChrome; + } + bool UserRulesEnabled() const { + return mParsingMode == css::eAgentSheetFeatures || + mParsingMode == css::eUserSheetFeatures; + } + + CSSEnabledState EnabledState() const { + static_assert(int(CSSEnabledState::eForAllContent) == 0, + "CSSEnabledState::eForAllContent should be zero for " + "this bitfield to work"); + CSSEnabledState enabledState = CSSEnabledState::eForAllContent; + if (AgentRulesEnabled()) { + enabledState |= CSSEnabledState::eInUASheets; + } + if (mIsChrome) { + enabledState |= CSSEnabledState::eInChrome; + } + return enabledState; + } + + nsCSSPropertyID LookupEnabledProperty(const nsAString& aProperty) { + return nsCSSProps::LookupProperty(aProperty, EnabledState()); + } + +protected: + class nsAutoParseCompoundProperty; + friend class nsAutoParseCompoundProperty; + + class nsAutoFailingSupportsRule; + friend class nsAutoFailingSupportsRule; + + class nsAutoSuppressErrors; + friend class nsAutoSuppressErrors; + + void AppendRule(css::Rule* aRule); + friend void AppendRuleToSheet(css::Rule*, void*); // calls AppendRule + + /** + * This helper class automatically calls SetParsingCompoundProperty in its + * constructor and takes care of resetting it to false in its destructor. + */ + class nsAutoParseCompoundProperty { + public: + explicit nsAutoParseCompoundProperty(CSSParserImpl* aParser) : mParser(aParser) + { + NS_ASSERTION(!aParser->IsParsingCompoundProperty(), + "already parsing compound property"); + NS_ASSERTION(aParser, "Null parser?"); + aParser->SetParsingCompoundProperty(true); + } + + ~nsAutoParseCompoundProperty() + { + mParser->SetParsingCompoundProperty(false); + } + private: + CSSParserImpl* mParser; + }; + + /** + * This helper class conditionally sets mInFailingSupportsRule to + * true if aCondition = false, and resets it to its original value in its + * destructor. If we are already somewhere within a failing @supports + * rule, passing in aCondition = true does not change mInFailingSupportsRule. + */ + class nsAutoFailingSupportsRule { + public: + nsAutoFailingSupportsRule(CSSParserImpl* aParser, + bool aCondition) + : mParser(aParser), + mOriginalValue(aParser->mInFailingSupportsRule) + { + if (!aCondition) { + mParser->mInFailingSupportsRule = true; + } + } + + ~nsAutoFailingSupportsRule() + { + mParser->mInFailingSupportsRule = mOriginalValue; + } + + private: + CSSParserImpl* mParser; + bool mOriginalValue; + }; + + /** + * Auto class to set aParser->mSuppressErrors to the specified value + * and restore it to its original value later. + */ + class nsAutoSuppressErrors { + public: + explicit nsAutoSuppressErrors(CSSParserImpl* aParser, + bool aSuppressErrors = true) + : mParser(aParser), + mOriginalValue(aParser->mSuppressErrors) + { + mParser->mSuppressErrors = aSuppressErrors; + } + + ~nsAutoSuppressErrors() + { + mParser->mSuppressErrors = mOriginalValue; + } + + private: + CSSParserImpl* mParser; + bool mOriginalValue; + }; + + /** + * RAII class to set aParser->mInSupportsCondition to true and restore it + * to false later. + */ + class MOZ_RAII nsAutoInSupportsCondition + { + public: + explicit nsAutoInSupportsCondition(CSSParserImpl* aParser) + : mParser(aParser) + { + MOZ_ASSERT(!aParser->mInSupportsCondition, + "nsAutoInSupportsCondition is not designed to be used " + "re-entrantly"); + mParser->mInSupportsCondition = true; + } + + ~nsAutoInSupportsCondition() + { + mParser->mInSupportsCondition = false; + } + + private: + CSSParserImpl* const mParser; + }; + + // the caller must hold on to aString until parsing is done + void InitScanner(nsCSSScanner& aScanner, + css::ErrorReporter& aReporter, + nsIURI* aSheetURI, nsIURI* aBaseURI, + nsIPrincipal* aSheetPrincipal); + void ReleaseScanner(void); + + /** + * This is a RAII class which behaves like an "AutoRestore<>" for our parser + * input state. When instantiated, this class saves the current parser input + * state (in a CSSParserInputState object), and it restores the parser to + * that state when destructed, unless "DoNotRestore()" has been called. + */ + class MOZ_RAII nsAutoCSSParserInputStateRestorer { + public: + explicit nsAutoCSSParserInputStateRestorer(CSSParserImpl* aParser + MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + : mParser(aParser), + mShouldRestore(true) + { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + mParser->SaveInputState(mSavedState); + } + + void DoNotRestore() + { + mShouldRestore = false; + } + + ~nsAutoCSSParserInputStateRestorer() + { + if (mShouldRestore) { + mParser->RestoreSavedInputState(mSavedState); + } + } + + private: + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER + CSSParserImpl* mParser; + CSSParserInputState mSavedState; + bool mShouldRestore; + }; + + /** + * This is a RAII class which creates a temporary nsCSSScanner for the given + * string, and reconfigures aParser to use *that* scanner instead of its + * existing scanner, until we go out of scope. (This allows us to rewrite + * a portion of a stylesheet using a temporary string, and switch to parsing + * that rewritten section, and then resume parsing the original stylesheet.) + * + * aParser must have a non-null nsCSSScanner (which we'll be temporarily + * replacing) and ErrorReporter (which this class will co-opt for the + * temporary parser). While we're in scope, we also suppress error reporting, + * so it doesn't really matter which reporter we use. We suppress reporting + * because this class is only used with CSS that is synthesized & didn't + * come directly from an author, and it would be confusing if we reported + * syntax errors for CSS that an author didn't provide. + * + * XXXdholbert we could also change this & report errors, if needed. Might + * want to customize the error reporting somehow though. + */ + class MOZ_RAII nsAutoScannerChanger { + public: + nsAutoScannerChanger(CSSParserImpl* aParser, + const nsAString& aStringToScan + MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + : mParser(aParser), + mOriginalScanner(aParser->mScanner), + mStringScanner(aStringToScan, 0), + mParserStateRestorer(aParser), + mErrorSuppresser(aParser) + { + MOZ_ASSERT(mOriginalScanner, + "Shouldn't use nsAutoScannerChanger unless we already " + "have a scanner"); + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + + // Set & setup the new scanner: + mParser->mScanner = &mStringScanner; + mStringScanner.SetErrorReporter(mParser->mReporter); + + // We might've had push-back on our original scanner (and if we did, + // that fact is saved via mParserStateRestorer). But we don't have + // push-back in mStringScanner, so clear that flag. + mParser->mHavePushBack = false; + } + + ~nsAutoScannerChanger() + { + // Restore original scanner. All other cleanup is done by RAII members. + mParser->mScanner = mOriginalScanner; + } + + private: + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER + CSSParserImpl* mParser; + nsCSSScanner *mOriginalScanner; + nsCSSScanner mStringScanner; + nsAutoCSSParserInputStateRestorer mParserStateRestorer; + nsAutoSuppressErrors mErrorSuppresser; + }; + + + bool IsSVGMode() const { + return mScanner->IsSVGMode(); + } + + /** + * Saves the current input state, which includes any currently pushed + * back token, and the current position of the scanner. + */ + void SaveInputState(CSSParserInputState& aState); + + /** + * Restores the saved input state by pushing back any saved pushback + * token and calling RestoreSavedPosition on the scanner. + */ + void RestoreSavedInputState(const CSSParserInputState& aState); + + bool GetToken(bool aSkipWS); + void UngetToken(); + bool GetNextTokenLocation(bool aSkipWS, uint32_t *linenum, uint32_t *colnum); + void AssertNextTokenAt(uint32_t aLine, uint32_t aCol) + { + // Beware that this method will call GetToken/UngetToken (in + // GetNextTokenLocation) in DEBUG builds, but not in non-DEBUG builds. + DebugOnly<uint32_t> lineAfter, colAfter; + MOZ_ASSERT(GetNextTokenLocation(true, &lineAfter, &colAfter) && + lineAfter == aLine && colAfter == aCol, + "shouldn't have consumed any tokens"); + } + + bool ExpectSymbol(char16_t aSymbol, bool aSkipWS); + bool ExpectEndProperty(); + bool CheckEndProperty(); + nsSubstring* NextIdent(); + + // returns true when the stop symbol is found, and false for EOF + bool SkipUntil(char16_t aStopSymbol); + void SkipUntilOneOf(const char16_t* aStopSymbolChars); + // For each character in aStopSymbolChars from the end of the array + // to the start, calls SkipUntil with that character. + typedef AutoTArray<char16_t, 16> StopSymbolCharStack; + void SkipUntilAllOf(const StopSymbolCharStack& aStopSymbolChars); + // returns true if the stop symbol or EOF is found, and false for an + // unexpected ')', ']' or '}'; this not safe to call outside variable + // resolution, as it doesn't handle mismatched content + bool SkipBalancedContentUntil(char16_t aStopSymbol); + + void SkipRuleSet(bool aInsideBraces); + bool SkipAtRule(bool aInsideBlock); + bool SkipDeclaration(bool aCheckForBraces); + + void PushGroup(css::GroupRule* aRule); + void PopGroup(); + + bool ParseRuleSet(RuleAppendFunc aAppendFunc, void* aProcessData, + bool aInsideBraces = false); + bool ParseAtRule(RuleAppendFunc aAppendFunc, void* aProcessData, + bool aInAtRule); + bool ParseCharsetRule(RuleAppendFunc aAppendFunc, void* aProcessData); + bool ParseImportRule(RuleAppendFunc aAppendFunc, void* aProcessData); + bool ParseURLOrString(nsString& aURL); + bool GatherMedia(nsMediaList* aMedia, bool aInAtRule); + + enum eMediaQueryType { eMediaQueryNormal, + // Parsing an at rule + eMediaQueryAtRule, + // Attempt to consume a single media-condition and + // stop. Note that the spec defines "expression and/or + // expression" as one condition but "expression, + // expression" as two. + eMediaQuerySingleCondition }; + bool ParseMediaQuery(eMediaQueryType aMode, nsMediaQuery **aQuery, + bool *aHitStop); + bool ParseMediaQueryExpression(nsMediaQuery* aQuery); + void ProcessImport(const nsString& aURLSpec, + nsMediaList* aMedia, + RuleAppendFunc aAppendFunc, + void* aProcessData, + uint32_t aLineNumber, + uint32_t aColumnNumber); + bool ParseGroupRule(css::GroupRule* aRule, RuleAppendFunc aAppendFunc, + void* aProcessData); + bool ParseMediaRule(RuleAppendFunc aAppendFunc, void* aProcessData); + bool ParseMozDocumentRule(RuleAppendFunc aAppendFunc, void* aProcessData); + bool ParseNameSpaceRule(RuleAppendFunc aAppendFunc, void* aProcessData); + void ProcessNameSpace(const nsString& aPrefix, + const nsString& aURLSpec, RuleAppendFunc aAppendFunc, + void* aProcessData, + uint32_t aLineNumber, uint32_t aColumnNumber); + + bool ParseFontFaceRule(RuleAppendFunc aAppendFunc, void* aProcessData); + bool ParseFontFeatureValuesRule(RuleAppendFunc aAppendFunc, + void* aProcessData); + bool ParseFontFeatureValueSet(nsCSSFontFeatureValuesRule *aRule); + bool ParseFontDescriptor(nsCSSFontFaceRule* aRule); + bool ParseFontDescriptorValue(nsCSSFontDesc aDescID, + nsCSSValue& aValue); + + bool ParsePageRule(RuleAppendFunc aAppendFunc, void* aProcessData); + bool ParseKeyframesRule(RuleAppendFunc aAppendFunc, void* aProcessData); + already_AddRefed<nsCSSKeyframeRule> ParseKeyframeRule(); + bool ParseKeyframeSelectorList(InfallibleTArray<float>& aSelectorList); + + bool ParseSupportsRule(RuleAppendFunc aAppendFunc, void* aProcessData); + bool ParseSupportsCondition(bool& aConditionMet); + bool ParseSupportsConditionNegation(bool& aConditionMet); + bool ParseSupportsConditionInParens(bool& aConditionMet); + bool ParseSupportsMozBoolPrefName(bool& aConditionMet); + bool ParseSupportsConditionInParensInsideParens(bool& aConditionMet); + bool ParseSupportsConditionTerms(bool& aConditionMet); + enum SupportsConditionTermOperator { eAnd, eOr }; + bool ParseSupportsConditionTermsAfterOperator( + bool& aConditionMet, + SupportsConditionTermOperator aOperator); + + bool ParseCounterStyleRule(RuleAppendFunc aAppendFunc, void* aProcessData); + bool ParseCounterStyleName(nsAString& aName, bool aForDefinition); + bool ParseCounterStyleNameValue(nsCSSValue& aValue); + bool ParseCounterDescriptor(nsCSSCounterStyleRule *aRule); + bool ParseCounterDescriptorValue(nsCSSCounterDesc aDescID, + nsCSSValue& aValue); + bool ParseCounterRange(nsCSSValuePair& aPair); + + /** + * Parses the current input stream for a CSS token stream value and resolves + * any variable references using the variables in aVariables. + * + * @param aVariables The set of variable values to use when resolving variable + * references. + * @param aResult Out parameter that, if the function returns true, will be + * replaced with the resolved value. + * @return Whether aResult could be parsed successfully and variable reference + * substitution succeeded. + */ + bool ResolveValueWithVariableReferences( + const CSSVariableValues* aVariables, + nsString& aResult, + nsCSSTokenSerializationType& aResultFirstToken, + nsCSSTokenSerializationType& aResultLastToken); + // Helper function for ResolveValueWithVariableReferences. + bool ResolveValueWithVariableReferencesRec( + nsString& aResult, + nsCSSTokenSerializationType& aResultFirstToken, + nsCSSTokenSerializationType& aResultLastToken, + const CSSVariableValues* aVariables); + + enum nsSelectorParsingStatus { + // we have parsed a selector and we saw a token that cannot be + // part of a selector: + eSelectorParsingStatus_Done, + // we should continue parsing the selector: + eSelectorParsingStatus_Continue, + // we saw an unexpected token or token value, + // or we saw end-of-file with an unfinished selector: + eSelectorParsingStatus_Error + }; + nsSelectorParsingStatus ParseIDSelector(int32_t& aDataMask, + nsCSSSelector& aSelector); + + nsSelectorParsingStatus ParseClassSelector(int32_t& aDataMask, + nsCSSSelector& aSelector); + + // aPseudoElement and aPseudoElementArgs are the location where + // pseudo-elements (as opposed to pseudo-classes) are stored; + // pseudo-classes are stored on aSelector. aPseudoElement and + // aPseudoElementArgs must be non-null iff !aIsNegated. + nsSelectorParsingStatus ParsePseudoSelector(int32_t& aDataMask, + nsCSSSelector& aSelector, + bool aIsNegated, + nsIAtom** aPseudoElement, + nsAtomList** aPseudoElementArgs, + CSSPseudoElementType* aPseudoElementType); + + nsSelectorParsingStatus ParseAttributeSelector(int32_t& aDataMask, + nsCSSSelector& aSelector); + + nsSelectorParsingStatus ParseTypeOrUniversalSelector(int32_t& aDataMask, + nsCSSSelector& aSelector, + bool aIsNegated); + + nsSelectorParsingStatus ParsePseudoClassWithIdentArg(nsCSSSelector& aSelector, + CSSPseudoClassType aType); + + nsSelectorParsingStatus ParsePseudoClassWithNthPairArg(nsCSSSelector& aSelector, + CSSPseudoClassType aType); + + nsSelectorParsingStatus ParsePseudoClassWithSelectorListArg(nsCSSSelector& aSelector, + CSSPseudoClassType aType); + + nsSelectorParsingStatus ParseNegatedSimpleSelector(int32_t& aDataMask, + nsCSSSelector& aSelector); + + // If aStopChar is non-zero, the selector list is done when we hit + // aStopChar. Otherwise, it's done when we hit EOF. + bool ParseSelectorList(nsCSSSelectorList*& aListHead, + char16_t aStopChar); + bool ParseSelectorGroup(nsCSSSelectorList*& aListHead); + bool ParseSelector(nsCSSSelectorList* aList, char16_t aPrevCombinator); + + enum { + eParseDeclaration_InBraces = 1 << 0, + eParseDeclaration_AllowImportant = 1 << 1, + // The declaration we're parsing was generated by the CSSUnprefixingService: + eParseDeclaration_FromUnprefixingSvc = 1 << 2 + }; + enum nsCSSContextType { + eCSSContext_General, + eCSSContext_Page + }; + + already_AddRefed<css::Declaration> + ParseDeclarationBlock(uint32_t aFlags, + nsCSSContextType aContext = eCSSContext_General); + bool ParseDeclaration(css::Declaration* aDeclaration, + uint32_t aFlags, + bool aMustCallValueAppended, + bool* aChanged, + nsCSSContextType aContext = eCSSContext_General); + + // A "prefix-aware" wrapper for nsCSSKeywords::LookupKeyword(). + // Use this instead of LookupKeyword() if you might be parsing an unprefixed + // property (like "display") for which we emulate a vendor-prefixed value + // (like "-webkit-box"). + nsCSSKeyword LookupKeywordPrefixAware(nsAString& aKeywordStr, + const KTableEntry aKeywordTable[]); + + bool ShouldUseUnprefixingService() const; + bool ParsePropertyWithUnprefixingService(const nsAString& aPropertyName, + css::Declaration* aDeclaration, + uint32_t aFlags, + bool aMustCallValueAppended, + bool* aChanged, + nsCSSContextType aContext); + // When we detect a webkit-prefixed gradient expression, this function can be + // used to parse its body into outparam |aValue|, with the help of the + // CSSUnprefixingService. + // Only call if ShouldUseUnprefixingService() returns true. + bool ParseWebkitPrefixedGradientWithService(nsAString& aPrefixedFuncName, + nsCSSValue& aValue); + + bool ParseProperty(nsCSSPropertyID aPropID); + bool ParsePropertyByFunction(nsCSSPropertyID aPropID); + CSSParseResult ParseSingleValueProperty(nsCSSValue& aValue, + nsCSSPropertyID aPropID); + bool ParseSingleValuePropertyByFunction(nsCSSValue& aValue, + nsCSSPropertyID aPropID); + + // This is similar to ParseSingleValueProperty but only works for + // properties that are parsed with ParseBoxProperties or + // ParseGroupedBoxProperty. + // + // Only works with variants with the following flags: + // A, C, H, K, L, N, P, CALC. + CSSParseResult ParseBoxProperty(nsCSSValue& aValue, + nsCSSPropertyID aPropID); + + enum PriorityParsingStatus { + ePriority_None, + ePriority_Important, + ePriority_Error + }; + PriorityParsingStatus ParsePriority(); + +#ifdef MOZ_XUL + bool ParseTreePseudoElement(nsAtomList **aPseudoElementArgs); +#endif + + // Property specific parsing routines + bool ParseImageLayers(const nsCSSPropertyID aTable[]); + + struct ImageLayersShorthandParseState { + nsCSSValue& mColor; + nsCSSValueList* mImage; + nsCSSValuePairList* mRepeat; + nsCSSValueList* mAttachment; // A property for background layer only + nsCSSValueList* mClip; + nsCSSValueList* mOrigin; + nsCSSValueList* mPositionX; + nsCSSValueList* mPositionY; + nsCSSValuePairList* mSize; + nsCSSValueList* mComposite; // A property for mask layer only + nsCSSValueList* mMode; // A property for mask layer only + ImageLayersShorthandParseState( + nsCSSValue& aColor, nsCSSValueList* aImage, nsCSSValuePairList* aRepeat, + nsCSSValueList* aAttachment, nsCSSValueList* aClip, + nsCSSValueList* aOrigin, + nsCSSValueList* aPositionX, nsCSSValueList* aPositionY, + nsCSSValuePairList* aSize, nsCSSValueList* aComposite, + nsCSSValueList* aMode) : + mColor(aColor), mImage(aImage), mRepeat(aRepeat), + mAttachment(aAttachment), mClip(aClip), mOrigin(aOrigin), + mPositionX(aPositionX), mPositionY(aPositionY), + mSize(aSize), mComposite(aComposite), + mMode(aMode) {}; + }; + + bool IsFunctionTokenValidForImageLayerImage(const nsCSSToken& aToken) const; + bool ParseImageLayersItem(ImageLayersShorthandParseState& aState, + const nsCSSPropertyID aTable[]); + + bool ParseValueList(nsCSSPropertyID aPropID); // a single value prop-id + bool ParseImageLayerRepeat(nsCSSPropertyID aPropID); + bool ParseImageLayerRepeatValues(nsCSSValuePair& aValue); + bool ParseImageLayerPosition(const nsCSSPropertyID aTable[]); + bool ParseImageLayerPositionCoord(nsCSSPropertyID aPropID, bool aIsHorizontal); + + // ParseBoxPositionValues parses the CSS 2.1 background-position syntax, + // which is still used by some properties. See ParsePositionValue + // for the css3-background syntax. + bool ParseBoxPositionValues(nsCSSValuePair& aOut, bool aAcceptsInherit, + bool aAllowExplicitCenter = true); // deprecated + + // ParsePositionValue parses a CSS <position> value, which is used by + // the 'background-position' property. + bool ParsePositionValue(nsCSSValue& aOut); + bool ParsePositionValueSeparateCoords(nsCSSValue& aOutX, nsCSSValue& aOutY); + + bool ParseImageLayerPositionCoordItem(nsCSSValue& aOut, bool aIsHorizontal); + bool ParseImageLayerSize(nsCSSPropertyID aPropID); + bool ParseImageLayerSizeValues(nsCSSValuePair& aOut); + bool ParseBorderColor(); + bool ParseBorderColors(nsCSSPropertyID aProperty); + void SetBorderImageInitialValues(); + bool ParseBorderImageRepeat(bool aAcceptsInherit); + // If ParseBorderImageSlice returns false, aConsumedTokens indicates + // whether or not any tokens were consumed (in other words, was the property + // in error or just not present). If ParseBorderImageSlice returns true + // aConsumedTokens is always true. + bool ParseBorderImageSlice(bool aAcceptsInherit, bool* aConsumedTokens); + bool ParseBorderImageWidth(bool aAcceptsInherit); + bool ParseBorderImageOutset(bool aAcceptsInherit); + bool ParseBorderImage(); + bool ParseBorderSpacing(); + bool ParseBorderSide(const nsCSSPropertyID aPropIDs[], + bool aSetAllSides); + bool ParseBorderStyle(); + bool ParseBorderWidth(); + + bool ParseCalc(nsCSSValue &aValue, uint32_t aVariantMask); + bool ParseCalcAdditiveExpression(nsCSSValue& aValue, + uint32_t& aVariantMask); + bool ParseCalcMultiplicativeExpression(nsCSSValue& aValue, + uint32_t& aVariantMask, + bool *aHadFinalWS); + bool ParseCalcTerm(nsCSSValue& aValue, uint32_t& aVariantMask); + bool RequireWhitespace(); + + // For "flex" shorthand property, defined in CSS Flexbox spec + bool ParseFlex(); + // For "flex-flow" shorthand property, defined in CSS Flexbox spec + bool ParseFlexFlow(); + + // CSS Grid + bool ParseGridAutoFlow(); + + // Parse a <line-names> expression. + // If successful, either leave aValue untouched, + // to indicate that we parsed the empty list, + // or set it to a eCSSUnit_List of eCSSUnit_Ident. + // + // To parse an optional <line-names> (ie. if not finding an open bracket + // is considered the same as an empty list), + // treat CSSParseResult::NotFound the same as CSSParseResult::Ok. + // + // If aValue is already a eCSSUnit_List, append to that list. + CSSParseResult ParseGridLineNames(nsCSSValue& aValue); + bool ParseGridLineNameListRepeat(nsCSSValueList** aTailPtr); + bool ParseOptionalLineNameListAfterSubgrid(nsCSSValue& aValue); + + CSSParseResult ParseGridTrackBreadth(nsCSSValue& aValue); + // eFixedTrackSize in aFlags makes it parse a <fixed-size>. + CSSParseResult ParseGridTrackSize(nsCSSValue& aValue, + GridTrackSizeFlags aFlags = GridTrackSizeFlags::eDefaultTrackSize); + + bool ParseGridAutoColumnsRows(nsCSSPropertyID aPropID); + bool ParseGridTrackListRepeat(nsCSSValueList** aTailPtr); + bool ParseGridTrackRepeatIntro(bool aForSubgrid, + int32_t* aRepetitions, + Maybe<int32_t>* aRepeatAutoEnum); + + // Assuming a [ <line-names>? ] has already been parsed, + // parse the rest of a <track-list>. + // + // This exists because [ <line-names>? ] is ambiguous in the 'grid-template' + // shorthand: it can be either the start of a <track-list> (in + // a <'grid-template-rows'>) or of the intertwined syntax that sets both + // grid-template-rows and grid-template-areas. + // + // On success, |aValue| will be a list of odd length >= 3, + // starting with a <line-names> (which is itself a list) + // and alternating between that and <track-size>. + bool ParseGridTrackListWithFirstLineNames(nsCSSValue& aValue, + const nsCSSValue& aFirstLineNames, + GridTrackListFlags aFlags = GridTrackListFlags::eDefaultTrackList); + + bool ParseGridTrackList(nsCSSPropertyID aPropID, + GridTrackListFlags aFlags = GridTrackListFlags::eDefaultTrackList); + bool ParseGridTemplateColumnsRows(nsCSSPropertyID aPropID); + + // |aAreaIndices| is a lookup table to help us parse faster, + // mapping area names to indices in |aResult.mNamedAreas|. + bool ParseGridTemplateAreasLine(const nsAutoString& aInput, + css::GridTemplateAreasValue* aResult, + nsDataHashtable<nsStringHashKey, uint32_t>& aAreaIndices); + bool ParseGridTemplateAreas(); + bool ParseGridTemplateColumnsOrAutoFlow(bool aForGridShorthand); + bool ParseGridTemplate(bool aForGridShorthand = false); + bool ParseGridTemplateAfterString(const nsCSSValue& aFirstLineNames); + bool ParseGrid(); + CSSParseResult ParseGridShorthandAutoProps(int32_t aAutoFlowAxis); + bool ParseGridLine(nsCSSValue& aValue); + bool ParseGridColumnRowStartEnd(nsCSSPropertyID aPropID); + bool ParseGridColumnRow(nsCSSPropertyID aStartPropID, + nsCSSPropertyID aEndPropID); + bool ParseGridArea(); + bool ParseGridGap(); + + bool ParseInitialLetter(); + + // parsing 'align/justify-items/self' from the css-align spec + bool ParseAlignJustifyPosition(nsCSSValue& aResult, + const KTableEntry aTable[]); + bool ParseJustifyItems(); + bool ParseAlignItems(); + bool ParseAlignJustifySelf(nsCSSPropertyID aPropID); + // parsing 'align/justify-content' from the css-align spec + bool ParseAlignJustifyContent(nsCSSPropertyID aPropID); + bool ParsePlaceContent(); + bool ParsePlaceItems(); + bool ParsePlaceSelf(); + + // for 'clip' and '-moz-image-region' + bool ParseRect(nsCSSPropertyID aPropID); + bool ParseColumns(); + bool ParseContain(nsCSSValue& aValue); + bool ParseContent(); + bool ParseCounterData(nsCSSPropertyID aPropID); + bool ParseCursor(); + bool ParseFont(); + bool ParseFontSynthesis(nsCSSValue& aValue); + bool ParseSingleAlternate(int32_t& aWhichFeature, nsCSSValue& aValue); + bool ParseFontVariantAlternates(nsCSSValue& aValue); + bool MergeBitmaskValue(int32_t aNewValue, const int32_t aMasks[], + int32_t& aMergedValue); + bool ParseBitmaskValues(nsCSSValue& aValue, + const KTableEntry aKeywordTable[], + const int32_t aMasks[]); + bool ParseFontVariantEastAsian(nsCSSValue& aValue); + bool ParseFontVariantLigatures(nsCSSValue& aValue); + bool ParseFontVariantNumeric(nsCSSValue& aValue); + bool ParseFontVariant(); + bool ParseFontWeight(nsCSSValue& aValue); + bool ParseOneFamily(nsAString& aFamily, bool& aOneKeyword, bool& aQuoted); + bool ParseFamily(nsCSSValue& aValue); + bool ParseFontFeatureSettings(nsCSSValue& aValue); + bool ParseFontSrc(nsCSSValue& aValue); + bool ParseFontSrcFormat(InfallibleTArray<nsCSSValue>& values); + bool ParseFontRanges(nsCSSValue& aValue); + bool ParseListStyle(); + bool ParseListStyleType(nsCSSValue& aValue); + bool ParseMargin(); + bool ParseClipPath(nsCSSValue& aValue); + bool ParseTransform(bool aIsPrefixed, bool aDisallowRelativeValues = false); + bool ParseObjectPosition(); + bool ParseOutline(); + bool ParseOverflow(); + bool ParsePadding(); + bool ParseQuotes(); + bool ParseTextAlign(nsCSSValue& aValue, + const KTableEntry aTable[]); + bool ParseTextAlign(nsCSSValue& aValue); + bool ParseTextAlignLast(nsCSSValue& aValue); + bool ParseTextDecoration(); + bool ParseTextDecorationLine(nsCSSValue& aValue); + bool ParseTextEmphasis(); + bool ParseTextEmphasisPosition(nsCSSValue& aValue); + bool ParseTextEmphasisStyle(nsCSSValue& aValue); + bool ParseTextCombineUpright(nsCSSValue& aValue); + bool ParseTextOverflow(nsCSSValue& aValue); + bool ParseTouchAction(nsCSSValue& aValue); + + bool ParseShadowItem(nsCSSValue& aValue, bool aIsBoxShadow); + bool ParseShadowList(nsCSSPropertyID aProperty); + bool ParseShapeOutside(nsCSSValue& aValue); + bool ParseTransitionProperty(); + bool ParseTransitionTimingFunctionValues(nsCSSValue& aValue); + bool ParseTransitionTimingFunctionValueComponent(float& aComponent, + char aStop, + bool aIsXPoint); + bool ParseTransitionStepTimingFunctionValues(nsCSSValue& aValue); + enum ParseAnimationOrTransitionShorthandResult { + eParseAnimationOrTransitionShorthand_Values, + eParseAnimationOrTransitionShorthand_Inherit, + eParseAnimationOrTransitionShorthand_Error + }; + ParseAnimationOrTransitionShorthandResult + ParseAnimationOrTransitionShorthand(const nsCSSPropertyID* aProperties, + const nsCSSValue* aInitialValues, + nsCSSValue* aValues, + size_t aNumProperties); + bool ParseTransition(); + bool ParseAnimation(); + bool ParseWillChange(); + + bool ParsePaint(nsCSSPropertyID aPropID); + bool ParseDasharray(); + bool ParseMarker(); + bool ParsePaintOrder(); + bool ParseAll(); + bool ParseScrollSnapType(); + bool ParseScrollSnapPoints(nsCSSValue& aValue, nsCSSPropertyID aPropID); + bool ParseScrollSnapDestination(nsCSSValue& aValue); + bool ParseScrollSnapCoordinate(nsCSSValue& aValue); + bool ParseWebkitTextStroke(); + + /** + * Parses a variable value from a custom property declaration. + * + * @param aType Out parameter into which will be stored the type of variable + * value, indicating whether the parsed value was a token stream or one of + * the CSS-wide keywords. + * @param aValue Out parameter into which will be stored the token stream + * as a string, if the parsed custom property did take a token stream. + * @return Whether parsing succeeded. + */ + bool ParseVariableDeclaration(CSSVariableDeclarations::Type* aType, + nsString& aValue); + + /** + * Parses a CSS variable value. This could be 'initial', 'inherit', 'unset' + * or a token stream, which may or may not include variable references. + * + * @param aType Out parameter into which the type of the variable value + * will be stored. + * @param aDropBackslash Out parameter indicating whether during variable + * value parsing there was a trailing backslash before EOF that must + * be dropped when serializing the variable value. + * @param aImpliedCharacters Out parameter appended to which will be any + * characters that were implied when encountering EOF and which + * must be included at the end of the serialized variable value. + * @param aFunc A callback function to invoke when a variable reference + * is encountered. May be null. Arguments are the variable name + * and the aData argument passed in to this function. + * @param User data to pass in to the callback. + * @return Whether parsing succeeded. + */ + bool ParseValueWithVariables(CSSVariableDeclarations::Type* aType, + bool* aDropBackslash, + nsString& aImpliedCharacters, + void (*aFunc)(const nsAString&, void*), + void* aData); + + /** + * Returns whether the scanner dropped a backslash just before EOF. + */ + bool BackslashDropped(); + + /** + * Calls AppendImpliedEOFCharacters on mScanner. + */ + void AppendImpliedEOFCharacters(nsAString& aResult); + + // Reused utility parsing routines + void AppendValue(nsCSSPropertyID aPropID, const nsCSSValue& aValue); + bool ParseBoxProperties(const nsCSSPropertyID aPropIDs[]); + bool ParseGroupedBoxProperty(int32_t aVariantMask, + nsCSSValue& aValue, + uint32_t aRestrictions); + bool ParseBoxCornerRadius(const nsCSSPropertyID aPropID); + bool ParseBoxCornerRadiiInternals(nsCSSValue array[]); + bool ParseBoxCornerRadii(const nsCSSPropertyID aPropIDs[]); + + int32_t ParseChoice(nsCSSValue aValues[], + const nsCSSPropertyID aPropIDs[], int32_t aNumIDs); + + CSSParseResult ParseColor(nsCSSValue& aValue); + + template<typename ComponentType> + bool ParseRGBColor(ComponentType& aR, + ComponentType& aG, + ComponentType& aB, + ComponentType& aA); + bool ParseHSLColor(float& aHue, float& aSaturation, float& aLightness, + float& aOpacity); + + // The ParseColorOpacityAndCloseParen methods below attempt to parse an + // optional [ separator <alpha-value> ] expression, followed by a + // close-parenthesis, at the end of a css color function (e.g. "rgba()" or + // "hsla()"). If these functions simply encounter a close-parenthesis (without + // any [separator <alpha-value>]), they will still succeed (i.e. return true), + // with outparam 'aOpacity' set to a default opacity value (fully-opaque). + // + // The range of opacity component is [0, 255], and the default opacity value + // is 255 (fully-opaque) for this function. + bool ParseColorOpacityAndCloseParen(uint8_t& aOpacity, + char aSeparator); + // Similar to the previous one, but the range of opacity component is + // [0.0f, 1.0f] and the default opacity value is 1.0f (fully-opaque). + bool ParseColorOpacityAndCloseParen(float& aOpacity, + char aSeparator); + + // Parse a <number> color component. The range of color component is [0, 255]. + // If |aSeparator| is provided, this function will also attempt to parse that + // character after parsing the color component. + bool ParseColorComponent(uint8_t& aComponent, Maybe<char> aSeparator); + // Similar to the previous one, but parse a <percentage> color component. + // The range of color component is [0.0f, 1.0f]. + bool ParseColorComponent(float& aComponent, Maybe<char> aSeparator); + + // Parse a <hue> component. + // <hue> = <number> | <angle> + // The unit of outparam 'aAngle' is degree. Assume the unit to be degree if an + // unitless <number> is parsed. + bool ParseHue(float& aAngle); + + bool ParseEnum(nsCSSValue& aValue, + const KTableEntry aKeywordTable[]); + + // A special ParseEnum for the CSS Box Alignment properties that have + // 'baseline' values. In addition to the keywords in aKeywordTable, it also + // parses 'first baseline' and 'last baseline' as a single value. + // (aKeywordTable must contain 'baseline') + bool ParseAlignEnum(nsCSSValue& aValue, const KTableEntry aKeywordTable[]); + + // Variant parsing methods + CSSParseResult ParseVariant(nsCSSValue& aValue, + uint32_t aVariantMask, + const KTableEntry aKeywordTable[]); + CSSParseResult ParseVariantWithRestrictions(nsCSSValue& aValue, + int32_t aVariantMask, + const KTableEntry aKeywordTable[], + uint32_t aRestrictions); + CSSParseResult ParseNonNegativeVariant(nsCSSValue& aValue, + int32_t aVariantMask, + const KTableEntry aKeywordTable[]); + CSSParseResult ParseOneOrLargerVariant(nsCSSValue& aValue, + int32_t aVariantMask, + const KTableEntry aKeywordTable[]); + + // Variant parsing methods that are guaranteed to UngetToken any token + // consumed on failure + bool ParseSingleTokenVariant(nsCSSValue& aValue, + int32_t aVariantMask, + const KTableEntry aKeywordTable[]) + { + MOZ_ASSERT(!(aVariantMask & VARIANT_MULTIPLE_TOKENS), + "use ParseVariant for variants in VARIANT_MULTIPLE_TOKENS"); + CSSParseResult result = ParseVariant(aValue, aVariantMask, aKeywordTable); + MOZ_ASSERT(result != CSSParseResult::Error); + return result == CSSParseResult::Ok; + } + bool ParseSingleTokenVariantWithRestrictions( + nsCSSValue& aValue, + int32_t aVariantMask, + const KTableEntry aKeywordTable[], + uint32_t aRestrictions) + { + MOZ_ASSERT(!(aVariantMask & VARIANT_MULTIPLE_TOKENS), + "use ParseVariantWithRestrictions for variants in " + "VARIANT_MULTIPLE_TOKENS"); + CSSParseResult result = + ParseVariantWithRestrictions(aValue, aVariantMask, aKeywordTable, + aRestrictions); + MOZ_ASSERT(result != CSSParseResult::Error); + return result == CSSParseResult::Ok; + } + bool ParseSingleTokenNonNegativeVariant(nsCSSValue& aValue, + int32_t aVariantMask, + const KTableEntry aKeywordTable[]) + { + MOZ_ASSERT(!(aVariantMask & VARIANT_MULTIPLE_TOKENS), + "use ParseNonNegativeVariant for variants in " + "VARIANT_MULTIPLE_TOKENS"); + CSSParseResult result = + ParseNonNegativeVariant(aValue, aVariantMask, aKeywordTable); + MOZ_ASSERT(result != CSSParseResult::Error); + return result == CSSParseResult::Ok; + } + bool ParseSingleTokenOneOrLargerVariant(nsCSSValue& aValue, + int32_t aVariantMask, + const KTableEntry aKeywordTable[]) + { + MOZ_ASSERT(!(aVariantMask & VARIANT_MULTIPLE_TOKENS), + "use ParseOneOrLargerVariant for variants in " + "VARIANT_MULTIPLE_TOKENS"); + CSSParseResult result = + ParseOneOrLargerVariant(aValue, aVariantMask, aKeywordTable); + MOZ_ASSERT(result != CSSParseResult::Error); + return result == CSSParseResult::Ok; + } + + // Helpers for some common ParseSingleTokenNonNegativeVariant calls. + bool ParseNonNegativeInteger(nsCSSValue& aValue) + { + return ParseSingleTokenNonNegativeVariant(aValue, VARIANT_INTEGER, nullptr); + } + bool ParseNonNegativeNumber(nsCSSValue& aValue) + { + return ParseSingleTokenNonNegativeVariant(aValue, VARIANT_NUMBER, nullptr); + } + + // Helpers for some common ParseSingleTokenOneOrLargerVariant calls. + bool ParseOneOrLargerInteger(nsCSSValue& aValue) + { + return ParseSingleTokenOneOrLargerVariant(aValue, VARIANT_INTEGER, nullptr); + } + bool ParseOneOrLargerNumber(nsCSSValue& aValue) + { + return ParseSingleTokenOneOrLargerVariant(aValue, VARIANT_NUMBER, nullptr); + } + + // http://dev.w3.org/csswg/css-values/#custom-idents + // Parse an identifier that is none of: + // * a CSS-wide keyword + // * "default" + // * a keyword in |aExcludedKeywords| + // * a keyword in |aPropertyKTable| + // + // |aExcludedKeywords| is an array of nsCSSKeyword + // that ends with a eCSSKeyword_UNKNOWN marker. + // + // |aPropertyKTable| can be used if some of the keywords to exclude + // also appear in an existing nsCSSProps::KTableEntry, + // to avoid duplicating them. + bool ParseCustomIdent(nsCSSValue& aValue, + const nsAutoString& aIdentValue, + const nsCSSKeyword aExcludedKeywords[] = nullptr, + const nsCSSProps::KTableEntry aPropertyKTable[] = nullptr); + bool ParseCounter(nsCSSValue& aValue); + bool ParseAttr(nsCSSValue& aValue); + bool ParseSymbols(nsCSSValue& aValue); + bool SetValueToURL(nsCSSValue& aValue, const nsString& aURL); + bool TranslateDimension(nsCSSValue& aValue, uint32_t aVariantMask, + float aNumber, const nsString& aUnit); + bool ParseImageOrientation(nsCSSValue& aAngle); + bool ParseImageRect(nsCSSValue& aImage); + bool ParseElement(nsCSSValue& aValue); + bool ParseColorStop(nsCSSValueGradient* aGradient); + + enum GradientParsingFlags { + eGradient_Repeating = 1 << 0, // repeating-{linear|radial}-gradient + eGradient_MozLegacy = 1 << 1, // -moz-{linear|radial}-gradient + eGradient_WebkitLegacy = 1 << 2, // -webkit-{linear|radial}-gradient + + // Mask to catch both "legacy" flags: + eGradient_AnyLegacy = eGradient_MozLegacy | eGradient_WebkitLegacy + }; + bool ParseLinearGradient(nsCSSValue& aValue, uint8_t aFlags); + bool ParseRadialGradient(nsCSSValue& aValue, uint8_t aFlags); + bool IsLegacyGradientLine(const nsCSSTokenType& aType, + const nsString& aId); + bool ParseGradientColorStops(nsCSSValueGradient* aGradient, + nsCSSValue& aValue); + + // For the ancient "-webkit-gradient(linear|radial, ...)" syntax: + bool ParseWebkitGradientPointComponent(nsCSSValue& aComponent, + bool aIsHorizontal); + bool ParseWebkitGradientPoint(nsCSSValuePair& aPoint); + bool ParseWebkitGradientRadius(float& aRadius); + bool ParseWebkitGradientColorStop(nsCSSValueGradient* aGradient); + bool ParseWebkitGradientColorStops(nsCSSValueGradient* aGradient); + void FinalizeLinearWebkitGradient(nsCSSValueGradient* aGradient, + const nsCSSValuePair& aStartPoint, + const nsCSSValuePair& aSecondPoint); + void FinalizeRadialWebkitGradient(nsCSSValueGradient* aGradient, + const nsCSSValuePair& aFirstCenter, + const nsCSSValuePair& aSecondCenter, + const float aFirstRadius, + const float aSecondRadius); + bool ParseWebkitGradient(nsCSSValue& aValue); + + void SetParsingCompoundProperty(bool aBool) { + mParsingCompoundProperty = aBool; + } + bool IsParsingCompoundProperty(void) const { + return mParsingCompoundProperty; + } + + /* Functions for basic shapes */ + bool ParseReferenceBoxAndBasicShape(nsCSSValue& aValue, + const KTableEntry aBoxKeywordTable[]); + bool ParseBasicShape(nsCSSValue& aValue, bool* aConsumedTokens); + bool ParsePolygonFunction(nsCSSValue& aValue); + bool ParseCircleOrEllipseFunction(nsCSSKeyword, nsCSSValue& aValue); + bool ParseInsetFunction(nsCSSValue& aValue); + // We parse position values differently for basic-shape, by expanding defaults + // and replacing keywords with percentages + bool ParsePositionValueForBasicShape(nsCSSValue& aOut); + + + /* Functions for transform Parsing */ + bool ParseSingleTransform(bool aIsPrefixed, bool aDisallowRelativeValues, + nsCSSValue& aValue); + bool ParseFunction(nsCSSKeyword aFunction, const uint32_t aAllowedTypes[], + uint32_t aVariantMaskAll, uint16_t aMinElems, + uint16_t aMaxElems, nsCSSValue &aValue); + bool ParseFunctionInternals(const uint32_t aVariantMask[], + uint32_t aVariantMaskAll, + uint16_t aMinElems, + uint16_t aMaxElems, + InfallibleTArray<nsCSSValue>& aOutput); + + /* Functions for transform-origin/perspective-origin Parsing */ + bool ParseTransformOrigin(bool aPerspective); + + /* Functions for filter parsing */ + bool ParseFilter(); + bool ParseSingleFilter(nsCSSValue* aValue); + bool ParseDropShadow(nsCSSValue* aValue); + + /* Find and return the namespace ID associated with aPrefix. + If aPrefix has not been declared in an @namespace rule, returns + kNameSpaceID_Unknown. */ + int32_t GetNamespaceIdForPrefix(const nsString& aPrefix); + + /* Find the correct default namespace, and set it on aSelector. */ + void SetDefaultNamespaceOnSelector(nsCSSSelector& aSelector); + + // Current token. The value is valid after calling GetToken and invalidated + // by UngetToken. + nsCSSToken mToken; + + // Our scanner. + nsCSSScanner* mScanner; + + // Our error reporter. + css::ErrorReporter* mReporter; + + // The URI to be used as a base for relative URIs. + nsCOMPtr<nsIURI> mBaseURI; + + // The URI to be used as an HTTP "Referer" and for error reporting. + nsCOMPtr<nsIURI> mSheetURI; + + // The principal of the sheet involved + nsCOMPtr<nsIPrincipal> mSheetPrincipal; + + // The sheet we're parsing into + RefPtr<CSSStyleSheet> mSheet; + + // Used for @import rules + css::Loader* mChildLoader; // not ref counted, it owns us + + // Any sheets we may reuse when parsing an @import. + css::LoaderReusableStyleSheets* mReusableSheets; + + // Sheet section we're in. This is used to enforce correct ordering of the + // various rule types (eg the fact that a @charset rule must come before + // anything else). Note that there are checks of similar things in various + // places in CSSStyleSheet.cpp (e.g in insertRule, RebuildChildList). + enum nsCSSSection { + eCSSSection_Charset, + eCSSSection_Import, + eCSSSection_NameSpace, + eCSSSection_General + }; + nsCSSSection mSection; + + nsXMLNameSpaceMap *mNameSpaceMap; // weak, mSheet owns it + + // After an UngetToken is done this flag is true. The next call to + // GetToken clears the flag. + bool mHavePushBack : 1; + + // True if we are in quirks mode; false in standards or almost standards mode + bool mNavQuirkMode : 1; + + // True when the hashless color quirk applies. + bool mHashlessColorQuirk : 1; + + // True when the unitless length quirk applies. + bool mUnitlessLengthQuirk : 1; + + // Controls access to nonstandard style constructs that are not safe + // for use on the public Web but necessary in UA sheets and/or + // useful in user sheets. The only values stored in this field are + // 0, 1, and 2; three bits are allocated to avoid issues should the + // enum type be signed. + css::SheetParsingMode mParsingMode : 3; + + // True if we are in parsing rules for the chrome. + bool mIsChrome : 1; + + // True if viewport units should be allowed. + bool mViewportUnitsEnabled : 1; + + // True for parsing media lists for HTML attributes, where we have to + // ignore CSS comments. + bool mHTMLMediaMode : 1; + + // This flag is set when parsing a non-box shorthand; it's used to not apply + // some quirks during shorthand parsing + bool mParsingCompoundProperty : 1; + + // True if we are in the middle of parsing an @supports condition. + // This is used to avoid recording the input stream when variable references + // are encountered in a property declaration in the @supports condition. + bool mInSupportsCondition : 1; + + // True if we are somewhere within a @supports rule whose condition is + // false. + bool mInFailingSupportsRule : 1; + + // True if we will suppress all parse errors (except unexpected EOFs). + // This is used to prevent errors for declarations inside a failing + // @supports rule. + bool mSuppressErrors : 1; + + // True if any parsing of URL values requires a sheet principal to have + // been passed in the nsCSSScanner constructor. This is usually the case. + // It can be set to false, for example, when we create an nsCSSParser solely + // to parse a property value to test it for syntactic correctness. When + // false, an assertion that mSheetPrincipal is non-null is skipped. Should + // not be set to false if any nsCSSValues created during parsing can escape + // out of the parser. + bool mSheetPrincipalRequired; + + // This enum helps us track whether we've unprefixed "display: -webkit-box" + // (treating it as "display: flex") in an earlier declaration within a series + // of declarations. (This only impacts behavior when the function + // "ShouldUseUnprefixingService()" returns true, and that should only happen + // for a short whitelist of origins.) + enum WebkitBoxUnprefixState : uint8_t { + eNotParsingDecls, // We are *not* currently parsing a sequence of + // CSS declarations. (default state) + + // The next two enum values indicate that we *are* currently parsing a + // sequence of declarations (in ParseDeclarations or ParseDeclarationBlock) + // and... + eHaveNotUnprefixed, // ...we have not unprefixed 'display:-webkit-box' in + // this sequence of CSS declarations. + eHaveUnprefixed // ...we *have* unprefixed 'display:-webkit-box' earlier in + // this sequence of CSS declarations. + }; + WebkitBoxUnprefixState mWebkitBoxUnprefixState; + + // Stack of rule groups; used for @media and such. + InfallibleTArray<RefPtr<css::GroupRule> > mGroupStack; + + // During the parsing of a property (which may be a shorthand), the data + // are stored in |mTempData|. (It is needed to ensure that parser + // errors cause the data to be ignored, and to ensure that a + // non-'!important' declaration does not override an '!important' + // one.) + nsCSSExpandedDataBlock mTempData; + + // All data from successfully parsed properties are placed into |mData|. + nsCSSExpandedDataBlock mData; + +public: + // Used from nsCSSParser constructors and destructors + CSSParserImpl* mNextFree; +}; + +static void AssignRuleToPointer(css::Rule* aRule, void* aPointer) +{ + css::Rule **pointer = static_cast<css::Rule**>(aPointer); + NS_ADDREF(*pointer = aRule); +} + +static void AppendRuleToSheet(css::Rule* aRule, void* aParser) +{ + CSSParserImpl* parser = (CSSParserImpl*) aParser; + parser->AppendRule(aRule); +} + +#define REPORT_UNEXPECTED(msg_) \ + { if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_); } + +#define REPORT_UNEXPECTED_P(msg_, param_) \ + { if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_, param_); } + +#define REPORT_UNEXPECTED_P_V(msg_, param_, value_) \ + { if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_, param_, value_); } + +#define REPORT_UNEXPECTED_TOKEN(msg_) \ + { if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_, mToken); } + +#define REPORT_UNEXPECTED_TOKEN_CHAR(msg_, ch_) \ + { if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_, mToken, ch_); } + +#define REPORT_UNEXPECTED_EOF(lf_) \ + mReporter->ReportUnexpectedEOF(#lf_) + +#define REPORT_UNEXPECTED_EOF_CHAR(ch_) \ + mReporter->ReportUnexpectedEOF(ch_) + +#define OUTPUT_ERROR() \ + mReporter->OutputError() + +#define OUTPUT_ERROR_WITH_POSITION(linenum_, lineoff_) \ + mReporter->OutputError(linenum_, lineoff_) + +#define CLEAR_ERROR() \ + mReporter->ClearError() + +CSSParserImpl::CSSParserImpl() + : mToken(), + mScanner(nullptr), + mReporter(nullptr), + mChildLoader(nullptr), + mReusableSheets(nullptr), + mSection(eCSSSection_Charset), + mNameSpaceMap(nullptr), + mHavePushBack(false), + mNavQuirkMode(false), + mHashlessColorQuirk(false), + mUnitlessLengthQuirk(false), + mParsingMode(css::eAuthorSheetFeatures), + mIsChrome(false), + mViewportUnitsEnabled(true), + mHTMLMediaMode(false), + mParsingCompoundProperty(false), + mInSupportsCondition(false), + mInFailingSupportsRule(false), + mSuppressErrors(false), + mSheetPrincipalRequired(true), + mWebkitBoxUnprefixState(eNotParsingDecls), + mNextFree(nullptr) +{ +} + +CSSParserImpl::~CSSParserImpl() +{ + mData.AssertInitialState(); + mTempData.AssertInitialState(); +} + +nsresult +CSSParserImpl::SetStyleSheet(CSSStyleSheet* aSheet) +{ + if (aSheet != mSheet) { + // Switch to using the new sheet, if any + mGroupStack.Clear(); + mSheet = aSheet; + if (mSheet) { + mNameSpaceMap = mSheet->GetNameSpaceMap(); + } else { + mNameSpaceMap = nullptr; + } + } else if (mSheet) { + mNameSpaceMap = mSheet->GetNameSpaceMap(); + } + + return NS_OK; +} + +nsIDocument* +CSSParserImpl::GetDocument() +{ + if (!mSheet) { + return nullptr; + } + return mSheet->GetDocument(); +} + +nsresult +CSSParserImpl::SetQuirkMode(bool aQuirkMode) +{ + mNavQuirkMode = aQuirkMode; + return NS_OK; +} + +nsresult +CSSParserImpl::SetChildLoader(mozilla::css::Loader* aChildLoader) +{ + mChildLoader = aChildLoader; // not ref counted, it owns us + return NS_OK; +} + +void +CSSParserImpl::Reset() +{ + NS_ASSERTION(!mScanner, "resetting with scanner active"); + SetStyleSheet(nullptr); + SetQuirkMode(false); + SetChildLoader(nullptr); +} + +void +CSSParserImpl::InitScanner(nsCSSScanner& aScanner, + css::ErrorReporter& aReporter, + nsIURI* aSheetURI, nsIURI* aBaseURI, + nsIPrincipal* aSheetPrincipal) +{ + NS_PRECONDITION(!mHTMLMediaMode, "Bad initial state"); + NS_PRECONDITION(!mParsingCompoundProperty, "Bad initial state"); + NS_PRECONDITION(!mScanner, "already have scanner"); + + mScanner = &aScanner; + mReporter = &aReporter; + mScanner->SetErrorReporter(mReporter); + + mBaseURI = aBaseURI; + mSheetURI = aSheetURI; + mSheetPrincipal = aSheetPrincipal; + mHavePushBack = false; +} + +void +CSSParserImpl::ReleaseScanner() +{ + mScanner = nullptr; + mReporter = nullptr; + mBaseURI = nullptr; + mSheetURI = nullptr; + mSheetPrincipal = nullptr; +} + +nsresult +CSSParserImpl::ParseSheet(const nsAString& aInput, + nsIURI* aSheetURI, + nsIURI* aBaseURI, + nsIPrincipal* aSheetPrincipal, + uint32_t aLineNumber, + css::LoaderReusableStyleSheets* aReusableSheets) +{ + NS_PRECONDITION(aSheetPrincipal, "Must have principal here!"); + NS_PRECONDITION(aBaseURI, "need base URI"); + NS_PRECONDITION(aSheetURI, "need sheet URI"); + NS_PRECONDITION(mSheet, "Must have sheet to parse into"); + NS_ENSURE_STATE(mSheet); + +#ifdef DEBUG + nsIURI* uri = mSheet->GetSheetURI(); + bool equal; + NS_ASSERTION(NS_SUCCEEDED(aSheetURI->Equals(uri, &equal)) && equal, + "Sheet URI does not match passed URI"); + NS_ASSERTION(NS_SUCCEEDED(mSheet->Principal()->Equals(aSheetPrincipal, + &equal)) && + equal, + "Sheet principal does not match passed principal"); +#endif + + nsCSSScanner scanner(aInput, aLineNumber); + css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI); + InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal); + + int32_t ruleCount = mSheet->StyleRuleCount(); + if (0 < ruleCount) { + const css::Rule* lastRule = mSheet->GetStyleRuleAt(ruleCount - 1); + if (lastRule) { + switch (lastRule->GetType()) { + case css::Rule::CHARSET_RULE: + case css::Rule::IMPORT_RULE: + mSection = eCSSSection_Import; + break; + case css::Rule::NAMESPACE_RULE: + mSection = eCSSSection_NameSpace; + break; + default: + mSection = eCSSSection_General; + break; + } + } + } + else { + mSection = eCSSSection_Charset; // sheet is empty, any rules are fair + } + + mParsingMode = mSheet->ParsingMode(); + mIsChrome = dom::IsChromeURI(aSheetURI); + mReusableSheets = aReusableSheets; + + nsCSSToken* tk = &mToken; + for (;;) { + // Get next non-whitespace token + if (!GetToken(true)) { + OUTPUT_ERROR(); + break; + } + if (eCSSToken_HTMLComment == tk->mType) { + continue; // legal here only + } + if (eCSSToken_AtKeyword == tk->mType) { + ParseAtRule(AppendRuleToSheet, this, false); + continue; + } + UngetToken(); + if (ParseRuleSet(AppendRuleToSheet, this)) { + mSection = eCSSSection_General; + } + } + ReleaseScanner(); + + mParsingMode = css::eAuthorSheetFeatures; + mIsChrome = false; + mReusableSheets = nullptr; + + return NS_OK; +} + +/** + * Determines whether the identifier contained in the given string is a + * vendor-specific identifier, as described in CSS 2.1 section 4.1.2.1. + */ +static bool +NonMozillaVendorIdentifier(const nsAString& ident) +{ + return (ident.First() == char16_t('-') && + !StringBeginsWith(ident, NS_LITERAL_STRING("-moz-"))) || + ident.First() == char16_t('_'); + +} + +already_AddRefed<css::Declaration> +CSSParserImpl::ParseStyleAttribute(const nsAString& aAttributeValue, + nsIURI* aDocURI, + nsIURI* aBaseURI, + nsIPrincipal* aNodePrincipal) +{ + NS_PRECONDITION(aNodePrincipal, "Must have principal here!"); + NS_PRECONDITION(aBaseURI, "need base URI"); + + // XXX line number? + nsCSSScanner scanner(aAttributeValue, 0); + css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aDocURI); + InitScanner(scanner, reporter, aDocURI, aBaseURI, aNodePrincipal); + + mSection = eCSSSection_General; + + uint32_t parseFlags = eParseDeclaration_AllowImportant; + + RefPtr<css::Declaration> declaration = ParseDeclarationBlock(parseFlags); + + ReleaseScanner(); + + return declaration.forget(); +} + +nsresult +CSSParserImpl::ParseDeclarations(const nsAString& aBuffer, + nsIURI* aSheetURI, + nsIURI* aBaseURI, + nsIPrincipal* aSheetPrincipal, + css::Declaration* aDeclaration, + bool* aChanged) +{ + *aChanged = false; + + NS_PRECONDITION(aSheetPrincipal, "Must have principal here!"); + + nsCSSScanner scanner(aBuffer, 0); + css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI); + InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal); + + MOZ_ASSERT(mWebkitBoxUnprefixState == eNotParsingDecls, + "Someone forgot to clear mWebkitBoxUnprefixState!"); + AutoRestore<WebkitBoxUnprefixState> autoRestore(mWebkitBoxUnprefixState); + mWebkitBoxUnprefixState = eHaveNotUnprefixed; + + mSection = eCSSSection_General; + + mData.AssertInitialState(); + aDeclaration->ClearData(); + // We could check if it was already empty, but... + *aChanged = true; + + for (;;) { + // If we cleared the old decl, then we want to be calling + // ValueAppended as we parse. + if (!ParseDeclaration(aDeclaration, eParseDeclaration_AllowImportant, + true, aChanged)) { + if (!SkipDeclaration(false)) { + break; + } + } + } + + aDeclaration->CompressFrom(&mData); + ReleaseScanner(); + return NS_OK; +} + +nsresult +CSSParserImpl::ParseRule(const nsAString& aRule, + nsIURI* aSheetURI, + nsIURI* aBaseURI, + nsIPrincipal* aSheetPrincipal, + css::Rule** aResult) +{ + NS_PRECONDITION(aSheetPrincipal, "Must have principal here!"); + NS_PRECONDITION(aBaseURI, "need base URI"); + + *aResult = nullptr; + + nsCSSScanner scanner(aRule, 0); + css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI); + InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal); + + mSection = eCSSSection_Charset; // callers are responsible for rejecting invalid rules. + + nsCSSToken* tk = &mToken; + // Get first non-whitespace token + nsresult rv = NS_OK; + if (!GetToken(true)) { + REPORT_UNEXPECTED(PEParseRuleWSOnly); + OUTPUT_ERROR(); + rv = NS_ERROR_DOM_SYNTAX_ERR; + } else { + if (eCSSToken_AtKeyword == tk->mType) { + // FIXME: perhaps aInsideBlock should be true when we are? + ParseAtRule(AssignRuleToPointer, aResult, false); + } else { + UngetToken(); + ParseRuleSet(AssignRuleToPointer, aResult); + } + + if (*aResult && GetToken(true)) { + // garbage after rule + REPORT_UNEXPECTED_TOKEN(PERuleTrailing); + NS_RELEASE(*aResult); + } + + if (!*aResult) { + rv = NS_ERROR_DOM_SYNTAX_ERR; + OUTPUT_ERROR(); + } + } + + ReleaseScanner(); + return rv; +} + +void +CSSParserImpl::ParseLonghandProperty(const nsCSSPropertyID aPropID, + const nsAString& aPropValue, + nsIURI* aSheetURL, + nsIURI* aBaseURL, + nsIPrincipal* aSheetPrincipal, + nsCSSValue& aValue) +{ + MOZ_ASSERT(aPropID < eCSSProperty_COUNT_no_shorthands, + "ParseLonghandProperty must only take a longhand property"); + + RefPtr<css::Declaration> declaration = new css::Declaration; + declaration->InitializeEmpty(); + + bool changed; + ParseProperty(aPropID, aPropValue, aSheetURL, aBaseURL, aSheetPrincipal, + declaration, &changed, + /* aIsImportant */ false, + /* aIsSVGMode */ false); + + if (changed) { + aValue = *declaration->GetNormalBlock()->ValueFor(aPropID); + } else { + aValue.Reset(); + } +} + +bool +CSSParserImpl::ParseTransformProperty(const nsAString& aPropValue, + bool aDisallowRelativeValues, + nsCSSValue& aValue) +{ + RefPtr<css::Declaration> declaration = new css::Declaration(); + declaration->InitializeEmpty(); + + mData.AssertInitialState(); + mTempData.AssertInitialState(); + + nsCSSScanner scanner(aPropValue, 0); + css::ErrorReporter reporter(scanner, mSheet, mChildLoader, nullptr); + InitScanner(scanner, reporter, nullptr, nullptr, nullptr); + + bool parsedOK = ParseTransform(false, aDisallowRelativeValues); + // We should now be at EOF + if (parsedOK && GetToken(true)) { + parsedOK = false; + } + + bool changed = false; + if (parsedOK) { + declaration->ExpandTo(&mData); + changed = mData.TransferFromBlock(mTempData, eCSSProperty_transform, + EnabledState(), false, + true, false, declaration, + GetDocument()); + declaration->CompressFrom(&mData); + } + + if (changed) { + aValue = *declaration->GetNormalBlock()->ValueFor(eCSSProperty_transform); + } else { + aValue.Reset(); + } + + ReleaseScanner(); + + return parsedOK; +} + +void +CSSParserImpl::ParseProperty(const nsCSSPropertyID aPropID, + const nsAString& aPropValue, + nsIURI* aSheetURI, + nsIURI* aBaseURI, + nsIPrincipal* aSheetPrincipal, + css::Declaration* aDeclaration, + bool* aChanged, + bool aIsImportant, + bool aIsSVGMode) +{ + NS_PRECONDITION(aSheetPrincipal, "Must have principal here!"); + NS_PRECONDITION(aBaseURI, "need base URI"); + NS_PRECONDITION(aDeclaration, "Need declaration to parse into!"); + MOZ_ASSERT(aPropID != eCSSPropertyExtra_variable); + + mData.AssertInitialState(); + mTempData.AssertInitialState(); + aDeclaration->AssertMutable(); + + nsCSSScanner scanner(aPropValue, 0); + css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI); + InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal); + mSection = eCSSSection_General; + scanner.SetSVGMode(aIsSVGMode); + + *aChanged = false; + + // Check for unknown or preffed off properties + if (eCSSProperty_UNKNOWN == aPropID || + !nsCSSProps::IsEnabled(aPropID, EnabledState())) { + NS_ConvertASCIItoUTF16 propName(nsCSSProps::GetStringValue(aPropID)); + REPORT_UNEXPECTED_P(PEUnknownProperty, propName); + REPORT_UNEXPECTED(PEDeclDropped); + OUTPUT_ERROR(); + ReleaseScanner(); + return; + } + + bool parsedOK = ParseProperty(aPropID); + // We should now be at EOF + if (parsedOK && GetToken(true)) { + REPORT_UNEXPECTED_TOKEN(PEExpectEndValue); + parsedOK = false; + } + + if (!parsedOK) { + NS_ConvertASCIItoUTF16 propName(nsCSSProps::GetStringValue(aPropID)); + REPORT_UNEXPECTED_P(PEValueParsingError, propName); + REPORT_UNEXPECTED(PEDeclDropped); + OUTPUT_ERROR(); + mTempData.ClearProperty(aPropID); + } else { + + // We know we don't need to force a ValueAppended call for the new + // value. So if we are not processing a shorthand, and there's + // already a value for this property in the declaration at the + // same importance level, then we can just copy our parsed value + // directly into the declaration without going through the whole + // expand/compress thing. + if (!aDeclaration->TryReplaceValue(aPropID, aIsImportant, mTempData, + aChanged)) { + // Do it the slow way + aDeclaration->ExpandTo(&mData); + *aChanged = mData.TransferFromBlock(mTempData, aPropID, + EnabledState(), aIsImportant, + true, false, aDeclaration, + GetDocument()); + aDeclaration->CompressFrom(&mData); + } + CLEAR_ERROR(); + } + + mTempData.AssertInitialState(); + + ReleaseScanner(); +} + +void +CSSParserImpl::ParseVariable(const nsAString& aVariableName, + const nsAString& aPropValue, + nsIURI* aSheetURI, + nsIURI* aBaseURI, + nsIPrincipal* aSheetPrincipal, + css::Declaration* aDeclaration, + bool* aChanged, + bool aIsImportant) +{ + NS_PRECONDITION(aSheetPrincipal, "Must have principal here!"); + NS_PRECONDITION(aBaseURI, "need base URI"); + NS_PRECONDITION(aDeclaration, "Need declaration to parse into!"); + NS_PRECONDITION(nsLayoutUtils::CSSVariablesEnabled(), + "expected Variables to be enabled"); + + mData.AssertInitialState(); + mTempData.AssertInitialState(); + aDeclaration->AssertMutable(); + + nsCSSScanner scanner(aPropValue, 0); + css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI); + InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal); + mSection = eCSSSection_General; + + *aChanged = false; + + CSSVariableDeclarations::Type variableType; + nsString variableValue; + + bool parsedOK = ParseVariableDeclaration(&variableType, variableValue); + + // We should now be at EOF + if (parsedOK && GetToken(true)) { + REPORT_UNEXPECTED_TOKEN(PEExpectEndValue); + parsedOK = false; + } + + if (!parsedOK) { + REPORT_UNEXPECTED_P(PEValueParsingError, NS_LITERAL_STRING("--") + + aVariableName); + REPORT_UNEXPECTED(PEDeclDropped); + OUTPUT_ERROR(); + } else { + CLEAR_ERROR(); + aDeclaration->AddVariable(aVariableName, variableType, + variableValue, aIsImportant, true); + *aChanged = true; + } + + mTempData.AssertInitialState(); + + ReleaseScanner(); +} + +void +CSSParserImpl::ParseMediaList(const nsSubstring& aBuffer, + nsIURI* aURI, // for error reporting + uint32_t aLineNumber, // for error reporting + nsMediaList* aMediaList, + bool aHTMLMode) +{ + // XXX Are there cases where the caller wants to keep what it already + // has in case of parser error? If GatherMedia ever changes to return + // a value other than true, we probably should avoid modifying aMediaList. + aMediaList->Clear(); + + // fake base URI since media lists don't have URIs in them + nsCSSScanner scanner(aBuffer, aLineNumber); + css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI); + InitScanner(scanner, reporter, aURI, aURI, nullptr); + + mHTMLMediaMode = aHTMLMode; + + // XXXldb We need to make the scanner not skip CSS comments! (Or + // should we?) + + // For aHTMLMode, we used to follow the parsing rules in + // http://www.w3.org/TR/1999/REC-html401-19991224/types.html#type-media-descriptors + // which wouldn't work for media queries since they remove all but the + // first word. However, they're changed in + // http://www.whatwg.org/specs/web-apps/current-work/multipage/section-document.html#media2 + // (as of 2008-05-29) which says that the media attribute just points + // to a media query. (The main substative difference is the relative + // precedence of commas and paretheses.) + + DebugOnly<bool> parsedOK = GatherMedia(aMediaList, false); + NS_ASSERTION(parsedOK, "GatherMedia returned false; we probably want to avoid " + "trashing aMediaList"); + + CLEAR_ERROR(); + ReleaseScanner(); + mHTMLMediaMode = false; +} + +// <source-size-list> = <source-size>#? +// <source-size> = <media-condition>? <length> +bool +CSSParserImpl::ParseSourceSizeList(const nsAString& aBuffer, + nsIURI* aURI, // for error reporting + uint32_t aLineNumber, // for error reporting + InfallibleTArray< nsAutoPtr<nsMediaQuery> >& aQueries, + InfallibleTArray<nsCSSValue>& aValues, + bool aHTMLMode) +{ + aQueries.Clear(); + aValues.Clear(); + + // fake base URI since media value lists don't have URIs in them + nsCSSScanner scanner(aBuffer, aLineNumber); + css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI); + InitScanner(scanner, reporter, aURI, aURI, nullptr); + + // See ParseMediaList comment about HTML mode + mHTMLMediaMode = aHTMLMode; + + // https://html.spec.whatwg.org/multipage/embedded-content.html#parse-a-sizes-attribute + bool hitEnd = false; + do { + bool hitError = false; + // Parse single <media-condition> <source-size-value> + do { + nsAutoPtr<nsMediaQuery> query; + nsCSSValue value; + + bool hitStop; + if (!ParseMediaQuery(eMediaQuerySingleCondition, getter_Transfers(query), + &hitStop)) { + NS_ASSERTION(!hitStop, "should return true when hit stop"); + hitError = true; + break; + } + + if (!query) { + REPORT_UNEXPECTED_EOF(PEParseSourceSizeListEOF); + NS_ASSERTION(hitStop, + "should return hitStop or an error if returning no query"); + hitError = true; + break; + } + + if (hitStop) { + // Empty conditions (e.g. just a bare value) should be treated as always + // matching (a query with no expressions fails to match, so a negated one + // always matches.) + query->SetNegated(); + } + + // https://html.spec.whatwg.org/multipage/embedded-content.html#source-size-value + // Percentages are not allowed in a <source-size-value>, to avoid + // confusion about what it would be relative to. + if (ParseNonNegativeVariant(value, VARIANT_LCALC, nullptr) != + CSSParseResult::Ok) { + hitError = true; + break; + } + + if (GetToken(true)) { + if (!mToken.IsSymbol(',')) { + REPORT_UNEXPECTED_TOKEN(PEParseSourceSizeListNotComma); + hitError = true; + break; + } + } else { + hitEnd = true; + } + + aQueries.AppendElement(query.forget()); + aValues.AppendElement(value); + } while(0); + + if (hitError) { + OUTPUT_ERROR(); + + // Per spec, we just skip the current entry if there was a parse error. + // Jumps to next entry of <source-size-list> which is a comma-separated list. + if (!SkipUntil(',')) { + hitEnd = true; + } + } + } while (!hitEnd); + + CLEAR_ERROR(); + ReleaseScanner(); + mHTMLMediaMode = false; + + return !aQueries.IsEmpty(); +} + +bool +CSSParserImpl::ParseColorString(const nsSubstring& aBuffer, + nsIURI* aURI, // for error reporting + uint32_t aLineNumber, // for error reporting + nsCSSValue& aValue, + bool aSuppressErrors /* false */) +{ + nsCSSScanner scanner(aBuffer, aLineNumber); + css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI); + InitScanner(scanner, reporter, aURI, aURI, nullptr); + + nsAutoSuppressErrors suppressErrors(this, aSuppressErrors); + + // Parse a color, and check that there's nothing else after it. + bool colorParsed = ParseColor(aValue) == CSSParseResult::Ok && + !GetToken(true); + + if (aSuppressErrors) { + CLEAR_ERROR(); + } else { + OUTPUT_ERROR(); + } + + ReleaseScanner(); + return colorParsed; +} + +bool +CSSParserImpl::ParseMarginString(const nsSubstring& aBuffer, + nsIURI* aURI, // for error reporting + uint32_t aLineNumber, // for error reporting + nsCSSValue& aValue, + bool aSuppressErrors /* false */) +{ + nsCSSScanner scanner(aBuffer, aLineNumber); + css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI); + InitScanner(scanner, reporter, aURI, aURI, nullptr); + + nsAutoSuppressErrors suppressErrors(this, aSuppressErrors); + + // Parse a margin, and check that there's nothing else after it. + bool marginParsed = ParseGroupedBoxProperty(VARIANT_LP, aValue, 0) && !GetToken(true); + + if (aSuppressErrors) { + CLEAR_ERROR(); + } else { + OUTPUT_ERROR(); + } + + ReleaseScanner(); + return marginParsed; +} + +bool +CSSParserImpl::ParseFontFamilyListString(const nsSubstring& aBuffer, + nsIURI* aURI, // for error reporting + uint32_t aLineNumber, // for error reporting + nsCSSValue& aValue) +{ + nsCSSScanner scanner(aBuffer, aLineNumber); + css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI); + InitScanner(scanner, reporter, aURI, aURI, nullptr); + + // Parse a font family list, and check that there's nothing else after it. + bool familyParsed = ParseFamily(aValue) && !GetToken(true); + OUTPUT_ERROR(); + ReleaseScanner(); + return familyParsed; +} + +nsresult +CSSParserImpl::ParseSelectorString(const nsSubstring& aSelectorString, + nsIURI* aURI, // for error reporting + uint32_t aLineNumber, // for error reporting + nsCSSSelectorList **aSelectorList) +{ + nsCSSScanner scanner(aSelectorString, aLineNumber); + css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI); + InitScanner(scanner, reporter, aURI, aURI, nullptr); + + bool success = ParseSelectorList(*aSelectorList, char16_t(0)); + + // We deliberately do not call OUTPUT_ERROR here, because all our + // callers map a failure return to a JS exception, and if that JS + // exception is caught, people don't want to see parser diagnostics; + // see e.g. http://bugs.jquery.com/ticket/7535 + // It would be nice to be able to save the parser diagnostics into + // the exception, so that if it _isn't_ caught we can report them + // along with the usual uncaught-exception message, but we don't + // have any way to do that at present; see bug 631621. + CLEAR_ERROR(); + ReleaseScanner(); + + if (success) { + NS_ASSERTION(*aSelectorList, "Should have list!"); + return NS_OK; + } + + NS_ASSERTION(!*aSelectorList, "Shouldn't have list!"); + + return NS_ERROR_DOM_SYNTAX_ERR; +} + + +already_AddRefed<nsCSSKeyframeRule> +CSSParserImpl::ParseKeyframeRule(const nsSubstring& aBuffer, + nsIURI* aURI, + uint32_t aLineNumber) +{ + nsCSSScanner scanner(aBuffer, aLineNumber); + css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI); + InitScanner(scanner, reporter, aURI, aURI, nullptr); + + RefPtr<nsCSSKeyframeRule> result = ParseKeyframeRule(); + if (GetToken(true)) { + // extra garbage at the end + result = nullptr; + } + + OUTPUT_ERROR(); + ReleaseScanner(); + + return result.forget(); +} + +bool +CSSParserImpl::ParseKeyframeSelectorString(const nsSubstring& aSelectorString, + nsIURI* aURI, // for error reporting + uint32_t aLineNumber, // for error reporting + InfallibleTArray<float>& aSelectorList) +{ + MOZ_ASSERT(aSelectorList.IsEmpty(), "given list should start empty"); + + nsCSSScanner scanner(aSelectorString, aLineNumber); + css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI); + InitScanner(scanner, reporter, aURI, aURI, nullptr); + + bool success = ParseKeyframeSelectorList(aSelectorList) && + // must consume entire input string + !GetToken(true); + + OUTPUT_ERROR(); + ReleaseScanner(); + + if (success) { + NS_ASSERTION(!aSelectorList.IsEmpty(), "should not be empty"); + } else { + aSelectorList.Clear(); + } + + return success; +} + +bool +CSSParserImpl::EvaluateSupportsDeclaration(const nsAString& aProperty, + const nsAString& aValue, + nsIURI* aDocURL, + nsIURI* aBaseURL, + nsIPrincipal* aDocPrincipal) +{ + nsCSSPropertyID propID = LookupEnabledProperty(aProperty); + if (propID == eCSSProperty_UNKNOWN) { + return false; + } + + nsCSSScanner scanner(aValue, 0); + css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aDocURL); + InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal); + nsAutoSuppressErrors suppressErrors(this); + + bool parsedOK; + + if (propID == eCSSPropertyExtra_variable) { + MOZ_ASSERT(Substring(aProperty, 0, + CSS_CUSTOM_NAME_PREFIX_LENGTH).EqualsLiteral("--")); + const nsDependentSubstring varName = + Substring(aProperty, CSS_CUSTOM_NAME_PREFIX_LENGTH); // remove '--' + CSSVariableDeclarations::Type variableType; + nsString variableValue; + parsedOK = ParseVariableDeclaration(&variableType, variableValue) && + !GetToken(true); + } else { + parsedOK = ParseProperty(propID) && !GetToken(true); + + mTempData.ClearProperty(propID); + mTempData.AssertInitialState(); + } + + CLEAR_ERROR(); + ReleaseScanner(); + + return parsedOK; +} + +bool +CSSParserImpl::EvaluateSupportsCondition(const nsAString& aDeclaration, + nsIURI* aDocURL, + nsIURI* aBaseURL, + nsIPrincipal* aDocPrincipal) +{ + nsCSSScanner scanner(aDeclaration, 0); + css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aDocURL); + InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal); + nsAutoSuppressErrors suppressErrors(this); + + bool conditionMet; + bool parsedOK = ParseSupportsCondition(conditionMet) && !GetToken(true); + + CLEAR_ERROR(); + ReleaseScanner(); + + return parsedOK && conditionMet; +} + +bool +CSSParserImpl::EnumerateVariableReferences(const nsAString& aPropertyValue, + VariableEnumFunc aFunc, + void* aData) +{ + nsCSSScanner scanner(aPropertyValue, 0); + css::ErrorReporter reporter(scanner, nullptr, nullptr, nullptr); + InitScanner(scanner, reporter, nullptr, nullptr, nullptr); + nsAutoSuppressErrors suppressErrors(this); + + CSSVariableDeclarations::Type type; + bool dropBackslash; + nsString impliedCharacters; + bool result = ParseValueWithVariables(&type, &dropBackslash, + impliedCharacters, aFunc, aData) && + !GetToken(true); + + ReleaseScanner(); + + return result; +} + +static bool +SeparatorRequiredBetweenTokens(nsCSSTokenSerializationType aToken1, + nsCSSTokenSerializationType aToken2) +{ + // The two lines marked with (*) do not correspond to entries in + // the table in the css-syntax spec but which we need to handle, + // as we treat them as whole tokens. + switch (aToken1) { + case eCSSTokenSerialization_Ident: + return aToken2 == eCSSTokenSerialization_Ident || + aToken2 == eCSSTokenSerialization_Function || + aToken2 == eCSSTokenSerialization_URL_or_BadURL || + aToken2 == eCSSTokenSerialization_Symbol_Minus || + aToken2 == eCSSTokenSerialization_Number || + aToken2 == eCSSTokenSerialization_Percentage || + aToken2 == eCSSTokenSerialization_Dimension || + aToken2 == eCSSTokenSerialization_URange || + aToken2 == eCSSTokenSerialization_CDC || + aToken2 == eCSSTokenSerialization_Symbol_OpenParen; + case eCSSTokenSerialization_AtKeyword_or_Hash: + case eCSSTokenSerialization_Dimension: + return aToken2 == eCSSTokenSerialization_Ident || + aToken2 == eCSSTokenSerialization_Function || + aToken2 == eCSSTokenSerialization_URL_or_BadURL || + aToken2 == eCSSTokenSerialization_Symbol_Minus || + aToken2 == eCSSTokenSerialization_Number || + aToken2 == eCSSTokenSerialization_Percentage || + aToken2 == eCSSTokenSerialization_Dimension || + aToken2 == eCSSTokenSerialization_URange || + aToken2 == eCSSTokenSerialization_CDC; + case eCSSTokenSerialization_Symbol_Hash: + case eCSSTokenSerialization_Symbol_Minus: + return aToken2 == eCSSTokenSerialization_Ident || + aToken2 == eCSSTokenSerialization_Function || + aToken2 == eCSSTokenSerialization_URL_or_BadURL || + aToken2 == eCSSTokenSerialization_Symbol_Minus || + aToken2 == eCSSTokenSerialization_Number || + aToken2 == eCSSTokenSerialization_Percentage || + aToken2 == eCSSTokenSerialization_Dimension || + aToken2 == eCSSTokenSerialization_URange; + case eCSSTokenSerialization_Number: + return aToken2 == eCSSTokenSerialization_Ident || + aToken2 == eCSSTokenSerialization_Function || + aToken2 == eCSSTokenSerialization_URL_or_BadURL || + aToken2 == eCSSTokenSerialization_Number || + aToken2 == eCSSTokenSerialization_Percentage || + aToken2 == eCSSTokenSerialization_Dimension || + aToken2 == eCSSTokenSerialization_URange; + case eCSSTokenSerialization_Symbol_At: + return aToken2 == eCSSTokenSerialization_Ident || + aToken2 == eCSSTokenSerialization_Function || + aToken2 == eCSSTokenSerialization_URL_or_BadURL || + aToken2 == eCSSTokenSerialization_Symbol_Minus || + aToken2 == eCSSTokenSerialization_URange; + case eCSSTokenSerialization_URange: + return aToken2 == eCSSTokenSerialization_Ident || + aToken2 == eCSSTokenSerialization_Function || + aToken2 == eCSSTokenSerialization_Number || + aToken2 == eCSSTokenSerialization_Percentage || + aToken2 == eCSSTokenSerialization_Dimension || + aToken2 == eCSSTokenSerialization_Symbol_Question; + case eCSSTokenSerialization_Symbol_Dot_or_Plus: + return aToken2 == eCSSTokenSerialization_Number || + aToken2 == eCSSTokenSerialization_Percentage || + aToken2 == eCSSTokenSerialization_Dimension; + case eCSSTokenSerialization_Symbol_Assorted: + case eCSSTokenSerialization_Symbol_Asterisk: + return aToken2 == eCSSTokenSerialization_Symbol_Equals; + case eCSSTokenSerialization_Symbol_Bar: + return aToken2 == eCSSTokenSerialization_Symbol_Equals || + aToken2 == eCSSTokenSerialization_Symbol_Bar || + aToken2 == eCSSTokenSerialization_DashMatch; // (*) + case eCSSTokenSerialization_Symbol_Slash: + return aToken2 == eCSSTokenSerialization_Symbol_Asterisk || + aToken2 == eCSSTokenSerialization_ContainsMatch; // (*) + default: + MOZ_ASSERT(aToken1 == eCSSTokenSerialization_Nothing || + aToken1 == eCSSTokenSerialization_Whitespace || + aToken1 == eCSSTokenSerialization_Percentage || + aToken1 == eCSSTokenSerialization_URL_or_BadURL || + aToken1 == eCSSTokenSerialization_Function || + aToken1 == eCSSTokenSerialization_CDC || + aToken1 == eCSSTokenSerialization_Symbol_OpenParen || + aToken1 == eCSSTokenSerialization_Symbol_Question || + aToken1 == eCSSTokenSerialization_Symbol_Assorted || + aToken1 == eCSSTokenSerialization_Symbol_Asterisk || + aToken1 == eCSSTokenSerialization_Symbol_Equals || + aToken1 == eCSSTokenSerialization_Symbol_Bar || + aToken1 == eCSSTokenSerialization_Symbol_Slash || + aToken1 == eCSSTokenSerialization_Other, + "unexpected nsCSSTokenSerializationType value"); + return false; + } +} + +/** + * Appends aValue to aResult, possibly inserting an empty CSS + * comment between the two to ensure that tokens from both strings + * remain separated. + */ +static void +AppendTokens(nsAString& aResult, + nsCSSTokenSerializationType& aResultFirstToken, + nsCSSTokenSerializationType& aResultLastToken, + nsCSSTokenSerializationType aValueFirstToken, + nsCSSTokenSerializationType aValueLastToken, + const nsAString& aValue) +{ + if (SeparatorRequiredBetweenTokens(aResultLastToken, aValueFirstToken)) { + aResult.AppendLiteral("/**/"); + } + aResult.Append(aValue); + if (aResultFirstToken == eCSSTokenSerialization_Nothing) { + aResultFirstToken = aValueFirstToken; + } + if (aValueLastToken != eCSSTokenSerialization_Nothing) { + aResultLastToken = aValueLastToken; + } +} + +/** + * Stops the given scanner recording, and appends the recorded result + * to aResult, possibly inserting an empty CSS comment between the two to + * ensure that tokens from both strings remain separated. + */ +static void +StopRecordingAndAppendTokens(nsString& aResult, + nsCSSTokenSerializationType& aResultFirstToken, + nsCSSTokenSerializationType& aResultLastToken, + nsCSSTokenSerializationType aValueFirstToken, + nsCSSTokenSerializationType aValueLastToken, + nsCSSScanner* aScanner) +{ + if (SeparatorRequiredBetweenTokens(aResultLastToken, aValueFirstToken)) { + aResult.AppendLiteral("/**/"); + } + aScanner->StopRecording(aResult); + if (aResultFirstToken == eCSSTokenSerialization_Nothing) { + aResultFirstToken = aValueFirstToken; + } + if (aValueLastToken != eCSSTokenSerialization_Nothing) { + aResultLastToken = aValueLastToken; + } +} + +bool +CSSParserImpl::ResolveValueWithVariableReferencesRec( + nsString& aResult, + nsCSSTokenSerializationType& aResultFirstToken, + nsCSSTokenSerializationType& aResultLastToken, + const CSSVariableValues* aVariables) +{ + // This function assumes we are already recording, and will leave the scanner + // recording when it returns. + MOZ_ASSERT(mScanner->IsRecording()); + MOZ_ASSERT(aResult.IsEmpty()); + + // Stack of closing characters for currently open constructs. + AutoTArray<char16_t, 16> stack; + + // The resolved value for this ResolveValueWithVariableReferencesRec call. + nsString value; + + // The length of the scanner's recording before the currently parsed token. + // This is used so that when we encounter a "var(" token, we can strip + // it off the end of the recording, regardless of how long the token was. + // (With escapes, it could be longer than four characters.) + uint32_t lengthBeforeVar = 0; + + // Tracking the type of token that appears at the start and end of |value| + // and that appears at the start and end of the scanner recording. These are + // used to determine whether we need to insert "/**/" when pasting token + // streams together. + nsCSSTokenSerializationType valueFirstToken = eCSSTokenSerialization_Nothing, + valueLastToken = eCSSTokenSerialization_Nothing, + recFirstToken = eCSSTokenSerialization_Nothing, + recLastToken = eCSSTokenSerialization_Nothing; + +#define UPDATE_RECORDING_TOKENS(type) \ + if (recFirstToken == eCSSTokenSerialization_Nothing) { \ + recFirstToken = type; \ + } \ + recLastToken = type; + + while (GetToken(false)) { + switch (mToken.mType) { + case eCSSToken_Symbol: { + nsCSSTokenSerializationType type = eCSSTokenSerialization_Other; + if (mToken.mSymbol == '(') { + stack.AppendElement(')'); + type = eCSSTokenSerialization_Symbol_OpenParen; + } else if (mToken.mSymbol == '[') { + stack.AppendElement(']'); + } else if (mToken.mSymbol == '{') { + stack.AppendElement('}'); + } else if (mToken.mSymbol == ';') { + if (stack.IsEmpty()) { + // A ';' that is at the top level of the value or at the top level + // of a variable reference's fallback is invalid. + return false; + } + } else if (mToken.mSymbol == '!') { + if (stack.IsEmpty()) { + // An '!' that is at the top level of the value or at the top level + // of a variable reference's fallback is invalid. + return false; + } + } else if (mToken.mSymbol == ')' && + stack.IsEmpty()) { + // We're closing a "var(". + nsString finalTokens; + mScanner->StopRecording(finalTokens); + MOZ_ASSERT(finalTokens[finalTokens.Length() - 1] == ')'); + finalTokens.Truncate(finalTokens.Length() - 1); + aResult.Append(value); + + AppendTokens(aResult, valueFirstToken, valueLastToken, + recFirstToken, recLastToken, finalTokens); + + mScanner->StartRecording(); + UngetToken(); + aResultFirstToken = valueFirstToken; + aResultLastToken = valueLastToken; + return true; + } else if (mToken.mSymbol == ')' || + mToken.mSymbol == ']' || + mToken.mSymbol == '}') { + if (stack.IsEmpty() || + stack.LastElement() != mToken.mSymbol) { + // A mismatched closing bracket is invalid. + return false; + } + stack.TruncateLength(stack.Length() - 1); + } else if (mToken.mSymbol == '#') { + type = eCSSTokenSerialization_Symbol_Hash; + } else if (mToken.mSymbol == '@') { + type = eCSSTokenSerialization_Symbol_At; + } else if (mToken.mSymbol == '.' || + mToken.mSymbol == '+') { + type = eCSSTokenSerialization_Symbol_Dot_or_Plus; + } else if (mToken.mSymbol == '-') { + type = eCSSTokenSerialization_Symbol_Minus; + } else if (mToken.mSymbol == '?') { + type = eCSSTokenSerialization_Symbol_Question; + } else if (mToken.mSymbol == '$' || + mToken.mSymbol == '^' || + mToken.mSymbol == '~') { + type = eCSSTokenSerialization_Symbol_Assorted; + } else if (mToken.mSymbol == '=') { + type = eCSSTokenSerialization_Symbol_Equals; + } else if (mToken.mSymbol == '|') { + type = eCSSTokenSerialization_Symbol_Bar; + } else if (mToken.mSymbol == '/') { + type = eCSSTokenSerialization_Symbol_Slash; + } else if (mToken.mSymbol == '*') { + type = eCSSTokenSerialization_Symbol_Asterisk; + } + UPDATE_RECORDING_TOKENS(type); + break; + } + + case eCSSToken_Function: + if (mToken.mIdent.LowerCaseEqualsLiteral("var")) { + // Save the tokens before the "var(" to our resolved value. + nsString recording; + mScanner->StopRecording(recording); + recording.Truncate(lengthBeforeVar); + AppendTokens(value, valueFirstToken, valueLastToken, + recFirstToken, recLastToken, recording); + recFirstToken = eCSSTokenSerialization_Nothing; + recLastToken = eCSSTokenSerialization_Nothing; + + if (!GetToken(true) || + mToken.mType != eCSSToken_Ident || + !nsCSSProps::IsCustomPropertyName(mToken.mIdent)) { + // "var(" must be followed by an identifier, and it must be a + // custom property name. + return false; + } + + // Turn the custom property name into a variable name by removing the + // '--' prefix. + MOZ_ASSERT(Substring(mToken.mIdent, 0, + CSS_CUSTOM_NAME_PREFIX_LENGTH). + EqualsLiteral("--")); + nsDependentString variableName(mToken.mIdent, + CSS_CUSTOM_NAME_PREFIX_LENGTH); + + // Get the value of the identified variable. Note that we + // check if the variable value is the empty string, as that means + // that the variable was invalid at computed value time due to + // unresolveable variable references or cycles. + nsString variableValue; + nsCSSTokenSerializationType varFirstToken, varLastToken; + bool valid = aVariables->Get(variableName, variableValue, + varFirstToken, varLastToken) && + !variableValue.IsEmpty(); + + if (!GetToken(true) || + mToken.IsSymbol(')')) { + mScanner->StartRecording(); + if (!valid) { + // Invalid variable with no fallback. + return false; + } + // Valid variable with no fallback. + AppendTokens(value, valueFirstToken, valueLastToken, + varFirstToken, varLastToken, variableValue); + } else if (mToken.IsSymbol(',')) { + mScanner->StartRecording(); + if (!GetToken(false) || + mToken.IsSymbol(')')) { + // Comma must be followed by at least one fallback token. + return false; + } + UngetToken(); + if (valid) { + // Valid variable with ignored fallback. + mScanner->StopRecording(); + AppendTokens(value, valueFirstToken, valueLastToken, + varFirstToken, varLastToken, variableValue); + bool ok = SkipBalancedContentUntil(')'); + mScanner->StartRecording(); + if (!ok) { + return false; + } + } else { + nsString fallback; + if (!ResolveValueWithVariableReferencesRec(fallback, + varFirstToken, + varLastToken, + aVariables)) { + // Fallback value had invalid tokens or an invalid variable reference + // that itself had no fallback. + return false; + } + AppendTokens(value, valueFirstToken, valueLastToken, + varFirstToken, varLastToken, fallback); + // Now we're either at the pushed back ')' that finished the + // fallback or at EOF. + DebugOnly<bool> gotToken = GetToken(false); + MOZ_ASSERT(!gotToken || mToken.IsSymbol(')')); + } + } else { + // Expected ',' or ')' after the variable name. + mScanner->StartRecording(); + return false; + } + } else { + stack.AppendElement(')'); + UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Function); + } + break; + + case eCSSToken_Bad_String: + case eCSSToken_Bad_URL: + return false; + + case eCSSToken_Whitespace: + UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Whitespace); + break; + + case eCSSToken_AtKeyword: + case eCSSToken_Hash: + UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_AtKeyword_or_Hash); + break; + + case eCSSToken_Number: + UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Number); + break; + + case eCSSToken_Dimension: + UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Dimension); + break; + + case eCSSToken_Ident: + UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Ident); + break; + + case eCSSToken_Percentage: + UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Percentage); + break; + + case eCSSToken_URange: + UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_URange); + break; + + case eCSSToken_URL: + UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_URL_or_BadURL); + break; + + case eCSSToken_HTMLComment: + if (mToken.mIdent[0] == '-') { + UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_CDC); + } else { + UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Other); + } + break; + + case eCSSToken_Dashmatch: + UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_DashMatch); + break; + + case eCSSToken_Containsmatch: + UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_ContainsMatch); + break; + + default: + MOZ_FALLTHROUGH_ASSERT("unexpected token type"); + case eCSSToken_ID: + case eCSSToken_String: + case eCSSToken_Includes: + case eCSSToken_Beginsmatch: + case eCSSToken_Endsmatch: + UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Other); + break; + } + + lengthBeforeVar = mScanner->RecordingLength(); + } + +#undef UPDATE_RECORDING_TOKENS + + aResult.Append(value); + StopRecordingAndAppendTokens(aResult, valueFirstToken, valueLastToken, + recFirstToken, recLastToken, mScanner); + + // Append any implicitly closed brackets. + if (!stack.IsEmpty()) { + do { + aResult.Append(stack.LastElement()); + stack.TruncateLength(stack.Length() - 1); + } while (!stack.IsEmpty()); + valueLastToken = eCSSTokenSerialization_Other; + } + + mScanner->StartRecording(); + aResultFirstToken = valueFirstToken; + aResultLastToken = valueLastToken; + return true; +} + +bool +CSSParserImpl::ResolveValueWithVariableReferences( + const CSSVariableValues* aVariables, + nsString& aResult, + nsCSSTokenSerializationType& aFirstToken, + nsCSSTokenSerializationType& aLastToken) +{ + aResult.Truncate(0); + + // Start recording before we read the first token. + mScanner->StartRecording(); + + if (!GetToken(false)) { + // Value was empty since we reached EOF. + mScanner->StopRecording(); + return false; + } + + UngetToken(); + + nsString value; + nsCSSTokenSerializationType firstToken, lastToken; + bool ok = ResolveValueWithVariableReferencesRec(value, firstToken, lastToken, aVariables) && + !GetToken(true); + + mScanner->StopRecording(); + + if (ok) { + aResult = value; + aFirstToken = firstToken; + aLastToken = lastToken; + } + return ok; +} + +bool +CSSParserImpl::ResolveVariableValue(const nsAString& aPropertyValue, + const CSSVariableValues* aVariables, + nsString& aResult, + nsCSSTokenSerializationType& aFirstToken, + nsCSSTokenSerializationType& aLastToken) +{ + nsCSSScanner scanner(aPropertyValue, 0); + + // At this point, we know that aPropertyValue is syntactically correct + // for a token stream that has variable references. We also won't be + // interpreting any of the stream as we parse it, apart from expanding + // var() references, so we don't need a base URL etc. or any useful + // error reporting. + css::ErrorReporter reporter(scanner, nullptr, nullptr, nullptr); + InitScanner(scanner, reporter, nullptr, nullptr, nullptr); + + bool valid = ResolveValueWithVariableReferences(aVariables, aResult, + aFirstToken, aLastToken); + + ReleaseScanner(); + return valid; +} + +void +CSSParserImpl::ParsePropertyWithVariableReferences( + nsCSSPropertyID aPropertyID, + nsCSSPropertyID aShorthandPropertyID, + const nsAString& aValue, + const CSSVariableValues* aVariables, + nsRuleData* aRuleData, + nsIURI* aDocURL, + nsIURI* aBaseURL, + nsIPrincipal* aDocPrincipal, + CSSStyleSheet* aSheet, + uint32_t aLineNumber, + uint32_t aLineOffset) +{ + mTempData.AssertInitialState(); + + bool valid; + nsString expandedValue; + + // Resolve any variable references in the property value. + { + nsCSSScanner scanner(aValue, 0); + css::ErrorReporter reporter(scanner, aSheet, mChildLoader, aDocURL); + InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal); + + nsCSSTokenSerializationType firstToken, lastToken; + valid = ResolveValueWithVariableReferences(aVariables, expandedValue, + firstToken, lastToken); + if (!valid) { + NS_ConvertASCIItoUTF16 propName(nsCSSProps::GetStringValue(aPropertyID)); + REPORT_UNEXPECTED(PEInvalidVariableReference); + REPORT_UNEXPECTED_P(PEValueParsingError, propName); + if (nsCSSProps::IsInherited(aPropertyID)) { + REPORT_UNEXPECTED(PEValueWithVariablesFallbackInherit); + } else { + REPORT_UNEXPECTED(PEValueWithVariablesFallbackInitial); + } + OUTPUT_ERROR_WITH_POSITION(aLineNumber, aLineOffset); + } + ReleaseScanner(); + } + + nsCSSPropertyID propertyToParse = + aShorthandPropertyID != eCSSProperty_UNKNOWN ? aShorthandPropertyID : + aPropertyID; + + // Parse the property with that resolved value. + if (valid) { + nsCSSScanner scanner(expandedValue, 0); + css::ErrorReporter reporter(scanner, aSheet, mChildLoader, aDocURL); + InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal); + valid = ParseProperty(propertyToParse); + if (valid && GetToken(true)) { + REPORT_UNEXPECTED_TOKEN(PEExpectEndValue); + valid = false; + } + if (!valid) { + NS_ConvertASCIItoUTF16 propName(nsCSSProps::GetStringValue( + propertyToParse)); + REPORT_UNEXPECTED_P_V(PEValueWithVariablesParsingErrorInValue, + propName, expandedValue); + if (nsCSSProps::IsInherited(aPropertyID)) { + REPORT_UNEXPECTED(PEValueWithVariablesFallbackInherit); + } else { + REPORT_UNEXPECTED(PEValueWithVariablesFallbackInitial); + } + OUTPUT_ERROR_WITH_POSITION(aLineNumber, aLineOffset); + } + ReleaseScanner(); + } + + // If the property could not be parsed with the resolved value, then we + // treat it as if the value were 'initial' or 'inherit', depending on whether + // the property is an inherited property. + if (!valid) { + nsCSSValue defaultValue; + if (nsCSSProps::IsInherited(aPropertyID)) { + defaultValue.SetInheritValue(); + } else { + defaultValue.SetInitialValue(); + } + mTempData.AddLonghandProperty(aPropertyID, defaultValue); + } + + // Copy the property value into the rule data. + mTempData.MapRuleInfoInto(aPropertyID, aRuleData); + + mTempData.ClearProperty(propertyToParse); + mTempData.AssertInitialState(); +} + +bool +CSSParserImpl::ParseCounterStyleName(const nsAString& aBuffer, + nsIURI* aURL, + nsAString& aName) +{ + nsCSSScanner scanner(aBuffer, 0); + css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURL); + InitScanner(scanner, reporter, aURL, aURL, nullptr); + + bool success = ParseCounterStyleName(aName, true) && !GetToken(true); + + OUTPUT_ERROR(); + ReleaseScanner(); + + return success; +} + +bool +CSSParserImpl::ParseCounterDescriptor(nsCSSCounterDesc aDescID, + const nsAString& aBuffer, + nsIURI* aSheetURL, + nsIURI* aBaseURL, + nsIPrincipal* aSheetPrincipal, + nsCSSValue& aValue) +{ + nsCSSScanner scanner(aBuffer, 0); + css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURL); + InitScanner(scanner, reporter, aSheetURL, aBaseURL, aSheetPrincipal); + + bool success = ParseCounterDescriptorValue(aDescID, aValue) && + !GetToken(true); + + OUTPUT_ERROR(); + ReleaseScanner(); + + return success; +} + +bool +CSSParserImpl::ParseFontFaceDescriptor(nsCSSFontDesc aDescID, + const nsAString& aBuffer, + nsIURI* aSheetURL, + nsIURI* aBaseURL, + nsIPrincipal* aSheetPrincipal, + nsCSSValue& aValue) +{ + nsCSSScanner scanner(aBuffer, 0); + css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURL); + InitScanner(scanner, reporter, aSheetURL, aBaseURL, aSheetPrincipal); + + bool success = ParseFontDescriptorValue(aDescID, aValue) && + !GetToken(true); + + OUTPUT_ERROR(); + ReleaseScanner(); + + return success; +} + +//---------------------------------------------------------------------- + +bool +CSSParserImpl::GetToken(bool aSkipWS) +{ + if (mHavePushBack) { + mHavePushBack = false; + if (!aSkipWS || mToken.mType != eCSSToken_Whitespace) { + return true; + } + } + return mScanner->Next(mToken, aSkipWS ? + eCSSScannerExclude_WhitespaceAndComments : + eCSSScannerExclude_Comments); +} + +void +CSSParserImpl::UngetToken() +{ + NS_PRECONDITION(!mHavePushBack, "double pushback"); + mHavePushBack = true; +} + +bool +CSSParserImpl::GetNextTokenLocation(bool aSkipWS, uint32_t *linenum, uint32_t *colnum) +{ + // Peek at next token so that mScanner updates line and column vals + if (!GetToken(aSkipWS)) { + return false; + } + UngetToken(); + // The scanner uses one-indexing for line numbers but zero-indexing + // for column numbers. + *linenum = mScanner->GetLineNumber(); + *colnum = 1 + mScanner->GetColumnNumber(); + return true; +} + +bool +CSSParserImpl::ExpectSymbol(char16_t aSymbol, + bool aSkipWS) +{ + if (!GetToken(aSkipWS)) { + // CSS2.1 specifies that all "open constructs" are to be closed at + // EOF. It simplifies higher layers if we claim to have found an + // ), ], }, or ; if we encounter EOF while looking for one of them. + // Do still issue a diagnostic, to aid debugging. + if (aSymbol == ')' || aSymbol == ']' || + aSymbol == '}' || aSymbol == ';') { + REPORT_UNEXPECTED_EOF_CHAR(aSymbol); + return true; + } + else + return false; + } + if (mToken.IsSymbol(aSymbol)) { + return true; + } + UngetToken(); + return false; +} + +// Checks to see if we're at the end of a property. If an error occurs during +// the check, does not signal a parse error. +bool +CSSParserImpl::CheckEndProperty() +{ + if (!GetToken(true)) { + return true; // properties may end with eof + } + if ((eCSSToken_Symbol == mToken.mType) && + ((';' == mToken.mSymbol) || + ('!' == mToken.mSymbol) || + ('}' == mToken.mSymbol) || + (')' == mToken.mSymbol))) { + // XXX need to verify that ! is only followed by "important [;|}] + // XXX this requires a multi-token pushback buffer + UngetToken(); + return true; + } + UngetToken(); + return false; +} + +// Checks if we're at the end of a property, raising an error if we're not. +bool +CSSParserImpl::ExpectEndProperty() +{ + if (CheckEndProperty()) + return true; + + // If we're here, we read something incorrect, so we should report it. + REPORT_UNEXPECTED_TOKEN(PEExpectEndValue); + return false; +} + +// Parses the priority suffix on a property, which at present may be +// either '!important' or nothing. +CSSParserImpl::PriorityParsingStatus +CSSParserImpl::ParsePriority() +{ + if (!GetToken(true)) { + return ePriority_None; // properties may end with EOF + } + if (!mToken.IsSymbol('!')) { + UngetToken(); + return ePriority_None; // dunno what it is, but it's not a priority + } + + if (!GetToken(true)) { + // EOF is not ok after ! + REPORT_UNEXPECTED_EOF(PEImportantEOF); + return ePriority_Error; + } + + if (mToken.mType != eCSSToken_Ident || + !mToken.mIdent.LowerCaseEqualsLiteral("important")) { + REPORT_UNEXPECTED_TOKEN(PEExpectedImportant); + UngetToken(); + return ePriority_Error; + } + + return ePriority_Important; +} + +nsSubstring* +CSSParserImpl::NextIdent() +{ + // XXX Error reporting? + if (!GetToken(true)) { + return nullptr; + } + if (eCSSToken_Ident != mToken.mType) { + UngetToken(); + return nullptr; + } + return &mToken.mIdent; +} + +bool +CSSParserImpl::SkipAtRule(bool aInsideBlock) +{ + for (;;) { + if (!GetToken(true)) { + REPORT_UNEXPECTED_EOF(PESkipAtRuleEOF2); + return false; + } + if (eCSSToken_Symbol == mToken.mType) { + char16_t symbol = mToken.mSymbol; + if (symbol == ';') { + break; + } + if (aInsideBlock && symbol == '}') { + // The closing } doesn't belong to us. + UngetToken(); + break; + } + if (symbol == '{') { + SkipUntil('}'); + break; + } else if (symbol == '(') { + SkipUntil(')'); + } else if (symbol == '[') { + SkipUntil(']'); + } + } else if (eCSSToken_Function == mToken.mType || + eCSSToken_Bad_URL == mToken.mType) { + SkipUntil(')'); + } + } + return true; +} + +bool +CSSParserImpl::ParseAtRule(RuleAppendFunc aAppendFunc, + void* aData, + bool aInAtRule) +{ + + nsCSSSection newSection; + bool (CSSParserImpl::*parseFunc)(RuleAppendFunc, void*); + + if ((mSection <= eCSSSection_Charset) && + (mToken.mIdent.LowerCaseEqualsLiteral("charset"))) { + parseFunc = &CSSParserImpl::ParseCharsetRule; + newSection = eCSSSection_Import; // only one charset allowed + + } else if ((mSection <= eCSSSection_Import) && + mToken.mIdent.LowerCaseEqualsLiteral("import")) { + parseFunc = &CSSParserImpl::ParseImportRule; + newSection = eCSSSection_Import; + + } else if ((mSection <= eCSSSection_NameSpace) && + mToken.mIdent.LowerCaseEqualsLiteral("namespace")) { + parseFunc = &CSSParserImpl::ParseNameSpaceRule; + newSection = eCSSSection_NameSpace; + + } else if (mToken.mIdent.LowerCaseEqualsLiteral("media")) { + parseFunc = &CSSParserImpl::ParseMediaRule; + newSection = eCSSSection_General; + + } else if (mToken.mIdent.LowerCaseEqualsLiteral("-moz-document")) { + parseFunc = &CSSParserImpl::ParseMozDocumentRule; + newSection = eCSSSection_General; + + } else if (mToken.mIdent.LowerCaseEqualsLiteral("font-face")) { + parseFunc = &CSSParserImpl::ParseFontFaceRule; + newSection = eCSSSection_General; + + } else if (mToken.mIdent.LowerCaseEqualsLiteral("font-feature-values")) { + parseFunc = &CSSParserImpl::ParseFontFeatureValuesRule; + newSection = eCSSSection_General; + + } else if (mToken.mIdent.LowerCaseEqualsLiteral("page")) { + parseFunc = &CSSParserImpl::ParsePageRule; + newSection = eCSSSection_General; + + } else if ((nsCSSProps::IsEnabled(eCSSPropertyAlias_MozAnimation, + EnabledState()) && + mToken.mIdent.LowerCaseEqualsLiteral("-moz-keyframes")) || + (nsCSSProps::IsEnabled(eCSSPropertyAlias_WebkitAnimation) && + mToken.mIdent.LowerCaseEqualsLiteral("-webkit-keyframes")) || + mToken.mIdent.LowerCaseEqualsLiteral("keyframes")) { + parseFunc = &CSSParserImpl::ParseKeyframesRule; + newSection = eCSSSection_General; + + } else if (mToken.mIdent.LowerCaseEqualsLiteral("supports")) { + parseFunc = &CSSParserImpl::ParseSupportsRule; + newSection = eCSSSection_General; + + } else if (mToken.mIdent.LowerCaseEqualsLiteral("counter-style")) { + parseFunc = &CSSParserImpl::ParseCounterStyleRule; + newSection = eCSSSection_General; + + } else { + if (!NonMozillaVendorIdentifier(mToken.mIdent)) { + REPORT_UNEXPECTED_TOKEN(PEUnknownAtRule); + OUTPUT_ERROR(); + } + // Skip over unsupported at rule, don't advance section + return SkipAtRule(aInAtRule); + } + + // Inside of @-rules, only the rules that can occur anywhere + // are allowed. + bool unnestable = aInAtRule && newSection != eCSSSection_General; + if (unnestable) { + REPORT_UNEXPECTED_TOKEN(PEGroupRuleNestedAtRule); + } + + if (unnestable || !(this->*parseFunc)(aAppendFunc, aData)) { + // Skip over invalid at rule, don't advance section + OUTPUT_ERROR(); + return SkipAtRule(aInAtRule); + } + + // Nested @-rules don't affect the top-level rule chain requirement + if (!aInAtRule) { + mSection = newSection; + } + + return true; +} + +bool +CSSParserImpl::ParseCharsetRule(RuleAppendFunc aAppendFunc, + void* aData) +{ + uint32_t linenum, colnum; + if (!GetNextTokenLocation(true, &linenum, &colnum) || + !GetToken(true)) { + REPORT_UNEXPECTED_EOF(PECharsetRuleEOF); + return false; + } + + if (eCSSToken_String != mToken.mType) { + UngetToken(); + REPORT_UNEXPECTED_TOKEN(PECharsetRuleNotString); + return false; + } + + nsAutoString charset = mToken.mIdent; + + if (!ExpectSymbol(';', true)) { + return false; + } + + // It's intentional that we don't create a rule object for @charset rules. + + return true; +} + +bool +CSSParserImpl::ParseURLOrString(nsString& aURL) +{ + if (!GetToken(true)) { + return false; + } + if (eCSSToken_String == mToken.mType || eCSSToken_URL == mToken.mType) { + aURL = mToken.mIdent; + return true; + } + UngetToken(); + return false; +} + +bool +CSSParserImpl::ParseMediaQuery(eMediaQueryType aQueryType, + nsMediaQuery **aQuery, + bool *aHitStop) +{ + *aQuery = nullptr; + *aHitStop = false; + bool inAtRule = aQueryType == eMediaQueryAtRule; + // Attempt to parse a single condition and stop + bool singleCondition = aQueryType == eMediaQuerySingleCondition; + + // "If the comma-separated list is the empty list it is assumed to + // specify the media query 'all'." (css3-mediaqueries, section + // "Media Queries") + if (!GetToken(true)) { + *aHitStop = true; + // expected termination by EOF + if (!inAtRule) + return true; + + // unexpected termination by EOF + REPORT_UNEXPECTED_EOF(PEGatherMediaEOF); + return true; + } + + if (eCSSToken_Symbol == mToken.mType && inAtRule && + (mToken.mSymbol == ';' || mToken.mSymbol == '{' || mToken.mSymbol == '}' )) { + *aHitStop = true; + UngetToken(); + return true; + } + UngetToken(); + + nsMediaQuery* query = new nsMediaQuery; + *aQuery = query; + + if (ExpectSymbol('(', true)) { + // we got an expression without a media type + UngetToken(); // so ParseMediaQueryExpression can handle it + query->SetType(nsGkAtoms::all); + query->SetTypeOmitted(); + // Just parse the first expression here. + if (!ParseMediaQueryExpression(query)) { + OUTPUT_ERROR(); + query->SetHadUnknownExpression(); + } + } else if (singleCondition) { + // Since we are only trying to consume a single condition, which precludes + // media types and not/only, this should be the same as reaching immediate + // EOF (no condition to parse) + *aHitStop = true; + return true; + } else { + nsCOMPtr<nsIAtom> mediaType; + bool gotNotOrOnly = false; + for (;;) { + if (!GetToken(true)) { + REPORT_UNEXPECTED_EOF(PEGatherMediaEOF); + return false; + } + if (eCSSToken_Ident != mToken.mType) { + REPORT_UNEXPECTED_TOKEN(PEGatherMediaNotIdent); + UngetToken(); + return false; + } + // case insensitive from CSS - must be lower cased + nsContentUtils::ASCIIToLower(mToken.mIdent); + mediaType = NS_Atomize(mToken.mIdent); + if (!gotNotOrOnly && mediaType == nsGkAtoms::_not) { + gotNotOrOnly = true; + query->SetNegated(); + } else if (!gotNotOrOnly && mediaType == nsGkAtoms::only) { + gotNotOrOnly = true; + query->SetHasOnly(); + } else if (mediaType == nsGkAtoms::_not || + mediaType == nsGkAtoms::only || + mediaType == nsGkAtoms::_and || + mediaType == nsGkAtoms::_or) { + REPORT_UNEXPECTED_TOKEN(PEGatherMediaReservedMediaType); + UngetToken(); + return false; + } else { + // valid media type + break; + } + } + query->SetType(mediaType); + } + + for (;;) { + if (!GetToken(true)) { + *aHitStop = true; + // expected termination by EOF + if (!inAtRule) + break; + + // unexpected termination by EOF + REPORT_UNEXPECTED_EOF(PEGatherMediaEOF); + break; + } + + if (eCSSToken_Symbol == mToken.mType && inAtRule && + (mToken.mSymbol == ';' || mToken.mSymbol == '{' || mToken.mSymbol == '}')) { + *aHitStop = true; + UngetToken(); + break; + } + if (!singleCondition && + eCSSToken_Symbol == mToken.mType && mToken.mSymbol == ',') { + // Done with the expressions for this query + break; + } + if (eCSSToken_Ident != mToken.mType || + !mToken.mIdent.LowerCaseEqualsLiteral("and")) { + if (singleCondition) { + // We have a condition at this point -- if we're not chained to other + // conditions with and/or, we're done. + UngetToken(); + break; + } else { + REPORT_UNEXPECTED_TOKEN(PEGatherMediaNotComma); + UngetToken(); + return false; + } + } + if (!ParseMediaQueryExpression(query)) { + OUTPUT_ERROR(); + query->SetHadUnknownExpression(); + } + } + return true; +} + +// Returns false only when there is a low-level error in the scanner +// (out-of-memory). +bool +CSSParserImpl::GatherMedia(nsMediaList* aMedia, + bool aInAtRule) +{ + eMediaQueryType type = aInAtRule ? eMediaQueryAtRule : eMediaQueryNormal; + for (;;) { + nsAutoPtr<nsMediaQuery> query; + bool hitStop; + if (!ParseMediaQuery(type, getter_Transfers(query), &hitStop)) { + NS_ASSERTION(!hitStop, "should return true when hit stop"); + OUTPUT_ERROR(); + if (query) { + query->SetHadUnknownExpression(); + } + if (aInAtRule) { + const char16_t stopChars[] = + { char16_t(','), char16_t('{'), char16_t(';'), char16_t('}'), char16_t(0) }; + SkipUntilOneOf(stopChars); + } else { + SkipUntil(','); + } + // Rely on SkipUntilOneOf leaving mToken around as the last token read. + if (mToken.mType == eCSSToken_Symbol && aInAtRule && + (mToken.mSymbol == '{' || mToken.mSymbol == ';' || mToken.mSymbol == '}')) { + UngetToken(); + hitStop = true; + } + } + if (query) { + aMedia->AppendQuery(query); + } + if (hitStop) { + break; + } + } + return true; +} + +bool +CSSParserImpl::ParseMediaQueryExpression(nsMediaQuery* aQuery) +{ + if (!ExpectSymbol('(', true)) { + REPORT_UNEXPECTED_TOKEN(PEMQExpectedExpressionStart); + return false; + } + if (! GetToken(true)) { + REPORT_UNEXPECTED_EOF(PEMQExpressionEOF); + return false; + } + if (eCSSToken_Ident != mToken.mType) { + REPORT_UNEXPECTED_TOKEN(PEMQExpectedFeatureName); + UngetToken(); + SkipUntil(')'); + return false; + } + + nsMediaExpression *expr = aQuery->NewExpression(); + + // case insensitive from CSS - must be lower cased + nsContentUtils::ASCIIToLower(mToken.mIdent); + nsDependentString featureString(mToken.mIdent, 0); + uint8_t satisfiedReqFlags = 0; + + // Strip off "-webkit-" prefix from featureString: + if (sWebkitPrefixedAliasesEnabled && + StringBeginsWith(featureString, NS_LITERAL_STRING("-webkit-"))) { + satisfiedReqFlags |= nsMediaFeature::eHasWebkitPrefix; + featureString.Rebind(featureString, 8); + } + if (sWebkitDevicePixelRatioEnabled) { + satisfiedReqFlags |= nsMediaFeature::eWebkitDevicePixelRatioPrefEnabled; + } + + // Strip off "min-"/"max-" prefix from featureString: + if (StringBeginsWith(featureString, NS_LITERAL_STRING("min-"))) { + expr->mRange = nsMediaExpression::eMin; + featureString.Rebind(featureString, 4); + } else if (StringBeginsWith(featureString, NS_LITERAL_STRING("max-"))) { + expr->mRange = nsMediaExpression::eMax; + featureString.Rebind(featureString, 4); + } else { + expr->mRange = nsMediaExpression::eEqual; + } + + nsCOMPtr<nsIAtom> mediaFeatureAtom = NS_Atomize(featureString); + const nsMediaFeature *feature = nsMediaFeatures::features; + for (; feature->mName; ++feature) { + // See if name matches & all requirement flags are satisfied: + // (We check requirements by turning off all of the flags that have been + // satisfied, and then see if the result is 0.) + if (*(feature->mName) == mediaFeatureAtom && + !(feature->mReqFlags & ~satisfiedReqFlags)) { + break; + } + } + if (!feature->mName || + (expr->mRange != nsMediaExpression::eEqual && + feature->mRangeType != nsMediaFeature::eMinMaxAllowed)) { + REPORT_UNEXPECTED_TOKEN(PEMQExpectedFeatureName); + SkipUntil(')'); + return false; + } + expr->mFeature = feature; + + if (!GetToken(true) || mToken.IsSymbol(')')) { + // Query expressions for any feature can be given without a value. + // However, min/max prefixes are not allowed. + if (expr->mRange != nsMediaExpression::eEqual) { + REPORT_UNEXPECTED(PEMQNoMinMaxWithoutValue); + return false; + } + expr->mValue.Reset(); + return true; + } + + if (!mToken.IsSymbol(':')) { + REPORT_UNEXPECTED_TOKEN(PEMQExpectedFeatureNameEnd); + UngetToken(); + SkipUntil(')'); + return false; + } + + bool rv = false; + switch (feature->mValueType) { + case nsMediaFeature::eLength: + rv = ParseSingleTokenNonNegativeVariant(expr->mValue, VARIANT_LENGTH, + nullptr); + break; + case nsMediaFeature::eInteger: + case nsMediaFeature::eBoolInteger: + rv = ParseNonNegativeInteger(expr->mValue); + // Enforce extra restrictions for eBoolInteger + if (rv && + feature->mValueType == nsMediaFeature::eBoolInteger && + expr->mValue.GetIntValue() > 1) + rv = false; + break; + case nsMediaFeature::eFloat: + rv = ParseNonNegativeNumber(expr->mValue); + break; + case nsMediaFeature::eIntRatio: + { + // Two integers separated by '/', with optional whitespace on + // either side of the '/'. + RefPtr<nsCSSValue::Array> a = nsCSSValue::Array::Create(2); + expr->mValue.SetArrayValue(a, eCSSUnit_Array); + // We don't bother with ParseNonNegativeVariant since we have to + // check for != 0 as well; no need to worry about the UngetToken + // since we're throwing out up to the next ')' anyway. + rv = ParseSingleTokenVariant(a->Item(0), VARIANT_INTEGER, nullptr) && + a->Item(0).GetIntValue() > 0 && + ExpectSymbol('/', true) && + ParseSingleTokenVariant(a->Item(1), VARIANT_INTEGER, nullptr) && + a->Item(1).GetIntValue() > 0; + } + break; + case nsMediaFeature::eResolution: + rv = GetToken(true); + if (!rv) + break; + rv = mToken.mType == eCSSToken_Dimension && mToken.mNumber > 0.0f; + if (!rv) { + UngetToken(); + break; + } + // No worries about whether unitless zero is allowed, since the + // value must be positive (and we checked that above). + NS_ASSERTION(!mToken.mIdent.IsEmpty(), "unit lied"); + if (mToken.mIdent.LowerCaseEqualsLiteral("dpi")) { + expr->mValue.SetFloatValue(mToken.mNumber, eCSSUnit_Inch); + } else if (mToken.mIdent.LowerCaseEqualsLiteral("dppx")) { + expr->mValue.SetFloatValue(mToken.mNumber, eCSSUnit_Pixel); + } else if (mToken.mIdent.LowerCaseEqualsLiteral("dpcm")) { + expr->mValue.SetFloatValue(mToken.mNumber, eCSSUnit_Centimeter); + } else { + rv = false; + } + break; + case nsMediaFeature::eEnumerated: + rv = ParseSingleTokenVariant(expr->mValue, VARIANT_KEYWORD, + feature->mData.mKeywordTable); + break; + case nsMediaFeature::eIdent: + rv = ParseSingleTokenVariant(expr->mValue, VARIANT_IDENTIFIER, nullptr); + break; + } + if (!rv || !ExpectSymbol(')', true)) { + REPORT_UNEXPECTED(PEMQExpectedFeatureValue); + SkipUntil(')'); + return false; + } + + return true; +} + +// Parse a CSS2 import rule: "@import STRING | URL [medium [, medium]]" +bool +CSSParserImpl::ParseImportRule(RuleAppendFunc aAppendFunc, void* aData) +{ + RefPtr<nsMediaList> media = new nsMediaList(); + + uint32_t linenum, colnum; + nsAutoString url; + if (!GetNextTokenLocation(true, &linenum, &colnum) || + !ParseURLOrString(url)) { + REPORT_UNEXPECTED_TOKEN(PEImportNotURI); + return false; + } + + if (!ExpectSymbol(';', true)) { + if (!GatherMedia(media, true) || + !ExpectSymbol(';', true)) { + REPORT_UNEXPECTED_TOKEN(PEImportUnexpected); + // don't advance section, simply ignore invalid @import + return false; + } + + // Safe to assert this, since we ensured that there is something + // other than the ';' coming after the @import's url() token. + NS_ASSERTION(media->Length() != 0, "media list must be nonempty"); + } + + ProcessImport(url, media, aAppendFunc, aData, linenum, colnum); + return true; +} + +void +CSSParserImpl::ProcessImport(const nsString& aURLSpec, + nsMediaList* aMedia, + RuleAppendFunc aAppendFunc, + void* aData, + uint32_t aLineNumber, + uint32_t aColumnNumber) +{ + RefPtr<css::ImportRule> rule = new css::ImportRule(aMedia, aURLSpec, + aLineNumber, + aColumnNumber); + (*aAppendFunc)(rule, aData); + + // Diagnose bad URIs even if we don't have a child loader. + nsCOMPtr<nsIURI> url; + // Charset will be deduced from mBaseURI, which is more or less correct. + nsresult rv = NS_NewURI(getter_AddRefs(url), aURLSpec, nullptr, mBaseURI); + + if (NS_FAILED(rv)) { + if (rv == NS_ERROR_MALFORMED_URI) { + // import url is bad + REPORT_UNEXPECTED_P(PEImportBadURI, aURLSpec); + OUTPUT_ERROR(); + } + return; + } + + if (mChildLoader) { + mChildLoader->LoadChildSheet(mSheet, url, aMedia, rule, mReusableSheets); + } +} + +// Parse the {} part of an @media or @-moz-document rule. +bool +CSSParserImpl::ParseGroupRule(css::GroupRule* aRule, + RuleAppendFunc aAppendFunc, + void* aData) +{ + // XXXbz this could use better error reporting throughout the method + if (!ExpectSymbol('{', true)) { + return false; + } + + // push rule on stack, loop over children + PushGroup(aRule); + nsCSSSection holdSection = mSection; + mSection = eCSSSection_General; + + for (;;) { + // Get next non-whitespace token + if (! GetToken(true)) { + REPORT_UNEXPECTED_EOF(PEGroupRuleEOF2); + break; + } + if (mToken.IsSymbol('}')) { // done! + UngetToken(); + break; + } + if (eCSSToken_AtKeyword == mToken.mType) { + // Parse for nested rules + ParseAtRule(aAppendFunc, aData, true); + continue; + } + UngetToken(); + ParseRuleSet(AppendRuleToSheet, this, true); + } + PopGroup(); + + if (!ExpectSymbol('}', true)) { + mSection = holdSection; + return false; + } + (*aAppendFunc)(aRule, aData); + return true; +} + +// Parse a CSS2 media rule: "@media medium [, medium] { ... }" +bool +CSSParserImpl::ParseMediaRule(RuleAppendFunc aAppendFunc, void* aData) +{ + RefPtr<nsMediaList> media = new nsMediaList(); + uint32_t linenum, colnum; + if (GetNextTokenLocation(true, &linenum, &colnum) && + GatherMedia(media, true)) { + // XXXbz this could use better error reporting throughout the method + RefPtr<css::MediaRule> rule = new css::MediaRule(linenum, colnum); + // Append first, so when we do SetMedia() the rule + // knows what its stylesheet is. + if (ParseGroupRule(rule, aAppendFunc, aData)) { + rule->SetMedia(media); + return true; + } + } + + return false; +} + +// Parse a @-moz-document rule. This is like an @media rule, but instead +// of a medium it has a nonempty list of items where each item is either +// url(), url-prefix(), or domain(). +bool +CSSParserImpl::ParseMozDocumentRule(RuleAppendFunc aAppendFunc, void* aData) +{ + css::DocumentRule::URL *urls = nullptr; + css::DocumentRule::URL **next = &urls; + + uint32_t linenum, colnum; + if (!GetNextTokenLocation(true, &linenum, &colnum)) { + return false; + } + + do { + if (!GetToken(true)) { + REPORT_UNEXPECTED_EOF(PEMozDocRuleEOF); + delete urls; + return false; + } + + if (!(eCSSToken_URL == mToken.mType || + (eCSSToken_Function == mToken.mType && + (mToken.mIdent.LowerCaseEqualsLiteral("url-prefix") || + mToken.mIdent.LowerCaseEqualsLiteral("domain") || + mToken.mIdent.LowerCaseEqualsLiteral("regexp"))))) { + REPORT_UNEXPECTED_TOKEN(PEMozDocRuleBadFunc2); + UngetToken(); + delete urls; + return false; + } + css::DocumentRule::URL *cur = *next = new css::DocumentRule::URL; + next = &cur->next; + if (mToken.mType == eCSSToken_URL) { + cur->func = css::DocumentRule::eURL; + CopyUTF16toUTF8(mToken.mIdent, cur->url); + } else if (mToken.mIdent.LowerCaseEqualsLiteral("regexp")) { + // regexp() is different from url-prefix() and domain() (but + // probably the way they *should* have been* in that it requires a + // string argument, and doesn't try to behave like url(). + cur->func = css::DocumentRule::eRegExp; + GetToken(true); + // copy before we know it's valid (but before ExpectSymbol changes + // mToken.mIdent) + CopyUTF16toUTF8(mToken.mIdent, cur->url); + if (eCSSToken_String != mToken.mType || !ExpectSymbol(')', true)) { + REPORT_UNEXPECTED_TOKEN(PEMozDocRuleNotString); + SkipUntil(')'); + delete urls; + return false; + } + } else { + if (mToken.mIdent.LowerCaseEqualsLiteral("url-prefix")) { + cur->func = css::DocumentRule::eURLPrefix; + } else if (mToken.mIdent.LowerCaseEqualsLiteral("domain")) { + cur->func = css::DocumentRule::eDomain; + } + + NS_ASSERTION(!mHavePushBack, "mustn't have pushback at this point"); + mScanner->NextURL(mToken); + if (mToken.mType != eCSSToken_URL) { + REPORT_UNEXPECTED_TOKEN(PEMozDocRuleNotURI); + SkipUntil(')'); + delete urls; + return false; + } + + // We could try to make the URL (as long as it's not domain()) + // canonical and absolute with NS_NewURI and GetSpec, but I'm + // inclined to think we shouldn't. + CopyUTF16toUTF8(mToken.mIdent, cur->url); + } + } while (ExpectSymbol(',', true)); + + RefPtr<css::DocumentRule> rule = new css::DocumentRule(linenum, colnum); + rule->SetURLs(urls); + + return ParseGroupRule(rule, aAppendFunc, aData); +} + +// Parse a CSS3 namespace rule: "@namespace [prefix] STRING | URL;" +bool +CSSParserImpl::ParseNameSpaceRule(RuleAppendFunc aAppendFunc, void* aData) +{ + uint32_t linenum, colnum; + if (!GetNextTokenLocation(true, &linenum, &colnum) || + !GetToken(true)) { + REPORT_UNEXPECTED_EOF(PEAtNSPrefixEOF); + return false; + } + + nsAutoString prefix; + nsAutoString url; + + if (eCSSToken_Ident == mToken.mType) { + prefix = mToken.mIdent; + // user-specified identifiers are case-sensitive (bug 416106) + } else { + UngetToken(); + } + + if (!ParseURLOrString(url) || !ExpectSymbol(';', true)) { + if (mHavePushBack) { + REPORT_UNEXPECTED_TOKEN(PEAtNSUnexpected); + } else { + REPORT_UNEXPECTED_EOF(PEAtNSURIEOF); + } + return false; + } + + ProcessNameSpace(prefix, url, aAppendFunc, aData, linenum, colnum); + return true; +} + +void +CSSParserImpl::ProcessNameSpace(const nsString& aPrefix, + const nsString& aURLSpec, + RuleAppendFunc aAppendFunc, + void* aData, + uint32_t aLineNumber, + uint32_t aColumnNumber) +{ + nsCOMPtr<nsIAtom> prefix; + + if (!aPrefix.IsEmpty()) { + prefix = NS_Atomize(aPrefix); + } + + RefPtr<css::NameSpaceRule> rule = new css::NameSpaceRule(prefix, aURLSpec, + aLineNumber, + aColumnNumber); + (*aAppendFunc)(rule, aData); + + // If this was the first namespace rule encountered, it will trigger + // creation of a namespace map. + if (!mNameSpaceMap) { + mNameSpaceMap = mSheet->GetNameSpaceMap(); + } +} + +// font-face-rule: '@font-face' '{' font-description '}' +// font-description: font-descriptor+ +bool +CSSParserImpl::ParseFontFaceRule(RuleAppendFunc aAppendFunc, void* aData) +{ + uint32_t linenum, colnum; + if (!GetNextTokenLocation(true, &linenum, &colnum) || + !ExpectSymbol('{', true)) { + REPORT_UNEXPECTED_TOKEN(PEBadFontBlockStart); + return false; + } + + RefPtr<nsCSSFontFaceRule> rule(new nsCSSFontFaceRule(linenum, colnum)); + + for (;;) { + if (!GetToken(true)) { + REPORT_UNEXPECTED_EOF(PEFontFaceEOF); + break; + } + if (mToken.IsSymbol('}')) { // done! + UngetToken(); + break; + } + + // ignore extra semicolons + if (mToken.IsSymbol(';')) + continue; + + if (!ParseFontDescriptor(rule)) { + REPORT_UNEXPECTED(PEDeclSkipped); + OUTPUT_ERROR(); + if (!SkipDeclaration(true)) + break; + } + } + if (!ExpectSymbol('}', true)) { + REPORT_UNEXPECTED_TOKEN(PEBadFontBlockEnd); + return false; + } + (*aAppendFunc)(rule, aData); + return true; +} + +// font-descriptor: font-family-desc +// | font-style-desc +// | font-weight-desc +// | font-stretch-desc +// | font-src-desc +// | unicode-range-desc +// +// All font-*-desc productions follow the pattern +// IDENT ':' value ';' +// +// On entry to this function, mToken is the IDENT. + +bool +CSSParserImpl::ParseFontDescriptor(nsCSSFontFaceRule* aRule) +{ + if (eCSSToken_Ident != mToken.mType) { + REPORT_UNEXPECTED_TOKEN(PEFontDescExpected); + return false; + } + + nsString descName = mToken.mIdent; + if (!ExpectSymbol(':', true)) { + REPORT_UNEXPECTED_TOKEN(PEParseDeclarationNoColon); + OUTPUT_ERROR(); + return false; + } + + nsCSSFontDesc descID = nsCSSProps::LookupFontDesc(descName); + nsCSSValue value; + + if (descID == eCSSFontDesc_UNKNOWN || + (descID == eCSSFontDesc_Display && + !Preferences::GetBool("layout.css.font-display.enabled"))) { + if (NonMozillaVendorIdentifier(descName)) { + // silently skip other vendors' extensions + SkipDeclaration(true); + return true; + } else { + REPORT_UNEXPECTED_P(PEUnknownFontDesc, descName); + return false; + } + } + + if (!ParseFontDescriptorValue(descID, value)) { + REPORT_UNEXPECTED_P(PEValueParsingError, descName); + return false; + } + + // Expect termination by ;, }, or EOF. + if (GetToken(true)) { + if (!mToken.IsSymbol(';') && + !mToken.IsSymbol('}')) { + UngetToken(); + REPORT_UNEXPECTED_TOKEN(PEExpectEndValue); + return false; + } + UngetToken(); + } + + aRule->SetDesc(descID, value); + return true; +} + +// @font-feature-values <font-family># { +// @<feature-type> { +// <feature-ident> : <feature-index>+; +// <feature-ident> : <feature-index>+; +// ... +// } +// ... +// } + +bool +CSSParserImpl::ParseFontFeatureValuesRule(RuleAppendFunc aAppendFunc, + void* aData) +{ + uint32_t linenum, colnum; + if (!GetNextTokenLocation(true, &linenum, &colnum)) { + return false; + } + + RefPtr<nsCSSFontFeatureValuesRule> + valuesRule(new nsCSSFontFeatureValuesRule(linenum, colnum)); + + // parse family list + nsCSSValue fontlistValue; + + if (!ParseFamily(fontlistValue) || + fontlistValue.GetUnit() != eCSSUnit_FontFamilyList) + { + REPORT_UNEXPECTED_TOKEN(PEFFVNoFamily); + return false; + } + + // add family to rule + const FontFamilyList* fontlist = fontlistValue.GetFontFamilyListValue(); + + // family list has generic ==> parse error + if (fontlist->HasGeneric()) { + REPORT_UNEXPECTED_TOKEN(PEFFVGenericInFamilyList); + return false; + } + + valuesRule->SetFamilyList(*fontlist); + + // open brace + if (!ExpectSymbol('{', true)) { + REPORT_UNEXPECTED_TOKEN(PEFFVBlockStart); + return false; + } + + // list of sets of feature values, each set bound to a specific + // feature-type (e.g. swash, annotation) + for (;;) { + if (!GetToken(true)) { + REPORT_UNEXPECTED_EOF(PEFFVUnexpectedEOF); + break; + } + if (mToken.IsSymbol('}')) { // done! + UngetToken(); + break; + } + + if (!ParseFontFeatureValueSet(valuesRule)) { + if (!SkipAtRule(false)) { + break; + } + } + } + if (!ExpectSymbol('}', true)) { + REPORT_UNEXPECTED_TOKEN(PEFFVUnexpectedBlockEnd); + SkipUntil('}'); + return false; + } + + (*aAppendFunc)(valuesRule, aData); + return true; +} + +#define NUMVALUES_NO_LIMIT 0xFFFF + +// parse a single value set containing name-value pairs for a single feature type +// @<feature-type> { [ <feature-ident> : <feature-index>+ ; ]* } +// Ex: @swash { flowing: 1; delicate: 2; } +bool +CSSParserImpl::ParseFontFeatureValueSet(nsCSSFontFeatureValuesRule + *aFeatureValuesRule) +{ + // -- @keyword (e.g. swash, styleset) + if (eCSSToken_AtKeyword != mToken.mType) { + REPORT_UNEXPECTED_TOKEN(PEFontFeatureValuesNoAt); + OUTPUT_ERROR(); + UngetToken(); + return false; + } + + // which font-specific variant of font-variant-alternates + int32_t whichVariant; + nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent); + if (keyword == eCSSKeyword_UNKNOWN || + !nsCSSProps::FindKeyword(keyword, + nsCSSProps::kFontVariantAlternatesFuncsKTable, + whichVariant)) + { + if (!NonMozillaVendorIdentifier(mToken.mIdent)) { + REPORT_UNEXPECTED_TOKEN(PEFFVUnknownFontVariantPropValue); + OUTPUT_ERROR(); + } + UngetToken(); + return false; + } + + nsAutoString featureType(mToken.mIdent); + + // open brace + if (!ExpectSymbol('{', true)) { + REPORT_UNEXPECTED_TOKEN(PEFFVValueSetStart); + return false; + } + + // styleset and character-variant can be multi-valued, otherwise single value + int32_t limitNumValues; + + switch (keyword) { + case eCSSKeyword_styleset: + limitNumValues = NUMVALUES_NO_LIMIT; + break; + case eCSSKeyword_character_variant: + limitNumValues = 2; + break; + default: + limitNumValues = 1; + break; + } + + // -- ident integer+ [, ident integer+] + AutoTArray<gfxFontFeatureValueSet::ValueList, 5> values; + + // list of font-feature-values-declaration's + for (;;) { + nsAutoString valueId; + + if (!GetToken(true)) { + REPORT_UNEXPECTED_EOF(PEFFVUnexpectedEOF); + break; + } + + // ignore extra semicolons + if (mToken.IsSymbol(';')) { + continue; + } + + // close brace ==> done + if (mToken.IsSymbol('}')) { + break; + } + + // ident + if (eCSSToken_Ident != mToken.mType) { + REPORT_UNEXPECTED_TOKEN(PEFFVExpectedIdent); + if (!SkipDeclaration(true)) { + break; + } + continue; + } + + valueId.Assign(mToken.mIdent); + + // colon + if (!ExpectSymbol(':', true)) { + REPORT_UNEXPECTED_TOKEN(PEParseDeclarationNoColon); + OUTPUT_ERROR(); + if (!SkipDeclaration(true)) { + break; + } + continue; + } + + // value list + AutoTArray<uint32_t,4> featureSelectors; + + nsCSSValue intValue; + while (ParseNonNegativeInteger(intValue)) { + featureSelectors.AppendElement(uint32_t(intValue.GetIntValue())); + } + + int32_t numValues = featureSelectors.Length(); + + if (numValues == 0) { + REPORT_UNEXPECTED_TOKEN(PEFFVExpectedValue); + OUTPUT_ERROR(); + if (!SkipDeclaration(true)) { + break; + } + continue; + } + + if (numValues > limitNumValues) { + REPORT_UNEXPECTED_P(PEFFVTooManyValues, featureType); + OUTPUT_ERROR(); + if (!SkipDeclaration(true)) { + break; + } + continue; + } + + if (!GetToken(true)) { + REPORT_UNEXPECTED_EOF(PEFFVUnexpectedEOF); + gfxFontFeatureValueSet::ValueList v(valueId, featureSelectors); + values.AppendElement(v); + break; + } + + // ';' or '}' to end definition + if (!mToken.IsSymbol(';') && !mToken.IsSymbol('}')) { + REPORT_UNEXPECTED_TOKEN(PEFFVValueDefinitionTrailing); + OUTPUT_ERROR(); + if (!SkipDeclaration(true)) { + break; + } + continue; + } + + gfxFontFeatureValueSet::ValueList v(valueId, featureSelectors); + values.AppendElement(v); + + if (mToken.IsSymbol('}')) { + break; + } + } + + aFeatureValuesRule->AddValueList(whichVariant, values); + return true; +} + +bool +CSSParserImpl::ParseKeyframesRule(RuleAppendFunc aAppendFunc, void* aData) +{ + uint32_t linenum, colnum; + if (!GetNextTokenLocation(true, &linenum, &colnum) || + !GetToken(true)) { + REPORT_UNEXPECTED_EOF(PEKeyframeNameEOF); + return false; + } + + if (mToken.mType != eCSSToken_Ident) { + REPORT_UNEXPECTED_TOKEN(PEKeyframeBadName); + UngetToken(); + return false; + } + nsString name(mToken.mIdent); + + if (!ExpectSymbol('{', true)) { + REPORT_UNEXPECTED_TOKEN(PEKeyframeBrace); + return false; + } + + RefPtr<nsCSSKeyframesRule> rule = new nsCSSKeyframesRule(name, + linenum, colnum); + + while (!ExpectSymbol('}', true)) { + RefPtr<nsCSSKeyframeRule> kid = ParseKeyframeRule(); + if (kid) { + rule->AppendStyleRule(kid); + } else { + OUTPUT_ERROR(); + SkipRuleSet(true); + } + } + + (*aAppendFunc)(rule, aData); + return true; +} + +bool +CSSParserImpl::ParsePageRule(RuleAppendFunc aAppendFunc, void* aData) +{ + uint32_t linenum, colnum; + if (!GetNextTokenLocation(true, &linenum, &colnum)) { + return false; + } + + // TODO: There can be page selectors after @page such as ":first", ":left". + uint32_t parseFlags = eParseDeclaration_InBraces | + eParseDeclaration_AllowImportant; + + // Forbid viewport units in @page rules. See bug 811391. + MOZ_ASSERT(mViewportUnitsEnabled, + "Viewport units should be enabled outside of @page rules."); + mViewportUnitsEnabled = false; + RefPtr<css::Declaration> declaration = + ParseDeclarationBlock(parseFlags, eCSSContext_Page); + mViewportUnitsEnabled = true; + + if (!declaration) { + return false; + } + + RefPtr<nsCSSPageRule> rule = + new nsCSSPageRule(declaration, linenum, colnum); + + (*aAppendFunc)(rule, aData); + return true; +} + +already_AddRefed<nsCSSKeyframeRule> +CSSParserImpl::ParseKeyframeRule() +{ + InfallibleTArray<float> selectorList; + uint32_t linenum, colnum; + if (!GetNextTokenLocation(true, &linenum, &colnum) || + !ParseKeyframeSelectorList(selectorList)) { + REPORT_UNEXPECTED(PEBadSelectorKeyframeRuleIgnored); + return nullptr; + } + + // Ignore !important in keyframe rules + uint32_t parseFlags = eParseDeclaration_InBraces; + RefPtr<css::Declaration> declaration(ParseDeclarationBlock(parseFlags)); + if (!declaration) { + return nullptr; + } + + // Takes ownership of declaration + RefPtr<nsCSSKeyframeRule> rule = + new nsCSSKeyframeRule(Move(selectorList), declaration.forget(), + linenum, colnum); + return rule.forget(); +} + +bool +CSSParserImpl::ParseKeyframeSelectorList(InfallibleTArray<float>& aSelectorList) +{ + for (;;) { + if (!GetToken(true)) { + // The first time through the loop, this means we got an empty + // list. Otherwise, it means we have a trailing comma. + return false; + } + float value; + switch (mToken.mType) { + case eCSSToken_Percentage: + value = mToken.mNumber; + break; + case eCSSToken_Ident: + if (mToken.mIdent.LowerCaseEqualsLiteral("from")) { + value = 0.0f; + break; + } + if (mToken.mIdent.LowerCaseEqualsLiteral("to")) { + value = 1.0f; + break; + } + MOZ_FALLTHROUGH; + default: + UngetToken(); + // The first time through the loop, this means we got an empty + // list. Otherwise, it means we have a trailing comma. + return false; + } + aSelectorList.AppendElement(value); + if (!ExpectSymbol(',', true)) { + return true; + } + } +} + +// supports_rule +// : "@supports" supports_condition group_rule_body +// ; +bool +CSSParserImpl::ParseSupportsRule(RuleAppendFunc aAppendFunc, void* aProcessData) +{ + bool conditionMet = false; + nsString condition; + + mScanner->StartRecording(); + + uint32_t linenum, colnum; + if (!GetNextTokenLocation(true, &linenum, &colnum)) { + return false; + } + + bool parsed = ParseSupportsCondition(conditionMet); + + if (!parsed) { + mScanner->StopRecording(); + return false; + } + + if (!ExpectSymbol('{', true)) { + REPORT_UNEXPECTED_TOKEN(PESupportsGroupRuleStart); + mScanner->StopRecording(); + return false; + } + + UngetToken(); + mScanner->StopRecording(condition); + + // Remove the "{" that would follow the condition. + if (condition.Length() != 0) { + condition.Truncate(condition.Length() - 1); + } + + // Remove spaces from the start and end of the recorded supports condition. + condition.Trim(" ", true, true, false); + + // Record whether we are in a failing @supports, so that property parse + // errors don't get reported. + nsAutoFailingSupportsRule failing(this, conditionMet); + + RefPtr<css::GroupRule> rule = new CSSSupportsRule(conditionMet, condition, + linenum, colnum); + return ParseGroupRule(rule, aAppendFunc, aProcessData); +} + +// supports_condition +// : supports_condition_in_parens supports_condition_terms +// | supports_condition_negation +// ; +bool +CSSParserImpl::ParseSupportsCondition(bool& aConditionMet) +{ + nsAutoInSupportsCondition aisc(this); + + if (!GetToken(true)) { + REPORT_UNEXPECTED_EOF(PESupportsConditionStartEOF2); + return false; + } + + UngetToken(); + + mScanner->ClearSeenBadToken(); + + if (mToken.IsSymbol('(') || + mToken.mType == eCSSToken_Function || + mToken.mType == eCSSToken_URL || + mToken.mType == eCSSToken_Bad_URL) { + bool result = ParseSupportsConditionInParens(aConditionMet) && + ParseSupportsConditionTerms(aConditionMet) && + !mScanner->SeenBadToken(); + return result; + } + + if (mToken.mType == eCSSToken_Ident && + mToken.mIdent.LowerCaseEqualsLiteral("not")) { + bool result = ParseSupportsConditionNegation(aConditionMet) && + !mScanner->SeenBadToken(); + return result; + } + + REPORT_UNEXPECTED_TOKEN(PESupportsConditionExpectedStart); + return false; +} + +// supports_condition_negation +// : 'not' S+ supports_condition_in_parens +// ; +bool +CSSParserImpl::ParseSupportsConditionNegation(bool& aConditionMet) +{ + if (!GetToken(true)) { + REPORT_UNEXPECTED_EOF(PESupportsConditionNotEOF); + return false; + } + + if (mToken.mType != eCSSToken_Ident || + !mToken.mIdent.LowerCaseEqualsLiteral("not")) { + REPORT_UNEXPECTED_TOKEN(PESupportsConditionExpectedNot); + return false; + } + + if (!RequireWhitespace()) { + REPORT_UNEXPECTED(PESupportsWhitespaceRequired); + return false; + } + + if (ParseSupportsConditionInParens(aConditionMet)) { + aConditionMet = !aConditionMet; + return true; + } + + return false; +} + +// supports_condition_in_parens +// : '(' S* supports_condition_in_parens_inside_parens ')' S* +// | supports_condition_pref +// | general_enclosed +// ; +bool +CSSParserImpl::ParseSupportsConditionInParens(bool& aConditionMet) +{ + if (!GetToken(true)) { + REPORT_UNEXPECTED_EOF(PESupportsConditionInParensStartEOF); + return false; + } + + if (mToken.mType == eCSSToken_URL) { + aConditionMet = false; + return true; + } + + if (AgentRulesEnabled() && + mToken.mType == eCSSToken_Function && + mToken.mIdent.LowerCaseEqualsLiteral("-moz-bool-pref")) { + return ParseSupportsMozBoolPrefName(aConditionMet); + } + + if (mToken.mType == eCSSToken_Function || + mToken.mType == eCSSToken_Bad_URL) { + if (!SkipUntil(')')) { + REPORT_UNEXPECTED_EOF(PESupportsConditionInParensEOF); + return false; + } + aConditionMet = false; + return true; + } + + if (!mToken.IsSymbol('(')) { + REPORT_UNEXPECTED_TOKEN(PESupportsConditionExpectedOpenParenOrFunction); + UngetToken(); + return false; + } + + if (!ParseSupportsConditionInParensInsideParens(aConditionMet)) { + if (!SkipUntil(')')) { + REPORT_UNEXPECTED_EOF(PESupportsConditionInParensEOF); + return false; + } + aConditionMet = false; + return true; + } + + if (!(ExpectSymbol(')', true))) { + SkipUntil(')'); + aConditionMet = false; + return true; + } + + return true; +} + +// supports_condition_pref +// : '-moz-bool-pref(' bool_pref_name ')' +// ; +bool +CSSParserImpl::ParseSupportsMozBoolPrefName(bool& aConditionMet) +{ + if (!GetToken(true)) { + return false; + } + + if (mToken.mType != eCSSToken_String) { + SkipUntil(')'); + return false; + } + + aConditionMet = Preferences::GetBool( + NS_ConvertUTF16toUTF8(mToken.mIdent).get()); + + if (!ExpectSymbol(')', true)) { + SkipUntil(')'); + return false; + } + + return true; +} + +// supports_condition_in_parens_inside_parens +// : core_declaration +// | supports_condition_negation +// | supports_condition_in_parens supports_condition_terms +// ; +bool +CSSParserImpl::ParseSupportsConditionInParensInsideParens(bool& aConditionMet) +{ + if (!GetToken(true)) { + return false; + } + + if (mToken.mType == eCSSToken_Ident) { + if (!mToken.mIdent.LowerCaseEqualsLiteral("not")) { + nsAutoString propertyName = mToken.mIdent; + if (!ExpectSymbol(':', true)) { + return false; + } + + nsCSSPropertyID propID = LookupEnabledProperty(propertyName); + if (propID == eCSSProperty_UNKNOWN) { + if (ExpectSymbol(')', true)) { + UngetToken(); + return false; + } + aConditionMet = false; + SkipUntil(')'); + UngetToken(); + } else if (propID == eCSSPropertyExtra_variable) { + if (ExpectSymbol(')', false)) { + UngetToken(); + return false; + } + CSSVariableDeclarations::Type variableType; + nsString variableValue; + aConditionMet = + ParseVariableDeclaration(&variableType, variableValue) && + ParsePriority() != ePriority_Error; + if (!aConditionMet) { + SkipUntil(')'); + UngetToken(); + } + } else { + if (ExpectSymbol(')', true)) { + UngetToken(); + return false; + } + aConditionMet = ParseProperty(propID) && + ParsePriority() != ePriority_Error; + if (!aConditionMet) { + SkipUntil(')'); + UngetToken(); + } + mTempData.ClearProperty(propID); + mTempData.AssertInitialState(); + } + return true; + } + + UngetToken(); + return ParseSupportsConditionNegation(aConditionMet); + } + + UngetToken(); + return ParseSupportsConditionInParens(aConditionMet) && + ParseSupportsConditionTerms(aConditionMet); +} + +// supports_condition_terms +// : S+ 'and' supports_condition_terms_after_operator('and') +// | S+ 'or' supports_condition_terms_after_operator('or') +// | +// ; +bool +CSSParserImpl::ParseSupportsConditionTerms(bool& aConditionMet) +{ + if (!RequireWhitespace() || !GetToken(false)) { + return true; + } + + if (mToken.mType != eCSSToken_Ident) { + UngetToken(); + return true; + } + + if (mToken.mIdent.LowerCaseEqualsLiteral("and")) { + return ParseSupportsConditionTermsAfterOperator(aConditionMet, eAnd); + } + + if (mToken.mIdent.LowerCaseEqualsLiteral("or")) { + return ParseSupportsConditionTermsAfterOperator(aConditionMet, eOr); + } + + UngetToken(); + return true; +} + +// supports_condition_terms_after_operator(operator) +// : S+ supports_condition_in_parens ( <operator> supports_condition_in_parens )* +// ; +bool +CSSParserImpl::ParseSupportsConditionTermsAfterOperator( + bool& aConditionMet, + CSSParserImpl::SupportsConditionTermOperator aOperator) +{ + if (!RequireWhitespace()) { + REPORT_UNEXPECTED(PESupportsWhitespaceRequired); + return false; + } + + const char* token = aOperator == eAnd ? "and" : "or"; + for (;;) { + bool termConditionMet = false; + if (!ParseSupportsConditionInParens(termConditionMet)) { + return false; + } + aConditionMet = aOperator == eAnd ? aConditionMet && termConditionMet : + aConditionMet || termConditionMet; + + if (!GetToken(true)) { + return true; + } + + if (mToken.mType != eCSSToken_Ident || + !mToken.mIdent.LowerCaseEqualsASCII(token)) { + UngetToken(); + return true; + } + } +} + +bool +CSSParserImpl::ParseCounterStyleRule(RuleAppendFunc aAppendFunc, void* aData) +{ + nsAutoString name; + uint32_t linenum, colnum; + if (!GetNextTokenLocation(true, &linenum, &colnum) || + !ParseCounterStyleName(name, true)) { + REPORT_UNEXPECTED_TOKEN(PECounterStyleNotIdent); + return false; + } + + if (!ExpectSymbol('{', true)) { + REPORT_UNEXPECTED_TOKEN(PECounterStyleBadBlockStart); + return false; + } + + RefPtr<nsCSSCounterStyleRule> rule = new nsCSSCounterStyleRule(name, + linenum, + colnum); + for (;;) { + if (!GetToken(true)) { + REPORT_UNEXPECTED_EOF(PECounterStyleEOF); + break; + } + if (mToken.IsSymbol('}')) { + break; + } + if (mToken.IsSymbol(';')) { + continue; + } + + if (!ParseCounterDescriptor(rule)) { + REPORT_UNEXPECTED(PEDeclSkipped); + OUTPUT_ERROR(); + if (!SkipDeclaration(true)) { + REPORT_UNEXPECTED_EOF(PECounterStyleEOF); + break; + } + } + } + + int32_t system = rule->GetSystem(); + bool isCorrect = false; + switch (system) { + case NS_STYLE_COUNTER_SYSTEM_CYCLIC: + case NS_STYLE_COUNTER_SYSTEM_NUMERIC: + case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC: + case NS_STYLE_COUNTER_SYSTEM_SYMBOLIC: + case NS_STYLE_COUNTER_SYSTEM_FIXED: { + // check whether symbols is set and the length is sufficient + const nsCSSValue& symbols = rule->GetDesc(eCSSCounterDesc_Symbols); + if (symbols.GetUnit() == eCSSUnit_List && + nsCSSCounterStyleRule::CheckDescValue( + system, eCSSCounterDesc_Symbols, symbols)) { + isCorrect = true; + } + break; + } + case NS_STYLE_COUNTER_SYSTEM_ADDITIVE: { + // for additive system, additive-symbols must be set + const nsCSSValue& symbols = + rule->GetDesc(eCSSCounterDesc_AdditiveSymbols); + if (symbols.GetUnit() == eCSSUnit_PairList) { + isCorrect = true; + } + break; + } + case NS_STYLE_COUNTER_SYSTEM_EXTENDS: { + // for extends system, symbols & additive-symbols must not be set + const nsCSSValue& symbols = rule->GetDesc(eCSSCounterDesc_Symbols); + const nsCSSValue& additiveSymbols = + rule->GetDesc(eCSSCounterDesc_AdditiveSymbols); + if (symbols.GetUnit() == eCSSUnit_Null && + additiveSymbols.GetUnit() == eCSSUnit_Null) { + isCorrect = true; + } + break; + } + default: + NS_NOTREACHED("unknown system"); + } + + if (isCorrect) { + (*aAppendFunc)(rule, aData); + } + return true; +} + +bool +CSSParserImpl::ParseCounterStyleName(nsAString& aName, bool aForDefinition) +{ + if (!GetToken(true)) { + return false; + } + + if (mToken.mType != eCSSToken_Ident) { + UngetToken(); + return false; + } + + static const nsCSSKeyword kReservedNames[] = { + eCSSKeyword_none, + eCSSKeyword_decimal, + eCSSKeyword_UNKNOWN + }; + + nsCSSValue value; // we don't actually care about the value + if (!ParseCustomIdent(value, mToken.mIdent, + aForDefinition ? kReservedNames : nullptr)) { + REPORT_UNEXPECTED_TOKEN(PECounterStyleBadName); + UngetToken(); + return false; + } + + aName = mToken.mIdent; + if (nsCSSProps::IsPredefinedCounterStyle(aName)) { + ToLowerCase(aName); + } + return true; +} + +bool +CSSParserImpl::ParseCounterStyleNameValue(nsCSSValue& aValue) +{ + nsString name; + if (ParseCounterStyleName(name, false)) { + aValue.SetStringValue(name, eCSSUnit_Ident); + return true; + } + return false; +} + +bool +CSSParserImpl::ParseCounterDescriptor(nsCSSCounterStyleRule* aRule) +{ + if (eCSSToken_Ident != mToken.mType) { + REPORT_UNEXPECTED_TOKEN(PECounterDescExpected); + return false; + } + + nsString descName = mToken.mIdent; + if (!ExpectSymbol(':', true)) { + REPORT_UNEXPECTED_TOKEN(PEParseDeclarationNoColon); + OUTPUT_ERROR(); + return false; + } + + nsCSSCounterDesc descID = nsCSSProps::LookupCounterDesc(descName); + nsCSSValue value; + + if (descID == eCSSCounterDesc_UNKNOWN) { + REPORT_UNEXPECTED_P(PEUnknownCounterDesc, descName); + return false; + } + + if (!ParseCounterDescriptorValue(descID, value)) { + REPORT_UNEXPECTED_P(PEValueParsingError, descName); + return false; + } + + if (!ExpectEndProperty()) { + return false; + } + + aRule->SetDesc(descID, value); + return true; +} + +bool +CSSParserImpl::ParseCounterDescriptorValue(nsCSSCounterDesc aDescID, + nsCSSValue& aValue) +{ + // Should also include VARIANT_IMAGE, but it is not supported currently. + // See bug 1024179. + static const int32_t VARIANT_COUNTER_SYMBOL = + VARIANT_STRING | VARIANT_IDENTIFIER; + + switch (aDescID) { + case eCSSCounterDesc_System: { + nsCSSValue system; + if (!ParseEnum(system, nsCSSProps::kCounterSystemKTable)) { + return false; + } + switch (system.GetIntValue()) { + case NS_STYLE_COUNTER_SYSTEM_FIXED: { + nsCSSValue start; + if (!ParseSingleTokenVariant(start, VARIANT_INTEGER, nullptr)) { + start.SetIntValue(1, eCSSUnit_Integer); + } + aValue.SetPairValue(system, start); + return true; + } + case NS_STYLE_COUNTER_SYSTEM_EXTENDS: { + nsCSSValue name; + if (!ParseCounterStyleNameValue(name)) { + REPORT_UNEXPECTED_TOKEN(PECounterExtendsNotIdent); + return false; + } + aValue.SetPairValue(system, name); + return true; + } + default: + aValue = system; + return true; + } + } + + case eCSSCounterDesc_Negative: { + nsCSSValue first, second; + if (!ParseSingleTokenVariant(first, VARIANT_COUNTER_SYMBOL, nullptr)) { + return false; + } + if (!ParseSingleTokenVariant(second, VARIANT_COUNTER_SYMBOL, nullptr)) { + aValue = first; + } else { + aValue.SetPairValue(first, second); + } + return true; + } + + case eCSSCounterDesc_Prefix: + case eCSSCounterDesc_Suffix: + return ParseSingleTokenVariant(aValue, VARIANT_COUNTER_SYMBOL, nullptr); + + case eCSSCounterDesc_Range: { + if (ParseSingleTokenVariant(aValue, VARIANT_AUTO, nullptr)) { + return true; + } + nsCSSValuePairList* item = aValue.SetPairListValue(); + for (;;) { + nsCSSValuePair pair; + if (!ParseCounterRange(pair)) { + return false; + } + item->mXValue = pair.mXValue; + item->mYValue = pair.mYValue; + if (!ExpectSymbol(',', true)) { + return true; + } + item->mNext = new nsCSSValuePairList; + item = item->mNext; + } + // should always return in the loop + } + + case eCSSCounterDesc_Pad: { + nsCSSValue width, symbol; + bool hasWidth = ParseNonNegativeInteger(width); + if (!ParseSingleTokenVariant(symbol, VARIANT_COUNTER_SYMBOL, nullptr) || + (!hasWidth && !ParseNonNegativeInteger(width))) { + return false; + } + aValue.SetPairValue(width, symbol); + return true; + } + + case eCSSCounterDesc_Fallback: + return ParseCounterStyleNameValue(aValue); + + case eCSSCounterDesc_Symbols: { + nsCSSValueList* item = nullptr; + for (;;) { + nsCSSValue value; + if (!ParseSingleTokenVariant(value, VARIANT_COUNTER_SYMBOL, nullptr)) { + return !!item; + } + if (!item) { + item = aValue.SetListValue(); + } else { + item->mNext = new nsCSSValueList; + item = item->mNext; + } + item->mValue = value; + } + // should always return in the loop + } + + case eCSSCounterDesc_AdditiveSymbols: { + nsCSSValuePairList* item = nullptr; + int32_t lastWeight = -1; + for (;;) { + nsCSSValue weight, symbol; + bool hasWeight = ParseNonNegativeInteger(weight); + if (!ParseSingleTokenVariant(symbol, VARIANT_COUNTER_SYMBOL, nullptr) || + (!hasWeight && !ParseNonNegativeInteger(weight))) { + return false; + } + if (lastWeight != -1 && weight.GetIntValue() >= lastWeight) { + REPORT_UNEXPECTED(PECounterASWeight); + return false; + } + lastWeight = weight.GetIntValue(); + if (!item) { + item = aValue.SetPairListValue(); + } else { + item->mNext = new nsCSSValuePairList; + item = item->mNext; + } + item->mXValue = weight; + item->mYValue = symbol; + if (!ExpectSymbol(',', true)) { + return true; + } + } + // should always return in the loop + } + + case eCSSCounterDesc_SpeakAs: + if (ParseSingleTokenVariant(aValue, VARIANT_AUTO | VARIANT_KEYWORD, + nsCSSProps::kCounterSpeakAsKTable)) { + if (aValue.GetUnit() == eCSSUnit_Enumerated && + aValue.GetIntValue() == NS_STYLE_COUNTER_SPEAKAS_SPELL_OUT) { + // Currently spell-out is not supported, so it is explicitly + // rejected here rather than parsed as a custom identifier. + // See bug 1024178. + return false; + } + return true; + } + return ParseCounterStyleNameValue(aValue); + + default: + NS_NOTREACHED("unknown descriptor"); + return false; + } +} + +bool +CSSParserImpl::ParseCounterRange(nsCSSValuePair& aPair) +{ + static const int32_t VARIANT_BOUND = VARIANT_INTEGER | VARIANT_KEYWORD; + nsCSSValue lower, upper; + if (!ParseSingleTokenVariant(lower, VARIANT_BOUND, + nsCSSProps::kCounterRangeKTable) || + !ParseSingleTokenVariant(upper, VARIANT_BOUND, + nsCSSProps::kCounterRangeKTable)) { + return false; + } + if (lower.GetUnit() != eCSSUnit_Enumerated && + upper.GetUnit() != eCSSUnit_Enumerated && + lower.GetIntValue() > upper.GetIntValue()) { + return false; + } + aPair = nsCSSValuePair(lower, upper); + return true; +} + +bool +CSSParserImpl::SkipUntil(char16_t aStopSymbol) +{ + nsCSSToken* tk = &mToken; + AutoTArray<char16_t, 16> stack; + stack.AppendElement(aStopSymbol); + for (;;) { + if (!GetToken(true)) { + return false; + } + if (eCSSToken_Symbol == tk->mType) { + char16_t symbol = tk->mSymbol; + uint32_t stackTopIndex = stack.Length() - 1; + if (symbol == stack.ElementAt(stackTopIndex)) { + stack.RemoveElementAt(stackTopIndex); + if (stackTopIndex == 0) { + return true; + } + + // Just handle out-of-memory by parsing incorrectly. It's + // highly unlikely we're dealing with a legitimate style sheet + // anyway. + } else if ('{' == symbol) { + stack.AppendElement('}'); + } else if ('[' == symbol) { + stack.AppendElement(']'); + } else if ('(' == symbol) { + stack.AppendElement(')'); + } + } else if (eCSSToken_Function == tk->mType || + eCSSToken_Bad_URL == tk->mType) { + stack.AppendElement(')'); + } + } +} + +bool +CSSParserImpl::SkipBalancedContentUntil(char16_t aStopSymbol) +{ + nsCSSToken* tk = &mToken; + AutoTArray<char16_t, 16> stack; + stack.AppendElement(aStopSymbol); + for (;;) { + if (!GetToken(true)) { + return true; + } + if (eCSSToken_Symbol == tk->mType) { + char16_t symbol = tk->mSymbol; + uint32_t stackTopIndex = stack.Length() - 1; + if (symbol == stack.ElementAt(stackTopIndex)) { + stack.RemoveElementAt(stackTopIndex); + if (stackTopIndex == 0) { + return true; + } + + // Just handle out-of-memory by parsing incorrectly. It's + // highly unlikely we're dealing with a legitimate style sheet + // anyway. + } else if ('{' == symbol) { + stack.AppendElement('}'); + } else if ('[' == symbol) { + stack.AppendElement(']'); + } else if ('(' == symbol) { + stack.AppendElement(')'); + } else if (')' == symbol || + ']' == symbol || + '}' == symbol) { + UngetToken(); + return false; + } + } else if (eCSSToken_Function == tk->mType || + eCSSToken_Bad_URL == tk->mType) { + stack.AppendElement(')'); + } + } +} + +void +CSSParserImpl::SkipUntilOneOf(const char16_t* aStopSymbolChars) +{ + nsCSSToken* tk = &mToken; + nsDependentString stopSymbolChars(aStopSymbolChars); + for (;;) { + if (!GetToken(true)) { + break; + } + if (eCSSToken_Symbol == tk->mType) { + char16_t symbol = tk->mSymbol; + if (stopSymbolChars.FindChar(symbol) != -1) { + break; + } else if ('{' == symbol) { + SkipUntil('}'); + } else if ('[' == symbol) { + SkipUntil(']'); + } else if ('(' == symbol) { + SkipUntil(')'); + } + } else if (eCSSToken_Function == tk->mType || + eCSSToken_Bad_URL == tk->mType) { + SkipUntil(')'); + } + } +} + +void +CSSParserImpl::SkipUntilAllOf(const StopSymbolCharStack& aStopSymbolChars) +{ + uint32_t i = aStopSymbolChars.Length(); + while (i--) { + SkipUntil(aStopSymbolChars[i]); + } +} + +bool +CSSParserImpl::SkipDeclaration(bool aCheckForBraces) +{ + nsCSSToken* tk = &mToken; + for (;;) { + if (!GetToken(true)) { + if (aCheckForBraces) { + REPORT_UNEXPECTED_EOF(PESkipDeclBraceEOF); + } + return false; + } + if (eCSSToken_Symbol == tk->mType) { + char16_t symbol = tk->mSymbol; + if (';' == symbol) { + break; + } + if (aCheckForBraces) { + if ('}' == symbol) { + UngetToken(); + break; + } + } + if ('{' == symbol) { + SkipUntil('}'); + } else if ('(' == symbol) { + SkipUntil(')'); + } else if ('[' == symbol) { + SkipUntil(']'); + } + } else if (eCSSToken_Function == tk->mType || + eCSSToken_Bad_URL == tk->mType) { + SkipUntil(')'); + } + } + return true; +} + +void +CSSParserImpl::SkipRuleSet(bool aInsideBraces) +{ + nsCSSToken* tk = &mToken; + for (;;) { + if (!GetToken(true)) { + REPORT_UNEXPECTED_EOF(PESkipRSBraceEOF); + break; + } + if (eCSSToken_Symbol == tk->mType) { + char16_t symbol = tk->mSymbol; + if ('}' == symbol && aInsideBraces) { + // leave block closer for higher-level grammar to consume + UngetToken(); + break; + } else if ('{' == symbol) { + SkipUntil('}'); + break; + } else if ('(' == symbol) { + SkipUntil(')'); + } else if ('[' == symbol) { + SkipUntil(']'); + } + } else if (eCSSToken_Function == tk->mType || + eCSSToken_Bad_URL == tk->mType) { + SkipUntil(')'); + } + } +} + +void +CSSParserImpl::PushGroup(css::GroupRule* aRule) +{ + mGroupStack.AppendElement(aRule); +} + +void +CSSParserImpl::PopGroup() +{ + uint32_t count = mGroupStack.Length(); + if (0 < count) { + mGroupStack.RemoveElementAt(count - 1); + } +} + +void +CSSParserImpl::AppendRule(css::Rule* aRule) +{ + uint32_t count = mGroupStack.Length(); + if (0 < count) { + mGroupStack[count - 1]->AppendStyleRule(aRule); + } + else { + mSheet->AppendStyleRule(aRule); + } +} + +bool +CSSParserImpl::ParseRuleSet(RuleAppendFunc aAppendFunc, void* aData, + bool aInsideBraces) +{ + // First get the list of selectors for the rule + nsCSSSelectorList* slist = nullptr; + uint32_t linenum, colnum; + if (!GetNextTokenLocation(true, &linenum, &colnum) || + !ParseSelectorList(slist, char16_t('{'))) { + REPORT_UNEXPECTED(PEBadSelectorRSIgnored); + OUTPUT_ERROR(); + SkipRuleSet(aInsideBraces); + return false; + } + NS_ASSERTION(nullptr != slist, "null selector list"); + CLEAR_ERROR(); + + // Next parse the declaration block + uint32_t parseFlags = eParseDeclaration_InBraces | + eParseDeclaration_AllowImportant; + RefPtr<css::Declaration> declaration = ParseDeclarationBlock(parseFlags); + if (nullptr == declaration) { + delete slist; + return false; + } + +#if 0 + slist->Dump(); + fputs("{\n", stdout); + declaration->List(); + fputs("}\n", stdout); +#endif + + // Translate the selector list and declaration block into style data + + RefPtr<css::StyleRule> rule = new css::StyleRule(slist, declaration, + linenum, colnum); + (*aAppendFunc)(rule, aData); + + return true; +} + +bool +CSSParserImpl::ParseSelectorList(nsCSSSelectorList*& aListHead, + char16_t aStopChar) +{ + nsCSSSelectorList* list = nullptr; + if (! ParseSelectorGroup(list)) { + // must have at least one selector group + aListHead = nullptr; + return false; + } + NS_ASSERTION(nullptr != list, "no selector list"); + aListHead = list; + + // After that there must either be a "," or a "{" (the latter if + // StopChar is nonzero) + nsCSSToken* tk = &mToken; + for (;;) { + if (! GetToken(true)) { + if (aStopChar == char16_t(0)) { + return true; + } + + REPORT_UNEXPECTED_EOF(PESelectorListExtraEOF); + break; + } + + if (eCSSToken_Symbol == tk->mType) { + if (',' == tk->mSymbol) { + nsCSSSelectorList* newList = nullptr; + // Another selector group must follow + if (! ParseSelectorGroup(newList)) { + break; + } + // add new list to the end of the selector list + list->mNext = newList; + list = newList; + continue; + } else if (aStopChar == tk->mSymbol && aStopChar != char16_t(0)) { + UngetToken(); + return true; + } + } + REPORT_UNEXPECTED_TOKEN(PESelectorListExtra); + UngetToken(); + break; + } + + delete aListHead; + aListHead = nullptr; + return false; +} + +static bool IsUniversalSelector(const nsCSSSelector& aSelector) +{ + return bool((aSelector.mNameSpace == kNameSpaceID_Unknown) && + (aSelector.mLowercaseTag == nullptr) && + (aSelector.mIDList == nullptr) && + (aSelector.mClassList == nullptr) && + (aSelector.mAttrList == nullptr) && + (aSelector.mNegations == nullptr) && + (aSelector.mPseudoClassList == nullptr)); +} + +bool +CSSParserImpl::ParseSelectorGroup(nsCSSSelectorList*& aList) +{ + char16_t combinator = 0; + nsAutoPtr<nsCSSSelectorList> list(new nsCSSSelectorList()); + + for (;;) { + if (!ParseSelector(list, combinator)) { + return false; + } + + // Look for a combinator. + if (!GetToken(false)) { + break; // EOF ok here + } + + combinator = char16_t(0); + if (mToken.mType == eCSSToken_Whitespace) { + if (!GetToken(true)) { + break; // EOF ok here + } + combinator = char16_t(' '); + } + + if (mToken.mType != eCSSToken_Symbol) { + UngetToken(); // not a combinator + } else { + char16_t symbol = mToken.mSymbol; + if (symbol == '+' || symbol == '>' || symbol == '~') { + combinator = mToken.mSymbol; + } else { + UngetToken(); // not a combinator + if (symbol == ',' || symbol == '{' || symbol == ')') { + break; // end of selector group + } + } + } + + if (!combinator) { + REPORT_UNEXPECTED_TOKEN(PESelectorListExtra); + return false; + } + } + + aList = list.forget(); + return true; +} + +#define SEL_MASK_NSPACE 0x01 +#define SEL_MASK_ELEM 0x02 +#define SEL_MASK_ID 0x04 +#define SEL_MASK_CLASS 0x08 +#define SEL_MASK_ATTRIB 0x10 +#define SEL_MASK_PCLASS 0x20 +#define SEL_MASK_PELEM 0x40 + +// +// Parses an ID selector #name +// +CSSParserImpl::nsSelectorParsingStatus +CSSParserImpl::ParseIDSelector(int32_t& aDataMask, + nsCSSSelector& aSelector) +{ + NS_ASSERTION(!mToken.mIdent.IsEmpty(), + "Empty mIdent in eCSSToken_ID token?"); + aDataMask |= SEL_MASK_ID; + aSelector.AddID(mToken.mIdent); + return eSelectorParsingStatus_Continue; +} + +// +// Parses a class selector .name +// +CSSParserImpl::nsSelectorParsingStatus +CSSParserImpl::ParseClassSelector(int32_t& aDataMask, + nsCSSSelector& aSelector) +{ + if (! GetToken(false)) { // get ident + REPORT_UNEXPECTED_EOF(PEClassSelEOF); + return eSelectorParsingStatus_Error; + } + if (eCSSToken_Ident != mToken.mType) { // malformed selector + REPORT_UNEXPECTED_TOKEN(PEClassSelNotIdent); + UngetToken(); + return eSelectorParsingStatus_Error; + } + aDataMask |= SEL_MASK_CLASS; + + aSelector.AddClass(mToken.mIdent); + + return eSelectorParsingStatus_Continue; +} + +// +// Parse a type element selector or a universal selector +// namespace|type or namespace|* or *|* or * +// +CSSParserImpl::nsSelectorParsingStatus +CSSParserImpl::ParseTypeOrUniversalSelector(int32_t& aDataMask, + nsCSSSelector& aSelector, + bool aIsNegated) +{ + nsAutoString buffer; + if (mToken.IsSymbol('*')) { // universal element selector, or universal namespace + if (ExpectSymbol('|', false)) { // was namespace + aDataMask |= SEL_MASK_NSPACE; + aSelector.SetNameSpace(kNameSpaceID_Unknown); // namespace wildcard + + if (! GetToken(false)) { + REPORT_UNEXPECTED_EOF(PETypeSelEOF); + return eSelectorParsingStatus_Error; + } + if (eCSSToken_Ident == mToken.mType) { // element name + aDataMask |= SEL_MASK_ELEM; + + aSelector.SetTag(mToken.mIdent); + } + else if (mToken.IsSymbol('*')) { // universal selector + aDataMask |= SEL_MASK_ELEM; + // don't set tag + } + else { + REPORT_UNEXPECTED_TOKEN(PETypeSelNotType); + UngetToken(); + return eSelectorParsingStatus_Error; + } + } + else { // was universal element selector + SetDefaultNamespaceOnSelector(aSelector); + aDataMask |= SEL_MASK_ELEM; + // don't set any tag in the selector + } + if (! GetToken(false)) { // premature eof is ok (here!) + return eSelectorParsingStatus_Done; + } + } + else if (eCSSToken_Ident == mToken.mType) { // element name or namespace name + buffer = mToken.mIdent; // hang on to ident + + if (ExpectSymbol('|', false)) { // was namespace + aDataMask |= SEL_MASK_NSPACE; + int32_t nameSpaceID = GetNamespaceIdForPrefix(buffer); + if (nameSpaceID == kNameSpaceID_Unknown) { + return eSelectorParsingStatus_Error; + } + aSelector.SetNameSpace(nameSpaceID); + + if (! GetToken(false)) { + REPORT_UNEXPECTED_EOF(PETypeSelEOF); + return eSelectorParsingStatus_Error; + } + if (eCSSToken_Ident == mToken.mType) { // element name + aDataMask |= SEL_MASK_ELEM; + aSelector.SetTag(mToken.mIdent); + } + else if (mToken.IsSymbol('*')) { // universal selector + aDataMask |= SEL_MASK_ELEM; + // don't set tag + } + else { + REPORT_UNEXPECTED_TOKEN(PETypeSelNotType); + UngetToken(); + return eSelectorParsingStatus_Error; + } + } + else { // was element name + SetDefaultNamespaceOnSelector(aSelector); + aSelector.SetTag(buffer); + + aDataMask |= SEL_MASK_ELEM; + } + if (! GetToken(false)) { // premature eof is ok (here!) + return eSelectorParsingStatus_Done; + } + } + else if (mToken.IsSymbol('|')) { // No namespace + aDataMask |= SEL_MASK_NSPACE; + aSelector.SetNameSpace(kNameSpaceID_None); // explicit NO namespace + + // get mandatory tag + if (! GetToken(false)) { + REPORT_UNEXPECTED_EOF(PETypeSelEOF); + return eSelectorParsingStatus_Error; + } + if (eCSSToken_Ident == mToken.mType) { // element name + aDataMask |= SEL_MASK_ELEM; + aSelector.SetTag(mToken.mIdent); + } + else if (mToken.IsSymbol('*')) { // universal selector + aDataMask |= SEL_MASK_ELEM; + // don't set tag + } + else { + REPORT_UNEXPECTED_TOKEN(PETypeSelNotType); + UngetToken(); + return eSelectorParsingStatus_Error; + } + if (! GetToken(false)) { // premature eof is ok (here!) + return eSelectorParsingStatus_Done; + } + } + else { + SetDefaultNamespaceOnSelector(aSelector); + } + + if (aIsNegated) { + // restore last token read in case of a negated type selector + UngetToken(); + } + return eSelectorParsingStatus_Continue; +} + +// +// Parse attribute selectors [attr], [attr=value], [attr|=value], +// [attr~=value], [attr^=value], [attr$=value] and [attr*=value] +// +CSSParserImpl::nsSelectorParsingStatus +CSSParserImpl::ParseAttributeSelector(int32_t& aDataMask, + nsCSSSelector& aSelector) +{ + if (! GetToken(true)) { // premature EOF + REPORT_UNEXPECTED_EOF(PEAttributeNameEOF); + return eSelectorParsingStatus_Error; + } + + int32_t nameSpaceID = kNameSpaceID_None; + nsAutoString attr; + if (mToken.IsSymbol('*')) { // wildcard namespace + nameSpaceID = kNameSpaceID_Unknown; + if (ExpectSymbol('|', false)) { + if (! GetToken(false)) { // premature EOF + REPORT_UNEXPECTED_EOF(PEAttributeNameEOF); + return eSelectorParsingStatus_Error; + } + if (eCSSToken_Ident == mToken.mType) { // attr name + attr = mToken.mIdent; + } + else { + REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected); + UngetToken(); + return eSelectorParsingStatus_Error; + } + } + else { + REPORT_UNEXPECTED_TOKEN(PEAttSelNoBar); + return eSelectorParsingStatus_Error; + } + } + else if (mToken.IsSymbol('|')) { // NO namespace + if (! GetToken(false)) { // premature EOF + REPORT_UNEXPECTED_EOF(PEAttributeNameEOF); + return eSelectorParsingStatus_Error; + } + if (eCSSToken_Ident == mToken.mType) { // attr name + attr = mToken.mIdent; + } + else { + REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected); + UngetToken(); + return eSelectorParsingStatus_Error; + } + } + else if (eCSSToken_Ident == mToken.mType) { // attr name or namespace + attr = mToken.mIdent; // hang on to it + if (ExpectSymbol('|', false)) { // was a namespace + nameSpaceID = GetNamespaceIdForPrefix(attr); + if (nameSpaceID == kNameSpaceID_Unknown) { + return eSelectorParsingStatus_Error; + } + if (! GetToken(false)) { // premature EOF + REPORT_UNEXPECTED_EOF(PEAttributeNameEOF); + return eSelectorParsingStatus_Error; + } + if (eCSSToken_Ident == mToken.mType) { // attr name + attr = mToken.mIdent; + } + else { + REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected); + UngetToken(); + return eSelectorParsingStatus_Error; + } + } + } + else { // malformed + REPORT_UNEXPECTED_TOKEN(PEAttributeNameOrNamespaceExpected); + UngetToken(); + return eSelectorParsingStatus_Error; + } + + bool gotEOF = false; + if (! GetToken(true)) { // premature EOF + // Treat this just like we saw a ']', but do still output the + // warning, similar to what ExpectSymbol does. + REPORT_UNEXPECTED_EOF(PEAttSelInnerEOF); + gotEOF = true; + } + if (gotEOF || + (eCSSToken_Symbol == mToken.mType) || + (eCSSToken_Includes == mToken.mType) || + (eCSSToken_Dashmatch == mToken.mType) || + (eCSSToken_Beginsmatch == mToken.mType) || + (eCSSToken_Endsmatch == mToken.mType) || + (eCSSToken_Containsmatch == mToken.mType)) { + uint8_t func; + // Important: Check the EOF/']' case first, since if gotEOF we + // don't want to be examining mToken. + if (gotEOF || ']' == mToken.mSymbol) { + aDataMask |= SEL_MASK_ATTRIB; + aSelector.AddAttribute(nameSpaceID, attr); + func = NS_ATTR_FUNC_SET; + } + else if (eCSSToken_Includes == mToken.mType) { + func = NS_ATTR_FUNC_INCLUDES; + } + else if (eCSSToken_Dashmatch == mToken.mType) { + func = NS_ATTR_FUNC_DASHMATCH; + } + else if (eCSSToken_Beginsmatch == mToken.mType) { + func = NS_ATTR_FUNC_BEGINSMATCH; + } + else if (eCSSToken_Endsmatch == mToken.mType) { + func = NS_ATTR_FUNC_ENDSMATCH; + } + else if (eCSSToken_Containsmatch == mToken.mType) { + func = NS_ATTR_FUNC_CONTAINSMATCH; + } + else if ('=' == mToken.mSymbol) { + func = NS_ATTR_FUNC_EQUALS; + } + else { + REPORT_UNEXPECTED_TOKEN(PEAttSelUnexpected); + UngetToken(); // bad function + return eSelectorParsingStatus_Error; + } + if (NS_ATTR_FUNC_SET != func) { // get value + if (! GetToken(true)) { // premature EOF + REPORT_UNEXPECTED_EOF(PEAttSelValueEOF); + return eSelectorParsingStatus_Error; + } + if ((eCSSToken_Ident == mToken.mType) || (eCSSToken_String == mToken.mType)) { + nsAutoString value(mToken.mIdent); + // Avoid duplicating the eof handling by just not checking for + // the 'i' annotation if we got eof. + typedef nsAttrSelector::ValueCaseSensitivity ValueCaseSensitivity; + ValueCaseSensitivity valueCaseSensitivity = + ValueCaseSensitivity::CaseSensitive; + bool eof = !GetToken(true); + if (!eof) { + if (eCSSToken_Ident == mToken.mType && + mToken.mIdent.LowerCaseEqualsLiteral("i")) { + valueCaseSensitivity = ValueCaseSensitivity::CaseInsensitive; + eof = !GetToken(true); + } + } + bool gotClosingBracket; + if (eof) { // premature EOF + // Report a warning, but then treat it as a closing bracket. + REPORT_UNEXPECTED_EOF(PEAttSelCloseEOF); + gotClosingBracket = true; + } else { + gotClosingBracket = mToken.IsSymbol(']'); + } + if (gotClosingBracket) { + // For cases when this style sheet is applied to an HTML + // element in an HTML document, and the attribute selector is + // for a non-namespaced attribute, then check to see if it's + // one of the known attributes whose VALUE is + // case-insensitive. + if (valueCaseSensitivity != ValueCaseSensitivity::CaseInsensitive && + nameSpaceID == kNameSpaceID_None) { + static const char* caseInsensitiveHTMLAttribute[] = { + // list based on http://www.w3.org/TR/html4/ + "lang", + "dir", + "http-equiv", + "text", + "link", + "vlink", + "alink", + "compact", + "align", + "frame", + "rules", + "valign", + "scope", + "axis", + "nowrap", + "hreflang", + "rel", + "rev", + "charset", + "codetype", + "declare", + "valuetype", + "shape", + "nohref", + "media", + "bgcolor", + "clear", + "color", + "face", + "noshade", + "noresize", + "scrolling", + "target", + "method", + "enctype", + "accept-charset", + "accept", + "checked", + "multiple", + "selected", + "disabled", + "readonly", + "language", + "defer", + "type", + // additional attributes not in HTML4 + "direction", // marquee + nullptr + }; + short i = 0; + const char* htmlAttr; + while ((htmlAttr = caseInsensitiveHTMLAttribute[i++])) { + if (attr.LowerCaseEqualsASCII(htmlAttr)) { + valueCaseSensitivity = ValueCaseSensitivity::CaseInsensitiveInHTML; + break; + } + } + } + aDataMask |= SEL_MASK_ATTRIB; + aSelector.AddAttribute(nameSpaceID, attr, func, value, + valueCaseSensitivity); + } + else { + REPORT_UNEXPECTED_TOKEN(PEAttSelNoClose); + UngetToken(); + return eSelectorParsingStatus_Error; + } + } + else { + REPORT_UNEXPECTED_TOKEN(PEAttSelBadValue); + UngetToken(); + return eSelectorParsingStatus_Error; + } + } + } + else { + REPORT_UNEXPECTED_TOKEN(PEAttSelUnexpected); + UngetToken(); // bad dog, no biscut! + return eSelectorParsingStatus_Error; + } + return eSelectorParsingStatus_Continue; +} + +// +// Parse pseudo-classes and pseudo-elements +// +CSSParserImpl::nsSelectorParsingStatus +CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask, + nsCSSSelector& aSelector, + bool aIsNegated, + nsIAtom** aPseudoElement, + nsAtomList** aPseudoElementArgs, + CSSPseudoElementType* aPseudoElementType) +{ + NS_ASSERTION(aIsNegated || (aPseudoElement && aPseudoElementArgs), + "expected location to store pseudo element"); + NS_ASSERTION(!aIsNegated || (!aPseudoElement && !aPseudoElementArgs), + "negated selectors shouldn't have a place to store " + "pseudo elements"); + if (! GetToken(false)) { // premature eof + REPORT_UNEXPECTED_EOF(PEPseudoSelEOF); + return eSelectorParsingStatus_Error; + } + + // First, find out whether we are parsing a CSS3 pseudo-element + bool parsingPseudoElement = false; + if (mToken.IsSymbol(':')) { + parsingPseudoElement = true; + if (! GetToken(false)) { // premature eof + REPORT_UNEXPECTED_EOF(PEPseudoSelEOF); + return eSelectorParsingStatus_Error; + } + } + + // Do some sanity-checking on the token + if (eCSSToken_Ident != mToken.mType && eCSSToken_Function != mToken.mType) { + // malformed selector + REPORT_UNEXPECTED_TOKEN(PEPseudoSelBadName); + UngetToken(); + return eSelectorParsingStatus_Error; + } + + // OK, now we know we have an mIdent. Atomize it. All the atoms, for + // pseudo-classes as well as pseudo-elements, start with a single ':'. + nsAutoString buffer; + buffer.Append(char16_t(':')); + buffer.Append(mToken.mIdent); + nsContentUtils::ASCIIToLower(buffer); + nsCOMPtr<nsIAtom> pseudo = NS_Atomize(buffer); + + // stash away some info about this pseudo so we only have to get it once. + bool isTreePseudo = false; + CSSEnabledState enabledState = EnabledState(); + CSSPseudoElementType pseudoElementType = + nsCSSPseudoElements::GetPseudoType(pseudo, enabledState); + CSSPseudoClassType pseudoClassType = + nsCSSPseudoClasses::GetPseudoType(pseudo, enabledState); + bool pseudoClassIsUserAction = + nsCSSPseudoClasses::IsUserActionPseudoClass(pseudoClassType); + + if (nsCSSAnonBoxes::IsNonElement(pseudo)) { + // Non-element anonymous boxes should not match any rule. + REPORT_UNEXPECTED_TOKEN(PEPseudoSelUnknown); + UngetToken(); + return eSelectorParsingStatus_Error; + } + + // We currently allow :-moz-placeholder and ::-moz-placeholder and + // ::placeholder. We have to be a bit stricter regarding the + // pseudo-element parsing rules. + if (pseudoElementType == CSSPseudoElementType::placeholder && + pseudoClassType == CSSPseudoClassType::mozPlaceholder) { + if (parsingPseudoElement) { + pseudoClassType = CSSPseudoClassType::NotPseudo; + } else { + pseudoElementType = CSSPseudoElementType::NotPseudo; + } + } + +#ifdef MOZ_XUL + isTreePseudo = (pseudoElementType == CSSPseudoElementType::XULTree); + // If a tree pseudo-element is using the function syntax, it will + // get isTree set here and will pass the check below that only + // allows functions if they are in our list of things allowed to be + // functions. If it is _not_ using the function syntax, isTree will + // be false, and it will still pass that check. So the tree + // pseudo-elements are allowed to be either functions or not, as + // desired. + bool isTree = (eCSSToken_Function == mToken.mType) && isTreePseudo; +#endif + bool isPseudoElement = (pseudoElementType < CSSPseudoElementType::Count); + // anonymous boxes are only allowed if they're the tree boxes or we have + // enabled agent rules + bool isAnonBox = isTreePseudo || + (pseudoElementType == CSSPseudoElementType::AnonBox && + AgentRulesEnabled()); + bool isPseudoClass = + (pseudoClassType != CSSPseudoClassType::NotPseudo); + + NS_ASSERTION(!isPseudoClass || + pseudoElementType == CSSPseudoElementType::NotPseudo, + "Why is this atom both a pseudo-class and a pseudo-element?"); + NS_ASSERTION(isPseudoClass + isPseudoElement + isAnonBox <= 1, + "Shouldn't be more than one of these"); + + if (!isPseudoClass && !isPseudoElement && !isAnonBox) { + // Not a pseudo-class, not a pseudo-element.... forget it + REPORT_UNEXPECTED_TOKEN(PEPseudoSelUnknown); + UngetToken(); + return eSelectorParsingStatus_Error; + } + + // If it's a function token, it better be on our "ok" list, and if the name + // is that of a function pseudo it better be a function token + if ((eCSSToken_Function == mToken.mType) != + ( +#ifdef MOZ_XUL + isTree || +#endif + CSSPseudoClassType::negation == pseudoClassType || + nsCSSPseudoClasses::HasStringArg(pseudoClassType) || + nsCSSPseudoClasses::HasNthPairArg(pseudoClassType) || + nsCSSPseudoClasses::HasSelectorListArg(pseudoClassType))) { + // There are no other function pseudos + REPORT_UNEXPECTED_TOKEN(PEPseudoSelNonFunc); + UngetToken(); + return eSelectorParsingStatus_Error; + } + + // If it starts with "::", it better be a pseudo-element + if (parsingPseudoElement && + !isPseudoElement && + !isAnonBox) { + REPORT_UNEXPECTED_TOKEN(PEPseudoSelNotPE); + UngetToken(); + return eSelectorParsingStatus_Error; + } + + if (aSelector.IsPseudoElement()) { + CSSPseudoElementType type = aSelector.PseudoType(); + if (type >= CSSPseudoElementType::Count || + !nsCSSPseudoElements::PseudoElementSupportsUserActionState(type)) { + // We only allow user action pseudo-classes on certain pseudo-elements. + REPORT_UNEXPECTED_TOKEN(PEPseudoSelNoUserActionPC); + UngetToken(); + return eSelectorParsingStatus_Error; + } + if (!isPseudoClass || !pseudoClassIsUserAction) { + // CSS 4 Selectors says that pseudo-elements can only be followed by + // a user action pseudo-class. + REPORT_UNEXPECTED_TOKEN(PEPseudoClassNotUserAction); + UngetToken(); + return eSelectorParsingStatus_Error; + } + } + + if (!parsingPseudoElement && + CSSPseudoClassType::negation == pseudoClassType) { + if (aIsNegated) { // :not() can't be itself negated + REPORT_UNEXPECTED_TOKEN(PEPseudoSelDoubleNot); + UngetToken(); + return eSelectorParsingStatus_Error; + } + // CSS 3 Negation pseudo-class takes one simple selector as argument + nsSelectorParsingStatus parsingStatus = + ParseNegatedSimpleSelector(aDataMask, aSelector); + if (eSelectorParsingStatus_Continue != parsingStatus) { + return parsingStatus; + } + } + else if (!parsingPseudoElement && isPseudoClass) { + aDataMask |= SEL_MASK_PCLASS; + if (eCSSToken_Function == mToken.mType) { + nsSelectorParsingStatus parsingStatus; + if (nsCSSPseudoClasses::HasStringArg(pseudoClassType)) { + parsingStatus = + ParsePseudoClassWithIdentArg(aSelector, pseudoClassType); + } + else if (nsCSSPseudoClasses::HasNthPairArg(pseudoClassType)) { + parsingStatus = + ParsePseudoClassWithNthPairArg(aSelector, pseudoClassType); + } + else { + MOZ_ASSERT(nsCSSPseudoClasses::HasSelectorListArg(pseudoClassType), + "unexpected pseudo with function token"); + parsingStatus = ParsePseudoClassWithSelectorListArg(aSelector, + pseudoClassType); + } + if (eSelectorParsingStatus_Continue != parsingStatus) { + if (eSelectorParsingStatus_Error == parsingStatus) { + SkipUntil(')'); + } + return parsingStatus; + } + } + else { + aSelector.AddPseudoClass(pseudoClassType); + } + } + else if (isPseudoElement || isAnonBox) { + // Pseudo-element. Make some more sanity checks. + + if (aIsNegated) { // pseudo-elements can't be negated + REPORT_UNEXPECTED_TOKEN(PEPseudoSelPEInNot); + UngetToken(); + return eSelectorParsingStatus_Error; + } + // CSS2 pseudo-elements and -moz-tree-* pseudo-elements are allowed + // to have a single ':' on them. Others (CSS3+ pseudo-elements and + // various -moz-* pseudo-elements) must have |parsingPseudoElement| + // set. + if (!parsingPseudoElement && + !nsCSSPseudoElements::IsCSS2PseudoElement(pseudo) +#ifdef MOZ_XUL + && !isTreePseudo +#endif + ) { + REPORT_UNEXPECTED_TOKEN(PEPseudoSelNewStyleOnly); + UngetToken(); + return eSelectorParsingStatus_Error; + } + + if (0 == (aDataMask & SEL_MASK_PELEM)) { + aDataMask |= SEL_MASK_PELEM; + NS_ADDREF(*aPseudoElement = pseudo); + *aPseudoElementType = pseudoElementType; + +#ifdef MOZ_XUL + if (isTree) { + // We have encountered a pseudoelement of the form + // -moz-tree-xxxx(a,b,c). We parse (a,b,c) and add each + // item in the list to the pseudoclass list. They will be pulled + // from the list later along with the pseudo-element. + if (!ParseTreePseudoElement(aPseudoElementArgs)) { + return eSelectorParsingStatus_Error; + } + } +#endif + + // Pseudo-elements can only be followed by user action pseudo-classes + // or be the end of the selector. So the next non-whitespace token must + // be ':', '{' or ',' or EOF. + if (!GetToken(true)) { // premature eof is ok (here!) + return eSelectorParsingStatus_Done; + } + if (parsingPseudoElement && mToken.IsSymbol(':')) { + UngetToken(); + return eSelectorParsingStatus_Continue; + } + if ((mToken.IsSymbol('{') || mToken.IsSymbol(','))) { + UngetToken(); + return eSelectorParsingStatus_Done; + } + REPORT_UNEXPECTED_TOKEN(PEPseudoSelEndOrUserActionPC); + UngetToken(); + return eSelectorParsingStatus_Error; + } + else { // multiple pseudo elements, not legal + REPORT_UNEXPECTED_TOKEN(PEPseudoSelMultiplePE); + UngetToken(); + return eSelectorParsingStatus_Error; + } + } +#ifdef DEBUG + else { + // We should never end up here. Indeed, if we ended up here, we know (from + // the current if/else cascade) that !isPseudoElement and !isAnonBox. But + // then due to our earlier check we know that isPseudoClass. Since we + // didn't fall into the isPseudoClass case in this cascade, we must have + // parsingPseudoElement. But we've already checked the + // parsingPseudoElement && !isPseudoClass && !isAnonBox case and bailed if + // it's happened. + NS_NOTREACHED("How did this happen?"); + } +#endif + return eSelectorParsingStatus_Continue; +} + +// +// Parse the argument of a negation pseudo-class :not() +// +CSSParserImpl::nsSelectorParsingStatus +CSSParserImpl::ParseNegatedSimpleSelector(int32_t& aDataMask, + nsCSSSelector& aSelector) +{ + if (! GetToken(true)) { // premature eof + REPORT_UNEXPECTED_EOF(PENegationEOF); + return eSelectorParsingStatus_Error; + } + + if (mToken.IsSymbol(')')) { + REPORT_UNEXPECTED_TOKEN(PENegationBadArg); + return eSelectorParsingStatus_Error; + } + + // Create a new nsCSSSelector and add it to the end of + // aSelector.mNegations. + // Given the current parsing rules, every selector in mNegations + // contains only one simple selector (css3 definition) within it. + // This could easily change in future versions of CSS, and the only + // thing we need to change to support that is this parsing code and the + // serialization code for nsCSSSelector. + nsCSSSelector *newSel = new nsCSSSelector(); + nsCSSSelector* negations = &aSelector; + while (negations->mNegations) { + negations = negations->mNegations; + } + negations->mNegations = newSel; + + nsSelectorParsingStatus parsingStatus; + if (eCSSToken_ID == mToken.mType) { // #id + parsingStatus = ParseIDSelector(aDataMask, *newSel); + } + else if (mToken.IsSymbol('.')) { // .class + parsingStatus = ParseClassSelector(aDataMask, *newSel); + } + else if (mToken.IsSymbol(':')) { // :pseudo + parsingStatus = ParsePseudoSelector(aDataMask, *newSel, true, + nullptr, nullptr, nullptr); + } + else if (mToken.IsSymbol('[')) { // [attribute + parsingStatus = ParseAttributeSelector(aDataMask, *newSel); + if (eSelectorParsingStatus_Error == parsingStatus) { + // Skip forward to the matching ']' + SkipUntil(']'); + } + } + else { + // then it should be a type element or universal selector + parsingStatus = ParseTypeOrUniversalSelector(aDataMask, *newSel, true); + } + if (eSelectorParsingStatus_Error == parsingStatus) { + REPORT_UNEXPECTED_TOKEN(PENegationBadInner); + SkipUntil(')'); + return parsingStatus; + } + // close the parenthesis + if (!ExpectSymbol(')', true)) { + REPORT_UNEXPECTED_TOKEN(PENegationNoClose); + SkipUntil(')'); + return eSelectorParsingStatus_Error; + } + + NS_ASSERTION(newSel->mNameSpace == kNameSpaceID_Unknown || + (!newSel->mIDList && !newSel->mClassList && + !newSel->mPseudoClassList && !newSel->mAttrList), + "Need to fix the serialization code to deal with this"); + + return eSelectorParsingStatus_Continue; +} + +// +// Parse the argument of a pseudo-class that has an ident arg +// +CSSParserImpl::nsSelectorParsingStatus +CSSParserImpl::ParsePseudoClassWithIdentArg(nsCSSSelector& aSelector, + CSSPseudoClassType aType) +{ + if (! GetToken(true)) { // premature eof + REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF); + return eSelectorParsingStatus_Error; + } + // We expect an identifier with a language abbreviation + if (eCSSToken_Ident != mToken.mType) { + REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotIdent); + UngetToken(); + return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') + } + + // -moz-locale-dir and dir take an identifier argument. While + // only 'ltr' and 'rtl' (case-insensitively) will match anything, any + // other identifier is still valid. + if (aType == CSSPseudoClassType::mozLocaleDir || + aType == CSSPseudoClassType::mozDir || + aType == CSSPseudoClassType::dir) { + nsContentUtils::ASCIIToLower(mToken.mIdent); // case insensitive + } + + // Add the pseudo with the language parameter + aSelector.AddPseudoClass(aType, mToken.mIdent.get()); + + // close the parenthesis + if (!ExpectSymbol(')', true)) { + REPORT_UNEXPECTED_TOKEN(PEPseudoClassNoClose); + return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') + } + + return eSelectorParsingStatus_Continue; +} + +CSSParserImpl::nsSelectorParsingStatus +CSSParserImpl::ParsePseudoClassWithNthPairArg(nsCSSSelector& aSelector, + CSSPseudoClassType aType) +{ + int32_t numbers[2] = { 0, 0 }; + int32_t sign[2] = { 1, 1 }; + bool hasSign[2] = { false, false }; + bool lookForB = true; + + // Follow the whitespace rules as proposed in + // http://lists.w3.org/Archives/Public/www-style/2008Mar/0121.html + + if (! GetToken(true)) { + REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF); + return eSelectorParsingStatus_Error; + } + + if (mToken.IsSymbol('+') || mToken.IsSymbol('-')) { + hasSign[0] = true; + if (mToken.IsSymbol('-')) { + sign[0] = -1; + } + if (! GetToken(false)) { + REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF); + return eSelectorParsingStatus_Error; + } + } + + // A helper function that checks if the token starts with literal string + // |aStr| using a case-insensitive match. + auto TokenBeginsWith = [this] (const nsLiteralString& aStr) { + return StringBeginsWith(mToken.mIdent, aStr, + nsASCIICaseInsensitiveStringComparator()); + }; + + if (eCSSToken_Ident == mToken.mType || eCSSToken_Dimension == mToken.mType) { + // The CSS tokenization doesn't handle :nth-child() containing - well: + // 2n-1 is a dimension + // n-1 is an identifier + // The easiest way to deal with that is to push everything from the + // minus on back onto the scanner's pushback buffer. + uint32_t truncAt = 0; + if (TokenBeginsWith(NS_LITERAL_STRING("n-"))) { + truncAt = 1; + } else if (TokenBeginsWith(NS_LITERAL_STRING("-n-")) && !hasSign[0]) { + truncAt = 2; + } + if (truncAt != 0) { + mScanner->Backup(mToken.mIdent.Length() - truncAt); + mToken.mIdent.Truncate(truncAt); + } + } + + if (eCSSToken_Ident == mToken.mType) { + if (mToken.mIdent.LowerCaseEqualsLiteral("odd") && !hasSign[0]) { + numbers[0] = 2; + numbers[1] = 1; + lookForB = false; + } + else if (mToken.mIdent.LowerCaseEqualsLiteral("even") && !hasSign[0]) { + numbers[0] = 2; + numbers[1] = 0; + lookForB = false; + } + else if (mToken.mIdent.LowerCaseEqualsLiteral("n")) { + numbers[0] = sign[0]; + } + else if (mToken.mIdent.LowerCaseEqualsLiteral("-n") && !hasSign[0]) { + numbers[0] = -1; + } + else { + REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth); + return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') + } + } + else if (eCSSToken_Number == mToken.mType) { + if (!mToken.mIntegerValid) { + REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth); + return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') + } + // for +-an case + if (mToken.mHasSign && hasSign[0]) { + REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth); + return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') + } + int32_t intValue = mToken.mInteger * sign[0]; + // for -a/**/n case + if (! GetToken(false)) { + numbers[1] = intValue; + lookForB = false; + } + else { + if (eCSSToken_Ident == mToken.mType && mToken.mIdent.LowerCaseEqualsLiteral("n")) { + numbers[0] = intValue; + } + else if (eCSSToken_Ident == mToken.mType && TokenBeginsWith(NS_LITERAL_STRING("n-"))) { + numbers[0] = intValue; + mScanner->Backup(mToken.mIdent.Length() - 1); + } + else { + UngetToken(); + numbers[1] = intValue; + lookForB = false; + } + } + } + else if (eCSSToken_Dimension == mToken.mType) { + if (!mToken.mIntegerValid || !mToken.mIdent.LowerCaseEqualsLiteral("n")) { + REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth); + return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') + } + // for +-an case + if ( mToken.mHasSign && hasSign[0] ) { + REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth); + return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') + } + numbers[0] = mToken.mInteger * sign[0]; + } + // XXX If it's a ')', is that valid? (as 0n+0) + else { + REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth); + UngetToken(); + return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') + } + + if (! GetToken(true)) { + REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF); + return eSelectorParsingStatus_Error; + } + if (lookForB && !mToken.IsSymbol(')')) { + // The '+' or '-' sign can optionally be separated by whitespace. + // If it is separated by whitespace from what follows it, it appears + // as a separate token rather than part of the number token. + if (mToken.IsSymbol('+') || mToken.IsSymbol('-')) { + hasSign[1] = true; + if (mToken.IsSymbol('-')) { + sign[1] = -1; + } + if (! GetToken(true)) { + REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF); + return eSelectorParsingStatus_Error; + } + } + if (eCSSToken_Number != mToken.mType || + !mToken.mIntegerValid || mToken.mHasSign == hasSign[1]) { + REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth); + UngetToken(); + return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') + } + numbers[1] = mToken.mInteger * sign[1]; + if (! GetToken(true)) { + REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF); + return eSelectorParsingStatus_Error; + } + } + if (!mToken.IsSymbol(')')) { + REPORT_UNEXPECTED_TOKEN(PEPseudoClassNoClose); + return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') + } + aSelector.AddPseudoClass(aType, numbers); + return eSelectorParsingStatus_Continue; +} + +// +// Parse the argument of a pseudo-class that has a selector list argument. +// Such selector lists cannot contain combinators, but can contain +// anything that goes between a pair of combinators. +// +CSSParserImpl::nsSelectorParsingStatus +CSSParserImpl::ParsePseudoClassWithSelectorListArg(nsCSSSelector& aSelector, + CSSPseudoClassType aType) +{ + nsAutoPtr<nsCSSSelectorList> slist; + if (! ParseSelectorList(*getter_Transfers(slist), char16_t(')'))) { + return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') + } + + // Check that none of the selectors in the list have combinators or + // pseudo-elements. + for (nsCSSSelectorList *l = slist; l; l = l->mNext) { + nsCSSSelector *s = l->mSelectors; + if (s->mNext || s->IsPseudoElement()) { + return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') + } + } + + // Add the pseudo with the selector list parameter + aSelector.AddPseudoClass(aType, slist.forget()); + + // close the parenthesis + if (!ExpectSymbol(')', true)) { + REPORT_UNEXPECTED_TOKEN(PEPseudoClassNoClose); + return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') + } + + return eSelectorParsingStatus_Continue; +} + + +/** + * This is the format for selectors: + * operator? [[namespace |]? element_name]? [ ID | class | attrib | pseudo ]* + */ +bool +CSSParserImpl::ParseSelector(nsCSSSelectorList* aList, + char16_t aPrevCombinator) +{ + if (! GetToken(true)) { + REPORT_UNEXPECTED_EOF(PESelectorEOF); + return false; + } + + nsCSSSelector* selector = aList->AddSelector(aPrevCombinator); + nsCOMPtr<nsIAtom> pseudoElement; + nsAutoPtr<nsAtomList> pseudoElementArgs; + CSSPseudoElementType pseudoElementType = CSSPseudoElementType::NotPseudo; + + int32_t dataMask = 0; + nsSelectorParsingStatus parsingStatus = + ParseTypeOrUniversalSelector(dataMask, *selector, false); + + while (parsingStatus == eSelectorParsingStatus_Continue) { + if (mToken.IsSymbol(':')) { // :pseudo + parsingStatus = ParsePseudoSelector(dataMask, *selector, false, + getter_AddRefs(pseudoElement), + getter_Transfers(pseudoElementArgs), + &pseudoElementType); + if (pseudoElement && + pseudoElementType != CSSPseudoElementType::AnonBox) { + // Pseudo-elements other than anonymous boxes are represented with + // a special ':' combinator. + + aList->mWeight += selector->CalcWeight(); + + selector = aList->AddSelector(':'); + + selector->mLowercaseTag.swap(pseudoElement); + selector->mClassList = pseudoElementArgs.forget(); + selector->SetPseudoType(pseudoElementType); + } + } else if (selector->IsPseudoElement()) { + // Once we parsed a pseudo-element, we can only parse + // pseudo-classes (and only a limited set, which + // ParsePseudoSelector knows how to handle). + parsingStatus = eSelectorParsingStatus_Done; + UngetToken(); + break; + } else if (eCSSToken_ID == mToken.mType) { // #id + parsingStatus = ParseIDSelector(dataMask, *selector); + } else if (mToken.IsSymbol('.')) { // .class + parsingStatus = ParseClassSelector(dataMask, *selector); + } + else if (mToken.IsSymbol('[')) { // [attribute + parsingStatus = ParseAttributeSelector(dataMask, *selector); + if (eSelectorParsingStatus_Error == parsingStatus) { + SkipUntil(']'); + } + } + else { // not a selector token, we're done + parsingStatus = eSelectorParsingStatus_Done; + UngetToken(); + break; + } + + if (parsingStatus != eSelectorParsingStatus_Continue) { + break; + } + + if (! GetToken(false)) { // premature eof is ok (here!) + parsingStatus = eSelectorParsingStatus_Done; + break; + } + } + + if (parsingStatus == eSelectorParsingStatus_Error) { + return false; + } + + if (!dataMask) { + if (selector->mNext) { + REPORT_UNEXPECTED(PESelectorGroupExtraCombinator); + } else { + REPORT_UNEXPECTED(PESelectorGroupNoSelector); + } + return false; + } + + if (pseudoElementType == CSSPseudoElementType::AnonBox) { + // We got an anonymous box pseudo-element; it must be the only + // thing in this selector group. + if (selector->mNext || !IsUniversalSelector(*selector)) { + REPORT_UNEXPECTED(PEAnonBoxNotAlone); + return false; + } + + // Rewrite the current selector as this pseudo-element. + // It does not contribute to selector weight. + selector->mLowercaseTag.swap(pseudoElement); + selector->mClassList = pseudoElementArgs.forget(); + selector->SetPseudoType(pseudoElementType); + return true; + } + + aList->mWeight += selector->CalcWeight(); + + return true; +} + +already_AddRefed<css::Declaration> +CSSParserImpl::ParseDeclarationBlock(uint32_t aFlags, nsCSSContextType aContext) +{ + bool checkForBraces = (aFlags & eParseDeclaration_InBraces) != 0; + + MOZ_ASSERT(mWebkitBoxUnprefixState == eNotParsingDecls, + "Someone forgot to clear mWebkitBoxUnprefixState!"); + AutoRestore<WebkitBoxUnprefixState> autoRestore(mWebkitBoxUnprefixState); + mWebkitBoxUnprefixState = eHaveNotUnprefixed; + + if (checkForBraces) { + if (!ExpectSymbol('{', true)) { + REPORT_UNEXPECTED_TOKEN(PEBadDeclBlockStart); + OUTPUT_ERROR(); + return nullptr; + } + } + RefPtr<css::Declaration> declaration = new css::Declaration(); + mData.AssertInitialState(); + for (;;) { + bool changed = false; + if (!ParseDeclaration(declaration, aFlags, true, &changed, aContext)) { + if (!SkipDeclaration(checkForBraces)) { + break; + } + if (checkForBraces) { + if (ExpectSymbol('}', true)) { + break; + } + } + // Since the skipped declaration didn't end the block we parse + // the next declaration. + } + } + declaration->CompressFrom(&mData); + return declaration.forget(); +} + +CSSParseResult +CSSParserImpl::ParseColor(nsCSSValue& aValue) +{ + if (!GetToken(true)) { + REPORT_UNEXPECTED_EOF(PEColorEOF); + return CSSParseResult::NotFound; + } + + nsCSSToken* tk = &mToken; + nscolor rgba; + switch (tk->mType) { + case eCSSToken_ID: + case eCSSToken_Hash: + // #rgb, #rrggbb, #rgba, #rrggbbaa + if (NS_HexToRGBA(tk->mIdent, nsHexColorType::AllowAlpha, &rgba)) { + nsCSSUnit unit; + switch (tk->mIdent.Length()) { + case 3: + unit = eCSSUnit_ShortHexColor; + break; + case 4: + unit = eCSSUnit_ShortHexColorAlpha; + break; + case 6: + unit = eCSSUnit_HexColor; + break; + default: + MOZ_FALLTHROUGH_ASSERT("unexpected hex color length"); + case 8: + unit = eCSSUnit_HexColorAlpha; + break; + } + aValue.SetIntegerColorValue(rgba, unit); + return CSSParseResult::Ok; + } + break; + + case eCSSToken_Ident: + if (NS_ColorNameToRGB(tk->mIdent, &rgba)) { + aValue.SetStringValue(tk->mIdent, eCSSUnit_Ident); + return CSSParseResult::Ok; + } + else { + nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(tk->mIdent); + if (eCSSKeyword_UNKNOWN < keyword) { // known keyword + int32_t value; + if (nsCSSProps::FindKeyword(keyword, nsCSSProps::kColorKTable, value)) { + aValue.SetIntValue(value, eCSSUnit_EnumColor); + return CSSParseResult::Ok; + } + } + } + break; + case eCSSToken_Function: { + bool isRGB; + bool isHSL; + if ((isRGB = mToken.mIdent.LowerCaseEqualsLiteral("rgb")) || + mToken.mIdent.LowerCaseEqualsLiteral("rgba")) { + // rgb() = rgb( <percentage>{3} [ / <alpha-value> ]? ) | + // rgb( <number>{3} [ / <alpha-value> ]? ) | + // rgb( <percentage>#{3} , <alpha-value>? ) | + // rgb( <number>#{3} , <alpha-value>? ) + // <alpha-value> = <number> | <percentage> + // rgba is an alias of rgb. + + if (GetToken(true)) { + UngetToken(); + } + if (mToken.mType == eCSSToken_Number) { // <number> + uint8_t r, g, b, a; + + if (ParseRGBColor(r, g, b, a)) { + aValue.SetIntegerColorValue(NS_RGBA(r, g, b, a), + isRGB ? eCSSUnit_RGBColor : eCSSUnit_RGBAColor); + return CSSParseResult::Ok; + } + } else { // <percentage> + float r, g, b, a; + + if (ParseRGBColor(r, g, b, a)) { + aValue.SetFloatColorValue(r, g, b, a, + isRGB ? eCSSUnit_PercentageRGBColor : eCSSUnit_PercentageRGBAColor); + return CSSParseResult::Ok; + } + } + SkipUntil(')'); + return CSSParseResult::Error; + } + else if ((isHSL = mToken.mIdent.LowerCaseEqualsLiteral("hsl")) || + mToken.mIdent.LowerCaseEqualsLiteral("hsla")) { + // hsl() = hsl( <hue> <percentage> <percentage> [ / <alpha-value> ]? ) || + // hsl( <hue>, <percentage>, <percentage>, <alpha-value>? ) + // <hue> = <number> | <angle> + // hsla is an alias of hsl. + + float h, s, l, a; + + if (ParseHSLColor(h, s, l, a)) { + aValue.SetFloatColorValue(h, s, l, a, + isHSL ? eCSSUnit_HSLColor : eCSSUnit_HSLAColor); + return CSSParseResult::Ok; + } + SkipUntil(')'); + return CSSParseResult::Error; + } + break; + } + default: + break; + } + + // try 'xxyyzz' without '#' prefix for compatibility with IE and Nav4x (bug 23236 and 45804) + if (mHashlessColorQuirk) { + // - If the string starts with 'a-f', the nsCSSScanner builds the + // token as a eCSSToken_Ident and we can parse the string as a + // 'xxyyzz' RGB color. + // - If it only contains '0-9' digits, the token is a + // eCSSToken_Number and it must be converted back to a 6 + // characters string to be parsed as a RGB color. + // - If it starts with '0-9' and contains any 'a-f', the token is a + // eCSSToken_Dimension, the mNumber part must be converted back to + // a string and the mIdent part must be appended to that string so + // that the resulting string has 6 characters. + // Note: This is a hack for Nav compatibility. Do not attempt to + // simplify it by hacking into the ncCSSScanner. This would be very + // bad. + nsAutoString str; + char buffer[20]; + switch (tk->mType) { + case eCSSToken_Ident: + str.Assign(tk->mIdent); + break; + + case eCSSToken_Number: + if (tk->mIntegerValid) { + SprintfLiteral(buffer, "%06d", tk->mInteger); + str.AssignWithConversion(buffer); + } + break; + + case eCSSToken_Dimension: + if (tk->mIdent.Length() <= 6) { + SprintfLiteral(buffer, "%06.0f", tk->mNumber); + nsAutoString temp; + temp.AssignWithConversion(buffer); + temp.Right(str, 6 - tk->mIdent.Length()); + str.Append(tk->mIdent); + } + break; + default: + // There is a whole bunch of cases that are + // not handled by this switch. Ignore them. + break; + } + // The hashless color quirk does not support 4 & 8 digit colors with alpha. + if (NS_HexToRGBA(str, nsHexColorType::NoAlpha, &rgba)) { + aValue.SetIntegerColorValue(rgba, eCSSUnit_HexColor); + return CSSParseResult::Ok; + } + } + + // It's not a color + REPORT_UNEXPECTED_TOKEN(PEColorNotColor); + UngetToken(); + return CSSParseResult::NotFound; +} + +bool +CSSParserImpl::ParseColorComponent(uint8_t& aComponent, Maybe<char> aSeparator) +{ + if (!GetToken(true)) { + REPORT_UNEXPECTED_EOF(PEColorComponentEOF); + return false; + } + + if (mToken.mType != eCSSToken_Number) { + REPORT_UNEXPECTED_TOKEN(PEExpectedNumber); + UngetToken(); + return false; + } + + float value = mToken.mNumber; + + if (aSeparator && !ExpectSymbol(*aSeparator, true)) { + REPORT_UNEXPECTED_TOKEN_CHAR(PEColorComponentBadTerm, *aSeparator); + return false; + } + + if (value < 0.0f) value = 0.0f; + if (value > 255.0f) value = 255.0f; + + aComponent = NSToIntRound(value); + return true; +} + +bool +CSSParserImpl::ParseColorComponent(float& aComponent, Maybe<char> aSeparator) +{ + if (!GetToken(true)) { + REPORT_UNEXPECTED_EOF(PEColorComponentEOF); + return false; + } + + if (mToken.mType != eCSSToken_Percentage) { + REPORT_UNEXPECTED_TOKEN(PEExpectedPercent); + UngetToken(); + return false; + } + + float value = mToken.mNumber; + + if (aSeparator && !ExpectSymbol(*aSeparator, true)) { + REPORT_UNEXPECTED_TOKEN_CHAR(PEColorComponentBadTerm, *aSeparator); + return false; + } + + if (value < 0.0f) value = 0.0f; + if (value > 1.0f) value = 1.0f; + + aComponent = value; + return true; +} + +bool +CSSParserImpl::ParseHue(float& aAngle) +{ + if (!GetToken(true)) { + REPORT_UNEXPECTED_EOF(PEColorHueEOF); + return false; + } + + // <number> + if (mToken.mType == eCSSToken_Number) { + aAngle = mToken.mNumber; + return true; + } + UngetToken(); + + // <angle> + nsCSSValue angleValue; + // The '0' value is handled by <number> parsing, so use VARIANT_ANGLE flag + // instead of VARIANT_ANGLE_OR_ZERO. + if (ParseSingleTokenVariant(angleValue, VARIANT_ANGLE, nullptr)) { + aAngle = angleValue.GetAngleValueInDegrees(); + return true; + } + + REPORT_UNEXPECTED_TOKEN(PEExpectedNumberOrAngle); + return false; +} + +bool +CSSParserImpl::ParseHSLColor(float& aHue, float& aSaturation, float& aLightness, + float& aOpacity) +{ + // comma-less expression: + // hsl() = hsl( <hue> <saturation> <lightness> [ / <alpha-value> ]? ) + // the expression with comma: + // hsl() = hsl( <hue>, <saturation>, <lightness>, <alpha-value>? ) + + const char commaSeparator = ','; + + // Parse hue. + // <hue> = <number> | <angle> + float degreeAngle; + if (!ParseHue(degreeAngle)) { + return false; + } + aHue = degreeAngle / 360.0f; + // hue values are wraparound + aHue = aHue - floor(aHue); + // Look for a comma separator after "hue" component to determine if the + // expression is comma-less or not. + bool hasComma = ExpectSymbol(commaSeparator, true); + + // Parse saturation, lightness and opacity. + // The saturation and lightness are <percentage>, so reuse the float version + // of ParseColorComponent function for them. No need to check the separator + // after 'lightness'. It will be checked in opacity value parsing. + const char separatorBeforeAlpha = hasComma ? commaSeparator : '/'; + if (ParseColorComponent(aSaturation, hasComma ? Some(commaSeparator) : Nothing()) && + ParseColorComponent(aLightness, Nothing()) && + ParseColorOpacityAndCloseParen(aOpacity, separatorBeforeAlpha)) { + return true; + } + + return false; +} + + +bool +CSSParserImpl::ParseColorOpacityAndCloseParen(uint8_t& aOpacity, + char aSeparator) +{ + float floatOpacity; + if (!ParseColorOpacityAndCloseParen(floatOpacity, aSeparator)) { + return false; + } + + uint8_t value = nsStyleUtil::FloatToColorComponent(floatOpacity); + // Need to compare to something slightly larger + // than 0.5 due to floating point inaccuracies. + NS_ASSERTION(fabs(255.0f * floatOpacity - value) <= 0.51f, + "FloatToColorComponent did something weird"); + + aOpacity = value; + return true; +} + +bool +CSSParserImpl::ParseColorOpacityAndCloseParen(float& aOpacity, + char aSeparator) +{ + if (ExpectSymbol(')', true)) { + // The optional [separator <alpha-value>] was omitted, so set the opacity + // to a fully-opaque value '1.0f' and return success. + aOpacity = 1.0f; + return true; + } + + if (!ExpectSymbol(aSeparator, true)) { + REPORT_UNEXPECTED_TOKEN_CHAR(PEColorComponentBadTerm, aSeparator); + return false; + } + + if (!GetToken(true)) { + REPORT_UNEXPECTED_EOF(PEColorOpacityEOF); + return false; + } + + // eCSSToken_Number or eCSSToken_Percentage. + if (mToken.mType != eCSSToken_Number && mToken.mType != eCSSToken_Percentage) { + REPORT_UNEXPECTED_TOKEN(PEExpectedNumberOrPercent); + UngetToken(); + return false; + } + + if (!ExpectSymbol(')', true)) { + REPORT_UNEXPECTED_TOKEN(PEExpectedCloseParen); + return false; + } + + if (mToken.mNumber < 0.0f) { + mToken.mNumber = 0.0f; + } else if (mToken.mNumber > 1.0f) { + mToken.mNumber = 1.0f; + } + + aOpacity = mToken.mNumber; + return true; +} + +template<typename ComponentType> +bool +CSSParserImpl::ParseRGBColor(ComponentType& aR, + ComponentType& aG, + ComponentType& aB, + ComponentType& aA) +{ + // comma-less expression: + // rgb() = rgb( component{3} [ / <alpha-value> ]? ) + // the expression with comma: + // rgb() = rgb( component#{3} , <alpha-value>? ) + + const char commaSeparator = ','; + + // Parse R. + if (!ParseColorComponent(aR, Nothing())) { + return false; + } + // Look for a comma separator after "r" component to determine if the + // expression is comma-less or not. + bool hasComma = ExpectSymbol(commaSeparator, true); + + // Parse G, B and A. + // No need to check the separator after 'B'. It will be checked in 'A' value + // parsing. + const char separatorBeforeAlpha = hasComma ? commaSeparator : '/'; + if (ParseColorComponent(aG, hasComma ? Some(commaSeparator) : Nothing()) && + ParseColorComponent(aB, Nothing()) && + ParseColorOpacityAndCloseParen(aA, separatorBeforeAlpha)) { + return true; + } + + return false; +} + +#ifdef MOZ_XUL +bool +CSSParserImpl::ParseTreePseudoElement(nsAtomList **aPseudoElementArgs) +{ + // The argument to a tree pseudo-element is a sequence of identifiers + // that are either space- or comma-separated. (Was the intent to + // allow only comma-separated? That's not what was done.) + nsCSSSelector fakeSelector; // so we can reuse AddPseudoClass + + while (!ExpectSymbol(')', true)) { + if (!GetToken(true)) { + return false; + } + if (eCSSToken_Ident == mToken.mType) { + fakeSelector.AddClass(mToken.mIdent); + } + else if (!mToken.IsSymbol(',')) { + UngetToken(); + SkipUntil(')'); + return false; + } + } + *aPseudoElementArgs = fakeSelector.mClassList; + fakeSelector.mClassList = nullptr; + return true; +} +#endif + +nsCSSKeyword +CSSParserImpl::LookupKeywordPrefixAware(nsAString& aKeywordStr, + const KTableEntry aKeywordTable[]) +{ + nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(aKeywordStr); + + if (aKeywordTable == nsCSSProps::kDisplayKTable) { + // NOTE: This code will be considerably simpler once we can do away with + // all Unprefixing Service code, in bug 1259348. But for the time being, we + // have to support two different strategies for handling -webkit-box here: + // (1) "Native support" (sWebkitPrefixedAliasesEnabled): we assume that + // -webkit-box will parse correctly (via an entry in kDisplayKTable), + // and we simply make a note that we've parsed it (so that we can we + // can give later "-moz-box" styling special handling as noted below). + // (2) "Unprefixing Service support" (ShouldUseUnprefixingService): we + // convert "-webkit-box" directly to modern "flex" (& do the same for + // any later "-moz-box" styling). + // + // Note that sWebkitPrefixedAliasesEnabled and + // ShouldUseUnprefixingService() are mutually exlusive, because the latter + // explicitly defers to the former. + if ((keyword == eCSSKeyword__webkit_box || + keyword == eCSSKeyword__webkit_inline_box)) { + const bool usingUnprefixingService = ShouldUseUnprefixingService(); + if (sWebkitPrefixedAliasesEnabled || usingUnprefixingService) { + // Make a note that we're accepting some "-webkit-{inline-}box" styling, + // so we can give special treatment to subsequent "-moz-{inline}-box". + // (See special treatment below.) + if (mWebkitBoxUnprefixState == eHaveNotUnprefixed) { + mWebkitBoxUnprefixState = eHaveUnprefixed; + } + if (usingUnprefixingService) { + // When we're using the unprefixing service, we treat + // "display:-webkit-box" as if it were "display:flex" + // (and "-webkit-inline-box" as "inline-flex"). + return (keyword == eCSSKeyword__webkit_box) ? + eCSSKeyword_flex : eCSSKeyword_inline_flex; + } + } + } + + // If we've seen "display: -webkit-box" (or "-webkit-inline-box") in an + // earlier declaration and we honored it, then we have to watch out for + // later "display: -moz-box" (and "-moz-inline-box") declarations; they're + // likely just a halfhearted attempt at compatibility, and they actually + // end up stomping on our emulation of the earlier -webkit-box + // display-value, via the CSS cascade. To prevent this problem, we treat + // "display: -moz-box" & "-moz-inline-box" as if they were simply a + // repetition of the webkit equivalent that we already parsed. + if (mWebkitBoxUnprefixState == eHaveUnprefixed && + (keyword == eCSSKeyword__moz_box || + keyword == eCSSKeyword__moz_inline_box)) { + MOZ_ASSERT(sWebkitPrefixedAliasesEnabled || ShouldUseUnprefixingService(), + "mDidUnprefixWebkitBoxInEarlierDecl should only be set if " + "we're supporting webkit-prefixed aliases, or if we're using " + "the css unprefixing service on this site"); + if (sWebkitPrefixedAliasesEnabled) { + return (keyword == eCSSKeyword__moz_box) ? + eCSSKeyword__webkit_box : eCSSKeyword__webkit_inline_box; + } + // (If we get here, we're using the Unprefixing Service, which means + // we're unprefixing all the way to modern flexbox display values.) + return (keyword == eCSSKeyword__moz_box) ? + eCSSKeyword_flex : eCSSKeyword_inline_flex; + } + } + + return keyword; +} + +bool +CSSParserImpl::ShouldUseUnprefixingService() const +{ + if (!sUnprefixingServiceEnabled) { + // Unprefixing is globally disabled. + return false; + } + if (sWebkitPrefixedAliasesEnabled) { + // Native webkit-prefix support is enabled, which trumps the unprefixing + // service for handling prefixed CSS. Don't try to use both at once. + return false; + } + +#ifdef NIGHTLY_BUILD + if (sUnprefixingServiceGloballyWhitelisted) { + // Unprefixing is globally whitelisted, + // so no need to check mSheetPrincipal. + return true; + } +#endif + // Unprefixing enabled; see if our principal is whitelisted for unprefixing. + return mSheetPrincipal && mSheetPrincipal->IsOnCSSUnprefixingWhitelist(); +} + +bool +CSSParserImpl::ParsePropertyWithUnprefixingService( + const nsAString& aPropertyName, + css::Declaration* aDeclaration, + uint32_t aFlags, + bool aMustCallValueAppended, + bool* aChanged, + nsCSSContextType aContext) +{ + MOZ_ASSERT(ShouldUseUnprefixingService(), + "Caller should've checked ShouldUseUnprefixingService()"); + + nsCOMPtr<nsICSSUnprefixingService> unprefixingSvc = + do_GetService(NS_CSSUNPREFIXINGSERVICE_CONTRACTID); + NS_ENSURE_TRUE(unprefixingSvc, false); + + // Save the state so we can jump back to this spot if our unprefixing fails + // (so we can behave as if we didn't even try to unprefix). + nsAutoCSSParserInputStateRestorer parserStateBeforeTryingToUnprefix(this); + + // Caller has already parsed the first half of the declaration -- + // aPropertyName and the ":". Now, we record the rest of the CSS declaration + // (the part after ':') into rightHalfOfDecl. (This is the property value, + // plus anything else up to the end of the declaration -- maybe "!important", + // maybe trailing junk characters, maybe a semicolon, maybe a trailing "}".) + bool checkForBraces = (aFlags & eParseDeclaration_InBraces) != 0; + nsAutoString rightHalfOfDecl; + mScanner->StartRecording(); + SkipDeclaration(checkForBraces); + mScanner->StopRecording(rightHalfOfDecl); + + // Try to unprefix: + bool success; + nsAutoString unprefixedDecl; + nsresult rv = + unprefixingSvc->GenerateUnprefixedDeclaration(aPropertyName, + rightHalfOfDecl, + unprefixedDecl, &success); + if (NS_FAILED(rv) || !success) { + return false; + } + + // Attempt to parse the unprefixed declaration: + nsAutoScannerChanger scannerChanger(this, unprefixedDecl); + success = ParseDeclaration(aDeclaration, + aFlags | eParseDeclaration_FromUnprefixingSvc, + aMustCallValueAppended, aChanged, aContext); + if (success) { + // We succeeded, so we'll leave the parser pointing at the end of + // the declaration; don't restore it to the pre-recording position. + parserStateBeforeTryingToUnprefix.DoNotRestore(); + } + + return success; +} + +bool +CSSParserImpl::ParseWebkitPrefixedGradientWithService( + nsAString& aPrefixedFuncName, + nsCSSValue& aValue) +{ + MOZ_ASSERT(ShouldUseUnprefixingService(), + "Should only call if we're allowed to use unprefixing service"); + + // Record the body of the "-webkit-*gradient" function into a string. + // Note: we're already just after the opening "(". + nsAutoString prefixedFuncBody; + mScanner->StartRecording(); + bool gotCloseParen = SkipUntil(')'); + mScanner->StopRecording(prefixedFuncBody); + if (gotCloseParen) { + // Strip off trailing close-paren, so that the value we pass to the + // unprefixing service is *just* the function-body (no parens). + prefixedFuncBody.Truncate(prefixedFuncBody.Length() - 1); + } + + // NOTE: Even if we fail, we'll be leaving the parser's cursor just after + // the close of the "-webkit-*gradient(...)" expression. This is the same + // behavior that the other Parse*Gradient functions have in their failure + // cases -- they call "SkipUntil(')') before returning false. So this is + // probably what we want. + nsCOMPtr<nsICSSUnprefixingService> unprefixingSvc = + do_GetService(NS_CSSUNPREFIXINGSERVICE_CONTRACTID); + NS_ENSURE_TRUE(unprefixingSvc, false); + + bool success; + nsAutoString unprefixedFuncName; + nsAutoString unprefixedFuncBody; + nsresult rv = + unprefixingSvc->GenerateUnprefixedGradientValue(aPrefixedFuncName, + prefixedFuncBody, + unprefixedFuncName, + unprefixedFuncBody, + &success); + + if (NS_FAILED(rv) || !success) { + return false; + } + + // JS service thinks it successfully converted the gradient! Now let's try + // to parse the resulting string. + + // First, add a close-paren if we originally recorded one (so that what we're + // about to put into the CSS parser is a faithful representation of what it + // would've seen if it were just parsing the original input stream): + if (gotCloseParen) { + unprefixedFuncBody.Append(char16_t(')')); + } + + nsAutoScannerChanger scannerChanger(this, unprefixedFuncBody); + if (unprefixedFuncName.EqualsLiteral("linear-gradient")) { + return ParseLinearGradient(aValue, 0); + } + if (unprefixedFuncName.EqualsLiteral("radial-gradient")) { + return ParseRadialGradient(aValue, 0); + } + + NS_ERROR("CSSUnprefixingService returned an unrecognized type of " + "gradient function"); + + return false; +} + +//---------------------------------------------------------------------- + +bool +CSSParserImpl::ParseDeclaration(css::Declaration* aDeclaration, + uint32_t aFlags, + bool aMustCallValueAppended, + bool* aChanged, + nsCSSContextType aContext) +{ + NS_PRECONDITION(aContext == eCSSContext_General || + aContext == eCSSContext_Page, + "Must be page or general context"); + + bool checkForBraces = (aFlags & eParseDeclaration_InBraces) != 0; + + mTempData.AssertInitialState(); + + // Get property name + nsCSSToken* tk = &mToken; + nsAutoString propertyName; + for (;;) { + if (!GetToken(true)) { + if (checkForBraces) { + REPORT_UNEXPECTED_EOF(PEDeclEndEOF); + } + return false; + } + if (eCSSToken_Ident == tk->mType) { + propertyName = tk->mIdent; + // grab the ident before the ExpectSymbol trashes the token + if (!ExpectSymbol(':', true)) { + REPORT_UNEXPECTED_TOKEN(PEParseDeclarationNoColon); + REPORT_UNEXPECTED(PEDeclDropped); + OUTPUT_ERROR(); + return false; + } + break; + } + if (tk->IsSymbol(';')) { + // dangling semicolons are skipped + continue; + } + + if (!tk->IsSymbol('}')) { + REPORT_UNEXPECTED_TOKEN(PEParseDeclarationDeclExpected); + REPORT_UNEXPECTED(PEDeclSkipped); + OUTPUT_ERROR(); + + if (eCSSToken_AtKeyword == tk->mType) { + SkipAtRule(checkForBraces); + return true; // Not a declaration, but don't skip until ';' + } + } + // Not a declaration... + UngetToken(); + return false; + } + + // Don't report property parse errors if we're inside a failing @supports + // rule. + nsAutoSuppressErrors suppressErrors(this, mInFailingSupportsRule); + + // Information about a parsed non-custom property. + nsCSSPropertyID propID; + + // Information about a parsed custom property. + CSSVariableDeclarations::Type variableType; + nsString variableValue; + + // Check if the property name is a custom property. + bool customProperty = nsLayoutUtils::CSSVariablesEnabled() && + nsCSSProps::IsCustomPropertyName(propertyName) && + aContext == eCSSContext_General; + + if (customProperty) { + if (!ParseVariableDeclaration(&variableType, variableValue)) { + REPORT_UNEXPECTED_P(PEValueParsingError, propertyName); + REPORT_UNEXPECTED(PEDeclDropped); + OUTPUT_ERROR(); + return false; + } + } else { + // Map property name to its ID. + propID = LookupEnabledProperty(propertyName); + if (eCSSProperty_UNKNOWN == propID || + eCSSPropertyExtra_variable == propID || + (aContext == eCSSContext_Page && + !nsCSSProps::PropHasFlags(propID, + CSS_PROPERTY_APPLIES_TO_PAGE_RULE))) { // unknown property + if (NonMozillaVendorIdentifier(propertyName)) { + if (!mInSupportsCondition && + aContext == eCSSContext_General && + !(aFlags & eParseDeclaration_FromUnprefixingSvc) && // no recursion + ShouldUseUnprefixingService()) { + if (ParsePropertyWithUnprefixingService(propertyName, + aDeclaration, aFlags, + aMustCallValueAppended, + aChanged, aContext)) { + return true; + } + } + } else { + REPORT_UNEXPECTED_P(PEUnknownProperty, propertyName); + REPORT_UNEXPECTED(PEDeclDropped); + OUTPUT_ERROR(); + } + return false; + } + // Then parse the property. + if (!ParseProperty(propID)) { + // XXX Much better to put stuff in the value parsers instead... + REPORT_UNEXPECTED_P(PEValueParsingError, propertyName); + REPORT_UNEXPECTED(PEDeclDropped); + OUTPUT_ERROR(); + mTempData.ClearProperty(propID); + mTempData.AssertInitialState(); + return false; + } + } + + CLEAR_ERROR(); + + // Look for "!important". + PriorityParsingStatus status; + if ((aFlags & eParseDeclaration_AllowImportant) != 0) { + status = ParsePriority(); + } else { + status = ePriority_None; + } + + // Look for a semicolon or close brace. + if (status != ePriority_Error) { + if (!GetToken(true)) { + // EOF is always ok + } else if (mToken.IsSymbol(';')) { + // semicolon is always ok + } else if (mToken.IsSymbol('}')) { + // brace is ok if checkForBraces, but don't eat it + UngetToken(); + if (!checkForBraces) { + status = ePriority_Error; + } + } else { + UngetToken(); + status = ePriority_Error; + } + } + + if (status == ePriority_Error) { + if (checkForBraces) { + REPORT_UNEXPECTED_TOKEN(PEBadDeclOrRuleEnd2); + } else { + REPORT_UNEXPECTED_TOKEN(PEBadDeclEnd); + } + REPORT_UNEXPECTED(PEDeclDropped); + OUTPUT_ERROR(); + if (!customProperty) { + mTempData.ClearProperty(propID); + } + mTempData.AssertInitialState(); + return false; + } + + if (customProperty) { + MOZ_ASSERT(Substring(propertyName, 0, + CSS_CUSTOM_NAME_PREFIX_LENGTH).EqualsLiteral("--")); + // remove '--' + nsDependentString varName(propertyName, CSS_CUSTOM_NAME_PREFIX_LENGTH); + aDeclaration->AddVariable(varName, variableType, variableValue, + status == ePriority_Important, false); + } else { + *aChanged |= mData.TransferFromBlock(mTempData, propID, EnabledState(), + status == ePriority_Important, + false, aMustCallValueAppended, + aDeclaration, GetDocument()); + } + + return true; +} + +static const nsCSSPropertyID kBorderTopIDs[] = { + eCSSProperty_border_top_width, + eCSSProperty_border_top_style, + eCSSProperty_border_top_color +}; +static const nsCSSPropertyID kBorderRightIDs[] = { + eCSSProperty_border_right_width, + eCSSProperty_border_right_style, + eCSSProperty_border_right_color +}; +static const nsCSSPropertyID kBorderBottomIDs[] = { + eCSSProperty_border_bottom_width, + eCSSProperty_border_bottom_style, + eCSSProperty_border_bottom_color +}; +static const nsCSSPropertyID kBorderLeftIDs[] = { + eCSSProperty_border_left_width, + eCSSProperty_border_left_style, + eCSSProperty_border_left_color +}; +static const nsCSSPropertyID kBorderInlineStartIDs[] = { + eCSSProperty_border_inline_start_width, + eCSSProperty_border_inline_start_style, + eCSSProperty_border_inline_start_color +}; +static const nsCSSPropertyID kBorderInlineEndIDs[] = { + eCSSProperty_border_inline_end_width, + eCSSProperty_border_inline_end_style, + eCSSProperty_border_inline_end_color +}; +static const nsCSSPropertyID kBorderBlockStartIDs[] = { + eCSSProperty_border_block_start_width, + eCSSProperty_border_block_start_style, + eCSSProperty_border_block_start_color +}; +static const nsCSSPropertyID kBorderBlockEndIDs[] = { + eCSSProperty_border_block_end_width, + eCSSProperty_border_block_end_style, + eCSSProperty_border_block_end_color +}; +static const nsCSSPropertyID kColumnRuleIDs[] = { + eCSSProperty_column_rule_width, + eCSSProperty_column_rule_style, + eCSSProperty_column_rule_color +}; + +bool +CSSParserImpl::ParseEnum(nsCSSValue& aValue, + const KTableEntry aKeywordTable[]) +{ + nsSubstring* ident = NextIdent(); + if (nullptr == ident) { + return false; + } + nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(*ident); + if (eCSSKeyword_UNKNOWN < keyword) { + int32_t value; + if (nsCSSProps::FindKeyword(keyword, aKeywordTable, value)) { + aValue.SetIntValue(value, eCSSUnit_Enumerated); + return true; + } + } + + // Put the unknown identifier back and return + UngetToken(); + return false; +} + +bool +CSSParserImpl::ParseAlignEnum(nsCSSValue& aValue, + const KTableEntry aKeywordTable[]) +{ + MOZ_ASSERT(nsCSSProps::ValueToKeywordEnum(NS_STYLE_ALIGN_BASELINE, + aKeywordTable) != + eCSSKeyword_UNKNOWN, + "Please use ParseEnum instead"); + nsSubstring* ident = NextIdent(); + if (!ident) { + return false; + } + nsCSSKeyword baselinePrefix = eCSSKeyword_first; + nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(*ident); + if (keyword == eCSSKeyword_first || keyword == eCSSKeyword_last) { + baselinePrefix = keyword; + ident = NextIdent(); + if (!ident) { + return false; + } + keyword = nsCSSKeywords::LookupKeyword(*ident); + } + if (eCSSKeyword_UNKNOWN < keyword) { + int32_t value; + if (nsCSSProps::FindKeyword(keyword, aKeywordTable, value)) { + if (baselinePrefix == eCSSKeyword_last && + keyword == eCSSKeyword_baseline) { + value = NS_STYLE_ALIGN_LAST_BASELINE; + } + aValue.SetIntValue(value, eCSSUnit_Enumerated); + return true; + } + } + + // Put the unknown identifier back and return + UngetToken(); + return false; +} + +struct UnitInfo { + char name[6]; // needs to be long enough for the longest unit, with + // terminating null. + uint32_t length; + nsCSSUnit unit; + int32_t type; +}; + +#define STR_WITH_LEN(_str) \ + _str, sizeof(_str) - 1 + +const UnitInfo UnitData[] = { + { STR_WITH_LEN("px"), eCSSUnit_Pixel, VARIANT_LENGTH }, + { STR_WITH_LEN("em"), eCSSUnit_EM, VARIANT_LENGTH }, + { STR_WITH_LEN("ex"), eCSSUnit_XHeight, VARIANT_LENGTH }, + { STR_WITH_LEN("pt"), eCSSUnit_Point, VARIANT_LENGTH }, + { STR_WITH_LEN("in"), eCSSUnit_Inch, VARIANT_LENGTH }, + { STR_WITH_LEN("cm"), eCSSUnit_Centimeter, VARIANT_LENGTH }, + { STR_WITH_LEN("ch"), eCSSUnit_Char, VARIANT_LENGTH }, + { STR_WITH_LEN("rem"), eCSSUnit_RootEM, VARIANT_LENGTH }, + { STR_WITH_LEN("mm"), eCSSUnit_Millimeter, VARIANT_LENGTH }, + { STR_WITH_LEN("mozmm"), eCSSUnit_PhysicalMillimeter, VARIANT_LENGTH }, + { STR_WITH_LEN("vw"), eCSSUnit_ViewportWidth, VARIANT_LENGTH }, + { STR_WITH_LEN("vh"), eCSSUnit_ViewportHeight, VARIANT_LENGTH }, + { STR_WITH_LEN("vmin"), eCSSUnit_ViewportMin, VARIANT_LENGTH }, + { STR_WITH_LEN("vmax"), eCSSUnit_ViewportMax, VARIANT_LENGTH }, + { STR_WITH_LEN("pc"), eCSSUnit_Pica, VARIANT_LENGTH }, + { STR_WITH_LEN("q"), eCSSUnit_Quarter, VARIANT_LENGTH }, + { STR_WITH_LEN("deg"), eCSSUnit_Degree, VARIANT_ANGLE }, + { STR_WITH_LEN("grad"), eCSSUnit_Grad, VARIANT_ANGLE }, + { STR_WITH_LEN("rad"), eCSSUnit_Radian, VARIANT_ANGLE }, + { STR_WITH_LEN("turn"), eCSSUnit_Turn, VARIANT_ANGLE }, + { STR_WITH_LEN("hz"), eCSSUnit_Hertz, VARIANT_FREQUENCY }, + { STR_WITH_LEN("khz"), eCSSUnit_Kilohertz, VARIANT_FREQUENCY }, + { STR_WITH_LEN("s"), eCSSUnit_Seconds, VARIANT_TIME }, + { STR_WITH_LEN("ms"), eCSSUnit_Milliseconds, VARIANT_TIME } +}; + +#undef STR_WITH_LEN + +bool +CSSParserImpl::TranslateDimension(nsCSSValue& aValue, + uint32_t aVariantMask, + float aNumber, + const nsString& aUnit) +{ + nsCSSUnit units; + int32_t type = 0; + if (!aUnit.IsEmpty()) { + uint32_t i; + for (i = 0; i < ArrayLength(UnitData); ++i) { + if (aUnit.LowerCaseEqualsASCII(UnitData[i].name, + UnitData[i].length)) { + units = UnitData[i].unit; + type = UnitData[i].type; + break; + } + } + + if (i == ArrayLength(UnitData)) { + // Unknown unit + return false; + } + + if (!mViewportUnitsEnabled && + (eCSSUnit_ViewportWidth == units || + eCSSUnit_ViewportHeight == units || + eCSSUnit_ViewportMin == units || + eCSSUnit_ViewportMax == units)) { + // Viewport units aren't allowed right now, probably because we're + // inside an @page declaration. Fail. + return false; + } + + if ((VARIANT_ABSOLUTE_DIMENSION & aVariantMask) != 0 && + !nsCSSValue::IsPixelLengthUnit(units)) { + return false; + } + } else { + // Must be a zero number... + NS_ASSERTION(0 == aNumber, "numbers without units must be 0"); + if ((VARIANT_LENGTH & aVariantMask) != 0) { + units = eCSSUnit_Pixel; + type = VARIANT_LENGTH; + } + else if ((VARIANT_ANGLE & aVariantMask) != 0) { + NS_ASSERTION(aVariantMask & VARIANT_ZERO_ANGLE, + "must have allowed zero angle"); + units = eCSSUnit_Degree; + type = VARIANT_ANGLE; + } + else { + NS_ERROR("Variant mask does not include dimension; why were we called?"); + return false; + } + } + if ((type & aVariantMask) != 0) { + aValue.SetFloatValue(aNumber, units); + return true; + } + return false; +} + +// Note that this does include VARIANT_CALC, which is numeric. This is +// because calc() parsing, as proposed, drops range restrictions inside +// the calc() expression and clamps the result of the calculation to the +// range. +#define VARIANT_ALL_NONNUMERIC \ + VARIANT_KEYWORD | \ + VARIANT_COLOR | \ + VARIANT_URL | \ + VARIANT_STRING | \ + VARIANT_COUNTER | \ + VARIANT_ATTR | \ + VARIANT_IDENTIFIER | \ + VARIANT_IDENTIFIER_NO_INHERIT | \ + VARIANT_AUTO | \ + VARIANT_INHERIT | \ + VARIANT_NONE | \ + VARIANT_NORMAL | \ + VARIANT_SYSFONT | \ + VARIANT_GRADIENT | \ + VARIANT_TIMING_FUNCTION | \ + VARIANT_ALL | \ + VARIANT_CALC | \ + VARIANT_OPENTYPE_SVG_KEYWORD + +CSSParseResult +CSSParserImpl::ParseVariantWithRestrictions(nsCSSValue& aValue, + int32_t aVariantMask, + const KTableEntry aKeywordTable[], + uint32_t aRestrictions) +{ + switch (aRestrictions) { + default: + MOZ_FALLTHROUGH_ASSERT("should not be reached"); + case 0: + return ParseVariant(aValue, aVariantMask, aKeywordTable); + case CSS_PROPERTY_VALUE_NONNEGATIVE: + return ParseNonNegativeVariant(aValue, aVariantMask, aKeywordTable); + case CSS_PROPERTY_VALUE_AT_LEAST_ONE: + return ParseOneOrLargerVariant(aValue, aVariantMask, aKeywordTable); + } +} + +// Note that callers passing VARIANT_CALC in aVariantMask will get +// full-range parsing inside the calc() expression, and the code that +// computes the calc will be required to clamp the resulting value to an +// appropriate range. +CSSParseResult +CSSParserImpl::ParseNonNegativeVariant(nsCSSValue& aValue, + int32_t aVariantMask, + const KTableEntry aKeywordTable[]) +{ + // The variant mask must only contain non-numeric variants or the ones + // that we specifically handle. + MOZ_ASSERT((aVariantMask & ~(VARIANT_ALL_NONNUMERIC | + VARIANT_NUMBER | + VARIANT_LENGTH | + VARIANT_PERCENT | + VARIANT_INTEGER)) == 0, + "need to update code below to handle additional variants"); + + CSSParseResult result = ParseVariant(aValue, aVariantMask, aKeywordTable); + if (result == CSSParseResult::Ok) { + if (eCSSUnit_Number == aValue.GetUnit() || + aValue.IsLengthUnit()){ + if (aValue.GetFloatValue() < 0) { + UngetToken(); + return CSSParseResult::NotFound; + } + } + else if (aValue.GetUnit() == eCSSUnit_Percent) { + if (aValue.GetPercentValue() < 0) { + UngetToken(); + return CSSParseResult::NotFound; + } + } else if (aValue.GetUnit() == eCSSUnit_Integer) { + if (aValue.GetIntValue() < 0) { + UngetToken(); + return CSSParseResult::NotFound; + } + } + } + return result; +} + +// Note that callers passing VARIANT_CALC in aVariantMask will get +// full-range parsing inside the calc() expression, and the code that +// computes the calc will be required to clamp the resulting value to an +// appropriate range. +CSSParseResult +CSSParserImpl::ParseOneOrLargerVariant(nsCSSValue& aValue, + int32_t aVariantMask, + const KTableEntry aKeywordTable[]) +{ + // The variant mask must only contain non-numeric variants or the ones + // that we specifically handle. + MOZ_ASSERT((aVariantMask & ~(VARIANT_ALL_NONNUMERIC | + VARIANT_NUMBER | + VARIANT_INTEGER)) == 0, + "need to update code below to handle additional variants"); + + CSSParseResult result = ParseVariant(aValue, aVariantMask, aKeywordTable); + if (result == CSSParseResult::Ok) { + if (aValue.GetUnit() == eCSSUnit_Integer) { + if (aValue.GetIntValue() < 1) { + UngetToken(); + return CSSParseResult::NotFound; + } + } else if (eCSSUnit_Number == aValue.GetUnit()) { + if (aValue.GetFloatValue() < 1.0f) { + UngetToken(); + return CSSParseResult::NotFound; + } + } + } + return result; +} + +static bool +IsCSSTokenCalcFunction(const nsCSSToken& aToken) +{ + return aToken.mType == eCSSToken_Function && + (aToken.mIdent.LowerCaseEqualsLiteral("calc") || + aToken.mIdent.LowerCaseEqualsLiteral("-moz-calc")); +} + +// Assigns to aValue iff it returns CSSParseResult::Ok. +CSSParseResult +CSSParserImpl::ParseVariant(nsCSSValue& aValue, + uint32_t aVariantMask, + const KTableEntry aKeywordTable[]) +{ + NS_ASSERTION(!(mHashlessColorQuirk && (aVariantMask & VARIANT_COLOR)) || + !(aVariantMask & VARIANT_NUMBER), + "can't distinguish colors from numbers"); + NS_ASSERTION(!(mHashlessColorQuirk && (aVariantMask & VARIANT_COLOR)) || + !(mUnitlessLengthQuirk && (aVariantMask & VARIANT_LENGTH)), + "can't distinguish colors from lengths"); + NS_ASSERTION(!(mUnitlessLengthQuirk && (aVariantMask & VARIANT_LENGTH)) || + !(aVariantMask & VARIANT_NUMBER), + "can't distinguish lengths from numbers"); + MOZ_ASSERT(!(aVariantMask & VARIANT_IDENTIFIER) || + !(aVariantMask & VARIANT_IDENTIFIER_NO_INHERIT), + "must not set both VARIANT_IDENTIFIER and " + "VARIANT_IDENTIFIER_NO_INHERIT"); + + uint32_t lineBefore, colBefore; + if (!GetNextTokenLocation(true, &lineBefore, &colBefore) || + !GetToken(true)) { + // Must be at EOF. + return CSSParseResult::NotFound; + } + + nsCSSToken* tk = &mToken; + if (((aVariantMask & (VARIANT_AHK | VARIANT_NORMAL | VARIANT_NONE | VARIANT_ALL)) != 0) && + (eCSSToken_Ident == tk->mType)) { + nsCSSKeyword keyword = LookupKeywordPrefixAware(tk->mIdent, + aKeywordTable); + + if (eCSSKeyword_UNKNOWN < keyword) { // known keyword + if ((aVariantMask & VARIANT_AUTO) != 0) { + if (eCSSKeyword_auto == keyword) { + aValue.SetAutoValue(); + return CSSParseResult::Ok; + } + } + if ((aVariantMask & VARIANT_INHERIT) != 0) { + // XXX Should we check IsParsingCompoundProperty, or do all + // callers handle it? (Not all callers set it, though, since + // they want the quirks that are disabled by setting it.) + + // IMPORTANT: If new keywords are added here, + // they probably need to be added in ParseCustomIdent as well. + if (eCSSKeyword_inherit == keyword) { + aValue.SetInheritValue(); + return CSSParseResult::Ok; + } + else if (eCSSKeyword_initial == keyword) { + aValue.SetInitialValue(); + return CSSParseResult::Ok; + } + else if (eCSSKeyword_unset == keyword && + nsLayoutUtils::UnsetValueEnabled()) { + aValue.SetUnsetValue(); + return CSSParseResult::Ok; + } + } + if ((aVariantMask & VARIANT_NONE) != 0) { + if (eCSSKeyword_none == keyword) { + aValue.SetNoneValue(); + return CSSParseResult::Ok; + } + } + if ((aVariantMask & VARIANT_ALL) != 0) { + if (eCSSKeyword_all == keyword) { + aValue.SetAllValue(); + return CSSParseResult::Ok; + } + } + if ((aVariantMask & VARIANT_NORMAL) != 0) { + if (eCSSKeyword_normal == keyword) { + aValue.SetNormalValue(); + return CSSParseResult::Ok; + } + } + if ((aVariantMask & VARIANT_SYSFONT) != 0) { + if (eCSSKeyword__moz_use_system_font == keyword && + !IsParsingCompoundProperty()) { + aValue.SetSystemFontValue(); + return CSSParseResult::Ok; + } + } + if ((aVariantMask & VARIANT_OPENTYPE_SVG_KEYWORD) != 0) { + if (sOpentypeSVGEnabled) { + aVariantMask |= VARIANT_KEYWORD; + } + } + if ((aVariantMask & VARIANT_KEYWORD) != 0) { + int32_t value; + if (nsCSSProps::FindKeyword(keyword, aKeywordTable, value)) { + aValue.SetIntValue(value, eCSSUnit_Enumerated); + return CSSParseResult::Ok; + } + } + } + } + // Check VARIANT_NUMBER and VARIANT_INTEGER before VARIANT_LENGTH or + // VARIANT_ZERO_ANGLE. + if (((aVariantMask & VARIANT_NUMBER) != 0) && + (eCSSToken_Number == tk->mType)) { + aValue.SetFloatValue(tk->mNumber, eCSSUnit_Number); + return CSSParseResult::Ok; + } + if (((aVariantMask & VARIANT_INTEGER) != 0) && + (eCSSToken_Number == tk->mType) && tk->mIntegerValid) { + aValue.SetIntValue(tk->mInteger, eCSSUnit_Integer); + return CSSParseResult::Ok; + } + if (((aVariantMask & (VARIANT_LENGTH | VARIANT_ANGLE | + VARIANT_FREQUENCY | VARIANT_TIME)) != 0 && + eCSSToken_Dimension == tk->mType) || + ((aVariantMask & (VARIANT_LENGTH | VARIANT_ZERO_ANGLE)) != 0 && + eCSSToken_Number == tk->mType && + tk->mNumber == 0.0f)) { + if ((aVariantMask & VARIANT_NONNEGATIVE_DIMENSION) != 0 && + tk->mNumber < 0.0) { + UngetToken(); + AssertNextTokenAt(lineBefore, colBefore); + return CSSParseResult::NotFound; + } + if (TranslateDimension(aValue, aVariantMask, tk->mNumber, tk->mIdent)) { + return CSSParseResult::Ok; + } + // Put the token back; we didn't parse it, so we shouldn't consume it + UngetToken(); + AssertNextTokenAt(lineBefore, colBefore); + return CSSParseResult::NotFound; + } + if (((aVariantMask & VARIANT_PERCENT) != 0) && + (eCSSToken_Percentage == tk->mType)) { + aValue.SetPercentValue(tk->mNumber); + return CSSParseResult::Ok; + } + if (mUnitlessLengthQuirk) { // NONSTANDARD: Nav interprets unitless numbers as px + if (((aVariantMask & VARIANT_LENGTH) != 0) && + (eCSSToken_Number == tk->mType)) { + aValue.SetFloatValue(tk->mNumber, eCSSUnit_Pixel); + return CSSParseResult::Ok; + } + } + + if (IsSVGMode() && !IsParsingCompoundProperty()) { + // STANDARD: SVG Spec states that lengths and coordinates can be unitless + // in which case they default to user-units (1 px = 1 user unit) + if (((aVariantMask & VARIANT_LENGTH) != 0) && + (eCSSToken_Number == tk->mType)) { + aValue.SetFloatValue(tk->mNumber, eCSSUnit_Pixel); + return CSSParseResult::Ok; + } + } + + if (((aVariantMask & VARIANT_URL) != 0) && + eCSSToken_URL == tk->mType) { + SetValueToURL(aValue, tk->mIdent); + return CSSParseResult::Ok; + } + if ((aVariantMask & VARIANT_GRADIENT) != 0 && + eCSSToken_Function == tk->mType) { + // a generated gradient + nsDependentString tmp(tk->mIdent, 0); + uint8_t gradientFlags = 0; + if (sMozGradientsEnabled && + StringBeginsWith(tmp, NS_LITERAL_STRING("-moz-"))) { + tmp.Rebind(tmp, 5); + gradientFlags |= eGradient_MozLegacy; + } else if (sWebkitPrefixedAliasesEnabled && + StringBeginsWith(tmp, NS_LITERAL_STRING("-webkit-"))) { + tmp.Rebind(tmp, 8); + gradientFlags |= eGradient_WebkitLegacy; + } + if (StringBeginsWith(tmp, NS_LITERAL_STRING("repeating-"))) { + tmp.Rebind(tmp, 10); + gradientFlags |= eGradient_Repeating; + } + + if (tmp.LowerCaseEqualsLiteral("linear-gradient")) { + if (!ParseLinearGradient(aValue, gradientFlags)) { + return CSSParseResult::Error; + } + return CSSParseResult::Ok; + } + if (tmp.LowerCaseEqualsLiteral("radial-gradient")) { + if (!ParseRadialGradient(aValue, gradientFlags)) { + return CSSParseResult::Error; + } + return CSSParseResult::Ok; + } + if ((gradientFlags == eGradient_WebkitLegacy) && + tmp.LowerCaseEqualsLiteral("gradient")) { + // Note: we check gradientFlags using '==' to select *exactly* + // eGradient_WebkitLegacy -- and exclude eGradient_Repeating -- because + // we don't want to accept -webkit-repeating-gradient() expressions. + // (This is not a recognized syntax.) + if (!ParseWebkitGradient(aValue)) { + return CSSParseResult::Error; + } + return CSSParseResult::Ok; + } + + if (ShouldUseUnprefixingService() && + !gradientFlags && + StringBeginsWith(tmp, NS_LITERAL_STRING("-webkit-"))) { + // Copy 'tmp' into a string on the stack, since as soon as we + // start parsing, its backing store (in "tk") will be overwritten + nsAutoString prefixedFuncName(tmp); + if (!ParseWebkitPrefixedGradientWithService(prefixedFuncName, aValue)) { + return CSSParseResult::Error; + } + return CSSParseResult::Ok; + } + } + if ((aVariantMask & VARIANT_IMAGE_RECT) != 0 && + eCSSToken_Function == tk->mType && + tk->mIdent.LowerCaseEqualsLiteral("-moz-image-rect")) { + if (!ParseImageRect(aValue)) { + return CSSParseResult::Error; + } + return CSSParseResult::Ok; + } + if ((aVariantMask & VARIANT_ELEMENT) != 0 && + eCSSToken_Function == tk->mType && + tk->mIdent.LowerCaseEqualsLiteral("-moz-element")) { + if (!ParseElement(aValue)) { + return CSSParseResult::Error; + } + return CSSParseResult::Ok; + } + if ((aVariantMask & VARIANT_COLOR) != 0) { + if (mHashlessColorQuirk || // NONSTANDARD: Nav interprets 'xxyyzz' values even without '#' prefix + (eCSSToken_ID == tk->mType) || + (eCSSToken_Hash == tk->mType) || + (eCSSToken_Ident == tk->mType) || + ((eCSSToken_Function == tk->mType) && + (tk->mIdent.LowerCaseEqualsLiteral("rgb") || + tk->mIdent.LowerCaseEqualsLiteral("hsl") || + tk->mIdent.LowerCaseEqualsLiteral("rgba") || + tk->mIdent.LowerCaseEqualsLiteral("hsla")))) + { + // Put token back so that parse color can get it + UngetToken(); + return ParseColor(aValue); + } + } + if (((aVariantMask & VARIANT_STRING) != 0) && + (eCSSToken_String == tk->mType)) { + nsAutoString buffer; + buffer.Append(tk->mIdent); + aValue.SetStringValue(buffer, eCSSUnit_String); + return CSSParseResult::Ok; + } + if (((aVariantMask & + (VARIANT_IDENTIFIER | VARIANT_IDENTIFIER_NO_INHERIT)) != 0) && + (eCSSToken_Ident == tk->mType) && + ((aVariantMask & VARIANT_IDENTIFIER) != 0 || + !(tk->mIdent.LowerCaseEqualsLiteral("inherit") || + tk->mIdent.LowerCaseEqualsLiteral("initial") || + (tk->mIdent.LowerCaseEqualsLiteral("unset") && + nsLayoutUtils::UnsetValueEnabled())))) { + aValue.SetStringValue(tk->mIdent, eCSSUnit_Ident); + return CSSParseResult::Ok; + } + if (((aVariantMask & VARIANT_COUNTER) != 0) && + (eCSSToken_Function == tk->mType) && + (tk->mIdent.LowerCaseEqualsLiteral("counter") || + tk->mIdent.LowerCaseEqualsLiteral("counters"))) { + if (!ParseCounter(aValue)) { + return CSSParseResult::Error; + } + return CSSParseResult::Ok; + } + if (((aVariantMask & VARIANT_ATTR) != 0) && + (eCSSToken_Function == tk->mType) && + tk->mIdent.LowerCaseEqualsLiteral("attr")) { + if (!ParseAttr(aValue)) { + SkipUntil(')'); + return CSSParseResult::Error; + } + return CSSParseResult::Ok; + } + if (((aVariantMask & VARIANT_TIMING_FUNCTION) != 0) && + (eCSSToken_Function == tk->mType)) { + if (tk->mIdent.LowerCaseEqualsLiteral("cubic-bezier")) { + if (!ParseTransitionTimingFunctionValues(aValue)) { + SkipUntil(')'); + return CSSParseResult::Error; + } + return CSSParseResult::Ok; + } + if (tk->mIdent.LowerCaseEqualsLiteral("steps")) { + if (!ParseTransitionStepTimingFunctionValues(aValue)) { + SkipUntil(')'); + return CSSParseResult::Error; + } + return CSSParseResult::Ok; + } + } + if ((aVariantMask & VARIANT_CALC) && + IsCSSTokenCalcFunction(*tk)) { + // calc() currently allows only lengths and percents and number inside it. + // And note that in current implementation, number cannot be mixed with + // length and percent. + if (!ParseCalc(aValue, aVariantMask & VARIANT_LPN)) { + return CSSParseResult::Error; + } + return CSSParseResult::Ok; + } + + UngetToken(); + AssertNextTokenAt(lineBefore, colBefore); + return CSSParseResult::NotFound; +} + +bool +CSSParserImpl::ParseCustomIdent(nsCSSValue& aValue, + const nsAutoString& aIdentValue, + const nsCSSKeyword aExcludedKeywords[], + const nsCSSProps::KTableEntry aPropertyKTable[]) +{ + nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(aIdentValue); + if (keyword == eCSSKeyword_UNKNOWN) { + // Fast path for identifiers that are not known CSS keywords: + aValue.SetStringValue(mToken.mIdent, eCSSUnit_Ident); + return true; + } + if (keyword == eCSSKeyword_inherit || + keyword == eCSSKeyword_initial || + keyword == eCSSKeyword_unset || + keyword == eCSSKeyword_default || + (aPropertyKTable && + nsCSSProps::FindIndexOfKeyword(keyword, aPropertyKTable) >= 0)) { + return false; + } + if (aExcludedKeywords) { + for (uint32_t i = 0;; i++) { + nsCSSKeyword excludedKeyword = aExcludedKeywords[i]; + if (excludedKeyword == eCSSKeyword_UNKNOWN) { + break; + } + if (excludedKeyword == keyword) { + return false; + } + } + } + aValue.SetStringValue(mToken.mIdent, eCSSUnit_Ident); + return true; +} + +bool +CSSParserImpl::ParseCounter(nsCSSValue& aValue) +{ + nsCSSUnit unit = (mToken.mIdent.LowerCaseEqualsLiteral("counter") ? + eCSSUnit_Counter : eCSSUnit_Counters); + + // A non-iterative for loop to break out when an error occurs. + for (;;) { + if (!GetToken(true)) { + break; + } + if (eCSSToken_Ident != mToken.mType) { + UngetToken(); + break; + } + + RefPtr<nsCSSValue::Array> val = + nsCSSValue::Array::Create(unit == eCSSUnit_Counter ? 2 : 3); + + val->Item(0).SetStringValue(mToken.mIdent, eCSSUnit_Ident); + + if (eCSSUnit_Counters == unit) { + // must have a comma and then a separator string + if (!ExpectSymbol(',', true) || !GetToken(true)) { + break; + } + if (eCSSToken_String != mToken.mType) { + UngetToken(); + break; + } + val->Item(1).SetStringValue(mToken.mIdent, eCSSUnit_String); + } + + // get optional type + int32_t typeItem = eCSSUnit_Counters == unit ? 2 : 1; + nsCSSValue& type = val->Item(typeItem); + if (ExpectSymbol(',', true)) { + if (!ParseCounterStyleNameValue(type) && !ParseSymbols(type)) { + break; + } + } else { + type.SetStringValue(NS_LITERAL_STRING("decimal"), eCSSUnit_Ident); + } + + if (!ExpectSymbol(')', true)) { + break; + } + + aValue.SetArrayValue(val, unit); + return true; + } + + SkipUntil(')'); + return false; +} + +bool +CSSParserImpl::ParseAttr(nsCSSValue& aValue) +{ + if (!GetToken(true)) { + return false; + } + + nsAutoString attr; + if (eCSSToken_Ident == mToken.mType) { // attr name or namespace + nsAutoString holdIdent(mToken.mIdent); + if (ExpectSymbol('|', false)) { // namespace + int32_t nameSpaceID = GetNamespaceIdForPrefix(holdIdent); + if (nameSpaceID == kNameSpaceID_Unknown) { + return false; + } + attr.AppendInt(nameSpaceID, 10); + attr.Append(char16_t('|')); + if (! GetToken(false)) { + REPORT_UNEXPECTED_EOF(PEAttributeNameEOF); + return false; + } + if (eCSSToken_Ident == mToken.mType) { + attr.Append(mToken.mIdent); + } + else { + REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected); + UngetToken(); + return false; + } + } + else { // no namespace + attr = holdIdent; + } + } + else if (mToken.IsSymbol('*')) { // namespace wildcard + // Wildcard namespace makes no sense here and is not allowed + REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected); + UngetToken(); + return false; + } + else if (mToken.IsSymbol('|')) { // explicit NO namespace + if (! GetToken(false)) { + REPORT_UNEXPECTED_EOF(PEAttributeNameEOF); + return false; + } + if (eCSSToken_Ident == mToken.mType) { + attr.Append(mToken.mIdent); + } + else { + REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected); + UngetToken(); + return false; + } + } + else { + REPORT_UNEXPECTED_TOKEN(PEAttributeNameOrNamespaceExpected); + UngetToken(); + return false; + } + if (!ExpectSymbol(')', true)) { + return false; + } + aValue.SetStringValue(attr, eCSSUnit_Attr); + return true; +} + +bool +CSSParserImpl::ParseSymbols(nsCSSValue& aValue) +{ + if (!GetToken(true)) { + return false; + } + if (mToken.mType != eCSSToken_Function && + !mToken.mIdent.LowerCaseEqualsLiteral("symbols")) { + UngetToken(); + return false; + } + + RefPtr<nsCSSValue::Array> params = nsCSSValue::Array::Create(2); + nsCSSValue& type = params->Item(0); + nsCSSValue& symbols = params->Item(1); + + if (!ParseEnum(type, nsCSSProps::kCounterSymbolsSystemKTable)) { + type.SetIntValue(NS_STYLE_COUNTER_SYSTEM_SYMBOLIC, eCSSUnit_Enumerated); + } + + bool first = true; + nsCSSValueList* item = symbols.SetListValue(); + for (;;) { + // FIXME Should also include VARIANT_IMAGE. See bug 1071436. + if (!ParseSingleTokenVariant(item->mValue, VARIANT_STRING, nullptr)) { + break; + } + if (ExpectSymbol(')', true)) { + if (first) { + switch (type.GetIntValue()) { + case NS_STYLE_COUNTER_SYSTEM_NUMERIC: + case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC: + // require at least two symbols + return false; + } + } + aValue.SetArrayValue(params, eCSSUnit_Symbols); + return true; + } + item->mNext = new nsCSSValueList; + item = item->mNext; + first = false; + } + + SkipUntil(')'); + return false; +} + +bool +CSSParserImpl::SetValueToURL(nsCSSValue& aValue, const nsString& aURL) +{ + if (!mSheetPrincipal) { + if (!mSheetPrincipalRequired) { + /* Pretend to succeed. */ + return true; + } + + NS_NOTREACHED("Codepaths that expect to parse URLs MUST pass in an " + "origin principal"); + return false; + } + + RefPtr<nsStringBuffer> buffer(nsCSSValue::BufferFromString(aURL)); + + // Note: urlVal retains its own reference to |buffer|. + mozilla::css::URLValue *urlVal = + new mozilla::css::URLValue(buffer, mBaseURI, mSheetURI, mSheetPrincipal); + aValue.SetURLValue(urlVal); + return true; +} + +/** + * Parse the image-orientation property, which has the grammar: + * <angle> flip? | flip | from-image + */ +bool +CSSParserImpl::ParseImageOrientation(nsCSSValue& aValue) +{ + if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT, nullptr)) { + // 'inherit', 'initial' and 'unset' must be alone + return true; + } + + // Check for an angle with optional 'flip'. + nsCSSValue angle; + if (ParseSingleTokenVariant(angle, VARIANT_ANGLE, nullptr)) { + nsCSSValue flip; + + if (ParseSingleTokenVariant(flip, VARIANT_KEYWORD, + nsCSSProps::kImageOrientationFlipKTable)) { + RefPtr<nsCSSValue::Array> array = nsCSSValue::Array::Create(2); + array->Item(0) = angle; + array->Item(1) = flip; + aValue.SetArrayValue(array, eCSSUnit_Array); + } else { + aValue = angle; + } + + return true; + } + + // The remaining possibilities (bare 'flip' and 'from-image') are both + // keywords, so we can handle them at the same time. + nsCSSValue keyword; + if (ParseSingleTokenVariant(keyword, VARIANT_KEYWORD, + nsCSSProps::kImageOrientationKTable)) { + aValue = keyword; + return true; + } + + // All possibilities failed. + return false; +} + +/** + * Parse the arguments of -moz-image-rect() function. + * -moz-image-rect(<uri>, <top>, <right>, <bottom>, <left>) + */ +bool +CSSParserImpl::ParseImageRect(nsCSSValue& aImage) +{ + // A non-iterative for loop to break out when an error occurs. + for (;;) { + nsCSSValue newFunction; + static const uint32_t kNumArgs = 5; + nsCSSValue::Array* func = + newFunction.InitFunction(eCSSKeyword__moz_image_rect, kNumArgs); + + // func->Item(0) is reserved for the function name. + nsCSSValue& url = func->Item(1); + nsCSSValue& top = func->Item(2); + nsCSSValue& right = func->Item(3); + nsCSSValue& bottom = func->Item(4); + nsCSSValue& left = func->Item(5); + + nsAutoString urlString; + if (!ParseURLOrString(urlString) || + !SetValueToURL(url, urlString) || + !ExpectSymbol(',', true)) { + break; + } + + static const int32_t VARIANT_SIDE = VARIANT_NUMBER | VARIANT_PERCENT; + if (!ParseSingleTokenNonNegativeVariant(top, VARIANT_SIDE, nullptr) || + !ExpectSymbol(',', true) || + !ParseSingleTokenNonNegativeVariant(right, VARIANT_SIDE, nullptr) || + !ExpectSymbol(',', true) || + !ParseSingleTokenNonNegativeVariant(bottom, VARIANT_SIDE, nullptr) || + !ExpectSymbol(',', true) || + !ParseSingleTokenNonNegativeVariant(left, VARIANT_SIDE, nullptr) || + !ExpectSymbol(')', true)) + break; + + aImage = newFunction; + return true; + } + + SkipUntil(')'); + return false; +} + +// <element>: -moz-element(# <element_id> ) +bool +CSSParserImpl::ParseElement(nsCSSValue& aValue) +{ + // A non-iterative for loop to break out when an error occurs. + for (;;) { + if (!GetToken(true)) + break; + + if (mToken.mType == eCSSToken_ID) { + aValue.SetStringValue(mToken.mIdent, eCSSUnit_Element); + } else { + UngetToken(); + break; + } + + if (!ExpectSymbol(')', true)) + break; + + return true; + } + + // If we detect a syntax error, we must match the opening parenthesis of the + // function with the closing parenthesis and skip all the tokens in between. + SkipUntil(')'); + return false; +} + +// flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ] +bool +CSSParserImpl::ParseFlex() +{ + // First check for inherit / initial / unset + nsCSSValue tmpVal; + if (ParseSingleTokenVariant(tmpVal, VARIANT_INHERIT, nullptr)) { + AppendValue(eCSSProperty_flex_grow, tmpVal); + AppendValue(eCSSProperty_flex_shrink, tmpVal); + AppendValue(eCSSProperty_flex_basis, tmpVal); + return true; + } + + // Next, check for 'none' == '0 0 auto' + if (ParseSingleTokenVariant(tmpVal, VARIANT_NONE, nullptr)) { + AppendValue(eCSSProperty_flex_grow, nsCSSValue(0.0f, eCSSUnit_Number)); + AppendValue(eCSSProperty_flex_shrink, nsCSSValue(0.0f, eCSSUnit_Number)); + AppendValue(eCSSProperty_flex_basis, nsCSSValue(eCSSUnit_Auto)); + return true; + } + + // OK, try parsing our value as individual per-subproperty components: + // [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ] + + // Each subproperty has a default value that it takes when it's omitted in a + // "flex" shorthand value. These default values are *only* for the shorthand + // syntax -- they're distinct from the subproperties' own initial values. We + // start with each subproperty at its default, as if we had "flex: 1 1 0%". + nsCSSValue flexGrow(1.0f, eCSSUnit_Number); + nsCSSValue flexShrink(1.0f, eCSSUnit_Number); + nsCSSValue flexBasis(0.0f, eCSSUnit_Percent); + + // OVERVIEW OF PARSING STRATEGY: + // ============================= + // a) Parse the first component as either flex-basis or flex-grow. + // b) If it wasn't flex-grow, parse the _next_ component as flex-grow. + // c) Now we've just parsed flex-grow -- so try parsing the next thing as + // flex-shrink. + // d) Finally: If we didn't get flex-basis at the beginning, try to parse + // it now, at the end. + // + // More details in each section below. + + uint32_t flexBasisVariantMask = + (nsCSSProps::ParserVariant(eCSSProperty_flex_basis) & ~(VARIANT_INHERIT)); + + // (a) Parse first component. It can be either be a 'flex-basis' value or a + // 'flex-grow' value, so we use the flex-basis-specific variant mask, along + // with VARIANT_NUMBER to accept 'flex-grow' values. + // + // NOTE: if we encounter unitless 0 here, we *must* interpret it as a + // 'flex-grow' value (a number), *not* as a 'flex-basis' value (a length). + // Conveniently, that's the behavior this combined variant-mask gives us -- + // it'll treat unitless 0 as a number. The flexbox spec requires this: + // "a unitless zero that is not already preceded by two flex factors must be + // interpreted as a flex factor. + if (ParseNonNegativeVariant(tmpVal, flexBasisVariantMask | VARIANT_NUMBER, + nsCSSProps::kWidthKTable) != CSSParseResult::Ok) { + // First component was not a valid flex-basis or flex-grow value. Fail. + return false; + } + + // Record what we just parsed as either flex-basis or flex-grow: + bool wasFirstComponentFlexBasis = (tmpVal.GetUnit() != eCSSUnit_Number); + (wasFirstComponentFlexBasis ? flexBasis : flexGrow) = tmpVal; + + // (b) If we didn't get flex-grow yet, parse _next_ component as flex-grow. + bool doneParsing = false; + if (wasFirstComponentFlexBasis) { + if (ParseNonNegativeNumber(tmpVal)) { + flexGrow = tmpVal; + } else { + // Failed to parse anything after our flex-basis -- that's fine. We can + // skip the remaining parsing. + doneParsing = true; + } + } + + if (!doneParsing) { + // (c) OK -- the last thing we parsed was flex-grow, so look for a + // flex-shrink in the next position. + if (ParseNonNegativeNumber(tmpVal)) { + flexShrink = tmpVal; + } + + // d) Finally: If we didn't get flex-basis at the beginning, try to parse + // it now, at the end. + // + // NOTE: If we encounter unitless 0 in this final position, we'll parse it + // as a 'flex-basis' value. That's OK, because we know it must have + // been "preceded by 2 flex factors" (justification below), which gets us + // out of the spec's requirement of otherwise having to treat unitless 0 + // as a flex factor. + // + // JUSTIFICATION: How do we know that a unitless 0 here must have been + // preceded by 2 flex factors? Well, suppose we had a unitless 0 that + // was preceded by only 1 flex factor. Then, we would have already + // accepted this unitless 0 as the 'flex-shrink' value, up above (since + // it's a valid flex-shrink value), and we'd have moved on to the next + // token (if any). And of course, if we instead had a unitless 0 preceded + // by *no* flex factors (if it were the first token), we would've already + // parsed it in our very first call to ParseNonNegativeVariant(). So, any + // unitless 0 encountered here *must* have been preceded by 2 flex factors. + if (!wasFirstComponentFlexBasis) { + CSSParseResult result = + ParseNonNegativeVariant(tmpVal, flexBasisVariantMask, + nsCSSProps::kWidthKTable); + if (result == CSSParseResult::Error) { + return false; + } else if (result == CSSParseResult::Ok) { + flexBasis = tmpVal; + } + } + } + + AppendValue(eCSSProperty_flex_grow, flexGrow); + AppendValue(eCSSProperty_flex_shrink, flexShrink); + AppendValue(eCSSProperty_flex_basis, flexBasis); + + return true; +} + +// flex-flow: <flex-direction> || <flex-wrap> +bool +CSSParserImpl::ParseFlexFlow() +{ + static const nsCSSPropertyID kFlexFlowSubprops[] = { + eCSSProperty_flex_direction, + eCSSProperty_flex_wrap + }; + const size_t numProps = MOZ_ARRAY_LENGTH(kFlexFlowSubprops); + nsCSSValue values[numProps]; + + int32_t found = ParseChoice(values, kFlexFlowSubprops, numProps); + + // Bail if we didn't successfully parse anything + if (found < 1) { + return false; + } + + // If either property didn't get an explicit value, use its initial value. + if ((found & 1) == 0) { + values[0].SetIntValue(NS_STYLE_FLEX_DIRECTION_ROW, eCSSUnit_Enumerated); + } + if ((found & 2) == 0) { + values[1].SetIntValue(NS_STYLE_FLEX_WRAP_NOWRAP, eCSSUnit_Enumerated); + } + + // Store these values and declare success! + for (size_t i = 0; i < numProps; i++) { + AppendValue(kFlexFlowSubprops[i], values[i]); + } + return true; +} + +bool +CSSParserImpl::ParseGridAutoFlow() +{ + nsCSSValue value; + if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { + AppendValue(eCSSProperty_grid_auto_flow, value); + return true; + } + + static const int32_t mask[] = { + NS_STYLE_GRID_AUTO_FLOW_ROW | NS_STYLE_GRID_AUTO_FLOW_COLUMN, + MASK_END_VALUE + }; + if (!ParseBitmaskValues(value, nsCSSProps::kGridAutoFlowKTable, mask)) { + return false; + } + int32_t bitField = value.GetIntValue(); + + // If neither row nor column is provided, row is assumed. + if (!(bitField & (NS_STYLE_GRID_AUTO_FLOW_ROW | + NS_STYLE_GRID_AUTO_FLOW_COLUMN))) { + value.SetIntValue(bitField | NS_STYLE_GRID_AUTO_FLOW_ROW, + eCSSUnit_Enumerated); + } + + AppendValue(eCSSProperty_grid_auto_flow, value); + return true; +} + +static const nsCSSKeyword kGridLineKeywords[] = { + eCSSKeyword_span, + eCSSKeyword_UNKNOWN // End-of-array marker +}; + +CSSParseResult +CSSParserImpl::ParseGridLineNames(nsCSSValue& aValue) +{ + if (!ExpectSymbol('[', true)) { + return CSSParseResult::NotFound; + } + if (!GetToken(true) || mToken.IsSymbol(']')) { + return CSSParseResult::Ok; + } + // 'return' so far leaves aValue untouched, to represent an empty list. + + nsCSSValueList* item; + if (aValue.GetUnit() == eCSSUnit_List) { + // Find the end of an existing list. + // The grid-template shorthand uses this, at most once for a given list. + + // NOTE: we could avoid this traversal by somehow keeping around + // a pointer to the last item from the previous call. + // It's not yet clear if this is worth the additional code complexity. + item = aValue.GetListValue(); + while (item->mNext) { + item = item->mNext; + } + item->mNext = new nsCSSValueList; + item = item->mNext; + } else { + MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Null, "Unexpected unit"); + item = aValue.SetListValue(); + } + for (;;) { + if (!(eCSSToken_Ident == mToken.mType && + ParseCustomIdent(item->mValue, mToken.mIdent, kGridLineKeywords))) { + UngetToken(); + SkipUntil(']'); + return CSSParseResult::Error; + } + if (!GetToken(true) || mToken.IsSymbol(']')) { + return CSSParseResult::Ok; + } + item->mNext = new nsCSSValueList; + item = item->mNext; + } +} + +// Assuming the 'repeat(' function token has already been consumed, +// parse the rest of repeat(<positive-integer> | auto-fill, <line-names>+) +// Append to the linked list whose end is given by |aTailPtr|, +// and update |aTailPtr| to point to the new end of the list. +bool +CSSParserImpl::ParseGridLineNameListRepeat(nsCSSValueList** aTailPtr) +{ + int32_t repetitions; + Maybe<int32_t> repeatAutoEnum; + if (!ParseGridTrackRepeatIntro(true, &repetitions, &repeatAutoEnum)) { + return false; + } + if (repeatAutoEnum.isSome()) { + // Parse exactly one <line-names>. + nsCSSValue listValue; + nsCSSValueList* list = listValue.SetListValue(); + if (ParseGridLineNames(list->mValue) != CSSParseResult::Ok) { + return false; + } + if (!ExpectSymbol(')', true)) { + return false; + } + // Instead of hooking up this list into the flat name list as usual, + // we create a pair(Int, List) where the first value is the auto-fill + // keyword and the second is the name list to repeat. + nsCSSValue kwd; + kwd.SetIntValue(repeatAutoEnum.value(), eCSSUnit_Enumerated); + *aTailPtr = (*aTailPtr)->mNext = new nsCSSValueList; + (*aTailPtr)->mValue.SetPairValue(kwd, listValue); + return true; + } + + // Parse at least one <line-names> + nsCSSValueList* tail = *aTailPtr; + do { + tail->mNext = new nsCSSValueList; + tail = tail->mNext; + if (ParseGridLineNames(tail->mValue) != CSSParseResult::Ok) { + return false; + } + } while (!ExpectSymbol(')', true)); + nsCSSValueList* firstRepeatedItem = (*aTailPtr)->mNext; + nsCSSValueList* lastRepeatedItem = tail; + + // Our repeated items are already in the target list once, + // so they need to be repeated |repetitions - 1| more times. + MOZ_ASSERT(repetitions > 0, "Expected positive repetitions"); + while (--repetitions) { + nsCSSValueList* repeatedItem = firstRepeatedItem; + for (;;) { + tail->mNext = new nsCSSValueList; + tail = tail->mNext; + tail->mValue = repeatedItem->mValue; + if (repeatedItem == lastRepeatedItem) { + break; + } + repeatedItem = repeatedItem->mNext; + } + } + *aTailPtr = tail; + return true; +} + +// Assuming a 'subgrid' keyword was already consumed, parse <line-name-list>? +bool +CSSParserImpl::ParseOptionalLineNameListAfterSubgrid(nsCSSValue& aValue) +{ + nsCSSValueList* item = aValue.SetListValue(); + // This marker distinguishes the value from a <track-list>. + item->mValue.SetIntValue(NS_STYLE_GRID_TEMPLATE_SUBGRID, + eCSSUnit_Enumerated); + bool haveRepeatAuto = false; + for (;;) { + // First try to parse <name-repeat>, i.e. + // repeat(<positive-integer> | auto-fill, <line-names>+) + if (!GetToken(true)) { + return true; + } + if (mToken.mType == eCSSToken_Function && + mToken.mIdent.LowerCaseEqualsLiteral("repeat")) { + nsCSSValueList* startOfRepeat = item; + if (!ParseGridLineNameListRepeat(&item)) { + SkipUntil(')'); + return false; + } + if (startOfRepeat->mNext->mValue.GetUnit() == eCSSUnit_Pair) { + if (haveRepeatAuto) { + REPORT_UNEXPECTED(PEMoreThanOneGridRepeatAutoFillInNameList); + return false; + } + haveRepeatAuto = true; + } + } else { + UngetToken(); + + // This was not a repeat() function. Try to parse <line-names>. + nsCSSValue lineNames; + CSSParseResult result = ParseGridLineNames(lineNames); + if (result == CSSParseResult::NotFound) { + return true; + } + if (result == CSSParseResult::Error) { + return false; + } + item->mNext = new nsCSSValueList; + item = item->mNext; + item->mValue = lineNames; + } + } +} + +CSSParseResult +CSSParserImpl::ParseGridTrackBreadth(nsCSSValue& aValue) +{ + CSSParseResult result = ParseNonNegativeVariant(aValue, + VARIANT_AUTO | VARIANT_LPCALC | VARIANT_KEYWORD, + nsCSSProps::kGridTrackBreadthKTable); + + if (result == CSSParseResult::Ok || + result == CSSParseResult::Error) { + return result; + } + + // Attempt to parse <flex> (a dimension with the "fr" unit). + if (!GetToken(true)) { + return CSSParseResult::NotFound; + } + if (!(eCSSToken_Dimension == mToken.mType && + mToken.mIdent.LowerCaseEqualsLiteral("fr") && + mToken.mNumber >= 0)) { + UngetToken(); + return CSSParseResult::NotFound; + } + aValue.SetFloatValue(mToken.mNumber, eCSSUnit_FlexFraction); + return CSSParseResult::Ok; +} + +// Parse a <track-size>, or <fixed-size> when aFlags has eFixedTrackSize. +CSSParseResult +CSSParserImpl::ParseGridTrackSize(nsCSSValue& aValue, + GridTrackSizeFlags aFlags) +{ + const bool requireFixedSize = + !!(aFlags & GridTrackSizeFlags::eFixedTrackSize); + // Attempt to parse a single <track-breadth>. + CSSParseResult result = ParseGridTrackBreadth(aValue); + if (requireFixedSize && result == CSSParseResult::Ok && + !aValue.IsLengthPercentCalcUnit()) { + result = CSSParseResult::Error; + } + if (result == CSSParseResult::Error) { + return result; + } + if (result == CSSParseResult::Ok) { + if (aValue.GetUnit() == eCSSUnit_FlexFraction) { + // Single value <flex> is represented internally as minmax(auto, <flex>). + nsCSSValue minmax; + nsCSSValue::Array* func = minmax.InitFunction(eCSSKeyword_minmax, 2); + func->Item(1).SetAutoValue(); + func->Item(2) = aValue; + aValue = minmax; + } + return result; + } + + // Attempt to parse a minmax() or fit-content() function. + if (!GetToken(true)) { + return CSSParseResult::NotFound; + } + if (eCSSToken_Function != mToken.mType) { + UngetToken(); + return CSSParseResult::NotFound; + } + if (mToken.mIdent.LowerCaseEqualsLiteral("fit-content")) { + nsCSSValue::Array* func = aValue.InitFunction(eCSSKeyword_fit_content, 1); + if (ParseGridTrackBreadth(func->Item(1)) == CSSParseResult::Ok && + func->Item(1).IsLengthPercentCalcUnit() && + ExpectSymbol(')', true)) { + return CSSParseResult::Ok; + } + SkipUntil(')'); + return CSSParseResult::Error; + } + if (!mToken.mIdent.LowerCaseEqualsLiteral("minmax")) { + UngetToken(); + return CSSParseResult::NotFound; + } + nsCSSValue::Array* func = aValue.InitFunction(eCSSKeyword_minmax, 2); + if (ParseGridTrackBreadth(func->Item(1)) == CSSParseResult::Ok && + ExpectSymbol(',', true) && + ParseGridTrackBreadth(func->Item(2)) == CSSParseResult::Ok && + ExpectSymbol(')', true)) { + if (requireFixedSize && + !func->Item(1).IsLengthPercentCalcUnit() && + !func->Item(2).IsLengthPercentCalcUnit()) { + return CSSParseResult::Error; + } + // Reject <flex> min-sizing. + if (func->Item(1).GetUnit() == eCSSUnit_FlexFraction) { + return CSSParseResult::Error; + } + return CSSParseResult::Ok; + } + SkipUntil(')'); + return CSSParseResult::Error; +} + +bool +CSSParserImpl::ParseGridAutoColumnsRows(nsCSSPropertyID aPropID) +{ + nsCSSValue value; + if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr) || + ParseGridTrackSize(value) == CSSParseResult::Ok) { + AppendValue(aPropID, value); + return true; + } + return false; +} + +bool +CSSParserImpl::ParseGridTrackListWithFirstLineNames(nsCSSValue& aValue, + const nsCSSValue& aFirstLineNames, + GridTrackListFlags aFlags) +{ + nsCSSValueList* firstLineNamesItem = aValue.SetListValue(); + firstLineNamesItem->mValue = aFirstLineNames; + + // This function is trying to parse <track-list>, which is + // [ <line-names>? [ <track-size> | <repeat()> ] ]+ <line-names>? + // and we're already past the first "<line-names>?". + // If aFlags contains eExplicitTrackList then <repeat()> is disallowed. + // + // Each iteration of the following loop attempts to parse either a + // repeat() or a <track-size> expression, and then an (optional) + // <line-names> expression. + // + // The only successful exit point from this loop is the ::NotFound + // case after ParseGridTrackSize(); i.e. we'll greedily parse + // repeat()/<track-size> until we can't find one. + nsCSSValueList* item = firstLineNamesItem; + bool haveRepeatAuto = false; + for (;;) { + // First try to parse repeat() + if (!GetToken(true)) { + break; + } + if (!(aFlags & GridTrackListFlags::eExplicitTrackList) && + mToken.mType == eCSSToken_Function && + mToken.mIdent.LowerCaseEqualsLiteral("repeat")) { + nsCSSValueList* startOfRepeat = item; + if (!ParseGridTrackListRepeat(&item)) { + SkipUntil(')'); + return false; + } + auto firstRepeat = startOfRepeat->mNext; + if (firstRepeat->mValue.GetUnit() == eCSSUnit_Pair) { + if (haveRepeatAuto) { + REPORT_UNEXPECTED(PEMoreThanOneGridRepeatAutoFillFitInTrackList); + return false; + } + haveRepeatAuto = true; + // We're parsing an <auto-track-list>, which requires that all tracks + // are <fixed-size>, so we need to check the ones we've parsed already. + for (nsCSSValueList* list = firstLineNamesItem->mNext; + list != firstRepeat; list = list->mNext) { + if (list->mValue.GetUnit() == eCSSUnit_Function) { + nsCSSValue::Array* func = list->mValue.GetArrayValue(); + auto funcName = func->Item(0).GetKeywordValue(); + if (funcName == eCSSKeyword_minmax) { + if (!func->Item(1).IsLengthPercentCalcUnit() && + !func->Item(2).IsLengthPercentCalcUnit()) { + return false; + } + } else { + MOZ_ASSERT(funcName == eCSSKeyword_fit_content, + "Expected minmax() or fit-content() function"); + return false; // fit-content() is not a <fixed-size> + } + } else if (!list->mValue.IsLengthPercentCalcUnit()) { + return false; + } + list = list->mNext; // skip line names + } + } + } else { + UngetToken(); + + // Not a repeat() function; try to parse <track-size> | <fixed-size>. + nsCSSValue trackSize; + GridTrackSizeFlags flags = haveRepeatAuto + ? GridTrackSizeFlags::eFixedTrackSize + : GridTrackSizeFlags::eDefaultTrackSize; + CSSParseResult result = ParseGridTrackSize(trackSize, flags); + if (result == CSSParseResult::Error) { + return false; + } + if (result == CSSParseResult::NotFound) { + // What we've parsed so far is a valid <track-list> + // (modulo the "at least one <track-size>" check below.) + // Stop here. + break; + } + item->mNext = new nsCSSValueList; + item = item->mNext; + item->mValue = trackSize; + + item->mNext = new nsCSSValueList; + item = item->mNext; + } + if (ParseGridLineNames(item->mValue) == CSSParseResult::Error) { + return false; + } + } + + // Require at least one <track-size>. + if (item == firstLineNamesItem) { + return false; + } + + MOZ_ASSERT(aValue.GetListValue() && + aValue.GetListValue()->mNext && + aValue.GetListValue()->mNext->mNext, + "<track-list> should have a minimum length of 3"); + return true; +} + +// Takes ownership of |aSecond| +static void +ConcatLineNames(nsCSSValue& aFirst, nsCSSValue& aSecond) +{ + if (aSecond.GetUnit() == eCSSUnit_Null) { + // Nothing to do. + return; + } + if (aFirst.GetUnit() == eCSSUnit_Null) { + // Empty or omitted <line-names>. Replace it. + aFirst = aSecond; + return; + } + + // Join the two <line-names> lists. + nsCSSValueList* source = aSecond.GetListValue(); + nsCSSValueList* target = aFirst.GetListValue(); + // Find the end: + while (target->mNext) { + target = target->mNext; + } + // Copy the first name. We can't take ownership of it + // as it'll be destroyed when |aSecond| goes out of scope. + target->mNext = new nsCSSValueList; + target = target->mNext; + target->mValue = source->mValue; + // Move the rest of the linked list. + target->mNext = source->mNext; + source->mNext = nullptr; +} + +// Assuming the 'repeat(' function token has already been consumed, +// parse "repeat( <positive-integer> | auto-fill | auto-fit ," +// (or "repeat( <positive-integer> | auto-fill ," when aForSubgrid is true) +// and stop after the comma. Return true when parsing succeeds, +// with aRepetitions set to the number of repetitions and aRepeatAutoEnum set +// to an enum value for auto-fill | auto-fit (it's not set at all when +// <positive-integer> was parsed). +bool +CSSParserImpl::ParseGridTrackRepeatIntro(bool aForSubgrid, + int32_t* aRepetitions, + Maybe<int32_t>* aRepeatAutoEnum) +{ + if (!GetToken(true)) { + return false; + } + if (mToken.mType == eCSSToken_Ident) { + if (mToken.mIdent.LowerCaseEqualsLiteral("auto-fill")) { + aRepeatAutoEnum->emplace(NS_STYLE_GRID_REPEAT_AUTO_FILL); + } else if (!aForSubgrid && + mToken.mIdent.LowerCaseEqualsLiteral("auto-fit")) { + aRepeatAutoEnum->emplace(NS_STYLE_GRID_REPEAT_AUTO_FIT); + } else { + return false; + } + *aRepetitions = 1; + } else if (mToken.mType == eCSSToken_Number) { + if (!(mToken.mIntegerValid && + mToken.mInteger > 0)) { + return false; + } + *aRepetitions = std::min(mToken.mInteger, GRID_TEMPLATE_MAX_REPETITIONS); + } else { + return false; + } + + if (!ExpectSymbol(',', true)) { + return false; + } + return true; +} + +// Assuming the 'repeat(' function token has already been consumed, +// parse the rest of +// repeat( <positive-integer> | auto-fill | auto-fit , +// [ <line-names>? <track-size> ]+ <line-names>? ) +// Append to the linked list whose end is given by |aTailPtr|, +// and update |aTailPtr| to point to the new end of the list. +// Note: only one <track-size> is allowed for auto-fill/fit +bool +CSSParserImpl::ParseGridTrackListRepeat(nsCSSValueList** aTailPtr) +{ + int32_t repetitions; + Maybe<int32_t> repeatAutoEnum; + if (!ParseGridTrackRepeatIntro(false, &repetitions, &repeatAutoEnum)) { + return false; + } + + // Parse [ <line-names>? <track-size> ]+ <line-names>? + // but keep the first and last <line-names> separate + // because they'll need to be joined. + // http://dev.w3.org/csswg/css-grid/#repeat-notation + nsCSSValue firstLineNames; + nsCSSValue trackSize; + nsCSSValue lastLineNames; + // Optional + if (ParseGridLineNames(firstLineNames) == CSSParseResult::Error) { + return false; + } + // Required + GridTrackSizeFlags flags = repeatAutoEnum.isSome() + ? GridTrackSizeFlags::eFixedTrackSize + : GridTrackSizeFlags::eDefaultTrackSize; + if (ParseGridTrackSize(trackSize, flags) != CSSParseResult::Ok) { + return false; + } + // Use nsAutoPtr to free the list in case of early return. + nsAutoPtr<nsCSSValueList> firstTrackSizeItemAuto(new nsCSSValueList); + firstTrackSizeItemAuto->mValue = trackSize; + + nsCSSValueList* item = firstTrackSizeItemAuto; + for (;;) { + // Optional + if (ParseGridLineNames(lastLineNames) == CSSParseResult::Error) { + return false; + } + + if (ExpectSymbol(')', true)) { + break; + } + + // <auto-repeat> only accepts a single track size: + // <line-names>? <fixed-size> <line-names>? + if (repeatAutoEnum.isSome()) { + REPORT_UNEXPECTED(PEMoreThanOneGridRepeatTrackSize); + return false; + } + + // Required + if (ParseGridTrackSize(trackSize) != CSSParseResult::Ok) { + return false; + } + + item->mNext = new nsCSSValueList; + item = item->mNext; + item->mValue = lastLineNames; + // Do not append to this list at the next iteration. + lastLineNames.Reset(); + + item->mNext = new nsCSSValueList; + item = item->mNext; + item->mValue = trackSize; + } + nsCSSValueList* lastTrackSizeItem = item; + + // [ <line-names>? <track-size> ]+ <line-names>? is now parsed into: + // * firstLineNames: the first <line-names> + // * a linked list of odd length >= 1, from firstTrackSizeItem + // (the first <track-size>) to lastTrackSizeItem (the last), + // with the <line-names> sublists in between + // * lastLineNames: the last <line-names> + + if (repeatAutoEnum.isSome()) { + // Instead of hooking up this list into the flat track/name list as usual, + // we create a pair(Int, List) where the first value is the auto-fill/fit + // keyword and the second is the list to repeat. There are three items + // in this list, the first is the list of line names before the track size, + // the second item is the track size, and the last item is the list of line + // names after the track size. Note that the line names are NOT merged + // with any line names before/after the repeat() itself. + nsCSSValue listValue; + nsCSSValueList* list = listValue.SetListValue(); + list->mValue = firstLineNames; + list = list->mNext = new nsCSSValueList; + list->mValue = trackSize; + list = list->mNext = new nsCSSValueList; + list->mValue = lastLineNames; + nsCSSValue kwd; + kwd.SetIntValue(repeatAutoEnum.value(), eCSSUnit_Enumerated); + *aTailPtr = (*aTailPtr)->mNext = new nsCSSValueList; + (*aTailPtr)->mValue.SetPairValue(kwd, listValue); + // Append an empty list since the caller expects that to represent the names + // that follows the repeat() function. + *aTailPtr = (*aTailPtr)->mNext = new nsCSSValueList; + return true; + } + + // Join the last and first <line-names> (in that order.) + // For example, repeat(3, (a) 100px (b) 200px (c)) results in + // (a) 100px (b) 200px (c a) 100px (b) 200px (c a) 100px (b) 200px (c) + // This is (c a). + // Make deep copies: the originals will be moved. + nsCSSValue joinerLineNames; + { + nsCSSValueList* target = nullptr; + if (lastLineNames.GetUnit() != eCSSUnit_Null) { + target = joinerLineNames.SetListValue(); + nsCSSValueList* source = lastLineNames.GetListValue(); + for (;;) { + target->mValue = source->mValue; + source = source->mNext; + if (!source) { + break; + } + target->mNext = new nsCSSValueList; + target = target->mNext; + } + } + + if (firstLineNames.GetUnit() != eCSSUnit_Null) { + if (target) { + target->mNext = new nsCSSValueList; + target = target->mNext; + } else { + target = joinerLineNames.SetListValue(); + } + nsCSSValueList* source = firstLineNames.GetListValue(); + for (;;) { + target->mValue = source->mValue; + source = source->mNext; + if (!source) { + break; + } + target->mNext = new nsCSSValueList; + target = target->mNext; + } + } + } + + // Join our first <line-names> with the one before repeat(). + // (a) repeat(1, (b) 20px) expands to (a b) 20px + nsCSSValueList* previousItemBeforeRepeat = *aTailPtr; + ConcatLineNames(previousItemBeforeRepeat->mValue, firstLineNames); + + // Move our linked list + // (first to last <track-size>, with the <line-names> sublists in between). + // This is the first repetition. + NS_ASSERTION(previousItemBeforeRepeat->mNext == nullptr, + "Expected the end of a linked list"); + previousItemBeforeRepeat->mNext = firstTrackSizeItemAuto.forget(); + nsCSSValueList* firstTrackSizeItem = previousItemBeforeRepeat->mNext; + nsCSSValueList* tail = lastTrackSizeItem; + + // Repeat |repetitions - 1| more times: + // * the joiner <line-names> + // * the linked list + // (first to last <track-size>, with the <line-names> sublists in between) + MOZ_ASSERT(repetitions > 0, "Expected positive repetitions"); + while (--repetitions) { + tail->mNext = new nsCSSValueList; + tail = tail->mNext; + tail->mValue = joinerLineNames; + + nsCSSValueList* repeatedItem = firstTrackSizeItem; + for (;;) { + tail->mNext = new nsCSSValueList; + tail = tail->mNext; + tail->mValue = repeatedItem->mValue; + if (repeatedItem == lastTrackSizeItem) { + break; + } + repeatedItem = repeatedItem->mNext; + } + } + + // Finally, move our last <line-names>. + // Any <line-names> immediately after repeat() will append to it. + tail->mNext = new nsCSSValueList; + tail = tail->mNext; + tail->mValue = lastLineNames; + + *aTailPtr = tail; + return true; +} + +bool +CSSParserImpl::ParseGridTrackList(nsCSSPropertyID aPropID, + GridTrackListFlags aFlags) +{ + nsCSSValue value; + nsCSSValue firstLineNames; + if (ParseGridLineNames(firstLineNames) == CSSParseResult::Error || + !ParseGridTrackListWithFirstLineNames(value, firstLineNames, aFlags)) { + return false; + } + AppendValue(aPropID, value); + return true; +} + +bool +CSSParserImpl::ParseGridTemplateColumnsRows(nsCSSPropertyID aPropID) +{ + nsCSSValue value; + if (ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE, nullptr)) { + AppendValue(aPropID, value); + return true; + } + + nsSubstring* ident = NextIdent(); + if (ident) { + if (ident->LowerCaseEqualsLiteral("subgrid")) { + if (!nsLayoutUtils::IsGridTemplateSubgridValueEnabled()) { + REPORT_UNEXPECTED(PESubgridNotSupported); + return false; + } + if (!ParseOptionalLineNameListAfterSubgrid(value)) { + return false; + } + AppendValue(aPropID, value); + return true; + } + UngetToken(); + } + + return ParseGridTrackList(aPropID); +} + +bool +CSSParserImpl::ParseGridTemplateAreasLine(const nsAutoString& aInput, + css::GridTemplateAreasValue* aAreas, + nsDataHashtable<nsStringHashKey, uint32_t>& aAreaIndices) +{ + aAreas->mTemplates.AppendElement(mToken.mIdent); + + nsCSSGridTemplateAreaScanner scanner(aInput); + nsCSSGridTemplateAreaToken token; + css::GridNamedArea* currentArea = nullptr; + uint32_t row = aAreas->NRows(); + // Column numbers starts at 1, but we might not have any, eg + // grid-template-areas:""; which will result in mNColumns == 0. + uint32_t column = 0; + while (scanner.Next(token)) { + ++column; + if (token.isTrash) { + return false; + } + if (currentArea) { + if (token.mName == currentArea->mName) { + if (currentArea->mRowStart == row) { + // Next column in the first row of this named area. + currentArea->mColumnEnd++; + } + continue; + } + // We're exiting |currentArea|, so currentArea is ending at |column|. + // Make sure that this is consistent with currentArea on previous rows: + if (currentArea->mColumnEnd != column) { + NS_ASSERTION(currentArea->mRowStart != row, + "Inconsistent column end for the first row of a named area."); + // Not a rectangle + return false; + } + currentArea = nullptr; + } + if (!token.mName.IsEmpty()) { + // Named cell that doesn't have a cell with the same name on its left. + + // Check if this is the continuation of an existing named area: + uint32_t index; + if (aAreaIndices.Get(token.mName, &index)) { + MOZ_ASSERT(index < aAreas->mNamedAreas.Length(), + "Invalid aAreaIndices hash table"); + currentArea = &aAreas->mNamedAreas[index]; + if (currentArea->mColumnStart != column || + currentArea->mRowEnd != row) { + // Existing named area, but not forming a rectangle + return false; + } + // Next row of an existing named area + currentArea->mRowEnd++; + } else { + // New named area + aAreaIndices.Put(token.mName, aAreas->mNamedAreas.Length()); + currentArea = aAreas->mNamedAreas.AppendElement(); + currentArea->mName = token.mName; + // For column or row N (starting at 1), + // the start line is N, the end line is N + 1 + currentArea->mColumnStart = column; + currentArea->mColumnEnd = column + 1; + currentArea->mRowStart = row; + currentArea->mRowEnd = row + 1; + } + } + } + if (currentArea && currentArea->mColumnEnd != column + 1) { + NS_ASSERTION(currentArea->mRowStart != row, + "Inconsistent column end for the first row of a named area."); + // Not a rectangle + return false; + } + + // On the first row, set the number of columns + // that grid-template-areas contributes to the explicit grid. + // On other rows, check that the number of columns is consistent + // between rows. + if (row == 1) { + aAreas->mNColumns = column; + } else if (aAreas->mNColumns != column) { + return false; + } + return true; +} + +bool +CSSParserImpl::ParseGridTemplateAreas() +{ + nsCSSValue value; + if (ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE, nullptr)) { + AppendValue(eCSSProperty_grid_template_areas, value); + return true; + } + + RefPtr<css::GridTemplateAreasValue> areas = + new css::GridTemplateAreasValue(); + nsDataHashtable<nsStringHashKey, uint32_t> areaIndices; + for (;;) { + if (!GetToken(true)) { + break; + } + if (eCSSToken_String != mToken.mType) { + UngetToken(); + break; + } + if (!ParseGridTemplateAreasLine(mToken.mIdent, areas, areaIndices)) { + return false; + } + } + + if (areas->NRows() == 0) { + return false; + } + + AppendValue(eCSSProperty_grid_template_areas, nsCSSValue(areas)); + return true; +} + +// [ auto-flow && dense? ] <'grid-auto-columns'>? | +// <'grid-template-columns'> +bool +CSSParserImpl::ParseGridTemplateColumnsOrAutoFlow(bool aForGridShorthand) +{ + if (aForGridShorthand) { + auto res = ParseGridShorthandAutoProps(NS_STYLE_GRID_AUTO_FLOW_COLUMN); + if (res == CSSParseResult::Error) { + return false; + } + if (res == CSSParseResult::Ok) { + nsCSSValue value(eCSSUnit_None); + AppendValue(eCSSProperty_grid_template_columns, value); + return true; + } + } + return ParseGridTemplateColumnsRows(eCSSProperty_grid_template_columns); +} + +bool +CSSParserImpl::ParseGridTemplate(bool aForGridShorthand) +{ + // none | + // subgrid | + // <'grid-template-rows'> / <'grid-template-columns'> | + // [ <line-names>? <string> <track-size>? <line-names>? ]+ [ / <explicit-track-list>]? + // or additionally when aForGridShorthand is true: + // <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>? + nsCSSValue value; + if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { + AppendValue(eCSSProperty_grid_template_areas, value); + AppendValue(eCSSProperty_grid_template_rows, value); + AppendValue(eCSSProperty_grid_template_columns, value); + return true; + } + + // 'none' can appear either by itself, + // or as the beginning of <'grid-template-rows'> / <'grid-template-columns'> + if (ParseSingleTokenVariant(value, VARIANT_NONE, nullptr)) { + AppendValue(eCSSProperty_grid_template_rows, value); + AppendValue(eCSSProperty_grid_template_areas, value); + if (ExpectSymbol('/', true)) { + return ParseGridTemplateColumnsOrAutoFlow(aForGridShorthand); + } + AppendValue(eCSSProperty_grid_template_columns, value); + return true; + } + + // 'subgrid' can appear either by itself, + // or as the beginning of <'grid-template-rows'> / <'grid-template-columns'> + nsSubstring* ident = NextIdent(); + if (ident) { + if (ident->LowerCaseEqualsLiteral("subgrid")) { + if (!nsLayoutUtils::IsGridTemplateSubgridValueEnabled()) { + REPORT_UNEXPECTED(PESubgridNotSupported); + return false; + } + if (!ParseOptionalLineNameListAfterSubgrid(value)) { + return false; + } + AppendValue(eCSSProperty_grid_template_rows, value); + AppendValue(eCSSProperty_grid_template_areas, nsCSSValue(eCSSUnit_None)); + if (ExpectSymbol('/', true)) { + return ParseGridTemplateColumnsOrAutoFlow(aForGridShorthand); + } + if (value.GetListValue()->mNext) { + // Non-empty <line-name-list> after 'subgrid'. + // This is only valid as part of <'grid-template-rows'>, + // which must be followed by a slash. + return false; + } + // 'subgrid' by itself sets both grid-template-rows/columns. + AppendValue(eCSSProperty_grid_template_columns, value); + return true; + } + UngetToken(); + } + + // [ <line-names>? ] here is ambiguous: + // it can be either the start of a <track-list> (in a <'grid-template-rows'>), + // or the start of [ <line-names>? <string> <track-size>? <line-names>? ]+ + nsCSSValue firstLineNames; + if (ParseGridLineNames(firstLineNames) == CSSParseResult::Error || + !GetToken(true)) { + return false; + } + if (mToken.mType == eCSSToken_String) { + // It's the [ <line-names>? <string> <track-size>? <line-names>? ]+ case. + if (!ParseGridTemplateAfterString(firstLineNames)) { + return false; + } + // Parse an optional [ / <explicit-track-list> ] as the columns value. + if (ExpectSymbol('/', true)) { + return ParseGridTrackList(eCSSProperty_grid_template_columns, + GridTrackListFlags::eExplicitTrackList); + } + value.SetNoneValue(); // absent means 'none' + AppendValue(eCSSProperty_grid_template_columns, value); + return true; + } + UngetToken(); + + // Finish parsing <'grid-template-rows'> with the |firstLineNames| we have, + // and then parse a mandatory [ / <'grid-template-columns'> ]. + if (!ParseGridTrackListWithFirstLineNames(value, firstLineNames) || + !ExpectSymbol('/', true)) { + return false; + } + AppendValue(eCSSProperty_grid_template_rows, value); + value.SetNoneValue(); + AppendValue(eCSSProperty_grid_template_areas, value); + return ParseGridTemplateColumnsOrAutoFlow(aForGridShorthand); +} + +// Helper for parsing the 'grid-template' shorthand: +// Parse [ <line-names>? <string> <track-size>? <line-names>? ]+ +// with a <line-names>? already consumed, stored in |aFirstLineNames|, +// and the current token a <string> +bool +CSSParserImpl::ParseGridTemplateAfterString(const nsCSSValue& aFirstLineNames) +{ + MOZ_ASSERT(mToken.mType == eCSSToken_String, + "ParseGridTemplateAfterString called with a non-string token"); + + nsCSSValue rowsValue; + RefPtr<css::GridTemplateAreasValue> areas = + new css::GridTemplateAreasValue(); + nsDataHashtable<nsStringHashKey, uint32_t> areaIndices; + nsCSSValueList* rowsItem = rowsValue.SetListValue(); + rowsItem->mValue = aFirstLineNames; + + for (;;) { + if (!ParseGridTemplateAreasLine(mToken.mIdent, areas, areaIndices)) { + return false; + } + + rowsItem->mNext = new nsCSSValueList; + rowsItem = rowsItem->mNext; + CSSParseResult result = ParseGridTrackSize(rowsItem->mValue); + if (result == CSSParseResult::Error) { + return false; + } + if (result == CSSParseResult::NotFound) { + rowsItem->mValue.SetAutoValue(); + } + + rowsItem->mNext = new nsCSSValueList; + rowsItem = rowsItem->mNext; + result = ParseGridLineNames(rowsItem->mValue); + if (result == CSSParseResult::Error) { + return false; + } + if (result == CSSParseResult::Ok) { + // Append to the same list as the previous call to ParseGridLineNames. + result = ParseGridLineNames(rowsItem->mValue); + if (result == CSSParseResult::Error) { + return false; + } + if (result == CSSParseResult::Ok) { + // Parsed <line-name> twice. + // The property value can not end here, we expect a string next. + if (!GetToken(true)) { + return false; + } + if (eCSSToken_String != mToken.mType) { + UngetToken(); + return false; + } + continue; + } + } + + // Did not find a <line-names>. + // Next, we expect either a string or the end of the property value. + if (!GetToken(true)) { + break; + } + if (eCSSToken_String != mToken.mType) { + UngetToken(); + break; + } + } + + AppendValue(eCSSProperty_grid_template_areas, nsCSSValue(areas)); + AppendValue(eCSSProperty_grid_template_rows, rowsValue); + return true; +} + +// <'grid-template'> | +// <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>? | +// [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'> +bool +CSSParserImpl::ParseGrid() +{ + nsCSSValue value; + if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { + for (const nsCSSPropertyID* subprops = + nsCSSProps::SubpropertyEntryFor(eCSSProperty_grid); + *subprops != eCSSProperty_UNKNOWN; ++subprops) { + AppendValue(*subprops, value); + } + return true; + } + + // https://drafts.csswg.org/css-grid/#grid-shorthand + // "Also, the gutter properties are reset by this shorthand, + // even though they can't be set by it." + value.SetFloatValue(0.0f, eCSSUnit_Pixel); + AppendValue(eCSSProperty_grid_row_gap, value); + AppendValue(eCSSProperty_grid_column_gap, value); + + // [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'> + auto res = ParseGridShorthandAutoProps(NS_STYLE_GRID_AUTO_FLOW_ROW); + if (res == CSSParseResult::Error) { + return false; + } + if (res == CSSParseResult::Ok) { + value.SetAutoValue(); + AppendValue(eCSSProperty_grid_auto_columns, value); + nsCSSValue none(eCSSUnit_None); + AppendValue(eCSSProperty_grid_template_areas, none); + AppendValue(eCSSProperty_grid_template_rows, none); + if (!ExpectSymbol('/', true)) { + return false; + } + return ParseGridTemplateColumnsRows(eCSSProperty_grid_template_columns); + } + + // Set remaining subproperties that might not be set by ParseGridTemplate to + // their initial values and then parse <'grid-template'> | + // <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>? . + value.SetIntValue(NS_STYLE_GRID_AUTO_FLOW_ROW, eCSSUnit_Enumerated); + AppendValue(eCSSProperty_grid_auto_flow, value); + value.SetAutoValue(); + AppendValue(eCSSProperty_grid_auto_rows, value); + AppendValue(eCSSProperty_grid_auto_columns, value); + return ParseGridTemplate(true); +} + +// Parse [ auto-flow && dense? ] <'grid-auto-[rows|columns]'>? for the 'grid' +// shorthand. If aAutoFlowAxis == NS_STYLE_GRID_AUTO_FLOW_ROW then we're +// parsing row values, otherwise column values. +CSSParseResult +CSSParserImpl::ParseGridShorthandAutoProps(int32_t aAutoFlowAxis) +{ + MOZ_ASSERT(aAutoFlowAxis == NS_STYLE_GRID_AUTO_FLOW_ROW || + aAutoFlowAxis == NS_STYLE_GRID_AUTO_FLOW_COLUMN); + if (!GetToken(true)) { + return CSSParseResult::NotFound; + } + // [ auto-flow && dense? ] + int32_t autoFlowValue = 0; + if (mToken.mType == eCSSToken_Ident) { + nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent); + if (keyword == eCSSKeyword_auto_flow) { + autoFlowValue = aAutoFlowAxis; + if (GetToken(true)) { + if (mToken.mType == eCSSToken_Ident && + nsCSSKeywords::LookupKeyword(mToken.mIdent) == eCSSKeyword_dense) { + autoFlowValue |= NS_STYLE_GRID_AUTO_FLOW_DENSE; + } else { + UngetToken(); + } + } + } else if (keyword == eCSSKeyword_dense) { + if (!GetToken(true)) { + return CSSParseResult::Error; + } + if (mToken.mType != eCSSToken_Ident || + nsCSSKeywords::LookupKeyword(mToken.mIdent) != eCSSKeyword_auto_flow) { + UngetToken(); + return CSSParseResult::Error; + } + autoFlowValue = aAutoFlowAxis | NS_STYLE_GRID_AUTO_FLOW_DENSE; + } + } + if (autoFlowValue) { + nsCSSValue value; + value.SetIntValue(autoFlowValue, eCSSUnit_Enumerated); + AppendValue(eCSSProperty_grid_auto_flow, value); + } else { + UngetToken(); + return CSSParseResult::NotFound; + } + + // <'grid-auto-[rows|columns]'>? + nsCSSValue autoTrackValue; + CSSParseResult result = ParseGridTrackSize(autoTrackValue); + if (result == CSSParseResult::Error) { + return result; + } + if (result == CSSParseResult::NotFound) { + autoTrackValue.SetAutoValue(); + } + AppendValue(aAutoFlowAxis == NS_STYLE_GRID_AUTO_FLOW_ROW ? + eCSSProperty_grid_auto_rows : eCSSProperty_grid_auto_columns, + autoTrackValue); + return CSSParseResult::Ok; +} + +// Parse a <grid-line>. +// If successful, set aValue to eCSSUnit_Auto, +// or a eCSSUnit_List containing, in that order: +// +// * An optional eCSSUnit_Enumerated marking a "span" keyword. +// * An optional eCSSUnit_Integer +// * An optional eCSSUnit_Ident +// +// At least one of eCSSUnit_Integer or eCSSUnit_Ident is present. +bool +CSSParserImpl::ParseGridLine(nsCSSValue& aValue) +{ + // <grid-line> = + // auto | + // <custom-ident> | + // [ <integer> && <custom-ident>? ] | + // [ span && [ <integer> || <custom-ident> ] ] + // + // Syntactically, this simplifies to: + // + // <grid-line> = + // auto | + // [ span? && [ <integer> || <custom-ident> ] ] + + if (ParseSingleTokenVariant(aValue, VARIANT_AUTO, nullptr)) { + return true; + } + + bool hasSpan = false; + bool hasIdent = false; + Maybe<int32_t> integer; + nsCSSValue ident; + +#ifdef MOZ_VALGRIND + // Make the contained value be defined even though we really want a + // Nothing here. This works around an otherwise difficult to avoid + // Memcheck false positive when this is compiled by gcc-5.3 -O2. + // See bug 1301856. + integer.emplace(0); + integer.reset(); +#endif + + if (!GetToken(true)) { + return false; + } + if (mToken.mType == eCSSToken_Ident && + mToken.mIdent.LowerCaseEqualsLiteral("span")) { + hasSpan = true; + if (!GetToken(true)) { + return false; + } + } + + do { + if (!hasIdent && + mToken.mType == eCSSToken_Ident && + ParseCustomIdent(ident, mToken.mIdent, kGridLineKeywords)) { + hasIdent = true; + } else if (integer.isNothing() && + mToken.mType == eCSSToken_Number && + mToken.mIntegerValid && + mToken.mInteger != 0) { + integer.emplace(mToken.mInteger); + } else { + UngetToken(); + break; + } + } while (!(integer.isSome() && hasIdent) && GetToken(true)); + + // Require at least one of <integer> or <custom-ident> + if (!(integer.isSome() || hasIdent)) { + return false; + } + + if (!hasSpan && GetToken(true)) { + if (mToken.mType == eCSSToken_Ident && + mToken.mIdent.LowerCaseEqualsLiteral("span")) { + hasSpan = true; + } else { + UngetToken(); + } + } + + nsCSSValueList* item = aValue.SetListValue(); + if (hasSpan) { + // Given "span", a negative <integer> is invalid. + if (integer.isSome() && integer.ref() < 0) { + return false; + } + // '1' here is a dummy value. + // The mere presence of eCSSUnit_Enumerated indicates a "span" keyword. + item->mValue.SetIntValue(1, eCSSUnit_Enumerated); + item->mNext = new nsCSSValueList; + item = item->mNext; + } + if (integer.isSome()) { + item->mValue.SetIntValue(integer.ref(), eCSSUnit_Integer); + if (hasIdent) { + item->mNext = new nsCSSValueList; + item = item->mNext; + } + } + if (hasIdent) { + item->mValue = ident; + } + return true; +} + +bool +CSSParserImpl::ParseGridColumnRowStartEnd(nsCSSPropertyID aPropID) +{ + nsCSSValue value; + if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr) || + ParseGridLine(value)) { + AppendValue(aPropID, value); + return true; + } + return false; +} + +// If |aFallback| is a List containing a single Ident, set |aValue| to that. +// Otherwise, set |aValue| to Auto. +// Used with |aFallback| from ParseGridLine() +static void +HandleGridLineFallback(const nsCSSValue& aFallback, nsCSSValue& aValue) +{ + if (aFallback.GetUnit() == eCSSUnit_List && + aFallback.GetListValue()->mValue.GetUnit() == eCSSUnit_Ident && + !aFallback.GetListValue()->mNext) { + aValue = aFallback; + } else { + aValue.SetAutoValue(); + } +} + +bool +CSSParserImpl::ParseGridColumnRow(nsCSSPropertyID aStartPropID, + nsCSSPropertyID aEndPropID) +{ + nsCSSValue value; + nsCSSValue secondValue; + if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { + AppendValue(aStartPropID, value); + AppendValue(aEndPropID, value); + return true; + } + + if (!ParseGridLine(value)) { + return false; + } + if (GetToken(true)) { + if (mToken.IsSymbol('/')) { + if (ParseGridLine(secondValue)) { + AppendValue(aStartPropID, value); + AppendValue(aEndPropID, secondValue); + return true; + } else { + return false; + } + } + UngetToken(); + } + + // A single <custom-ident> is repeated to both properties, + // anything else sets the grid-{column,row}-end property to 'auto'. + HandleGridLineFallback(value, secondValue); + + AppendValue(aStartPropID, value); + AppendValue(aEndPropID, secondValue); + return true; +} + +bool +CSSParserImpl::ParseGridArea() +{ + nsCSSValue values[4]; + if (ParseSingleTokenVariant(values[0], VARIANT_INHERIT, nullptr)) { + AppendValue(eCSSProperty_grid_row_start, values[0]); + AppendValue(eCSSProperty_grid_column_start, values[0]); + AppendValue(eCSSProperty_grid_row_end, values[0]); + AppendValue(eCSSProperty_grid_column_end, values[0]); + return true; + } + + int32_t i = 0; + for (;;) { + if (!ParseGridLine(values[i])) { + return false; + } + if (++i == 4 || !GetToken(true)) { + break; + } + if (!mToken.IsSymbol('/')) { + UngetToken(); + break; + } + } + + MOZ_ASSERT(i >= 1, "should have parsed at least one grid-line (or returned)"); + if (i < 2) { + HandleGridLineFallback(values[0], values[1]); + } + if (i < 3) { + HandleGridLineFallback(values[0], values[2]); + } + if (i < 4) { + HandleGridLineFallback(values[1], values[3]); + } + + AppendValue(eCSSProperty_grid_row_start, values[0]); + AppendValue(eCSSProperty_grid_column_start, values[1]); + AppendValue(eCSSProperty_grid_row_end, values[2]); + AppendValue(eCSSProperty_grid_column_end, values[3]); + return true; +} + +bool +CSSParserImpl::ParseGridGap() +{ + nsCSSValue first; + if (ParseSingleTokenVariant(first, VARIANT_INHERIT, nullptr)) { + AppendValue(eCSSProperty_grid_row_gap, first); + AppendValue(eCSSProperty_grid_column_gap, first); + return true; + } + if (ParseNonNegativeVariant(first, VARIANT_LPCALC, nullptr) != + CSSParseResult::Ok) { + return false; + } + nsCSSValue second; + auto result = ParseNonNegativeVariant(second, VARIANT_LPCALC, nullptr); + if (result == CSSParseResult::Error) { + return false; + } + AppendValue(eCSSProperty_grid_row_gap, first); + AppendValue(eCSSProperty_grid_column_gap, + result == CSSParseResult::NotFound ? first : second); + return true; +} + +// normal | [<number> <integer>?] +bool +CSSParserImpl::ParseInitialLetter() +{ + nsCSSValue value; + // 'inherit', 'initial', 'unset', 'none', and 'normal' must be alone + if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NORMAL, + nullptr)) { + nsCSSValue first, second; + if (!ParseOneOrLargerNumber(first)) { + return false; + } + + if (!ParseOneOrLargerInteger(second)) { + AppendValue(eCSSProperty_initial_letter, first); + return true; + } else { + RefPtr<nsCSSValue::Array> val = nsCSSValue::Array::Create(2); + val->Item(0) = first; + val->Item(1) = second; + value.SetArrayValue(val, eCSSUnit_Array); + } + } + AppendValue(eCSSProperty_initial_letter, value); + return true; +} + +// [ $aTable && <overflow-position>? ] ? +// $aTable is for <content-position> or <self-position> +bool +CSSParserImpl::ParseAlignJustifyPosition(nsCSSValue& aResult, + const KTableEntry aTable[]) +{ + nsCSSValue pos, overflowPos; + int32_t value = 0; + if (ParseEnum(pos, aTable)) { + value = pos.GetIntValue(); + if (ParseEnum(overflowPos, nsCSSProps::kAlignOverflowPosition)) { + value |= overflowPos.GetIntValue(); + } + aResult.SetIntValue(value, eCSSUnit_Enumerated); + return true; + } + if (ParseEnum(overflowPos, nsCSSProps::kAlignOverflowPosition)) { + if (ParseEnum(pos, aTable)) { + aResult.SetIntValue(pos.GetIntValue() | overflowPos.GetIntValue(), + eCSSUnit_Enumerated); + return true; + } + return false; // <overflow-position> must be followed by a value in $table + } + return true; +} + +// auto | normal | stretch | <baseline-position> | +// [ <self-position> && <overflow-position>? ] | +// [ legacy && [ left | right | center ] ] +bool +CSSParserImpl::ParseJustifyItems() +{ + nsCSSValue value; + if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { + if (MOZ_UNLIKELY(ParseEnum(value, nsCSSProps::kAlignLegacy))) { + nsCSSValue legacy; + if (!ParseEnum(legacy, nsCSSProps::kAlignLegacyPosition)) { + return false; // leading 'legacy' not followed by 'left' etc is an error + } + value.SetIntValue(value.GetIntValue() | legacy.GetIntValue(), + eCSSUnit_Enumerated); + } else { + if (!ParseAlignEnum(value, nsCSSProps::kAlignAutoNormalStretchBaseline)) { + if (!ParseAlignJustifyPosition(value, nsCSSProps::kAlignSelfPosition) || + value.GetUnit() == eCSSUnit_Null) { + return false; + } + // check for a trailing 'legacy' after 'left' etc + auto val = value.GetIntValue(); + if (val == NS_STYLE_JUSTIFY_CENTER || + val == NS_STYLE_JUSTIFY_LEFT || + val == NS_STYLE_JUSTIFY_RIGHT) { + nsCSSValue legacy; + if (ParseEnum(legacy, nsCSSProps::kAlignLegacy)) { + value.SetIntValue(val | legacy.GetIntValue(), eCSSUnit_Enumerated); + } + } + } + } + } + AppendValue(eCSSProperty_justify_items, value); + return true; +} + +// normal | stretch | <baseline-position> | +// [ <overflow-position>? && <self-position> ] +bool +CSSParserImpl::ParseAlignItems() +{ + nsCSSValue value; + if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { + if (!ParseAlignEnum(value, nsCSSProps::kAlignNormalStretchBaseline)) { + if (!ParseAlignJustifyPosition(value, nsCSSProps::kAlignSelfPosition) || + value.GetUnit() == eCSSUnit_Null) { + return false; + } + } + } + AppendValue(eCSSProperty_align_items, value); + return true; +} + +// auto | normal | stretch | <baseline-position> | +// [ <overflow-position>? && <self-position> ] +bool +CSSParserImpl::ParseAlignJustifySelf(nsCSSPropertyID aPropID) +{ + nsCSSValue value; + if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { + if (!ParseAlignEnum(value, nsCSSProps::kAlignAutoNormalStretchBaseline)) { + if (!ParseAlignJustifyPosition(value, nsCSSProps::kAlignSelfPosition) || + value.GetUnit() == eCSSUnit_Null) { + return false; + } + } + } + AppendValue(aPropID, value); + return true; +} + +// normal | <baseline-position> | [ <content-distribution> || +// [ <overflow-position>? && <content-position> ] ] +// (the part after the || is called <*-position> below) +bool +CSSParserImpl::ParseAlignJustifyContent(nsCSSPropertyID aPropID) +{ + nsCSSValue value; + if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { + if (!ParseAlignEnum(value, nsCSSProps::kAlignNormalBaseline)) { + nsCSSValue fallbackValue; + if (!ParseEnum(value, nsCSSProps::kAlignContentDistribution)) { + if (!ParseAlignJustifyPosition(fallbackValue, + nsCSSProps::kAlignContentPosition) || + fallbackValue.GetUnit() == eCSSUnit_Null) { + return false; + } + // optional <content-distribution> after <*-position> ... + if (!ParseEnum(value, nsCSSProps::kAlignContentDistribution)) { + // ... is missing so the <*-position> is the value, not the fallback + value = fallbackValue; + fallbackValue.Reset(); + } + } else { + // any optional <*-position> is a fallback value + if (!ParseAlignJustifyPosition(fallbackValue, + nsCSSProps::kAlignContentPosition)) { + return false; + } + } + if (fallbackValue.GetUnit() != eCSSUnit_Null) { + auto fallback = fallbackValue.GetIntValue(); + value.SetIntValue(value.GetIntValue() | + (fallback << NS_STYLE_ALIGN_ALL_SHIFT), + eCSSUnit_Enumerated); + } + } + } + AppendValue(aPropID, value); + return true; +} + +// place-content: [ normal | <baseline-position> | <content-distribution> | +// <content-position> ]{1,2} +bool +CSSParserImpl::ParsePlaceContent() +{ + nsCSSValue first; + if (ParseSingleTokenVariant(first, VARIANT_INHERIT, nullptr)) { + AppendValue(eCSSProperty_align_content, first); + AppendValue(eCSSProperty_justify_content, first); + return true; + } + if (!ParseAlignEnum(first, nsCSSProps::kAlignNormalBaseline) && + !ParseEnum(first, nsCSSProps::kAlignContentDistribution) && + !ParseEnum(first, nsCSSProps::kAlignContentPosition)) { + return false; + } + AppendValue(eCSSProperty_align_content, first); + nsCSSValue second; + if (!ParseAlignEnum(second, nsCSSProps::kAlignNormalBaseline) && + !ParseEnum(second, nsCSSProps::kAlignContentDistribution) && + !ParseEnum(second, nsCSSProps::kAlignContentPosition)) { + AppendValue(eCSSProperty_justify_content, first); + } else { + AppendValue(eCSSProperty_justify_content, second); + } + return true; +} + +// place-items: <x> [ auto | <x> ]? +// <x> = [ normal | stretch | <baseline-position> | <self-position> ] +bool +CSSParserImpl::ParsePlaceItems() +{ + nsCSSValue first; + if (ParseSingleTokenVariant(first, VARIANT_INHERIT, nullptr)) { + AppendValue(eCSSProperty_align_items, first); + AppendValue(eCSSProperty_justify_items, first); + return true; + } + if (!ParseAlignEnum(first, nsCSSProps::kAlignNormalStretchBaseline) && + !ParseEnum(first, nsCSSProps::kAlignSelfPosition)) { + return false; + } + AppendValue(eCSSProperty_align_items, first); + nsCSSValue second; + // Note: 'auto' is valid for justify-items, but not align-items. + if (!ParseAlignEnum(second, nsCSSProps::kAlignAutoNormalStretchBaseline) && + !ParseEnum(second, nsCSSProps::kAlignSelfPosition)) { + AppendValue(eCSSProperty_justify_items, first); + } else { + AppendValue(eCSSProperty_justify_items, second); + } + return true; +} + +// place-self: [ auto | normal | stretch | <baseline-position> | +// <self-position> ]{1,2} +bool +CSSParserImpl::ParsePlaceSelf() +{ + nsCSSValue first; + if (ParseSingleTokenVariant(first, VARIANT_INHERIT, nullptr)) { + AppendValue(eCSSProperty_align_self, first); + AppendValue(eCSSProperty_justify_self, first); + return true; + } + if (!ParseAlignEnum(first, nsCSSProps::kAlignAutoNormalStretchBaseline) && + !ParseEnum(first, nsCSSProps::kAlignSelfPosition)) { + return false; + } + AppendValue(eCSSProperty_align_self, first); + nsCSSValue second; + if (!ParseAlignEnum(second, nsCSSProps::kAlignAutoNormalStretchBaseline) && + !ParseEnum(second, nsCSSProps::kAlignSelfPosition)) { + AppendValue(eCSSProperty_justify_self, first); + } else { + AppendValue(eCSSProperty_justify_self, second); + } + return true; +} + +// <color-stop> : <color> [ <percentage> | <length> ]? +bool +CSSParserImpl::ParseColorStop(nsCSSValueGradient* aGradient) +{ + nsCSSValueGradientStop* stop = aGradient->mStops.AppendElement(); + CSSParseResult result = ParseVariant(stop->mColor, VARIANT_COLOR, nullptr); + if (result == CSSParseResult::Error) { + return false; + } else if (result == CSSParseResult::NotFound) { + stop->mIsInterpolationHint = true; + } + + // Stop positions do not have to fall between the starting-point and + // ending-point, so we don't use ParseNonNegativeVariant. + result = ParseVariant(stop->mLocation, VARIANT_LP | VARIANT_CALC, nullptr); + if (result == CSSParseResult::Error) { + return false; + } else if (result == CSSParseResult::NotFound) { + if (stop->mIsInterpolationHint) { + return false; + } + stop->mLocation.SetNoneValue(); + } + return true; +} + +// Helper for ParseLinearGradient -- returns true iff aPosition represents a +// box-position value which was parsed with only edge keywords. +// e.g. "left top", or "bottom", but not "left 10px" +// +// (NOTE: Even though callers may want to exclude explicit "center", we still +// need to allow for _CENTER here, because omitted position-values (e.g. the +// x-component of a value like "top") will have been parsed as being *implicit* +// center. The correct way to disallow *explicit* center is to pass "false" for +// ParseBoxPositionValues()'s "aAllowExplicitCenter" parameter, before you +// call this function.) +static bool +IsBoxPositionStrictlyEdgeKeywords(nsCSSValuePair& aPosition) +{ + const nsCSSValue& xValue = aPosition.mXValue; + const nsCSSValue& yValue = aPosition.mYValue; + return (xValue.GetUnit() == eCSSUnit_Enumerated && + (xValue.GetIntValue() & (NS_STYLE_IMAGELAYER_POSITION_LEFT | + NS_STYLE_IMAGELAYER_POSITION_CENTER | + NS_STYLE_IMAGELAYER_POSITION_RIGHT)) && + yValue.GetUnit() == eCSSUnit_Enumerated && + (yValue.GetIntValue() & (NS_STYLE_IMAGELAYER_POSITION_TOP | + NS_STYLE_IMAGELAYER_POSITION_CENTER | + NS_STYLE_IMAGELAYER_POSITION_BOTTOM))); +} + +// <gradient> +// : linear-gradient( <linear-gradient-line>? <color-stops> ')' +// | radial-gradient( <radial-gradient-line>? <color-stops> ')' +// +// <linear-gradient-line> : [ to [left | right] || [top | bottom] ] , +// | <legacy-gradient-line> +// <radial-gradient-line> : [ <shape> || <size> ] [ at <position> ]? , +// | [ at <position> ] , +// | <legacy-gradient-line>? <legacy-shape-size>? +// <shape> : circle | ellipse +// <size> : closest-side | closest-corner | farthest-side | farthest-corner +// | <length> | [<length> | <percentage>]{2} +// +// <legacy-gradient-line> : [ <position> || <angle>] , +// +// <legacy-shape-size> : [ <shape> || <legacy-size> ] , +// <legacy-size> : closest-side | closest-corner | farthest-side +// | farthest-corner | contain | cover +// +// <color-stops> : <color-stop> , <color-stop> [, <color-stop>]* +bool +CSSParserImpl::ParseLinearGradient(nsCSSValue& aValue, + uint8_t aFlags) +{ + RefPtr<nsCSSValueGradient> cssGradient + = new nsCSSValueGradient(false, aFlags & eGradient_Repeating); + + if (!GetToken(true)) { + return false; + } + + // Check for "to" syntax (but not if parsing a -webkit-linear-gradient) + if (!(aFlags & eGradient_WebkitLegacy) && + mToken.mType == eCSSToken_Ident && + mToken.mIdent.LowerCaseEqualsLiteral("to")) { + + // "to" syntax doesn't allow explicit "center" + if (!ParseBoxPositionValues(cssGradient->mBgPos, false, false)) { + SkipUntil(')'); + return false; + } + + // [ to [left | right] || [top | bottom] ] , + if (!IsBoxPositionStrictlyEdgeKeywords(cssGradient->mBgPos)) { + SkipUntil(')'); + return false; + } + + if (!ExpectSymbol(',', true)) { + SkipUntil(')'); + return false; + } + + return ParseGradientColorStops(cssGradient, aValue); + } + + if (!(aFlags & eGradient_AnyLegacy)) { + // We're parsing an unprefixed linear-gradient, and we tried & failed to + // parse a 'to' token above. Put the token back & try to re-parse our + // expression as <angle>? <color-stop-list> + UngetToken(); + + // <angle> , + if (ParseSingleTokenVariant(cssGradient->mAngle, VARIANT_ANGLE, nullptr) && + !ExpectSymbol(',', true)) { + SkipUntil(')'); + return false; + } + + return ParseGradientColorStops(cssGradient, aValue); + } + + // If we get here, we're parsing a prefixed linear-gradient expression. Put + // back the first token (which we may have checked for "to" above) and try to + // parse expression as <legacy-gradient-line>? <color-stop-list> + bool haveGradientLine = IsLegacyGradientLine(mToken.mType, mToken.mIdent); + UngetToken(); + + if (haveGradientLine) { + // Parse a <legacy-gradient-line> + cssGradient->mIsLegacySyntax = true; + // In -webkit-linear-gradient expressions (handled below), we need to accept + // unitless 0 for angles, to match WebKit/Blink. + int32_t angleFlags = (aFlags & eGradient_WebkitLegacy) ? + VARIANT_ANGLE | VARIANT_ZERO_ANGLE : + VARIANT_ANGLE; + + bool haveAngle = + ParseSingleTokenVariant(cssGradient->mAngle, angleFlags, nullptr); + + // If we got an angle, we might now have a comma, ending the gradient-line. + bool haveAngleComma = haveAngle && ExpectSymbol(',', true); + + // If we're webkit-prefixed & didn't get an angle, + // OR if we're moz-prefixed & didn't get an angle+comma, + // then proceed to parse a box-position. + if (((aFlags & eGradient_WebkitLegacy) && !haveAngle) || + ((aFlags & eGradient_MozLegacy) && !haveAngleComma)) { + // (Note: 3rd arg controls whether the "center" keyword is allowed. + // -moz-linear-gradient allows it; -webkit-linear-gradient does not.) + if (!ParseBoxPositionValues(cssGradient->mBgPos, false, + (aFlags & eGradient_MozLegacy))) { + SkipUntil(')'); + return false; + } + + // -webkit-linear-gradient only supports edge keywords here. + if ((aFlags & eGradient_WebkitLegacy) && + !IsBoxPositionStrictlyEdgeKeywords(cssGradient->mBgPos)) { + SkipUntil(')'); + return false; + } + + if (!ExpectSymbol(',', true) && + // If we didn't already get an angle, and we're not -webkit prefixed, + // we can parse an angle+comma now. Otherwise it's an error. + (haveAngle || + (aFlags & eGradient_WebkitLegacy) || + !ParseSingleTokenVariant(cssGradient->mAngle, VARIANT_ANGLE, + nullptr) || + // now we better have a comma + !ExpectSymbol(',', true))) { + SkipUntil(')'); + return false; + } + } + } + + return ParseGradientColorStops(cssGradient, aValue); +} + +bool +CSSParserImpl::ParseRadialGradient(nsCSSValue& aValue, + uint8_t aFlags) +{ + RefPtr<nsCSSValueGradient> cssGradient + = new nsCSSValueGradient(true, aFlags & eGradient_Repeating); + + // [ <shape> || <size> ] + bool haveShape = + ParseSingleTokenVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD, + nsCSSProps::kRadialGradientShapeKTable); + + bool haveSize = + ParseSingleTokenVariant(cssGradient->GetRadialSize(), VARIANT_KEYWORD, + (aFlags & eGradient_AnyLegacy) ? + nsCSSProps::kRadialGradientLegacySizeKTable : + nsCSSProps::kRadialGradientSizeKTable); + if (haveSize) { + if (!haveShape) { + // <size> <shape> + haveShape = + ParseSingleTokenVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD, + nsCSSProps::kRadialGradientShapeKTable); + } + } else if (!(aFlags & eGradient_AnyLegacy)) { + // Save RadialShape before parsing RadiusX because RadialShape and + // RadiusX share the storage. + int32_t shape = + cssGradient->GetRadialShape().GetUnit() == eCSSUnit_Enumerated ? + cssGradient->GetRadialShape().GetIntValue() : -1; + // <length> | [<length> | <percentage>]{2} + cssGradient->mIsExplicitSize = true; + haveSize = + ParseSingleTokenNonNegativeVariant(cssGradient->GetRadiusX(), VARIANT_LP, + nullptr); + if (!haveSize) { + // It was not an explicit size after all. + // Note that ParseNonNegativeVariant may have put something + // invalid into our storage, but only in the case where it was + // rejected only for being negative. Since this means the token + // was a length or a percentage, we know it's not valid syntax + // (which must be a comma, the 'at' keyword, or a color), so we + // know this value will be dropped. This means it doesn't matter + // that we have something invalid in our storage. + cssGradient->mIsExplicitSize = false; + } else { + // vertical extent is optional + bool haveYSize = + ParseSingleTokenNonNegativeVariant(cssGradient->GetRadiusY(), + VARIANT_LP, nullptr); + if (!haveShape) { + nsCSSValue shapeValue; + haveShape = + ParseSingleTokenVariant(shapeValue, VARIANT_KEYWORD, + nsCSSProps::kRadialGradientShapeKTable); + if (haveShape) { + shape = shapeValue.GetIntValue(); + } + } + if (haveYSize + ? shape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR + : cssGradient->GetRadiusX().GetUnit() == eCSSUnit_Percent || + shape == NS_STYLE_GRADIENT_SHAPE_ELLIPTICAL) { + SkipUntil(')'); + return false; + } + } + } + + if ((haveShape || haveSize) && ExpectSymbol(',', true)) { + // [ <shape> || <size> ] , + return ParseGradientColorStops(cssGradient, aValue); + } + + if (!GetToken(true)) { + return false; + } + + if (!(aFlags & eGradient_AnyLegacy)) { + if (mToken.mType == eCSSToken_Ident && + mToken.mIdent.LowerCaseEqualsLiteral("at")) { + // [ <shape> || <size> ]? at <position> , + if (!ParseBoxPositionValues(cssGradient->mBgPos, false) || + !ExpectSymbol(',', true)) { + SkipUntil(')'); + return false; + } + + return ParseGradientColorStops(cssGradient, aValue); + } + + // <color-stops> only + UngetToken(); + return ParseGradientColorStops(cssGradient, aValue); + } + MOZ_ASSERT(!cssGradient->mIsExplicitSize); + + nsCSSTokenType ty = mToken.mType; + nsString id = mToken.mIdent; + UngetToken(); + + // <legacy-gradient-line> + bool haveGradientLine = false; + // if we already encountered a shape or size, + // we can not have a gradient-line in legacy syntax + if (!haveShape && !haveSize) { + haveGradientLine = IsLegacyGradientLine(ty, id); + } + if (haveGradientLine) { + // Note: -webkit-radial-gradient() doesn't accept angles. + bool haveAngle = (aFlags & eGradient_WebkitLegacy) + ? false + : ParseSingleTokenVariant(cssGradient->mAngle, VARIANT_ANGLE, nullptr); + + // If we got an angle, we might now have a comma, ending the gradient-line + if (!haveAngle || !ExpectSymbol(',', true)) { + if (!ParseBoxPositionValues(cssGradient->mBgPos, false)) { + SkipUntil(')'); + return false; + } + + if (!ExpectSymbol(',', true) && + // If we didn't already get an angle, and we're not -webkit prefixed, + // can parse an angle+comma now. Otherwise it's an error. + (haveAngle || + (aFlags & eGradient_WebkitLegacy) || + !ParseSingleTokenVariant(cssGradient->mAngle, VARIANT_ANGLE, + nullptr) || + // now we better have a comma + !ExpectSymbol(',', true))) { + SkipUntil(')'); + return false; + } + } + + if (cssGradient->mAngle.GetUnit() != eCSSUnit_None) { + cssGradient->mIsLegacySyntax = true; + } + } + + // radial gradients might have a shape and size here for legacy syntax + if (!haveShape && !haveSize) { + haveShape = + ParseSingleTokenVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD, + nsCSSProps::kRadialGradientShapeKTable); + haveSize = + ParseSingleTokenVariant(cssGradient->GetRadialSize(), VARIANT_KEYWORD, + nsCSSProps::kRadialGradientLegacySizeKTable); + + // could be in either order + if (!haveShape) { + haveShape = + ParseSingleTokenVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD, + nsCSSProps::kRadialGradientShapeKTable); + } + } + + if ((haveShape || haveSize) && !ExpectSymbol(',', true)) { + SkipUntil(')'); + return false; + } + + return ParseGradientColorStops(cssGradient, aValue); +} + +bool +CSSParserImpl::IsLegacyGradientLine(const nsCSSTokenType& aType, + const nsString& aId) +{ + // N.B. ParseBoxPositionValues is not guaranteed to put back + // everything it scanned if it fails, so we must only call it + // if there is no alternative to consuming a <box-position>. + // ParseVariant, as used here, will either succeed and consume + // a single token, or fail and consume none, so we can be more + // cavalier about calling it. + + bool haveGradientLine = false; + switch (aType) { + case eCSSToken_Percentage: + case eCSSToken_Number: + case eCSSToken_Dimension: + haveGradientLine = true; + break; + + case eCSSToken_Function: + if (aId.LowerCaseEqualsLiteral("calc") || + aId.LowerCaseEqualsLiteral("-moz-calc")) { + haveGradientLine = true; + break; + } + MOZ_FALLTHROUGH; + case eCSSToken_ID: + case eCSSToken_Hash: + // this is a color + break; + + case eCSSToken_Ident: { + // This is only a gradient line if it's a box position keyword. + nsCSSKeyword kw = nsCSSKeywords::LookupKeyword(aId); + int32_t junk; + if (kw != eCSSKeyword_UNKNOWN && + nsCSSProps::FindKeyword(kw, nsCSSProps::kImageLayerPositionKTable, + junk)) { + haveGradientLine = true; + } + break; + } + + default: + // error + break; + } + + return haveGradientLine; +} + +bool +CSSParserImpl::ParseGradientColorStops(nsCSSValueGradient* aGradient, + nsCSSValue& aValue) +{ + // At least two color stops are required + if (!ParseColorStop(aGradient) || + !ExpectSymbol(',', true) || + !ParseColorStop(aGradient)) { + SkipUntil(')'); + return false; + } + + // Additional color stops + while (ExpectSymbol(',', true)) { + if (!ParseColorStop(aGradient)) { + SkipUntil(')'); + return false; + } + } + + if (!ExpectSymbol(')', true)) { + SkipUntil(')'); + return false; + } + + // Check if interpolation hints are in the correct location + bool previousPointWasInterpolationHint = true; + for (size_t x = 0; x < aGradient->mStops.Length(); x++) { + bool isInterpolationHint = aGradient->mStops[x].mIsInterpolationHint; + if (isInterpolationHint && previousPointWasInterpolationHint) { + return false; + } + previousPointWasInterpolationHint = isInterpolationHint; + } + + if (previousPointWasInterpolationHint) { + return false; + } + + aValue.SetGradientValue(aGradient); + return true; +} + +// Parses the x or y component of a -webkit-gradient() <point> expression. +// See ParseWebkitGradientPoint() documentation for more. +bool +CSSParserImpl::ParseWebkitGradientPointComponent(nsCSSValue& aComponent, + bool aIsHorizontal) +{ + if (!GetToken(true)) { + return false; + } + + // Keyword tables to use for keyword-matching + // (Keyword order is important; we assume the index can be multiplied by 50% + // to convert to a percent-valued component.) + static const nsCSSKeyword kHorizKeywords[] = { + eCSSKeyword_left, // 0% + eCSSKeyword_center, // 50% + eCSSKeyword_right // 100% + }; + static const nsCSSKeyword kVertKeywords[] = { + eCSSKeyword_top, // 0% + eCSSKeyword_center, // 50% + eCSSKeyword_bottom // 100% + }; + static const size_t kNumKeywords = MOZ_ARRAY_LENGTH(kHorizKeywords); + static_assert(kNumKeywords == MOZ_ARRAY_LENGTH(kVertKeywords), + "Horizontal & vertical keyword tables must have same count"); + + // Try to parse the component as a number, or a percent, or a + // keyword-converted-to-percent. + if (mToken.mType == eCSSToken_Number) { + aComponent.SetFloatValue(mToken.mNumber, eCSSUnit_Pixel); + } else if (mToken.mType == eCSSToken_Percentage) { + aComponent.SetPercentValue(mToken.mNumber); + } else if (mToken.mType == eCSSToken_Ident) { + nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent); + if (keyword == eCSSKeyword_UNKNOWN) { + return false; + } + // Choose our keyword table: + const nsCSSKeyword* kwTable = aIsHorizontal ? kHorizKeywords : kVertKeywords; + // Convert keyword to percent value (0%, 50%, or 100%) + bool didAcceptKeyword = false; + for (size_t i = 0; i < kNumKeywords; i++) { + if (keyword == kwTable[i]) { + // 0%, 50%, or 100%: + aComponent.SetPercentValue(i * 0.5); + didAcceptKeyword = true; + break; + } + } + if (!didAcceptKeyword) { + return false; + } + } else { + // Unrecognized token type. Put it back. (It might be a closing-paren of an + // invalid -webkit-gradient(...) expression, and we need to be sure caller + // can see it & stops parsing at that point.) + UngetToken(); + return false; + } + + MOZ_ASSERT(aComponent.GetUnit() == eCSSUnit_Pixel || + aComponent.GetUnit() == eCSSUnit_Percent, + "If we get here, we should've successfully parsed a number (as a " + "pixel length), a percent, or a keyword (converted to percent)"); + return true; +} + +// This function parses a "<point>" expression for -webkit-gradient(...) +// Quoting https://www.webkit.org/blog/175/introducing-css-gradients/ : +// "A point is a pair of space-separated values. +// The syntax supports numbers, percentages or +// the keywords top, bottom, left and right +// for point values." +// +// Two additional notes: +// - WebKit also accepts the "center" keyword (not listed in the text above). +// - WebKit only accepts horizontal-flavored keywords (left/center/right) in +// the first ("x") component, and vertical-flavored keywords +// (top/center/bottom) in the second ("y") component. (This is different +// from the standard gradient syntax, which accepts both orderings, e.g. +// "top left" as well as "left top".) +bool +CSSParserImpl::ParseWebkitGradientPoint(nsCSSValuePair& aPoint) +{ + return ParseWebkitGradientPointComponent(aPoint.mXValue, true) && + ParseWebkitGradientPointComponent(aPoint.mYValue, false); +} + +// Parse the next token as a <number> (for a <radius> in a -webkit-gradient +// expresison). Returns true on success; returns false & puts back +// whatever it parsed on failure. +bool +CSSParserImpl::ParseWebkitGradientRadius(float& aRadius) +{ + if (!GetToken(true)) { + return false; + } + + if (mToken.mType != eCSSToken_Number) { + UngetToken(); + return false; + } + + aRadius = mToken.mNumber; + return true; +} + +// Parse one of: +// color-stop(number|percent, color) +// from(color) +// to(color) +// +// Quoting https://www.webkit.org/blog/175/introducing-css-gradients/ : +// A stop is a function, color-stop, that takes two arguments, the stop value +// (either a percentage or a number between 0 and 1.0), and a color (any +// valid CSS color). In addition the shorthand functions from and to are +// supported. These functions only require a color argument and are +// equivalent to color-stop(0, ...) and color-stop(1.0, …) respectively. +bool +CSSParserImpl::ParseWebkitGradientColorStop(nsCSSValueGradient* aGradient) +{ + MOZ_ASSERT(aGradient, "null gradient"); + + if (!GetToken(true)) { + return false; + } + + // We're expecting color-stop(...), from(...), or to(...) which are all + // functions. Bail if we got anything else. + if (mToken.mType != eCSSToken_Function) { + UngetToken(); + return false; + } + + nsCSSValueGradientStop* stop = aGradient->mStops.AppendElement(); + + // Parse color-stop location (or infer it, for shorthands "from"/"to"): + if (mToken.mIdent.LowerCaseEqualsLiteral("color-stop")) { + // Parse stop location, followed by comma. + if (!ParseSingleTokenVariant(stop->mLocation, + VARIANT_NUMBER | VARIANT_PERCENT, + nullptr) || + !ExpectSymbol(',', true)) { + SkipUntil(')'); // Skip to end of color-stop(...) expression. + return false; + } + + // If we got a <number>, convert it to percentage for consistency: + if (stop->mLocation.GetUnit() == eCSSUnit_Number) { + stop->mLocation.SetPercentValue(stop->mLocation.GetFloatValue()); + } + } else if (mToken.mIdent.LowerCaseEqualsLiteral("from")) { + // Shorthand for color-stop(0%, ...) + stop->mLocation.SetPercentValue(0.0f); + } else if (mToken.mIdent.LowerCaseEqualsLiteral("to")) { + // Shorthand for color-stop(100%, ...) + stop->mLocation.SetPercentValue(1.0f); + } else { + // Unrecognized function name (invalid for a -webkit-gradient color stop). + UngetToken(); + return false; + } + + CSSParseResult result = ParseVariant(stop->mColor, VARIANT_COLOR, nullptr); + if (result != CSSParseResult::Ok || + (stop->mColor.GetUnit() == eCSSUnit_EnumColor && + stop->mColor.GetIntValue() == NS_COLOR_CURRENTCOLOR)) { + // Parse failure, or parsed "currentColor" which is forbidden in + // -webkit-gradient for some reason. + SkipUntil(')'); + return false; + } + + // Parse color-stop function close-paren + if (!ExpectSymbol(')', true)) { + SkipUntil(')'); + return false; + } + + MOZ_ASSERT(stop->mLocation.GetUnit() == eCSSUnit_Percent, + "Should produce only percent-valued stop-locations. " + "(Caller depends on this when sorting color stops.)"); + + return true; +} + +// Comparatison function to use for sorting -webkit-gradient() stops by +// location. This function assumes stops have percent-valued locations (and +// CSSParserImpl::ParseWebkitGradientColorStop should enforce this). +static bool +IsColorStopPctLocationLessThan(const nsCSSValueGradientStop& aStop1, + const nsCSSValueGradientStop& aStop2) { + return (aStop1.mLocation.GetPercentValue() < + aStop2.mLocation.GetPercentValue()); +} + +// This function parses a list of comma-separated color-stops for a +// -webkit-gradient(...) expression, and then pads & sorts the list as-needed. +bool +CSSParserImpl::ParseWebkitGradientColorStops(nsCSSValueGradient* aGradient) +{ + MOZ_ASSERT(aGradient, "null gradient"); + + // Parse any number of ", <color-stop>" expressions. (0 or more) + // Note: This is different from unprefixed gradient syntax, which + // requires at least 2 stops. + while (ExpectSymbol(',', true)) { + if (!ParseWebkitGradientColorStop(aGradient)) { + return false; + } + } + + // Pad up to 2 stops as-needed: + // (Modern gradient expressions are required to have at least 2 stops, so we + // depend on this internally -- e.g. we have an assertion about this in + // nsCSSRendering.cpp. -webkit-gradient syntax allows 0 stops or 1 stop, + // though, so we just pad up to 2 stops in this case). + + // If we have no stops, pad with transparent-black: + if (aGradient->mStops.IsEmpty()) { + nsCSSValueGradientStop* stop1 = aGradient->mStops.AppendElement(); + stop1->mColor.SetIntegerColorValue(NS_RGBA(0, 0, 0, 0), + eCSSUnit_RGBAColor); + stop1->mLocation.SetPercentValue(0.0f); + + nsCSSValueGradientStop* stop2 = aGradient->mStops.AppendElement(); + stop2->mColor.SetIntegerColorValue(NS_RGBA(0, 0, 0, 0), + eCSSUnit_RGBAColor); + stop2->mLocation.SetPercentValue(1.0f); + } else if (aGradient->mStops.Length() == 1) { + // Copy whatever the author provided in the first stop: + nsCSSValueGradientStop* stop = aGradient->mStops.AppendElement(); + *stop = aGradient->mStops[0]; + } else { + // We have >2 stops. Sort them in order of increasing location. + std::stable_sort(aGradient->mStops.begin(), + aGradient->mStops.end(), + IsColorStopPctLocationLessThan); + } + return true; +} + +// Compares aStartCoord to aEndCoord, and returns true iff they share the same +// unit (both pixel, or both percent) and aStartCoord is larger. +static bool +IsWebkitGradientCoordLarger(const nsCSSValue& aStartCoord, + const nsCSSValue& aEndCoord) +{ + if (aStartCoord.GetUnit() == eCSSUnit_Percent && + aEndCoord.GetUnit() == eCSSUnit_Percent) { + return aStartCoord.GetPercentValue() > aEndCoord.GetPercentValue(); + } + + if (aStartCoord.GetUnit() == eCSSUnit_Pixel && + aEndCoord.GetUnit() == eCSSUnit_Pixel) { + return aStartCoord.GetFloatValue() > aEndCoord.GetFloatValue(); + } + + // We can't compare them, since their units differ. Returning false suggests + // that aEndCoord is larger, which is probably a decent guess anyway. + return false; +} + +// Finalize our internal representation of a -webkit-gradient(linear, ...) +// expression, given the parsed points. (The parsed color stops +// should already be hanging off of the passed-in nsCSSValueGradient.) +// +// Note: linear gradients progress along a line between two points. The +// -webkit-gradient(linear, ...) syntax lets the author precisely specify the +// starting and ending point. However, our internal gradient structures +// only store one point, and the other point is implicitly its reflection +// across the painted area's center. (The legacy -moz-linear-gradient syntax +// also lets us store an angle.) +// +// In this function, we try to go from the two-point representation to an +// equivalent or approximately-equivalent one-point representation. +void +CSSParserImpl::FinalizeLinearWebkitGradient(nsCSSValueGradient* aGradient, + const nsCSSValuePair& aStartPoint, + const nsCSSValuePair& aEndPoint) +{ + MOZ_ASSERT(!aGradient->mIsRadial, "passed-in gradient must be linear"); + + // If the start & end points have the same Y-coordinate, then we can treat + // this as a horizontal gradient progressing towards the center of the left + // or right side. + if (aStartPoint.mYValue == aEndPoint.mYValue) { + aGradient->mBgPos.mYValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_CENTER, + eCSSUnit_Enumerated); + if (IsWebkitGradientCoordLarger(aStartPoint.mXValue, aEndPoint.mXValue)) { + aGradient->mBgPos.mXValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_LEFT, + eCSSUnit_Enumerated); + } else { + aGradient->mBgPos.mXValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_RIGHT, + eCSSUnit_Enumerated); + } + return; + } + + // If the start & end points have the same X-coordinate, then we can treat + // this as a horizontal gradient progressing towards the center of the top + // or bottom side. + if (aStartPoint.mXValue == aEndPoint.mXValue) { + aGradient->mBgPos.mXValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_CENTER, + eCSSUnit_Enumerated); + if (IsWebkitGradientCoordLarger(aStartPoint.mYValue, aEndPoint.mYValue)) { + aGradient->mBgPos.mYValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_TOP, + eCSSUnit_Enumerated); + } else { + aGradient->mBgPos.mYValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_BOTTOM, + eCSSUnit_Enumerated); + } + return; + } + + // OK, the gradient is angled, which means we likely can't represent it + // exactly in |aGradient|, without doing analysis on the two points to + // extract an angle (which we might not be able to do depending on the units + // used). For now, we'll just do something really basic -- just use the + // first point as if it were the starting point in a legacy + // -moz-linear-gradient() expression. That way, the rendered gradient will + // progress from this first point, towards the center of the covered element, + // to a reflected end point on the far side. Note that we have to use + // mIsLegacySyntax=true for this to work, because standardized (non-legacy) + // gradients place some restrictions on the reference point [namely, that it + // use percent units & be on the border of the element]. + aGradient->mIsLegacySyntax = true; + aGradient->mBgPos = aStartPoint; +} + +// Finalize our internal representation of a -webkit-gradient(radial, ...) +// expression, given the parsed points & radii. (The parsed color-stops +// should already be hanging off of the passed-in nsCSSValueGradient). +void +CSSParserImpl::FinalizeRadialWebkitGradient(nsCSSValueGradient* aGradient, + const nsCSSValuePair& aFirstCenter, + const nsCSSValuePair& aSecondCenter, + const float aFirstRadius, + const float aSecondRadius) +{ + MOZ_ASSERT(aGradient->mIsRadial, "passed-in gradient must be radial"); + + // NOTE: -webkit-gradient(radial, ...) has *two arbitrary circles*, with the + // gradient stretching between the circles' edges. In contrast, the standard + // syntax (and hence our data structures) can only represent *one* circle, + // with the gradient going from its center to its edge. To bridge this gap + // in expressiveness, we'll just see which of our two circles is smaller, and + // we'll treat that circle as if it were zero-sized and located at the center + // of the larger circle. Then, we'll be able to use the same data structures + // that we use for the standard radial-gradient syntax. + if (aSecondRadius >= aFirstRadius) { + // Second circle is larger. + aGradient->mBgPos = aSecondCenter; + aGradient->mIsExplicitSize = true; + aGradient->GetRadiusX().SetFloatValue(aSecondRadius, eCSSUnit_Pixel); + return; + } + + // First circle is larger, so we'll have it be the outer circle. + aGradient->mBgPos = aFirstCenter; + aGradient->mIsExplicitSize = true; + aGradient->GetRadiusX().SetFloatValue(aFirstRadius, eCSSUnit_Pixel); + + // For this to work properly (with the earlier color stops attached to the + // first circle), we need to also reverse the color-stop list, so that + // e.g. the author's "from" color is attached to the outer edge (the first + // circle), rather than attached to the center (the collapsed second circle). + std::reverse(aGradient->mStops.begin(), aGradient->mStops.end()); + + // And now invert the stop locations: + for (nsCSSValueGradientStop& colorStop : aGradient->mStops) { + float origLocation = colorStop.mLocation.GetPercentValue(); + colorStop.mLocation.SetPercentValue(1.0f - origLocation); + } +} + +bool +CSSParserImpl::ParseWebkitGradient(nsCSSValue& aValue) +{ + // Parse type of gradient + if (!GetToken(true)) { + return false; + } + + if (mToken.mType != eCSSToken_Ident) { + UngetToken(); // Important; the token might be ")", which we're about to + // seek to. + SkipUntil(')'); + return false; + } + + bool isRadial; + if (mToken.mIdent.LowerCaseEqualsLiteral("radial")) { + isRadial = true; + } else if (mToken.mIdent.LowerCaseEqualsLiteral("linear")) { + isRadial = false; + } else { + // Unrecognized gradient type. + SkipUntil(')'); + return false; + } + + // Parse a comma + first point: + nsCSSValuePair firstPoint; + if (!ExpectSymbol(',', true) || + !ParseWebkitGradientPoint(firstPoint)) { + SkipUntil(')'); + return false; + } + + // If radial, parse comma + first radius: + float firstRadius; + if (isRadial) { + if (!ExpectSymbol(',', true) || + !ParseWebkitGradientRadius(firstRadius)) { + SkipUntil(')'); + return false; + } + } + + // Parse a comma + second point: + nsCSSValuePair secondPoint; + if (!ExpectSymbol(',', true) || + !ParseWebkitGradientPoint(secondPoint)) { + SkipUntil(')'); + return false; + } + + // If radial, parse comma + second radius: + float secondRadius; + if (isRadial) { + if (!ExpectSymbol(',', true) || + !ParseWebkitGradientRadius(secondRadius)) { + SkipUntil(')'); + return false; + } + } + + // Construct a nsCSSValueGradient object, and parse color stops into it: + RefPtr<nsCSSValueGradient> cssGradient = + new nsCSSValueGradient(isRadial, false /* aIsRepeating */); + + if (!ParseWebkitGradientColorStops(cssGradient) || + !ExpectSymbol(')', true)) { + // Failed to parse color-stops, or found trailing junk between them & ')'. + SkipUntil(')'); + return false; + } + + // Finish building cssGradient, based on our parsed positioning/sizing info: + if (isRadial) { + FinalizeRadialWebkitGradient(cssGradient, firstPoint, secondPoint, + firstRadius, secondRadius); + } else { + FinalizeLinearWebkitGradient(cssGradient, firstPoint, secondPoint); + } + + aValue.SetGradientValue(cssGradient); + return true; +} + +bool +CSSParserImpl::ParseWebkitTextStroke() +{ + static const nsCSSPropertyID kWebkitTextStrokeIDs[] = { + eCSSProperty__webkit_text_stroke_width, + eCSSProperty__webkit_text_stroke_color + }; + + const size_t numProps = MOZ_ARRAY_LENGTH(kWebkitTextStrokeIDs); + nsCSSValue values[numProps]; + + int32_t found = ParseChoice(values, kWebkitTextStrokeIDs, numProps); + if (found < 1) { + return false; + } + + if (!(found & 1)) { // Provide default -webkit-text-stroke-width + values[0].SetFloatValue(0, eCSSUnit_Pixel); + } + + if (!(found & 2)) { // Provide default -webkit-text-stroke-color + values[1].SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor); + } + + for (size_t index = 0; index < numProps; ++index) { + AppendValue(kWebkitTextStrokeIDs[index], values[index]); + } + + return true; +} + + int32_t +CSSParserImpl::ParseChoice(nsCSSValue aValues[], + const nsCSSPropertyID aPropIDs[], int32_t aNumIDs) +{ + int32_t found = 0; + nsAutoParseCompoundProperty compound(this); + + int32_t loop; + for (loop = 0; loop < aNumIDs; loop++) { + // Try each property parser in order + int32_t hadFound = found; + int32_t index; + for (index = 0; index < aNumIDs; index++) { + int32_t bit = 1 << index; + if ((found & bit) == 0) { + CSSParseResult result = + ParseSingleValueProperty(aValues[index], aPropIDs[index]); + if (result == CSSParseResult::Error) { + return -1; + } + if (result == CSSParseResult::Ok) { + found |= bit; + // It's more efficient to break since it will reset |hadFound| + // to |found|. Furthermore, ParseListStyle depends on our going + // through the properties in order for each value.. + break; + } + } + } + if (found == hadFound) { // found nothing new + break; + } + } + if (0 < found) { + if (1 == found) { // only first property + if (eCSSUnit_Inherit == aValues[0].GetUnit()) { // one inherit, all inherit + for (loop = 1; loop < aNumIDs; loop++) { + aValues[loop].SetInheritValue(); + } + found = ((1 << aNumIDs) - 1); + } + else if (eCSSUnit_Initial == aValues[0].GetUnit()) { // one initial, all initial + for (loop = 1; loop < aNumIDs; loop++) { + aValues[loop].SetInitialValue(); + } + found = ((1 << aNumIDs) - 1); + } + else if (eCSSUnit_Unset == aValues[0].GetUnit()) { // one unset, all unset + for (loop = 1; loop < aNumIDs; loop++) { + aValues[loop].SetUnsetValue(); + } + found = ((1 << aNumIDs) - 1); + } + } + else { // more than one value, verify no inherits, initials or unsets + for (loop = 0; loop < aNumIDs; loop++) { + if (eCSSUnit_Inherit == aValues[loop].GetUnit()) { + found = -1; + break; + } + else if (eCSSUnit_Initial == aValues[loop].GetUnit()) { + found = -1; + break; + } + else if (eCSSUnit_Unset == aValues[loop].GetUnit()) { + found = -1; + break; + } + } + } + } + return found; +} + +void +CSSParserImpl::AppendValue(nsCSSPropertyID aPropID, const nsCSSValue& aValue) +{ + mTempData.AddLonghandProperty(aPropID, aValue); +} + +/** + * Parse a "box" property. Box properties have 1 to 4 values. When less + * than 4 values are provided a standard mapping is used to replicate + * existing values. + */ +bool +CSSParserImpl::ParseBoxProperties(const nsCSSPropertyID aPropIDs[]) +{ + // Get up to four values for the property + int32_t count = 0; + nsCSSRect result; + NS_FOR_CSS_SIDES (index) { + CSSParseResult parseResult = + ParseBoxProperty(result.*(nsCSSRect::sides[index]), aPropIDs[index]); + if (parseResult == CSSParseResult::NotFound) { + break; + } + if (parseResult == CSSParseResult::Error) { + return false; + } + count++; + } + if (count == 0) { + return false; + } + + if (1 < count) { // verify no more than single inherit, initial or unset + NS_FOR_CSS_SIDES (index) { + nsCSSUnit unit = (result.*(nsCSSRect::sides[index])).GetUnit(); + if (eCSSUnit_Inherit == unit || + eCSSUnit_Initial == unit || + eCSSUnit_Unset == unit) { + return false; + } + } + } + + // Provide missing values by replicating some of the values found + switch (count) { + case 1: // Make right == top + result.mRight = result.mTop; + MOZ_FALLTHROUGH; + case 2: // Make bottom == top + result.mBottom = result.mTop; + MOZ_FALLTHROUGH; + case 3: // Make left == right + result.mLeft = result.mRight; + } + + NS_FOR_CSS_SIDES (index) { + AppendValue(aPropIDs[index], result.*(nsCSSRect::sides[index])); + } + return true; +} + +// Similar to ParseBoxProperties, except there is only one property +// with the result as its value, not four. +bool +CSSParserImpl::ParseGroupedBoxProperty(int32_t aVariantMask, + /** outparam */ nsCSSValue& aValue, + uint32_t aRestrictions) +{ + nsCSSRect& result = aValue.SetRectValue(); + + int32_t count = 0; + NS_FOR_CSS_SIDES (index) { + CSSParseResult parseResult = + ParseVariantWithRestrictions(result.*(nsCSSRect::sides[index]), + aVariantMask, nullptr, + aRestrictions); + if (parseResult == CSSParseResult::NotFound) { + break; + } + if (parseResult == CSSParseResult::Error) { + return false; + } + count++; + } + + if (count == 0) { + return false; + } + + // Provide missing values by replicating some of the values found + switch (count) { + case 1: // Make right == top + result.mRight = result.mTop; + MOZ_FALLTHROUGH; + case 2: // Make bottom == top + result.mBottom = result.mTop; + MOZ_FALLTHROUGH; + case 3: // Make left == right + result.mLeft = result.mRight; + } + + return true; +} + +bool +CSSParserImpl::ParseBoxCornerRadius(nsCSSPropertyID aPropID) +{ + nsCSSValue dimenX, dimenY; + // required first value + if (ParseNonNegativeVariant(dimenX, VARIANT_HLP | VARIANT_CALC, nullptr) != + CSSParseResult::Ok) { + return false; + } + + // optional second value (forbidden if first value is inherit/initial/unset) + if (dimenX.GetUnit() != eCSSUnit_Inherit && + dimenX.GetUnit() != eCSSUnit_Initial && + dimenX.GetUnit() != eCSSUnit_Unset) { + if (ParseNonNegativeVariant(dimenY, VARIANT_LP | VARIANT_CALC, nullptr) == + CSSParseResult::Error) { + return false; + } + } + + if (dimenX == dimenY || dimenY.GetUnit() == eCSSUnit_Null) { + AppendValue(aPropID, dimenX); + } else { + nsCSSValue value; + value.SetPairValue(dimenX, dimenY); + AppendValue(aPropID, value); + } + return true; +} + +bool +CSSParserImpl::ParseBoxCornerRadiiInternals(nsCSSValue array[]) +{ + // Rectangles are used as scratch storage. + // top => top-left, right => top-right, + // bottom => bottom-right, left => bottom-left. + nsCSSRect dimenX, dimenY; + int32_t countX = 0, countY = 0; + + NS_FOR_CSS_SIDES (side) { + CSSParseResult result = + ParseNonNegativeVariant(dimenX.*nsCSSRect::sides[side], + (side > 0 ? 0 : VARIANT_INHERIT) | + VARIANT_LP | VARIANT_CALC, + nullptr); + if (result == CSSParseResult::Error) { + return false; + } else if (result == CSSParseResult::NotFound) { + break; + } + countX++; + } + if (countX == 0) + return false; + + if (ExpectSymbol('/', true)) { + NS_FOR_CSS_SIDES (side) { + CSSParseResult result = + ParseNonNegativeVariant(dimenY.*nsCSSRect::sides[side], + VARIANT_LP | VARIANT_CALC, nullptr); + if (result == CSSParseResult::Error) { + return false; + } else if (result == CSSParseResult::NotFound) { + break; + } + countY++; + } + if (countY == 0) + return false; + } + + // if 'initial', 'inherit' or 'unset' was used, it must be the only value + if (countX > 1 || countY > 0) { + nsCSSUnit unit = dimenX.mTop.GetUnit(); + if (eCSSUnit_Inherit == unit || + eCSSUnit_Initial == unit || + eCSSUnit_Unset == unit) + return false; + } + + // if we have no Y-values, use the X-values + if (countY == 0) { + dimenY = dimenX; + countY = countX; + } + + // Provide missing values by replicating some of the values found + switch (countX) { + case 1: // Make top-right same as top-left + dimenX.mRight = dimenX.mTop; + MOZ_FALLTHROUGH; + case 2: // Make bottom-right same as top-left + dimenX.mBottom = dimenX.mTop; + MOZ_FALLTHROUGH; + case 3: // Make bottom-left same as top-right + dimenX.mLeft = dimenX.mRight; + } + + switch (countY) { + case 1: // Make top-right same as top-left + dimenY.mRight = dimenY.mTop; + MOZ_FALLTHROUGH; + case 2: // Make bottom-right same as top-left + dimenY.mBottom = dimenY.mTop; + MOZ_FALLTHROUGH; + case 3: // Make bottom-left same as top-right + dimenY.mLeft = dimenY.mRight; + } + + NS_FOR_CSS_SIDES(side) { + nsCSSValue& x = dimenX.*nsCSSRect::sides[side]; + nsCSSValue& y = dimenY.*nsCSSRect::sides[side]; + + if (x == y) { + array[side] = x; + } else { + nsCSSValue pair; + pair.SetPairValue(x, y); + array[side] = pair; + } + } + return true; +} + +bool +CSSParserImpl::ParseBoxCornerRadii(const nsCSSPropertyID aPropIDs[]) +{ + nsCSSValue value[4]; + if (!ParseBoxCornerRadiiInternals(value)) { + return false; + } + + NS_FOR_CSS_SIDES(side) { + AppendValue(aPropIDs[side], value[side]); + } + return true; +} + +// These must be in CSS order (top,right,bottom,left) for indexing to work +static const nsCSSPropertyID kBorderStyleIDs[] = { + eCSSProperty_border_top_style, + eCSSProperty_border_right_style, + eCSSProperty_border_bottom_style, + eCSSProperty_border_left_style +}; +static const nsCSSPropertyID kBorderWidthIDs[] = { + eCSSProperty_border_top_width, + eCSSProperty_border_right_width, + eCSSProperty_border_bottom_width, + eCSSProperty_border_left_width +}; +static const nsCSSPropertyID kBorderColorIDs[] = { + eCSSProperty_border_top_color, + eCSSProperty_border_right_color, + eCSSProperty_border_bottom_color, + eCSSProperty_border_left_color +}; +static const nsCSSPropertyID kBorderRadiusIDs[] = { + eCSSProperty_border_top_left_radius, + eCSSProperty_border_top_right_radius, + eCSSProperty_border_bottom_right_radius, + eCSSProperty_border_bottom_left_radius +}; +static const nsCSSPropertyID kOutlineRadiusIDs[] = { + eCSSProperty__moz_outline_radius_topLeft, + eCSSProperty__moz_outline_radius_topRight, + eCSSProperty__moz_outline_radius_bottomRight, + eCSSProperty__moz_outline_radius_bottomLeft +}; + +void +CSSParserImpl::SaveInputState(CSSParserInputState& aState) +{ + aState.mToken = mToken; + aState.mHavePushBack = mHavePushBack; + mScanner->SavePosition(aState.mPosition); +} + +void +CSSParserImpl::RestoreSavedInputState(const CSSParserInputState& aState) +{ + mToken = aState.mToken; + mHavePushBack = aState.mHavePushBack; + mScanner->RestoreSavedPosition(aState.mPosition); +} + +bool +CSSParserImpl::ParseProperty(nsCSSPropertyID aPropID) +{ + // Can't use AutoRestore<bool> because it's a bitfield. + MOZ_ASSERT(!mHashlessColorQuirk, + "hashless color quirk should not be set"); + MOZ_ASSERT(!mUnitlessLengthQuirk, + "unitless length quirk should not be set"); + MOZ_ASSERT(aPropID != eCSSPropertyExtra_variable); + + if (mNavQuirkMode) { + mHashlessColorQuirk = + nsCSSProps::PropHasFlags(aPropID, CSS_PROPERTY_HASHLESS_COLOR_QUIRK); + mUnitlessLengthQuirk = + nsCSSProps::PropHasFlags(aPropID, CSS_PROPERTY_UNITLESS_LENGTH_QUIRK); + } + + // Save the current input state so that we can restore it later if we + // have to re-parse the property value as a variable-reference-containing + // token stream. + CSSParserInputState stateBeforeProperty; + SaveInputState(stateBeforeProperty); + mScanner->ClearSeenVariableReference(); + + NS_ASSERTION(aPropID < eCSSProperty_COUNT, "index out of range"); + bool allowVariables = true; + bool result; + switch (nsCSSProps::PropertyParseType(aPropID)) { + case CSS_PROPERTY_PARSE_INACCESSIBLE: { + // The user can't use these + REPORT_UNEXPECTED(PEInaccessibleProperty2); + allowVariables = false; + result = false; + break; + } + case CSS_PROPERTY_PARSE_FUNCTION: { + result = ParsePropertyByFunction(aPropID); + break; + } + case CSS_PROPERTY_PARSE_VALUE: { + result = false; + nsCSSValue value; + if (ParseSingleValueProperty(value, aPropID) == CSSParseResult::Ok) { + AppendValue(aPropID, value); + result = true; + } + // XXX Report errors? + break; + } + case CSS_PROPERTY_PARSE_VALUE_LIST: { + result = ParseValueList(aPropID); + break; + } + default: { + result = false; + allowVariables = false; + MOZ_ASSERT(false, + "Property's flags field in nsCSSPropList.h is missing " + "one of the CSS_PROPERTY_PARSE_* constants"); + break; + } + } + + if (result) { + // We need to call ExpectEndProperty() to decide whether to reparse + // with variables. This is needed because the property parsing may + // have stopped upon finding a variable (e.g., 'margin: 1px var(a)') + // in a way that future variable substitutions will be valid, or + // because it parsed everything that's possible but we still want to + // act as though the property contains variables even though we know + // the substitution will never work (e.g., for 'margin: 1px 2px 3px + // 4px 5px var(a)'). + // + // It would be nice to find a better solution here + // (and for the SkipUntilOneOf below), though, that doesn't depend + // on using what we don't accept for doing parsing correctly. + if (!ExpectEndProperty()) { + result = false; + } + } + + bool seenVariable = mScanner->SeenVariableReference() || + (stateBeforeProperty.mHavePushBack && + stateBeforeProperty.mToken.mType == eCSSToken_Function && + stateBeforeProperty.mToken.mIdent.LowerCaseEqualsLiteral("var")); + bool parseAsTokenStream; + + if (!result && allowVariables) { + parseAsTokenStream = true; + if (!seenVariable) { + // We might have stopped parsing the property before its end and before + // finding a variable reference. Keep checking until the end of the + // property. + CSSParserInputState stateAtError; + SaveInputState(stateAtError); + + const char16_t stopChars[] = { ';', '!', '}', ')', 0 }; + SkipUntilOneOf(stopChars); + UngetToken(); + parseAsTokenStream = mScanner->SeenVariableReference(); + + if (!parseAsTokenStream) { + // If we parsed to the end of the propery and didn't find any variable + // references, then the real position we want to report the error at + // is |stateAtError|. + RestoreSavedInputState(stateAtError); + } + } + } else { + parseAsTokenStream = false; + } + + if (parseAsTokenStream) { + // Go back to the start of the property value and parse it to make sure + // its variable references are syntactically valid and is otherwise + // balanced. + RestoreSavedInputState(stateBeforeProperty); + + if (!mInSupportsCondition) { + mScanner->StartRecording(); + } + + CSSVariableDeclarations::Type type; + bool dropBackslash; + nsString impliedCharacters; + nsCSSValue value; + if (ParseValueWithVariables(&type, &dropBackslash, impliedCharacters, + nullptr, nullptr)) { + MOZ_ASSERT(type == CSSVariableDeclarations::eTokenStream, + "a non-custom property reparsed since it contained variable " + "references should not have been 'initial' or 'inherit'"); + + nsString propertyValue; + + if (!mInSupportsCondition) { + // If we are in an @supports condition, we don't need to store the + // actual token stream on the nsCSSValue. + mScanner->StopRecording(propertyValue); + if (dropBackslash) { + MOZ_ASSERT(!propertyValue.IsEmpty() && + propertyValue[propertyValue.Length() - 1] == '\\'); + propertyValue.Truncate(propertyValue.Length() - 1); + } + propertyValue.Append(impliedCharacters); + } + + if (mHavePushBack) { + // If we came to the end of a property value that had a variable + // reference and a token was pushed back, then it would have been + // ended by '!', ')', ';', ']' or '}'. We should remove it from the + // recorded property value. + MOZ_ASSERT(mToken.IsSymbol('!') || + mToken.IsSymbol(')') || + mToken.IsSymbol(';') || + mToken.IsSymbol(']') || + mToken.IsSymbol('}')); + if (!mInSupportsCondition) { + MOZ_ASSERT(!propertyValue.IsEmpty()); + MOZ_ASSERT(propertyValue[propertyValue.Length() - 1] == + mToken.mSymbol); + propertyValue.Truncate(propertyValue.Length() - 1); + } + } + + if (!mInSupportsCondition) { + if (nsCSSProps::IsShorthand(aPropID)) { + // If this is a shorthand property, we store the token stream on each + // of its corresponding longhand properties. + CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aPropID, EnabledState()) { + nsCSSValueTokenStream* tokenStream = new nsCSSValueTokenStream; + tokenStream->mPropertyID = *p; + tokenStream->mShorthandPropertyID = aPropID; + tokenStream->mTokenStream = propertyValue; + tokenStream->mBaseURI = mBaseURI; + tokenStream->mSheetURI = mSheetURI; + tokenStream->mSheetPrincipal = mSheetPrincipal; + // XXX Should store sheet here (see bug 952338). + // tokenStream->mSheet = mSheet; + tokenStream->mLineNumber = stateBeforeProperty.mPosition.LineNumber(); + tokenStream->mLineOffset = stateBeforeProperty.mPosition.LineOffset(); + value.SetTokenStreamValue(tokenStream); + AppendValue(*p, value); + } + } else { + nsCSSValueTokenStream* tokenStream = new nsCSSValueTokenStream; + tokenStream->mPropertyID = aPropID; + tokenStream->mTokenStream = propertyValue; + tokenStream->mBaseURI = mBaseURI; + tokenStream->mSheetURI = mSheetURI; + tokenStream->mSheetPrincipal = mSheetPrincipal; + // XXX Should store sheet here (see bug 952338). + // tokenStream->mSheet = mSheet; + tokenStream->mLineNumber = stateBeforeProperty.mPosition.LineNumber(); + tokenStream->mLineOffset = stateBeforeProperty.mPosition.LineOffset(); + value.SetTokenStreamValue(tokenStream); + AppendValue(aPropID, value); + } + } + result = true; + } else { + if (!mInSupportsCondition) { + mScanner->StopRecording(); + } + } + } + + if (mNavQuirkMode) { + mHashlessColorQuirk = false; + mUnitlessLengthQuirk = false; + } + + return result; +} + +bool +CSSParserImpl::ParsePropertyByFunction(nsCSSPropertyID aPropID) +{ + switch (aPropID) { // handle shorthand or multiple properties + case eCSSProperty_place_content: + return ParsePlaceContent(); + case eCSSProperty_place_items: + return ParsePlaceItems(); + case eCSSProperty_place_self: + return ParsePlaceSelf(); + case eCSSProperty_background: + return ParseImageLayers(nsStyleImageLayers::kBackgroundLayerTable); + case eCSSProperty_background_repeat: + return ParseImageLayerRepeat(eCSSProperty_background_repeat); + case eCSSProperty_background_position: + return ParseImageLayerPosition(nsStyleImageLayers::kBackgroundLayerTable); + case eCSSProperty_background_position_x: + case eCSSProperty_background_position_y: + return ParseImageLayerPositionCoord(aPropID, + aPropID == eCSSProperty_background_position_x); + case eCSSProperty_background_size: + return ParseImageLayerSize(eCSSProperty_background_size); + case eCSSProperty_border: + return ParseBorderSide(kBorderTopIDs, true); + case eCSSProperty_border_color: + return ParseBorderColor(); + case eCSSProperty_border_spacing: + return ParseBorderSpacing(); + case eCSSProperty_border_style: + return ParseBorderStyle(); + case eCSSProperty_border_block_end: + return ParseBorderSide(kBorderBlockEndIDs, false); + case eCSSProperty_border_block_start: + return ParseBorderSide(kBorderBlockStartIDs, false); + case eCSSProperty_border_bottom: + return ParseBorderSide(kBorderBottomIDs, false); + case eCSSProperty_border_inline_end: + return ParseBorderSide(kBorderInlineEndIDs, false); + case eCSSProperty_border_inline_start: + return ParseBorderSide(kBorderInlineStartIDs, false); + case eCSSProperty_border_left: + return ParseBorderSide(kBorderLeftIDs, false); + case eCSSProperty_border_right: + return ParseBorderSide(kBorderRightIDs, false); + case eCSSProperty_border_top: + return ParseBorderSide(kBorderTopIDs, false); + case eCSSProperty_border_bottom_colors: + case eCSSProperty_border_left_colors: + case eCSSProperty_border_right_colors: + case eCSSProperty_border_top_colors: + return ParseBorderColors(aPropID); + case eCSSProperty_border_image_slice: + return ParseBorderImageSlice(true, nullptr); + case eCSSProperty_border_image_width: + return ParseBorderImageWidth(true); + case eCSSProperty_border_image_outset: + return ParseBorderImageOutset(true); + case eCSSProperty_border_image_repeat: + return ParseBorderImageRepeat(true); + case eCSSProperty_border_image: + return ParseBorderImage(); + case eCSSProperty_border_width: + return ParseBorderWidth(); + case eCSSProperty_border_radius: + return ParseBoxCornerRadii(kBorderRadiusIDs); + case eCSSProperty__moz_outline_radius: + return ParseBoxCornerRadii(kOutlineRadiusIDs); + + case eCSSProperty_border_top_left_radius: + case eCSSProperty_border_top_right_radius: + case eCSSProperty_border_bottom_right_radius: + case eCSSProperty_border_bottom_left_radius: + case eCSSProperty__moz_outline_radius_topLeft: + case eCSSProperty__moz_outline_radius_topRight: + case eCSSProperty__moz_outline_radius_bottomRight: + case eCSSProperty__moz_outline_radius_bottomLeft: + return ParseBoxCornerRadius(aPropID); + + case eCSSProperty_box_shadow: + case eCSSProperty_text_shadow: + return ParseShadowList(aPropID); + + case eCSSProperty_clip: + return ParseRect(eCSSProperty_clip); + case eCSSProperty_columns: + return ParseColumns(); + case eCSSProperty_column_rule: + return ParseBorderSide(kColumnRuleIDs, false); + case eCSSProperty_content: + return ParseContent(); + case eCSSProperty_counter_increment: + case eCSSProperty_counter_reset: + return ParseCounterData(aPropID); + case eCSSProperty_cursor: + return ParseCursor(); + case eCSSProperty_filter: + return ParseFilter(); + case eCSSProperty_flex: + return ParseFlex(); + case eCSSProperty_flex_flow: + return ParseFlexFlow(); + case eCSSProperty_font: + return ParseFont(); + case eCSSProperty_font_variant: + return ParseFontVariant(); + case eCSSProperty_grid_auto_flow: + return ParseGridAutoFlow(); + case eCSSProperty_grid_auto_columns: + case eCSSProperty_grid_auto_rows: + return ParseGridAutoColumnsRows(aPropID); + case eCSSProperty_grid_template_areas: + return ParseGridTemplateAreas(); + case eCSSProperty_grid_template_columns: + case eCSSProperty_grid_template_rows: + return ParseGridTemplateColumnsRows(aPropID); + case eCSSProperty_grid_template: + return ParseGridTemplate(); + case eCSSProperty_grid: + return ParseGrid(); + case eCSSProperty_grid_column_start: + case eCSSProperty_grid_column_end: + case eCSSProperty_grid_row_start: + case eCSSProperty_grid_row_end: + return ParseGridColumnRowStartEnd(aPropID); + case eCSSProperty_grid_column: + return ParseGridColumnRow(eCSSProperty_grid_column_start, + eCSSProperty_grid_column_end); + case eCSSProperty_grid_row: + return ParseGridColumnRow(eCSSProperty_grid_row_start, + eCSSProperty_grid_row_end); + case eCSSProperty_grid_area: + return ParseGridArea(); + case eCSSProperty_grid_gap: + return ParseGridGap(); + case eCSSProperty_image_region: + return ParseRect(eCSSProperty_image_region); + case eCSSProperty_align_content: + case eCSSProperty_justify_content: + return ParseAlignJustifyContent(aPropID); + case eCSSProperty_align_items: + return ParseAlignItems(); + case eCSSProperty_align_self: + case eCSSProperty_justify_self: + return ParseAlignJustifySelf(aPropID); + case eCSSProperty_initial_letter: + return ParseInitialLetter(); + case eCSSProperty_justify_items: + return ParseJustifyItems(); + case eCSSProperty_list_style: + return ParseListStyle(); + case eCSSProperty_margin: + return ParseMargin(); + case eCSSProperty_object_position: + return ParseObjectPosition(); + case eCSSProperty_outline: + return ParseOutline(); + case eCSSProperty_overflow: + return ParseOverflow(); + case eCSSProperty_padding: + return ParsePadding(); + case eCSSProperty_quotes: + return ParseQuotes(); + case eCSSProperty_text_decoration: + return ParseTextDecoration(); + case eCSSProperty_text_emphasis: + return ParseTextEmphasis(); + case eCSSProperty_will_change: + return ParseWillChange(); + case eCSSProperty_transform: + return ParseTransform(false); + case eCSSProperty__moz_transform: + return ParseTransform(true); + case eCSSProperty_transform_origin: + return ParseTransformOrigin(false); + case eCSSProperty_perspective_origin: + return ParseTransformOrigin(true); + case eCSSProperty_transition: + return ParseTransition(); + case eCSSProperty_animation: + return ParseAnimation(); + case eCSSProperty_transition_property: + return ParseTransitionProperty(); + case eCSSProperty_fill: + case eCSSProperty_stroke: + return ParsePaint(aPropID); + case eCSSProperty_stroke_dasharray: + return ParseDasharray(); + case eCSSProperty_marker: + return ParseMarker(); + case eCSSProperty_paint_order: + return ParsePaintOrder(); + case eCSSProperty_scroll_snap_type: + return ParseScrollSnapType(); +#ifdef MOZ_ENABLE_MASK_AS_SHORTHAND + case eCSSProperty_mask: + return ParseImageLayers(nsStyleImageLayers::kMaskLayerTable); + case eCSSProperty_mask_repeat: + return ParseImageLayerRepeat(eCSSProperty_mask_repeat); + case eCSSProperty_mask_position: + return ParseImageLayerPosition(nsStyleImageLayers::kMaskLayerTable); + case eCSSProperty_mask_position_x: + case eCSSProperty_mask_position_y: + return ParseImageLayerPositionCoord(aPropID, + aPropID == eCSSProperty_mask_position_x); + case eCSSProperty_mask_size: + return ParseImageLayerSize(eCSSProperty_mask_size); +#endif + case eCSSProperty__webkit_text_stroke: + return ParseWebkitTextStroke(); + case eCSSProperty_all: + return ParseAll(); + default: + MOZ_ASSERT(false, "should not be called"); + return false; + } +} + +// Bits used in determining which background position info we have +#define BG_CENTER NS_STYLE_IMAGELAYER_POSITION_CENTER +#define BG_TOP NS_STYLE_IMAGELAYER_POSITION_TOP +#define BG_BOTTOM NS_STYLE_IMAGELAYER_POSITION_BOTTOM +#define BG_LEFT NS_STYLE_IMAGELAYER_POSITION_LEFT +#define BG_RIGHT NS_STYLE_IMAGELAYER_POSITION_RIGHT +#define BG_CTB (BG_CENTER | BG_TOP | BG_BOTTOM) +#define BG_TB (BG_TOP | BG_BOTTOM) +#define BG_CLR (BG_CENTER | BG_LEFT | BG_RIGHT) +#define BG_LR (BG_LEFT | BG_RIGHT) + +CSSParseResult +CSSParserImpl::ParseBoxProperty(nsCSSValue& aValue, + nsCSSPropertyID aPropID) +{ + if (aPropID < 0 || aPropID >= eCSSProperty_COUNT_no_shorthands) { + MOZ_ASSERT(false, "must only be called for longhand properties"); + return CSSParseResult::NotFound; + } + + MOZ_ASSERT(!nsCSSProps::PropHasFlags(aPropID, + CSS_PROPERTY_VALUE_PARSER_FUNCTION), + "must only be called for non-function-parsed properties"); + + uint32_t variant = nsCSSProps::ParserVariant(aPropID); + if (variant == 0) { + MOZ_ASSERT(false, "must only be called for variant-parsed properties"); + return CSSParseResult::NotFound; + } + + if (variant & ~(VARIANT_AHKLP | VARIANT_COLOR | VARIANT_CALC)) { + MOZ_ASSERT(false, "must only be called for properties that take certain " + "variants"); + return CSSParseResult::NotFound; + } + + const KTableEntry* kwtable = nsCSSProps::kKeywordTableTable[aPropID]; + uint32_t restrictions = nsCSSProps::ValueRestrictions(aPropID); + + return ParseVariantWithRestrictions(aValue, variant, kwtable, restrictions); +} + +bool +CSSParserImpl::ParseSingleValuePropertyByFunction(nsCSSValue& aValue, + nsCSSPropertyID aPropID) +{ + switch (aPropID) { + case eCSSProperty_clip_path: + return ParseClipPath(aValue); + case eCSSProperty_contain: + return ParseContain(aValue); + case eCSSProperty_font_family: + return ParseFamily(aValue); + case eCSSProperty_font_synthesis: + return ParseFontSynthesis(aValue); + case eCSSProperty_font_variant_alternates: + return ParseFontVariantAlternates(aValue); + case eCSSProperty_font_variant_east_asian: + return ParseFontVariantEastAsian(aValue); + case eCSSProperty_font_variant_ligatures: + return ParseFontVariantLigatures(aValue); + case eCSSProperty_font_variant_numeric: + return ParseFontVariantNumeric(aValue); + case eCSSProperty_font_feature_settings: + return ParseFontFeatureSettings(aValue); + case eCSSProperty_font_weight: + return ParseFontWeight(aValue); + case eCSSProperty_image_orientation: + return ParseImageOrientation(aValue); + case eCSSProperty_list_style_type: + return ParseListStyleType(aValue); + case eCSSProperty_scroll_snap_points_x: + return ParseScrollSnapPoints(aValue, eCSSProperty_scroll_snap_points_x); + case eCSSProperty_scroll_snap_points_y: + return ParseScrollSnapPoints(aValue, eCSSProperty_scroll_snap_points_y); + case eCSSProperty_scroll_snap_destination: + return ParseScrollSnapDestination(aValue); + case eCSSProperty_scroll_snap_coordinate: + return ParseScrollSnapCoordinate(aValue); + case eCSSProperty_shape_outside: + return ParseShapeOutside(aValue); + case eCSSProperty_text_align: + return ParseTextAlign(aValue); + case eCSSProperty_text_align_last: + return ParseTextAlignLast(aValue); + case eCSSProperty_text_decoration_line: + return ParseTextDecorationLine(aValue); + case eCSSProperty_text_combine_upright: + return ParseTextCombineUpright(aValue); + case eCSSProperty_text_emphasis_position: + return ParseTextEmphasisPosition(aValue); + case eCSSProperty_text_emphasis_style: + return ParseTextEmphasisStyle(aValue); + case eCSSProperty_text_overflow: + return ParseTextOverflow(aValue); + case eCSSProperty_touch_action: + return ParseTouchAction(aValue); + default: + MOZ_ASSERT(false, "should not reach here"); + return false; + } +} + +CSSParseResult +CSSParserImpl::ParseSingleValueProperty(nsCSSValue& aValue, + nsCSSPropertyID aPropID) +{ + if (aPropID == eCSSPropertyExtra_x_none_value) { + return ParseVariant(aValue, VARIANT_NONE | VARIANT_INHERIT, nullptr); + } + + if (aPropID == eCSSPropertyExtra_x_auto_value) { + return ParseVariant(aValue, VARIANT_AUTO | VARIANT_INHERIT, nullptr); + } + + if (aPropID < 0 || aPropID >= eCSSProperty_COUNT_no_shorthands) { + MOZ_ASSERT(false, "not a single value property"); + return CSSParseResult::NotFound; + } + + if (nsCSSProps::PropHasFlags(aPropID, CSS_PROPERTY_VALUE_PARSER_FUNCTION)) { + uint32_t lineBefore, colBefore; + if (!GetNextTokenLocation(true, &lineBefore, &colBefore)) { + // We're at EOF before parsing. + return CSSParseResult::NotFound; + } + + if (ParseSingleValuePropertyByFunction(aValue, aPropID)) { + return CSSParseResult::Ok; + } + + uint32_t lineAfter, colAfter; + if (!GetNextTokenLocation(true, &lineAfter, &colAfter) || + lineAfter != lineBefore || + colAfter != colBefore) { + // Any single token value that was invalid will have been pushed back, + // so GetNextTokenLocation encountering EOF means we failed while + // parsing a multi-token value. + return CSSParseResult::Error; + } + + return CSSParseResult::NotFound; + } + + uint32_t variant = nsCSSProps::ParserVariant(aPropID); + if (variant == 0) { + MOZ_ASSERT(false, "not a single value property"); + return CSSParseResult::NotFound; + } + + const KTableEntry* kwtable = nsCSSProps::kKeywordTableTable[aPropID]; + uint32_t restrictions = nsCSSProps::ValueRestrictions(aPropID); + return ParseVariantWithRestrictions(aValue, variant, kwtable, restrictions); +} + +// font-descriptor: descriptor ':' value ';' +// caller has advanced mToken to point at the descriptor +bool +CSSParserImpl::ParseFontDescriptorValue(nsCSSFontDesc aDescID, + nsCSSValue& aValue) +{ + switch (aDescID) { + // These four are similar to the properties of the same name, + // possibly with more restrictions on the values they can take. + case eCSSFontDesc_Family: { + nsCSSValue value; + if (!ParseFamily(value) || + value.GetUnit() != eCSSUnit_FontFamilyList) + return false; + + // name can only be a single, non-generic name + const FontFamilyList* f = value.GetFontFamilyListValue(); + const nsTArray<FontFamilyName>& fontlist = f->GetFontlist(); + + if (fontlist.Length() != 1 || !fontlist[0].IsNamed()) { + return false; + } + + aValue.SetStringValue(fontlist[0].mName, eCSSUnit_String); + return true; + } + + case eCSSFontDesc_Style: + // property is VARIANT_HMK|VARIANT_SYSFONT + return ParseSingleTokenVariant(aValue, VARIANT_KEYWORD | VARIANT_NORMAL, + nsCSSProps::kFontStyleKTable); + + case eCSSFontDesc_Display: + return ParseSingleTokenVariant(aValue, VARIANT_KEYWORD, + nsCSSProps::kFontDisplayKTable); + + case eCSSFontDesc_Weight: + return (ParseFontWeight(aValue) && + aValue.GetUnit() != eCSSUnit_Inherit && + aValue.GetUnit() != eCSSUnit_Initial && + aValue.GetUnit() != eCSSUnit_Unset && + (aValue.GetUnit() != eCSSUnit_Enumerated || + (aValue.GetIntValue() != NS_STYLE_FONT_WEIGHT_BOLDER && + aValue.GetIntValue() != NS_STYLE_FONT_WEIGHT_LIGHTER))); + + case eCSSFontDesc_Stretch: + // property is VARIANT_HK|VARIANT_SYSFONT + return ParseSingleTokenVariant(aValue, VARIANT_KEYWORD, + nsCSSProps::kFontStretchKTable); + + // These two are unique to @font-face and have their own special grammar. + case eCSSFontDesc_Src: + return ParseFontSrc(aValue); + + case eCSSFontDesc_UnicodeRange: + return ParseFontRanges(aValue); + + case eCSSFontDesc_FontFeatureSettings: + return ParseFontFeatureSettings(aValue); + + case eCSSFontDesc_FontLanguageOverride: + return ParseSingleTokenVariant(aValue, VARIANT_NORMAL | VARIANT_STRING, + nullptr); + + case eCSSFontDesc_UNKNOWN: + case eCSSFontDesc_COUNT: + NS_NOTREACHED("bad nsCSSFontDesc code"); + } + // explicitly do NOT have a default case to let the compiler + // help find missing descriptors + return false; +} + +static nsCSSValue +BoxPositionMaskToCSSValue(int32_t aMask, bool isX) +{ + int32_t val = NS_STYLE_IMAGELAYER_POSITION_CENTER; + if (isX) { + if (aMask & BG_LEFT) { + val = NS_STYLE_IMAGELAYER_POSITION_LEFT; + } + else if (aMask & BG_RIGHT) { + val = NS_STYLE_IMAGELAYER_POSITION_RIGHT; + } + } + else { + if (aMask & BG_TOP) { + val = NS_STYLE_IMAGELAYER_POSITION_TOP; + } + else if (aMask & BG_BOTTOM) { + val = NS_STYLE_IMAGELAYER_POSITION_BOTTOM; + } + } + + return nsCSSValue(val, eCSSUnit_Enumerated); +} + +bool +CSSParserImpl::ParseImageLayers(const nsCSSPropertyID aTable[]) +{ + nsAutoParseCompoundProperty compound(this); + + // background-color can only be set once, so it's not a list. + nsCSSValue color; + + // Check first for inherit/initial/unset. + if (ParseSingleTokenVariant(color, VARIANT_INHERIT, nullptr)) { + // must be alone + for (const nsCSSPropertyID* subprops = + nsCSSProps::SubpropertyEntryFor(aTable[nsStyleImageLayers::shorthand]); + *subprops != eCSSProperty_UNKNOWN; ++subprops) { + AppendValue(*subprops, color); + } + return true; + } + + nsCSSValue image, repeat, attachment, clip, origin, positionX, positionY, size, + composite, maskMode; + ImageLayersShorthandParseState state(color, image.SetListValue(), + repeat.SetPairListValue(), + attachment.SetListValue(), clip.SetListValue(), + origin.SetListValue(), + positionX.SetListValue(), positionY.SetListValue(), + size.SetPairListValue(), composite.SetListValue(), + maskMode.SetListValue()); + + for (;;) { + if (!ParseImageLayersItem(state, aTable)) { + return false; + } + + // If we saw a color, this must be the last item. + if (color.GetUnit() != eCSSUnit_Null) { + MOZ_ASSERT(aTable[nsStyleImageLayers::color] != eCSSProperty_UNKNOWN); + break; + } + + // If there's a comma, expect another item. + if (!ExpectSymbol(',', true)) { + break; + } + +#define APPENDNEXT(propID_, propMember_, propType_) \ + if (aTable[propID_] != eCSSProperty_UNKNOWN) { \ + propMember_->mNext = new propType_; \ + propMember_ = propMember_->mNext; \ + } + // Chain another entry on all the lists. + APPENDNEXT(nsStyleImageLayers::image, state.mImage, + nsCSSValueList); + APPENDNEXT(nsStyleImageLayers::repeat, state.mRepeat, + nsCSSValuePairList); + APPENDNEXT(nsStyleImageLayers::clip, state.mClip, + nsCSSValueList); + APPENDNEXT(nsStyleImageLayers::origin, state.mOrigin, + nsCSSValueList); + APPENDNEXT(nsStyleImageLayers::positionX, state.mPositionX, + nsCSSValueList); + APPENDNEXT(nsStyleImageLayers::positionY, state.mPositionY, + nsCSSValueList); + APPENDNEXT(nsStyleImageLayers::size, state.mSize, + nsCSSValuePairList); + APPENDNEXT(nsStyleImageLayers::attachment, state.mAttachment, + nsCSSValueList); + APPENDNEXT(nsStyleImageLayers::maskMode, state.mMode, + nsCSSValueList); + APPENDNEXT(nsStyleImageLayers::composite, state.mComposite, + nsCSSValueList); +#undef APPENDNEXT + } + + // If we get to this point without seeing a color, provide a default. + if (aTable[nsStyleImageLayers::color] != eCSSProperty_UNKNOWN) { + if (color.GetUnit() == eCSSUnit_Null) { + color.SetIntegerColorValue(NS_RGBA(0,0,0,0), eCSSUnit_RGBAColor); + } + } + +#define APPENDVALUE(propID_, propValue_) \ + if (propID_ != eCSSProperty_UNKNOWN) { \ + AppendValue(propID_, propValue_); \ + } + + APPENDVALUE(aTable[nsStyleImageLayers::image], image); + APPENDVALUE(aTable[nsStyleImageLayers::repeat], repeat); + APPENDVALUE(aTable[nsStyleImageLayers::clip], clip); + APPENDVALUE(aTable[nsStyleImageLayers::origin], origin); + APPENDVALUE(aTable[nsStyleImageLayers::positionX], positionX); + APPENDVALUE(aTable[nsStyleImageLayers::positionY], positionY); + APPENDVALUE(aTable[nsStyleImageLayers::size], size); + APPENDVALUE(aTable[nsStyleImageLayers::color], color); + APPENDVALUE(aTable[nsStyleImageLayers::attachment], attachment); + APPENDVALUE(aTable[nsStyleImageLayers::maskMode], maskMode); + APPENDVALUE(aTable[nsStyleImageLayers::composite], composite); + +#undef APPENDVALUE + + return true; +} + +// Helper for ParseImageLayersItem. Returns true if the passed-in nsCSSToken is +// a function which is accepted for background-image. +bool +CSSParserImpl::IsFunctionTokenValidForImageLayerImage( + const nsCSSToken& aToken) const +{ + MOZ_ASSERT(aToken.mType == eCSSToken_Function, + "Should only be called for function-typed tokens"); + + const nsAString& funcName = aToken.mIdent; + + return funcName.LowerCaseEqualsLiteral("linear-gradient") || + funcName.LowerCaseEqualsLiteral("radial-gradient") || + funcName.LowerCaseEqualsLiteral("repeating-linear-gradient") || + funcName.LowerCaseEqualsLiteral("repeating-radial-gradient") || + funcName.LowerCaseEqualsLiteral("-moz-linear-gradient") || + funcName.LowerCaseEqualsLiteral("-moz-radial-gradient") || + funcName.LowerCaseEqualsLiteral("-moz-repeating-linear-gradient") || + funcName.LowerCaseEqualsLiteral("-moz-repeating-radial-gradient") || + funcName.LowerCaseEqualsLiteral("-moz-image-rect") || + funcName.LowerCaseEqualsLiteral("-moz-element") || + ((sWebkitPrefixedAliasesEnabled || ShouldUseUnprefixingService()) && + (funcName.LowerCaseEqualsLiteral("-webkit-gradient") || + funcName.LowerCaseEqualsLiteral("-webkit-linear-gradient") || + funcName.LowerCaseEqualsLiteral("-webkit-radial-gradient") || + funcName.LowerCaseEqualsLiteral("-webkit-repeating-linear-gradient") || + funcName.LowerCaseEqualsLiteral("-webkit-repeating-radial-gradient"))); +} + +// Parse one item of the background shorthand property. +bool +CSSParserImpl::ParseImageLayersItem( + CSSParserImpl::ImageLayersShorthandParseState& aState, + const nsCSSPropertyID aTable[]) +{ + // Fill in the values that the shorthand will set if we don't find + // other values. + aState.mImage->mValue.SetNoneValue(); + aState.mAttachment->mValue.SetIntValue(NS_STYLE_IMAGELAYER_ATTACHMENT_SCROLL, + eCSSUnit_Enumerated); + aState.mClip->mValue.SetIntValue(NS_STYLE_IMAGELAYER_CLIP_BORDER, + eCSSUnit_Enumerated); + + aState.mRepeat->mXValue.SetIntValue(NS_STYLE_IMAGELAYER_REPEAT_REPEAT, + eCSSUnit_Enumerated); + aState.mRepeat->mYValue.Reset(); + + RefPtr<nsCSSValue::Array> positionXArr = nsCSSValue::Array::Create(2); + RefPtr<nsCSSValue::Array> positionYArr = nsCSSValue::Array::Create(2); + aState.mPositionX->mValue.SetArrayValue(positionXArr, eCSSUnit_Array); + aState.mPositionY->mValue.SetArrayValue(positionYArr, eCSSUnit_Array); + + if (eCSSProperty_mask == aTable[nsStyleImageLayers::shorthand]) { + aState.mOrigin->mValue.SetIntValue(NS_STYLE_IMAGELAYER_ORIGIN_BORDER, + eCSSUnit_Enumerated); + } else { + aState.mOrigin->mValue.SetIntValue(NS_STYLE_IMAGELAYER_ORIGIN_PADDING, + eCSSUnit_Enumerated); + } + positionXArr->Item(1).SetPercentValue(0.0f); + positionYArr->Item(1).SetPercentValue(0.0f); + + aState.mSize->mXValue.SetAutoValue(); + aState.mSize->mYValue.SetAutoValue(); + aState.mComposite->mValue.SetIntValue(NS_STYLE_MASK_COMPOSITE_ADD, + eCSSUnit_Enumerated); + aState.mMode->mValue.SetIntValue(NS_STYLE_MASK_MODE_MATCH_SOURCE, + eCSSUnit_Enumerated); + bool haveColor = false, + haveImage = false, + haveRepeat = false, + haveAttach = false, + havePositionAndSize = false, + haveOrigin = false, + haveComposite = false, + haveMode = false, + haveSomething = false; + + while (GetToken(true)) { + nsCSSTokenType tt = mToken.mType; + UngetToken(); // ...but we'll still cheat and use mToken + if (tt == eCSSToken_Symbol) { + // ExpectEndProperty only looks for symbols, and nothing else will + // show up as one. + break; + } + + if (tt == eCSSToken_Ident) { + nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent); + int32_t dummy; + if (keyword == eCSSKeyword_inherit || + keyword == eCSSKeyword_initial || + keyword == eCSSKeyword_unset) { + return false; + } else if (keyword == eCSSKeyword_none) { + if (haveImage) + return false; + haveImage = true; + if (ParseSingleValueProperty(aState.mImage->mValue, + aTable[nsStyleImageLayers::image]) != + CSSParseResult::Ok) { + NS_NOTREACHED("should be able to parse"); + return false; + } + } else if (aTable[nsStyleImageLayers::attachment] != + eCSSProperty_UNKNOWN && + nsCSSProps::FindKeyword(keyword, + nsCSSProps::kImageLayerAttachmentKTable, dummy)) { + if (haveAttach) + return false; + haveAttach = true; + if (ParseSingleValueProperty(aState.mAttachment->mValue, + aTable[nsStyleImageLayers::attachment]) != + CSSParseResult::Ok) { + NS_NOTREACHED("should be able to parse"); + return false; + } + } else if (nsCSSProps::FindKeyword(keyword, + nsCSSProps::kImageLayerRepeatKTable, dummy)) { + if (haveRepeat) + return false; + haveRepeat = true; + nsCSSValuePair scratch; + if (!ParseImageLayerRepeatValues(scratch)) { + NS_NOTREACHED("should be able to parse"); + return false; + } + aState.mRepeat->mXValue = scratch.mXValue; + aState.mRepeat->mYValue = scratch.mYValue; + } else if (nsCSSProps::FindKeyword(keyword, + nsCSSProps::kImageLayerPositionKTable, dummy)) { + if (havePositionAndSize) + return false; + havePositionAndSize = true; + + if (!ParsePositionValueSeparateCoords(aState.mPositionX->mValue, + aState.mPositionY->mValue)) { + return false; + } + if (ExpectSymbol('/', true)) { + nsCSSValuePair scratch; + if (!ParseImageLayerSizeValues(scratch)) { + return false; + } + aState.mSize->mXValue = scratch.mXValue; + aState.mSize->mYValue = scratch.mYValue; + } + } else if (nsCSSProps::FindKeyword(keyword, + nsCSSProps::kImageLayerOriginKTable, dummy)) { + if (haveOrigin) + return false; + haveOrigin = true; + if (ParseSingleValueProperty(aState.mOrigin->mValue, + aTable[nsStyleImageLayers::origin]) != + CSSParseResult::Ok) { + NS_NOTREACHED("should be able to parse"); + return false; + } + + // The spec allows a second box value (for background-clip), + // immediately following the first one (for background-origin). + +#ifdef DEBUG + for (size_t i = 0; nsCSSProps::kImageLayerOriginKTable[i].mValue != -1; i++) { + // For each keyword & value in kOriginKTable, ensure that + // kBackgroundKTable has a matching entry at the same position. + MOZ_ASSERT(nsCSSProps::kImageLayerOriginKTable[i].mKeyword == + nsCSSProps::kBackgroundClipKTable[i].mKeyword); + MOZ_ASSERT(nsCSSProps::kImageLayerOriginKTable[i].mValue == + nsCSSProps::kBackgroundClipKTable[i].mValue); + } +#endif + static_assert(NS_STYLE_IMAGELAYER_CLIP_BORDER == + NS_STYLE_IMAGELAYER_ORIGIN_BORDER && + NS_STYLE_IMAGELAYER_CLIP_PADDING == + NS_STYLE_IMAGELAYER_ORIGIN_PADDING && + NS_STYLE_IMAGELAYER_CLIP_CONTENT == + NS_STYLE_IMAGELAYER_ORIGIN_CONTENT, + "bg-clip and bg-origin style constants must agree"); + + CSSParseResult result = + ParseSingleValueProperty(aState.mClip->mValue, + aTable[nsStyleImageLayers::clip]); + MOZ_ASSERT(result != CSSParseResult::Error, + "how can failing to parse a single background-clip value " + "consume tokens?"); + if (result == CSSParseResult::NotFound) { + // When exactly one <box> value is set, it is used for both + // 'background-origin' and 'background-clip'. + // See assertions above showing these values are compatible. + aState.mClip->mValue = aState.mOrigin->mValue; + } + } else if (aTable[nsStyleImageLayers::composite] != eCSSProperty_UNKNOWN && + nsCSSProps::FindKeyword(keyword, + nsCSSProps::kImageLayerCompositeKTable, dummy)) { + if (haveComposite) + return false; + haveComposite = true; + if (ParseSingleValueProperty(aState.mComposite->mValue, + aTable[nsStyleImageLayers::composite]) != + CSSParseResult::Ok) { + NS_NOTREACHED("should be able to parse"); + return false; + } + } else if (aTable[nsStyleImageLayers::maskMode] != eCSSProperty_UNKNOWN && + nsCSSProps::FindKeyword(keyword, + nsCSSProps::kImageLayerModeKTable, dummy)) { + if (haveMode) + return false; + haveMode = true; + if (ParseSingleValueProperty(aState.mMode->mValue, + aTable[nsStyleImageLayers::maskMode]) != + CSSParseResult::Ok) { + NS_NOTREACHED("should be able to parse"); + return false; + } + } else if (aTable[nsStyleImageLayers::color] != eCSSProperty_UNKNOWN) { + if (haveColor) + return false; + haveColor = true; + if (ParseSingleValueProperty(aState.mColor, + aTable[nsStyleImageLayers::color]) != + CSSParseResult::Ok) { + return false; + } + } else { + return false; + } + } else if (tt == eCSSToken_URL || + (tt == eCSSToken_Function && + IsFunctionTokenValidForImageLayerImage(mToken))) { + if (haveImage) + return false; + haveImage = true; + if (ParseSingleValueProperty(aState.mImage->mValue, + aTable[nsStyleImageLayers::image]) != + CSSParseResult::Ok) { + return false; + } + } else if (tt == eCSSToken_Dimension || + tt == eCSSToken_Number || + tt == eCSSToken_Percentage || + (tt == eCSSToken_Function && + (mToken.mIdent.LowerCaseEqualsLiteral("calc") || + mToken.mIdent.LowerCaseEqualsLiteral("-moz-calc")))) { + if (havePositionAndSize) + return false; + havePositionAndSize = true; + if (!ParsePositionValueSeparateCoords(aState.mPositionX->mValue, + aState.mPositionY->mValue)) { + return false; + } + if (ExpectSymbol('/', true)) { + nsCSSValuePair scratch; + if (!ParseImageLayerSizeValues(scratch)) { + return false; + } + aState.mSize->mXValue = scratch.mXValue; + aState.mSize->mYValue = scratch.mYValue; + } + } else if (aTable[nsStyleImageLayers::color] != eCSSProperty_UNKNOWN) { + if (haveColor) + return false; + haveColor = true; + // Note: This parses 'inherit', 'initial' and 'unset', but + // we've already checked for them, so it's ok. + if (ParseSingleValueProperty(aState.mColor, + aTable[nsStyleImageLayers::color]) != + CSSParseResult::Ok) { + return false; + } + } else { + return false; + } + + haveSomething = true; + } + + return haveSomething; +} + +// This function is very similar to ParseScrollSnapCoordinate, +// ParseImageLayerPosition, and ParseImageLayersSize. +bool +CSSParserImpl::ParseValueList(nsCSSPropertyID aPropID) +{ + // aPropID is a single value prop-id + nsCSSValue value; + // 'initial', 'inherit' and 'unset' stand alone, no list permitted. + if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { + nsCSSValueList* item = value.SetListValue(); + for (;;) { + if (ParseSingleValueProperty(item->mValue, aPropID) != + CSSParseResult::Ok) { + return false; + } + if (!ExpectSymbol(',', true)) { + break; + } + item->mNext = new nsCSSValueList; + item = item->mNext; + } + } + AppendValue(aPropID, value); + return true; +} + +bool +CSSParserImpl::ParseImageLayerRepeat(nsCSSPropertyID aPropID) +{ + nsCSSValue value; + // 'initial', 'inherit' and 'unset' stand alone, no list permitted. + if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { + nsCSSValuePair valuePair; + if (!ParseImageLayerRepeatValues(valuePair)) { + return false; + } + nsCSSValuePairList* item = value.SetPairListValue(); + for (;;) { + item->mXValue = valuePair.mXValue; + item->mYValue = valuePair.mYValue; + if (!ExpectSymbol(',', true)) { + break; + } + if (!ParseImageLayerRepeatValues(valuePair)) { + return false; + } + item->mNext = new nsCSSValuePairList; + item = item->mNext; + } + } + + AppendValue(aPropID, value); + return true; +} + +bool +CSSParserImpl::ParseImageLayerRepeatValues(nsCSSValuePair& aValue) +{ + nsCSSValue& xValue = aValue.mXValue; + nsCSSValue& yValue = aValue.mYValue; + + if (ParseEnum(xValue, nsCSSProps::kImageLayerRepeatKTable)) { + int32_t value = xValue.GetIntValue(); + // For single values set yValue as eCSSUnit_Null. + if (value == NS_STYLE_IMAGELAYER_REPEAT_REPEAT_X || + value == NS_STYLE_IMAGELAYER_REPEAT_REPEAT_Y || + !ParseEnum(yValue, nsCSSProps::kImageLayerRepeatPartKTable)) { + // the caller will fail cases like "repeat-x no-repeat" + // by expecting a list separator or an end property. + yValue.Reset(); + } + return true; + } + + return false; +} + +bool +CSSParserImpl::ParseImageLayerPosition(const nsCSSPropertyID aTable[]) +{ + // 'initial', 'inherit' and 'unset' stand alone, no list permitted. + nsCSSValue position; + if (ParseSingleTokenVariant(position, VARIANT_INHERIT, nullptr)) { + AppendValue(aTable[nsStyleImageLayers::positionX], position); + AppendValue(aTable[nsStyleImageLayers::positionY], position); + return true; + } + + nsCSSValue itemValueX; + nsCSSValue itemValueY; + if (!ParsePositionValueSeparateCoords(itemValueX, itemValueY)) { + return false; + } + + nsCSSValue valueX; + nsCSSValue valueY; + nsCSSValueList* itemX = valueX.SetListValue(); + nsCSSValueList* itemY = valueY.SetListValue(); + for (;;) { + itemX->mValue = itemValueX; + itemY->mValue = itemValueY; + if (!ExpectSymbol(',', true)) { + break; + } + if (!ParsePositionValueSeparateCoords(itemValueX, itemValueY)) { + return false; + } + itemX->mNext = new nsCSSValueList; + itemY->mNext = new nsCSSValueList; + itemX = itemX->mNext; + itemY = itemY->mNext; + } + AppendValue(aTable[nsStyleImageLayers::positionX], valueX); + AppendValue(aTable[nsStyleImageLayers::positionY], valueY); + return true; +} + +bool +CSSParserImpl::ParseImageLayerPositionCoord(nsCSSPropertyID aPropID, bool aIsHorizontal) +{ + nsCSSValue value; + // 'initial', 'inherit' and 'unset' stand alone, no list permitted. + if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { + nsCSSValue itemValue; + if (!ParseImageLayerPositionCoordItem(itemValue, aIsHorizontal)) { + return false; + } + nsCSSValueList* item = value.SetListValue(); + for (;;) { + item->mValue = itemValue; + if (!ExpectSymbol(',', true)) { + break; + } + if (!ParseImageLayerPositionCoordItem(itemValue, aIsHorizontal)) { + return false; + } + item->mNext = new nsCSSValueList; + item = item->mNext; + } + } + AppendValue(aPropID, value); + return true; +} + +/** + * BoxPositionMaskToCSSValue and ParseBoxPositionValues are used + * for parsing the CSS 2.1 background-position syntax (which has at + * most two values). (Compare to the css3-background syntax which + * takes up to four values.) Some current CSS specifications that + * use background-position-like syntax still use this old syntax. + ** + * Parses two values that correspond to positions in a box. These can be + * values corresponding to percentages of the box, raw offsets, or keywords + * like "top," "left center," etc. + * + * @param aOut The nsCSSValuePair in which to place the result. + * @param aAcceptsInherit If true, 'inherit', 'initial' and 'unset' are + * legal values + * @param aAllowExplicitCenter If true, 'center' is a legal value + * @return Whether or not the operation succeeded. + */ +bool CSSParserImpl::ParseBoxPositionValues(nsCSSValuePair &aOut, + bool aAcceptsInherit, + bool aAllowExplicitCenter) +{ + // First try a percentage or a length value + nsCSSValue &xValue = aOut.mXValue, + &yValue = aOut.mYValue; + int32_t variantMask = + (aAcceptsInherit ? VARIANT_INHERIT : 0) | VARIANT_LP | VARIANT_CALC; + CSSParseResult result = ParseVariant(xValue, variantMask, nullptr); + if (result == CSSParseResult::Error) { + return false; + } else if (result == CSSParseResult::Ok) { + if (eCSSUnit_Inherit == xValue.GetUnit() || + eCSSUnit_Initial == xValue.GetUnit() || + eCSSUnit_Unset == xValue.GetUnit()) { // both are inherit, initial or unset + yValue = xValue; + return true; + } + // We have one percentage/length/calc. Get the optional second + // percentage/length/calc/keyword. + result = ParseVariant(yValue, VARIANT_LP | VARIANT_CALC, nullptr); + if (result == CSSParseResult::Error) { + return false; + } else if (result == CSSParseResult::Ok) { + // We have two numbers + return true; + } + + if (ParseEnum(yValue, nsCSSProps::kImageLayerPositionKTable)) { + int32_t yVal = yValue.GetIntValue(); + if (!(yVal & BG_CTB)) { + // The second keyword can only be 'center', 'top', or 'bottom' + return false; + } + yValue = BoxPositionMaskToCSSValue(yVal, false); + return true; + } + + // If only one percentage or length value is given, it sets the + // horizontal position only, and the vertical position will be 50%. + yValue.SetPercentValue(0.5f); + return true; + } + + // Now try keywords. We do this manually to allow for the first + // appearance of "center" to apply to the either the x or y + // position (it's ambiguous so we have to disambiguate). Each + // allowed keyword value is assigned it's own bit. We don't allow + // any duplicate keywords other than center. We try to get two + // keywords but it's okay if there is only one. + int32_t mask = 0; + if (ParseEnum(xValue, nsCSSProps::kImageLayerPositionKTable)) { + int32_t bit = xValue.GetIntValue(); + mask |= bit; + if (ParseEnum(xValue, nsCSSProps::kImageLayerPositionKTable)) { + bit = xValue.GetIntValue(); + if (mask & (bit & ~BG_CENTER)) { + // Only the 'center' keyword can be duplicated. + return false; + } + mask |= bit; + } + else { + // Only one keyword. See if we have a length, percentage, or calc. + result = ParseVariant(yValue, VARIANT_LP | VARIANT_CALC, nullptr); + if (result == CSSParseResult::Error) { + return false; + } else if (result == CSSParseResult::Ok) { + if (!(mask & BG_CLR)) { + // The first keyword can only be 'center', 'left', or 'right' + return false; + } + + xValue = BoxPositionMaskToCSSValue(mask, true); + return true; + } + } + } + + // Check for bad input. Bad input consists of no matching keywords, + // or pairs of x keywords or pairs of y keywords. + if ((mask == 0) || (mask == (BG_TOP | BG_BOTTOM)) || + (mask == (BG_LEFT | BG_RIGHT)) || + (!aAllowExplicitCenter && (mask & BG_CENTER))) { + return false; + } + + // Create style values + xValue = BoxPositionMaskToCSSValue(mask, true); + yValue = BoxPositionMaskToCSSValue(mask, false); + return true; +} + +// Parses a CSS <position> value, for e.g. the 'background-position' property. +// Spec reference: http://www.w3.org/TR/css3-background/#ltpositiongt +// Invariants: +// - Always produces a four-value array on a successful parse. +// - The values are: X edge, X offset, Y edge, Y offset. +// - Edges are always keywords or null. +// - A |center| edge will not have an offset. +bool +CSSParserImpl::ParsePositionValue(nsCSSValue& aOut) +{ + RefPtr<nsCSSValue::Array> value = nsCSSValue::Array::Create(4); + aOut.SetArrayValue(value, eCSSUnit_Array); + + // The following clarifies organisation of the array. + nsCSSValue &xEdge = value->Item(0), + &xOffset = value->Item(1), + &yEdge = value->Item(2), + &yOffset = value->Item(3); + + // Parse all the values into the array. + uint32_t valueCount = 0; + for (int32_t i = 0; i < 4; i++) { + CSSParseResult result = + ParseVariant(value->Item(i), VARIANT_LPCALC | VARIANT_KEYWORD, + nsCSSProps::kImageLayerPositionKTable); + if (result == CSSParseResult::Error) { + return false; + } else if (result == CSSParseResult::NotFound) { + break; + } + ++valueCount; + } + + switch (valueCount) { + case 4: + // "If three or four values are given, then each <percentage> or <length> + // represents an offset and must be preceded by a keyword, which specifies + // from which edge the offset is given." + if (eCSSUnit_Enumerated != xEdge.GetUnit() || + BG_CENTER == xEdge.GetIntValue() || + eCSSUnit_Enumerated == xOffset.GetUnit() || + eCSSUnit_Enumerated != yEdge.GetUnit() || + BG_CENTER == yEdge.GetIntValue() || + eCSSUnit_Enumerated == yOffset.GetUnit()) { + return false; + } + break; + case 3: + // "If three or four values are given, then each <percentage> or<length> + // represents an offset and must be preceded by a keyword, which specifies + // from which edge the offset is given." ... "If three values are given, + // the missing offset is assumed to be zero." + if (eCSSUnit_Enumerated != value->Item(1).GetUnit()) { + // keyword offset keyword + // Second value is non-keyword, thus first value must be a non-center + // keyword. + if (eCSSUnit_Enumerated != value->Item(0).GetUnit() || + BG_CENTER == value->Item(0).GetIntValue()) { + return false; + } + + // Remaining value must be a keyword. + if (eCSSUnit_Enumerated != value->Item(2).GetUnit()) { + return false; + } + + yOffset.Reset(); // Everything else is in the correct position. + } else if (eCSSUnit_Enumerated != value->Item(2).GetUnit()) { + // keyword keyword offset + // Third value is non-keyword, thus second value must be non-center + // keyword. + if (BG_CENTER == value->Item(1).GetIntValue()) { + return false; + } + + // Remaining value must be a keyword. + if (eCSSUnit_Enumerated != value->Item(0).GetUnit()) { + return false; + } + + // Move the values to the correct position in the array. + value->Item(3) = value->Item(2); // yOffset + value->Item(2) = value->Item(1); // yEdge + value->Item(1).Reset(); // xOffset + } else { + return false; + } + break; + case 2: + // "If two values are given and at least one value is not a keyword, then + // the first value represents the horizontal position (or offset) and the + // second represents the vertical position (or offset)" + if (eCSSUnit_Enumerated == value->Item(0).GetUnit()) { + if (eCSSUnit_Enumerated == value->Item(1).GetUnit()) { + // keyword keyword + value->Item(2) = value->Item(1); // move yEdge to correct position + xOffset.Reset(); + yOffset.Reset(); + } else { + // keyword offset + // First value must represent horizontal position. + if ((BG_TOP | BG_BOTTOM) & value->Item(0).GetIntValue()) { + return false; + } + value->Item(3) = value->Item(1); // move yOffset to correct position + xOffset.Reset(); + yEdge.Reset(); + } + } else { + if (eCSSUnit_Enumerated == value->Item(1).GetUnit()) { + // offset keyword + // Second value must represent vertical position. + if ((BG_LEFT | BG_RIGHT) & value->Item(1).GetIntValue()) { + return false; + } + value->Item(2) = value->Item(1); // move yEdge to correct position + value->Item(1) = value->Item(0); // move xOffset to correct position + xEdge.Reset(); + yOffset.Reset(); + } else { + // offset offset + value->Item(3) = value->Item(1); // move yOffset to correct position + value->Item(1) = value->Item(0); // move xOffset to correct position + xEdge.Reset(); + yEdge.Reset(); + } + } + break; + case 1: + // "If only one value is specified, the second value is assumed to be + // center." + if (eCSSUnit_Enumerated == value->Item(0).GetUnit()) { + xOffset.Reset(); + } else { + value->Item(1) = value->Item(0); // move xOffset to correct position + xEdge.Reset(); + } + yEdge.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_CENTER, eCSSUnit_Enumerated); + yOffset.Reset(); + break; + default: + return false; + } + + // For compatibility with CSS2.1 code the edges can be unspecified. + // Unspecified edges are recorded as nullptr. + NS_ASSERTION((eCSSUnit_Enumerated == xEdge.GetUnit() || + eCSSUnit_Null == xEdge.GetUnit()) && + (eCSSUnit_Enumerated == yEdge.GetUnit() || + eCSSUnit_Null == yEdge.GetUnit()) && + eCSSUnit_Enumerated != xOffset.GetUnit() && + eCSSUnit_Enumerated != yOffset.GetUnit(), + "Unexpected units"); + + // Keywords in first and second pairs can not both be vertical or + // horizontal keywords. (eg. left right, bottom top). Additionally, + // non-center keyword can not be duplicated (eg. left left). + int32_t xEdgeEnum = + xEdge.GetUnit() == eCSSUnit_Enumerated ? xEdge.GetIntValue() : 0; + int32_t yEdgeEnum = + yEdge.GetUnit() == eCSSUnit_Enumerated ? yEdge.GetIntValue() : 0; + if ((xEdgeEnum | yEdgeEnum) == (BG_LEFT | BG_RIGHT) || + (xEdgeEnum | yEdgeEnum) == (BG_TOP | BG_BOTTOM) || + (xEdgeEnum & yEdgeEnum & ~BG_CENTER)) { + return false; + } + + // The values could be in an order that is different than expected. + // eg. x contains vertical information, y contains horizontal information. + // Swap if incorrect order. + if (xEdgeEnum & (BG_TOP | BG_BOTTOM) || + yEdgeEnum & (BG_LEFT | BG_RIGHT)) { + nsCSSValue swapEdge = xEdge; + nsCSSValue swapOffset = xOffset; + xEdge = yEdge; + xOffset = yOffset; + yEdge = swapEdge; + yOffset = swapOffset; + } + + return true; +} + +static void +AdjustEdgeOffsetPairForBasicShape(nsCSSValue& aEdge, + nsCSSValue& aOffset, + uint8_t aDefaultEdge) +{ + // 0 length offsets are 0% + if (aOffset.IsLengthUnit() && aOffset.GetFloatValue() == 0.0) { + aOffset.SetPercentValue(0); + } + + // Default edge is top/left in the 4-value case + // In case of 1 or 0 values, the default is center, + // but ParsePositionValue already handles this case + if (eCSSUnit_Null == aEdge.GetUnit()) { + aEdge.SetIntValue(aDefaultEdge, eCSSUnit_Enumerated); + } + // Default offset is 0% + if (eCSSUnit_Null == aOffset.GetUnit()) { + aOffset.SetPercentValue(0.0); + } + if (eCSSUnit_Enumerated == aEdge.GetUnit() && + eCSSUnit_Percent == aOffset.GetUnit()) { + switch (aEdge.GetIntValue()) { + case NS_STYLE_IMAGELAYER_POSITION_CENTER: + aEdge.SetIntValue(aDefaultEdge, eCSSUnit_Enumerated); + MOZ_ASSERT(aOffset.GetPercentValue() == 0.0, + "center cannot be used with an offset"); + aOffset.SetPercentValue(0.5); + break; + case NS_STYLE_IMAGELAYER_POSITION_BOTTOM: + MOZ_ASSERT(aDefaultEdge == NS_STYLE_IMAGELAYER_POSITION_TOP); + aEdge.SetIntValue(aDefaultEdge, eCSSUnit_Enumerated); + aOffset.SetPercentValue(1 - aOffset.GetPercentValue()); + break; + case NS_STYLE_IMAGELAYER_POSITION_RIGHT: + MOZ_ASSERT(aDefaultEdge == NS_STYLE_IMAGELAYER_POSITION_LEFT); + aEdge.SetIntValue(aDefaultEdge, eCSSUnit_Enumerated); + aOffset.SetPercentValue(1 - aOffset.GetPercentValue()); + } + } +} + +// https://drafts.csswg.org/css-shapes/#basic-shape-serialization +// We set values to defaults while parsing for basic shapes +// Invariants: +// - Always produces a four-value array on a successful parse. +// - The values are: X edge, X offset, Y edge, Y offset +// - Edges are always keywords (not including center) +// - Offsets are nonnull +// - Percentage offsets have keywords folded into them, +// so "bottom 40%" or "right 20%" will not exist. +bool +CSSParserImpl::ParsePositionValueForBasicShape(nsCSSValue& aOut) +{ + if (!ParsePositionValue(aOut)) { + return false; + } + nsCSSValue::Array* value = aOut.GetArrayValue(); + nsCSSValue& xEdge = value->Item(0); + nsCSSValue& xOffset = value->Item(1); + nsCSSValue& yEdge = value->Item(2); + nsCSSValue& yOffset = value->Item(3); + // A keyword edge + percent offset pair can be contracted + // into the percentage with the default value in the edge. + // Offset lengths which are 0 can also be rewritten as 0% + AdjustEdgeOffsetPairForBasicShape(xEdge, xOffset, + NS_STYLE_IMAGELAYER_POSITION_LEFT); + AdjustEdgeOffsetPairForBasicShape(yEdge, yOffset, + NS_STYLE_IMAGELAYER_POSITION_TOP); + return true; +} + +bool +CSSParserImpl::ParsePositionValueSeparateCoords(nsCSSValue& aOutX, nsCSSValue& aOutY) +{ + nsCSSValue scratch; + if (!ParsePositionValue(scratch)) { + return false; + } + + // Separate the four values into two pairs of two values for X and Y. + RefPtr<nsCSSValue::Array> valueX = nsCSSValue::Array::Create(2); + RefPtr<nsCSSValue::Array> valueY = nsCSSValue::Array::Create(2); + aOutX.SetArrayValue(valueX, eCSSUnit_Array); + aOutY.SetArrayValue(valueY, eCSSUnit_Array); + + RefPtr<nsCSSValue::Array> value = scratch.GetArrayValue(); + valueX->Item(0) = value->Item(0); + valueX->Item(1) = value->Item(1); + valueY->Item(0) = value->Item(2); + valueY->Item(1) = value->Item(3); + return true; +} + +// Parses one item in a list of values for the 'background-position-x' or +// 'background-position-y' property. Does not support the start/end keywords. +// Spec reference: https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-x +bool +CSSParserImpl::ParseImageLayerPositionCoordItem(nsCSSValue& aOut, bool aIsHorizontal) +{ + RefPtr<nsCSSValue::Array> value = nsCSSValue::Array::Create(2); + aOut.SetArrayValue(value, eCSSUnit_Array); + + nsCSSValue &edge = value->Item(0), + &offset = value->Item(1); + + nsCSSValue edgeOrOffset; + CSSParseResult result = + ParseVariant(edgeOrOffset, VARIANT_LPCALC | VARIANT_KEYWORD, + nsCSSProps::kImageLayerPositionKTable); + if (result != CSSParseResult::Ok) { + return false; + } + + if (edgeOrOffset.GetUnit() == eCSSUnit_Enumerated) { + edge = edgeOrOffset; + + // The edge can be followed by an optional offset. + result = ParseVariant(offset, VARIANT_LPCALC, nullptr); + if (result == CSSParseResult::Error) { + return false; + } + } else { + offset = edgeOrOffset; + } + + // Keywords for horizontal properties cannot be vertical keywords, and + // keywords for vertical properties cannot be horizontal keywords. + // Also, if an offset is specified, the edge cannot be center. + int32_t edgeEnum = + edge.GetUnit() == eCSSUnit_Enumerated ? edge.GetIntValue() : 0; + int32_t allowedKeywords = + (aIsHorizontal ? (BG_LEFT | BG_RIGHT) : (BG_TOP | BG_BOTTOM)) | + (offset.GetUnit() == eCSSUnit_Null ? BG_CENTER : 0); + if (edgeEnum & ~allowedKeywords) { + return false; + } + + NS_ASSERTION((eCSSUnit_Enumerated == edge.GetUnit() || + eCSSUnit_Null == edge.GetUnit()) && + eCSSUnit_Enumerated != offset.GetUnit(), + "Unexpected units"); + + return true; +} + +// This function is very similar to ParseScrollSnapCoordinate, +// ParseImageLayers, and ParseImageLayerPosition. +bool +CSSParserImpl::ParseImageLayerSize(nsCSSPropertyID aPropID) +{ + nsCSSValue value; + // 'initial', 'inherit' and 'unset' stand alone, no list permitted. + if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { + nsCSSValuePair valuePair; + if (!ParseImageLayerSizeValues(valuePair)) { + return false; + } + nsCSSValuePairList* item = value.SetPairListValue(); + for (;;) { + item->mXValue = valuePair.mXValue; + item->mYValue = valuePair.mYValue; + if (!ExpectSymbol(',', true)) { + break; + } + if (!ParseImageLayerSizeValues(valuePair)) { + return false; + } + item->mNext = new nsCSSValuePairList; + item = item->mNext; + } + } + AppendValue(aPropID, value); + return true; +} + +/** + * Parses two values that correspond to lengths for the background-size + * property. These can be one or two lengths (or the 'auto' keyword) or + * percentages corresponding to the element's dimensions or the single keywords + * 'contain' or 'cover'. 'initial', 'inherit' and 'unset' must be handled by + * the caller if desired. + * + * @param aOut The nsCSSValuePair in which to place the result. + * @return Whether or not the operation succeeded. + */ +#define BG_SIZE_VARIANT (VARIANT_LP | VARIANT_AUTO | VARIANT_CALC) +bool CSSParserImpl::ParseImageLayerSizeValues(nsCSSValuePair &aOut) +{ + // First try a percentage or a length value + nsCSSValue &xValue = aOut.mXValue, + &yValue = aOut.mYValue; + CSSParseResult result = + ParseNonNegativeVariant(xValue, BG_SIZE_VARIANT, nullptr); + if (result == CSSParseResult::Error) { + return false; + } else if (result == CSSParseResult::Ok) { + // We have one percentage/length/calc/auto. Get the optional second + // percentage/length/calc/keyword. + result = ParseNonNegativeVariant(yValue, BG_SIZE_VARIANT, nullptr); + if (result == CSSParseResult::Error) { + return false; + } else if (result == CSSParseResult::Ok) { + // We have a second percentage/length/calc/auto. + return true; + } + + // If only one percentage or length value is given, it sets the + // horizontal size only, and the vertical size will be as if by 'auto'. + yValue.SetAutoValue(); + return true; + } + + // Now address 'contain' and 'cover'. + if (!ParseEnum(xValue, nsCSSProps::kImageLayerSizeKTable)) + return false; + yValue.Reset(); + return true; +} + +#undef BG_SIZE_VARIANT + +bool +CSSParserImpl::ParseBorderColor() +{ + return ParseBoxProperties(kBorderColorIDs); +} + +void +CSSParserImpl::SetBorderImageInitialValues() +{ + // border-image-source: none + nsCSSValue source; + source.SetNoneValue(); + AppendValue(eCSSProperty_border_image_source, source); + + // border-image-slice: 100% + nsCSSValue sliceBoxValue; + nsCSSRect& sliceBox = sliceBoxValue.SetRectValue(); + sliceBox.SetAllSidesTo(nsCSSValue(1.0f, eCSSUnit_Percent)); + nsCSSValue slice; + nsCSSValueList* sliceList = slice.SetListValue(); + sliceList->mValue = sliceBoxValue; + AppendValue(eCSSProperty_border_image_slice, slice); + + // border-image-width: 1 + nsCSSValue width; + nsCSSRect& widthBox = width.SetRectValue(); + widthBox.SetAllSidesTo(nsCSSValue(1.0f, eCSSUnit_Number)); + AppendValue(eCSSProperty_border_image_width, width); + + // border-image-outset: 0 + nsCSSValue outset; + nsCSSRect& outsetBox = outset.SetRectValue(); + outsetBox.SetAllSidesTo(nsCSSValue(0.0f, eCSSUnit_Number)); + AppendValue(eCSSProperty_border_image_outset, outset); + + // border-image-repeat: repeat + nsCSSValue repeat; + nsCSSValuePair repeatPair; + repeatPair.SetBothValuesTo(nsCSSValue(NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH, + eCSSUnit_Enumerated)); + repeat.SetPairValue(&repeatPair); + AppendValue(eCSSProperty_border_image_repeat, repeat); +} + +bool +CSSParserImpl::ParseBorderImageSlice(bool aAcceptsInherit, + bool* aConsumedTokens) +{ + // border-image-slice: initial | [<number>|<percentage>]{1,4} && fill? + nsCSSValue value; + + if (aConsumedTokens) { + *aConsumedTokens = true; + } + + if (aAcceptsInherit && + ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { + // Keywords "inherit", "initial" and "unset" can not be mixed, so we + // are done. + AppendValue(eCSSProperty_border_image_slice, value); + return true; + } + + // Try parsing "fill" value. + nsCSSValue imageSliceFillValue; + bool hasFill = ParseEnum(imageSliceFillValue, + nsCSSProps::kBorderImageSliceKTable); + + // Parse the box dimensions. + nsCSSValue imageSliceBoxValue; + if (!ParseGroupedBoxProperty(VARIANT_PN, imageSliceBoxValue, + CSS_PROPERTY_VALUE_NONNEGATIVE)) { + if (!hasFill && aConsumedTokens) { + *aConsumedTokens = false; + } + + return false; + } + + // Try parsing "fill" keyword again if the first time failed because keyword + // and slice dimensions can be in any order. + if (!hasFill) { + hasFill = ParseEnum(imageSliceFillValue, + nsCSSProps::kBorderImageSliceKTable); + } + + nsCSSValueList* borderImageSlice = value.SetListValue(); + // Put the box value into the list. + borderImageSlice->mValue = imageSliceBoxValue; + + if (hasFill) { + // Put the "fill" value into the list. + borderImageSlice->mNext = new nsCSSValueList; + borderImageSlice->mNext->mValue = imageSliceFillValue; + } + + AppendValue(eCSSProperty_border_image_slice, value); + return true; +} + +bool +CSSParserImpl::ParseBorderImageWidth(bool aAcceptsInherit) +{ + // border-image-width: initial | [<length>|<number>|<percentage>|auto]{1,4} + nsCSSValue value; + + if (aAcceptsInherit && + ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { + // Keywords "inherit", "initial" and "unset" can not be mixed, so we + // are done. + AppendValue(eCSSProperty_border_image_width, value); + return true; + } + + // Parse the box dimensions. + if (!ParseGroupedBoxProperty(VARIANT_ALPN, value, CSS_PROPERTY_VALUE_NONNEGATIVE)) { + return false; + } + + AppendValue(eCSSProperty_border_image_width, value); + return true; +} + +bool +CSSParserImpl::ParseBorderImageOutset(bool aAcceptsInherit) +{ + // border-image-outset: initial | [<length>|<number>]{1,4} + nsCSSValue value; + + if (aAcceptsInherit && + ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { + // Keywords "inherit", "initial" and "unset" can not be mixed, so we + // are done. + AppendValue(eCSSProperty_border_image_outset, value); + return true; + } + + // Parse the box dimensions. + if (!ParseGroupedBoxProperty(VARIANT_LN, value, CSS_PROPERTY_VALUE_NONNEGATIVE)) { + return false; + } + + AppendValue(eCSSProperty_border_image_outset, value); + return true; +} + +bool +CSSParserImpl::ParseBorderImageRepeat(bool aAcceptsInherit) +{ + nsCSSValue value; + if (aAcceptsInherit && + ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { + // Keywords "inherit", "initial" and "unset" can not be mixed, so we + // are done. + AppendValue(eCSSProperty_border_image_repeat, value); + return true; + } + + nsCSSValuePair result; + if (!ParseEnum(result.mXValue, nsCSSProps::kBorderImageRepeatKTable)) { + return false; + } + + // optional second keyword, defaults to first + if (!ParseEnum(result.mYValue, nsCSSProps::kBorderImageRepeatKTable)) { + result.mYValue = result.mXValue; + } + + value.SetPairValue(&result); + AppendValue(eCSSProperty_border_image_repeat, value); + return true; +} + +bool +CSSParserImpl::ParseBorderImage() +{ + nsAutoParseCompoundProperty compound(this); + + // border-image: inherit | initial | + // <border-image-source> || + // <border-image-slice> + // [ / <border-image-width> | + // / <border-image-width>? / <border-image-outset> ]? || + // <border-image-repeat> + + nsCSSValue value; + if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { + AppendValue(eCSSProperty_border_image_source, value); + AppendValue(eCSSProperty_border_image_slice, value); + AppendValue(eCSSProperty_border_image_width, value); + AppendValue(eCSSProperty_border_image_outset, value); + AppendValue(eCSSProperty_border_image_repeat, value); + // Keywords "inherit", "initial" and "unset" can't be mixed, so we are done. + return true; + } + + // No empty property. + if (CheckEndProperty()) { + return false; + } + + // Shorthand properties are required to set everything they can. + SetBorderImageInitialValues(); + + bool foundSource = false; + bool foundSliceWidthOutset = false; + bool foundRepeat = false; + + // This loop is used to handle the parsing of border-image properties which + // can appear in any order. + nsCSSValue imageSourceValue; + while (!CheckEndProperty()) { + // <border-image-source> + if (!foundSource) { + CSSParseResult result = + ParseVariant(imageSourceValue, VARIANT_IMAGE, nullptr); + if (result == CSSParseResult::Error) { + return false; + } else if (result == CSSParseResult::Ok) { + AppendValue(eCSSProperty_border_image_source, imageSourceValue); + foundSource = true; + continue; + } + } + + // <border-image-slice> + // ParseBorderImageSlice is weird. It may consume tokens and then return + // false, because it parses a property with two required components that + // can appear in either order. Since the tokens that were consumed cannot + // parse as anything else we care about, this isn't a problem. + if (!foundSliceWidthOutset) { + bool sliceConsumedTokens = false; + if (ParseBorderImageSlice(false, &sliceConsumedTokens)) { + foundSliceWidthOutset = true; + + // [ / <border-image-width>? + if (ExpectSymbol('/', true)) { + bool foundBorderImageWidth = ParseBorderImageWidth(false); + + // [ / <border-image-outset> + if (ExpectSymbol('/', true)) { + if (!ParseBorderImageOutset(false)) { + return false; + } + } else if (!foundBorderImageWidth) { + // If this part has an trailing slash, the whole declaration is + // invalid. + return false; + } + } + + continue; + } else { + // If we consumed some tokens for <border-image-slice> but did not + // successfully parse it, we have an error. + if (sliceConsumedTokens) { + return false; + } + } + } + + // <border-image-repeat> + if (!foundRepeat && ParseBorderImageRepeat(false)) { + foundRepeat = true; + continue; + } + + return false; + } + + return true; +} + +bool +CSSParserImpl::ParseBorderSpacing() +{ + nsCSSValue xValue, yValue; + if (ParseNonNegativeVariant(xValue, VARIANT_HL | VARIANT_CALC, nullptr) != + CSSParseResult::Ok) { + return false; + } + + // If we have one length, get the optional second length. + // set the second value equal to the first. + if (xValue.IsLengthUnit() || xValue.IsCalcUnit()) { + if (ParseNonNegativeVariant(yValue, VARIANT_LENGTH | VARIANT_CALC, + nullptr) == CSSParseResult::Error) { + return false; + } + } + + if (yValue == xValue || yValue.GetUnit() == eCSSUnit_Null) { + AppendValue(eCSSProperty_border_spacing, xValue); + } else { + nsCSSValue pair; + pair.SetPairValue(xValue, yValue); + AppendValue(eCSSProperty_border_spacing, pair); + } + return true; +} + +bool +CSSParserImpl::ParseBorderSide(const nsCSSPropertyID aPropIDs[], + bool aSetAllSides) +{ + const int32_t numProps = 3; + nsCSSValue values[numProps]; + + int32_t found = ParseChoice(values, aPropIDs, numProps); + if (found < 1) { + return false; + } + + if ((found & 1) == 0) { // Provide default border-width + values[0].SetIntValue(NS_STYLE_BORDER_WIDTH_MEDIUM, eCSSUnit_Enumerated); + } + if ((found & 2) == 0) { // Provide default border-style + values[1].SetIntValue(NS_STYLE_BORDER_STYLE_NONE, eCSSUnit_Enumerated); + } + if ((found & 4) == 0) { // text color will be used + values[2].SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor); + } + + if (aSetAllSides) { + // Parsing "border" shorthand; set all four sides to the same thing + for (int32_t index = 0; index < 4; index++) { + NS_ASSERTION(numProps == 3, "This code needs updating"); + AppendValue(kBorderWidthIDs[index], values[0]); + AppendValue(kBorderStyleIDs[index], values[1]); + AppendValue(kBorderColorIDs[index], values[2]); + } + + static const nsCSSPropertyID kBorderColorsProps[] = { + eCSSProperty_border_top_colors, + eCSSProperty_border_right_colors, + eCSSProperty_border_bottom_colors, + eCSSProperty_border_left_colors + }; + + // Set the other properties that the border shorthand sets to their + // initial values. + nsCSSValue extraValue; + switch (values[0].GetUnit()) { + case eCSSUnit_Inherit: + case eCSSUnit_Initial: + case eCSSUnit_Unset: + extraValue = values[0]; + // Set value of border-image properties to initial/inherit/unset + AppendValue(eCSSProperty_border_image_source, extraValue); + AppendValue(eCSSProperty_border_image_slice, extraValue); + AppendValue(eCSSProperty_border_image_width, extraValue); + AppendValue(eCSSProperty_border_image_outset, extraValue); + AppendValue(eCSSProperty_border_image_repeat, extraValue); + break; + default: + extraValue.SetNoneValue(); + SetBorderImageInitialValues(); + break; + } + NS_FOR_CSS_SIDES(side) { + AppendValue(kBorderColorsProps[side], extraValue); + } + } + else { + // Just set our one side + for (int32_t index = 0; index < numProps; index++) { + AppendValue(aPropIDs[index], values[index]); + } + } + return true; +} + +bool +CSSParserImpl::ParseBorderStyle() +{ + return ParseBoxProperties(kBorderStyleIDs); +} + +bool +CSSParserImpl::ParseBorderWidth() +{ + return ParseBoxProperties(kBorderWidthIDs); +} + +bool +CSSParserImpl::ParseBorderColors(nsCSSPropertyID aProperty) +{ + nsCSSValue value; + // 'inherit', 'initial', 'unset' and 'none' are only allowed on their own + if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE, + nullptr)) { + nsCSSValueList *cur = value.SetListValue(); + for (;;) { + if (ParseVariant(cur->mValue, VARIANT_COLOR, nullptr) != + CSSParseResult::Ok) { + return false; + } + if (CheckEndProperty()) { + break; + } + cur->mNext = new nsCSSValueList; + cur = cur->mNext; + } + } + AppendValue(aProperty, value); + return true; +} + +// Parse the top level of a calc() expression. +bool +CSSParserImpl::ParseCalc(nsCSSValue &aValue, uint32_t aVariantMask) +{ + // Parsing calc expressions requires, in a number of cases, looking + // for a token that is *either* a value of the property or a number. + // This can be done without lookahead when we assume that the property + // values cannot themselves be numbers. + MOZ_ASSERT(aVariantMask != 0, "unexpected variant mask"); + + bool oldUnitlessLengthQuirk = mUnitlessLengthQuirk; + mUnitlessLengthQuirk = false; + + // One-iteration loop so we can break to the error-handling case. + do { + // The toplevel of a calc() is always an nsCSSValue::Array of length 1. + RefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(1); + + if (!ParseCalcAdditiveExpression(arr->Item(0), aVariantMask)) + break; + + if (!ExpectSymbol(')', true)) + break; + + aValue.SetArrayValue(arr, eCSSUnit_Calc); + mUnitlessLengthQuirk = oldUnitlessLengthQuirk; + return true; + } while (false); + + SkipUntil(')'); + mUnitlessLengthQuirk = oldUnitlessLengthQuirk; + return false; +} + +// We optimize away the <value-expression> production given that +// ParseVariant consumes initial whitespace and we call +// ExpectSymbol(')') with true for aSkipWS. +// * If aVariantMask is VARIANT_NUMBER, this function parses the +// <number-additive-expression> production. +// * If aVariantMask does not contain VARIANT_NUMBER, this function +// parses the <value-additive-expression> production. +// * Otherwise (VARIANT_NUMBER and other bits) this function parses +// whichever one of the productions matches ***and modifies +// aVariantMask*** to reflect which one it has parsed by either +// removing VARIANT_NUMBER or removing all other bits. +// It does so iteratively, but builds the correct recursive +// data structure. +bool +CSSParserImpl::ParseCalcAdditiveExpression(nsCSSValue& aValue, + uint32_t& aVariantMask) +{ + MOZ_ASSERT(aVariantMask != 0, "unexpected variant mask"); + nsCSSValue *storage = &aValue; + for (;;) { + bool haveWS; + if (!ParseCalcMultiplicativeExpression(*storage, aVariantMask, &haveWS)) + return false; + + if (!haveWS || !GetToken(false)) + return true; + nsCSSUnit unit; + if (mToken.IsSymbol('+')) { + unit = eCSSUnit_Calc_Plus; + } else if (mToken.IsSymbol('-')) { + unit = eCSSUnit_Calc_Minus; + } else { + UngetToken(); + return true; + } + if (!RequireWhitespace()) + return false; + + RefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(2); + arr->Item(0) = aValue; + storage = &arr->Item(1); + aValue.SetArrayValue(arr, unit); + } +} + +struct ReduceNumberCalcOps : public mozilla::css::BasicFloatCalcOps, + public mozilla::css::CSSValueInputCalcOps +{ + result_type ComputeLeafValue(const nsCSSValue& aValue) + { + MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Number, "unexpected unit"); + return aValue.GetFloatValue(); + } + + float ComputeNumber(const nsCSSValue& aValue) + { + return mozilla::css::ComputeCalc(aValue, *this); + } +}; + +// * If aVariantMask is VARIANT_NUMBER, this function parses the +// <number-multiplicative-expression> production. +// * If aVariantMask does not contain VARIANT_NUMBER, this function +// parses the <value-multiplicative-expression> production. +// * Otherwise (VARIANT_NUMBER and other bits) this function parses +// whichever one of the productions matches ***and modifies +// aVariantMask*** to reflect which one it has parsed by either +// removing VARIANT_NUMBER or removing all other bits. +// It does so iteratively, but builds the correct recursive data +// structure. +// This function always consumes *trailing* whitespace when it returns +// true; whether there was any such whitespace is returned in the +// aHadFinalWS parameter. +bool +CSSParserImpl::ParseCalcMultiplicativeExpression(nsCSSValue& aValue, + uint32_t& aVariantMask, + bool *aHadFinalWS) +{ + MOZ_ASSERT(aVariantMask != 0, "unexpected variant mask"); + bool gotValue = false; // already got the part with the unit + bool afterDivision = false; + + nsCSSValue *storage = &aValue; + for (;;) { + uint32_t variantMask; + if (afterDivision || gotValue) { + variantMask = VARIANT_NUMBER; + } else { + variantMask = aVariantMask | VARIANT_NUMBER; + } + if (!ParseCalcTerm(*storage, variantMask)) + return false; + MOZ_ASSERT(variantMask != 0, + "ParseCalcTerm did not set variantMask appropriately"); + MOZ_ASSERT(!(variantMask & VARIANT_NUMBER) || + !(variantMask & ~int32_t(VARIANT_NUMBER)), + "ParseCalcTerm did not set variantMask appropriately"); + + if (variantMask & VARIANT_NUMBER) { + // Simplify the value immediately so we can check for division by + // zero. + ReduceNumberCalcOps ops; + float number = mozilla::css::ComputeCalc(*storage, ops); + if (number == 0.0 && afterDivision) + return false; + storage->SetFloatValue(number, eCSSUnit_Number); + } else { + gotValue = true; + + if (storage != &aValue) { + // Simplify any numbers in the Times_L position (which are + // not simplified by the check above). + MOZ_ASSERT(storage == &aValue.GetArrayValue()->Item(1), + "unexpected relationship to current storage"); + nsCSSValue &leftValue = aValue.GetArrayValue()->Item(0); + ReduceNumberCalcOps ops; + float number = mozilla::css::ComputeCalc(leftValue, ops); + leftValue.SetFloatValue(number, eCSSUnit_Number); + } + } + + bool hadWS = RequireWhitespace(); + if (!GetToken(false)) { + *aHadFinalWS = hadWS; + break; + } + nsCSSUnit unit; + if (mToken.IsSymbol('*')) { + unit = gotValue ? eCSSUnit_Calc_Times_R : eCSSUnit_Calc_Times_L; + afterDivision = false; + } else if (mToken.IsSymbol('/')) { + unit = eCSSUnit_Calc_Divided; + afterDivision = true; + } else { + UngetToken(); + *aHadFinalWS = hadWS; + break; + } + + RefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(2); + arr->Item(0) = aValue; + storage = &arr->Item(1); + aValue.SetArrayValue(arr, unit); + } + + // Adjust aVariantMask (see comments above function) to reflect which + // option we took. + if (aVariantMask & VARIANT_NUMBER) { + if (gotValue) { + aVariantMask &= ~int32_t(VARIANT_NUMBER); + } else { + aVariantMask = VARIANT_NUMBER; + } + } else { + if (!gotValue) { + // We had to find a value, but we didn't. + return false; + } + } + + return true; +} + +// * If aVariantMask is VARIANT_NUMBER, this function parses the +// <number-term> production. +// * If aVariantMask does not contain VARIANT_NUMBER, this function +// parses the <value-term> production. +// * Otherwise (VARIANT_NUMBER and other bits) this function parses +// whichever one of the productions matches ***and modifies +// aVariantMask*** to reflect which one it has parsed by either +// removing VARIANT_NUMBER or removing all other bits. +bool +CSSParserImpl::ParseCalcTerm(nsCSSValue& aValue, uint32_t& aVariantMask) +{ + MOZ_ASSERT(aVariantMask != 0, "unexpected variant mask"); + if (!GetToken(true)) + return false; + // Either an additive expression in parentheses... + if (mToken.IsSymbol('(') || + // Treat nested calc() as plain parenthesis. + IsCSSTokenCalcFunction(mToken)) { + if (!ParseCalcAdditiveExpression(aValue, aVariantMask) || + !ExpectSymbol(')', true)) { + SkipUntil(')'); + return false; + } + return true; + } + // ... or just a value + UngetToken(); + // Always pass VARIANT_NUMBER to ParseVariant so that unitless zero + // always gets picked up + if (ParseVariant(aValue, aVariantMask | VARIANT_NUMBER, nullptr) != + CSSParseResult::Ok) { + return false; + } + // ...and do the VARIANT_NUMBER check ourselves. + if (!(aVariantMask & VARIANT_NUMBER) && aValue.GetUnit() == eCSSUnit_Number) { + return false; + } + // If we did the value parsing, we need to adjust aVariantMask to + // reflect which option we took (see above). + if (aVariantMask & VARIANT_NUMBER) { + if (aValue.GetUnit() == eCSSUnit_Number) { + aVariantMask = VARIANT_NUMBER; + } else { + aVariantMask &= ~int32_t(VARIANT_NUMBER); + } + } + return true; +} + +// This function consumes all consecutive whitespace and returns whether +// there was any. +bool +CSSParserImpl::RequireWhitespace() +{ + if (!GetToken(false)) + return false; + if (mToken.mType != eCSSToken_Whitespace) { + UngetToken(); + return false; + } + // Skip any additional whitespace tokens. + if (GetToken(true)) { + UngetToken(); + } + return true; +} + +bool +CSSParserImpl::ParseRect(nsCSSPropertyID aPropID) +{ + nsCSSValue val; + if (ParseSingleTokenVariant(val, VARIANT_INHERIT | VARIANT_AUTO, nullptr)) { + AppendValue(aPropID, val); + return true; + } + + if (! GetToken(true)) { + return false; + } + + if (mToken.mType == eCSSToken_Function && + mToken.mIdent.LowerCaseEqualsLiteral("rect")) { + nsCSSRect& rect = val.SetRectValue(); + bool useCommas; + NS_FOR_CSS_SIDES(side) { + if (!ParseSingleTokenVariant(rect.*(nsCSSRect::sides[side]), + VARIANT_AL, nullptr)) { + return false; + } + if (side == 0) { + useCommas = ExpectSymbol(',', true); + } else if (useCommas && side < 3) { + // Skip optional commas between elements, but only if the first + // separator was a comma. + if (!ExpectSymbol(',', true)) { + return false; + } + } + } + if (!ExpectSymbol(')', true)) { + return false; + } + } else { + UngetToken(); + return false; + } + + AppendValue(aPropID, val); + return true; +} + +bool +CSSParserImpl::ParseColumns() +{ + // We use a similar "fake value" hack to ParseListStyle, because + // "auto" is acceptable for both column-count and column-width. + // If the fake "auto" value is found, and one of the real values isn't, + // that means the fake auto value is meant for the real value we didn't + // find. + static const nsCSSPropertyID columnIDs[] = { + eCSSPropertyExtra_x_auto_value, + eCSSProperty_column_count, + eCSSProperty_column_width + }; + const int32_t numProps = MOZ_ARRAY_LENGTH(columnIDs); + + nsCSSValue values[numProps]; + int32_t found = ParseChoice(values, columnIDs, numProps); + if (found < 1) { + return false; + } + if ((found & (1|2|4)) == (1|2|4) && + values[0].GetUnit() == eCSSUnit_Auto) { + // We filled all 3 values, which is invalid + return false; + } + + if ((found & 2) == 0) { + // Provide auto column-count + values[1].SetAutoValue(); + } + if ((found & 4) == 0) { + // Provide auto column-width + values[2].SetAutoValue(); + } + + // Start at index 1 to skip the fake auto value. + for (int32_t index = 1; index < numProps; index++) { + AppendValue(columnIDs[index], values[index]); + } + return true; +} + +#define VARIANT_CONTENT (VARIANT_STRING | VARIANT_URL | VARIANT_COUNTER | VARIANT_ATTR | \ + VARIANT_KEYWORD) +bool +CSSParserImpl::ParseContent() +{ + // We need to divide the 'content' keywords into two classes for + // ParseVariant's sake, so we can't just use nsCSSProps::kContentKTable. + static const KTableEntry kContentListKWs[] = { + { eCSSKeyword_open_quote, NS_STYLE_CONTENT_OPEN_QUOTE }, + { eCSSKeyword_close_quote, NS_STYLE_CONTENT_CLOSE_QUOTE }, + { eCSSKeyword_no_open_quote, NS_STYLE_CONTENT_NO_OPEN_QUOTE }, + { eCSSKeyword_no_close_quote, NS_STYLE_CONTENT_NO_CLOSE_QUOTE }, + { eCSSKeyword_UNKNOWN, -1 } + }; + + static const KTableEntry kContentSolitaryKWs[] = { + { eCSSKeyword__moz_alt_content, NS_STYLE_CONTENT_ALT_CONTENT }, + { eCSSKeyword_UNKNOWN, -1 } + }; + + // Verify that these two lists add up to the size of + // nsCSSProps::kContentKTable. + MOZ_ASSERT(nsCSSProps::kContentKTable[ + ArrayLength(kContentListKWs) + + ArrayLength(kContentSolitaryKWs) - 2].mKeyword == + eCSSKeyword_UNKNOWN && + nsCSSProps::kContentKTable[ + ArrayLength(kContentListKWs) + + ArrayLength(kContentSolitaryKWs) - 2].mValue == -1, + "content keyword tables out of sync"); + + nsCSSValue value; + // 'inherit', 'initial', 'unset', 'normal', 'none', and 'alt-content' must + // be alone + if (!ParseSingleTokenVariant(value, VARIANT_HMK | VARIANT_NONE, + kContentSolitaryKWs)) { + nsCSSValueList* cur = value.SetListValue(); + for (;;) { + if (ParseVariant(cur->mValue, VARIANT_CONTENT, kContentListKWs) != + CSSParseResult::Ok) { + return false; + } + if (CheckEndProperty()) { + break; + } + cur->mNext = new nsCSSValueList; + cur = cur->mNext; + } + } + AppendValue(eCSSProperty_content, value); + return true; +} + +bool +CSSParserImpl::ParseCounterData(nsCSSPropertyID aPropID) +{ + static const nsCSSKeyword kCounterDataKTable[] = { + eCSSKeyword_none, + eCSSKeyword_UNKNOWN + }; + nsCSSValue value; + if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE, + nullptr)) { + if (!GetToken(true)) { + return false; + } + if (mToken.mType != eCSSToken_Ident) { + UngetToken(); + return false; + } + + nsCSSValuePairList *cur = value.SetPairListValue(); + for (;;) { + if (!ParseCustomIdent(cur->mXValue, mToken.mIdent, kCounterDataKTable)) { + return false; + } + if (!GetToken(true)) { + break; + } + if (mToken.mType == eCSSToken_Number && mToken.mIntegerValid) { + cur->mYValue.SetIntValue(mToken.mInteger, eCSSUnit_Integer); + } else { + UngetToken(); + } + if (!GetToken(true)) { + break; + } + if (mToken.mType != eCSSToken_Ident) { + UngetToken(); + break; + } + cur->mNext = new nsCSSValuePairList; + cur = cur->mNext; + } + } + AppendValue(aPropID, value); + return true; +} + +bool +CSSParserImpl::ParseCursor() +{ + nsCSSValue value; + // 'inherit', 'initial' and 'unset' must be alone + if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { + nsCSSValueList* cur = value.SetListValue(); + for (;;) { + if (!ParseSingleTokenVariant(cur->mValue, VARIANT_UK, + nsCSSProps::kCursorKTable)) { + return false; + } + if (cur->mValue.GetUnit() != eCSSUnit_URL) { // keyword must be last + break; + } + + // We have a URL, so make a value array with three values. + RefPtr<nsCSSValue::Array> val = nsCSSValue::Array::Create(3); + val->Item(0) = cur->mValue; + + // Parse optional x and y position of cursor hotspot (css3-ui). + if (ParseSingleTokenVariant(val->Item(1), VARIANT_NUMBER, nullptr)) { + // If we have one number, we must have two. + if (!ParseSingleTokenVariant(val->Item(2), VARIANT_NUMBER, nullptr)) { + return false; + } + } + cur->mValue.SetArrayValue(val, eCSSUnit_Array); + + if (!ExpectSymbol(',', true)) { // url must not be last + return false; + } + cur->mNext = new nsCSSValueList; + cur = cur->mNext; + } + } + AppendValue(eCSSProperty_cursor, value); + return true; +} + + +bool +CSSParserImpl::ParseFont() +{ + nsCSSValue family; + if (ParseSingleTokenVariant(family, VARIANT_HK, nsCSSProps::kFontKTable)) { + if (eCSSUnit_Inherit == family.GetUnit() || + eCSSUnit_Initial == family.GetUnit() || + eCSSUnit_Unset == family.GetUnit()) { + AppendValue(eCSSProperty__x_system_font, nsCSSValue(eCSSUnit_None)); + AppendValue(eCSSProperty_font_family, family); + AppendValue(eCSSProperty_font_style, family); + AppendValue(eCSSProperty_font_weight, family); + AppendValue(eCSSProperty_font_size, family); + AppendValue(eCSSProperty_line_height, family); + AppendValue(eCSSProperty_font_stretch, family); + AppendValue(eCSSProperty_font_size_adjust, family); + AppendValue(eCSSProperty_font_feature_settings, family); + AppendValue(eCSSProperty_font_language_override, family); + AppendValue(eCSSProperty_font_kerning, family); + AppendValue(eCSSProperty_font_synthesis, family); + AppendValue(eCSSProperty_font_variant_alternates, family); + AppendValue(eCSSProperty_font_variant_caps, family); + AppendValue(eCSSProperty_font_variant_east_asian, family); + AppendValue(eCSSProperty_font_variant_ligatures, family); + AppendValue(eCSSProperty_font_variant_numeric, family); + AppendValue(eCSSProperty_font_variant_position, family); + } + else { + AppendValue(eCSSProperty__x_system_font, family); + nsCSSValue systemFont(eCSSUnit_System_Font); + AppendValue(eCSSProperty_font_family, systemFont); + AppendValue(eCSSProperty_font_style, systemFont); + AppendValue(eCSSProperty_font_weight, systemFont); + AppendValue(eCSSProperty_font_size, systemFont); + AppendValue(eCSSProperty_line_height, systemFont); + AppendValue(eCSSProperty_font_stretch, systemFont); + AppendValue(eCSSProperty_font_size_adjust, systemFont); + AppendValue(eCSSProperty_font_feature_settings, systemFont); + AppendValue(eCSSProperty_font_language_override, systemFont); + AppendValue(eCSSProperty_font_kerning, systemFont); + AppendValue(eCSSProperty_font_synthesis, systemFont); + AppendValue(eCSSProperty_font_variant_alternates, systemFont); + AppendValue(eCSSProperty_font_variant_caps, systemFont); + AppendValue(eCSSProperty_font_variant_east_asian, systemFont); + AppendValue(eCSSProperty_font_variant_ligatures, systemFont); + AppendValue(eCSSProperty_font_variant_numeric, systemFont); + AppendValue(eCSSProperty_font_variant_position, systemFont); + } + return true; + } + + // Get optional font-style, font-variant, font-weight, font-stretch + // (in any order) + + // Indexes into fontIDs[] and values[] arrays. + const int kFontStyleIndex = 0; + const int kFontVariantIndex = 1; + const int kFontWeightIndex = 2; + const int kFontStretchIndex = 3; + + // The order of the initializers here must match the order of the indexes + // defined above! + static const nsCSSPropertyID fontIDs[] = { + eCSSProperty_font_style, + eCSSProperty_font_variant_caps, + eCSSProperty_font_weight, + eCSSProperty_font_stretch + }; + + const int32_t numProps = MOZ_ARRAY_LENGTH(fontIDs); + nsCSSValue values[numProps]; + int32_t found = ParseChoice(values, fontIDs, numProps); + if (found < 0 || + eCSSUnit_Inherit == values[kFontStyleIndex].GetUnit() || + eCSSUnit_Initial == values[kFontStyleIndex].GetUnit() || + eCSSUnit_Unset == values[kFontStyleIndex].GetUnit()) { // illegal data + return false; + } + if ((found & (1 << kFontStyleIndex)) == 0) { + // Provide default font-style + values[kFontStyleIndex].SetIntValue(NS_FONT_STYLE_NORMAL, + eCSSUnit_Enumerated); + } + if ((found & (1 << kFontVariantIndex)) == 0) { + // Provide default font-variant + values[kFontVariantIndex].SetNormalValue(); + } else { + if (values[kFontVariantIndex].GetUnit() == eCSSUnit_Enumerated && + values[kFontVariantIndex].GetIntValue() != + NS_FONT_VARIANT_CAPS_SMALLCAPS) { + // only normal or small-caps is allowed in font shorthand + // this also assumes other values for font-variant-caps never overlap + // possible values for style or weight + return false; + } + } + if ((found & (1 << kFontWeightIndex)) == 0) { + // Provide default font-weight + values[kFontWeightIndex].SetIntValue(NS_FONT_WEIGHT_NORMAL, + eCSSUnit_Enumerated); + } + if ((found & (1 << kFontStretchIndex)) == 0) { + // Provide default font-stretch + values[kFontStretchIndex].SetIntValue(NS_FONT_STRETCH_NORMAL, + eCSSUnit_Enumerated); + } + + // Get mandatory font-size + nsCSSValue size; + if (!ParseSingleTokenNonNegativeVariant(size, VARIANT_KEYWORD | VARIANT_LP, + nsCSSProps::kFontSizeKTable)) { + return false; + } + + // Get optional "/" line-height + nsCSSValue lineHeight; + if (ExpectSymbol('/', true)) { + if (ParseNonNegativeVariant(lineHeight, + VARIANT_NUMBER | VARIANT_LP | + VARIANT_NORMAL | VARIANT_CALC, + nullptr) != CSSParseResult::Ok) { + return false; + } + } + else { + lineHeight.SetNormalValue(); + } + + // Get final mandatory font-family + nsAutoParseCompoundProperty compound(this); + if (ParseFamily(family)) { + if (eCSSUnit_Inherit != family.GetUnit() && + eCSSUnit_Initial != family.GetUnit() && + eCSSUnit_Unset != family.GetUnit()) { + AppendValue(eCSSProperty__x_system_font, nsCSSValue(eCSSUnit_None)); + AppendValue(eCSSProperty_font_family, family); + AppendValue(eCSSProperty_font_style, values[kFontStyleIndex]); + AppendValue(eCSSProperty_font_variant_caps, values[kFontVariantIndex]); + AppendValue(eCSSProperty_font_weight, values[kFontWeightIndex]); + AppendValue(eCSSProperty_font_size, size); + AppendValue(eCSSProperty_line_height, lineHeight); + AppendValue(eCSSProperty_font_stretch, values[kFontStretchIndex]); + AppendValue(eCSSProperty_font_size_adjust, nsCSSValue(eCSSUnit_None)); + AppendValue(eCSSProperty_font_feature_settings, nsCSSValue(eCSSUnit_Normal)); + AppendValue(eCSSProperty_font_language_override, nsCSSValue(eCSSUnit_Normal)); + AppendValue(eCSSProperty_font_kerning, + nsCSSValue(NS_FONT_KERNING_AUTO, eCSSUnit_Enumerated)); + AppendValue(eCSSProperty_font_synthesis, + nsCSSValue(NS_FONT_SYNTHESIS_WEIGHT | NS_FONT_SYNTHESIS_STYLE, + eCSSUnit_Enumerated)); + AppendValue(eCSSProperty_font_variant_alternates, + nsCSSValue(eCSSUnit_Normal)); + AppendValue(eCSSProperty_font_variant_east_asian, + nsCSSValue(eCSSUnit_Normal)); + AppendValue(eCSSProperty_font_variant_ligatures, + nsCSSValue(eCSSUnit_Normal)); + AppendValue(eCSSProperty_font_variant_numeric, + nsCSSValue(eCSSUnit_Normal)); + AppendValue(eCSSProperty_font_variant_position, + nsCSSValue(eCSSUnit_Normal)); + return true; + } + } + return false; +} + +bool +CSSParserImpl::ParseFontSynthesis(nsCSSValue& aValue) +{ + if (!ParseSingleTokenVariant(aValue, VARIANT_HK | VARIANT_NONE, + nsCSSProps::kFontSynthesisKTable)) { + return false; + } + + // first value 'none' ==> done + if (eCSSUnit_None == aValue.GetUnit() || + eCSSUnit_Initial == aValue.GetUnit() || + eCSSUnit_Inherit == aValue.GetUnit() || + eCSSUnit_Unset == aValue.GetUnit()) + { + return true; + } + + // look for a second value + int32_t intValue = aValue.GetIntValue(); + nsCSSValue nextValue; + + if (ParseEnum(nextValue, nsCSSProps::kFontSynthesisKTable)) { + int32_t nextIntValue = nextValue.GetIntValue(); + if (nextIntValue & intValue) { + return false; + } + aValue.SetIntValue(nextIntValue | intValue, eCSSUnit_Enumerated); + } + + return true; +} + +// font-variant-alternates allows for a combination of multiple +// simple enumerated values and functional values. Functional values have +// parameter lists with one or more idents which are later resolved +// based on values defined in @font-feature-value rules. +// +// font-variant-alternates: swash(flowing) historical-forms styleset(alt-g, alt-m); +// +// So for this the nsCSSValue is set to a pair value, with one +// value for a bitmask of both simple and functional property values +// and another value containing a ValuePairList with lists of idents +// for each functional property value. +// +// pairValue +// o intValue +// NS_FONT_VARIANT_ALTERNATES_SWASH | +// NS_FONT_VARIANT_ALTERNATES_STYLESET +// o valuePairList, each element with +// - intValue - indicates which alternate +// - string or valueList of strings +// +// Note: when only 'historical-forms' is specified, there are no +// functional values to store, in which case the valuePairList is a +// single element dummy list. In all other cases, the length of the +// list will match the number of functional values. + +#define MAX_ALLOWED_FEATURES 512 + +static uint16_t +MaxElementsForAlternateType(nsCSSKeyword keyword) +{ + uint16_t maxElems = 1; + if (keyword == eCSSKeyword_styleset || + keyword == eCSSKeyword_character_variant) { + maxElems = MAX_ALLOWED_FEATURES; + } + return maxElems; +} + +bool +CSSParserImpl::ParseSingleAlternate(int32_t& aWhichFeature, + nsCSSValue& aValue) +{ + if (!GetToken(true)) { + return false; + } + + bool isIdent = (mToken.mType == eCSSToken_Ident); + if (mToken.mType != eCSSToken_Function && !isIdent) { + UngetToken(); + return false; + } + + // ident ==> simple enumerated prop val (e.g. historical-forms) + // function ==> e.g. swash(flowing) styleset(alt-g, alt-m) + + nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent); + if (!(eCSSKeyword_UNKNOWN < keyword && + nsCSSProps::FindKeyword(keyword, + (isIdent ? + nsCSSProps::kFontVariantAlternatesKTable : + nsCSSProps::kFontVariantAlternatesFuncsKTable), + aWhichFeature))) + { + // failed, pop token + UngetToken(); + return false; + } + + if (isIdent) { + aValue.SetIntValue(aWhichFeature, eCSSUnit_Enumerated); + return true; + } + + return ParseFunction(keyword, nullptr, VARIANT_IDENTIFIER, + 1, MaxElementsForAlternateType(keyword), aValue); +} + +bool +CSSParserImpl::ParseFontVariantAlternates(nsCSSValue& aValue) +{ + if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NORMAL, + nullptr)) { + return true; + } + + // iterate through parameters + nsCSSValue listValue; + int32_t feature, featureFlags = 0; + + // if no functional values, this may be a list with a single, unused element + listValue.SetListValue(); + + nsCSSValueList* list = nullptr; + nsCSSValue value; + while (ParseSingleAlternate(feature, value)) { + + // check to make sure value not already set + if (feature == 0 || + feature & featureFlags) { + return false; + } + + featureFlags |= feature; + + // if function, need to add to the list of functions + if (value.GetUnit() == eCSSUnit_Function) { + if (!list) { + list = listValue.GetListValue(); + } else { + list->mNext = new nsCSSValueList; + list = list->mNext; + } + list->mValue = value; + } + } + + if (featureFlags == 0) { + // ParseSingleAlternate failed the first time through the loop. + return false; + } + + nsCSSValue featureValue; + featureValue.SetIntValue(featureFlags, eCSSUnit_Enumerated); + aValue.SetPairValue(featureValue, listValue); + + return true; +} + +bool +CSSParserImpl::MergeBitmaskValue(int32_t aNewValue, + const int32_t aMasks[], + int32_t& aMergedValue) +{ + // check to make sure value not already set + if (aNewValue & aMergedValue) { + return false; + } + + const int32_t *m = aMasks; + int32_t c = 0; + + while (*m != MASK_END_VALUE) { + if (*m & aNewValue) { + c = aMergedValue & *m; + break; + } + m++; + } + + if (c) { + return false; + } + + aMergedValue |= aNewValue; + return true; +} + +// aMasks - array of masks for mutually-exclusive property values, +// e.g. proportial-nums, tabular-nums + +bool +CSSParserImpl::ParseBitmaskValues(nsCSSValue& aValue, + const KTableEntry aKeywordTable[], + const int32_t aMasks[]) +{ + // Parse at least one keyword + if (!ParseEnum(aValue, aKeywordTable)) { + return false; + } + + // look for more values + nsCSSValue nextValue; + int32_t mergedValue = aValue.GetIntValue(); + + while (ParseEnum(nextValue, aKeywordTable)) + { + if (!MergeBitmaskValue(nextValue.GetIntValue(), aMasks, mergedValue)) { + return false; + } + } + + aValue.SetIntValue(mergedValue, eCSSUnit_Enumerated); + + return true; +} + +static const int32_t maskEastAsian[] = { + NS_FONT_VARIANT_EAST_ASIAN_VARIANT_MASK, + NS_FONT_VARIANT_EAST_ASIAN_WIDTH_MASK, + MASK_END_VALUE +}; + +bool +CSSParserImpl::ParseFontVariantEastAsian(nsCSSValue& aValue) +{ + if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NORMAL, + nullptr)) { + return true; + } + + NS_ASSERTION(maskEastAsian[ArrayLength(maskEastAsian) - 1] == + MASK_END_VALUE, + "incorrectly terminated array"); + + return ParseBitmaskValues(aValue, nsCSSProps::kFontVariantEastAsianKTable, + maskEastAsian); +} + +bool +CSSParserImpl::ParseContain(nsCSSValue& aValue) +{ + if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NONE, + nullptr)) { + return true; + } + static const int32_t maskContain[] = { MASK_END_VALUE }; + if (!ParseBitmaskValues(aValue, nsCSSProps::kContainKTable, maskContain)) { + return false; + } + if (aValue.GetIntValue() & NS_STYLE_CONTAIN_STRICT) { + if (aValue.GetIntValue() != NS_STYLE_CONTAIN_STRICT) { + // Disallow any other keywords in combination with 'strict'. + return false; + } + // Strict implies layout, style, and paint. + // However, for serialization purposes, we keep the strict bit around. + aValue.SetIntValue(NS_STYLE_CONTAIN_STRICT | + NS_STYLE_CONTAIN_ALL_BITS, eCSSUnit_Enumerated); + } + return true; +} + +static const int32_t maskLigatures[] = { + NS_FONT_VARIANT_LIGATURES_COMMON_MASK, + NS_FONT_VARIANT_LIGATURES_DISCRETIONARY_MASK, + NS_FONT_VARIANT_LIGATURES_HISTORICAL_MASK, + NS_FONT_VARIANT_LIGATURES_CONTEXTUAL_MASK, + MASK_END_VALUE +}; + +bool +CSSParserImpl::ParseFontVariantLigatures(nsCSSValue& aValue) +{ + if (ParseSingleTokenVariant(aValue, + VARIANT_INHERIT | VARIANT_NORMAL | VARIANT_NONE, + nullptr)) { + return true; + } + + NS_ASSERTION(maskLigatures[ArrayLength(maskLigatures) - 1] == + MASK_END_VALUE, + "incorrectly terminated array"); + + return ParseBitmaskValues(aValue, nsCSSProps::kFontVariantLigaturesKTable, + maskLigatures); +} + +static const int32_t maskNumeric[] = { + NS_FONT_VARIANT_NUMERIC_FIGURE_MASK, + NS_FONT_VARIANT_NUMERIC_SPACING_MASK, + NS_FONT_VARIANT_NUMERIC_FRACTION_MASK, + MASK_END_VALUE +}; + +bool +CSSParserImpl::ParseFontVariantNumeric(nsCSSValue& aValue) +{ + if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NORMAL, + nullptr)) { + return true; + } + + NS_ASSERTION(maskNumeric[ArrayLength(maskNumeric) - 1] == + MASK_END_VALUE, + "incorrectly terminated array"); + + return ParseBitmaskValues(aValue, nsCSSProps::kFontVariantNumericKTable, + maskNumeric); +} + +bool +CSSParserImpl::ParseFontVariant() +{ + // parse single values - normal/inherit/none + nsCSSValue value; + nsCSSValue normal(eCSSUnit_Normal); + + if (ParseSingleTokenVariant(value, + VARIANT_INHERIT | VARIANT_NORMAL | VARIANT_NONE, + nullptr)) { + AppendValue(eCSSProperty_font_variant_ligatures, value); + if (eCSSUnit_None == value.GetUnit()) { + // 'none' applies the value 'normal' to all properties other + // than 'font-variant-ligatures' + value.SetNormalValue(); + } + AppendValue(eCSSProperty_font_variant_alternates, value); + AppendValue(eCSSProperty_font_variant_caps, value); + AppendValue(eCSSProperty_font_variant_east_asian, value); + AppendValue(eCSSProperty_font_variant_numeric, value); + AppendValue(eCSSProperty_font_variant_position, value); + return true; + } + + // set each of the individual subproperties + int32_t altFeatures = 0, capsFeatures = 0, eastAsianFeatures = 0, + ligFeatures = 0, numericFeatures = 0, posFeatures = 0; + nsCSSValue altListValue; + nsCSSValueList* altList = nullptr; + + // if no functional values, this may be a list with a single, unused element + altListValue.SetListValue(); + + bool foundValid = false; // found at least one proper value + while (GetToken(true)) { + // only an ident or a function at this point + bool isFunction = (mToken.mType == eCSSToken_Function); + if (mToken.mType != eCSSToken_Ident && !isFunction) { + UngetToken(); + break; + } + + nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent); + if (keyword == eCSSKeyword_UNKNOWN) { + UngetToken(); + return false; + } + + int32_t feature; + + // function? ==> font-variant-alternates + if (isFunction) { + if (!nsCSSProps::FindKeyword(keyword, + nsCSSProps::kFontVariantAlternatesFuncsKTable, + feature) || + (feature & altFeatures)) { + UngetToken(); + return false; + } + + altFeatures |= feature; + nsCSSValue funcValue; + if (!ParseFunction(keyword, nullptr, VARIANT_IDENTIFIER, 1, + MaxElementsForAlternateType(keyword), funcValue) || + funcValue.GetUnit() != eCSSUnit_Function) { + UngetToken(); + return false; + } + + if (!altList) { + altList = altListValue.GetListValue(); + } else { + altList->mNext = new nsCSSValueList; + altList = altList->mNext; + } + altList->mValue = funcValue; + } else if (nsCSSProps::FindKeyword(keyword, + nsCSSProps::kFontVariantCapsKTable, + feature)) { + if (capsFeatures != 0) { + // multiple values for font-variant-caps + UngetToken(); + return false; + } + capsFeatures = feature; + } else if (nsCSSProps::FindKeyword(keyword, + nsCSSProps::kFontVariantAlternatesKTable, + feature)) { + if (feature & altFeatures) { + // same value repeated + UngetToken(); + return false; + } + altFeatures |= feature; + } else if (nsCSSProps::FindKeyword(keyword, + nsCSSProps::kFontVariantEastAsianKTable, + feature)) { + if (!MergeBitmaskValue(feature, maskEastAsian, eastAsianFeatures)) { + // multiple mutually exclusive values + UngetToken(); + return false; + } + } else if (nsCSSProps::FindKeyword(keyword, + nsCSSProps::kFontVariantLigaturesKTable, + feature)) { + if (keyword == eCSSKeyword_none || + !MergeBitmaskValue(feature, maskLigatures, ligFeatures)) { + // none or multiple mutually exclusive values + UngetToken(); + return false; + } + } else if (nsCSSProps::FindKeyword(keyword, + nsCSSProps::kFontVariantNumericKTable, + feature)) { + if (!MergeBitmaskValue(feature, maskNumeric, numericFeatures)) { + // multiple mutually exclusive values + UngetToken(); + return false; + } + } else if (nsCSSProps::FindKeyword(keyword, + nsCSSProps::kFontVariantPositionKTable, + feature)) { + if (posFeatures != 0) { + // multiple values for font-variant-caps + UngetToken(); + return false; + } + posFeatures = feature; + } else { + // bogus keyword, bail... + UngetToken(); + return false; + } + + foundValid = true; + } + + if (!foundValid) { + return false; + } + + if (altFeatures) { + nsCSSValue featureValue; + featureValue.SetIntValue(altFeatures, eCSSUnit_Enumerated); + value.SetPairValue(featureValue, altListValue); + AppendValue(eCSSProperty_font_variant_alternates, value); + } else { + AppendValue(eCSSProperty_font_variant_alternates, normal); + } + + if (capsFeatures) { + value.SetIntValue(capsFeatures, eCSSUnit_Enumerated); + AppendValue(eCSSProperty_font_variant_caps, value); + } else { + AppendValue(eCSSProperty_font_variant_caps, normal); + } + + if (eastAsianFeatures) { + value.SetIntValue(eastAsianFeatures, eCSSUnit_Enumerated); + AppendValue(eCSSProperty_font_variant_east_asian, value); + } else { + AppendValue(eCSSProperty_font_variant_east_asian, normal); + } + + if (ligFeatures) { + value.SetIntValue(ligFeatures, eCSSUnit_Enumerated); + AppendValue(eCSSProperty_font_variant_ligatures, value); + } else { + AppendValue(eCSSProperty_font_variant_ligatures, normal); + } + + if (numericFeatures) { + value.SetIntValue(numericFeatures, eCSSUnit_Enumerated); + AppendValue(eCSSProperty_font_variant_numeric, value); + } else { + AppendValue(eCSSProperty_font_variant_numeric, normal); + } + + if (posFeatures) { + value.SetIntValue(posFeatures, eCSSUnit_Enumerated); + AppendValue(eCSSProperty_font_variant_position, value); + } else { + AppendValue(eCSSProperty_font_variant_position, normal); + } + + return true; +} + +bool +CSSParserImpl::ParseFontWeight(nsCSSValue& aValue) +{ + if (ParseSingleTokenVariant(aValue, VARIANT_HKI | VARIANT_SYSFONT, + nsCSSProps::kFontWeightKTable)) { + if (eCSSUnit_Integer == aValue.GetUnit()) { // ensure unit value + int32_t intValue = aValue.GetIntValue(); + if ((100 <= intValue) && + (intValue <= 900) && + (0 == (intValue % 100))) { + return true; + } else { + UngetToken(); + return false; + } + } + return true; + } + return false; +} + +bool +CSSParserImpl::ParseOneFamily(nsAString& aFamily, + bool& aOneKeyword, + bool& aQuoted) +{ + if (!GetToken(true)) + return false; + + nsCSSToken* tk = &mToken; + + aOneKeyword = false; + aQuoted = false; + if (eCSSToken_Ident == tk->mType) { + aOneKeyword = true; + aFamily.Append(tk->mIdent); + for (;;) { + if (!GetToken(false)) + break; + + if (eCSSToken_Ident == tk->mType) { + aOneKeyword = false; + // We had at least another keyword before. + // "If a sequence of identifiers is given as a font family name, + // the computed value is the name converted to a string by joining + // all the identifiers in the sequence by single spaces." + // -- CSS 2.1, section 15.3 + // Whitespace tokens do not actually matter, + // identifier tokens can be separated by comments. + aFamily.Append(char16_t(' ')); + aFamily.Append(tk->mIdent); + } else if (eCSSToken_Whitespace != tk->mType) { + UngetToken(); + break; + } + } + return true; + + } else if (eCSSToken_String == tk->mType) { + aQuoted = true; + aFamily.Append(tk->mIdent); // XXX What if it had escaped quotes? + return true; + + } else { + UngetToken(); + return false; + } +} + + +static bool +AppendGeneric(nsCSSKeyword aKeyword, FontFamilyList *aFamilyList) +{ + switch (aKeyword) { + case eCSSKeyword_serif: + aFamilyList->Append(FontFamilyName(eFamily_serif)); + return true; + case eCSSKeyword_sans_serif: + aFamilyList->Append(FontFamilyName(eFamily_sans_serif)); + return true; + case eCSSKeyword_monospace: + aFamilyList->Append(FontFamilyName(eFamily_monospace)); + return true; + case eCSSKeyword_cursive: + aFamilyList->Append(FontFamilyName(eFamily_cursive)); + return true; + case eCSSKeyword_fantasy: + aFamilyList->Append(FontFamilyName(eFamily_fantasy)); + return true; + case eCSSKeyword__moz_fixed: + aFamilyList->Append(FontFamilyName(eFamily_moz_fixed)); + return true; + default: + break; + } + + return false; +} + +bool +CSSParserImpl::ParseFamily(nsCSSValue& aValue) +{ + RefPtr<css::FontFamilyListRefCnt> familyList = + new css::FontFamilyListRefCnt(); + nsAutoString family; + bool single, quoted; + + // keywords only have meaning in the first position + if (!ParseOneFamily(family, single, quoted)) + return false; + + // check for keywords, but only when keywords appear by themselves + // i.e. not in compounds such as font-family: default blah; + bool foundGeneric = false; + if (single) { + nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(family); + switch (keyword) { + case eCSSKeyword_inherit: + aValue.SetInheritValue(); + return true; + case eCSSKeyword_default: + // 605231 - don't parse unquoted 'default' reserved keyword + return false; + case eCSSKeyword_initial: + aValue.SetInitialValue(); + return true; + case eCSSKeyword_unset: + if (nsLayoutUtils::UnsetValueEnabled()) { + aValue.SetUnsetValue(); + return true; + } + break; + case eCSSKeyword__moz_use_system_font: + if (!IsParsingCompoundProperty()) { + aValue.SetSystemFontValue(); + return true; + } + break; + default: + foundGeneric = AppendGeneric(keyword, familyList); + } + } + + if (!foundGeneric) { + familyList->Append( + FontFamilyName(family, (quoted ? eQuotedName : eUnquotedName))); + } + + for (;;) { + if (!ExpectSymbol(',', true)) + break; + + nsAutoString nextFamily; + if (!ParseOneFamily(nextFamily, single, quoted)) + return false; + + // at this point unquoted keywords are not allowed + // as font family names but can appear within names + foundGeneric = false; + if (single) { + nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(nextFamily); + switch (keyword) { + case eCSSKeyword_inherit: + case eCSSKeyword_initial: + case eCSSKeyword_default: + case eCSSKeyword__moz_use_system_font: + return false; + case eCSSKeyword_unset: + if (nsLayoutUtils::UnsetValueEnabled()) { + return false; + } + break; + default: + foundGeneric = AppendGeneric(keyword, familyList); + break; + } + } + + if (!foundGeneric) { + familyList->Append( + FontFamilyName(nextFamily, (quoted ? eQuotedName : eUnquotedName))); + } + } + + if (familyList->IsEmpty()) { + return false; + } + + aValue.SetFontFamilyListValue(familyList); + return true; +} + +// src: ( uri-src | local-src ) (',' ( uri-src | local-src ) )* +// uri-src: uri [ 'format(' string ( ',' string )* ')' ] +// local-src: 'local(' ( string | ident ) ')' + +bool +CSSParserImpl::ParseFontSrc(nsCSSValue& aValue) +{ + // could we maybe turn nsCSSValue::Array into InfallibleTArray<nsCSSValue>? + InfallibleTArray<nsCSSValue> values; + nsCSSValue cur; + for (;;) { + if (!GetToken(true)) + break; + + if (mToken.mType == eCSSToken_URL) { + SetValueToURL(cur, mToken.mIdent); + values.AppendElement(cur); + if (!ParseFontSrcFormat(values)) + return false; + + } else if (mToken.mType == eCSSToken_Function && + mToken.mIdent.LowerCaseEqualsLiteral("local")) { + // css3-fonts does not specify a formal grammar for local(). + // The text permits both unquoted identifiers and quoted + // strings. We resolve this ambiguity in the spec by + // assuming that the appropriate production is a single + // <family-name>, possibly surrounded by whitespace. + + nsAutoString family; + bool single, quoted; + if (!ParseOneFamily(family, single, quoted)) { + SkipUntil(')'); + return false; + } + if (!ExpectSymbol(')', true)) { + SkipUntil(')'); + return false; + } + + // reject generics + if (single) { + nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(family); + switch (keyword) { + case eCSSKeyword_serif: + case eCSSKeyword_sans_serif: + case eCSSKeyword_monospace: + case eCSSKeyword_cursive: + case eCSSKeyword_fantasy: + case eCSSKeyword__moz_fixed: + return false; + default: + break; + } + } + + cur.SetStringValue(family, eCSSUnit_Local_Font); + values.AppendElement(cur); + } else { + // We don't know what to do with this token; unget it and error out + UngetToken(); + return false; + } + + if (!ExpectSymbol(',', true)) + break; + } + + if (values.Length() == 0) + return false; + + RefPtr<nsCSSValue::Array> srcVals + = nsCSSValue::Array::Create(values.Length()); + + uint32_t i; + for (i = 0; i < values.Length(); i++) + srcVals->Item(i) = values[i]; + aValue.SetArrayValue(srcVals, eCSSUnit_Array); + return true; +} + +bool +CSSParserImpl::ParseFontSrcFormat(InfallibleTArray<nsCSSValue> & values) +{ + if (!GetToken(true)) + return true; // EOF harmless here + if (mToken.mType != eCSSToken_Function || + !mToken.mIdent.LowerCaseEqualsLiteral("format")) { + UngetToken(); + return true; + } + + do { + if (!GetToken(true)) + return false; // EOF - no need for SkipUntil + + if (mToken.mType != eCSSToken_String) { + UngetToken(); + SkipUntil(')'); + return false; + } + + nsCSSValue cur(mToken.mIdent, eCSSUnit_Font_Format); + values.AppendElement(cur); + } while (ExpectSymbol(',', true)); + + if (!ExpectSymbol(')', true)) { + SkipUntil(')'); + return false; + } + + return true; +} + +// font-ranges: urange ( ',' urange )* +bool +CSSParserImpl::ParseFontRanges(nsCSSValue& aValue) +{ + InfallibleTArray<uint32_t> ranges; + for (;;) { + if (!GetToken(true)) + break; + + if (mToken.mType != eCSSToken_URange) { + UngetToken(); + break; + } + + // An invalid range token is a parsing error, causing the entire + // descriptor to be ignored. + if (!mToken.mIntegerValid) + return false; + + uint32_t low = mToken.mInteger; + uint32_t high = mToken.mInteger2; + + // A range that descends, or a range that is entirely outside the + // current range of Unicode (U+0-10FFFF) is ignored, but does not + // invalidate the descriptor. A range that straddles the high end + // is clipped. + if (low <= 0x10FFFF && low <= high) { + if (high > 0x10FFFF) + high = 0x10FFFF; + + ranges.AppendElement(low); + ranges.AppendElement(high); + } + if (!ExpectSymbol(',', true)) + break; + } + + if (ranges.Length() == 0) + return false; + + RefPtr<nsCSSValue::Array> srcVals + = nsCSSValue::Array::Create(ranges.Length()); + + for (uint32_t i = 0; i < ranges.Length(); i++) + srcVals->Item(i).SetIntValue(ranges[i], eCSSUnit_Integer); + aValue.SetArrayValue(srcVals, eCSSUnit_Array); + return true; +} + +// font-feature-settings: normal | <feature-tag-value> [, <feature-tag-value>]* +// <feature-tag-value> = <string> [ <integer> | on | off ]? + +// minimum - "tagx", "tagy", "tagz" +// edge error case - "tagx" on 1, "tagx" "tagy", "tagx" -1, "tagx" big + +// pair value is always x = string, y = int + +// font feature tags must be four ASCII characters +#define FEATURE_TAG_LENGTH 4 + +static bool +ValidFontFeatureTag(const nsString& aTag) +{ + if (aTag.Length() != FEATURE_TAG_LENGTH) { + return false; + } + uint32_t i; + for (i = 0; i < FEATURE_TAG_LENGTH; i++) { + uint32_t ch = aTag[i]; + if (ch < 0x20 || ch > 0x7e) { + return false; + } + } + return true; +} + +bool +CSSParserImpl::ParseFontFeatureSettings(nsCSSValue& aValue) +{ + if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NORMAL, + nullptr)) { + return true; + } + + nsCSSValuePairList *cur = aValue.SetPairListValue(); + for (;;) { + // feature tag + if (!GetToken(true)) { + return false; + } + + if (mToken.mType != eCSSToken_String || + !ValidFontFeatureTag(mToken.mIdent)) { + UngetToken(); + return false; + } + cur->mXValue.SetStringValue(mToken.mIdent, eCSSUnit_String); + + if (!GetToken(true)) { + cur->mYValue.SetIntValue(1, eCSSUnit_Integer); + break; + } + + // optional value or on/off keyword + if (mToken.mType == eCSSToken_Number && mToken.mIntegerValid && + mToken.mInteger >= 0) { + cur->mYValue.SetIntValue(mToken.mInteger, eCSSUnit_Integer); + } else if (mToken.mType == eCSSToken_Ident && + mToken.mIdent.LowerCaseEqualsLiteral("on")) { + cur->mYValue.SetIntValue(1, eCSSUnit_Integer); + } else if (mToken.mType == eCSSToken_Ident && + mToken.mIdent.LowerCaseEqualsLiteral("off")) { + cur->mYValue.SetIntValue(0, eCSSUnit_Integer); + } else { + // something other than value/on/off, set default value + cur->mYValue.SetIntValue(1, eCSSUnit_Integer); + UngetToken(); + } + + if (!ExpectSymbol(',', true)) { + break; + } + + cur->mNext = new nsCSSValuePairList; + cur = cur->mNext; + } + + return true; +} + +bool +CSSParserImpl::ParseListStyle() +{ + // 'list-style' can accept 'none' for two different subproperties, + // 'list-style-type' and 'list-style-image'. In order to accept + // 'none' as the value of either but still allow another value for + // either, we need to ensure that the first 'none' we find gets + // allocated to a dummy property instead. Since parse function for + // 'list-style-type' could accept values for 'list-style-position', + // we put position in front of type. + static const nsCSSPropertyID listStyleIDs[] = { + eCSSPropertyExtra_x_none_value, + eCSSProperty_list_style_position, + eCSSProperty_list_style_type, + eCSSProperty_list_style_image + }; + + nsCSSValue values[MOZ_ARRAY_LENGTH(listStyleIDs)]; + int32_t found = + ParseChoice(values, listStyleIDs, ArrayLength(listStyleIDs)); + if (found < 1) { + return false; + } + + if ((found & (1|4|8)) == (1|4|8)) { + if (values[0].GetUnit() == eCSSUnit_None) { + // We found a 'none' plus another value for both of + // 'list-style-type' and 'list-style-image'. This is a parse + // error, since the 'none' has to count for at least one of them. + return false; + } else { + NS_ASSERTION(found == (1|2|4|8) && values[0] == values[1] && + values[0] == values[2] && values[0] == values[3], + "should be a special value"); + } + } + + if ((found & 2) == 0) { + values[1].SetIntValue(NS_STYLE_LIST_STYLE_POSITION_OUTSIDE, + eCSSUnit_Enumerated); + } + if ((found & 4) == 0) { + // Provide default values + nsString type = (found & 1) ? + NS_LITERAL_STRING("none") : NS_LITERAL_STRING("disc"); + values[2].SetStringValue(type, eCSSUnit_Ident); + } + if ((found & 8) == 0) { + values[3].SetNoneValue(); + } + + // Start at 1 to avoid appending fake value. + for (uint32_t index = 1; index < ArrayLength(listStyleIDs); ++index) { + AppendValue(listStyleIDs[index], values[index]); + } + return true; +} + +bool +CSSParserImpl::ParseListStyleType(nsCSSValue& aValue) +{ + if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_STRING, + nullptr)) { + return true; + } + + if (ParseCounterStyleNameValue(aValue) || ParseSymbols(aValue)) { + return true; + } + + return false; +} + +bool +CSSParserImpl::ParseMargin() +{ + static const nsCSSPropertyID kMarginSideIDs[] = { + eCSSProperty_margin_top, + eCSSProperty_margin_right, + eCSSProperty_margin_bottom, + eCSSProperty_margin_left + }; + + return ParseBoxProperties(kMarginSideIDs); +} + +bool +CSSParserImpl::ParseObjectPosition() +{ + nsCSSValue value; + if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr) && + !ParsePositionValue(value)) { + return false; + } + AppendValue(eCSSProperty_object_position, value); + return true; +} + +bool +CSSParserImpl::ParseOutline() +{ + const int32_t numProps = 3; + static const nsCSSPropertyID kOutlineIDs[] = { + eCSSProperty_outline_color, + eCSSProperty_outline_style, + eCSSProperty_outline_width + }; + + nsCSSValue values[numProps]; + int32_t found = ParseChoice(values, kOutlineIDs, numProps); + if (found < 1) { + return false; + } + + // Provide default values + if ((found & 1) == 0) { // Provide default outline-color + values[0].SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor); + } + if ((found & 2) == 0) { // Provide default outline-style + values[1].SetIntValue(NS_STYLE_BORDER_STYLE_NONE, eCSSUnit_Enumerated); + } + if ((found & 4) == 0) { // Provide default outline-width + values[2].SetIntValue(NS_STYLE_BORDER_WIDTH_MEDIUM, eCSSUnit_Enumerated); + } + + int32_t index; + for (index = 0; index < numProps; index++) { + AppendValue(kOutlineIDs[index], values[index]); + } + return true; +} + +bool +CSSParserImpl::ParseOverflow() +{ + nsCSSValue overflow; + if (!ParseSingleTokenVariant(overflow, VARIANT_HK, + nsCSSProps::kOverflowKTable)) { + return false; + } + + nsCSSValue overflowX(overflow); + nsCSSValue overflowY(overflow); + if (eCSSUnit_Enumerated == overflow.GetUnit()) + switch(overflow.GetIntValue()) { + case NS_STYLE_OVERFLOW_SCROLLBARS_HORIZONTAL: + overflowX.SetIntValue(NS_STYLE_OVERFLOW_SCROLL, eCSSUnit_Enumerated); + overflowY.SetIntValue(NS_STYLE_OVERFLOW_HIDDEN, eCSSUnit_Enumerated); + break; + case NS_STYLE_OVERFLOW_SCROLLBARS_VERTICAL: + overflowX.SetIntValue(NS_STYLE_OVERFLOW_HIDDEN, eCSSUnit_Enumerated); + overflowY.SetIntValue(NS_STYLE_OVERFLOW_SCROLL, eCSSUnit_Enumerated); + break; + } + AppendValue(eCSSProperty_overflow_x, overflowX); + AppendValue(eCSSProperty_overflow_y, overflowY); + return true; +} + +bool +CSSParserImpl::ParsePadding() +{ + static const nsCSSPropertyID kPaddingSideIDs[] = { + eCSSProperty_padding_top, + eCSSProperty_padding_right, + eCSSProperty_padding_bottom, + eCSSProperty_padding_left + }; + + return ParseBoxProperties(kPaddingSideIDs); +} + +bool +CSSParserImpl::ParseQuotes() +{ + nsCSSValue value; + if (!ParseSingleTokenVariant(value, VARIANT_HOS, nullptr)) { + return false; + } + if (value.GetUnit() == eCSSUnit_String) { + nsCSSValue open = value; + nsCSSValuePairList* quotes = value.SetPairListValue(); + for (;;) { + quotes->mXValue = open; + // get mandatory close + if (!ParseSingleTokenVariant(quotes->mYValue, VARIANT_STRING, nullptr)) { + return false; + } + // look for another open + if (!ParseSingleTokenVariant(open, VARIANT_STRING, nullptr)) { + break; + } + quotes->mNext = new nsCSSValuePairList; + quotes = quotes->mNext; + } + } + AppendValue(eCSSProperty_quotes, value); + return true; +} + +bool +CSSParserImpl::ParseTextDecoration() +{ + static const nsCSSPropertyID kTextDecorationIDs[] = { + eCSSProperty_text_decoration_line, + eCSSProperty_text_decoration_style, + eCSSProperty_text_decoration_color + }; + const int32_t numProps = MOZ_ARRAY_LENGTH(kTextDecorationIDs); + nsCSSValue values[numProps]; + + int32_t found = ParseChoice(values, kTextDecorationIDs, numProps); + if (found < 1) { + return false; + } + + // Provide default values + if ((found & 1) == 0) { // Provide default text-decoration-line + values[0].SetIntValue(NS_STYLE_TEXT_DECORATION_LINE_NONE, + eCSSUnit_Enumerated); + } + if ((found & 2) == 0) { // Provide default text-decoration-style + values[1].SetIntValue(NS_STYLE_TEXT_DECORATION_STYLE_SOLID, + eCSSUnit_Enumerated); + } + if ((found & 4) == 0) { // Provide default text-decoration-color + values[2].SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor); + } + + for (int32_t index = 0; index < numProps; index++) { + AppendValue(kTextDecorationIDs[index], values[index]); + } + return true; +} + +bool +CSSParserImpl::ParseTextEmphasis() +{ + static constexpr nsCSSPropertyID kTextEmphasisIDs[] = { + eCSSProperty_text_emphasis_style, + eCSSProperty_text_emphasis_color + }; + constexpr int32_t numProps = MOZ_ARRAY_LENGTH(kTextEmphasisIDs); + nsCSSValue values[numProps]; + + int32_t found = ParseChoice(values, kTextEmphasisIDs, numProps); + if (found < 1) { + return false; + } + + if (!(found & 1)) { // Provide default text-emphasis-style + values[0].SetNoneValue(); + } + if (!(found & 2)) { // Provide default text-emphasis-color + values[1].SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor); + } + + for (int32_t index = 0; index < numProps; index++) { + AppendValue(kTextEmphasisIDs[index], values[index]); + } + return true; +} + +bool +CSSParserImpl::ParseTextEmphasisPosition(nsCSSValue& aValue) +{ + static_assert((NS_STYLE_TEXT_EMPHASIS_POSITION_OVER ^ + NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER ^ + NS_STYLE_TEXT_EMPHASIS_POSITION_LEFT ^ + NS_STYLE_TEXT_EMPHASIS_POSITION_RIGHT) == + (NS_STYLE_TEXT_EMPHASIS_POSITION_OVER | + NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER | + NS_STYLE_TEXT_EMPHASIS_POSITION_LEFT | + NS_STYLE_TEXT_EMPHASIS_POSITION_RIGHT), + "text-emphasis-position constants should be bitmasks"); + + if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT, nullptr)) { + return true; + } + + nsCSSValue first, second; + const auto& kTable = nsCSSProps::kTextEmphasisPositionKTable; + if (!ParseSingleTokenVariant(first, VARIANT_KEYWORD, kTable) || + !ParseSingleTokenVariant(second, VARIANT_KEYWORD, kTable)) { + return false; + } + + auto firstValue = first.GetIntValue(); + auto secondValue = second.GetIntValue(); + if ((firstValue == NS_STYLE_TEXT_EMPHASIS_POSITION_OVER || + firstValue == NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER) == + (secondValue == NS_STYLE_TEXT_EMPHASIS_POSITION_OVER || + secondValue == NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER)) { + return false; + } + + aValue.SetIntValue(firstValue | secondValue, eCSSUnit_Enumerated); + return true; +} + +bool +CSSParserImpl::ParseTextEmphasisStyle(nsCSSValue& aValue) +{ + static_assert((NS_STYLE_TEXT_EMPHASIS_STYLE_SHAPE_MASK ^ + NS_STYLE_TEXT_EMPHASIS_STYLE_FILL_MASK) == + (NS_STYLE_TEXT_EMPHASIS_STYLE_SHAPE_MASK | + NS_STYLE_TEXT_EMPHASIS_STYLE_FILL_MASK), + "text-emphasis-style shape and fill constants " + "should not intersect"); + static_assert(NS_STYLE_TEXT_EMPHASIS_STYLE_FILLED == 0, + "Making 'filled' zero ensures that if neither 'filled' nor " + "'open' is specified, we compute it to 'filled' per spec"); + + if (ParseSingleTokenVariant(aValue, VARIANT_HOS, nullptr)) { + return true; + } + + nsCSSValue first, second; + const auto& fillKTable = nsCSSProps::kTextEmphasisStyleFillKTable; + const auto& shapeKTable = nsCSSProps::kTextEmphasisStyleShapeKTable; + if (ParseSingleTokenVariant(first, VARIANT_KEYWORD, fillKTable)) { + ParseSingleTokenVariant(second, VARIANT_KEYWORD, shapeKTable); + } else if (ParseSingleTokenVariant(first, VARIANT_KEYWORD, shapeKTable)) { + ParseSingleTokenVariant(second, VARIANT_KEYWORD, fillKTable); + } else { + return false; + } + + auto value = first.GetIntValue(); + if (second.GetUnit() == eCSSUnit_Enumerated) { + value |= second.GetIntValue(); + } + aValue.SetIntValue(value, eCSSUnit_Enumerated); + return true; +} + +bool +CSSParserImpl::ParseTextAlign(nsCSSValue& aValue, const KTableEntry aTable[]) +{ + if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT, nullptr)) { + // 'inherit', 'initial' and 'unset' must be alone + return true; + } + + nsCSSValue left; + if (!ParseSingleTokenVariant(left, VARIANT_KEYWORD, aTable)) { + return false; + } + + if (!nsLayoutUtils::IsTextAlignUnsafeValueEnabled()) { + aValue = left; + return true; + } + + nsCSSValue right; + if (ParseSingleTokenVariant(right, VARIANT_KEYWORD, aTable)) { + // 'true' must be combined with some other value than 'true'. + if (left.GetIntValue() == NS_STYLE_TEXT_ALIGN_UNSAFE && + right.GetIntValue() == NS_STYLE_TEXT_ALIGN_UNSAFE) { + return false; + } + aValue.SetPairValue(left, right); + } else { + // Single value 'true' is not allowed. + if (left.GetIntValue() == NS_STYLE_TEXT_ALIGN_UNSAFE) { + return false; + } + aValue = left; + } + return true; +} + +bool +CSSParserImpl::ParseTextAlign(nsCSSValue& aValue) +{ + return ParseTextAlign(aValue, nsCSSProps::kTextAlignKTable); +} + +bool +CSSParserImpl::ParseTextAlignLast(nsCSSValue& aValue) +{ + return ParseTextAlign(aValue, nsCSSProps::kTextAlignLastKTable); +} + +bool +CSSParserImpl::ParseTextDecorationLine(nsCSSValue& aValue) +{ + static_assert((NS_STYLE_TEXT_DECORATION_LINE_NONE ^ + NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE ^ + NS_STYLE_TEXT_DECORATION_LINE_OVERLINE ^ + NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH ^ + NS_STYLE_TEXT_DECORATION_LINE_BLINK ^ + NS_STYLE_TEXT_DECORATION_LINE_PREF_ANCHORS) == + (NS_STYLE_TEXT_DECORATION_LINE_NONE | + NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE | + NS_STYLE_TEXT_DECORATION_LINE_OVERLINE | + NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH | + NS_STYLE_TEXT_DECORATION_LINE_BLINK | + NS_STYLE_TEXT_DECORATION_LINE_PREF_ANCHORS), + "text decoration constants need to be bitmasks"); + if (ParseSingleTokenVariant(aValue, VARIANT_HK, + nsCSSProps::kTextDecorationLineKTable)) { + if (eCSSUnit_Enumerated == aValue.GetUnit()) { + int32_t intValue = aValue.GetIntValue(); + if (intValue != NS_STYLE_TEXT_DECORATION_LINE_NONE) { + // look for more keywords + nsCSSValue keyword; + int32_t index; + for (index = 0; index < 3; index++) { + if (ParseEnum(keyword, nsCSSProps::kTextDecorationLineKTable)) { + int32_t newValue = keyword.GetIntValue(); + if (newValue == NS_STYLE_TEXT_DECORATION_LINE_NONE || + newValue & intValue) { + // 'none' keyword in conjuction with others is not allowed, and + // duplicate keyword is not allowed. + return false; + } + intValue |= newValue; + } + else { + break; + } + } + aValue.SetIntValue(intValue, eCSSUnit_Enumerated); + } + } + return true; + } + return false; +} + +bool +CSSParserImpl::ParseTextOverflow(nsCSSValue& aValue) +{ + if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT, nullptr)) { + // 'inherit', 'initial' and 'unset' must be alone + return true; + } + + nsCSSValue left; + if (!ParseSingleTokenVariant(left, VARIANT_KEYWORD | VARIANT_STRING, + nsCSSProps::kTextOverflowKTable)) + return false; + + nsCSSValue right; + if (ParseSingleTokenVariant(right, VARIANT_KEYWORD | VARIANT_STRING, + nsCSSProps::kTextOverflowKTable)) + aValue.SetPairValue(left, right); + else { + aValue = left; + } + return true; +} + +bool +CSSParserImpl::ParseTouchAction(nsCSSValue& aValue) +{ + // Avaliable values of property touch-action: + // auto | none | [pan-x || pan-y] | manipulation + + if (!ParseSingleTokenVariant(aValue, VARIANT_HK, + nsCSSProps::kTouchActionKTable)) { + return false; + } + + // Auto and None keywords aren't allowed in conjunction with others. + // Also inherit, initial and unset values are available. + if (eCSSUnit_Enumerated != aValue.GetUnit()) { + return true; + } + + int32_t intValue = aValue.GetIntValue(); + nsCSSValue nextValue; + if (ParseEnum(nextValue, nsCSSProps::kTouchActionKTable)) { + int32_t nextIntValue = nextValue.GetIntValue(); + + // duplicates aren't allowed. + if (nextIntValue & intValue) { + return false; + } + + // Auto and None and Manipulation is not allowed in conjunction with others. + if ((intValue | nextIntValue) & (NS_STYLE_TOUCH_ACTION_NONE | + NS_STYLE_TOUCH_ACTION_AUTO | + NS_STYLE_TOUCH_ACTION_MANIPULATION)) { + return false; + } + + aValue.SetIntValue(nextIntValue | intValue, eCSSUnit_Enumerated); + } + + return true; +} + +bool +CSSParserImpl::ParseTextCombineUpright(nsCSSValue& aValue) +{ + if (!ParseSingleTokenVariant(aValue, VARIANT_HK, + nsCSSProps::kTextCombineUprightKTable)) { + return false; + } + + // if 'digits', need to check for an explicit number [2, 3, 4] + if (eCSSUnit_Enumerated == aValue.GetUnit() && + aValue.GetIntValue() == NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_2) { + if (!nsLayoutUtils::TextCombineUprightDigitsEnabled()) { + return false; + } + if (!GetToken(true)) { + return true; + } + if (mToken.mType == eCSSToken_Number && mToken.mIntegerValid) { + switch (mToken.mInteger) { + case 2: // already set, nothing to do + break; + case 3: + aValue.SetIntValue(NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_3, + eCSSUnit_Enumerated); + break; + case 4: + aValue.SetIntValue(NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_4, + eCSSUnit_Enumerated); + break; + default: + // invalid digits value + return false; + } + } else { + UngetToken(); + } + } + return true; +} + +/////////////////////////////////////////////////////// +// transform Parsing Implementation + +/* Reads a function list of arguments and consumes the closing parenthesis. + * Do not call this function directly; it's meant to be called from + * ParseFunction. + */ +bool +CSSParserImpl::ParseFunctionInternals(const uint32_t aVariantMask[], + uint32_t aVariantMaskAll, + uint16_t aMinElems, + uint16_t aMaxElems, + InfallibleTArray<nsCSSValue> &aOutput) +{ + NS_ASSERTION((aVariantMask && !aVariantMaskAll) || + (!aVariantMask && aVariantMaskAll), + "only one of the two variant mask parameters can be set"); + + for (uint16_t index = 0; index < aMaxElems; ++index) { + nsCSSValue newValue; + uint32_t m = aVariantMaskAll ? aVariantMaskAll : aVariantMask[index]; + if (ParseVariant(newValue, m, nullptr) != CSSParseResult::Ok) { + break; + } + + if (nsCSSValue::IsFloatUnit(newValue.GetUnit())) { + // Clamp infinity or -infinity values to max float or -max float to avoid + // calculations with infinity. + newValue.SetFloatValue( + mozilla::clamped(newValue.GetFloatValue(), + -std::numeric_limits<float>::max(), + std::numeric_limits<float>::max()), + newValue.GetUnit()); + } + + aOutput.AppendElement(newValue); + + if (ExpectSymbol(',', true)) { + // Move on to the next argument if we see a comma. + continue; + } + + if (ExpectSymbol(')', true)) { + // Make sure we've read enough symbols if we see a closing parenthesis. + return (index + 1) >= aMinElems; + } + + // Only a comma or a closing parenthesis is valid after an argument. + break; + } + + // If we're here, we've hit an error without seeing a closing parenthesis or + // we've read too many elements without seeing a closing parenthesis. + SkipUntil(')'); + return false; +} + +/* Parses a function [ input of the form (a [, b]*) ] and stores it + * as an nsCSSValue that holds a function of the form + * function-name arg1 arg2 ... argN + * + * On error, the return value is false. + * + * @param aFunction The name of the function that we're reading. + * @param aAllowedTypes An array of values corresponding to the legal + * types for each element in the function. The zeroth element in the + * array corresponds to the first function parameter, etc. The length + * of this array _must_ be greater than or equal to aMaxElems or the + * behavior is undefined. If not null, aAllowTypesAll must be 0. + * @param aAllowedTypesAll If set, every element tested for these types + * @param aMinElems Minimum number of elements to read. Reading fewer than + * this many elements will result in the function failing. + * @param aMaxElems Maximum number of elements to read. Reading more than + * this many elements will result in the function failing. + * @param aValue (out) The value that was parsed. + */ +bool +CSSParserImpl::ParseFunction(nsCSSKeyword aFunction, + const uint32_t aAllowedTypes[], + uint32_t aAllowedTypesAll, + uint16_t aMinElems, uint16_t aMaxElems, + nsCSSValue &aValue) +{ + NS_ASSERTION((aAllowedTypes && !aAllowedTypesAll) || + (!aAllowedTypes && aAllowedTypesAll), + "only one of the two allowed type parameter can be set"); + typedef InfallibleTArray<nsCSSValue>::size_type arrlen_t; + + /* 2^16 - 2, so that if we have 2^16 - 2 transforms, we have 2^16 - 1 + * elements stored in the the nsCSSValue::Array. + */ + static const arrlen_t MAX_ALLOWED_ELEMS = 0xFFFE; + + /* Read in a list of values as an array, failing if we can't or if + * it's out of bounds. + * + * We reserve 16 entries in the foundValues array in order to avoid + * having to resize the array dynamically when parsing some well-formed + * functions. The number 16 is coming from the number of arguments that + * matrix3d() accepts. + */ + AutoTArray<nsCSSValue, 16> foundValues; + if (!ParseFunctionInternals(aAllowedTypes, aAllowedTypesAll, aMinElems, + aMaxElems, foundValues)) { + return false; + } + + /* + * In case the user has given us more than 2^16 - 2 arguments, + * we'll truncate them at 2^16 - 2 arguments. + */ + uint16_t numArgs = std::min(foundValues.Length(), MAX_ALLOWED_ELEMS); + RefPtr<nsCSSValue::Array> convertedArray = + aValue.InitFunction(aFunction, numArgs); + + /* Copy things over. */ + for (uint16_t index = 0; index < numArgs; ++index) + convertedArray->Item(index + 1) = foundValues[static_cast<arrlen_t>(index)]; + + /* Return it! */ + return true; +} + +/** + * Given a token, determines the minimum and maximum number of function + * parameters to read, along with the mask that should be used to read + * those function parameters. If the token isn't a transform function, + * returns an error. + * + * @param aToken The token identifying the function. + * @param aIsPrefixed If true, parse matrices using the matrix syntax + * for -moz-transform. + * @param aDisallowRelativeValues If true, only allow variants that are + * numbers or have non-relative dimensions. + * @param aMinElems [out] The minimum number of elements to read. + * @param aMaxElems [out] The maximum number of elements to read + * @param aVariantMask [out] The variant mask to use during parsing + * @return Whether the information was loaded successfully. + */ +static bool GetFunctionParseInformation(nsCSSKeyword aToken, + bool aIsPrefixed, + bool aDisallowRelativeValues, + uint16_t &aMinElems, + uint16_t &aMaxElems, + const uint32_t *& aVariantMask) +{ +/* These types represent the common variant masks that will be used to + * parse out the individual functions. The order in the enumeration + * must match the order in which the masks are declared. + */ + enum { eLengthPercentCalc, + eLengthCalc, + eAbsoluteLengthCalc, + eTwoLengthPercentCalcs, + eTwoAbsoluteLengthCalcs, + eTwoLengthPercentCalcsOneLengthCalc, + eThreeAbsoluteLengthCalc, + eAngle, + eTwoAngles, + eNumber, + eNonNegativeLength, + eNonNegativeAbsoluteLength, + eTwoNumbers, + eThreeNumbers, + eThreeNumbersOneAngle, + eMatrix, + eMatrixPrefixed, + eMatrix3d, + eMatrix3dPrefixed, + eNumVariantMasks }; + static const int32_t kMaxElemsPerFunction = 16; + static const uint32_t kVariantMasks[eNumVariantMasks][kMaxElemsPerFunction] = { + {VARIANT_LPCALC}, + {VARIANT_LCALC}, + {VARIANT_LB}, + {VARIANT_LPCALC, VARIANT_LPCALC}, + {VARIANT_LBCALC, VARIANT_LBCALC}, + {VARIANT_LPCALC, VARIANT_LPCALC, VARIANT_LCALC}, + {VARIANT_LBCALC, VARIANT_LBCALC, VARIANT_LBCALC}, + {VARIANT_ANGLE_OR_ZERO}, + {VARIANT_ANGLE_OR_ZERO, VARIANT_ANGLE_OR_ZERO}, + {VARIANT_NUMBER}, + {VARIANT_LENGTH|VARIANT_NONNEGATIVE_DIMENSION}, + {VARIANT_LB|VARIANT_NONNEGATIVE_DIMENSION}, + {VARIANT_NUMBER, VARIANT_NUMBER}, + {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER}, + {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_ANGLE_OR_ZERO}, + {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, + VARIANT_NUMBER, VARIANT_NUMBER}, + {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, + VARIANT_LPNCALC, VARIANT_LPNCALC}, + {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, + VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, + VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, + VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER}, + {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, + VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, + VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, + VARIANT_LPNCALC, VARIANT_LPNCALC, VARIANT_LNCALC, VARIANT_NUMBER}}; + // Map from a mask to a congruent mask that excludes relative variants. + static const int32_t kNonRelativeVariantMap[eNumVariantMasks] = { + eAbsoluteLengthCalc, + eAbsoluteLengthCalc, + eAbsoluteLengthCalc, + eTwoAbsoluteLengthCalcs, + eTwoAbsoluteLengthCalcs, + eThreeAbsoluteLengthCalc, + eThreeAbsoluteLengthCalc, + eAngle, + eTwoAngles, + eNumber, + eNonNegativeAbsoluteLength, + eNonNegativeAbsoluteLength, + eTwoNumbers, + eThreeNumbers, + eThreeNumbersOneAngle, + eMatrix, + eMatrix, + eMatrix3d, + eMatrix3d }; + +#ifdef DEBUG + static const uint8_t kVariantMaskLengths[eNumVariantMasks] = + {1, 1, 1, 2, 2, 3, 3, 1, 2, 1, 1, 1, 2, 3, 4, 6, 6, 16, 16}; +#endif + + int32_t variantIndex = eNumVariantMasks; + + switch (aToken) { + case eCSSKeyword_translatex: + case eCSSKeyword_translatey: + /* Exactly one length or percent. */ + variantIndex = eLengthPercentCalc; + aMinElems = 1U; + aMaxElems = 1U; + break; + case eCSSKeyword_translatez: + /* Exactly one length */ + variantIndex = eLengthCalc; + aMinElems = 1U; + aMaxElems = 1U; + break; + case eCSSKeyword_translate3d: + /* Exactly two lengthds or percents and a number */ + variantIndex = eTwoLengthPercentCalcsOneLengthCalc; + aMinElems = 3U; + aMaxElems = 3U; + break; + case eCSSKeyword_scalez: + case eCSSKeyword_scalex: + case eCSSKeyword_scaley: + /* Exactly one scale factor. */ + variantIndex = eNumber; + aMinElems = 1U; + aMaxElems = 1U; + break; + case eCSSKeyword_scale3d: + /* Exactly three scale factors. */ + variantIndex = eThreeNumbers; + aMinElems = 3U; + aMaxElems = 3U; + break; + case eCSSKeyword_rotatex: + case eCSSKeyword_rotatey: + case eCSSKeyword_rotate: + case eCSSKeyword_rotatez: + /* Exactly one angle. */ + variantIndex = eAngle; + aMinElems = 1U; + aMaxElems = 1U; + break; + case eCSSKeyword_rotate3d: + variantIndex = eThreeNumbersOneAngle; + aMinElems = 4U; + aMaxElems = 4U; + break; + case eCSSKeyword_translate: + /* One or two lengths or percents. */ + variantIndex = eTwoLengthPercentCalcs; + aMinElems = 1U; + aMaxElems = 2U; + break; + case eCSSKeyword_skew: + /* Exactly one or two angles. */ + variantIndex = eTwoAngles; + aMinElems = 1U; + aMaxElems = 2U; + break; + case eCSSKeyword_scale: + /* One or two scale factors. */ + variantIndex = eTwoNumbers; + aMinElems = 1U; + aMaxElems = 2U; + break; + case eCSSKeyword_skewx: + /* Exactly one angle. */ + variantIndex = eAngle; + aMinElems = 1U; + aMaxElems = 1U; + break; + case eCSSKeyword_skewy: + /* Exactly one angle. */ + variantIndex = eAngle; + aMinElems = 1U; + aMaxElems = 1U; + break; + case eCSSKeyword_matrix: + /* Six values, all numbers. */ + variantIndex = aIsPrefixed ? eMatrixPrefixed : eMatrix; + aMinElems = 6U; + aMaxElems = 6U; + break; + case eCSSKeyword_matrix3d: + /* 16 matrix values, all numbers */ + variantIndex = aIsPrefixed ? eMatrix3dPrefixed : eMatrix3d; + aMinElems = 16U; + aMaxElems = 16U; + break; + case eCSSKeyword_perspective: + /* Exactly one scale number. */ + variantIndex = eNonNegativeLength; + aMinElems = 1U; + aMaxElems = 1U; + break; + default: + /* Oh dear, we didn't match. Report an error. */ + return false; + } + + if (aDisallowRelativeValues) { + variantIndex = kNonRelativeVariantMap[variantIndex]; + } + + NS_ASSERTION(aMinElems > 0, "Didn't update minimum elements!"); + NS_ASSERTION(aMaxElems > 0, "Didn't update maximum elements!"); + NS_ASSERTION(aMinElems <= aMaxElems, "aMinElems > aMaxElems!"); + NS_ASSERTION(variantIndex >= 0, "Invalid variant mask!"); + NS_ASSERTION(variantIndex < eNumVariantMasks, "Invalid variant mask!"); +#ifdef DEBUG + NS_ASSERTION(aMaxElems <= kVariantMaskLengths[variantIndex], + "Invalid aMaxElems for this variant mask."); +#endif + + // Convert the index into a mask. + aVariantMask = kVariantMasks[variantIndex]; + + return true; +} + +bool CSSParserImpl::ParseWillChange() +{ + nsCSSValue listValue; + nsCSSValueList* currentListValue = listValue.SetListValue(); + bool first = true; + for (;;) { + const uint32_t variantMask = VARIANT_IDENTIFIER | + VARIANT_INHERIT | + VARIANT_NONE | + VARIANT_ALL | + VARIANT_AUTO; + nsCSSValue value; + if (!ParseSingleTokenVariant(value, variantMask, nullptr)) { + return false; + } + + if (value.GetUnit() == eCSSUnit_None || + value.GetUnit() == eCSSUnit_All) + { + return false; + } + + if (value.GetUnit() != eCSSUnit_Ident) { + if (first) { + AppendValue(eCSSProperty_will_change, value); + return true; + } else { + return false; + } + } + + nsString str; + value.GetStringValue(str); + if (str.LowerCaseEqualsLiteral("default") || + str.LowerCaseEqualsLiteral("will-change")) { + return false; + } + + currentListValue->mValue = value; + + if (!ExpectSymbol(',', true)) { + break; + } + currentListValue->mNext = new nsCSSValueList; + currentListValue = currentListValue->mNext; + first = false; + } + + AppendValue(eCSSProperty_will_change, listValue); + return true; +} + +/* Reads a single transform function from the tokenizer stream, reporting an + * error if something goes wrong. + */ +bool +CSSParserImpl::ParseSingleTransform(bool aIsPrefixed, + bool aDisallowRelativeValues, + nsCSSValue& aValue) +{ + if (!GetToken(true)) + return false; + + if (mToken.mType != eCSSToken_Function) { + UngetToken(); + return false; + } + + const uint32_t* variantMask; + uint16_t minElems, maxElems; + nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent); + + if (!GetFunctionParseInformation(keyword, aIsPrefixed, + aDisallowRelativeValues, + minElems, maxElems, + variantMask)) + return false; + + return ParseFunction(keyword, variantMask, 0, minElems, maxElems, aValue); +} + +/* Parses a transform property list by continuously reading in properties + * and constructing a matrix from it. + */ +bool +CSSParserImpl::ParseTransform(bool aIsPrefixed, bool aDisallowRelativeValues) +{ + nsCSSValue value; + // 'inherit', 'initial', 'unset' and 'none' must be alone + if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE, + nullptr)) { + nsCSSValueSharedList* list = new nsCSSValueSharedList; + value.SetSharedListValue(list); + list->mHead = new nsCSSValueList; + nsCSSValueList* cur = list->mHead; + for (;;) { + if (!ParseSingleTransform(aIsPrefixed, aDisallowRelativeValues, + cur->mValue)) { + return false; + } + if (CheckEndProperty()) { + break; + } + cur->mNext = new nsCSSValueList; + cur = cur->mNext; + } + } + AppendValue(eCSSProperty_transform, value); + return true; +} + +/* Reads a polygon function's argument list. + */ +bool +CSSParserImpl::ParsePolygonFunction(nsCSSValue& aValue) +{ + uint16_t numArgs = 1; + + nsCSSValue fillRuleValue; + if (ParseEnum(fillRuleValue, nsCSSProps::kFillRuleKTable)) { + numArgs++; + + // The fill-rule must be comma separated from the polygon points. + if (!ExpectSymbol(',', true)) { + REPORT_UNEXPECTED_TOKEN(PEExpectedComma); + SkipUntil(')'); + return false; + } + } + + nsCSSValue coordinates; + nsCSSValuePairList* item = coordinates.SetPairListValue(); + for (;;) { + nsCSSValue xValue, yValue; + if (ParseVariant(xValue, VARIANT_LPCALC, nullptr) != CSSParseResult::Ok || + ParseVariant(yValue, VARIANT_LPCALC, nullptr) != CSSParseResult::Ok) { + REPORT_UNEXPECTED_TOKEN(PECoordinatePair); + SkipUntil(')'); + return false; + } + item->mXValue = xValue; + item->mYValue = yValue; + + // See whether to continue or whether to look for end of function. + if (!ExpectSymbol(',', true)) { + // We need to read the closing parenthesis. + if (!ExpectSymbol(')', true)) { + REPORT_UNEXPECTED_TOKEN(PEExpectedCloseParen); + SkipUntil(')'); + return false; + } + break; + } + item->mNext = new nsCSSValuePairList; + item = item->mNext; + } + + RefPtr<nsCSSValue::Array> functionArray = + aValue.InitFunction(eCSSKeyword_polygon, numArgs); + functionArray->Item(numArgs) = coordinates; + if (numArgs > 1) { + functionArray->Item(1) = fillRuleValue; + } + + return true; +} + +bool +CSSParserImpl::ParseCircleOrEllipseFunction(nsCSSKeyword aKeyword, + nsCSSValue& aValue) +{ + nsCSSValue radiusX, radiusY, position; + bool hasRadius = false, hasPosition = false; + + int32_t mask = VARIANT_LPCALC | VARIANT_NONNEGATIVE_DIMENSION | + VARIANT_KEYWORD; + CSSParseResult result = + ParseVariant(radiusX, mask, nsCSSProps::kShapeRadiusKTable); + if (result == CSSParseResult::Error) { + return false; + } else if (result == CSSParseResult::Ok) { + if (aKeyword == eCSSKeyword_ellipse) { + if (ParseVariant(radiusY, mask, nsCSSProps::kShapeRadiusKTable) != + CSSParseResult::Ok) { + REPORT_UNEXPECTED_TOKEN(PEExpectedRadius); + SkipUntil(')'); + return false; + } + } + hasRadius = true; + } + + if (!ExpectSymbol(')', true)) { + if (!GetToken(true)) { + REPORT_UNEXPECTED_EOF(PEPositionEOF); + return false; + } + + if (mToken.mType != eCSSToken_Ident || + !mToken.mIdent.LowerCaseEqualsLiteral("at") || + !ParsePositionValueForBasicShape(position)) { + REPORT_UNEXPECTED_TOKEN(PEExpectedPosition); + SkipUntil(')'); + return false; + } + if (!ExpectSymbol(')', true)) { + REPORT_UNEXPECTED_TOKEN(PEExpectedCloseParen); + SkipUntil(')'); + return false; + } + hasPosition = true; + } + + size_t count = aKeyword == eCSSKeyword_circle ? 2 : 3; + RefPtr<nsCSSValue::Array> functionArray = + aValue.InitFunction(aKeyword, count); + if (hasRadius) { + functionArray->Item(1) = radiusX; + if (aKeyword == eCSSKeyword_ellipse) { + functionArray->Item(2) = radiusY; + } + } + if (hasPosition) { + functionArray->Item(count) = position; + } + + return true; +} + +bool +CSSParserImpl::ParseInsetFunction(nsCSSValue& aValue) +{ + RefPtr<nsCSSValue::Array> functionArray = + aValue.InitFunction(eCSSKeyword_inset, 5); + + int count = 0; + while (count < 4) { + CSSParseResult result = + ParseVariant(functionArray->Item(count + 1), VARIANT_LPCALC, nullptr); + if (result == CSSParseResult::Error) { + count = 0; + break; + } else if (result == CSSParseResult::NotFound) { + break; + } + ++count; + } + + if (count == 0) { + REPORT_UNEXPECTED_TOKEN(PEExpectedShapeArg); + SkipUntil(')'); + return false; + } + + if (!ExpectSymbol(')', true)) { + if (!GetToken(true)) { + NS_NOTREACHED("ExpectSymbol should have returned true"); + return false; + } + + RefPtr<nsCSSValue::Array> radiusArray = nsCSSValue::Array::Create(4); + functionArray->Item(5).SetArrayValue(radiusArray, eCSSUnit_Array); + if (mToken.mType != eCSSToken_Ident || + !mToken.mIdent.LowerCaseEqualsLiteral("round") || + !ParseBoxCornerRadiiInternals(radiusArray->ItemStorage())) { + REPORT_UNEXPECTED_TOKEN(PEExpectedRadius); + SkipUntil(')'); + return false; + } + + if (!ExpectSymbol(')', true)) { + REPORT_UNEXPECTED_TOKEN(PEExpectedCloseParen); + SkipUntil(')'); + return false; + } + } + + return true; +} + +bool +CSSParserImpl::ParseBasicShape(nsCSSValue& aValue, bool* aConsumedTokens) +{ + if (!GetToken(true)) { + return false; + } + + if (mToken.mType != eCSSToken_Function) { + UngetToken(); + return false; + } + + // Specific shape function parsing always consumes tokens. + *aConsumedTokens = true; + nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent); + switch (keyword) { + case eCSSKeyword_polygon: + return ParsePolygonFunction(aValue); + case eCSSKeyword_circle: + case eCSSKeyword_ellipse: + return ParseCircleOrEllipseFunction(keyword, aValue); + case eCSSKeyword_inset: + return ParseInsetFunction(aValue); + default: + return false; + } +} + +bool +CSSParserImpl::ParseReferenceBoxAndBasicShape( + nsCSSValue& aValue, + const KTableEntry aBoxKeywordTable[]) +{ + nsCSSValue referenceBox; + bool hasBox = ParseEnum(referenceBox, aBoxKeywordTable); + + const bool boxCameFirst = hasBox; + + nsCSSValue basicShape; + bool basicShapeConsumedTokens = false; + bool hasShape = ParseBasicShape(basicShape, &basicShapeConsumedTokens); + + // Parsing wasn't successful if ParseBasicShape consumed tokens but failed + // or if the token was neither a reference box nor a basic shape. + if ((!hasShape && basicShapeConsumedTokens) || (!hasBox && !hasShape)) { + return false; + } + + // Check if the second argument is a reference box if the first wasn't. + if (!hasBox) { + hasBox = ParseEnum(referenceBox, aBoxKeywordTable); + } + + RefPtr<nsCSSValue::Array> fullValue = + nsCSSValue::Array::Create((hasBox && hasShape) ? 2 : 1); + + if (hasBox && hasShape) { + fullValue->Item(boxCameFirst ? 0 : 1) = referenceBox; + fullValue->Item(boxCameFirst ? 1 : 0) = basicShape; + } else if (hasBox) { + fullValue->Item(0) = referenceBox; + } else { + MOZ_ASSERT(hasShape, "should've bailed if we got neither box nor shape"); + fullValue->Item(0) = basicShape; + } + + aValue.SetArrayValue(fullValue, eCSSUnit_Array); + return true; +} + +// Parse a clip-path url to a <clipPath> element or a basic shape. +bool +CSSParserImpl::ParseClipPath(nsCSSValue& aValue) +{ + if (ParseSingleTokenVariant(aValue, VARIANT_HUO, nullptr)) { + return true; + } + + if (!nsLayoutUtils::CSSClipPathShapesEnabled()) { + // With CSS Clip Path Shapes disabled, we should only accept + // SVG clipPath reference and none. + REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURL); + return false; + } + + return ParseReferenceBoxAndBasicShape( + aValue, nsCSSProps::kClipPathGeometryBoxKTable); +} + +// none | [ <basic-shape> || <shape-box> ] | <image> +bool +CSSParserImpl::ParseShapeOutside(nsCSSValue& aValue) +{ + if (ParseSingleTokenVariant(aValue, VARIANT_HUO, nullptr)) { + // 'inherit', 'initial', 'unset', 'none', and <image> url must be alone. + return true; + } + + return ParseReferenceBoxAndBasicShape( + aValue, nsCSSProps::kShapeOutsideShapeBoxKTable); +} + +bool CSSParserImpl::ParseTransformOrigin(bool aPerspective) +{ + nsCSSValuePair position; + if (!ParseBoxPositionValues(position, true)) + return false; + + nsCSSPropertyID prop = eCSSProperty_transform_origin; + if (aPerspective) { + prop = eCSSProperty_perspective_origin; + } + + // Unlike many other uses of pairs, this position should always be stored + // as a pair, even if the values are the same, so it always serializes as + // a pair, and to keep the computation code simple. + if (position.mXValue.GetUnit() == eCSSUnit_Inherit || + position.mXValue.GetUnit() == eCSSUnit_Initial || + position.mXValue.GetUnit() == eCSSUnit_Unset) { + MOZ_ASSERT(position.mXValue == position.mYValue, + "inherit/initial/unset only half?"); + AppendValue(prop, position.mXValue); + } else { + nsCSSValue value; + if (aPerspective) { + value.SetPairValue(position.mXValue, position.mYValue); + } else { + nsCSSValue depth; + CSSParseResult result = + ParseVariant(depth, VARIANT_LENGTH | VARIANT_CALC, nullptr); + if (result == CSSParseResult::Error) { + return false; + } else if (result == CSSParseResult::NotFound) { + depth.SetFloatValue(0.0f, eCSSUnit_Pixel); + } + value.SetTripletValue(position.mXValue, position.mYValue, depth); + } + + AppendValue(prop, value); + } + return true; +} + +/** + * Reads a drop-shadow value. At the moment the Filter Effects specification + * just expects one shadow item. Should this ever change to a list of shadow + * items, use ParseShadowList instead. + */ +bool +CSSParserImpl::ParseDropShadow(nsCSSValue* aValue) +{ + // Use nsCSSValueList to reuse the shadow resolving code in + // nsRuleNode and nsComputedDOMStyle. + nsCSSValue shadow; + nsCSSValueList* cur = shadow.SetListValue(); + if (!ParseShadowItem(cur->mValue, false)) + return false; + + if (!ExpectSymbol(')', true)) + return false; + + nsCSSValue::Array* dropShadow = aValue->InitFunction(eCSSKeyword_drop_shadow, 1); + + // Copy things over. + dropShadow->Item(1) = shadow; + + return true; +} + +/** + * Reads a single url or filter function from the tokenizer stream, reporting an + * error if something goes wrong. + */ +bool +CSSParserImpl::ParseSingleFilter(nsCSSValue* aValue) +{ + if (ParseSingleTokenVariant(*aValue, VARIANT_URL, nullptr)) { + return true; + } + + if (!nsLayoutUtils::CSSFiltersEnabled()) { + // With CSS Filters disabled, we should only accept an SVG reference filter. + REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURL); + return false; + } + + if (!GetToken(true)) { + REPORT_UNEXPECTED_EOF(PEFilterEOF); + return false; + } + + if (mToken.mType != eCSSToken_Function) { + REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURLOrFilterFunction); + UngetToken(); + return false; + } + + nsCSSKeyword functionName = nsCSSKeywords::LookupKeyword(mToken.mIdent); + // Parse drop-shadow independently of the other filter functions + // because of its more complex characteristics. + if (functionName == eCSSKeyword_drop_shadow) { + if (ParseDropShadow(aValue)) { + return true; + } else { + // Unrecognized filter function. + REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURLOrFilterFunction); + SkipUntil(')'); + return false; + } + } + + // Set up the parsing rules based on the filter function. + uint32_t variantMask = VARIANT_PN; + bool rejectNegativeArgument = true; + bool clampArgumentToOne = false; + switch (functionName) { + case eCSSKeyword_blur: + variantMask = VARIANT_LCALC | VARIANT_NONNEGATIVE_DIMENSION; + // VARIANT_NONNEGATIVE_DIMENSION will already reject negative lengths. + rejectNegativeArgument = false; + break; + case eCSSKeyword_brightness: + case eCSSKeyword_contrast: + case eCSSKeyword_saturate: + break; + case eCSSKeyword_grayscale: + case eCSSKeyword_invert: + case eCSSKeyword_sepia: + case eCSSKeyword_opacity: + clampArgumentToOne = true; + break; + case eCSSKeyword_hue_rotate: + variantMask = VARIANT_ANGLE; + rejectNegativeArgument = false; + break; + default: + // Unrecognized filter function. + REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURLOrFilterFunction); + SkipUntil(')'); + return false; + } + + // Parse the function. + uint16_t minElems = 1U; + uint16_t maxElems = 1U; + uint32_t allVariants = 0; + if (!ParseFunction(functionName, &variantMask, allVariants, + minElems, maxElems, *aValue)) { + REPORT_UNEXPECTED(PEFilterFunctionArgumentsParsingError); + return false; + } + + // Get the first and only argument to the filter function. + MOZ_ASSERT(aValue->GetUnit() == eCSSUnit_Function, + "expected a filter function"); + MOZ_ASSERT(aValue->UnitHasArrayValue(), + "filter function should be an array"); + MOZ_ASSERT(aValue->GetArrayValue()->Count() == 2, + "filter function should have exactly one argument"); + nsCSSValue& arg = aValue->GetArrayValue()->Item(1); + + if (rejectNegativeArgument && + ((arg.GetUnit() == eCSSUnit_Percent && arg.GetPercentValue() < 0.0f) || + (arg.GetUnit() == eCSSUnit_Number && arg.GetFloatValue() < 0.0f))) { + REPORT_UNEXPECTED(PEExpectedNonnegativeNP); + return false; + } + + if (clampArgumentToOne) { + if (arg.GetUnit() == eCSSUnit_Number && + arg.GetFloatValue() > 1.0f) { + arg.SetFloatValue(1.0f, arg.GetUnit()); + } else if (arg.GetUnit() == eCSSUnit_Percent && + arg.GetPercentValue() > 1.0f) { + arg.SetPercentValue(1.0f); + } + } + + return true; +} + +/** + * Parses a filter property value by continuously reading in urls and/or filter + * functions and constructing a list. + * + * When CSS Filters are enabled, the filter property accepts one or more SVG + * reference filters and/or CSS filter functions. + * e.g. filter: url(#my-filter-1) blur(3px) url(#my-filter-2) grayscale(50%); + * + * When CSS Filters are disabled, the filter property only accepts one SVG + * reference filter. + * e.g. filter: url(#my-filter); + */ +bool +CSSParserImpl::ParseFilter() +{ + nsCSSValue value; + // 'inherit', 'initial', 'unset' and 'none' must be alone + if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE, + nullptr)) { + nsCSSValueList* cur = value.SetListValue(); + while (cur) { + if (!ParseSingleFilter(&cur->mValue)) { + return false; + } + if (CheckEndProperty()) { + break; + } + if (!nsLayoutUtils::CSSFiltersEnabled()) { + // With CSS Filters disabled, we should only accept one SVG reference + // filter. + REPORT_UNEXPECTED_TOKEN(PEExpectEndValue); + return false; + } + cur->mNext = new nsCSSValueList; + cur = cur->mNext; + } + } + AppendValue(eCSSProperty_filter, value); + return true; +} + +bool +CSSParserImpl::ParseTransitionProperty() +{ + nsCSSValue value; + // 'inherit', 'initial', 'unset' and 'none' must be alone + if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE, + nullptr)) { + // Accept a list of arbitrary identifiers. They should be + // CSS properties, but we want to accept any so that we + // accept properties that we don't know about yet, e.g. + // transition-property: invalid-property, left, opacity; + nsCSSValueList* cur = value.SetListValue(); + for (;;) { + if (!ParseSingleTokenVariant(cur->mValue, + VARIANT_IDENTIFIER | VARIANT_ALL, + nullptr)) { + return false; + } + if (cur->mValue.GetUnit() == eCSSUnit_Ident) { + nsDependentString str(cur->mValue.GetStringBufferValue()); + // Exclude 'none', 'inherit', 'initial' and 'unset' according to the + // same rules as for 'counter-reset' in CSS 2.1. + if (str.LowerCaseEqualsLiteral("none") || + str.LowerCaseEqualsLiteral("inherit") || + str.LowerCaseEqualsLiteral("initial") || + (str.LowerCaseEqualsLiteral("unset") && + nsLayoutUtils::UnsetValueEnabled())) { + return false; + } + } + if (!ExpectSymbol(',', true)) { + break; + } + cur->mNext = new nsCSSValueList; + cur = cur->mNext; + } + } + AppendValue(eCSSProperty_transition_property, value); + return true; +} + +bool +CSSParserImpl::ParseTransitionTimingFunctionValues(nsCSSValue& aValue) +{ + NS_ASSERTION(!mHavePushBack && + mToken.mType == eCSSToken_Function && + mToken.mIdent.LowerCaseEqualsLiteral("cubic-bezier"), + "unexpected initial state"); + + RefPtr<nsCSSValue::Array> val = nsCSSValue::Array::Create(4); + + float x1, x2, y1, y2; + if (!ParseTransitionTimingFunctionValueComponent(x1, ',', true) || + !ParseTransitionTimingFunctionValueComponent(y1, ',', false) || + !ParseTransitionTimingFunctionValueComponent(x2, ',', true) || + !ParseTransitionTimingFunctionValueComponent(y2, ')', false)) { + return false; + } + + val->Item(0).SetFloatValue(x1, eCSSUnit_Number); + val->Item(1).SetFloatValue(y1, eCSSUnit_Number); + val->Item(2).SetFloatValue(x2, eCSSUnit_Number); + val->Item(3).SetFloatValue(y2, eCSSUnit_Number); + + aValue.SetArrayValue(val, eCSSUnit_Cubic_Bezier); + + return true; +} + +bool +CSSParserImpl::ParseTransitionTimingFunctionValueComponent(float& aComponent, + char aStop, + bool aIsXPoint) +{ + if (!GetToken(true)) { + return false; + } + nsCSSToken* tk = &mToken; + if (tk->mType == eCSSToken_Number) { + float num = tk->mNumber; + + // Clamp infinity or -infinity values to max float or -max float to avoid + // calculations with infinity. + num = mozilla::clamped(num, -std::numeric_limits<float>::max(), + std::numeric_limits<float>::max()); + + // X control point should be inside [0, 1] range. + if (aIsXPoint && (num < 0.0 || num > 1.0)) { + return false; + } + aComponent = num; + if (ExpectSymbol(aStop, true)) { + return true; + } + } + return false; +} + +bool +CSSParserImpl::ParseTransitionStepTimingFunctionValues(nsCSSValue& aValue) +{ + NS_ASSERTION(!mHavePushBack && + mToken.mType == eCSSToken_Function && + mToken.mIdent.LowerCaseEqualsLiteral("steps"), + "unexpected initial state"); + + RefPtr<nsCSSValue::Array> val = nsCSSValue::Array::Create(2); + + if (!ParseSingleTokenOneOrLargerVariant(val->Item(0), VARIANT_INTEGER, + nullptr)) { + return false; + } + + int32_t type = -1; // indicates an implicit end value + if (ExpectSymbol(',', true)) { + if (!GetToken(true)) { + return false; + } + if (mToken.mType == eCSSToken_Ident) { + if (mToken.mIdent.LowerCaseEqualsLiteral("start")) { + type = NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_START; + } else if (mToken.mIdent.LowerCaseEqualsLiteral("end")) { + type = NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_END; + } + } + if (type == -1) { + UngetToken(); + return false; + } + } + val->Item(1).SetIntValue(type, eCSSUnit_Enumerated); + + if (!ExpectSymbol(')', true)) { + return false; + } + + aValue.SetArrayValue(val, eCSSUnit_Steps); + return true; +} + +static nsCSSValueList* +AppendValueToList(nsCSSValue& aContainer, + nsCSSValueList* aTail, + const nsCSSValue& aValue) +{ + nsCSSValueList* entry; + if (aContainer.GetUnit() == eCSSUnit_Null) { + MOZ_ASSERT(!aTail, "should not have an entry"); + entry = aContainer.SetListValue(); + } else { + MOZ_ASSERT(!aTail->mNext, "should not have a next entry"); + MOZ_ASSERT(aContainer.GetUnit() == eCSSUnit_List, "not a list"); + entry = new nsCSSValueList; + aTail->mNext = entry; + } + entry->mValue = aValue; + return entry; +} + +CSSParserImpl::ParseAnimationOrTransitionShorthandResult +CSSParserImpl::ParseAnimationOrTransitionShorthand( + const nsCSSPropertyID* aProperties, + const nsCSSValue* aInitialValues, + nsCSSValue* aValues, + size_t aNumProperties) +{ + nsCSSValue tempValue; + // first see if 'inherit', 'initial' or 'unset' is specified. If one is, + // it can be the only thing specified, so don't attempt to parse any + // additional properties + if (ParseSingleTokenVariant(tempValue, VARIANT_INHERIT, nullptr)) { + for (uint32_t i = 0; i < aNumProperties; ++i) { + AppendValue(aProperties[i], tempValue); + } + return eParseAnimationOrTransitionShorthand_Inherit; + } + + static const size_t maxNumProperties = 8; + MOZ_ASSERT(aNumProperties <= maxNumProperties, + "can't handle this many properties"); + nsCSSValueList *cur[maxNumProperties]; + bool parsedProperty[maxNumProperties]; + + for (size_t i = 0; i < aNumProperties; ++i) { + cur[i] = nullptr; + } + bool atEOP = false; // at end of property? + for (;;) { // loop over comma-separated transitions or animations + // whether a particular subproperty was specified for this + // transition or animation + bool haveAnyProperty = false; + for (size_t i = 0; i < aNumProperties; ++i) { + parsedProperty[i] = false; + } + for (;;) { // loop over values within a transition or animation + bool foundProperty = false; + // check to see if we're at the end of one full transition or + // animation definition (either because we hit a comma or because + // we hit the end of the property definition) + if (ExpectSymbol(',', true)) + break; + if (CheckEndProperty()) { + atEOP = true; + break; + } + + // else, try to parse the next transition or animation sub-property + for (uint32_t i = 0; !foundProperty && i < aNumProperties; ++i) { + if (!parsedProperty[i]) { + // if we haven't found this property yet, try to parse it + CSSParseResult result = + ParseSingleValueProperty(tempValue, aProperties[i]); + if (result == CSSParseResult::Error) { + return eParseAnimationOrTransitionShorthand_Error; + } + if (result == CSSParseResult::Ok) { + parsedProperty[i] = true; + cur[i] = AppendValueToList(aValues[i], cur[i], tempValue); + foundProperty = true; + haveAnyProperty = true; + break; // out of inner loop; continue looking for next sub-property + } + } + } + if (!foundProperty) { + // We're not at a ',' or at the end of the property, but we couldn't + // parse any of the sub-properties, so the declaration is invalid. + return eParseAnimationOrTransitionShorthand_Error; + } + } + + if (!haveAnyProperty) { + // Got an empty item. + return eParseAnimationOrTransitionShorthand_Error; + } + + // We hit the end of the property or the end of one transition + // or animation definition, add its components to the list. + for (uint32_t i = 0; i < aNumProperties; ++i) { + // If all of the subproperties were not explicitly specified, fill + // in the missing ones with initial values. + if (!parsedProperty[i]) { + cur[i] = AppendValueToList(aValues[i], cur[i], aInitialValues[i]); + } + } + + if (atEOP) + break; + // else we just hit a ',' so continue parsing the next compound transition + } + + return eParseAnimationOrTransitionShorthand_Values; +} + +bool +CSSParserImpl::ParseTransition() +{ + static const nsCSSPropertyID kTransitionProperties[] = { + eCSSProperty_transition_duration, + eCSSProperty_transition_timing_function, + // Must check 'transition-delay' after 'transition-duration', since + // that's our assumption about what the spec means for the shorthand + // syntax (the first time given is the duration, and the second + // given is the delay). + eCSSProperty_transition_delay, + // Must check 'transition-property' after + // 'transition-timing-function' since 'transition-property' accepts + // any keyword. + eCSSProperty_transition_property + }; + static const uint32_t numProps = MOZ_ARRAY_LENGTH(kTransitionProperties); + // this is a shorthand property that accepts -property, -delay, + // -duration, and -timing-function with some components missing. + // there can be multiple transitions, separated with commas + + nsCSSValue initialValues[numProps]; + initialValues[0].SetFloatValue(0.0, eCSSUnit_Seconds); + initialValues[1].SetIntValue(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE, + eCSSUnit_Enumerated); + initialValues[2].SetFloatValue(0.0, eCSSUnit_Seconds); + initialValues[3].SetAllValue(); + + nsCSSValue values[numProps]; + + ParseAnimationOrTransitionShorthandResult spres = + ParseAnimationOrTransitionShorthand(kTransitionProperties, + initialValues, values, numProps); + if (spres != eParseAnimationOrTransitionShorthand_Values) { + return spres != eParseAnimationOrTransitionShorthand_Error; + } + + // Make two checks on the list for 'transition-property': + // + If there is more than one item, then none of the items can be + // 'none'. + // + None of the items can be 'inherit', 'initial' or 'unset'. + { + MOZ_ASSERT(kTransitionProperties[3] == eCSSProperty_transition_property, + "array index mismatch"); + nsCSSValueList *l = values[3].GetListValue(); + bool multipleItems = !!l->mNext; + do { + const nsCSSValue& val = l->mValue; + if (val.GetUnit() == eCSSUnit_None) { + if (multipleItems) { + // This is a syntax error. + return false; + } + + // Unbox a solitary 'none'. + values[3].SetNoneValue(); + break; + } + if (val.GetUnit() == eCSSUnit_Ident) { + nsDependentString str(val.GetStringBufferValue()); + if (str.EqualsLiteral("inherit") || + str.EqualsLiteral("initial") || + (str.EqualsLiteral("unset") && + nsLayoutUtils::UnsetValueEnabled())) { + return false; + } + } + } while ((l = l->mNext)); + } + + // Save all parsed transition sub-properties in mTempData + for (uint32_t i = 0; i < numProps; ++i) { + AppendValue(kTransitionProperties[i], values[i]); + } + return true; +} + +bool +CSSParserImpl::ParseAnimation() +{ + static const nsCSSPropertyID kAnimationProperties[] = { + eCSSProperty_animation_duration, + eCSSProperty_animation_timing_function, + // Must check 'animation-delay' after 'animation-duration', since + // that's our assumption about what the spec means for the shorthand + // syntax (the first time given is the duration, and the second + // given is the delay). + eCSSProperty_animation_delay, + eCSSProperty_animation_direction, + eCSSProperty_animation_fill_mode, + eCSSProperty_animation_iteration_count, + eCSSProperty_animation_play_state, + // Must check 'animation-name' after 'animation-timing-function', + // 'animation-direction', 'animation-fill-mode', + // 'animation-iteration-count', and 'animation-play-state' since + // 'animation-name' accepts any keyword. + eCSSProperty_animation_name + }; + static const uint32_t numProps = MOZ_ARRAY_LENGTH(kAnimationProperties); + // this is a shorthand property that accepts -property, -delay, + // -duration, and -timing-function with some components missing. + // there can be multiple animations, separated with commas + + nsCSSValue initialValues[numProps]; + initialValues[0].SetFloatValue(0.0, eCSSUnit_Seconds); + initialValues[1].SetIntValue(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE, + eCSSUnit_Enumerated); + initialValues[2].SetFloatValue(0.0, eCSSUnit_Seconds); + initialValues[3].SetIntValue(static_cast<uint32_t>(mozilla::dom::PlaybackDirection::Normal), + eCSSUnit_Enumerated); + initialValues[4].SetIntValue(static_cast<uint32_t>(mozilla::dom::FillMode::None), + eCSSUnit_Enumerated); + initialValues[5].SetFloatValue(1.0f, eCSSUnit_Number); + initialValues[6].SetIntValue(NS_STYLE_ANIMATION_PLAY_STATE_RUNNING, eCSSUnit_Enumerated); + initialValues[7].SetNoneValue(); + + nsCSSValue values[numProps]; + + ParseAnimationOrTransitionShorthandResult spres = + ParseAnimationOrTransitionShorthand(kAnimationProperties, + initialValues, values, numProps); + if (spres != eParseAnimationOrTransitionShorthand_Values) { + return spres != eParseAnimationOrTransitionShorthand_Error; + } + + // Save all parsed animation sub-properties in mTempData + for (uint32_t i = 0; i < numProps; ++i) { + AppendValue(kAnimationProperties[i], values[i]); + } + return true; +} + +bool +CSSParserImpl::ParseShadowItem(nsCSSValue& aValue, bool aIsBoxShadow) +{ + // A shadow list item is an array, with entries in this sequence: + enum { + IndexX, + IndexY, + IndexRadius, + IndexSpread, // only for box-shadow + IndexColor, + IndexInset // only for box-shadow + }; + + RefPtr<nsCSSValue::Array> val = nsCSSValue::Array::Create(6); + + if (aIsBoxShadow) { + // Optional inset keyword (ignore errors) + ParseSingleTokenVariant(val->Item(IndexInset), VARIANT_KEYWORD, + nsCSSProps::kBoxShadowTypeKTable); + } + + nsCSSValue xOrColor; + bool haveColor = false; + if (ParseVariant(xOrColor, VARIANT_COLOR | VARIANT_LENGTH | VARIANT_CALC, + nullptr) != CSSParseResult::Ok) { + return false; + } + if (xOrColor.IsLengthUnit() || xOrColor.IsCalcUnit()) { + val->Item(IndexX) = xOrColor; + } else { + // Must be a color (as string or color value) + NS_ASSERTION(xOrColor.GetUnit() == eCSSUnit_Ident || + xOrColor.GetUnit() == eCSSUnit_EnumColor || + xOrColor.IsNumericColorUnit(), + "Must be a color value"); + val->Item(IndexColor) = xOrColor; + haveColor = true; + + // X coordinate mandatory after color + if (ParseVariant(val->Item(IndexX), VARIANT_LENGTH | VARIANT_CALC, + nullptr) != CSSParseResult::Ok) { + return false; + } + } + + // Y coordinate; mandatory + if (ParseVariant(val->Item(IndexY), VARIANT_LENGTH | VARIANT_CALC, + nullptr) != CSSParseResult::Ok) { + return false; + } + + // Optional radius. Ignore errors except if they pass a negative + // value which we must reject. If we use ParseNonNegativeVariant + // we can't tell the difference between an unspecified radius + // and a negative radius. + CSSParseResult result = + ParseVariant(val->Item(IndexRadius), VARIANT_LENGTH | VARIANT_CALC, + nullptr); + if (result == CSSParseResult::Error) { + return false; + } else if (result == CSSParseResult::Ok) { + if (val->Item(IndexRadius).IsLengthUnit() && + val->Item(IndexRadius).GetFloatValue() < 0) { + return false; + } + } + + if (aIsBoxShadow) { + // Optional spread + if (ParseVariant(val->Item(IndexSpread), VARIANT_LENGTH | VARIANT_CALC, + nullptr) == CSSParseResult::Error) { + return false; + } + } + + if (!haveColor) { + // Optional color + if (ParseVariant(val->Item(IndexColor), VARIANT_COLOR, nullptr) == + CSSParseResult::Error) { + return false; + } + } + + if (aIsBoxShadow && val->Item(IndexInset).GetUnit() == eCSSUnit_Null) { + // Optional inset keyword + ParseSingleTokenVariant(val->Item(IndexInset), VARIANT_KEYWORD, + nsCSSProps::kBoxShadowTypeKTable); + } + + aValue.SetArrayValue(val, eCSSUnit_Array); + return true; +} + +bool +CSSParserImpl::ParseShadowList(nsCSSPropertyID aProperty) +{ + nsAutoParseCompoundProperty compound(this); + bool isBoxShadow = aProperty == eCSSProperty_box_shadow; + + nsCSSValue value; + // 'inherit', 'initial', 'unset' and 'none' must be alone + if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE, + nullptr)) { + nsCSSValueList* cur = value.SetListValue(); + for (;;) { + if (!ParseShadowItem(cur->mValue, isBoxShadow)) { + return false; + } + if (!ExpectSymbol(',', true)) { + break; + } + cur->mNext = new nsCSSValueList; + cur = cur->mNext; + } + } + AppendValue(aProperty, value); + return true; +} + +int32_t +CSSParserImpl::GetNamespaceIdForPrefix(const nsString& aPrefix) +{ + NS_PRECONDITION(!aPrefix.IsEmpty(), "Must have a prefix here"); + + int32_t nameSpaceID = kNameSpaceID_Unknown; + if (mNameSpaceMap) { + // user-specified identifiers are case-sensitive (bug 416106) + nsCOMPtr<nsIAtom> prefix = NS_Atomize(aPrefix); + nameSpaceID = mNameSpaceMap->FindNameSpaceID(prefix); + } + // else no declared namespaces + + if (nameSpaceID == kNameSpaceID_Unknown) { // unknown prefix, dump it + REPORT_UNEXPECTED_P(PEUnknownNamespacePrefix, aPrefix); + } + + return nameSpaceID; +} + +void +CSSParserImpl::SetDefaultNamespaceOnSelector(nsCSSSelector& aSelector) +{ + if (mNameSpaceMap) { + aSelector.SetNameSpace(mNameSpaceMap->FindNameSpaceID(nullptr)); + } else { + aSelector.SetNameSpace(kNameSpaceID_Unknown); // wildcard + } +} + +bool +CSSParserImpl::ParsePaint(nsCSSPropertyID aPropID) +{ + nsCSSValue x, y; + + if (ParseVariant(x, VARIANT_HC | VARIANT_NONE | VARIANT_URL | + VARIANT_OPENTYPE_SVG_KEYWORD, + nsCSSProps::kContextPatternKTable) != CSSParseResult::Ok) { + return false; + } + + bool canHaveFallback = x.GetUnit() == eCSSUnit_URL || + x.GetUnit() == eCSSUnit_Enumerated; + if (canHaveFallback) { + CSSParseResult result = + ParseVariant(y, VARIANT_COLOR | VARIANT_NONE, nullptr); + if (result == CSSParseResult::Error) { + return false; + } else if (result == CSSParseResult::NotFound) { + y.SetNoneValue(); + } + } + + if (!canHaveFallback) { + AppendValue(aPropID, x); + } else { + nsCSSValue val; + val.SetPairValue(x, y); + AppendValue(aPropID, val); + } + return true; +} + +bool +CSSParserImpl::ParseDasharray() +{ + nsCSSValue value; + + // 'inherit', 'initial', 'unset' and 'none' are only allowed on their own + if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE | + VARIANT_OPENTYPE_SVG_KEYWORD, + nsCSSProps::kStrokeContextValueKTable)) { + nsCSSValueList *cur = value.SetListValue(); + for (;;) { + if (!ParseSingleTokenNonNegativeVariant(cur->mValue, VARIANT_LPN, + nullptr)) { + return false; + } + if (CheckEndProperty()) { + break; + } + // skip optional commas between elements + (void)ExpectSymbol(',', true); + + cur->mNext = new nsCSSValueList; + cur = cur->mNext; + } + } + AppendValue(eCSSProperty_stroke_dasharray, value); + return true; +} + +bool +CSSParserImpl::ParseMarker() +{ + nsCSSValue marker; + if (ParseSingleValueProperty(marker, eCSSProperty_marker_end) == + CSSParseResult::Ok) { + AppendValue(eCSSProperty_marker_end, marker); + AppendValue(eCSSProperty_marker_mid, marker); + AppendValue(eCSSProperty_marker_start, marker); + return true; + } + return false; +} + +bool +CSSParserImpl::ParsePaintOrder() +{ + static_assert + ((1 << NS_STYLE_PAINT_ORDER_BITWIDTH) > NS_STYLE_PAINT_ORDER_LAST_VALUE, + "bitfield width insufficient for paint-order constants"); + + static const KTableEntry kPaintOrderKTable[] = { + { eCSSKeyword_normal, NS_STYLE_PAINT_ORDER_NORMAL }, + { eCSSKeyword_fill, NS_STYLE_PAINT_ORDER_FILL }, + { eCSSKeyword_stroke, NS_STYLE_PAINT_ORDER_STROKE }, + { eCSSKeyword_markers, NS_STYLE_PAINT_ORDER_MARKERS }, + { eCSSKeyword_UNKNOWN, -1 } + }; + + static_assert(MOZ_ARRAY_LENGTH(kPaintOrderKTable) == + NS_STYLE_PAINT_ORDER_LAST_VALUE + 2, + "missing paint-order values in kPaintOrderKTable"); + + nsCSSValue value; + if (!ParseSingleTokenVariant(value, VARIANT_HK, kPaintOrderKTable)) { + return false; + } + + uint32_t seen = 0; + uint32_t order = 0; + uint32_t position = 0; + + // Ensure that even cast to a signed int32_t when stored in CSSValue, + // we have enough space for the entire paint-order value. + static_assert + (NS_STYLE_PAINT_ORDER_BITWIDTH * NS_STYLE_PAINT_ORDER_LAST_VALUE < 32, + "seen and order not big enough"); + + if (value.GetUnit() == eCSSUnit_Enumerated) { + uint32_t component = static_cast<uint32_t>(value.GetIntValue()); + if (component != NS_STYLE_PAINT_ORDER_NORMAL) { + bool parsedOK = true; + for (;;) { + if (seen & (1 << component)) { + // Already seen this component. + UngetToken(); + parsedOK = false; + break; + } + seen |= (1 << component); + order |= (component << position); + position += NS_STYLE_PAINT_ORDER_BITWIDTH; + if (!ParseEnum(value, kPaintOrderKTable)) { + break; + } + component = value.GetIntValue(); + if (component == NS_STYLE_PAINT_ORDER_NORMAL) { + // Can't have "normal" in the middle of the list of paint components. + UngetToken(); + parsedOK = false; + break; + } + } + + // Fill in the remaining paint-order components in the order of their + // constant values. + if (parsedOK) { + for (component = 1; + component <= NS_STYLE_PAINT_ORDER_LAST_VALUE; + component++) { + if (!(seen & (1 << component))) { + order |= (component << position); + position += NS_STYLE_PAINT_ORDER_BITWIDTH; + } + } + } + } + + static_assert(NS_STYLE_PAINT_ORDER_NORMAL == 0, + "unexpected value for NS_STYLE_PAINT_ORDER_NORMAL"); + value.SetIntValue(static_cast<int32_t>(order), eCSSUnit_Enumerated); + } + + AppendValue(eCSSProperty_paint_order, value); + return true; +} + +bool +CSSParserImpl::BackslashDropped() +{ + return mScanner->GetEOFCharacters() & + nsCSSScanner::eEOFCharacters_DropBackslash; +} + +void +CSSParserImpl::AppendImpliedEOFCharacters(nsAString& aResult) +{ + nsCSSScanner::AppendImpliedEOFCharacters(mScanner->GetEOFCharacters(), + aResult); +} + +bool +CSSParserImpl::ParseAll() +{ + nsCSSValue value; + if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { + return false; + } + + // It's unlikely we'll want to use 'all' from within a UA style sheet, so + // instead of computing the correct EnabledState value we just expand out + // to all content-visible properties. + CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, eCSSProperty_all, + CSSEnabledState::eForAllContent) { + AppendValue(*p, value); + } + return true; +} + +bool +CSSParserImpl::ParseVariableDeclaration(CSSVariableDeclarations::Type* aType, + nsString& aValue) +{ + CSSVariableDeclarations::Type type; + nsString variableValue; + bool dropBackslash; + nsString impliedCharacters; + + // Record the token stream while parsing a variable value. + if (!mInSupportsCondition) { + mScanner->StartRecording(); + } + if (!ParseValueWithVariables(&type, &dropBackslash, impliedCharacters, + nullptr, nullptr)) { + if (!mInSupportsCondition) { + mScanner->StopRecording(); + } + return false; + } + + if (!mInSupportsCondition) { + if (type == CSSVariableDeclarations::eTokenStream) { + // This was indeed a token stream value, so store it in variableValue. + mScanner->StopRecording(variableValue); + if (dropBackslash) { + MOZ_ASSERT(!variableValue.IsEmpty() && + variableValue[variableValue.Length() - 1] == '\\'); + variableValue.Truncate(variableValue.Length() - 1); + } + variableValue.Append(impliedCharacters); + } else { + // This was either 'inherit' or 'initial'; we don't need the recorded + // input. + mScanner->StopRecording(); + } + } + + if (mHavePushBack && type == CSSVariableDeclarations::eTokenStream) { + // If we came to the end of a valid variable declaration and a token was + // pushed back, then it would have been ended by '!', ')', ';', ']' or '}'. + // We need to remove it from the recorded variable value. + MOZ_ASSERT(mToken.IsSymbol('!') || + mToken.IsSymbol(')') || + mToken.IsSymbol(';') || + mToken.IsSymbol(']') || + mToken.IsSymbol('}')); + if (!mInSupportsCondition) { + MOZ_ASSERT(!variableValue.IsEmpty()); + MOZ_ASSERT(variableValue[variableValue.Length() - 1] == mToken.mSymbol); + variableValue.Truncate(variableValue.Length() - 1); + } + } + + *aType = type; + aValue = variableValue; + return true; +} + +bool +CSSParserImpl::ParseScrollSnapType() +{ + nsCSSValue value; + if (!ParseSingleTokenVariant(value, VARIANT_HK, + nsCSSProps::kScrollSnapTypeKTable)) { + return false; + } + AppendValue(eCSSProperty_scroll_snap_type_x, value); + AppendValue(eCSSProperty_scroll_snap_type_y, value); + return true; +} + +bool +CSSParserImpl::ParseScrollSnapPoints(nsCSSValue& aValue, nsCSSPropertyID aPropID) +{ + if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NONE, + nullptr)) { + return true; + } + if (!GetToken(true)) { + return false; + } + if (mToken.mType == eCSSToken_Function && + nsCSSKeywords::LookupKeyword(mToken.mIdent) == eCSSKeyword_repeat) { + nsCSSValue lengthValue; + if (ParseNonNegativeVariant(lengthValue, + VARIANT_LENGTH | VARIANT_PERCENT | VARIANT_CALC, + nullptr) != CSSParseResult::Ok) { + REPORT_UNEXPECTED(PEExpectedNonnegativeNP); + SkipUntil(')'); + return false; + } + if (!ExpectSymbol(')', true)) { + REPORT_UNEXPECTED(PEExpectedCloseParen); + SkipUntil(')'); + return false; + } + RefPtr<nsCSSValue::Array> functionArray = + aValue.InitFunction(eCSSKeyword_repeat, 1); + functionArray->Item(1) = lengthValue; + return true; + } + UngetToken(); + return false; +} + + +bool +CSSParserImpl::ParseScrollSnapDestination(nsCSSValue& aValue) +{ + if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT, nullptr)) { + return true; + } + nsCSSValue itemValue; + if (!ParsePositionValue(aValue)) { + REPORT_UNEXPECTED_TOKEN(PEExpectedPosition); + return false; + } + return true; +} + +// This function is very similar to ParseImageLayerPosition, and ParseImageLayerSize. +bool +CSSParserImpl::ParseScrollSnapCoordinate(nsCSSValue& aValue) +{ + if (ParseSingleTokenVariant(aValue, VARIANT_INHERIT | VARIANT_NONE, + nullptr)) { + return true; + } + nsCSSValue itemValue; + if (!ParsePositionValue(itemValue)) { + REPORT_UNEXPECTED_TOKEN(PEExpectedPosition); + return false; + } + nsCSSValueList* item = aValue.SetListValue(); + for (;;) { + item->mValue = itemValue; + if (!ExpectSymbol(',', true)) { + break; + } + if (!ParsePositionValue(itemValue)) { + REPORT_UNEXPECTED_TOKEN(PEExpectedPosition); + return false; + } + item->mNext = new nsCSSValueList; + item = item->mNext; + } + return true; +} + +bool +CSSParserImpl::ParseValueWithVariables(CSSVariableDeclarations::Type* aType, + bool* aDropBackslash, + nsString& aImpliedCharacters, + void (*aFunc)(const nsAString&, void*), + void* aData) +{ + // A property value is invalid if it contains variable references and also: + // + // * has unbalanced parens, brackets or braces + // * has any BAD_STRING or BAD_URL tokens + // * has any ';' or '!' tokens at the top level of a variable reference's + // fallback + // + // If the property is a custom property (i.e. a variable declaration), then + // it is also invalid if it consists of no tokens, such as: + // + // --invalid:; + // + // Note that is valid for a custom property to have a value that consists + // solely of white space, such as: + // + // --valid: ; + + // Stack of closing characters for currently open constructs. + StopSymbolCharStack stack; + + // Indexes into ')' characters in |stack| that correspond to "var(". This + // is used to stop parsing when we encounter a '!' or ';' at the top level + // of a variable reference's fallback. + AutoTArray<uint32_t, 16> references; + + if (!GetToken(false)) { + // Variable value was empty since we reached EOF. + REPORT_UNEXPECTED_EOF(PEVariableEOF); + return false; + } + + if (mToken.mType == eCSSToken_Symbol && + (mToken.mSymbol == '!' || + mToken.mSymbol == ')' || + mToken.mSymbol == ';' || + mToken.mSymbol == ']' || + mToken.mSymbol == '}')) { + // Variable value was empty since we reached the end of the construct. + UngetToken(); + REPORT_UNEXPECTED_TOKEN(PEVariableEmpty); + return false; + } + + if (mToken.mType == eCSSToken_Whitespace) { + if (!GetToken(true)) { + // Variable value was white space only. This is valid. + MOZ_ASSERT(!BackslashDropped()); + *aType = CSSVariableDeclarations::eTokenStream; + *aDropBackslash = false; + AppendImpliedEOFCharacters(aImpliedCharacters); + return true; + } + } + + // Look for 'initial', 'inherit' or 'unset' as the first non-white space + // token. + CSSVariableDeclarations::Type type = CSSVariableDeclarations::eTokenStream; + if (mToken.mType == eCSSToken_Ident) { + if (mToken.mIdent.LowerCaseEqualsLiteral("initial")) { + type = CSSVariableDeclarations::eInitial; + } else if (mToken.mIdent.LowerCaseEqualsLiteral("inherit")) { + type = CSSVariableDeclarations::eInherit; + } else if (mToken.mIdent.LowerCaseEqualsLiteral("unset")) { + type = CSSVariableDeclarations::eUnset; + } + } + + if (type != CSSVariableDeclarations::eTokenStream) { + if (!GetToken(true)) { + // Variable value was 'initial' or 'inherit' followed by EOF. + MOZ_ASSERT(!BackslashDropped()); + *aType = type; + *aDropBackslash = false; + AppendImpliedEOFCharacters(aImpliedCharacters); + return true; + } + UngetToken(); + if (mToken.mType == eCSSToken_Symbol && + (mToken.mSymbol == '!' || + mToken.mSymbol == ')' || + mToken.mSymbol == ';' || + mToken.mSymbol == ']' || + mToken.mSymbol == '}')) { + // Variable value was 'initial' or 'inherit' followed by the end + // of the declaration. + MOZ_ASSERT(!BackslashDropped()); + *aType = type; + *aDropBackslash = false; + return true; + } + } + + do { + switch (mToken.mType) { + case eCSSToken_Symbol: + if (mToken.mSymbol == '(') { + stack.AppendElement(')'); + } else if (mToken.mSymbol == '[') { + stack.AppendElement(']'); + } else if (mToken.mSymbol == '{') { + stack.AppendElement('}'); + } else if (mToken.mSymbol == ';' || + mToken.mSymbol == '!') { + if (stack.IsEmpty()) { + UngetToken(); + MOZ_ASSERT(!BackslashDropped()); + *aType = CSSVariableDeclarations::eTokenStream; + *aDropBackslash = false; + return true; + } else if (!references.IsEmpty() && + references.LastElement() == stack.Length() - 1) { + REPORT_UNEXPECTED_TOKEN(PEInvalidVariableTokenFallback); + SkipUntilAllOf(stack); + return false; + } + } else if (mToken.mSymbol == ')' || + mToken.mSymbol == ']' || + mToken.mSymbol == '}') { + for (;;) { + if (stack.IsEmpty()) { + UngetToken(); + MOZ_ASSERT(!BackslashDropped()); + *aType = CSSVariableDeclarations::eTokenStream; + *aDropBackslash = false; + return true; + } + char16_t c = stack.LastElement(); + stack.TruncateLength(stack.Length() - 1); + if (!references.IsEmpty() && + references.LastElement() == stack.Length()) { + references.TruncateLength(references.Length() - 1); + } + if (mToken.mSymbol == c) { + break; + } + } + } + break; + + case eCSSToken_Function: + if (mToken.mIdent.LowerCaseEqualsLiteral("var")) { + if (!GetToken(true)) { + // EOF directly after "var(". + REPORT_UNEXPECTED_EOF(PEExpectedVariableNameEOF); + return false; + } + if (mToken.mType != eCSSToken_Ident || + !nsCSSProps::IsCustomPropertyName(mToken.mIdent)) { + // There must be an identifier directly after the "var(" and + // it must be a custom property name. + UngetToken(); + REPORT_UNEXPECTED_TOKEN(PEExpectedVariableName); + SkipUntil(')'); + SkipUntilAllOf(stack); + return false; + } + if (aFunc) { + MOZ_ASSERT(Substring(mToken.mIdent, 0, + CSS_CUSTOM_NAME_PREFIX_LENGTH). + EqualsLiteral("--")); + // remove '--' + const nsDependentSubstring varName = + Substring(mToken.mIdent, CSS_CUSTOM_NAME_PREFIX_LENGTH); + aFunc(varName, aData); + } + if (!GetToken(true)) { + // EOF right after "var(<ident>". + stack.AppendElement(')'); + } else if (mToken.IsSymbol(',')) { + // Variable reference with fallback. + if (!GetToken(false) || mToken.IsSymbol(')')) { + // Comma must be followed by at least one fallback token. + REPORT_UNEXPECTED(PEExpectedVariableFallback); + SkipUntilAllOf(stack); + return false; + } + UngetToken(); + references.AppendElement(stack.Length()); + stack.AppendElement(')'); + } else if (mToken.IsSymbol(')')) { + // Correctly closed variable reference. + } else { + // Malformed variable reference. + REPORT_UNEXPECTED_TOKEN(PEExpectedVariableCommaOrCloseParen); + SkipUntil(')'); + SkipUntilAllOf(stack); + return false; + } + } else { + stack.AppendElement(')'); + } + break; + + case eCSSToken_Bad_String: + SkipUntilAllOf(stack); + return false; + + case eCSSToken_Bad_URL: + SkipUntil(')'); + SkipUntilAllOf(stack); + return false; + + default: + break; + } + } while (GetToken(true)); + + // Append any implied closing characters. + *aDropBackslash = BackslashDropped(); + AppendImpliedEOFCharacters(aImpliedCharacters); + uint32_t i = stack.Length(); + while (i--) { + aImpliedCharacters.Append(stack[i]); + } + + *aType = type; + return true; +} + +bool +CSSParserImpl::IsValueValidForProperty(const nsCSSPropertyID aPropID, + const nsAString& aPropValue) +{ + mData.AssertInitialState(); + mTempData.AssertInitialState(); + + nsCSSScanner scanner(aPropValue, 0); + css::ErrorReporter reporter(scanner, mSheet, mChildLoader, nullptr); + InitScanner(scanner, reporter, nullptr, nullptr, nullptr); + + // We normally would need to pass in a sheet principal to InitScanner, + // because we might parse a URL value. However, we will never use the + // parsed nsCSSValue (and so whether we have a sheet principal or not + // doesn't really matter), so to avoid failing the assertion in + // SetValueToURL, we set mSheetPrincipalRequired to false to declare + // that it's safe to skip the assertion. + AutoRestore<bool> autoRestore(mSheetPrincipalRequired); + mSheetPrincipalRequired = false; + + nsAutoSuppressErrors suppressErrors(this); + + mSection = eCSSSection_General; + scanner.SetSVGMode(false); + + // Check for unknown properties + if (eCSSProperty_UNKNOWN == aPropID) { + ReleaseScanner(); + return false; + } + + // Check that the property and value parse successfully + bool parsedOK = ParseProperty(aPropID); + + // Check for priority + parsedOK = parsedOK && ParsePriority() != ePriority_Error; + + // We should now be at EOF + parsedOK = parsedOK && !GetToken(true); + + mTempData.ClearProperty(aPropID); + mTempData.AssertInitialState(); + mData.AssertInitialState(); + + CLEAR_ERROR(); + ReleaseScanner(); + + return parsedOK; +} + +} // namespace + +// Recycling of parser implementation objects + +static CSSParserImpl* gFreeList = nullptr; + +/* static */ void +nsCSSParser::Startup() +{ + Preferences::AddBoolVarCache(&sOpentypeSVGEnabled, + "gfx.font_rendering.opentype_svg.enabled"); + Preferences::AddBoolVarCache(&sWebkitPrefixedAliasesEnabled, + "layout.css.prefixes.webkit"); + Preferences::AddBoolVarCache(&sWebkitDevicePixelRatioEnabled, + "layout.css.prefixes.device-pixel-ratio-webkit"); + Preferences::AddBoolVarCache(&sUnprefixingServiceEnabled, + "layout.css.unprefixing-service.enabled"); +#ifdef NIGHTLY_BUILD + Preferences::AddBoolVarCache(&sUnprefixingServiceGloballyWhitelisted, + "layout.css.unprefixing-service.globally-whitelisted"); +#endif + Preferences::AddBoolVarCache(&sMozGradientsEnabled, + "layout.css.prefixes.gradients"); + Preferences::AddBoolVarCache(&sControlCharVisibility, + "layout.css.control-characters.visible"); +} + +nsCSSParser::nsCSSParser(mozilla::css::Loader* aLoader, + CSSStyleSheet* aSheet) +{ + CSSParserImpl *impl = gFreeList; + if (impl) { + gFreeList = impl->mNextFree; + impl->mNextFree = nullptr; + } else { + impl = new CSSParserImpl(); + } + + if (aLoader) { + impl->SetChildLoader(aLoader); + impl->SetQuirkMode(aLoader->GetCompatibilityMode() == + eCompatibility_NavQuirks); + } + if (aSheet) { + impl->SetStyleSheet(aSheet); + } + + mImpl = static_cast<void*>(impl); +} + +nsCSSParser::~nsCSSParser() +{ + CSSParserImpl *impl = static_cast<CSSParserImpl*>(mImpl); + impl->Reset(); + impl->mNextFree = gFreeList; + gFreeList = impl; +} + +/* static */ void +nsCSSParser::Shutdown() +{ + CSSParserImpl *tofree = gFreeList; + CSSParserImpl *next; + while (tofree) + { + next = tofree->mNextFree; + delete tofree; + tofree = next; + } +} + +// Wrapper methods + +nsresult +nsCSSParser::ParseSheet(const nsAString& aInput, + nsIURI* aSheetURI, + nsIURI* aBaseURI, + nsIPrincipal* aSheetPrincipal, + uint32_t aLineNumber, + css::LoaderReusableStyleSheets* aReusableSheets) +{ + return static_cast<CSSParserImpl*>(mImpl)-> + ParseSheet(aInput, aSheetURI, aBaseURI, aSheetPrincipal, aLineNumber, + aReusableSheets); +} + +already_AddRefed<css::Declaration> +nsCSSParser::ParseStyleAttribute(const nsAString& aAttributeValue, + nsIURI* aDocURI, + nsIURI* aBaseURI, + nsIPrincipal* aNodePrincipal) +{ + return static_cast<CSSParserImpl*>(mImpl)-> + ParseStyleAttribute(aAttributeValue, aDocURI, aBaseURI, aNodePrincipal); +} + +nsresult +nsCSSParser::ParseDeclarations(const nsAString& aBuffer, + nsIURI* aSheetURI, + nsIURI* aBaseURI, + nsIPrincipal* aSheetPrincipal, + css::Declaration* aDeclaration, + bool* aChanged) +{ + return static_cast<CSSParserImpl*>(mImpl)-> + ParseDeclarations(aBuffer, aSheetURI, aBaseURI, aSheetPrincipal, + aDeclaration, aChanged); +} + +nsresult +nsCSSParser::ParseRule(const nsAString& aRule, + nsIURI* aSheetURI, + nsIURI* aBaseURI, + nsIPrincipal* aSheetPrincipal, + css::Rule** aResult) +{ + return static_cast<CSSParserImpl*>(mImpl)-> + ParseRule(aRule, aSheetURI, aBaseURI, aSheetPrincipal, aResult); +} + +void +nsCSSParser::ParseProperty(const nsCSSPropertyID aPropID, + const nsAString& aPropValue, + nsIURI* aSheetURI, + nsIURI* aBaseURI, + nsIPrincipal* aSheetPrincipal, + css::Declaration* aDeclaration, + bool* aChanged, + bool aIsImportant, + bool aIsSVGMode) +{ + static_cast<CSSParserImpl*>(mImpl)-> + ParseProperty(aPropID, aPropValue, aSheetURI, aBaseURI, + aSheetPrincipal, aDeclaration, aChanged, + aIsImportant, aIsSVGMode); +} + +void +nsCSSParser::ParseLonghandProperty(const nsCSSPropertyID aPropID, + const nsAString& aPropValue, + nsIURI* aSheetURI, + nsIURI* aBaseURI, + nsIPrincipal* aSheetPrincipal, + nsCSSValue& aResult) +{ + static_cast<CSSParserImpl*>(mImpl)-> + ParseLonghandProperty(aPropID, aPropValue, aSheetURI, aBaseURI, + aSheetPrincipal, aResult); +} + +bool +nsCSSParser::ParseTransformProperty(const nsAString& aPropValue, + bool aDisallowRelativeValues, + nsCSSValue& aResult) +{ + return static_cast<CSSParserImpl*>(mImpl)-> + ParseTransformProperty(aPropValue, aDisallowRelativeValues, aResult); +} + +void +nsCSSParser::ParseVariable(const nsAString& aVariableName, + const nsAString& aPropValue, + nsIURI* aSheetURI, + nsIURI* aBaseURI, + nsIPrincipal* aSheetPrincipal, + css::Declaration* aDeclaration, + bool* aChanged, + bool aIsImportant) +{ + static_cast<CSSParserImpl*>(mImpl)-> + ParseVariable(aVariableName, aPropValue, aSheetURI, aBaseURI, + aSheetPrincipal, aDeclaration, aChanged, aIsImportant); +} + +void +nsCSSParser::ParseMediaList(const nsSubstring& aBuffer, + nsIURI* aURI, + uint32_t aLineNumber, + nsMediaList* aMediaList, + bool aHTMLMode) +{ + static_cast<CSSParserImpl*>(mImpl)-> + ParseMediaList(aBuffer, aURI, aLineNumber, aMediaList, aHTMLMode); +} + +bool +nsCSSParser::ParseSourceSizeList(const nsAString& aBuffer, + nsIURI* aURI, + uint32_t aLineNumber, + InfallibleTArray< nsAutoPtr<nsMediaQuery> >& aQueries, + InfallibleTArray<nsCSSValue>& aValues, + bool aHTMLMode) +{ + return static_cast<CSSParserImpl*>(mImpl)-> + ParseSourceSizeList(aBuffer, aURI, aLineNumber, aQueries, aValues, + aHTMLMode); +} + +bool +nsCSSParser::ParseFontFamilyListString(const nsSubstring& aBuffer, + nsIURI* aURI, + uint32_t aLineNumber, + nsCSSValue& aValue) +{ + return static_cast<CSSParserImpl*>(mImpl)-> + ParseFontFamilyListString(aBuffer, aURI, aLineNumber, aValue); +} + +bool +nsCSSParser::ParseColorString(const nsSubstring& aBuffer, + nsIURI* aURI, + uint32_t aLineNumber, + nsCSSValue& aValue, + bool aSuppressErrors /* false */) +{ + return static_cast<CSSParserImpl*>(mImpl)-> + ParseColorString(aBuffer, aURI, aLineNumber, aValue, aSuppressErrors); +} + +bool +nsCSSParser::ParseMarginString(const nsSubstring& aBuffer, + nsIURI* aURI, + uint32_t aLineNumber, + nsCSSValue& aValue, + bool aSuppressErrors /* false */) +{ + return static_cast<CSSParserImpl*>(mImpl)-> + ParseMarginString(aBuffer, aURI, aLineNumber, aValue, aSuppressErrors); +} + +nsresult +nsCSSParser::ParseSelectorString(const nsSubstring& aSelectorString, + nsIURI* aURI, + uint32_t aLineNumber, + nsCSSSelectorList** aSelectorList) +{ + return static_cast<CSSParserImpl*>(mImpl)-> + ParseSelectorString(aSelectorString, aURI, aLineNumber, aSelectorList); +} + +already_AddRefed<nsCSSKeyframeRule> +nsCSSParser::ParseKeyframeRule(const nsSubstring& aBuffer, + nsIURI* aURI, + uint32_t aLineNumber) +{ + return static_cast<CSSParserImpl*>(mImpl)-> + ParseKeyframeRule(aBuffer, aURI, aLineNumber); +} + +bool +nsCSSParser::ParseKeyframeSelectorString(const nsSubstring& aSelectorString, + nsIURI* aURI, + uint32_t aLineNumber, + InfallibleTArray<float>& aSelectorList) +{ + return static_cast<CSSParserImpl*>(mImpl)-> + ParseKeyframeSelectorString(aSelectorString, aURI, aLineNumber, + aSelectorList); +} + +bool +nsCSSParser::EvaluateSupportsDeclaration(const nsAString& aProperty, + const nsAString& aValue, + nsIURI* aDocURL, + nsIURI* aBaseURL, + nsIPrincipal* aDocPrincipal) +{ + return static_cast<CSSParserImpl*>(mImpl)-> + EvaluateSupportsDeclaration(aProperty, aValue, aDocURL, aBaseURL, + aDocPrincipal); +} + +bool +nsCSSParser::EvaluateSupportsCondition(const nsAString& aCondition, + nsIURI* aDocURL, + nsIURI* aBaseURL, + nsIPrincipal* aDocPrincipal) +{ + return static_cast<CSSParserImpl*>(mImpl)-> + EvaluateSupportsCondition(aCondition, aDocURL, aBaseURL, aDocPrincipal); +} + +bool +nsCSSParser::EnumerateVariableReferences(const nsAString& aPropertyValue, + VariableEnumFunc aFunc, + void* aData) +{ + return static_cast<CSSParserImpl*>(mImpl)-> + EnumerateVariableReferences(aPropertyValue, aFunc, aData); +} + +bool +nsCSSParser::ResolveVariableValue(const nsAString& aPropertyValue, + const CSSVariableValues* aVariables, + nsString& aResult, + nsCSSTokenSerializationType& aFirstToken, + nsCSSTokenSerializationType& aLastToken) +{ + return static_cast<CSSParserImpl*>(mImpl)-> + ResolveVariableValue(aPropertyValue, aVariables, + aResult, aFirstToken, aLastToken); +} + +void +nsCSSParser::ParsePropertyWithVariableReferences( + nsCSSPropertyID aPropertyID, + nsCSSPropertyID aShorthandPropertyID, + const nsAString& aValue, + const CSSVariableValues* aVariables, + nsRuleData* aRuleData, + nsIURI* aDocURL, + nsIURI* aBaseURL, + nsIPrincipal* aDocPrincipal, + CSSStyleSheet* aSheet, + uint32_t aLineNumber, + uint32_t aLineOffset) +{ + static_cast<CSSParserImpl*>(mImpl)-> + ParsePropertyWithVariableReferences(aPropertyID, aShorthandPropertyID, + aValue, aVariables, aRuleData, aDocURL, + aBaseURL, aDocPrincipal, aSheet, + aLineNumber, aLineOffset); +} + +bool +nsCSSParser::ParseCounterStyleName(const nsAString& aBuffer, + nsIURI* aURL, + nsAString& aName) +{ + return static_cast<CSSParserImpl*>(mImpl)-> + ParseCounterStyleName(aBuffer, aURL, aName); +} + +bool +nsCSSParser::ParseCounterDescriptor(nsCSSCounterDesc aDescID, + const nsAString& aBuffer, + nsIURI* aSheetURL, + nsIURI* aBaseURL, + nsIPrincipal* aSheetPrincipal, + nsCSSValue& aValue) +{ + return static_cast<CSSParserImpl*>(mImpl)-> + ParseCounterDescriptor(aDescID, aBuffer, + aSheetURL, aBaseURL, aSheetPrincipal, aValue); +} + +bool +nsCSSParser::ParseFontFaceDescriptor(nsCSSFontDesc aDescID, + const nsAString& aBuffer, + nsIURI* aSheetURL, + nsIURI* aBaseURL, + nsIPrincipal* aSheetPrincipal, + nsCSSValue& aValue) +{ + return static_cast<CSSParserImpl*>(mImpl)-> + ParseFontFaceDescriptor(aDescID, aBuffer, + aSheetURL, aBaseURL, aSheetPrincipal, aValue); +} + +bool +nsCSSParser::IsValueValidForProperty(const nsCSSPropertyID aPropID, + const nsAString& aPropValue) +{ + return static_cast<CSSParserImpl*>(mImpl)-> + IsValueValidForProperty(aPropID, aPropValue); +} + +/* static */ +uint8_t +nsCSSParser::ControlCharVisibilityDefault() +{ + return sControlCharVisibility + ? NS_STYLE_CONTROL_CHARACTER_VISIBILITY_VISIBLE + : NS_STYLE_CONTROL_CHARACTER_VISIBILITY_HIDDEN; +} |