summaryrefslogtreecommitdiffstats
path: root/layout/style/nsCSSParser.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/style/nsCSSParser.cpp')
-rw-r--r--layout/style/nsCSSParser.cpp18324
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;
+}