/* -*- 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")) { if (requireFixedSize) { UngetToken(); return CSSParseResult::Error; } nsCSSValue::Array* func = aValue.InitFunction(eCSSKeyword_fit_content, 1); if (ParseGridTrackBreadth(func->Item(1)) == CSSParseResult::Ok && func->Item(1).IsLengthPercentCalcUnit() && ExpectSymbol(')', true)) { return CSSParseResult::Ok; } SkipUntil(')'); return CSSParseResult::Error; } if (!mToken.mIdent.LowerCaseEqualsLiteral("minmax")) { UngetToken(); return CSSParseResult::NotFound; } nsCSSValue::Array* func = aValue.InitFunction(eCSSKeyword_minmax, 2); if (ParseGridTrackBreadth(func->Item(1)) == CSSParseResult::Ok && ExpectSymbol(',', true) && ParseGridTrackBreadth(func->Item(2)) == CSSParseResult::Ok && ExpectSymbol(')', true)) { if (requireFixedSize && !func->Item(1).IsLengthPercentCalcUnit() && !func->Item(2).IsLengthPercentCalcUnit()) { return CSSParseResult::Error; } // Reject <flex> min-sizing. if (func->Item(1).GetUnit() == eCSSUnit_FlexFraction) { return CSSParseResult::Error; } return CSSParseResult::Ok; } SkipUntil(')'); return CSSParseResult::Error; } bool CSSParserImpl::ParseGridAutoColumnsRows(nsCSSPropertyID aPropID) { nsCSSValue value; if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr) || ParseGridTrackSize(value) == CSSParseResult::Ok) { AppendValue(aPropID, value); return true; } return false; } bool CSSParserImpl::ParseGridTrackListWithFirstLineNames(nsCSSValue& aValue, const nsCSSValue& aFirstLineNames, GridTrackListFlags aFlags) { nsCSSValueList* firstLineNamesItem = aValue.SetListValue(); firstLineNamesItem->mValue = aFirstLineNames; // This function is trying to parse <track-list>, which is // [ <line-names>? [ <track-size> | <repeat()> ] ]+ <line-names>? // and we're already past the first "<line-names>?". // If aFlags contains eExplicitTrackList then <repeat()> is disallowed. // // Each iteration of the following loop attempts to parse either a // repeat() or a <track-size> expression, and then an (optional) // <line-names> expression. // // The only successful exit point from this loop is the ::NotFound // case after ParseGridTrackSize(); i.e. we'll greedily parse // repeat()/<track-size> until we can't find one. nsCSSValueList* item = firstLineNamesItem; bool haveRepeatAuto = false; for (;;) { // First try to parse repeat() if (!GetToken(true)) { break; } if (!(aFlags & GridTrackListFlags::eExplicitTrackList) && mToken.mType == eCSSToken_Function && mToken.mIdent.LowerCaseEqualsLiteral("repeat")) { nsCSSValueList* startOfRepeat = item; if (!ParseGridTrackListRepeat(&item)) { SkipUntil(')'); return false; } auto firstRepeat = startOfRepeat->mNext; if (firstRepeat->mValue.GetUnit() == eCSSUnit_Pair) { if (haveRepeatAuto) { REPORT_UNEXPECTED(PEMoreThanOneGridRepeatAutoFillFitInTrackList); return false; } haveRepeatAuto = true; // We're parsing an <auto-track-list>, which requires that all tracks // are <fixed-size>, so we need to check the ones we've parsed already. for (nsCSSValueList* list = firstLineNamesItem->mNext; list != firstRepeat; list = list->mNext) { if (list->mValue.GetUnit() == eCSSUnit_Function) { nsCSSValue::Array* func = list->mValue.GetArrayValue(); auto funcName = func->Item(0).GetKeywordValue(); if (funcName == eCSSKeyword_minmax) { if (!func->Item(1).IsLengthPercentCalcUnit() && !func->Item(2).IsLengthPercentCalcUnit()) { return false; } } else { MOZ_ASSERT(funcName == eCSSKeyword_fit_content, "Expected minmax() or fit-content() function"); return false; // fit-content() is not a <fixed-size> } } else if (!list->mValue.IsLengthPercentCalcUnit()) { return false; } list = list->mNext; // skip line names } } } else { UngetToken(); // Not a repeat() function; try to parse <track-size> | <fixed-size>. nsCSSValue trackSize; GridTrackSizeFlags flags = haveRepeatAuto ? GridTrackSizeFlags::eFixedTrackSize : GridTrackSizeFlags::eDefaultTrackSize; CSSParseResult result = ParseGridTrackSize(trackSize, flags); if (result == CSSParseResult::Error) { return false; } if (result == CSSParseResult::NotFound) { // What we've parsed so far is a valid <track-list> // (modulo the "at least one <track-size>" check below.) // Stop here. break; } item->mNext = new nsCSSValueList; item = item->mNext; item->mValue = trackSize; item->mNext = new nsCSSValueList; item = item->mNext; } if (ParseGridLineNames(item->mValue) == CSSParseResult::Error) { return false; } } // Require at least one <track-size>. if (item == firstLineNamesItem) { return false; } MOZ_ASSERT(aValue.GetListValue() && aValue.GetListValue()->mNext && aValue.GetListValue()->mNext->mNext, "<track-list> should have a minimum length of 3"); return true; } // Takes ownership of |aSecond| static void ConcatLineNames(nsCSSValue& aFirst, nsCSSValue& aSecond) { if (aSecond.GetUnit() == eCSSUnit_Null) { // Nothing to do. return; } if (aFirst.GetUnit() == eCSSUnit_Null) { // Empty or omitted <line-names>. Replace it. aFirst = aSecond; return; } // Join the two <line-names> lists. nsCSSValueList* source = aSecond.GetListValue(); nsCSSValueList* target = aFirst.GetListValue(); // Find the end: while (target->mNext) { target = target->mNext; } // Copy the first name. We can't take ownership of it // as it'll be destroyed when |aSecond| goes out of scope. target->mNext = new nsCSSValueList; target = target->mNext; target->mValue = source->mValue; // Move the rest of the linked list. target->mNext = source->mNext; source->mNext = nullptr; } // Assuming the 'repeat(' function token has already been consumed, // parse "repeat( <positive-integer> | auto-fill | auto-fit ," // (or "repeat( <positive-integer> | auto-fill ," when aForSubgrid is true) // and stop after the comma. Return true when parsing succeeds, // with aRepetitions set to the number of repetitions and aRepeatAutoEnum set // to an enum value for auto-fill | auto-fit (it's not set at all when // <positive-integer> was parsed). bool CSSParserImpl::ParseGridTrackRepeatIntro(bool aForSubgrid, int32_t* aRepetitions, Maybe<int32_t>* aRepeatAutoEnum) { if (!GetToken(true)) { return false; } if (mToken.mType == eCSSToken_Ident) { if (mToken.mIdent.LowerCaseEqualsLiteral("auto-fill")) { aRepeatAutoEnum->emplace(NS_STYLE_GRID_REPEAT_AUTO_FILL); } else if (!aForSubgrid && mToken.mIdent.LowerCaseEqualsLiteral("auto-fit")) { aRepeatAutoEnum->emplace(NS_STYLE_GRID_REPEAT_AUTO_FIT); } else { return false; } *aRepetitions = 1; } else if (mToken.mType == eCSSToken_Number) { if (!(mToken.mIntegerValid && mToken.mInteger > 0)) { return false; } *aRepetitions = std::min(mToken.mInteger, GRID_TEMPLATE_MAX_REPETITIONS); } else { return false; } if (!ExpectSymbol(',', true)) { return false; } return true; } // Assuming the 'repeat(' function token has already been consumed, // parse the rest of // repeat( <positive-integer> | auto-fill | auto-fit , // [ <line-names>? <track-size> ]+ <line-names>? ) // Append to the linked list whose end is given by |aTailPtr|, // and update |aTailPtr| to point to the new end of the list. // Note: only one <track-size> is allowed for auto-fill/fit bool CSSParserImpl::ParseGridTrackListRepeat(nsCSSValueList** aTailPtr) { int32_t repetitions; Maybe<int32_t> repeatAutoEnum; if (!ParseGridTrackRepeatIntro(false, &repetitions, &repeatAutoEnum)) { return false; } // Parse [ <line-names>? <track-size> ]+ <line-names>? // but keep the first and last <line-names> separate // because they'll need to be joined. // http://dev.w3.org/csswg/css-grid/#repeat-notation nsCSSValue firstLineNames; nsCSSValue trackSize; nsCSSValue lastLineNames; // Optional if (ParseGridLineNames(firstLineNames) == CSSParseResult::Error) { return false; } // Required GridTrackSizeFlags flags = repeatAutoEnum.isSome() ? GridTrackSizeFlags::eFixedTrackSize : GridTrackSizeFlags::eDefaultTrackSize; if (ParseGridTrackSize(trackSize, flags) != CSSParseResult::Ok) { return false; } // Use nsAutoPtr to free the list in case of early return. nsAutoPtr<nsCSSValueList> firstTrackSizeItemAuto(new nsCSSValueList); firstTrackSizeItemAuto->mValue = trackSize; nsCSSValueList* item = firstTrackSizeItemAuto; for (;;) { // Optional if (ParseGridLineNames(lastLineNames) == CSSParseResult::Error) { return false; } if (ExpectSymbol(')', true)) { break; } // <auto-repeat> only accepts a single track size: // <line-names>? <fixed-size> <line-names>? if (repeatAutoEnum.isSome()) { REPORT_UNEXPECTED(PEMoreThanOneGridRepeatTrackSize); return false; } // Required if (ParseGridTrackSize(trackSize) != CSSParseResult::Ok) { return false; } item->mNext = new nsCSSValueList; item = item->mNext; item->mValue = lastLineNames; // Do not append to this list at the next iteration. lastLineNames.Reset(); item->mNext = new nsCSSValueList; item = item->mNext; item->mValue = trackSize; } nsCSSValueList* lastTrackSizeItem = item; // [ <line-names>? <track-size> ]+ <line-names>? is now parsed into: // * firstLineNames: the first <line-names> // * a linked list of odd length >= 1, from firstTrackSizeItem // (the first <track-size>) to lastTrackSizeItem (the last), // with the <line-names> sublists in between // * lastLineNames: the last <line-names> if (repeatAutoEnum.isSome()) { // Instead of hooking up this list into the flat track/name list as usual, // we create a pair(Int, List) where the first value is the auto-fill/fit // keyword and the second is the list to repeat. There are three items // in this list, the first is the list of line names before the track size, // the second item is the track size, and the last item is the list of line // names after the track size. Note that the line names are NOT merged // with any line names before/after the repeat() itself. nsCSSValue listValue; nsCSSValueList* list = listValue.SetListValue(); list->mValue = firstLineNames; list = list->mNext = new nsCSSValueList; list->mValue = trackSize; list = list->mNext = new nsCSSValueList; list->mValue = lastLineNames; nsCSSValue kwd; kwd.SetIntValue(repeatAutoEnum.value(), eCSSUnit_Enumerated); *aTailPtr = (*aTailPtr)->mNext = new nsCSSValueList; (*aTailPtr)->mValue.SetPairValue(kwd, listValue); // Append an empty list since the caller expects that to represent the names // that follows the repeat() function. *aTailPtr = (*aTailPtr)->mNext = new nsCSSValueList; return true; } // Join the last and first <line-names> (in that order.) // For example, repeat(3, (a) 100px (b) 200px (c)) results in // (a) 100px (b) 200px (c a) 100px (b) 200px (c a) 100px (b) 200px (c) // This is (c a). // Make deep copies: the originals will be moved. nsCSSValue joinerLineNames; { nsCSSValueList* target = nullptr; if (lastLineNames.GetUnit() != eCSSUnit_Null) { target = joinerLineNames.SetListValue(); nsCSSValueList* source = lastLineNames.GetListValue(); for (;;) { target->mValue = source->mValue; source = source->mNext; if (!source) { break; } target->mNext = new nsCSSValueList; target = target->mNext; } } if (firstLineNames.GetUnit() != eCSSUnit_Null) { if (target) { target->mNext = new nsCSSValueList; target = target->mNext; } else { target = joinerLineNames.SetListValue(); } nsCSSValueList* source = firstLineNames.GetListValue(); for (;;) { target->mValue = source->mValue; source = source->mNext; if (!source) { break; } target->mNext = new nsCSSValueList; target = target->mNext; } } } // Join our first <line-names> with the one before repeat(). // (a) repeat(1, (b) 20px) expands to (a b) 20px nsCSSValueList* previousItemBeforeRepeat = *aTailPtr; ConcatLineNames(previousItemBeforeRepeat->mValue, firstLineNames); // Move our linked list // (first to last <track-size>, with the <line-names> sublists in between). // This is the first repetition. NS_ASSERTION(previousItemBeforeRepeat->mNext == nullptr, "Expected the end of a linked list"); previousItemBeforeRepeat->mNext = firstTrackSizeItemAuto.forget(); nsCSSValueList* firstTrackSizeItem = previousItemBeforeRepeat->mNext; nsCSSValueList* tail = lastTrackSizeItem; // Repeat |repetitions - 1| more times: // * the joiner <line-names> // * the linked list // (first to last <track-size>, with the <line-names> sublists in between) MOZ_ASSERT(repetitions > 0, "Expected positive repetitions"); while (--repetitions) { tail->mNext = new nsCSSValueList; tail = tail->mNext; tail->mValue = joinerLineNames; nsCSSValueList* repeatedItem = firstTrackSizeItem; for (;;) { tail->mNext = new nsCSSValueList; tail = tail->mNext; tail->mValue = repeatedItem->mValue; if (repeatedItem == lastTrackSizeItem) { break; } repeatedItem = repeatedItem->mNext; } } // Finally, move our last <line-names>. // Any <line-names> immediately after repeat() will append to it. tail->mNext = new nsCSSValueList; tail = tail->mNext; tail->mValue = lastLineNames; *aTailPtr = tail; return true; } bool CSSParserImpl::ParseGridTrackList(nsCSSPropertyID aPropID, GridTrackListFlags aFlags) { nsCSSValue value; nsCSSValue firstLineNames; if (ParseGridLineNames(firstLineNames) == CSSParseResult::Error || !ParseGridTrackListWithFirstLineNames(value, firstLineNames, aFlags)) { return false; } AppendValue(aPropID, value); return true; } bool CSSParserImpl::ParseGridTemplateColumnsRows(nsCSSPropertyID aPropID) { nsCSSValue value; if (ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE, nullptr)) { AppendValue(aPropID, value); return true; } nsSubstring* ident = NextIdent(); if (ident) { if (ident->LowerCaseEqualsLiteral("subgrid")) { if (!nsLayoutUtils::IsGridTemplateSubgridValueEnabled()) { REPORT_UNEXPECTED(PESubgridNotSupported); return false; } if (!ParseOptionalLineNameListAfterSubgrid(value)) { return false; } AppendValue(aPropID, value); return true; } UngetToken(); } return ParseGridTrackList(aPropID); } bool CSSParserImpl::ParseGridTemplateAreasLine(const nsAutoString& aInput, css::GridTemplateAreasValue* aAreas, nsDataHashtable<nsStringHashKey, uint32_t>& aAreaIndices) { aAreas->mTemplates.AppendElement(mToken.mIdent); nsCSSGridTemplateAreaScanner scanner(aInput); nsCSSGridTemplateAreaToken token; css::GridNamedArea* currentArea = nullptr; uint32_t row = aAreas->NRows(); // Column numbers starts at 1, but we might not have any, eg // grid-template-areas:""; which will result in mNColumns == 0. uint32_t column = 0; while (scanner.Next(token)) { ++column; if (token.isTrash) { return false; } if (currentArea) { if (token.mName == currentArea->mName) { if (currentArea->mRowStart == row) { // Next column in the first row of this named area. currentArea->mColumnEnd++; } continue; } // We're exiting |currentArea|, so currentArea is ending at |column|. // Make sure that this is consistent with currentArea on previous rows: if (currentArea->mColumnEnd != column) { NS_ASSERTION(currentArea->mRowStart != row, "Inconsistent column end for the first row of a named area."); // Not a rectangle return false; } currentArea = nullptr; } if (!token.mName.IsEmpty()) { // Named cell that doesn't have a cell with the same name on its left. // Check if this is the continuation of an existing named area: uint32_t index; if (aAreaIndices.Get(token.mName, &index)) { MOZ_ASSERT(index < aAreas->mNamedAreas.Length(), "Invalid aAreaIndices hash table"); currentArea = &aAreas->mNamedAreas[index]; if (currentArea->mColumnStart != column || currentArea->mRowEnd != row) { // Existing named area, but not forming a rectangle return false; } // Next row of an existing named area currentArea->mRowEnd++; } else { // New named area aAreaIndices.Put(token.mName, aAreas->mNamedAreas.Length()); currentArea = aAreas->mNamedAreas.AppendElement(); currentArea->mName = token.mName; // For column or row N (starting at 1), // the start line is N, the end line is N + 1 currentArea->mColumnStart = column; currentArea->mColumnEnd = column + 1; currentArea->mRowStart = row; currentArea->mRowEnd = row + 1; } } } if (currentArea && currentArea->mColumnEnd != column + 1) { NS_ASSERTION(currentArea->mRowStart != row, "Inconsistent column end for the first row of a named area."); // Not a rectangle return false; } // On the first row, set the number of columns // that grid-template-areas contributes to the explicit grid. // On other rows, check that the number of columns is consistent // between rows. if (row == 1) { aAreas->mNColumns = column; } else if (aAreas->mNColumns != column) { return false; } return true; } bool CSSParserImpl::ParseGridTemplateAreas() { nsCSSValue value; if (ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NONE, nullptr)) { AppendValue(eCSSProperty_grid_template_areas, value); return true; } RefPtr<css::GridTemplateAreasValue> areas = new css::GridTemplateAreasValue(); nsDataHashtable<nsStringHashKey, uint32_t> areaIndices; for (;;) { if (!GetToken(true)) { break; } if (eCSSToken_String != mToken.mType) { UngetToken(); break; } if (!ParseGridTemplateAreasLine(mToken.mIdent, areas, areaIndices)) { return false; } } if (areas->NRows() == 0) { return false; } AppendValue(eCSSProperty_grid_template_areas, nsCSSValue(areas)); return true; } // [ auto-flow && dense? ] <'grid-auto-columns'>? | // <'grid-template-columns'> bool CSSParserImpl::ParseGridTemplateColumnsOrAutoFlow(bool aForGridShorthand) { if (aForGridShorthand) { auto res = ParseGridShorthandAutoProps(NS_STYLE_GRID_AUTO_FLOW_COLUMN); if (res == CSSParseResult::Error) { return false; } if (res == CSSParseResult::Ok) { nsCSSValue value(eCSSUnit_None); AppendValue(eCSSProperty_grid_template_columns, value); return true; } } return ParseGridTemplateColumnsRows(eCSSProperty_grid_template_columns); } bool CSSParserImpl::ParseGridTemplate(bool aForGridShorthand) { // none | // subgrid | // <'grid-template-rows'> / <'grid-template-columns'> | // [ <line-names>? <string> <track-size>? <line-names>? ]+ [ / <explicit-track-list>]? // or additionally when aForGridShorthand is true: // <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>? nsCSSValue value; if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { AppendValue(eCSSProperty_grid_template_areas, value); AppendValue(eCSSProperty_grid_template_rows, value); AppendValue(eCSSProperty_grid_template_columns, value); return true; } // 'none' can appear either by itself, // or as the beginning of <'grid-template-rows'> / <'grid-template-columns'> if (ParseSingleTokenVariant(value, VARIANT_NONE, nullptr)) { AppendValue(eCSSProperty_grid_template_rows, value); AppendValue(eCSSProperty_grid_template_areas, value); if (ExpectSymbol('/', true)) { return ParseGridTemplateColumnsOrAutoFlow(aForGridShorthand); } AppendValue(eCSSProperty_grid_template_columns, value); return true; } // 'subgrid' can appear either by itself, // or as the beginning of <'grid-template-rows'> / <'grid-template-columns'> nsSubstring* ident = NextIdent(); if (ident) { if (ident->LowerCaseEqualsLiteral("subgrid")) { if (!nsLayoutUtils::IsGridTemplateSubgridValueEnabled()) { REPORT_UNEXPECTED(PESubgridNotSupported); return false; } if (!ParseOptionalLineNameListAfterSubgrid(value)) { return false; } AppendValue(eCSSProperty_grid_template_rows, value); AppendValue(eCSSProperty_grid_template_areas, nsCSSValue(eCSSUnit_None)); if (ExpectSymbol('/', true)) { return ParseGridTemplateColumnsOrAutoFlow(aForGridShorthand); } if (value.GetListValue()->mNext) { // Non-empty <line-name-list> after 'subgrid'. // This is only valid as part of <'grid-template-rows'>, // which must be followed by a slash. return false; } // 'subgrid' by itself sets both grid-template-rows/columns. AppendValue(eCSSProperty_grid_template_columns, value); return true; } UngetToken(); } // [ <line-names>? ] here is ambiguous: // it can be either the start of a <track-list> (in a <'grid-template-rows'>), // or the start of [ <line-names>? <string> <track-size>? <line-names>? ]+ nsCSSValue firstLineNames; if (ParseGridLineNames(firstLineNames) == CSSParseResult::Error || !GetToken(true)) { return false; } if (mToken.mType == eCSSToken_String) { // It's the [ <line-names>? <string> <track-size>? <line-names>? ]+ case. if (!ParseGridTemplateAfterString(firstLineNames)) { return false; } // Parse an optional [ / <explicit-track-list> ] as the columns value. if (ExpectSymbol('/', true)) { return ParseGridTrackList(eCSSProperty_grid_template_columns, GridTrackListFlags::eExplicitTrackList); } value.SetNoneValue(); // absent means 'none' AppendValue(eCSSProperty_grid_template_columns, value); return true; } UngetToken(); // Finish parsing <'grid-template-rows'> with the |firstLineNames| we have, // and then parse a mandatory [ / <'grid-template-columns'> ]. if (!ParseGridTrackListWithFirstLineNames(value, firstLineNames) || !ExpectSymbol('/', true)) { return false; } AppendValue(eCSSProperty_grid_template_rows, value); value.SetNoneValue(); AppendValue(eCSSProperty_grid_template_areas, value); return ParseGridTemplateColumnsOrAutoFlow(aForGridShorthand); } // Helper for parsing the 'grid-template' shorthand: // Parse [ <line-names>? <string> <track-size>? <line-names>? ]+ // with a <line-names>? already consumed, stored in |aFirstLineNames|, // and the current token a <string> bool CSSParserImpl::ParseGridTemplateAfterString(const nsCSSValue& aFirstLineNames) { MOZ_ASSERT(mToken.mType == eCSSToken_String, "ParseGridTemplateAfterString called with a non-string token"); nsCSSValue rowsValue; RefPtr<css::GridTemplateAreasValue> areas = new css::GridTemplateAreasValue(); nsDataHashtable<nsStringHashKey, uint32_t> areaIndices; nsCSSValueList* rowsItem = rowsValue.SetListValue(); rowsItem->mValue = aFirstLineNames; for (;;) { if (!ParseGridTemplateAreasLine(mToken.mIdent, areas, areaIndices)) { return false; } rowsItem->mNext = new nsCSSValueList; rowsItem = rowsItem->mNext; CSSParseResult result = ParseGridTrackSize(rowsItem->mValue); if (result == CSSParseResult::Error) { return false; } if (result == CSSParseResult::NotFound) { rowsItem->mValue.SetAutoValue(); } rowsItem->mNext = new nsCSSValueList; rowsItem = rowsItem->mNext; result = ParseGridLineNames(rowsItem->mValue); if (result == CSSParseResult::Error) { return false; } if (result == CSSParseResult::Ok) { // Append to the same list as the previous call to ParseGridLineNames. result = ParseGridLineNames(rowsItem->mValue); if (result == CSSParseResult::Error) { return false; } if (result == CSSParseResult::Ok) { // Parsed <line-name> twice. // The property value can not end here, we expect a string next. if (!GetToken(true)) { return false; } if (eCSSToken_String != mToken.mType) { UngetToken(); return false; } continue; } } // Did not find a <line-names>. // Next, we expect either a string or the end of the property value. if (!GetToken(true)) { break; } if (eCSSToken_String != mToken.mType) { UngetToken(); break; } } AppendValue(eCSSProperty_grid_template_areas, nsCSSValue(areas)); AppendValue(eCSSProperty_grid_template_rows, rowsValue); return true; } // <'grid-template'> | // <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>? | // [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'> bool CSSParserImpl::ParseGrid() { nsCSSValue value; if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { for (const nsCSSPropertyID* subprops = nsCSSProps::SubpropertyEntryFor(eCSSProperty_grid); *subprops != eCSSProperty_UNKNOWN; ++subprops) { AppendValue(*subprops, value); } return true; } // https://drafts.csswg.org/css-grid/#grid-shorthand // "Also, the gutter properties are reset by this shorthand, // even though they can't be set by it." value.SetFloatValue(0.0f, eCSSUnit_Pixel); AppendValue(eCSSProperty_grid_row_gap, value); AppendValue(eCSSProperty_grid_column_gap, value); // [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'> auto res = ParseGridShorthandAutoProps(NS_STYLE_GRID_AUTO_FLOW_ROW); if (res == CSSParseResult::Error) { return false; } if (res == CSSParseResult::Ok) { value.SetAutoValue(); AppendValue(eCSSProperty_grid_auto_columns, value); nsCSSValue none(eCSSUnit_None); AppendValue(eCSSProperty_grid_template_areas, none); AppendValue(eCSSProperty_grid_template_rows, none); if (!ExpectSymbol('/', true)) { return false; } return ParseGridTemplateColumnsRows(eCSSProperty_grid_template_columns); } // Set remaining subproperties that might not be set by ParseGridTemplate to // their initial values and then parse <'grid-template'> | // <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>? . value.SetIntValue(NS_STYLE_GRID_AUTO_FLOW_ROW, eCSSUnit_Enumerated); AppendValue(eCSSProperty_grid_auto_flow, value); value.SetAutoValue(); AppendValue(eCSSProperty_grid_auto_rows, value); AppendValue(eCSSProperty_grid_auto_columns, value); return ParseGridTemplate(true); } // Parse [ auto-flow && dense? ] <'grid-auto-[rows|columns]'>? for the 'grid' // shorthand. If aAutoFlowAxis == NS_STYLE_GRID_AUTO_FLOW_ROW then we're // parsing row values, otherwise column values. CSSParseResult CSSParserImpl::ParseGridShorthandAutoProps(int32_t aAutoFlowAxis) { MOZ_ASSERT(aAutoFlowAxis == NS_STYLE_GRID_AUTO_FLOW_ROW || aAutoFlowAxis == NS_STYLE_GRID_AUTO_FLOW_COLUMN); if (!GetToken(true)) { return CSSParseResult::NotFound; } // [ auto-flow && dense? ] int32_t autoFlowValue = 0; if (mToken.mType == eCSSToken_Ident) { nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent); if (keyword == eCSSKeyword_auto_flow) { autoFlowValue = aAutoFlowAxis; if (GetToken(true)) { if (mToken.mType == eCSSToken_Ident && nsCSSKeywords::LookupKeyword(mToken.mIdent) == eCSSKeyword_dense) { autoFlowValue |= NS_STYLE_GRID_AUTO_FLOW_DENSE; } else { UngetToken(); } } } else if (keyword == eCSSKeyword_dense) { if (!GetToken(true)) { return CSSParseResult::Error; } if (mToken.mType != eCSSToken_Ident || nsCSSKeywords::LookupKeyword(mToken.mIdent) != eCSSKeyword_auto_flow) { UngetToken(); return CSSParseResult::Error; } autoFlowValue = aAutoFlowAxis | NS_STYLE_GRID_AUTO_FLOW_DENSE; } } if (autoFlowValue) { nsCSSValue value; value.SetIntValue(autoFlowValue, eCSSUnit_Enumerated); AppendValue(eCSSProperty_grid_auto_flow, value); } else { UngetToken(); return CSSParseResult::NotFound; } // <'grid-auto-[rows|columns]'>? nsCSSValue autoTrackValue; CSSParseResult result = ParseGridTrackSize(autoTrackValue); if (result == CSSParseResult::Error) { return result; } if (result == CSSParseResult::NotFound) { autoTrackValue.SetAutoValue(); } AppendValue(aAutoFlowAxis == NS_STYLE_GRID_AUTO_FLOW_ROW ? eCSSProperty_grid_auto_rows : eCSSProperty_grid_auto_columns, autoTrackValue); return CSSParseResult::Ok; } // Parse a <grid-line>. // If successful, set aValue to eCSSUnit_Auto, // or a eCSSUnit_List containing, in that order: // // * An optional eCSSUnit_Enumerated marking a "span" keyword. // * An optional eCSSUnit_Integer // * An optional eCSSUnit_Ident // // At least one of eCSSUnit_Integer or eCSSUnit_Ident is present. bool CSSParserImpl::ParseGridLine(nsCSSValue& aValue) { // <grid-line> = // auto | // <custom-ident> | // [ <integer> && <custom-ident>? ] | // [ span && [ <integer> || <custom-ident> ] ] // // Syntactically, this simplifies to: // // <grid-line> = // auto | // [ span? && [ <integer> || <custom-ident> ] ] if (ParseSingleTokenVariant(aValue, VARIANT_AUTO, nullptr)) { return true; } bool hasSpan = false; bool hasIdent = false; Maybe<int32_t> integer; nsCSSValue ident; #ifdef MOZ_VALGRIND // Make the contained value be defined even though we really want a // Nothing here. This works around an otherwise difficult to avoid // Memcheck false positive when this is compiled by gcc-5.3 -O2. // See bug 1301856. integer.emplace(0); integer.reset(); #endif if (!GetToken(true)) { return false; } if (mToken.mType == eCSSToken_Ident && mToken.mIdent.LowerCaseEqualsLiteral("span")) { hasSpan = true; if (!GetToken(true)) { return false; } } do { if (!hasIdent && mToken.mType == eCSSToken_Ident && ParseCustomIdent(ident, mToken.mIdent, kGridLineKeywords)) { hasIdent = true; } else if (integer.isNothing() && mToken.mType == eCSSToken_Number && mToken.mIntegerValid && mToken.mInteger != 0) { integer.emplace(mToken.mInteger); } else { UngetToken(); break; } } while (!(integer.isSome() && hasIdent) && GetToken(true)); // Require at least one of <integer> or <custom-ident> if (!(integer.isSome() || hasIdent)) { return false; } if (!hasSpan && GetToken(true)) { if (mToken.mType == eCSSToken_Ident && mToken.mIdent.LowerCaseEqualsLiteral("span")) { hasSpan = true; } else { UngetToken(); } } nsCSSValueList* item = aValue.SetListValue(); if (hasSpan) { // Given "span", a negative <integer> is invalid. if (integer.isSome() && integer.ref() < 0) { return false; } // '1' here is a dummy value. // The mere presence of eCSSUnit_Enumerated indicates a "span" keyword. item->mValue.SetIntValue(1, eCSSUnit_Enumerated); item->mNext = new nsCSSValueList; item = item->mNext; } if (integer.isSome()) { item->mValue.SetIntValue(integer.ref(), eCSSUnit_Integer); if (hasIdent) { item->mNext = new nsCSSValueList; item = item->mNext; } } if (hasIdent) { item->mValue = ident; } return true; } bool CSSParserImpl::ParseGridColumnRowStartEnd(nsCSSPropertyID aPropID) { nsCSSValue value; if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr) || ParseGridLine(value)) { AppendValue(aPropID, value); return true; } return false; } // If |aFallback| is a List containing a single Ident, set |aValue| to that. // Otherwise, set |aValue| to Auto. // Used with |aFallback| from ParseGridLine() static void HandleGridLineFallback(const nsCSSValue& aFallback, nsCSSValue& aValue) { if (aFallback.GetUnit() == eCSSUnit_List && aFallback.GetListValue()->mValue.GetUnit() == eCSSUnit_Ident && !aFallback.GetListValue()->mNext) { aValue = aFallback; } else { aValue.SetAutoValue(); } } bool CSSParserImpl::ParseGridColumnRow(nsCSSPropertyID aStartPropID, nsCSSPropertyID aEndPropID) { nsCSSValue value; nsCSSValue secondValue; if (ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { AppendValue(aStartPropID, value); AppendValue(aEndPropID, value); return true; } if (!ParseGridLine(value)) { return false; } if (GetToken(true)) { if (mToken.IsSymbol('/')) { if (ParseGridLine(secondValue)) { AppendValue(aStartPropID, value); AppendValue(aEndPropID, secondValue); return true; } else { return false; } } UngetToken(); } // A single <custom-ident> is repeated to both properties, // anything else sets the grid-{column,row}-end property to 'auto'. HandleGridLineFallback(value, secondValue); AppendValue(aStartPropID, value); AppendValue(aEndPropID, secondValue); return true; } bool CSSParserImpl::ParseGridArea() { nsCSSValue values[4]; if (ParseSingleTokenVariant(values[0], VARIANT_INHERIT, nullptr)) { AppendValue(eCSSProperty_grid_row_start, values[0]); AppendValue(eCSSProperty_grid_column_start, values[0]); AppendValue(eCSSProperty_grid_row_end, values[0]); AppendValue(eCSSProperty_grid_column_end, values[0]); return true; } int32_t i = 0; for (;;) { if (!ParseGridLine(values[i])) { return false; } if (++i == 4 || !GetToken(true)) { break; } if (!mToken.IsSymbol('/')) { UngetToken(); break; } } MOZ_ASSERT(i >= 1, "should have parsed at least one grid-line (or returned)"); if (i < 2) { HandleGridLineFallback(values[0], values[1]); } if (i < 3) { HandleGridLineFallback(values[0], values[2]); } if (i < 4) { HandleGridLineFallback(values[1], values[3]); } AppendValue(eCSSProperty_grid_row_start, values[0]); AppendValue(eCSSProperty_grid_column_start, values[1]); AppendValue(eCSSProperty_grid_row_end, values[2]); AppendValue(eCSSProperty_grid_column_end, values[3]); return true; } bool CSSParserImpl::ParseGridGap() { nsCSSValue first; if (ParseSingleTokenVariant(first, VARIANT_INHERIT, nullptr)) { AppendValue(eCSSProperty_grid_row_gap, first); AppendValue(eCSSProperty_grid_column_gap, first); return true; } if (ParseNonNegativeVariant(first, VARIANT_LPCALC, nullptr) != CSSParseResult::Ok) { return false; } nsCSSValue second; auto result = ParseNonNegativeVariant(second, VARIANT_LPCALC, nullptr); if (result == CSSParseResult::Error) { return false; } AppendValue(eCSSProperty_grid_row_gap, first); AppendValue(eCSSProperty_grid_column_gap, result == CSSParseResult::NotFound ? first : second); return true; } // normal | [<number> <integer>?] bool CSSParserImpl::ParseInitialLetter() { nsCSSValue value; // 'inherit', 'initial', 'unset', 'none', and 'normal' must be alone if (!ParseSingleTokenVariant(value, VARIANT_INHERIT | VARIANT_NORMAL, nullptr)) { nsCSSValue first, second; if (!ParseOneOrLargerNumber(first)) { return false; } if (!ParseOneOrLargerInteger(second)) { AppendValue(eCSSProperty_initial_letter, first); return true; } else { RefPtr<nsCSSValue::Array> val = nsCSSValue::Array::Create(2); val->Item(0) = first; val->Item(1) = second; value.SetArrayValue(val, eCSSUnit_Array); } } AppendValue(eCSSProperty_initial_letter, value); return true; } // [ $aTable && <overflow-position>? ] ? // $aTable is for <content-position> or <self-position> bool CSSParserImpl::ParseAlignJustifyPosition(nsCSSValue& aResult, const KTableEntry aTable[]) { nsCSSValue pos, overflowPos; int32_t value = 0; if (ParseEnum(pos, aTable)) { value = pos.GetIntValue(); if (ParseEnum(overflowPos, nsCSSProps::kAlignOverflowPosition)) { value |= overflowPos.GetIntValue(); } aResult.SetIntValue(value, eCSSUnit_Enumerated); return true; } if (ParseEnum(overflowPos, nsCSSProps::kAlignOverflowPosition)) { if (ParseEnum(pos, aTable)) { aResult.SetIntValue(pos.GetIntValue() | overflowPos.GetIntValue(), eCSSUnit_Enumerated); return true; } return false; // <overflow-position> must be followed by a value in $table } return true; } // auto | normal | stretch | <baseline-position> | // [ <self-position> && <overflow-position>? ] | // [ legacy && [ left | right | center ] ] bool CSSParserImpl::ParseJustifyItems() { nsCSSValue value; if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { if (MOZ_UNLIKELY(ParseEnum(value, nsCSSProps::kAlignLegacy))) { nsCSSValue legacy; if (!ParseEnum(legacy, nsCSSProps::kAlignLegacyPosition)) { return false; // leading 'legacy' not followed by 'left' etc is an error } value.SetIntValue(value.GetIntValue() | legacy.GetIntValue(), eCSSUnit_Enumerated); } else { if (!ParseAlignEnum(value, nsCSSProps::kAlignAutoNormalStretchBaseline)) { if (!ParseAlignJustifyPosition(value, nsCSSProps::kAlignSelfPosition) || value.GetUnit() == eCSSUnit_Null) { return false; } // check for a trailing 'legacy' after 'left' etc auto val = value.GetIntValue(); if (val == NS_STYLE_JUSTIFY_CENTER || val == NS_STYLE_JUSTIFY_LEFT || val == NS_STYLE_JUSTIFY_RIGHT) { nsCSSValue legacy; if (ParseEnum(legacy, nsCSSProps::kAlignLegacy)) { value.SetIntValue(val | legacy.GetIntValue(), eCSSUnit_Enumerated); } } } } } AppendValue(eCSSProperty_justify_items, value); return true; } // normal | stretch | <baseline-position> | // [ <overflow-position>? && <self-position> ] bool CSSParserImpl::ParseAlignItems() { nsCSSValue value; if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { if (!ParseAlignEnum(value, nsCSSProps::kAlignNormalStretchBaseline)) { if (!ParseAlignJustifyPosition(value, nsCSSProps::kAlignSelfPosition) || value.GetUnit() == eCSSUnit_Null) { return false; } } } AppendValue(eCSSProperty_align_items, value); return true; } // auto | normal | stretch | <baseline-position> | // [ <overflow-position>? && <self-position> ] bool CSSParserImpl::ParseAlignJustifySelf(nsCSSPropertyID aPropID) { nsCSSValue value; if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { if (!ParseAlignEnum(value, nsCSSProps::kAlignAutoNormalStretchBaseline)) { if (!ParseAlignJustifyPosition(value, nsCSSProps::kAlignSelfPosition) || value.GetUnit() == eCSSUnit_Null) { return false; } } } AppendValue(aPropID, value); return true; } // normal | <baseline-position> | [ <content-distribution> || // [ <overflow-position>? && <content-position> ] ] // (the part after the || is called <*-position> below) bool CSSParserImpl::ParseAlignJustifyContent(nsCSSPropertyID aPropID) { nsCSSValue value; if (!ParseSingleTokenVariant(value, VARIANT_INHERIT, nullptr)) { if (!ParseAlignEnum(value, nsCSSProps::kAlignNormalBaseline)) { nsCSSValue fallbackValue; if (!ParseEnum(value, nsCSSProps::kAlignContentDistribution)) { if (!ParseAlignJustifyPosition(fallbackValue, nsCSSProps::kAlignContentPosition) || fallbackValue.GetUnit() == eCSSUnit_Null) { return false; } // optional <content-distribution> after <*-position> ... if (!ParseEnum(value, nsCSSProps::kAlignContentDistribution)) { // ... is missing so the <*-position> is the value, not the fallback value = fallbackValue; fallbackValue.Reset(); } } else { // any optional <*-position> is a fallback value if (!ParseAlignJustifyPosition(fallbackValue, nsCSSProps::kAlignContentPosition)) { return false; } } if (fallbackValue.GetUnit() != eCSSUnit_Null) { auto fallback = fallbackValue.GetIntValue(); value.SetIntValue(value.GetIntValue() | (fallback << NS_STYLE_ALIGN_ALL_SHIFT), eCSSUnit_Enumerated); } } } AppendValue(aPropID, value); return true; } // place-content: [ normal | <baseline-position> | <content-distribution> | // <content-position> ]{1,2} bool CSSParserImpl::ParsePlaceContent() { nsCSSValue first; if (ParseSingleTokenVariant(first, VARIANT_INHERIT, nullptr)) { AppendValue(eCSSProperty_align_content, first); AppendValue(eCSSProperty_justify_content, first); return true; } if (!ParseAlignEnum(first, nsCSSProps::kAlignNormalBaseline) && !ParseEnum(first, nsCSSProps::kAlignContentDistribution) && !ParseEnum(first, nsCSSProps::kAlignContentPosition)) { return false; } AppendValue(eCSSProperty_align_content, first); nsCSSValue second; if (!ParseAlignEnum(second, nsCSSProps::kAlignNormalBaseline) && !ParseEnum(second, nsCSSProps::kAlignContentDistribution) && !ParseEnum(second, nsCSSProps::kAlignContentPosition)) { AppendValue(eCSSProperty_justify_content, first); } else { AppendValue(eCSSProperty_justify_content, second); } return true; } // place-items: <x> [ auto | <x> ]? // <x> = [ normal | stretch | <baseline-position> | <self-position> ] bool CSSParserImpl::ParsePlaceItems() { nsCSSValue first; if (ParseSingleTokenVariant(first, VARIANT_INHERIT, nullptr)) { AppendValue(eCSSProperty_align_items, first); AppendValue(eCSSProperty_justify_items, first); return true; } if (!ParseAlignEnum(first, nsCSSProps::kAlignNormalStretchBaseline) && !ParseEnum(first, nsCSSProps::kAlignSelfPosition)) { return false; } AppendValue(eCSSProperty_align_items, first); nsCSSValue second; // Note: 'auto' is valid for justify-items, but not align-items. if (!ParseAlignEnum(second, nsCSSProps::kAlignAutoNormalStretchBaseline) && !ParseEnum(second, nsCSSProps::kAlignSelfPosition)) { AppendValue(eCSSProperty_justify_items, first); } else { AppendValue(eCSSProperty_justify_items, second); } return true; } // place-self: [ auto | normal | stretch | <baseline-position> | // <self-position> ]{1,2} bool CSSParserImpl::ParsePlaceSelf() { nsCSSValue first; if (ParseSingleTokenVariant(first, VARIANT_INHERIT, nullptr)) { AppendValue(eCSSProperty_align_self, first); AppendValue(eCSSProperty_justify_self, first); return true; } if (!ParseAlignEnum(first, nsCSSProps::kAlignAutoNormalStretchBaseline) && !ParseEnum(first, nsCSSProps::kAlignSelfPosition)) { return false; } AppendValue(eCSSProperty_align_self, first); nsCSSValue second; if (!ParseAlignEnum(second, nsCSSProps::kAlignAutoNormalStretchBaseline) && !ParseEnum(second, nsCSSProps::kAlignSelfPosition)) { AppendValue(eCSSProperty_justify_self, first); } else { AppendValue(eCSSProperty_justify_self, second); } return true; } // <color-stop> : <color> [ <percentage> | <length> ]? bool CSSParserImpl::ParseColorStop(nsCSSValueGradient* aGradient) { nsCSSValueGradientStop* stop = aGradient->mStops.AppendElement(); CSSParseResult result = ParseVariant(stop->mColor, VARIANT_COLOR, nullptr); if (result == CSSParseResult::Error) { return false; } else if (result == CSSParseResult::NotFound) { stop->mIsInterpolationHint = true; } // Stop positions do not have to fall between the starting-point and // ending-point, so we don't use ParseNonNegativeVariant. result = ParseVariant(stop->mLocation, VARIANT_LP | VARIANT_CALC, nullptr); if (result == CSSParseResult::Error) { return false; } else if (result == CSSParseResult::NotFound) { if (stop->mIsInterpolationHint) { return false; } stop->mLocation.SetNoneValue(); } return true; } // Helper for ParseLinearGradient -- returns true iff aPosition represents a // box-position value which was parsed with only edge keywords. // e.g. "left top", or "bottom", but not "left 10px" // // (NOTE: Even though callers may want to exclude explicit "center", we still // need to allow for _CENTER here, because omitted position-values (e.g. the // x-component of a value like "top") will have been parsed as being *implicit* // center. The correct way to disallow *explicit* center is to pass "false" for // ParseBoxPositionValues()'s "aAllowExplicitCenter" parameter, before you // call this function.) static bool IsBoxPositionStrictlyEdgeKeywords(nsCSSValuePair& aPosition) { const nsCSSValue& xValue = aPosition.mXValue; const nsCSSValue& yValue = aPosition.mYValue; return (xValue.GetUnit() == eCSSUnit_Enumerated && (xValue.GetIntValue() & (NS_STYLE_IMAGELAYER_POSITION_LEFT | NS_STYLE_IMAGELAYER_POSITION_CENTER | NS_STYLE_IMAGELAYER_POSITION_RIGHT)) && yValue.GetUnit() == eCSSUnit_Enumerated && (yValue.GetIntValue() & (NS_STYLE_IMAGELAYER_POSITION_TOP | NS_STYLE_IMAGELAYER_POSITION_CENTER | NS_STYLE_IMAGELAYER_POSITION_BOTTOM))); } // <gradient> // : linear-gradient( <linear-gradient-line>? <color-stops> ')' // | radial-gradient( <radial-gradient-line>? <color-stops> ')' // // <linear-gradient-line> : [ to [left | right] || [top | bottom] ] , // | <legacy-gradient-line> // <radial-gradient-line> : [ <shape> || <size> ] [ at <position> ]? , // | [ at <position> ] , // | <legacy-gradient-line>? <legacy-shape-size>? // <shape> : circle | ellipse // <size> : closest-side | closest-corner | farthest-side | farthest-corner // | <length> | [<length> | <percentage>]{2} // // <legacy-gradient-line> : [ <position> || <angle>] , // // <legacy-shape-size> : [ <shape> || <legacy-size> ] , // <legacy-size> : closest-side | closest-corner | farthest-side // | farthest-corner | contain | cover // // <color-stops> : <color-stop> , <color-stop> [, <color-stop>]* bool CSSParserImpl::ParseLinearGradient(nsCSSValue& aValue, uint8_t aFlags) { RefPtr<nsCSSValueGradient> cssGradient = new nsCSSValueGradient(false, aFlags & eGradient_Repeating); if (!GetToken(true)) { return false; } // Check for "to" syntax (but not if parsing a -webkit-linear-gradient) if (!(aFlags & eGradient_WebkitLegacy) && mToken.mType == eCSSToken_Ident && mToken.mIdent.LowerCaseEqualsLiteral("to")) { // "to" syntax doesn't allow explicit "center" if (!ParseBoxPositionValues(cssGradient->mBgPos, false, false)) { SkipUntil(')'); return false; } // [ to [left | right] || [top | bottom] ] , if (!IsBoxPositionStrictlyEdgeKeywords(cssGradient->mBgPos)) { SkipUntil(')'); return false; } if (!ExpectSymbol(',', true)) { SkipUntil(')'); return false; } return ParseGradientColorStops(cssGradient, aValue); } if (!(aFlags & eGradient_AnyLegacy)) { // We're parsing an unprefixed linear-gradient, and we tried & failed to // parse a 'to' token above. Put the token back & try to re-parse our // expression as <angle>? <color-stop-list> UngetToken(); // <angle> , if (ParseSingleTokenVariant(cssGradient->mAngle, VARIANT_ANGLE_OR_ZERO, nullptr) && !ExpectSymbol(',', true)) { SkipUntil(')'); return false; } return ParseGradientColorStops(cssGradient, aValue); } // If we get here, we're parsing a prefixed linear-gradient expression. Put // back the first token (which we may have checked for "to" above) and try to // parse expression as <legacy-gradient-line>? <color-stop-list> bool haveGradientLine = IsLegacyGradientLine(mToken.mType, mToken.mIdent); UngetToken(); if (haveGradientLine) { // Parse a <legacy-gradient-line> cssGradient->mIsLegacySyntax = true; // In -webkit-linear-gradient expressions (handled below), we need to accept // unitless 0 for angles, to match WebKit/Blink. int32_t angleFlags = (aFlags & eGradient_WebkitLegacy) ? VARIANT_ANGLE | VARIANT_ZERO_ANGLE : VARIANT_ANGLE; bool haveAngle = ParseSingleTokenVariant(cssGradient->mAngle, angleFlags, nullptr); // If we got an angle, we might now have a comma, ending the gradient-line. bool haveAngleComma = haveAngle && ExpectSymbol(',', true); // If we're webkit-prefixed & didn't get an angle, // OR if we're moz-prefixed & didn't get an angle+comma, // then proceed to parse a box-position. if (((aFlags & eGradient_WebkitLegacy) && !haveAngle) || ((aFlags & eGradient_MozLegacy) && !haveAngleComma)) { // (Note: 3rd arg controls whether the "center" keyword is allowed. // -moz-linear-gradient allows it; -webkit-linear-gradient does not.) if (!ParseBoxPositionValues(cssGradient->mBgPos, false, (aFlags & eGradient_MozLegacy))) { SkipUntil(')'); return false; } // -webkit-linear-gradient only supports edge keywords here. if ((aFlags & eGradient_WebkitLegacy) && !IsBoxPositionStrictlyEdgeKeywords(cssGradient->mBgPos)) { SkipUntil(')'); return false; } if (!ExpectSymbol(',', true) && // If we didn't already get an angle, and we're not -webkit prefixed, // we can parse an angle+comma now. Otherwise it's an error. (haveAngle || (aFlags & eGradient_WebkitLegacy) || !ParseSingleTokenVariant(cssGradient->mAngle, VARIANT_ANGLE, nullptr) || // now we better have a comma !ExpectSymbol(',', true))) { SkipUntil(')'); return false; } } } return ParseGradientColorStops(cssGradient, aValue); } bool CSSParserImpl::ParseRadialGradient(nsCSSValue& aValue, uint8_t aFlags) { RefPtr<nsCSSValueGradient> cssGradient = new nsCSSValueGradient(true, aFlags & eGradient_Repeating); // [ <shape> || <size> ] bool haveShape = ParseSingleTokenVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD, nsCSSProps::kRadialGradientShapeKTable); bool haveSize = ParseSingleTokenVariant(cssGradient->GetRadialSize(), VARIANT_KEYWORD, (aFlags & eGradient_AnyLegacy) ? nsCSSProps::kRadialGradientLegacySizeKTable : nsCSSProps::kRadialGradientSizeKTable); if (haveSize) { if (!haveShape) { // <size> <shape> haveShape = ParseSingleTokenVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD, nsCSSProps::kRadialGradientShapeKTable); } } else if (!(aFlags & eGradient_AnyLegacy)) { // Save RadialShape before parsing RadiusX because RadialShape and // RadiusX share the storage. int32_t shape = cssGradient->GetRadialShape().GetUnit() == eCSSUnit_Enumerated ? cssGradient->GetRadialShape().GetIntValue() : -1; // <length> | [<length> | <percentage>]{2} cssGradient->mIsExplicitSize = true; haveSize = ParseSingleTokenNonNegativeVariant(cssGradient->GetRadiusX(), VARIANT_LP, nullptr); if (!haveSize) { // It was not an explicit size after all. // Note that ParseNonNegativeVariant may have put something // invalid into our storage, but only in the case where it was // rejected only for being negative. Since this means the token // was a length or a percentage, we know it's not valid syntax // (which must be a comma, the 'at' keyword, or a color), so we // know this value will be dropped. This means it doesn't matter // that we have something invalid in our storage. cssGradient->mIsExplicitSize = false; } else { // vertical extent is optional bool haveYSize = ParseSingleTokenNonNegativeVariant(cssGradient->GetRadiusY(), VARIANT_LP, nullptr); if (!haveShape) { nsCSSValue shapeValue; haveShape = ParseSingleTokenVariant(shapeValue, VARIANT_KEYWORD, nsCSSProps::kRadialGradientShapeKTable); if (haveShape) { shape = shapeValue.GetIntValue(); } } if (haveYSize ? shape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR : cssGradient->GetRadiusX().GetUnit() == eCSSUnit_Percent || shape == NS_STYLE_GRADIENT_SHAPE_ELLIPTICAL) { SkipUntil(')'); return false; } } } if ((haveShape || haveSize) && ExpectSymbol(',', true)) { // [ <shape> || <size> ] , return ParseGradientColorStops(cssGradient, aValue); } if (!GetToken(true)) { return false; } if (!(aFlags & eGradient_AnyLegacy)) { if (mToken.mType == eCSSToken_Ident && mToken.mIdent.LowerCaseEqualsLiteral("at")) { // [ <shape> || <size> ]? at <position> , if (!ParseBoxPositionValues(cssGradient->mBgPos, false) || !ExpectSymbol(',', true)) { SkipUntil(')'); return false; } return ParseGradientColorStops(cssGradient, aValue); } // <color-stops> only UngetToken(); return ParseGradientColorStops(cssGradient, aValue); } MOZ_ASSERT(!cssGradient->mIsExplicitSize); nsCSSTokenType ty = mToken.mType; nsString id = mToken.mIdent; UngetToken(); // <legacy-gradient-line> bool haveGradientLine = false; // if we already encountered a shape or size, // we can not have a gradient-line in legacy syntax if (!haveShape && !haveSize) { haveGradientLine = IsLegacyGradientLine(ty, id); } if (haveGradientLine) { // Note: -webkit-radial-gradient() doesn't accept angles. bool haveAngle = (aFlags & eGradient_WebkitLegacy) ? false : ParseSingleTokenVariant(cssGradient->mAngle, VARIANT_ANGLE, nullptr); // If we got an angle, we might now have a comma, ending the gradient-line if (!haveAngle || !ExpectSymbol(',', true)) { if (!ParseBoxPositionValues(cssGradient->mBgPos, false)) { SkipUntil(')'); return false; } if (!ExpectSymbol(',', true) && // If we didn't already get an angle, and we're not -webkit prefixed, // can parse an angle+comma now. Otherwise it's an error. (haveAngle || (aFlags & eGradient_WebkitLegacy) || !ParseSingleTokenVariant(cssGradient->mAngle, VARIANT_ANGLE, nullptr) || // now we better have a comma !ExpectSymbol(',', true))) { SkipUntil(')'); return false; } } if (cssGradient->mAngle.GetUnit() != eCSSUnit_None) { cssGradient->mIsLegacySyntax = true; } } // radial gradients might have a shape and size here for legacy syntax if (!haveShape && !haveSize) { haveShape = ParseSingleTokenVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD, nsCSSProps::kRadialGradientShapeKTable); haveSize = ParseSingleTokenVariant(cssGradient->GetRadialSize(), VARIANT_KEYWORD, nsCSSProps::kRadialGradientLegacySizeKTable); // could be in either order if (!haveShape) { haveShape = ParseSingleTokenVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD, nsCSSProps::kRadialGradientShapeKTable); } } if ((haveShape || haveSize) && !ExpectSymbol(',', true)) { SkipUntil(')'); return false; } return ParseGradientColorStops(cssGradient, aValue); } bool CSSParserImpl::IsLegacyGradientLine(const nsCSSTokenType& aType, const nsString& aId) { // N.B. ParseBoxPositionValues is not guaranteed to put back // everything it scanned if it fails, so we must only call it // if there is no alternative to consuming a <box-position>. // ParseVariant, as used here, will either succeed and consume // a single token, or fail and consume none, so we can be more // cavalier about calling it. bool haveGradientLine = false; switch (aType) { case eCSSToken_Percentage: case eCSSToken_Number: case eCSSToken_Dimension: haveGradientLine = true; break; case eCSSToken_Function: if (aId.LowerCaseEqualsLiteral("calc") || aId.LowerCaseEqualsLiteral("-moz-calc")) { haveGradientLine = true; break; } MOZ_FALLTHROUGH; case eCSSToken_ID: case eCSSToken_Hash: // this is a color break; case eCSSToken_Ident: { // This is only a gradient line if it's a box position keyword. nsCSSKeyword kw = nsCSSKeywords::LookupKeyword(aId); int32_t junk; if (kw != eCSSKeyword_UNKNOWN && nsCSSProps::FindKeyword(kw, nsCSSProps::kImageLayerPositionKTable, junk)) { haveGradientLine = true; } break; } default: // error break; } return haveGradientLine; } bool CSSParserImpl::ParseGradientColorStops(nsCSSValueGradient* aGradient, nsCSSValue& aValue) { // At least two color stops are required if (!ParseColorStop(aGradient) || !ExpectSymbol(',', true) || !ParseColorStop(aGradient)) { SkipUntil(')'); return false; } // Additional color stops while (ExpectSymbol(',', true)) { if (!ParseColorStop(aGradient)) { SkipUntil(')'); return false; } } if (!ExpectSymbol(')', true)) { SkipUntil(')'); return false; } // Check if interpolation hints are in the correct location bool previousPointWasInterpolationHint = true; for (size_t x = 0; x < aGradient->mStops.Length(); x++) { bool isInterpolationHint = aGradient->mStops[x].mIsInterpolationHint; if (isInterpolationHint && previousPointWasInterpolationHint) { return false; } previousPointWasInterpolationHint = isInterpolationHint; } if (previousPointWasInterpolationHint) { return false; } aValue.SetGradientValue(aGradient); return true; } // Parses the x or y component of a -webkit-gradient() <point> expression. // See ParseWebkitGradientPoint() documentation for more. bool CSSParserImpl::ParseWebkitGradientPointComponent(nsCSSValue& aComponent, bool aIsHorizontal) { if (!GetToken(true)) { return false; } // Keyword tables to use for keyword-matching // (Keyword order is important; we assume the index can be multiplied by 50% // to convert to a percent-valued component.) static const nsCSSKeyword kHorizKeywords[] = { eCSSKeyword_left, // 0% eCSSKeyword_center, // 50% eCSSKeyword_right // 100% }; static const nsCSSKeyword kVertKeywords[] = { eCSSKeyword_top, // 0% eCSSKeyword_center, // 50% eCSSKeyword_bottom // 100% }; static const size_t kNumKeywords = MOZ_ARRAY_LENGTH(kHorizKeywords); static_assert(kNumKeywords == MOZ_ARRAY_LENGTH(kVertKeywords), "Horizontal & vertical keyword tables must have same count"); // Try to parse the component as a number, or a percent, or a // keyword-converted-to-percent. if (mToken.mType == eCSSToken_Number) { aComponent.SetFloatValue(mToken.mNumber, eCSSUnit_Pixel); } else if (mToken.mType == eCSSToken_Percentage) { aComponent.SetPercentValue(mToken.mNumber); } else if (mToken.mType == eCSSToken_Ident) { nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent); if (keyword == eCSSKeyword_UNKNOWN) { return false; } // Choose our keyword table: const nsCSSKeyword* kwTable = aIsHorizontal ? kHorizKeywords : kVertKeywords; // Convert keyword to percent value (0%, 50%, or 100%) bool didAcceptKeyword = false; for (size_t i = 0; i < kNumKeywords; i++) { if (keyword == kwTable[i]) { // 0%, 50%, or 100%: aComponent.SetPercentValue(i * 0.5); didAcceptKeyword = true; break; } } if (!didAcceptKeyword) { return false; } } else { // Unrecognized token type. Put it back. (It might be a closing-paren of an // invalid -webkit-gradient(...) expression, and we need to be sure caller // can see it & stops parsing at that point.) UngetToken(); return false; } MOZ_ASSERT(aComponent.GetUnit() == eCSSUnit_Pixel || aComponent.GetUnit() == eCSSUnit_Percent, "If we get here, we should've successfully parsed a number (as a " "pixel length), a percent, or a keyword (converted to percent)"); return true; } // This function parses a "<point>" expression for -webkit-gradient(...) // Quoting https://www.webkit.org/blog/175/introducing-css-gradients/ : // "A point is a pair of space-separated values. // The syntax supports numbers, percentages or // the keywords top, bottom, left and right // for point values." // // Two additional notes: // - WebKit also accepts the "center" keyword (not listed in the text above). // - WebKit only accepts horizontal-flavored keywords (left/center/right) in // the first ("x") component, and vertical-flavored keywords // (top/center/bottom) in the second ("y") component. (This is different // from the standard gradient syntax, which accepts both orderings, e.g. // "top left" as well as "left top".) bool CSSParserImpl::ParseWebkitGradientPoint(nsCSSValuePair& aPoint) { return ParseWebkitGradientPointComponent(aPoint.mXValue, true) && ParseWebkitGradientPointComponent(aPoint.mYValue, false); } // Parse the next token as a <number> (for a <radius> in a -webkit-gradient // expresison). Returns true on success; returns false & puts back // whatever it parsed on failure. bool CSSParserImpl::ParseWebkitGradientRadius(float& aRadius) { if (!GetToken(true)) { return false; } if (mToken.mType != eCSSToken_Number) { UngetToken(); return false; } aRadius = mToken.mNumber; return true; } // Parse one of: // color-stop(number|percent, color) // from(color) // to(color) // // Quoting https://www.webkit.org/blog/175/introducing-css-gradients/ : // A stop is a function, color-stop, that takes two arguments, the stop value // (either a percentage or a number between 0 and 1.0), and a color (any // valid CSS color). In addition the shorthand functions from and to are // supported. These functions only require a color argument and are // equivalent to color-stop(0, ...) and color-stop(1.0, …) respectively. bool CSSParserImpl::ParseWebkitGradientColorStop(nsCSSValueGradient* aGradient) { MOZ_ASSERT(aGradient, "null gradient"); if (!GetToken(true)) { return false; } // We're expecting color-stop(...), from(...), or to(...) which are all // functions. Bail if we got anything else. if (mToken.mType != eCSSToken_Function) { UngetToken(); return false; } nsCSSValueGradientStop* stop = aGradient->mStops.AppendElement(); // Parse color-stop location (or infer it, for shorthands "from"/"to"): if (mToken.mIdent.LowerCaseEqualsLiteral("color-stop")) { // Parse stop location, followed by comma. if (!ParseSingleTokenVariant(stop->mLocation, VARIANT_NUMBER | VARIANT_PERCENT, nullptr) || !ExpectSymbol(',', true)) { SkipUntil(')'); // Skip to end of color-stop(...) expression. return false; } // If we got a <number>, convert it to percentage for consistency: if (stop->mLocation.GetUnit() == eCSSUnit_Number) { stop->mLocation.SetPercentValue(stop->mLocation.GetFloatValue()); } } else if (mToken.mIdent.LowerCaseEqualsLiteral("from")) { // Shorthand for color-stop(0%, ...) stop->mLocation.SetPercentValue(0.0f); } else if (mToken.mIdent.LowerCaseEqualsLiteral("to")) { // Shorthand for color-stop(100%, ...) stop->mLocation.SetPercentValue(1.0f); } else { // Unrecognized function name (invalid for a -webkit-gradient color stop). UngetToken(); return false; } CSSParseResult result = ParseVariant(stop->mColor, VARIANT_COLOR, nullptr); if (result != CSSParseResult::Ok || (stop->mColor.GetUnit() == eCSSUnit_EnumColor && stop->mColor.GetIntValue() == NS_COLOR_CURRENTCOLOR)) { // Parse failure, or parsed "currentColor" which is forbidden in // -webkit-gradient for some reason. SkipUntil(')'); return false; } // Parse color-stop function close-paren if (!ExpectSymbol(')', true)) { SkipUntil(')'); return false; } MOZ_ASSERT(stop->mLocation.GetUnit() == eCSSUnit_Percent, "Should produce only percent-valued stop-locations. " "(Caller depends on this when sorting color stops.)"); return true; } // Comparatison function to use for sorting -webkit-gradient() stops by // location. This function assumes stops have percent-valued locations (and // CSSParserImpl::ParseWebkitGradientColorStop should enforce this). static bool IsColorStopPctLocationLessThan(const nsCSSValueGradientStop& aStop1, const nsCSSValueGradientStop& aStop2) { return (aStop1.mLocation.GetPercentValue() < aStop2.mLocation.GetPercentValue()); } // This function parses a list of comma-separated color-stops for a // -webkit-gradient(...) expression, and then pads & sorts the list as-needed. bool CSSParserImpl::ParseWebkitGradientColorStops(nsCSSValueGradient* aGradient) { MOZ_ASSERT(aGradient, "null gradient"); // Parse any number of ", <color-stop>" expressions. (0 or more) // Note: This is different from unprefixed gradient syntax, which // requires at least 2 stops. while (ExpectSymbol(',', true)) { if (!ParseWebkitGradientColorStop(aGradient)) { return false; } } // Pad up to 2 stops as-needed: // (Modern gradient expressions are required to have at least 2 stops, so we // depend on this internally -- e.g. we have an assertion about this in // nsCSSRendering.cpp. -webkit-gradient syntax allows 0 stops or 1 stop, // though, so we just pad up to 2 stops in this case). // If we have no stops, pad with transparent-black: if (aGradient->mStops.IsEmpty()) { nsCSSValueGradientStop* stop1 = aGradient->mStops.AppendElement(); stop1->mColor.SetIntegerColorValue(NS_RGBA(0, 0, 0, 0), eCSSUnit_RGBAColor); stop1->mLocation.SetPercentValue(0.0f); nsCSSValueGradientStop* stop2 = aGradient->mStops.AppendElement(); stop2->mColor.SetIntegerColorValue(NS_RGBA(0, 0, 0, 0), eCSSUnit_RGBAColor); stop2->mLocation.SetPercentValue(1.0f); } else if (aGradient->mStops.Length() == 1) { // Copy whatever the author provided in the first stop: nsCSSValueGradientStop* stop = aGradient->mStops.AppendElement(); *stop = aGradient->mStops[0]; } else { // We have >2 stops. Sort them in order of increasing location. std::stable_sort(aGradient->mStops.begin(), aGradient->mStops.end(), IsColorStopPctLocationLessThan); } return true; } // Compares aStartCoord to aEndCoord, and returns true iff they share the same // unit (both pixel, or both percent) and aStartCoord is larger. static bool IsWebkitGradientCoordLarger(const nsCSSValue& aStartCoord, const nsCSSValue& aEndCoord) { if (aStartCoord.GetUnit() == eCSSUnit_Percent && aEndCoord.GetUnit() == eCSSUnit_Percent) { return aStartCoord.GetPercentValue() > aEndCoord.GetPercentValue(); } if (aStartCoord.GetUnit() == eCSSUnit_Pixel && aEndCoord.GetUnit() == eCSSUnit_Pixel) { return aStartCoord.GetFloatValue() > aEndCoord.GetFloatValue(); } // We can't compare them, since their units differ. Returning false suggests // that aEndCoord is larger, which is probably a decent guess anyway. return false; } // Finalize our internal representation of a -webkit-gradient(linear, ...) // expression, given the parsed points. (The parsed color stops // should already be hanging off of the passed-in nsCSSValueGradient.) // // Note: linear gradients progress along a line between two points. The // -webkit-gradient(linear, ...) syntax lets the author precisely specify the // starting and ending point. However, our internal gradient structures // only store one point, and the other point is implicitly its reflection // across the painted area's center. (The legacy -moz-linear-gradient syntax // also lets us store an angle.) // // In this function, we try to go from the two-point representation to an // equivalent or approximately-equivalent one-point representation. void CSSParserImpl::FinalizeLinearWebkitGradient(nsCSSValueGradient* aGradient, const nsCSSValuePair& aStartPoint, const nsCSSValuePair& aEndPoint) { MOZ_ASSERT(!aGradient->mIsRadial, "passed-in gradient must be linear"); // If the start & end points have the same Y-coordinate, then we can treat // this as a horizontal gradient progressing towards the center of the left // or right side. if (aStartPoint.mYValue == aEndPoint.mYValue) { aGradient->mBgPos.mYValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_CENTER, eCSSUnit_Enumerated); if (IsWebkitGradientCoordLarger(aStartPoint.mXValue, aEndPoint.mXValue)) { aGradient->mBgPos.mXValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_LEFT, eCSSUnit_Enumerated); } else { aGradient->mBgPos.mXValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_RIGHT, eCSSUnit_Enumerated); } return; } // If the start & end points have the same X-coordinate, then we can treat // this as a horizontal gradient progressing towards the center of the top // or bottom side. if (aStartPoint.mXValue == aEndPoint.mXValue) { aGradient->mBgPos.mXValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_CENTER, eCSSUnit_Enumerated); if (IsWebkitGradientCoordLarger(aStartPoint.mYValue, aEndPoint.mYValue)) { aGradient->mBgPos.mYValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_TOP, eCSSUnit_Enumerated); } else { aGradient->mBgPos.mYValue.SetIntValue(NS_STYLE_IMAGELAYER_POSITION_BOTTOM, eCSSUnit_Enumerated); } return; } // OK, the gradient is angled, which means we likely can't represent it // exactly in |aGradient|, without doing analysis on the two points to // extract an angle (which we might not be able to do depending on the units // used). For now, we'll just do something really basic -- just use the // first point as if it were the starting point in a legacy // -moz-linear-gradient() expression. That way, the rendered gradient will // progress from this first point, towards the center of the covered element, // to a reflected end point on the far side. Note that we have to use // mIsLegacySyntax=true for this to work, because standardized (non-legacy) // gradients place some restrictions on the reference point [namely, that it // use percent units & be on the border of the element]. aGradient->mIsLegacySyntax = true; aGradient->mBgPos = aStartPoint; } // Finalize our internal representation of a -webkit-gradient(radial, ...) // expression, given the parsed points & radii. (The parsed color-stops // should already be hanging off of the passed-in nsCSSValueGradient). void CSSParserImpl::FinalizeRadialWebkitGradient(nsCSSValueGradient* aGradient, const nsCSSValuePair& aFirstCenter, const nsCSSValuePair& aSecondCenter, const float aFirstRadius, const float aSecondRadius) { MOZ_ASSERT(aGradient->mIsRadial, "passed-in gradient must be radial"); // NOTE: -webkit-gradient(radial, ...) has *two arbitrary circles*, with the // gradient stretching between the circles' edges. In contrast, the standard // syntax (and hence our data structures) can only represent *one* circle, // with the gradient going from its center to its edge. To bridge this gap // in expressiveness, we'll just see which of our two circles is smaller, and // we'll treat that circle as if it were zero-sized and located at the center // of the larger circle. Then, we'll be able to use the same data structures // that we use for the standard radial-gradient syntax. if (aSecondRadius >= aFirstRadius) { // Second circle is larger. aGradient->mBgPos = aSecondCenter; aGradient->mIsExplicitSize = true; aGradient->GetRadiusX().SetFloatValue(aSecondRadius, eCSSUnit_Pixel); return; } // First circle is larger, so we'll have it be the outer circle. aGradient->mBgPos = aFirstCenter; aGradient->mIsExplicitSize = true; aGradient->GetRadiusX().SetFloatValue(aFirstRadius, eCSSUnit_Pixel); // For this to work properly (with the earlier color stops attached to the // first circle), we need to also reverse the color-stop list, so that // e.g. the author's "from" color is attached to the outer edge (the first // circle), rather than attached to the center (the collapsed second circle). std::reverse(aGradient->mStops.begin(), aGradient->mStops.end()); // And now invert the stop locations: for (nsCSSValueGradientStop& colorStop : aGradient->mStops) { float origLocation = colorStop.mLocation.GetPercentValue(); colorStop.mLocation.SetPercentValue(1.0f - origLocation); } } bool CSSParserImpl::ParseWebkitGradient(nsCSSValue& aValue) { // Parse type of gradient if (!GetToken(true)) { return false; } if (mToken.mType != eCSSToken_Ident) { UngetToken(); // Important; the token might be ")", which we're about to // seek to. SkipUntil(')'); return false; } bool isRadial; if (mToken.mIdent.LowerCaseEqualsLiteral("radial")) { isRadial = true; } else if (mToken.mIdent.LowerCaseEqualsLiteral("linear")) { isRadial = false; } else { // Unrecognized gradient type. SkipUntil(')'); return false; } // Parse a comma + first point: nsCSSValuePair firstPoint; if (!ExpectSymbol(',', true) || !ParseWebkitGradientPoint(firstPoint)) { SkipUntil(')'); return false; } // If radial, parse comma + first radius: float firstRadius; if (isRadial) { if (!ExpectSymbol(',', true) || !ParseWebkitGradientRadius(firstRadius)) { SkipUntil(')'); return false; } } // Parse a comma + second point: nsCSSValuePair secondPoint; if (!ExpectSymbol(',', true) || !ParseWebkitGradientPoint(secondPoint)) { SkipUntil(')'); return false; } // If radial, parse comma + second radius: float secondRadius; if (isRadial) { if (!ExpectSymbol(',', true) || !ParseWebkitGradientRadius(secondRadius)) { SkipUntil(')'); return false; } } // Construct a nsCSSValueGradient object, and parse color stops into it: RefPtr<nsCSSValueGradient> cssGradient = new nsCSSValueGradient(isRadial, false /* aIsRepeating */); if (!ParseWebkitGradientColorStops(cssGradient) || !ExpectSymbol(')', true)) { // Failed to parse color-stops, or found trailing junk between them & ')'. SkipUntil(')'); return false; } // Finish building cssGradient, based on our parsed positioning/sizing info: if (isRadial) { FinalizeRadialWebkitGradient(cssGradient, firstPoint, secondPoint, firstRadius, secondRadius); } else { FinalizeLinearWebkitGradient(cssGradient, firstPoint, secondPoint); } aValue.SetGradientValue(cssGradient); return true; } bool CSSParserImpl::ParseWebkitTextStroke() { static const nsCSSPropertyID kWebkitTextStrokeIDs[] = { eCSSProperty__webkit_text_stroke_width, eCSSProperty__webkit_text_stroke_color }; const size_t numProps = MOZ_ARRAY_LENGTH(kWebkitTextStrokeIDs); nsCSSValue values[numProps]; int32_t found = ParseChoice(values, kWebkitTextStrokeIDs, numProps); if (found < 1) { return false; } if (!(found & 1)) { // Provide default -webkit-text-stroke-width values[0].SetFloatValue(0, eCSSUnit_Pixel); } if (!(found & 2)) { // Provide default -webkit-text-stroke-color values[1].SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor); } for (size_t index = 0; index < numProps; ++index) { AppendValue(kWebkitTextStrokeIDs[index], values[index]); } return true; } int32_t CSSParserImpl::ParseChoice(nsCSSValue aValues[], const nsCSSPropertyID aPropIDs[], int32_t aNumIDs) { int32_t found = 0; nsAutoParseCompoundProperty compound(this); int32_t loop; for (loop = 0; loop < aNumIDs; loop++) { // Try each property parser in order int32_t hadFound = found; int32_t index; for (index = 0; index < aNumIDs; index++) { int32_t bit = 1 << index; if ((found & bit) == 0) { CSSParseResult result = ParseSingleValueProperty(aValues[index], aPropIDs[index]); if (result == CSSParseResult::Error) { return -1; } if (result == CSSParseResult::Ok) { found |= bit; // It's more efficient to break since it will reset |hadFound| // to |found|. Furthermore, ParseListStyle depends on our going // through the properties in order for each value.. break; } } } if (found == hadFound) { // found nothing new break; } } if (0 < found) { if (1 == found) { // only first property if (eCSSUnit_Inherit == aValues[0].GetUnit()) { // one inherit, all inherit for (loop = 1; loop < aNumIDs; loop++) { aValues[loop].SetInheritValue(); } found = ((1 << aNumIDs) - 1); } else if (eCSSUnit_Initial == aValues[0].GetUnit()) { // one initial, all initial for (loop = 1; loop < aNumIDs; loop++) { aValues[loop].SetInitialValue(); } found = ((1 << aNumIDs) - 1); } else if (eCSSUnit_Unset == aValues[0].GetUnit()) { // one unset, all unset for (loop = 1; loop < aNumIDs; loop++) { aValues[loop].SetUnsetValue(); } found = ((1 << aNumIDs) - 1); } } else { // more than one value, verify no inherits, initials or unsets for (loop = 0; loop < aNumIDs; loop++) { if (eCSSUnit_Inherit == aValues[loop].GetUnit()) { found = -1; break; } else if (eCSSUnit_Initial == aValues[loop].GetUnit()) { found = -1; break; } else if (eCSSUnit_Unset == aValues[loop].GetUnit()) { found = -1; break; } } } } return found; } void CSSParserImpl::AppendValue(nsCSSPropertyID aPropID, const nsCSSValue& aValue) { mTempData.AddLonghandProperty(aPropID, aValue); } /** * Parse a "box" property. Box properties have 1 to 4 values. When less * than 4 values are provided a standard mapping is used to replicate * existing values. */ bool CSSParserImpl::ParseBoxProperties(const nsCSSPropertyID aPropIDs[]) { // Get up to four values for the property int32_t count = 0; nsCSSRect result; NS_FOR_CSS_SIDES (index) { CSSParseResult parseResult = ParseBoxProperty(result.*(nsCSSRect::sides[index]), aPropIDs[index]); if (parseResult == CSSParseResult::NotFound) { break; } if (parseResult == CSSParseResult::Error) { return false; } count++; } if (count == 0) { return false; } if (1 < count) { // verify no more than single inherit, initial or unset NS_FOR_CSS_SIDES (index) { nsCSSUnit unit = (result.*(nsCSSRect::sides[index])).GetUnit(); if (eCSSUnit_Inherit == unit || eCSSUnit_Initial == unit || eCSSUnit_Unset == unit) { return false; } } } // Provide missing values by replicating some of the values found switch (count) { case 1: // Make right == top result.mRight = result.mTop; MOZ_FALLTHROUGH; case 2: // Make bottom == top result.mBottom = result.mTop; MOZ_FALLTHROUGH; case 3: // Make left == right result.mLeft = result.mRight; } NS_FOR_CSS_SIDES (index) { AppendValue(aPropIDs[index], result.*(nsCSSRect::sides[index])); } return true; } // Similar to ParseBoxProperties, except there is only one property // with the result as its value, not four. bool CSSParserImpl::ParseGroupedBoxProperty(int32_t aVariantMask, /** outparam */ nsCSSValue& aValue, uint32_t aRestrictions) { nsCSSRect& result = aValue.SetRectValue(); int32_t count = 0; NS_FOR_CSS_SIDES (index) { CSSParseResult parseResult = ParseVariantWithRestrictions(result.*(nsCSSRect::sides[index]), aVariantMask, nullptr, aRestrictions); if (parseResult == CSSParseResult::NotFound) { break; } if (parseResult == CSSParseResult::Error) { return false; } count++; } if (count == 0) { return false; } // Provide missing values by replicating some of the values found switch (count) { case 1: // Make right == top result.mRight = result.mTop; MOZ_FALLTHROUGH; case 2: // Make bottom == top result.mBottom = result.mTop; MOZ_FALLTHROUGH; case 3: // Make left == right result.mLeft = result.mRight; } return true; } bool CSSParserImpl::ParseBoxCornerRadius(nsCSSPropertyID aPropID) { nsCSSValue dimenX, dimenY; // required first value if (ParseNonNegativeVariant(dimenX, VARIANT_HLP | VARIANT_CALC, nullptr) != CSSParseResult::Ok) { return false; } // optional second value (forbidden if first value is inherit/initial/unset) if (dimenX.GetUnit() != eCSSUnit_Inherit && dimenX.GetUnit() != eCSSUnit_Initial && dimenX.GetUnit() != eCSSUnit_Unset) { if (ParseNonNegativeVariant(dimenY, VARIANT_LP | VARIANT_CALC, nullptr) == CSSParseResult::Error) { return false; } } if (dimenX == dimenY || dimenY.GetUnit() == eCSSUnit_Null) { AppendValue(aPropID, dimenX); } else { nsCSSValue value; value.SetPairValue(dimenX, dimenY); AppendValue(aPropID, value); } return true; } bool CSSParserImpl::ParseBoxCornerRadiiInternals(nsCSSValue array[]) { // Rectangles are used as scratch storage. // top => top-left, right => top-right, // bottom => bottom-right, left => bottom-left. nsCSSRect dimenX, dimenY; int32_t countX = 0, countY = 0; NS_FOR_CSS_SIDES (side) { CSSParseResult result = ParseNonNegativeVariant(dimenX.*nsCSSRect::sides[side], (side > 0 ? 0 : VARIANT_INHERIT) | VARIANT_LP | VARIANT_CALC, nullptr); if (result == CSSParseResult::Error) { return false; } else if (result == CSSParseResult::NotFound) { break; } countX++; } if (countX == 0) return false; if (ExpectSymbol('/', true)) { NS_FOR_CSS_SIDES (side) { CSSParseResult result = ParseNonNegativeVariant(dimenY.*nsCSSRect::sides[side], VARIANT_LP | VARIANT_CALC, nullptr); if (result == CSSParseResult::Error) { return false; } else if (result == CSSParseResult::NotFound) { break; } countY++; } if (countY == 0) return false; } // if 'initial', 'inherit' or 'unset' was used, it must be the only value if (countX > 1 || countY > 0) { nsCSSUnit unit = dimenX.mTop.GetUnit(); if (eCSSUnit_Inherit == unit || eCSSUnit_Initial == unit || eCSSUnit_Unset == unit) return false; } // if we have no Y-values, use the X-values if (countY == 0) { dimenY = dimenX; countY = countX; } // Provide missing values by replicating some of the values found switch (countX) { case 1: // Make top-right same as top-left dimenX.mRight = dimenX.mTop; MOZ_FALLTHROUGH; case 2: // Make bottom-right same as top-left dimenX.mBottom = dimenX.mTop; MOZ_FALLTHROUGH; case 3: // Make bottom-left same as top-right dimenX.mLeft = dimenX.mRight; } switch (countY) { case 1: // Make top-right same as top-left dimenY.mRight = dimenY.mTop; MOZ_FALLTHROUGH; case 2: // Make bottom-right same as top-left dimenY.mBottom = dimenY.mTop; MOZ_FALLTHROUGH; case 3: // Make bottom-left same as top-right dimenY.mLeft = dimenY.mRight; } NS_FOR_CSS_SIDES(side) { nsCSSValue& x = dimenX.*nsCSSRect::sides[side]; nsCSSValue& y = dimenY.*nsCSSRect::sides[side]; if (x == y) { array[side] = x; } else { nsCSSValue pair; pair.SetPairValue(x, y); array[side] = pair; } } return true; } bool CSSParserImpl::ParseBoxCornerRadii(const nsCSSPropertyID aPropIDs[]) { nsCSSValue value[4]; if (!ParseBoxCornerRadiiInternals(value)) { return false; } NS_FOR_CSS_SIDES(side) { AppendValue(aPropIDs[side], value[side]); } return true; } // These must be in CSS order (top,right,bottom,left) for indexing to work static const nsCSSPropertyID kBorderStyleIDs[] = { eCSSProperty_border_top_style, eCSSProperty_border_right_style, eCSSProperty_border_bottom_style, eCSSProperty_border_left_style }; static const nsCSSPropertyID kBorderWidthIDs[] = { eCSSProperty_border_top_width, eCSSProperty_border_right_width, eCSSProperty_border_bottom_width, eCSSProperty_border_left_width }; static const nsCSSPropertyID kBorderColorIDs[] = { eCSSProperty_border_top_color, eCSSProperty_border_right_color, eCSSProperty_border_bottom_color, eCSSProperty_border_left_color }; static const nsCSSPropertyID kBorderRadiusIDs[] = { eCSSProperty_border_top_left_radius, eCSSProperty_border_top_right_radius, eCSSProperty_border_bottom_right_radius, eCSSProperty_border_bottom_left_radius }; static const nsCSSPropertyID kOutlineRadiusIDs[] = { eCSSProperty__moz_outline_radius_topLeft, eCSSProperty__moz_outline_radius_topRight, eCSSProperty__moz_outline_radius_bottomRight, eCSSProperty__moz_outline_radius_bottomLeft }; void CSSParserImpl::SaveInputState(CSSParserInputState& aState) { aState.mToken = mToken; aState.mHavePushBack = mHavePushBack; mScanner->SavePosition(aState.mPosition); } void CSSParserImpl::RestoreSavedInputState(const CSSParserInputState& aState) { mToken = aState.mToken; mHavePushBack = aState.mHavePushBack; mScanner->RestoreSavedPosition(aState.mPosition); } bool CSSParserImpl::ParseProperty(nsCSSPropertyID aPropID) { // Can't use AutoRestore<bool> because it's a bitfield. MOZ_ASSERT(!mHashlessColorQuirk, "hashless color quirk should not be set"); MOZ_ASSERT(!mUnitlessLengthQuirk, "unitless length quirk should not be set"); MOZ_ASSERT(aPropID != eCSSPropertyExtra_variable); if (mNavQuirkMode) { mHashlessColorQuirk = nsCSSProps::PropHasFlags(aPropID, CSS_PROPERTY_HASHLESS_COLOR_QUIRK); mUnitlessLengthQuirk = nsCSSProps::PropHasFlags(aPropID, CSS_PROPERTY_UNITLESS_LENGTH_QUIRK); } // Save the current input state so that we can restore it later if we // have to re-parse the property value as a variable-reference-containing // token stream. CSSParserInputState stateBeforeProperty; SaveInputState(stateBeforeProperty); mScanner->ClearSeenVariableReference(); NS_ASSERTION(aPropID < eCSSProperty_COUNT, "index out of range"); bool allowVariables = true; bool result; switch (nsCSSProps::PropertyParseType(aPropID)) { case CSS_PROPERTY_PARSE_INACCESSIBLE: { // The user can't use these REPORT_UNEXPECTED(PEInaccessibleProperty2); allowVariables = false; result = false; break; } case CSS_PROPERTY_PARSE_FUNCTION: { result = ParsePropertyByFunction(aPropID); break; } case CSS_PROPERTY_PARSE_VALUE: { result = false; nsCSSValue value; if (ParseSingleValueProperty(value, aPropID) == CSSParseResult::Ok) { AppendValue(aPropID, value); result = true; } // XXX Report errors? break; } case CSS_PROPERTY_PARSE_VALUE_LIST: { result = ParseValueList(aPropID); break; } default: { result = false; allowVariables = false; MOZ_ASSERT(false, "Property's flags field in nsCSSPropList.h is missing " "one of the CSS_PROPERTY_PARSE_* constants"); break; } } if (result) { // We need to call ExpectEndProperty() to decide whether to reparse // with variables. This is needed because the property parsing may // have stopped upon finding a variable (e.g., 'margin: 1px var(a)') // in a way that future variable substitutions will be valid, or // because it parsed everything that's possible but we still want to // act as though the property contains variables even though we know // the substitution will never work (e.g., for 'margin: 1px 2px 3px // 4px 5px var(a)'). // // It would be nice to find a better solution here // (and for the SkipUntilOneOf below), though, that doesn't depend // on using what we don't accept for doing parsing correctly. if (!ExpectEndProperty()) { result = false; } } bool seenVariable = mScanner->SeenVariableReference() || (stateBeforeProperty.mHavePushBack && stateBeforeProperty.mToken.mType == eCSSToken_Function && stateBeforeProperty.mToken.mIdent.LowerCaseEqualsLiteral("var")); bool parseAsTokenStream; if (!result && allowVariables) { parseAsTokenStream = true; if (!seenVariable) { // We might have stopped parsing the property before its end and before // finding a variable reference. Keep checking until the end of the // property. CSSParserInputState stateAtError; SaveInputState(stateAtError); const char16_t stopChars[] = { ';', '!', '}', ')', 0 }; SkipUntilOneOf(stopChars); UngetToken(); parseAsTokenStream = mScanner->SeenVariableReference(); if (!parseAsTokenStream) { // If we parsed to the end of the propery and didn't find any variable // references, then the real position we want to report the error at // is |stateAtError|. RestoreSavedInputState(stateAtError); } } } else { parseAsTokenStream = false; } if (parseAsTokenStream) { // Go back to the start of the property value and parse it to make sure // its variable references are syntactically valid and is otherwise // balanced. RestoreSavedInputState(stateBeforeProperty); if (!mInSupportsCondition) { mScanner->StartRecording(); } CSSVariableDeclarations::Type type; bool dropBackslash; nsString impliedCharacters; nsCSSValue value; if (ParseValueWithVariables(&type, &dropBackslash, impliedCharacters, nullptr, nullptr)) { MOZ_ASSERT(type == CSSVariableDeclarations::eTokenStream, "a non-custom property reparsed since it contained variable " "references should not have been 'initial' or 'inherit'"); nsString propertyValue; if (!mInSupportsCondition) { // If we are in an @supports condition, we don't need to store the // actual token stream on the nsCSSValue. mScanner->StopRecording(propertyValue); if (dropBackslash) { MOZ_ASSERT(!propertyValue.IsEmpty() && propertyValue[propertyValue.Length() - 1] == '\\'); propertyValue.Truncate(propertyValue.Length() - 1); } propertyValue.Append(impliedCharacters); } if (mHavePushBack) { // If we came to the end of a property value that had a variable // reference and a token was pushed back, then it would have been // ended by '!', ')', ';', ']' or '}'. We should remove it from the // recorded property value. MOZ_ASSERT(mToken.IsSymbol('!') || mToken.IsSymbol(')') || mToken.IsSymbol(';') || mToken.IsSymbol(']') || mToken.IsSymbol('}')); if (!mInSupportsCondition) { MOZ_ASSERT(!propertyValue.IsEmpty()); MOZ_ASSERT(propertyValue[propertyValue.Length() - 1] == mToken.mSymbol); propertyValue.Truncate(propertyValue.Length() - 1); } } if (!mInSupportsCondition) { if (nsCSSProps::IsShorthand(aPropID)) { // If this is a shorthand property, we store the token stream on each // of its corresponding longhand properties. CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aPropID, EnabledState()) { nsCSSValueTokenStream* tokenStream = new nsCSSValueTokenStream; tokenStream->mPropertyID = *p; tokenStream->mShorthandPropertyID = aPropID; tokenStream->mTokenStream = propertyValue; tokenStream->mBaseURI = mBaseURI; tokenStream->mSheetURI = mSheetURI; tokenStream->mSheetPrincipal = mSheetPrincipal; // XXX Should store sheet here (see bug 952338). // tokenStream->mSheet = mSheet; tokenStream->mLineNumber = stateBeforeProperty.mPosition.LineNumber(); tokenStream->mLineOffset = stateBeforeProperty.mPosition.LineOffset(); value.SetTokenStreamValue(tokenStream); AppendValue(*p, value); } } else { nsCSSValueTokenStream* tokenStream = new nsCSSValueTokenStream; tokenStream->mPropertyID = aPropID; tokenStream->mTokenStream = propertyValue; tokenStream->mBaseURI = mBaseURI; tokenStream->mSheetURI = mSheetURI; tokenStream->mSheetPrincipal = mSheetPrincipal; // XXX Should store sheet here (see bug 952338). // tokenStream->mSheet = mSheet; tokenStream->mLineNumber = stateBeforeProperty.mPosition.LineNumber(); tokenStream->mLineOffset = stateBeforeProperty.mPosition.LineOffset(); value.SetTokenStreamValue(tokenStream); AppendValue(aPropID, value); } } result = true; } else { if (!mInSupportsCondition) { mScanner->StopRecording(); } } } if (mNavQuirkMode) { mHashlessColorQuirk = false; mUnitlessLengthQuirk = false; } return result; } bool CSSParserImpl::ParsePropertyByFunction(nsCSSPropertyID aPropID) { switch (aPropID) { // handle shorthand or multiple properties case eCSSProperty_place_content: return ParsePlaceContent(); case eCSSProperty_place_items: return ParsePlaceItems(); case eCSSProperty_place_self: return ParsePlaceSelf(); case eCSSProperty_background: return ParseImageLayers(nsStyleImageLayers::kBackgroundLayerTable); case eCSSProperty_background_repeat: return ParseImageLayerRepeat(eCSSProperty_background_repeat); case eCSSProperty_background_position: return ParseImageLayerPosition(nsStyleImageLayers::kBackgroundLayerTable); case eCSSProperty_background_position_x: case eCSSProperty_background_position_y: return ParseImageLayerPositionCoord(aPropID, aPropID == eCSSProperty_background_position_x); case eCSSProperty_background_size: return ParseImageLayerSize(eCSSProperty_background_size); case eCSSProperty_border: return ParseBorderSide(kBorderTopIDs, true); case eCSSProperty_border_color: return ParseBorderColor(); case eCSSProperty_border_spacing: return ParseBorderSpacing(); case eCSSProperty_border_style: return ParseBorderStyle(); case eCSSProperty_border_block_end: return ParseBorderSide(kBorderBlockEndIDs, false); case eCSSProperty_border_block_start: return ParseBorderSide(kBorderBlockStartIDs, false); case eCSSProperty_border_bottom: return ParseBorderSide(kBorderBottomIDs, false); case eCSSProperty_border_inline_end: return ParseBorderSide(kBorderInlineEndIDs, false); case eCSSProperty_border_inline_start: return ParseBorderSide(kBorderInlineStartIDs, false); case eCSSProperty_border_left: return ParseBorderSide(kBorderLeftIDs, false); case eCSSProperty_border_right: return ParseBorderSide(kBorderRightIDs, false); case eCSSProperty_border_top: return ParseBorderSide(kBorderTopIDs, false); case eCSSProperty_border_bottom_colors: case eCSSProperty_border_left_colors: case eCSSProperty_border_right_colors: case eCSSProperty_border_top_colors: return ParseBorderColors(aPropID); case eCSSProperty_border_image_slice: return ParseBorderImageSlice(true, nullptr); case eCSSProperty_border_image_width: return ParseBorderImageWidth(true); case eCSSProperty_border_image_outset: return ParseBorderImageOutset(true); case eCSSProperty_border_image_repeat: return ParseBorderImageRepeat(true); case eCSSProperty_border_image: return ParseBorderImage(); case eCSSProperty_border_width: return ParseBorderWidth(); case eCSSProperty_border_radius: return ParseBoxCornerRadii(kBorderRadiusIDs); case eCSSProperty__moz_outline_radius: return ParseBoxCornerRadii(kOutlineRadiusIDs); case eCSSProperty_border_top_left_radius: case eCSSProperty_border_top_right_radius: case eCSSProperty_border_bottom_right_radius: case eCSSProperty_border_bottom_left_radius: case eCSSProperty__moz_outline_radius_topLeft: case eCSSProperty__moz_outline_radius_topRight: case eCSSProperty__moz_outline_radius_bottomRight: case eCSSProperty__moz_outline_radius_bottomLeft: return ParseBoxCornerRadius(aPropID); case eCSSProperty_box_shadow: case eCSSProperty_text_shadow: return ParseShadowList(aPropID); case eCSSProperty_clip: return ParseRect(eCSSProperty_clip); case eCSSProperty_columns: return ParseColumns(); case eCSSProperty_column_rule: return ParseBorderSide(kColumnRuleIDs, false); case eCSSProperty_content: return ParseContent(); case eCSSProperty_counter_increment: case eCSSProperty_counter_reset: return ParseCounterData(aPropID); case eCSSProperty_cursor: return ParseCursor(); case eCSSProperty_filter: return ParseFilter(); case eCSSProperty_flex: return ParseFlex(); case eCSSProperty_flex_flow: return ParseFlexFlow(); case eCSSProperty_font: return ParseFont(); case eCSSProperty_font_variant: return ParseFontVariant(); case eCSSProperty_grid_auto_flow: return ParseGridAutoFlow(); case eCSSProperty_grid_auto_columns: case eCSSProperty_grid_auto_rows: return ParseGridAutoColumnsRows(aPropID); case eCSSProperty_grid_template_areas: return ParseGridTemplateAreas(); case eCSSProperty_grid_template_columns: case eCSSProperty_grid_template_rows: return ParseGridTemplateColumnsRows(aPropID); case eCSSProperty_grid_template: return ParseGridTemplate(); case eCSSProperty_grid: return ParseGrid(); case eCSSProperty_grid_column_start: case eCSSProperty_grid_column_end: case eCSSProperty_grid_row_start: case eCSSProperty_grid_row_end: return ParseGridColumnRowStartEnd(aPropID); case eCSSProperty_grid_column: return ParseGridColumnRow(eCSSProperty_grid_column_start, eCSSProperty_grid_column_end); case eCSSProperty_grid_row: return ParseGridColumnRow(eCSSProperty_grid_row_start, eCSSProperty_grid_row_end); case eCSSProperty_grid_area: return ParseGridArea(); case eCSSProperty_grid_gap: return ParseGridGap(); case eCSSProperty_image_region: return ParseRect(eCSSProperty_image_region); case eCSSProperty_align_content: case eCSSProperty_justify_content: return ParseAlignJustifyContent(aPropID); case eCSSProperty_align_items: return ParseAlignItems(); case eCSSProperty_align_self: case eCSSProperty_justify_self: return ParseAlignJustifySelf(aPropID); case eCSSProperty_initial_letter: return ParseInitialLetter(); case eCSSProperty_justify_items: return ParseJustifyItems(); case eCSSProperty_list_style: return ParseListStyle(); case eCSSProperty_margin: return ParseMargin(); case eCSSProperty_object_position: return ParseObjectPosition(); case eCSSProperty_outline: return ParseOutline(); case eCSSProperty_overflow: return ParseOverflow(); case eCSSProperty_padding: return ParsePadding(); case eCSSProperty_quotes: return ParseQuotes(); case eCSSProperty_text_decoration: return ParseTextDecoration(); case eCSSProperty_text_emphasis: return ParseTextEmphasis(); case eCSSProperty_will_change: return ParseWillChange(); case eCSSProperty_transform: return ParseTransform(false); case eCSSProperty__moz_transform: return ParseTransform(true); case eCSSProperty_transform_origin: return ParseTransformOrigin(false); case eCSSProperty_perspective_origin: return ParseTransformOrigin(true); case eCSSProperty_transition: return ParseTransition(); case eCSSProperty_animation: return ParseAnimation(); case eCSSProperty_transition_property: return ParseTransitionProperty(); case eCSSProperty_fill: case eCSSProperty_stroke: return ParsePaint(aPropID); case eCSSProperty_stroke_dasharray: return ParseDasharray(); case eCSSProperty_marker: return ParseMarker(); case eCSSProperty_paint_order: return ParsePaintOrder(); case eCSSProperty_scroll_snap_type: return ParseScrollSnapType(); #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; }