michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=78: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* parsing of CSS stylesheets, based on a token stream from the CSS scanner */ michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: #include "mozilla/DebugOnly.h" michael@0: michael@0: #include "nsCSSParser.h" michael@0: #include "nsCSSProps.h" michael@0: #include "nsCSSKeywords.h" michael@0: #include "nsCSSScanner.h" michael@0: #include "mozilla/css/ErrorReporter.h" michael@0: #include "mozilla/css/Loader.h" michael@0: #include "mozilla/css/StyleRule.h" michael@0: #include "mozilla/css/ImportRule.h" michael@0: #include "nsCSSRules.h" michael@0: #include "mozilla/css/NameSpaceRule.h" michael@0: #include "nsTArray.h" michael@0: #include "nsCSSStyleSheet.h" michael@0: #include "mozilla/css/Declaration.h" michael@0: #include "nsStyleConsts.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsString.h" michael@0: #include "nsIAtom.h" michael@0: #include "nsColor.h" michael@0: #include "nsCSSPseudoClasses.h" michael@0: #include "nsCSSPseudoElements.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsXMLNameSpaceMap.h" michael@0: #include "nsError.h" michael@0: #include "nsIMediaList.h" michael@0: #include "nsStyleUtil.h" michael@0: #include "nsIPrincipal.h" michael@0: #include "prprf.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "CSSCalc.h" michael@0: #include "nsMediaFeatures.h" michael@0: #include "nsLayoutUtils.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "nsRuleData.h" michael@0: #include "mozilla/CSSVariableValues.h" michael@0: #include "mozilla/dom/URL.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: typedef nsCSSProps::KTableValue KTableValue; michael@0: michael@0: const uint32_t michael@0: nsCSSProps::kParserVariantTable[eCSSProperty_COUNT_no_shorthands] = { michael@0: #define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, \ michael@0: stylestruct_, stylestructoffset_, animtype_) \ michael@0: parsevariant_, michael@0: #include "nsCSSPropList.h" michael@0: #undef CSS_PROP michael@0: }; michael@0: michael@0: // Maximum number of repetitions for the repeat() function michael@0: // in the grid-template-columns and grid-template-rows properties, michael@0: // to limit high memory usage from small stylesheets. michael@0: // Must be a positive integer. Should be large-ish. michael@0: #define GRID_TEMPLATE_MAX_REPETITIONS 10000 michael@0: michael@0: // End-of-array marker for mask arguments to ParseBitmaskValues michael@0: #define MASK_END_VALUE (-1) michael@0: michael@0: MOZ_BEGIN_ENUM_CLASS(CSSParseResult, int32_t) michael@0: // Parsed something successfully: michael@0: Ok, michael@0: // Did not find what we were looking for, but did not consume any token: michael@0: NotFound, michael@0: // Unexpected token or token value, too late for UngetToken() to be enough: michael@0: Error michael@0: MOZ_END_ENUM_CLASS(CSSParseResult) michael@0: michael@0: namespace { michael@0: michael@0: // Rule processing function michael@0: typedef void (* RuleAppendFunc) (css::Rule* aRule, void* aData); michael@0: static void AssignRuleToPointer(css::Rule* aRule, void* aPointer); michael@0: static void AppendRuleToSheet(css::Rule* aRule, void* aParser); michael@0: michael@0: struct CSSParserInputState { michael@0: nsCSSScannerPosition mPosition; michael@0: nsCSSToken mToken; michael@0: bool mHavePushBack; michael@0: }; michael@0: michael@0: // Your basic top-down recursive descent style parser michael@0: // The exposed methods and members of this class are precisely those michael@0: // needed by nsCSSParser, far below. michael@0: class CSSParserImpl { michael@0: public: michael@0: CSSParserImpl(); michael@0: ~CSSParserImpl(); michael@0: michael@0: nsresult SetStyleSheet(nsCSSStyleSheet* aSheet); michael@0: michael@0: nsresult SetQuirkMode(bool aQuirkMode); michael@0: michael@0: nsresult SetChildLoader(mozilla::css::Loader* aChildLoader); michael@0: michael@0: // Clears everything set by the above Set*() functions. michael@0: void Reset(); michael@0: michael@0: nsresult ParseSheet(const nsAString& aInput, michael@0: nsIURI* aSheetURI, michael@0: nsIURI* aBaseURI, michael@0: nsIPrincipal* aSheetPrincipal, michael@0: uint32_t aLineNumber, michael@0: bool aAllowUnsafeRules); michael@0: michael@0: nsresult ParseStyleAttribute(const nsAString& aAttributeValue, michael@0: nsIURI* aDocURL, michael@0: nsIURI* aBaseURL, michael@0: nsIPrincipal* aNodePrincipal, michael@0: css::StyleRule** aResult); michael@0: michael@0: nsresult ParseDeclarations(const nsAString& aBuffer, michael@0: nsIURI* aSheetURL, michael@0: nsIURI* aBaseURL, michael@0: nsIPrincipal* aSheetPrincipal, michael@0: css::Declaration* aDeclaration, michael@0: bool* aChanged); michael@0: michael@0: nsresult ParseRule(const nsAString& aRule, michael@0: nsIURI* aSheetURL, michael@0: nsIURI* aBaseURL, michael@0: nsIPrincipal* aSheetPrincipal, michael@0: css::Rule** aResult); michael@0: michael@0: nsresult ParseProperty(const nsCSSProperty aPropID, michael@0: const nsAString& aPropValue, michael@0: nsIURI* aSheetURL, michael@0: nsIURI* aBaseURL, michael@0: nsIPrincipal* aSheetPrincipal, michael@0: css::Declaration* aDeclaration, michael@0: bool* aChanged, michael@0: bool aIsImportant, michael@0: bool aIsSVGMode); michael@0: michael@0: void ParseMediaList(const nsSubstring& aBuffer, michael@0: nsIURI* aURL, // for error reporting michael@0: uint32_t aLineNumber, // for error reporting michael@0: nsMediaList* aMediaList, michael@0: bool aHTMLMode); michael@0: michael@0: nsresult ParseVariable(const nsAString& aVariableName, michael@0: const nsAString& aPropValue, michael@0: nsIURI* aSheetURL, michael@0: nsIURI* aBaseURL, michael@0: nsIPrincipal* aSheetPrincipal, michael@0: css::Declaration* aDeclaration, michael@0: bool* aChanged, michael@0: bool aIsImportant); michael@0: michael@0: bool ParseColorString(const nsSubstring& aBuffer, michael@0: nsIURI* aURL, // for error reporting michael@0: uint32_t aLineNumber, // for error reporting michael@0: nsCSSValue& aValue); michael@0: michael@0: nsresult ParseSelectorString(const nsSubstring& aSelectorString, michael@0: nsIURI* aURL, // for error reporting michael@0: uint32_t aLineNumber, // for error reporting michael@0: nsCSSSelectorList **aSelectorList); michael@0: michael@0: already_AddRefed michael@0: ParseKeyframeRule(const nsSubstring& aBuffer, michael@0: nsIURI* aURL, michael@0: uint32_t aLineNumber); michael@0: michael@0: bool ParseKeyframeSelectorString(const nsSubstring& aSelectorString, michael@0: nsIURI* aURL, // for error reporting michael@0: uint32_t aLineNumber, // for error reporting michael@0: InfallibleTArray& aSelectorList); michael@0: michael@0: bool EvaluateSupportsDeclaration(const nsAString& aProperty, michael@0: const nsAString& aValue, michael@0: nsIURI* aDocURL, michael@0: nsIURI* aBaseURL, michael@0: nsIPrincipal* aDocPrincipal); michael@0: michael@0: bool EvaluateSupportsCondition(const nsAString& aCondition, michael@0: nsIURI* aDocURL, michael@0: nsIURI* aBaseURL, michael@0: nsIPrincipal* aDocPrincipal); michael@0: michael@0: typedef nsCSSParser::VariableEnumFunc VariableEnumFunc; michael@0: michael@0: /** michael@0: * Parses a CSS token stream value and invokes a callback function for each michael@0: * variable reference that is encountered. michael@0: * michael@0: * @param aPropertyValue The CSS token stream value. michael@0: * @param aFunc The callback function to invoke; its parameters are the michael@0: * variable name found and the aData argument passed in to this function. michael@0: * @param aData User data to pass in to the callback. michael@0: * @return Whether aPropertyValue could be parsed as a valid CSS token stream michael@0: * value (e.g., without syntactic errors in variable references). michael@0: */ michael@0: bool EnumerateVariableReferences(const nsAString& aPropertyValue, michael@0: VariableEnumFunc aFunc, michael@0: void* aData); michael@0: michael@0: /** michael@0: * Parses aPropertyValue as a CSS token stream value and resolves any michael@0: * variable references using the variables in aVariables. michael@0: * michael@0: * @param aPropertyValue The CSS token stream value. michael@0: * @param aVariables The set of variable values to use when resolving variable michael@0: * references. michael@0: * @param aResult Out parameter that gets the resolved value. michael@0: * @param aFirstToken Out parameter that gets the type of the first token in michael@0: * aResult. michael@0: * @param aLastToken Out parameter that gets the type of the last token in michael@0: * aResult. michael@0: * @return Whether aResult could be parsed successfully and variable reference michael@0: * substitution succeeded. michael@0: */ michael@0: bool ResolveVariableValue(const nsAString& aPropertyValue, michael@0: const CSSVariableValues* aVariables, michael@0: nsString& aResult, michael@0: nsCSSTokenSerializationType& aFirstToken, michael@0: nsCSSTokenSerializationType& aLastToken); michael@0: michael@0: /** michael@0: * Parses a string as a CSS token stream value for particular property, michael@0: * resolving any variable references. The parsed property value is stored michael@0: * in the specified nsRuleData object. If aShorthandPropertyID has a value michael@0: * other than eCSSProperty_UNKNOWN, this is the property that will be parsed; michael@0: * otherwise, aPropertyID will be parsed. Either way, only aPropertyID, michael@0: * a longhand property, will be copied over to the rule data. michael@0: * michael@0: * If the property cannot be parsed, it will be treated as if 'initial' or michael@0: * 'inherit' were specified, for non-inherited and inherited properties michael@0: * respectively. michael@0: * michael@0: * @param aPropertyID The ID of the longhand property whose value is to be michael@0: * copied to the rule data. michael@0: * @param aShorthandPropertyID The ID of the shorthand property to be parsed. michael@0: * If a longhand property is to be parsed, aPropertyID is that property, michael@0: * and aShorthandPropertyID must be eCSSProperty_UNKNOWN. michael@0: * @param aValue The CSS token stream value. michael@0: * @param aVariables The set of variable values to use when resolving variable michael@0: * references. michael@0: * @param aRuleData The rule data object into which parsed property value for michael@0: * aPropertyID will be stored. michael@0: */ michael@0: void ParsePropertyWithVariableReferences(nsCSSProperty aPropertyID, michael@0: nsCSSProperty aShorthandPropertyID, michael@0: const nsAString& aValue, michael@0: const CSSVariableValues* aVariables, michael@0: nsRuleData* aRuleData, michael@0: nsIURI* aDocURL, michael@0: nsIURI* aBaseURL, michael@0: nsIPrincipal* aDocPrincipal, michael@0: nsCSSStyleSheet* aSheet, michael@0: uint32_t aLineNumber, michael@0: uint32_t aLineOffset); michael@0: michael@0: nsCSSProperty LookupEnabledProperty(const nsAString& aProperty) { michael@0: static_assert(nsCSSProps::eEnabledForAllContent == 0, michael@0: "nsCSSProps::eEnabledForAllContent should be zero for " michael@0: "this bitfield to work"); michael@0: nsCSSProps::EnabledState enabledState = nsCSSProps::eEnabledForAllContent; michael@0: if (mUnsafeRulesEnabled) { michael@0: enabledState |= nsCSSProps::eEnabledInUASheets; michael@0: } michael@0: if (mIsChromeOrCertifiedApp) { michael@0: enabledState |= nsCSSProps::eEnabledInChromeOrCertifiedApp; michael@0: } michael@0: return nsCSSProps::LookupProperty(aProperty, enabledState); michael@0: } michael@0: michael@0: protected: michael@0: class nsAutoParseCompoundProperty; michael@0: friend class nsAutoParseCompoundProperty; michael@0: michael@0: class nsAutoFailingSupportsRule; michael@0: friend class nsAutoFailingSupportsRule; michael@0: michael@0: class nsAutoSuppressErrors; michael@0: friend class nsAutoSuppressErrors; michael@0: michael@0: void AppendRule(css::Rule* aRule); michael@0: friend void AppendRuleToSheet(css::Rule*, void*); // calls AppendRule michael@0: michael@0: /** michael@0: * This helper class automatically calls SetParsingCompoundProperty in its michael@0: * constructor and takes care of resetting it to false in its destructor. michael@0: */ michael@0: class nsAutoParseCompoundProperty { michael@0: public: michael@0: nsAutoParseCompoundProperty(CSSParserImpl* aParser) : mParser(aParser) michael@0: { michael@0: NS_ASSERTION(!aParser->IsParsingCompoundProperty(), michael@0: "already parsing compound property"); michael@0: NS_ASSERTION(aParser, "Null parser?"); michael@0: aParser->SetParsingCompoundProperty(true); michael@0: } michael@0: michael@0: ~nsAutoParseCompoundProperty() michael@0: { michael@0: mParser->SetParsingCompoundProperty(false); michael@0: } michael@0: private: michael@0: CSSParserImpl* mParser; michael@0: }; michael@0: michael@0: /** michael@0: * This helper class conditionally sets mInFailingSupportsRule to michael@0: * true if aCondition = false, and resets it to its original value in its michael@0: * destructor. If we are already somewhere within a failing @supports michael@0: * rule, passing in aCondition = true does not change mInFailingSupportsRule. michael@0: */ michael@0: class nsAutoFailingSupportsRule { michael@0: public: michael@0: nsAutoFailingSupportsRule(CSSParserImpl* aParser, michael@0: bool aCondition) michael@0: : mParser(aParser), michael@0: mOriginalValue(aParser->mInFailingSupportsRule) michael@0: { michael@0: if (!aCondition) { michael@0: mParser->mInFailingSupportsRule = true; michael@0: } michael@0: } michael@0: michael@0: ~nsAutoFailingSupportsRule() michael@0: { michael@0: mParser->mInFailingSupportsRule = mOriginalValue; michael@0: } michael@0: michael@0: private: michael@0: CSSParserImpl* mParser; michael@0: bool mOriginalValue; michael@0: }; michael@0: michael@0: /** michael@0: * Auto class to set aParser->mSuppressErrors to the specified value michael@0: * and restore it to its original value later. michael@0: */ michael@0: class nsAutoSuppressErrors { michael@0: public: michael@0: nsAutoSuppressErrors(CSSParserImpl* aParser, michael@0: bool aSuppressErrors = true) michael@0: : mParser(aParser), michael@0: mOriginalValue(aParser->mSuppressErrors) michael@0: { michael@0: mParser->mSuppressErrors = aSuppressErrors; michael@0: } michael@0: michael@0: ~nsAutoSuppressErrors() michael@0: { michael@0: mParser->mSuppressErrors = mOriginalValue; michael@0: } michael@0: michael@0: private: michael@0: CSSParserImpl* mParser; michael@0: bool mOriginalValue; michael@0: }; michael@0: michael@0: // the caller must hold on to aString until parsing is done michael@0: void InitScanner(nsCSSScanner& aScanner, michael@0: css::ErrorReporter& aReporter, michael@0: nsIURI* aSheetURI, nsIURI* aBaseURI, michael@0: nsIPrincipal* aSheetPrincipal); michael@0: void ReleaseScanner(void); michael@0: bool IsSVGMode() const { michael@0: return mScanner->IsSVGMode(); michael@0: } michael@0: michael@0: /** michael@0: * Saves the current input state, which includes any currently pushed michael@0: * back token, and the current position of the scanner. michael@0: */ michael@0: void SaveInputState(CSSParserInputState& aState); michael@0: michael@0: /** michael@0: * Restores the saved input state by pushing back any saved pushback michael@0: * token and calling RestoreSavedPosition on the scanner. michael@0: */ michael@0: void RestoreSavedInputState(const CSSParserInputState& aState); michael@0: michael@0: bool GetToken(bool aSkipWS); michael@0: void UngetToken(); michael@0: bool GetNextTokenLocation(bool aSkipWS, uint32_t *linenum, uint32_t *colnum); michael@0: michael@0: bool ExpectSymbol(char16_t aSymbol, bool aSkipWS); michael@0: bool ExpectEndProperty(); michael@0: bool CheckEndProperty(); michael@0: nsSubstring* NextIdent(); michael@0: michael@0: // returns true when the stop symbol is found, and false for EOF michael@0: bool SkipUntil(char16_t aStopSymbol); michael@0: void SkipUntilOneOf(const char16_t* aStopSymbolChars); michael@0: // For each character in aStopSymbolChars from the end of the array michael@0: // to the start, calls SkipUntil with that character. michael@0: typedef nsAutoTArray StopSymbolCharStack; michael@0: void SkipUntilAllOf(const StopSymbolCharStack& aStopSymbolChars); michael@0: // returns true if the stop symbol or EOF is found, and false for an michael@0: // unexpected ')', ']' or '}'; this not safe to call outside variable michael@0: // resolution, as it doesn't handle mismatched content michael@0: bool SkipBalancedContentUntil(char16_t aStopSymbol); michael@0: michael@0: void SkipRuleSet(bool aInsideBraces); michael@0: bool SkipAtRule(bool aInsideBlock); michael@0: bool SkipDeclaration(bool aCheckForBraces); michael@0: michael@0: void PushGroup(css::GroupRule* aRule); michael@0: void PopGroup(); michael@0: michael@0: bool ParseRuleSet(RuleAppendFunc aAppendFunc, void* aProcessData, michael@0: bool aInsideBraces = false); michael@0: bool ParseAtRule(RuleAppendFunc aAppendFunc, void* aProcessData, michael@0: bool aInAtRule); michael@0: bool ParseCharsetRule(RuleAppendFunc aAppendFunc, void* aProcessData); michael@0: bool ParseImportRule(RuleAppendFunc aAppendFunc, void* aProcessData); michael@0: bool ParseURLOrString(nsString& aURL); michael@0: bool GatherMedia(nsMediaList* aMedia, michael@0: bool aInAtRule); michael@0: bool ParseMediaQuery(bool aInAtRule, nsMediaQuery **aQuery, michael@0: bool *aHitStop); michael@0: bool ParseMediaQueryExpression(nsMediaQuery* aQuery); michael@0: void ProcessImport(const nsString& aURLSpec, michael@0: nsMediaList* aMedia, michael@0: RuleAppendFunc aAppendFunc, michael@0: void* aProcessData); michael@0: bool ParseGroupRule(css::GroupRule* aRule, RuleAppendFunc aAppendFunc, michael@0: void* aProcessData); michael@0: bool ParseMediaRule(RuleAppendFunc aAppendFunc, void* aProcessData); michael@0: bool ParseMozDocumentRule(RuleAppendFunc aAppendFunc, void* aProcessData); michael@0: bool ParseNameSpaceRule(RuleAppendFunc aAppendFunc, void* aProcessData); michael@0: void ProcessNameSpace(const nsString& aPrefix, michael@0: const nsString& aURLSpec, RuleAppendFunc aAppendFunc, michael@0: void* aProcessData); michael@0: michael@0: bool ParseFontFaceRule(RuleAppendFunc aAppendFunc, void* aProcessData); michael@0: bool ParseFontFeatureValuesRule(RuleAppendFunc aAppendFunc, michael@0: void* aProcessData); michael@0: bool ParseFontFeatureValueSet(nsCSSFontFeatureValuesRule *aRule); michael@0: bool ParseFontDescriptor(nsCSSFontFaceRule* aRule); michael@0: bool ParseFontDescriptorValue(nsCSSFontDesc aDescID, michael@0: nsCSSValue& aValue); michael@0: michael@0: bool ParsePageRule(RuleAppendFunc aAppendFunc, void* aProcessData); michael@0: bool ParseKeyframesRule(RuleAppendFunc aAppendFunc, void* aProcessData); michael@0: already_AddRefed ParseKeyframeRule(); michael@0: bool ParseKeyframeSelectorList(InfallibleTArray& aSelectorList); michael@0: michael@0: bool ParseSupportsRule(RuleAppendFunc aAppendFunc, void* aProcessData); michael@0: bool ParseSupportsCondition(bool& aConditionMet); michael@0: bool ParseSupportsConditionNegation(bool& aConditionMet); michael@0: bool ParseSupportsConditionInParens(bool& aConditionMet); michael@0: bool ParseSupportsConditionInParensInsideParens(bool& aConditionMet); michael@0: bool ParseSupportsConditionTerms(bool& aConditionMet); michael@0: enum SupportsConditionTermOperator { eAnd, eOr }; michael@0: bool ParseSupportsConditionTermsAfterOperator( michael@0: bool& aConditionMet, michael@0: SupportsConditionTermOperator aOperator); michael@0: michael@0: /** michael@0: * Parses the current input stream for a CSS token stream value and resolves michael@0: * any variable references using the variables in aVariables. michael@0: * michael@0: * @param aVariables The set of variable values to use when resolving variable michael@0: * references. michael@0: * @param aResult Out parameter that, if the function returns true, will be michael@0: * replaced with the resolved value. michael@0: * @return Whether aResult could be parsed successfully and variable reference michael@0: * substitution succeeded. michael@0: */ michael@0: bool ResolveValueWithVariableReferences( michael@0: const CSSVariableValues* aVariables, michael@0: nsString& aResult, michael@0: nsCSSTokenSerializationType& aResultFirstToken, michael@0: nsCSSTokenSerializationType& aResultLastToken); michael@0: // Helper function for ResolveValueWithVariableReferences. michael@0: bool ResolveValueWithVariableReferencesRec( michael@0: nsString& aResult, michael@0: nsCSSTokenSerializationType& aResultFirstToken, michael@0: nsCSSTokenSerializationType& aResultLastToken, michael@0: const CSSVariableValues* aVariables); michael@0: michael@0: enum nsSelectorParsingStatus { michael@0: // we have parsed a selector and we saw a token that cannot be michael@0: // part of a selector: michael@0: eSelectorParsingStatus_Done, michael@0: // we should continue parsing the selector: michael@0: eSelectorParsingStatus_Continue, michael@0: // we saw an unexpected token or token value, michael@0: // or we saw end-of-file with an unfinished selector: michael@0: eSelectorParsingStatus_Error michael@0: }; michael@0: nsSelectorParsingStatus ParseIDSelector(int32_t& aDataMask, michael@0: nsCSSSelector& aSelector); michael@0: michael@0: nsSelectorParsingStatus ParseClassSelector(int32_t& aDataMask, michael@0: nsCSSSelector& aSelector); michael@0: michael@0: // aPseudoElement and aPseudoElementArgs are the location where michael@0: // pseudo-elements (as opposed to pseudo-classes) are stored; michael@0: // pseudo-classes are stored on aSelector. aPseudoElement and michael@0: // aPseudoElementArgs must be non-null iff !aIsNegated. michael@0: nsSelectorParsingStatus ParsePseudoSelector(int32_t& aDataMask, michael@0: nsCSSSelector& aSelector, michael@0: bool aIsNegated, michael@0: nsIAtom** aPseudoElement, michael@0: nsAtomList** aPseudoElementArgs, michael@0: nsCSSPseudoElements::Type* aPseudoElementType); michael@0: michael@0: nsSelectorParsingStatus ParseAttributeSelector(int32_t& aDataMask, michael@0: nsCSSSelector& aSelector); michael@0: michael@0: nsSelectorParsingStatus ParseTypeOrUniversalSelector(int32_t& aDataMask, michael@0: nsCSSSelector& aSelector, michael@0: bool aIsNegated); michael@0: michael@0: nsSelectorParsingStatus ParsePseudoClassWithIdentArg(nsCSSSelector& aSelector, michael@0: nsCSSPseudoClasses::Type aType); michael@0: michael@0: nsSelectorParsingStatus ParsePseudoClassWithNthPairArg(nsCSSSelector& aSelector, michael@0: nsCSSPseudoClasses::Type aType); michael@0: michael@0: nsSelectorParsingStatus ParsePseudoClassWithSelectorListArg(nsCSSSelector& aSelector, michael@0: nsCSSPseudoClasses::Type aType); michael@0: michael@0: nsSelectorParsingStatus ParseNegatedSimpleSelector(int32_t& aDataMask, michael@0: nsCSSSelector& aSelector); michael@0: michael@0: // If aStopChar is non-zero, the selector list is done when we hit michael@0: // aStopChar. Otherwise, it's done when we hit EOF. michael@0: bool ParseSelectorList(nsCSSSelectorList*& aListHead, michael@0: char16_t aStopChar); michael@0: bool ParseSelectorGroup(nsCSSSelectorList*& aListHead); michael@0: bool ParseSelector(nsCSSSelectorList* aList, char16_t aPrevCombinator); michael@0: michael@0: enum { michael@0: eParseDeclaration_InBraces = 1 << 0, michael@0: eParseDeclaration_AllowImportant = 1 << 1 michael@0: }; michael@0: enum nsCSSContextType { michael@0: eCSSContext_General, michael@0: eCSSContext_Page michael@0: }; michael@0: michael@0: css::Declaration* ParseDeclarationBlock(uint32_t aFlags, michael@0: nsCSSContextType aContext = eCSSContext_General); michael@0: bool ParseDeclaration(css::Declaration* aDeclaration, michael@0: uint32_t aFlags, michael@0: bool aMustCallValueAppended, michael@0: bool* aChanged, michael@0: nsCSSContextType aContext = eCSSContext_General); michael@0: michael@0: bool ParseProperty(nsCSSProperty aPropID); michael@0: bool ParsePropertyByFunction(nsCSSProperty aPropID); michael@0: bool ParseSingleValueProperty(nsCSSValue& aValue, michael@0: nsCSSProperty aPropID); michael@0: michael@0: enum PriorityParsingStatus { michael@0: ePriority_None, michael@0: ePriority_Important, michael@0: ePriority_Error michael@0: }; michael@0: PriorityParsingStatus ParsePriority(); michael@0: michael@0: #ifdef MOZ_XUL michael@0: bool ParseTreePseudoElement(nsAtomList **aPseudoElementArgs); michael@0: #endif michael@0: michael@0: void InitBoxPropsAsPhysical(const nsCSSProperty *aSourceProperties); michael@0: michael@0: // Property specific parsing routines michael@0: bool ParseBackground(); michael@0: michael@0: struct BackgroundParseState { michael@0: nsCSSValue& mColor; michael@0: nsCSSValueList* mImage; michael@0: nsCSSValuePairList* mRepeat; michael@0: nsCSSValueList* mAttachment; michael@0: nsCSSValueList* mClip; michael@0: nsCSSValueList* mOrigin; michael@0: nsCSSValueList* mPosition; michael@0: nsCSSValuePairList* mSize; michael@0: BackgroundParseState( michael@0: nsCSSValue& aColor, nsCSSValueList* aImage, nsCSSValuePairList* aRepeat, michael@0: nsCSSValueList* aAttachment, nsCSSValueList* aClip, michael@0: nsCSSValueList* aOrigin, nsCSSValueList* aPosition, michael@0: nsCSSValuePairList* aSize) : michael@0: mColor(aColor), mImage(aImage), mRepeat(aRepeat), michael@0: mAttachment(aAttachment), mClip(aClip), mOrigin(aOrigin), michael@0: mPosition(aPosition), mSize(aSize) {}; michael@0: }; michael@0: michael@0: bool ParseBackgroundItem(BackgroundParseState& aState); michael@0: michael@0: bool ParseValueList(nsCSSProperty aPropID); // a single value prop-id michael@0: bool ParseBackgroundRepeat(); michael@0: bool ParseBackgroundRepeatValues(nsCSSValuePair& aValue); michael@0: bool ParseBackgroundPosition(); michael@0: michael@0: // ParseBoxPositionValues parses the CSS 2.1 background-position syntax, michael@0: // which is still used by some properties. See ParseBackgroundPositionValues michael@0: // for the css3-background syntax. michael@0: bool ParseBoxPositionValues(nsCSSValuePair& aOut, bool aAcceptsInherit, michael@0: bool aAllowExplicitCenter = true); // deprecated michael@0: bool ParseBackgroundPositionValues(nsCSSValue& aOut, bool aAcceptsInherit); michael@0: michael@0: bool ParseBackgroundSize(); michael@0: bool ParseBackgroundSizeValues(nsCSSValuePair& aOut); michael@0: bool ParseBorderColor(); michael@0: bool ParseBorderColors(nsCSSProperty aProperty); michael@0: void SetBorderImageInitialValues(); michael@0: bool ParseBorderImageRepeat(bool aAcceptsInherit); michael@0: // If ParseBorderImageSlice returns false, aConsumedTokens indicates michael@0: // whether or not any tokens were consumed (in other words, was the property michael@0: // in error or just not present). If ParseBorderImageSlice returns true michael@0: // aConsumedTokens is always true. michael@0: bool ParseBorderImageSlice(bool aAcceptsInherit, bool* aConsumedTokens); michael@0: bool ParseBorderImageWidth(bool aAcceptsInherit); michael@0: bool ParseBorderImageOutset(bool aAcceptsInherit); michael@0: bool ParseBorderImage(); michael@0: bool ParseBorderSpacing(); michael@0: bool ParseBorderSide(const nsCSSProperty aPropIDs[], michael@0: bool aSetAllSides); michael@0: bool ParseDirectionalBorderSide(const nsCSSProperty aPropIDs[], michael@0: int32_t aSourceType); michael@0: bool ParseBorderStyle(); michael@0: bool ParseBorderWidth(); michael@0: michael@0: bool ParseCalc(nsCSSValue &aValue, int32_t aVariantMask); michael@0: bool ParseCalcAdditiveExpression(nsCSSValue& aValue, michael@0: int32_t& aVariantMask); michael@0: bool ParseCalcMultiplicativeExpression(nsCSSValue& aValue, michael@0: int32_t& aVariantMask, michael@0: bool *aHadFinalWS); michael@0: bool ParseCalcTerm(nsCSSValue& aValue, int32_t& aVariantMask); michael@0: bool RequireWhitespace(); michael@0: michael@0: // For "flex" shorthand property, defined in CSS Flexbox spec michael@0: bool ParseFlex(); michael@0: // For "flex-flow" shorthand property, defined in CSS Flexbox spec michael@0: bool ParseFlexFlow(); michael@0: michael@0: // CSS Grid michael@0: bool ParseGridAutoFlow(); michael@0: michael@0: // Parse a expression. michael@0: // If successful, either leave aValue untouched, michael@0: // to indicate that we parsed the empty list, michael@0: // or set it to a eCSSUnit_List of eCSSUnit_Ident. michael@0: // michael@0: // To parse an optional (ie. if not finding an open paren michael@0: // is considered the same as an empty list), michael@0: // treat CSSParseResult::NotFound the same as CSSParseResult::Ok. michael@0: // michael@0: // If aValue is already a eCSSUnit_List, append to that list. michael@0: CSSParseResult ParseGridLineNames(nsCSSValue& aValue); michael@0: bool ParseGridLineNameListRepeat(nsCSSValueList** aTailPtr); michael@0: bool ParseOptionalLineNameListAfterSubgrid(nsCSSValue& aValue); michael@0: bool ParseGridTrackBreadth(nsCSSValue& aValue); michael@0: CSSParseResult ParseGridTrackSize(nsCSSValue& aValue); michael@0: bool ParseGridAutoColumnsRows(nsCSSProperty aPropID); michael@0: bool ParseGridTrackListRepeat(nsCSSValueList** aTailPtr); michael@0: michael@0: // Assuming a [ ? ] has already been parsed, michael@0: // parse the rest of a . michael@0: // michael@0: // This exists because [ ? ] is ambiguous in the michael@0: // 'grid-template' shorthand: it can be either the start of a , michael@0: // or of the intertwined syntax that sets both michael@0: // grid-template-rows and grid-template-areas. michael@0: // michael@0: // On success, |aValue| will be a list of odd length >= 3, michael@0: // starting with a (which is itself a list) michael@0: // and alternating between that and . michael@0: bool ParseGridTrackListWithFirstLineNames(nsCSSValue& aValue, michael@0: const nsCSSValue& aFirstLineNames); michael@0: bool ParseGridTemplateColumnsRows(nsCSSProperty aPropID); michael@0: michael@0: // |aAreaIndices| is a lookup table to help us parse faster, michael@0: // mapping area names to indices in |aResult.mNamedAreas|. michael@0: bool ParseGridTemplateAreasLine(const nsAutoString& aInput, michael@0: css::GridTemplateAreasValue* aResult, michael@0: nsDataHashtable& aAreaIndices); michael@0: bool ParseGridTemplateAreas(); michael@0: bool ParseGridTemplate(); michael@0: bool ParseGridTemplateAfterSlash(bool aColumnsIsTrackList); michael@0: bool ParseGridTemplateAfterString(const nsCSSValue& aFirstLineNames); michael@0: bool ParseGrid(); michael@0: bool ParseGridShorthandAutoProps(); michael@0: bool ParseGridLine(nsCSSValue& aValue); michael@0: bool ParseGridAutoPosition(); michael@0: bool ParseGridColumnRowStartEnd(nsCSSProperty aPropID); michael@0: bool ParseGridColumnRow(nsCSSProperty aStartPropID, michael@0: nsCSSProperty aEndPropID); michael@0: bool ParseGridArea(); michael@0: michael@0: // for 'clip' and '-moz-image-region' michael@0: bool ParseRect(nsCSSProperty aPropID); michael@0: bool ParseColumns(); michael@0: bool ParseContent(); michael@0: bool ParseCounterData(nsCSSProperty aPropID); michael@0: bool ParseCursor(); michael@0: bool ParseFont(); michael@0: bool ParseFontSynthesis(nsCSSValue& aValue); michael@0: bool ParseSingleAlternate(int32_t& aWhichFeature, nsCSSValue& aValue); michael@0: bool ParseFontVariantAlternates(nsCSSValue& aValue); michael@0: bool ParseBitmaskValues(nsCSSValue& aValue, michael@0: const KTableValue aKeywordTable[], michael@0: const int32_t aMasks[]); michael@0: bool ParseFontVariantEastAsian(nsCSSValue& aValue); michael@0: bool ParseFontVariantLigatures(nsCSSValue& aValue); michael@0: bool ParseFontVariantNumeric(nsCSSValue& aValue); michael@0: bool ParseFontWeight(nsCSSValue& aValue); michael@0: bool ParseOneFamily(nsAString& aFamily, bool& aOneKeyword); michael@0: bool ParseFamily(nsCSSValue& aValue); michael@0: bool ParseFontFeatureSettings(nsCSSValue& aValue); michael@0: bool ParseFontSrc(nsCSSValue& aValue); michael@0: bool ParseFontSrcFormat(InfallibleTArray& values); michael@0: bool ParseFontRanges(nsCSSValue& aValue); michael@0: bool ParseListStyle(); michael@0: bool ParseMargin(); michael@0: bool ParseMarks(nsCSSValue& aValue); michael@0: bool ParseTransform(bool aIsPrefixed); michael@0: bool ParseOutline(); michael@0: bool ParseOverflow(); michael@0: bool ParsePadding(); michael@0: bool ParseQuotes(); michael@0: bool ParseSize(); michael@0: bool ParseTextAlign(nsCSSValue& aValue, michael@0: const KTableValue aTable[]); michael@0: bool ParseTextAlign(nsCSSValue& aValue); michael@0: bool ParseTextAlignLast(nsCSSValue& aValue); michael@0: bool ParseTextDecoration(); michael@0: bool ParseTextDecorationLine(nsCSSValue& aValue); michael@0: bool ParseTextCombineUpright(nsCSSValue& aValue); michael@0: bool ParseTextOverflow(nsCSSValue& aValue); michael@0: bool ParseTouchAction(nsCSSValue& aValue); michael@0: michael@0: bool ParseShadowItem(nsCSSValue& aValue, bool aIsBoxShadow); michael@0: bool ParseShadowList(nsCSSProperty aProperty); michael@0: bool ParseTransitionProperty(); michael@0: bool ParseTransitionTimingFunctionValues(nsCSSValue& aValue); michael@0: bool ParseTransitionTimingFunctionValueComponent(float& aComponent, michael@0: char aStop, michael@0: bool aCheckRange); michael@0: bool ParseTransitionStepTimingFunctionValues(nsCSSValue& aValue); michael@0: enum ParseAnimationOrTransitionShorthandResult { michael@0: eParseAnimationOrTransitionShorthand_Values, michael@0: eParseAnimationOrTransitionShorthand_Inherit, michael@0: eParseAnimationOrTransitionShorthand_Error michael@0: }; michael@0: ParseAnimationOrTransitionShorthandResult michael@0: ParseAnimationOrTransitionShorthand(const nsCSSProperty* aProperties, michael@0: const nsCSSValue* aInitialValues, michael@0: nsCSSValue* aValues, michael@0: size_t aNumProperties); michael@0: bool ParseTransition(); michael@0: bool ParseAnimation(); michael@0: bool ParseWillChange(); michael@0: michael@0: bool ParsePaint(nsCSSProperty aPropID); michael@0: bool ParseDasharray(); michael@0: bool ParseMarker(); michael@0: bool ParsePaintOrder(); michael@0: bool ParseAll(); michael@0: michael@0: /** michael@0: * Parses a variable value from a custom property declaration. michael@0: * michael@0: * @param aType Out parameter into which will be stored the type of variable michael@0: * value, indicating whether the parsed value was a token stream or one of michael@0: * the CSS-wide keywords. michael@0: * @param aValue Out parameter into which will be stored the token stream michael@0: * as a string, if the parsed custom property did take a token stream. michael@0: * @return Whether parsing succeeded. michael@0: */ michael@0: bool ParseVariableDeclaration(CSSVariableDeclarations::Type* aType, michael@0: nsString& aValue); michael@0: michael@0: /** michael@0: * Parses a CSS variable value. This could be 'initial', 'inherit', 'unset' michael@0: * or a token stream, which may or may not include variable references. michael@0: * michael@0: * @param aType Out parameter into which the type of the variable value michael@0: * will be stored. michael@0: * @param aDropBackslash Out parameter indicating whether during variable michael@0: * value parsing there was a trailing backslash before EOF that must michael@0: * be dropped when serializing the variable value. michael@0: * @param aImpliedCharacters Out parameter appended to which will be any michael@0: * characters that were implied when encountering EOF and which michael@0: * must be included at the end of the serialized variable value. michael@0: * @param aFunc A callback function to invoke when a variable reference michael@0: * is encountered. May be null. Arguments are the variable name michael@0: * and the aData argument passed in to this function. michael@0: * @param User data to pass in to the callback. michael@0: * @return Whether parsing succeeded. michael@0: */ michael@0: bool ParseValueWithVariables(CSSVariableDeclarations::Type* aType, michael@0: bool* aDropBackslash, michael@0: nsString& aImpliedCharacters, michael@0: void (*aFunc)(const nsAString&, void*), michael@0: void* aData); michael@0: michael@0: /** michael@0: * Returns whether the scanner dropped a backslash just before EOF. michael@0: */ michael@0: bool BackslashDropped(); michael@0: michael@0: /** michael@0: * Calls AppendImpliedEOFCharacters on mScanner. michael@0: */ michael@0: void AppendImpliedEOFCharacters(nsAString& aResult); michael@0: michael@0: // Reused utility parsing routines michael@0: void AppendValue(nsCSSProperty aPropID, const nsCSSValue& aValue); michael@0: bool ParseBoxProperties(const nsCSSProperty aPropIDs[]); michael@0: bool ParseGroupedBoxProperty(int32_t aVariantMask, michael@0: nsCSSValue& aValue); michael@0: bool ParseDirectionalBoxProperty(nsCSSProperty aProperty, michael@0: int32_t aSourceType); michael@0: bool ParseBoxCornerRadius(const nsCSSProperty aPropID); michael@0: bool ParseBoxCornerRadii(const nsCSSProperty aPropIDs[]); michael@0: int32_t ParseChoice(nsCSSValue aValues[], michael@0: const nsCSSProperty aPropIDs[], int32_t aNumIDs); michael@0: bool ParseColor(nsCSSValue& aValue); michael@0: bool ParseNumberColorComponent(uint8_t& aComponent, char aStop); michael@0: bool ParsePercentageColorComponent(float& aComponent, char aStop); michael@0: // ParseHSLColor parses everything starting with the opening '(' michael@0: // up through and including the aStop char. michael@0: bool ParseHSLColor(float& aHue, float& aSaturation, float& aLightness, michael@0: char aStop); michael@0: // ParseColorOpacity will enforce that the color ends with a ')' michael@0: // after the opacity michael@0: bool ParseColorOpacity(uint8_t& aOpacity); michael@0: bool ParseColorOpacity(float& aOpacity); michael@0: bool ParseEnum(nsCSSValue& aValue, michael@0: const KTableValue aKeywordTable[]); michael@0: bool ParseVariant(nsCSSValue& aValue, michael@0: int32_t aVariantMask, michael@0: const KTableValue aKeywordTable[]); michael@0: bool ParseNonNegativeVariant(nsCSSValue& aValue, michael@0: int32_t aVariantMask, michael@0: const KTableValue aKeywordTable[]); michael@0: bool ParseOneOrLargerVariant(nsCSSValue& aValue, michael@0: int32_t aVariantMask, michael@0: const KTableValue aKeywordTable[]); michael@0: michael@0: // http://dev.w3.org/csswg/css-values/#custom-idents michael@0: // Parse an identifier that is none of: michael@0: // * a CSS-wide keyword michael@0: // * "default" michael@0: // * a keyword in |aExcludedKeywords| michael@0: // * a keyword in |aPropertyKTable| michael@0: // michael@0: // |aExcludedKeywords| is an array of nsCSSKeyword michael@0: // that ends with a eCSSKeyword_UNKNOWN marker. michael@0: // michael@0: // |aPropertyKTable| can be used if some of the keywords to exclude michael@0: // also appear in an existing nsCSSProps::KTableValue, michael@0: // to avoid duplicating them. michael@0: bool ParseCustomIdent(nsCSSValue& aValue, michael@0: const nsAutoString& aIdentValue, michael@0: const nsCSSKeyword aExcludedKeywords[] = nullptr, michael@0: const nsCSSProps::KTableValue aPropertyKTable[] = nullptr); michael@0: bool ParseCounter(nsCSSValue& aValue); michael@0: bool ParseAttr(nsCSSValue& aValue); michael@0: bool SetValueToURL(nsCSSValue& aValue, const nsString& aURL); michael@0: bool TranslateDimension(nsCSSValue& aValue, int32_t aVariantMask, michael@0: float aNumber, const nsString& aUnit); michael@0: bool ParseImageOrientation(nsCSSValue& aAngle); michael@0: bool ParseImageRect(nsCSSValue& aImage); michael@0: bool ParseElement(nsCSSValue& aValue); michael@0: bool ParseColorStop(nsCSSValueGradient* aGradient); michael@0: bool ParseLinearGradient(nsCSSValue& aValue, bool aIsRepeating, michael@0: bool aIsLegacy); michael@0: bool ParseRadialGradient(nsCSSValue& aValue, bool aIsRepeating, michael@0: bool aIsLegacy); michael@0: bool IsLegacyGradientLine(const nsCSSTokenType& aType, michael@0: const nsString& aId); michael@0: bool ParseGradientColorStops(nsCSSValueGradient* aGradient, michael@0: nsCSSValue& aValue); michael@0: michael@0: void SetParsingCompoundProperty(bool aBool) { michael@0: mParsingCompoundProperty = aBool; michael@0: } michael@0: bool IsParsingCompoundProperty(void) const { michael@0: return mParsingCompoundProperty; michael@0: } michael@0: michael@0: /* Functions for transform Parsing */ michael@0: bool ParseSingleTransform(bool aIsPrefixed, nsCSSValue& aValue); michael@0: bool ParseFunction(nsCSSKeyword aFunction, const int32_t aAllowedTypes[], michael@0: int32_t aVariantMaskAll, uint16_t aMinElems, michael@0: uint16_t aMaxElems, nsCSSValue &aValue); michael@0: bool ParseFunctionInternals(const int32_t aVariantMask[], michael@0: int32_t aVariantMaskAll, michael@0: uint16_t aMinElems, michael@0: uint16_t aMaxElems, michael@0: InfallibleTArray& aOutput); michael@0: michael@0: /* Functions for transform-origin/perspective-origin Parsing */ michael@0: bool ParseTransformOrigin(bool aPerspective); michael@0: michael@0: /* Functions for filter parsing */ michael@0: bool ParseFilter(); michael@0: bool ParseSingleFilter(nsCSSValue* aValue); michael@0: bool ParseDropShadow(nsCSSValue* aValue); michael@0: michael@0: /* Find and return the namespace ID associated with aPrefix. michael@0: If aPrefix has not been declared in an @namespace rule, returns michael@0: kNameSpaceID_Unknown. */ michael@0: int32_t GetNamespaceIdForPrefix(const nsString& aPrefix); michael@0: michael@0: /* Find the correct default namespace, and set it on aSelector. */ michael@0: void SetDefaultNamespaceOnSelector(nsCSSSelector& aSelector); michael@0: michael@0: // Current token. The value is valid after calling GetToken and invalidated michael@0: // by UngetToken. michael@0: nsCSSToken mToken; michael@0: michael@0: // Our scanner. michael@0: nsCSSScanner* mScanner; michael@0: michael@0: // Our error reporter. michael@0: css::ErrorReporter* mReporter; michael@0: michael@0: // The URI to be used as a base for relative URIs. michael@0: nsCOMPtr mBaseURI; michael@0: michael@0: // The URI to be used as an HTTP "Referer" and for error reporting. michael@0: nsCOMPtr mSheetURI; michael@0: michael@0: // The principal of the sheet involved michael@0: nsCOMPtr mSheetPrincipal; michael@0: michael@0: // The sheet we're parsing into michael@0: nsRefPtr mSheet; michael@0: michael@0: // Used for @import rules michael@0: mozilla::css::Loader* mChildLoader; // not ref counted, it owns us michael@0: michael@0: // Sheet section we're in. This is used to enforce correct ordering of the michael@0: // various rule types (eg the fact that a @charset rule must come before michael@0: // anything else). Note that there are checks of similar things in various michael@0: // places in nsCSSStyleSheet.cpp (e.g in insertRule, RebuildChildList). michael@0: enum nsCSSSection { michael@0: eCSSSection_Charset, michael@0: eCSSSection_Import, michael@0: eCSSSection_NameSpace, michael@0: eCSSSection_General michael@0: }; michael@0: nsCSSSection mSection; michael@0: michael@0: nsXMLNameSpaceMap *mNameSpaceMap; // weak, mSheet owns it michael@0: michael@0: // After an UngetToken is done this flag is true. The next call to michael@0: // GetToken clears the flag. michael@0: bool mHavePushBack : 1; michael@0: michael@0: // True if we are in quirks mode; false in standards or almost standards mode michael@0: bool mNavQuirkMode : 1; michael@0: michael@0: // True when the hashless color quirk applies. michael@0: bool mHashlessColorQuirk : 1; michael@0: michael@0: // True when the unitless length quirk applies. michael@0: bool mUnitlessLengthQuirk : 1; michael@0: michael@0: // True if unsafe rules should be allowed michael@0: bool mUnsafeRulesEnabled : 1; michael@0: michael@0: // True if we are in parsing rules for Chrome or Certified App content, michael@0: // in which case CSS properties with the michael@0: // CSS_PROPERTY_ALWAYS_ENABLED_IN_CHROME_OR_CERTIFIED_APP michael@0: // flag should be allowed. michael@0: bool mIsChromeOrCertifiedApp : 1; michael@0: michael@0: // True if viewport units should be allowed. michael@0: bool mViewportUnitsEnabled : 1; michael@0: michael@0: // True for parsing media lists for HTML attributes, where we have to michael@0: // ignore CSS comments. michael@0: bool mHTMLMediaMode : 1; michael@0: michael@0: // This flag is set when parsing a non-box shorthand; it's used to not apply michael@0: // some quirks during shorthand parsing michael@0: bool mParsingCompoundProperty : 1; michael@0: michael@0: // True if we are in the middle of parsing an @supports condition. michael@0: // This is used to avoid recording the input stream when variable references michael@0: // are encountered in a property declaration in the @supports condition. michael@0: bool mInSupportsCondition : 1; michael@0: michael@0: // True if we are somewhere within a @supports rule whose condition is michael@0: // false. michael@0: bool mInFailingSupportsRule : 1; michael@0: michael@0: // True if we will suppress all parse errors (except unexpected EOFs). michael@0: // This is used to prevent errors for declarations inside a failing michael@0: // @supports rule. michael@0: bool mSuppressErrors : 1; michael@0: michael@0: // Stack of rule groups; used for @media and such. michael@0: InfallibleTArray > mGroupStack; michael@0: michael@0: // During the parsing of a property (which may be a shorthand), the data michael@0: // are stored in |mTempData|. (It is needed to ensure that parser michael@0: // errors cause the data to be ignored, and to ensure that a michael@0: // non-'!important' declaration does not override an '!important' michael@0: // one.) michael@0: nsCSSExpandedDataBlock mTempData; michael@0: michael@0: // All data from successfully parsed properties are placed into |mData|. michael@0: nsCSSExpandedDataBlock mData; michael@0: michael@0: public: michael@0: // Used from nsCSSParser constructors and destructors michael@0: CSSParserImpl* mNextFree; michael@0: }; michael@0: michael@0: static void AssignRuleToPointer(css::Rule* aRule, void* aPointer) michael@0: { michael@0: css::Rule **pointer = static_cast(aPointer); michael@0: NS_ADDREF(*pointer = aRule); michael@0: } michael@0: michael@0: static void AppendRuleToSheet(css::Rule* aRule, void* aParser) michael@0: { michael@0: CSSParserImpl* parser = (CSSParserImpl*) aParser; michael@0: parser->AppendRule(aRule); michael@0: } michael@0: michael@0: #define REPORT_UNEXPECTED(msg_) \ michael@0: { if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_); } michael@0: michael@0: #define REPORT_UNEXPECTED_P(msg_, param_) \ michael@0: { if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_, param_); } michael@0: michael@0: #define REPORT_UNEXPECTED_TOKEN(msg_) \ michael@0: { if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_, mToken); } michael@0: michael@0: #define REPORT_UNEXPECTED_TOKEN_CHAR(msg_, ch_) \ michael@0: { if (!mSuppressErrors) mReporter->ReportUnexpected(#msg_, mToken, ch_); } michael@0: michael@0: #define REPORT_UNEXPECTED_EOF(lf_) \ michael@0: mReporter->ReportUnexpectedEOF(#lf_) michael@0: michael@0: #define REPORT_UNEXPECTED_EOF_CHAR(ch_) \ michael@0: mReporter->ReportUnexpectedEOF(ch_) michael@0: michael@0: #define OUTPUT_ERROR() \ michael@0: mReporter->OutputError() michael@0: michael@0: #define OUTPUT_ERROR_WITH_POSITION(linenum_, lineoff_) \ michael@0: mReporter->OutputError(linenum_, lineoff_) michael@0: michael@0: #define CLEAR_ERROR() \ michael@0: mReporter->ClearError() michael@0: michael@0: CSSParserImpl::CSSParserImpl() michael@0: : mToken(), michael@0: mScanner(nullptr), michael@0: mReporter(nullptr), michael@0: mChildLoader(nullptr), michael@0: mSection(eCSSSection_Charset), michael@0: mNameSpaceMap(nullptr), michael@0: mHavePushBack(false), michael@0: mNavQuirkMode(false), michael@0: mHashlessColorQuirk(false), michael@0: mUnitlessLengthQuirk(false), michael@0: mUnsafeRulesEnabled(false), michael@0: mIsChromeOrCertifiedApp(false), michael@0: mViewportUnitsEnabled(true), michael@0: mHTMLMediaMode(false), michael@0: mParsingCompoundProperty(false), michael@0: mInSupportsCondition(false), michael@0: mInFailingSupportsRule(false), michael@0: mSuppressErrors(false), michael@0: mNextFree(nullptr) michael@0: { michael@0: } michael@0: michael@0: CSSParserImpl::~CSSParserImpl() michael@0: { michael@0: mData.AssertInitialState(); michael@0: mTempData.AssertInitialState(); michael@0: } michael@0: michael@0: nsresult michael@0: CSSParserImpl::SetStyleSheet(nsCSSStyleSheet* aSheet) michael@0: { michael@0: if (aSheet != mSheet) { michael@0: // Switch to using the new sheet, if any michael@0: mGroupStack.Clear(); michael@0: mSheet = aSheet; michael@0: if (mSheet) { michael@0: mNameSpaceMap = mSheet->GetNameSpaceMap(); michael@0: } else { michael@0: mNameSpaceMap = nullptr; michael@0: } michael@0: } else if (mSheet) { michael@0: mNameSpaceMap = mSheet->GetNameSpaceMap(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CSSParserImpl::SetQuirkMode(bool aQuirkMode) michael@0: { michael@0: mNavQuirkMode = aQuirkMode; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CSSParserImpl::SetChildLoader(mozilla::css::Loader* aChildLoader) michael@0: { michael@0: mChildLoader = aChildLoader; // not ref counted, it owns us michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: CSSParserImpl::Reset() michael@0: { michael@0: NS_ASSERTION(!mScanner, "resetting with scanner active"); michael@0: SetStyleSheet(nullptr); michael@0: SetQuirkMode(false); michael@0: SetChildLoader(nullptr); michael@0: } michael@0: michael@0: void michael@0: CSSParserImpl::InitScanner(nsCSSScanner& aScanner, michael@0: css::ErrorReporter& aReporter, michael@0: nsIURI* aSheetURI, nsIURI* aBaseURI, michael@0: nsIPrincipal* aSheetPrincipal) michael@0: { michael@0: NS_PRECONDITION(!mHTMLMediaMode, "Bad initial state"); michael@0: NS_PRECONDITION(!mParsingCompoundProperty, "Bad initial state"); michael@0: NS_PRECONDITION(!mScanner, "already have scanner"); michael@0: michael@0: mScanner = &aScanner; michael@0: mReporter = &aReporter; michael@0: mScanner->SetErrorReporter(mReporter); michael@0: michael@0: mBaseURI = aBaseURI; michael@0: mSheetURI = aSheetURI; michael@0: mSheetPrincipal = aSheetPrincipal; michael@0: mHavePushBack = false; michael@0: } michael@0: michael@0: void michael@0: CSSParserImpl::ReleaseScanner() michael@0: { michael@0: mScanner = nullptr; michael@0: mReporter = nullptr; michael@0: mBaseURI = nullptr; michael@0: mSheetURI = nullptr; michael@0: mSheetPrincipal = nullptr; michael@0: } michael@0: michael@0: nsresult michael@0: CSSParserImpl::ParseSheet(const nsAString& aInput, michael@0: nsIURI* aSheetURI, michael@0: nsIURI* aBaseURI, michael@0: nsIPrincipal* aSheetPrincipal, michael@0: uint32_t aLineNumber, michael@0: bool aAllowUnsafeRules) michael@0: { michael@0: NS_PRECONDITION(aSheetPrincipal, "Must have principal here!"); michael@0: NS_PRECONDITION(aBaseURI, "need base URI"); michael@0: NS_PRECONDITION(aSheetURI, "need sheet URI"); michael@0: NS_PRECONDITION(mSheet, "Must have sheet to parse into"); michael@0: NS_ENSURE_STATE(mSheet); michael@0: michael@0: #ifdef DEBUG michael@0: nsIURI* uri = mSheet->GetSheetURI(); michael@0: bool equal; michael@0: NS_ASSERTION(NS_SUCCEEDED(aSheetURI->Equals(uri, &equal)) && equal, michael@0: "Sheet URI does not match passed URI"); michael@0: NS_ASSERTION(NS_SUCCEEDED(mSheet->Principal()->Equals(aSheetPrincipal, michael@0: &equal)) && michael@0: equal, michael@0: "Sheet principal does not match passed principal"); michael@0: #endif michael@0: michael@0: nsCSSScanner scanner(aInput, aLineNumber); michael@0: css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI); michael@0: InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal); michael@0: michael@0: int32_t ruleCount = mSheet->StyleRuleCount(); michael@0: if (0 < ruleCount) { michael@0: const css::Rule* lastRule = mSheet->GetStyleRuleAt(ruleCount - 1); michael@0: if (lastRule) { michael@0: switch (lastRule->GetType()) { michael@0: case css::Rule::CHARSET_RULE: michael@0: case css::Rule::IMPORT_RULE: michael@0: mSection = eCSSSection_Import; michael@0: break; michael@0: case css::Rule::NAMESPACE_RULE: michael@0: mSection = eCSSSection_NameSpace; michael@0: break; michael@0: default: michael@0: mSection = eCSSSection_General; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: else { michael@0: mSection = eCSSSection_Charset; // sheet is empty, any rules are fair michael@0: } michael@0: michael@0: mUnsafeRulesEnabled = aAllowUnsafeRules; michael@0: mIsChromeOrCertifiedApp = michael@0: dom::IsChromeURI(aSheetURI) || michael@0: aSheetPrincipal->GetAppStatus() == nsIPrincipal::APP_STATUS_CERTIFIED; michael@0: michael@0: nsCSSToken* tk = &mToken; michael@0: for (;;) { michael@0: // Get next non-whitespace token michael@0: if (!GetToken(true)) { michael@0: OUTPUT_ERROR(); michael@0: break; michael@0: } michael@0: if (eCSSToken_HTMLComment == tk->mType) { michael@0: continue; // legal here only michael@0: } michael@0: if (eCSSToken_AtKeyword == tk->mType) { michael@0: ParseAtRule(AppendRuleToSheet, this, false); michael@0: continue; michael@0: } michael@0: UngetToken(); michael@0: if (ParseRuleSet(AppendRuleToSheet, this)) { michael@0: mSection = eCSSSection_General; michael@0: } michael@0: } michael@0: ReleaseScanner(); michael@0: michael@0: mUnsafeRulesEnabled = false; michael@0: mIsChromeOrCertifiedApp = false; michael@0: michael@0: // XXX check for low level errors michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** michael@0: * Determines whether the identifier contained in the given string is a michael@0: * vendor-specific identifier, as described in CSS 2.1 section 4.1.2.1. michael@0: */ michael@0: static bool michael@0: NonMozillaVendorIdentifier(const nsAString& ident) michael@0: { michael@0: return (ident.First() == char16_t('-') && michael@0: !StringBeginsWith(ident, NS_LITERAL_STRING("-moz-"))) || michael@0: ident.First() == char16_t('_'); michael@0: michael@0: } michael@0: michael@0: nsresult michael@0: CSSParserImpl::ParseStyleAttribute(const nsAString& aAttributeValue, michael@0: nsIURI* aDocURI, michael@0: nsIURI* aBaseURI, michael@0: nsIPrincipal* aNodePrincipal, michael@0: css::StyleRule** aResult) michael@0: { michael@0: NS_PRECONDITION(aNodePrincipal, "Must have principal here!"); michael@0: NS_PRECONDITION(aBaseURI, "need base URI"); michael@0: michael@0: // XXX line number? michael@0: nsCSSScanner scanner(aAttributeValue, 0); michael@0: css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aDocURI); michael@0: InitScanner(scanner, reporter, aDocURI, aBaseURI, aNodePrincipal); michael@0: michael@0: mSection = eCSSSection_General; michael@0: michael@0: uint32_t parseFlags = eParseDeclaration_AllowImportant; michael@0: michael@0: css::Declaration* declaration = ParseDeclarationBlock(parseFlags); michael@0: if (declaration) { michael@0: // Create a style rule for the declaration michael@0: NS_ADDREF(*aResult = new css::StyleRule(nullptr, declaration)); michael@0: } else { michael@0: *aResult = nullptr; michael@0: } michael@0: michael@0: ReleaseScanner(); michael@0: michael@0: // XXX check for low level errors michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CSSParserImpl::ParseDeclarations(const nsAString& aBuffer, michael@0: nsIURI* aSheetURI, michael@0: nsIURI* aBaseURI, michael@0: nsIPrincipal* aSheetPrincipal, michael@0: css::Declaration* aDeclaration, michael@0: bool* aChanged) michael@0: { michael@0: *aChanged = false; michael@0: michael@0: NS_PRECONDITION(aSheetPrincipal, "Must have principal here!"); michael@0: michael@0: nsCSSScanner scanner(aBuffer, 0); michael@0: css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI); michael@0: InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal); michael@0: michael@0: mSection = eCSSSection_General; michael@0: michael@0: mData.AssertInitialState(); michael@0: aDeclaration->ClearData(); michael@0: // We could check if it was already empty, but... michael@0: *aChanged = true; michael@0: michael@0: for (;;) { michael@0: // If we cleared the old decl, then we want to be calling michael@0: // ValueAppended as we parse. michael@0: if (!ParseDeclaration(aDeclaration, eParseDeclaration_AllowImportant, michael@0: true, aChanged)) { michael@0: if (!SkipDeclaration(false)) { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: aDeclaration->CompressFrom(&mData); michael@0: ReleaseScanner(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CSSParserImpl::ParseRule(const nsAString& aRule, michael@0: nsIURI* aSheetURI, michael@0: nsIURI* aBaseURI, michael@0: nsIPrincipal* aSheetPrincipal, michael@0: css::Rule** aResult) michael@0: { michael@0: NS_PRECONDITION(aSheetPrincipal, "Must have principal here!"); michael@0: NS_PRECONDITION(aBaseURI, "need base URI"); michael@0: michael@0: *aResult = nullptr; michael@0: michael@0: nsCSSScanner scanner(aRule, 0); michael@0: css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI); michael@0: InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal); michael@0: michael@0: mSection = eCSSSection_Charset; // callers are responsible for rejecting invalid rules. michael@0: michael@0: nsCSSToken* tk = &mToken; michael@0: // Get first non-whitespace token michael@0: nsresult rv = NS_OK; michael@0: if (!GetToken(true)) { michael@0: REPORT_UNEXPECTED(PEParseRuleWSOnly); michael@0: OUTPUT_ERROR(); michael@0: rv = NS_ERROR_DOM_SYNTAX_ERR; michael@0: } else { michael@0: if (eCSSToken_AtKeyword == tk->mType) { michael@0: // FIXME: perhaps aInsideBlock should be true when we are? michael@0: ParseAtRule(AssignRuleToPointer, aResult, false); michael@0: } else { michael@0: UngetToken(); michael@0: ParseRuleSet(AssignRuleToPointer, aResult); michael@0: } michael@0: michael@0: if (*aResult && GetToken(true)) { michael@0: // garbage after rule michael@0: REPORT_UNEXPECTED_TOKEN(PERuleTrailing); michael@0: NS_RELEASE(*aResult); michael@0: } michael@0: michael@0: if (!*aResult) { michael@0: rv = NS_ERROR_DOM_SYNTAX_ERR; michael@0: OUTPUT_ERROR(); michael@0: } michael@0: } michael@0: michael@0: ReleaseScanner(); michael@0: return rv; michael@0: } michael@0: michael@0: // See Bug 723197 michael@0: #ifdef _MSC_VER michael@0: #pragma optimize( "", off ) michael@0: #pragma warning( push ) michael@0: #pragma warning( disable : 4748 ) michael@0: #endif michael@0: michael@0: nsresult michael@0: CSSParserImpl::ParseProperty(const nsCSSProperty aPropID, michael@0: const nsAString& aPropValue, michael@0: nsIURI* aSheetURI, michael@0: nsIURI* aBaseURI, michael@0: nsIPrincipal* aSheetPrincipal, michael@0: css::Declaration* aDeclaration, michael@0: bool* aChanged, michael@0: bool aIsImportant, michael@0: bool aIsSVGMode) michael@0: { michael@0: NS_PRECONDITION(aSheetPrincipal, "Must have principal here!"); michael@0: NS_PRECONDITION(aBaseURI, "need base URI"); michael@0: NS_PRECONDITION(aDeclaration, "Need declaration to parse into!"); michael@0: michael@0: mData.AssertInitialState(); michael@0: mTempData.AssertInitialState(); michael@0: aDeclaration->AssertMutable(); michael@0: michael@0: nsCSSScanner scanner(aPropValue, 0); michael@0: css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI); michael@0: InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal); michael@0: mSection = eCSSSection_General; michael@0: scanner.SetSVGMode(aIsSVGMode); michael@0: michael@0: *aChanged = false; michael@0: michael@0: // Check for unknown or preffed off properties michael@0: if (eCSSProperty_UNKNOWN == aPropID || michael@0: !(nsCSSProps::IsEnabled(aPropID) || michael@0: (mUnsafeRulesEnabled && michael@0: nsCSSProps::PropHasFlags(aPropID, michael@0: CSS_PROPERTY_ALWAYS_ENABLED_IN_UA_SHEETS)))) { michael@0: NS_ConvertASCIItoUTF16 propName(nsCSSProps::GetStringValue(aPropID)); michael@0: REPORT_UNEXPECTED_P(PEUnknownProperty, propName); michael@0: REPORT_UNEXPECTED(PEDeclDropped); michael@0: OUTPUT_ERROR(); michael@0: ReleaseScanner(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool parsedOK = ParseProperty(aPropID); michael@0: // We should now be at EOF michael@0: if (parsedOK && GetToken(true)) { michael@0: REPORT_UNEXPECTED_TOKEN(PEExpectEndValue); michael@0: parsedOK = false; michael@0: } michael@0: michael@0: if (!parsedOK) { michael@0: NS_ConvertASCIItoUTF16 propName(nsCSSProps::GetStringValue(aPropID)); michael@0: REPORT_UNEXPECTED_P(PEValueParsingError, propName); michael@0: REPORT_UNEXPECTED(PEDeclDropped); michael@0: OUTPUT_ERROR(); michael@0: mTempData.ClearProperty(aPropID); michael@0: } else { michael@0: michael@0: // We know we don't need to force a ValueAppended call for the new michael@0: // value. So if we are not processing a shorthand, and there's michael@0: // already a value for this property in the declaration at the michael@0: // same importance level, then we can just copy our parsed value michael@0: // directly into the declaration without going through the whole michael@0: // expand/compress thing. michael@0: if (!aDeclaration->TryReplaceValue(aPropID, aIsImportant, mTempData, michael@0: aChanged)) { michael@0: // Do it the slow way michael@0: aDeclaration->ExpandTo(&mData); michael@0: *aChanged = mData.TransferFromBlock(mTempData, aPropID, aIsImportant, michael@0: true, false, aDeclaration); michael@0: aDeclaration->CompressFrom(&mData); michael@0: } michael@0: CLEAR_ERROR(); michael@0: } michael@0: michael@0: mTempData.AssertInitialState(); michael@0: michael@0: ReleaseScanner(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CSSParserImpl::ParseVariable(const nsAString& aVariableName, michael@0: const nsAString& aPropValue, michael@0: nsIURI* aSheetURI, michael@0: nsIURI* aBaseURI, michael@0: nsIPrincipal* aSheetPrincipal, michael@0: css::Declaration* aDeclaration, michael@0: bool* aChanged, michael@0: bool aIsImportant) michael@0: { michael@0: NS_PRECONDITION(aSheetPrincipal, "Must have principal here!"); michael@0: NS_PRECONDITION(aBaseURI, "need base URI"); michael@0: NS_PRECONDITION(aDeclaration, "Need declaration to parse into!"); michael@0: NS_PRECONDITION(nsLayoutUtils::CSSVariablesEnabled(), michael@0: "expected Variables to be enabled"); michael@0: michael@0: mData.AssertInitialState(); michael@0: mTempData.AssertInitialState(); michael@0: aDeclaration->AssertMutable(); michael@0: michael@0: nsCSSScanner scanner(aPropValue, 0); michael@0: css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aSheetURI); michael@0: InitScanner(scanner, reporter, aSheetURI, aBaseURI, aSheetPrincipal); michael@0: mSection = eCSSSection_General; michael@0: michael@0: *aChanged = false; michael@0: michael@0: CSSVariableDeclarations::Type variableType; michael@0: nsString variableValue; michael@0: michael@0: bool parsedOK = ParseVariableDeclaration(&variableType, variableValue); michael@0: michael@0: // We should now be at EOF michael@0: if (parsedOK && GetToken(true)) { michael@0: REPORT_UNEXPECTED_TOKEN(PEExpectEndValue); michael@0: parsedOK = false; michael@0: } michael@0: michael@0: if (!parsedOK) { michael@0: REPORT_UNEXPECTED_P(PEValueParsingError, NS_LITERAL_STRING("--") + michael@0: aVariableName); michael@0: REPORT_UNEXPECTED(PEDeclDropped); michael@0: OUTPUT_ERROR(); michael@0: } else { michael@0: CLEAR_ERROR(); michael@0: aDeclaration->AddVariableDeclaration(aVariableName, variableType, michael@0: variableValue, aIsImportant, true); michael@0: *aChanged = true; michael@0: } michael@0: michael@0: mTempData.AssertInitialState(); michael@0: michael@0: ReleaseScanner(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: #ifdef _MSC_VER michael@0: #pragma warning( pop ) michael@0: #pragma optimize( "", on ) michael@0: #endif michael@0: michael@0: void michael@0: CSSParserImpl::ParseMediaList(const nsSubstring& aBuffer, michael@0: nsIURI* aURI, // for error reporting michael@0: uint32_t aLineNumber, // for error reporting michael@0: nsMediaList* aMediaList, michael@0: bool aHTMLMode) michael@0: { michael@0: // XXX Are there cases where the caller wants to keep what it already michael@0: // has in case of parser error? If GatherMedia ever changes to return michael@0: // a value other than true, we probably should avoid modifying aMediaList. michael@0: aMediaList->Clear(); michael@0: michael@0: // fake base URI since media lists don't have URIs in them michael@0: nsCSSScanner scanner(aBuffer, aLineNumber); michael@0: css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI); michael@0: InitScanner(scanner, reporter, aURI, aURI, nullptr); michael@0: michael@0: mHTMLMediaMode = aHTMLMode; michael@0: michael@0: // XXXldb We need to make the scanner not skip CSS comments! (Or michael@0: // should we?) michael@0: michael@0: // For aHTMLMode, we used to follow the parsing rules in michael@0: // http://www.w3.org/TR/1999/REC-html401-19991224/types.html#type-media-descriptors michael@0: // which wouldn't work for media queries since they remove all but the michael@0: // first word. However, they're changed in michael@0: // http://www.whatwg.org/specs/web-apps/current-work/multipage/section-document.html#media2 michael@0: // (as of 2008-05-29) which says that the media attribute just points michael@0: // to a media query. (The main substative difference is the relative michael@0: // precedence of commas and paretheses.) michael@0: michael@0: DebugOnly parsedOK = GatherMedia(aMediaList, false); michael@0: NS_ASSERTION(parsedOK, "GatherMedia returned false; we probably want to avoid " michael@0: "trashing aMediaList"); michael@0: michael@0: CLEAR_ERROR(); michael@0: ReleaseScanner(); michael@0: mHTMLMediaMode = false; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseColorString(const nsSubstring& aBuffer, michael@0: nsIURI* aURI, // for error reporting michael@0: uint32_t aLineNumber, // for error reporting michael@0: nsCSSValue& aValue) michael@0: { michael@0: nsCSSScanner scanner(aBuffer, aLineNumber); michael@0: css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI); michael@0: InitScanner(scanner, reporter, aURI, aURI, nullptr); michael@0: michael@0: // Parse a color, and check that there's nothing else after it. michael@0: bool colorParsed = ParseColor(aValue) && !GetToken(true); michael@0: OUTPUT_ERROR(); michael@0: ReleaseScanner(); michael@0: return colorParsed; michael@0: } michael@0: michael@0: nsresult michael@0: CSSParserImpl::ParseSelectorString(const nsSubstring& aSelectorString, michael@0: nsIURI* aURI, // for error reporting michael@0: uint32_t aLineNumber, // for error reporting michael@0: nsCSSSelectorList **aSelectorList) michael@0: { michael@0: nsCSSScanner scanner(aSelectorString, aLineNumber); michael@0: css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI); michael@0: InitScanner(scanner, reporter, aURI, aURI, nullptr); michael@0: michael@0: bool success = ParseSelectorList(*aSelectorList, char16_t(0)); michael@0: michael@0: // We deliberately do not call OUTPUT_ERROR here, because all our michael@0: // callers map a failure return to a JS exception, and if that JS michael@0: // exception is caught, people don't want to see parser diagnostics; michael@0: // see e.g. http://bugs.jquery.com/ticket/7535 michael@0: // It would be nice to be able to save the parser diagnostics into michael@0: // the exception, so that if it _isn't_ caught we can report them michael@0: // along with the usual uncaught-exception message, but we don't michael@0: // have any way to do that at present; see bug 631621. michael@0: CLEAR_ERROR(); michael@0: ReleaseScanner(); michael@0: michael@0: if (success) { michael@0: NS_ASSERTION(*aSelectorList, "Should have list!"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_ASSERTION(!*aSelectorList, "Shouldn't have list!"); michael@0: michael@0: return NS_ERROR_DOM_SYNTAX_ERR; michael@0: } michael@0: michael@0: michael@0: already_AddRefed michael@0: CSSParserImpl::ParseKeyframeRule(const nsSubstring& aBuffer, michael@0: nsIURI* aURI, michael@0: uint32_t aLineNumber) michael@0: { michael@0: nsCSSScanner scanner(aBuffer, aLineNumber); michael@0: css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI); michael@0: InitScanner(scanner, reporter, aURI, aURI, nullptr); michael@0: michael@0: nsRefPtr result = ParseKeyframeRule(); michael@0: if (GetToken(true)) { michael@0: // extra garbage at the end michael@0: result = nullptr; michael@0: } michael@0: michael@0: OUTPUT_ERROR(); michael@0: ReleaseScanner(); michael@0: michael@0: return result.forget(); michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseKeyframeSelectorString(const nsSubstring& aSelectorString, michael@0: nsIURI* aURI, // for error reporting michael@0: uint32_t aLineNumber, // for error reporting michael@0: InfallibleTArray& aSelectorList) michael@0: { michael@0: NS_ABORT_IF_FALSE(aSelectorList.IsEmpty(), "given list should start empty"); michael@0: michael@0: nsCSSScanner scanner(aSelectorString, aLineNumber); michael@0: css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aURI); michael@0: InitScanner(scanner, reporter, aURI, aURI, nullptr); michael@0: michael@0: bool success = ParseKeyframeSelectorList(aSelectorList) && michael@0: // must consume entire input string michael@0: !GetToken(true); michael@0: michael@0: OUTPUT_ERROR(); michael@0: ReleaseScanner(); michael@0: michael@0: if (success) { michael@0: NS_ASSERTION(!aSelectorList.IsEmpty(), "should not be empty"); michael@0: } else { michael@0: aSelectorList.Clear(); michael@0: } michael@0: michael@0: return success; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::EvaluateSupportsDeclaration(const nsAString& aProperty, michael@0: const nsAString& aValue, michael@0: nsIURI* aDocURL, michael@0: nsIURI* aBaseURL, michael@0: nsIPrincipal* aDocPrincipal) michael@0: { michael@0: nsCSSProperty propID = LookupEnabledProperty(aProperty); michael@0: if (propID == eCSSProperty_UNKNOWN) { michael@0: return false; michael@0: } michael@0: michael@0: nsCSSScanner scanner(aValue, 0); michael@0: css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aDocURL); michael@0: InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal); michael@0: nsAutoSuppressErrors suppressErrors(this); michael@0: michael@0: bool parsedOK; michael@0: michael@0: if (propID == eCSSPropertyExtra_variable) { michael@0: MOZ_ASSERT(Substring(aProperty, 0, michael@0: CSS_CUSTOM_NAME_PREFIX_LENGTH).EqualsLiteral("--")); michael@0: const nsDependentSubstring varName = michael@0: Substring(aProperty, CSS_CUSTOM_NAME_PREFIX_LENGTH); // remove '--' michael@0: CSSVariableDeclarations::Type variableType; michael@0: nsString variableValue; michael@0: parsedOK = ParseVariableDeclaration(&variableType, variableValue) && michael@0: !GetToken(true); michael@0: } else { michael@0: parsedOK = ParseProperty(propID) && !GetToken(true); michael@0: michael@0: mTempData.ClearProperty(propID); michael@0: mTempData.AssertInitialState(); michael@0: } michael@0: michael@0: CLEAR_ERROR(); michael@0: ReleaseScanner(); michael@0: michael@0: return parsedOK; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::EvaluateSupportsCondition(const nsAString& aDeclaration, michael@0: nsIURI* aDocURL, michael@0: nsIURI* aBaseURL, michael@0: nsIPrincipal* aDocPrincipal) michael@0: { michael@0: nsCSSScanner scanner(aDeclaration, 0); michael@0: css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aDocURL); michael@0: InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal); michael@0: nsAutoSuppressErrors suppressErrors(this); michael@0: michael@0: bool conditionMet; michael@0: bool parsedOK = ParseSupportsCondition(conditionMet) && !GetToken(true); michael@0: michael@0: CLEAR_ERROR(); michael@0: ReleaseScanner(); michael@0: michael@0: return parsedOK && conditionMet; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::EnumerateVariableReferences(const nsAString& aPropertyValue, michael@0: VariableEnumFunc aFunc, michael@0: void* aData) michael@0: { michael@0: nsCSSScanner scanner(aPropertyValue, 0); michael@0: css::ErrorReporter reporter(scanner, nullptr, nullptr, nullptr); michael@0: InitScanner(scanner, reporter, nullptr, nullptr, nullptr); michael@0: nsAutoSuppressErrors suppressErrors(this); michael@0: michael@0: CSSVariableDeclarations::Type type; michael@0: bool dropBackslash; michael@0: nsString impliedCharacters; michael@0: bool result = ParseValueWithVariables(&type, &dropBackslash, michael@0: impliedCharacters, aFunc, aData) && michael@0: !GetToken(true); michael@0: michael@0: ReleaseScanner(); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: static bool michael@0: SeparatorRequiredBetweenTokens(nsCSSTokenSerializationType aToken1, michael@0: nsCSSTokenSerializationType aToken2) michael@0: { michael@0: // The two lines marked with (*) do not correspond to entries in michael@0: // the table in the css-syntax spec but which we need to handle, michael@0: // as we treat them as whole tokens. michael@0: switch (aToken1) { michael@0: case eCSSTokenSerialization_Ident: michael@0: return aToken2 == eCSSTokenSerialization_Ident || michael@0: aToken2 == eCSSTokenSerialization_Function || michael@0: aToken2 == eCSSTokenSerialization_URL_or_BadURL || michael@0: aToken2 == eCSSTokenSerialization_Symbol_Minus || michael@0: aToken2 == eCSSTokenSerialization_Number || michael@0: aToken2 == eCSSTokenSerialization_Percentage || michael@0: aToken2 == eCSSTokenSerialization_Dimension || michael@0: aToken2 == eCSSTokenSerialization_URange || michael@0: aToken2 == eCSSTokenSerialization_CDC || michael@0: aToken2 == eCSSTokenSerialization_Symbol_OpenParen; michael@0: case eCSSTokenSerialization_AtKeyword_or_Hash: michael@0: case eCSSTokenSerialization_Dimension: michael@0: return aToken2 == eCSSTokenSerialization_Ident || michael@0: aToken2 == eCSSTokenSerialization_Function || michael@0: aToken2 == eCSSTokenSerialization_URL_or_BadURL || michael@0: aToken2 == eCSSTokenSerialization_Symbol_Minus || michael@0: aToken2 == eCSSTokenSerialization_Number || michael@0: aToken2 == eCSSTokenSerialization_Percentage || michael@0: aToken2 == eCSSTokenSerialization_Dimension || michael@0: aToken2 == eCSSTokenSerialization_URange || michael@0: aToken2 == eCSSTokenSerialization_CDC; michael@0: case eCSSTokenSerialization_Symbol_Hash: michael@0: return aToken2 == eCSSTokenSerialization_Ident || michael@0: aToken2 == eCSSTokenSerialization_Function || michael@0: aToken2 == eCSSTokenSerialization_URL_or_BadURL || michael@0: aToken2 == eCSSTokenSerialization_Symbol_Minus || michael@0: aToken2 == eCSSTokenSerialization_Number || michael@0: aToken2 == eCSSTokenSerialization_Percentage || michael@0: aToken2 == eCSSTokenSerialization_Dimension || michael@0: aToken2 == eCSSTokenSerialization_URange; michael@0: case eCSSTokenSerialization_Symbol_Minus: michael@0: case eCSSTokenSerialization_Number: michael@0: return aToken2 == eCSSTokenSerialization_Ident || michael@0: aToken2 == eCSSTokenSerialization_Function || michael@0: aToken2 == eCSSTokenSerialization_URL_or_BadURL || michael@0: aToken2 == eCSSTokenSerialization_Number || michael@0: aToken2 == eCSSTokenSerialization_Percentage || michael@0: aToken2 == eCSSTokenSerialization_Dimension || michael@0: aToken2 == eCSSTokenSerialization_URange; michael@0: case eCSSTokenSerialization_Symbol_At: michael@0: return aToken2 == eCSSTokenSerialization_Ident || michael@0: aToken2 == eCSSTokenSerialization_Function || michael@0: aToken2 == eCSSTokenSerialization_URL_or_BadURL || michael@0: aToken2 == eCSSTokenSerialization_Symbol_Minus || michael@0: aToken2 == eCSSTokenSerialization_URange; michael@0: case eCSSTokenSerialization_URange: michael@0: return aToken2 == eCSSTokenSerialization_Ident || michael@0: aToken2 == eCSSTokenSerialization_Function || michael@0: aToken2 == eCSSTokenSerialization_Number || michael@0: aToken2 == eCSSTokenSerialization_Percentage || michael@0: aToken2 == eCSSTokenSerialization_Dimension || michael@0: aToken2 == eCSSTokenSerialization_Symbol_Question; michael@0: case eCSSTokenSerialization_Symbol_Dot_or_Plus: michael@0: return aToken2 == eCSSTokenSerialization_Number || michael@0: aToken2 == eCSSTokenSerialization_Percentage || michael@0: aToken2 == eCSSTokenSerialization_Dimension; michael@0: case eCSSTokenSerialization_Symbol_Assorted: michael@0: case eCSSTokenSerialization_Symbol_Asterisk: michael@0: return aToken2 == eCSSTokenSerialization_Symbol_Equals; michael@0: case eCSSTokenSerialization_Symbol_Bar: michael@0: return aToken2 == eCSSTokenSerialization_Symbol_Equals || michael@0: aToken2 == eCSSTokenSerialization_Symbol_Bar || michael@0: aToken2 == eCSSTokenSerialization_DashMatch; // (*) michael@0: case eCSSTokenSerialization_Symbol_Slash: michael@0: return aToken2 == eCSSTokenSerialization_Symbol_Asterisk || michael@0: aToken2 == eCSSTokenSerialization_ContainsMatch; // (*) michael@0: default: michael@0: MOZ_ASSERT(aToken1 == eCSSTokenSerialization_Nothing || michael@0: aToken1 == eCSSTokenSerialization_Whitespace || michael@0: aToken1 == eCSSTokenSerialization_Percentage || michael@0: aToken1 == eCSSTokenSerialization_URL_or_BadURL || michael@0: aToken1 == eCSSTokenSerialization_Function || michael@0: aToken1 == eCSSTokenSerialization_CDC || michael@0: aToken1 == eCSSTokenSerialization_Symbol_OpenParen || michael@0: aToken1 == eCSSTokenSerialization_Symbol_Question || michael@0: aToken1 == eCSSTokenSerialization_Symbol_Assorted || michael@0: aToken1 == eCSSTokenSerialization_Symbol_Asterisk || michael@0: aToken1 == eCSSTokenSerialization_Symbol_Equals || michael@0: aToken1 == eCSSTokenSerialization_Symbol_Bar || michael@0: aToken1 == eCSSTokenSerialization_Symbol_Slash || michael@0: aToken1 == eCSSTokenSerialization_Other || michael@0: "unexpected nsCSSTokenSerializationType value"); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Appends aValue to aResult, possibly inserting an empty CSS michael@0: * comment between the two to ensure that tokens from both strings michael@0: * remain separated. michael@0: */ michael@0: static void michael@0: AppendTokens(nsAString& aResult, michael@0: nsCSSTokenSerializationType& aResultFirstToken, michael@0: nsCSSTokenSerializationType& aResultLastToken, michael@0: nsCSSTokenSerializationType aValueFirstToken, michael@0: nsCSSTokenSerializationType aValueLastToken, michael@0: const nsAString& aValue) michael@0: { michael@0: if (SeparatorRequiredBetweenTokens(aResultLastToken, aValueFirstToken)) { michael@0: aResult.AppendLiteral("/**/"); michael@0: } michael@0: aResult.Append(aValue); michael@0: if (aResultFirstToken == eCSSTokenSerialization_Nothing) { michael@0: aResultFirstToken = aValueFirstToken; michael@0: } michael@0: if (aValueLastToken != eCSSTokenSerialization_Nothing) { michael@0: aResultLastToken = aValueLastToken; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Stops the given scanner recording, and appends the recorded result michael@0: * to aResult, possibly inserting an empty CSS comment between the two to michael@0: * ensure that tokens from both strings remain separated. michael@0: */ michael@0: static void michael@0: StopRecordingAndAppendTokens(nsString& aResult, michael@0: nsCSSTokenSerializationType& aResultFirstToken, michael@0: nsCSSTokenSerializationType& aResultLastToken, michael@0: nsCSSTokenSerializationType aValueFirstToken, michael@0: nsCSSTokenSerializationType aValueLastToken, michael@0: nsCSSScanner* aScanner) michael@0: { michael@0: if (SeparatorRequiredBetweenTokens(aResultLastToken, aValueFirstToken)) { michael@0: aResult.AppendLiteral("/**/"); michael@0: } michael@0: aScanner->StopRecording(aResult); michael@0: if (aResultFirstToken == eCSSTokenSerialization_Nothing) { michael@0: aResultFirstToken = aValueFirstToken; michael@0: } michael@0: if (aValueLastToken != eCSSTokenSerialization_Nothing) { michael@0: aResultLastToken = aValueLastToken; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ResolveValueWithVariableReferencesRec( michael@0: nsString& aResult, michael@0: nsCSSTokenSerializationType& aResultFirstToken, michael@0: nsCSSTokenSerializationType& aResultLastToken, michael@0: const CSSVariableValues* aVariables) michael@0: { michael@0: // This function assumes we are already recording, and will leave the scanner michael@0: // recording when it returns. michael@0: MOZ_ASSERT(mScanner->IsRecording()); michael@0: MOZ_ASSERT(aResult.IsEmpty()); michael@0: michael@0: // Stack of closing characters for currently open constructs. michael@0: nsAutoTArray stack; michael@0: michael@0: // The resolved value for this ResolveValueWithVariableReferencesRec call. michael@0: nsString value; michael@0: michael@0: // The length of the scanner's recording before the currently parsed token. michael@0: // This is used so that when we encounter a "var(" token, we can strip michael@0: // it off the end of the recording, regardless of how long the token was. michael@0: // (With escapes, it could be longer than four characters.) michael@0: uint32_t lengthBeforeVar = 0; michael@0: michael@0: // Tracking the type of token that appears at the start and end of |value| michael@0: // and that appears at the start and end of the scanner recording. These are michael@0: // used to determine whether we need to insert "/**/" when pasting token michael@0: // streams together. michael@0: nsCSSTokenSerializationType valueFirstToken = eCSSTokenSerialization_Nothing, michael@0: valueLastToken = eCSSTokenSerialization_Nothing, michael@0: recFirstToken = eCSSTokenSerialization_Nothing, michael@0: recLastToken = eCSSTokenSerialization_Nothing; michael@0: michael@0: #define UPDATE_RECORDING_TOKENS(type) \ michael@0: if (recFirstToken == eCSSTokenSerialization_Nothing) { \ michael@0: recFirstToken = type; \ michael@0: } \ michael@0: recLastToken = type; michael@0: michael@0: while (GetToken(false)) { michael@0: switch (mToken.mType) { michael@0: case eCSSToken_Symbol: { michael@0: nsCSSTokenSerializationType type = eCSSTokenSerialization_Other; michael@0: if (mToken.mSymbol == '(') { michael@0: stack.AppendElement(')'); michael@0: type = eCSSTokenSerialization_Symbol_OpenParen; michael@0: } else if (mToken.mSymbol == '[') { michael@0: stack.AppendElement(']'); michael@0: } else if (mToken.mSymbol == '{') { michael@0: stack.AppendElement('}'); michael@0: } else if (mToken.mSymbol == ';') { michael@0: if (stack.IsEmpty()) { michael@0: // A ';' that is at the top level of the value or at the top level michael@0: // of a variable reference's fallback is invalid. michael@0: return false; michael@0: } michael@0: } else if (mToken.mSymbol == '!') { michael@0: if (stack.IsEmpty()) { michael@0: // An '!' that is at the top level of the value or at the top level michael@0: // of a variable reference's fallback is invalid. michael@0: return false; michael@0: } michael@0: } else if (mToken.mSymbol == ')' && michael@0: stack.IsEmpty()) { michael@0: // We're closing a "var(". michael@0: nsString finalTokens; michael@0: mScanner->StopRecording(finalTokens); michael@0: MOZ_ASSERT(finalTokens[finalTokens.Length() - 1] == ')'); michael@0: finalTokens.Truncate(finalTokens.Length() - 1); michael@0: aResult.Append(value); michael@0: michael@0: AppendTokens(aResult, valueFirstToken, valueLastToken, michael@0: recFirstToken, recLastToken, finalTokens); michael@0: michael@0: mScanner->StartRecording(); michael@0: UngetToken(); michael@0: aResultFirstToken = valueFirstToken; michael@0: aResultLastToken = valueLastToken; michael@0: return true; michael@0: } else if (mToken.mSymbol == ')' || michael@0: mToken.mSymbol == ']' || michael@0: mToken.mSymbol == '}') { michael@0: if (stack.IsEmpty() || michael@0: stack.LastElement() != mToken.mSymbol) { michael@0: // A mismatched closing bracket is invalid. michael@0: return false; michael@0: } michael@0: stack.TruncateLength(stack.Length() - 1); michael@0: } else if (mToken.mSymbol == '#') { michael@0: type = eCSSTokenSerialization_Symbol_Hash; michael@0: } else if (mToken.mSymbol == '@') { michael@0: type = eCSSTokenSerialization_Symbol_At; michael@0: } else if (mToken.mSymbol == '.' || michael@0: mToken.mSymbol == '+') { michael@0: type = eCSSTokenSerialization_Symbol_Dot_or_Plus; michael@0: } else if (mToken.mSymbol == '-') { michael@0: type = eCSSTokenSerialization_Symbol_Minus; michael@0: } else if (mToken.mSymbol == '?') { michael@0: type = eCSSTokenSerialization_Symbol_Question; michael@0: } else if (mToken.mSymbol == '$' || michael@0: mToken.mSymbol == '^' || michael@0: mToken.mSymbol == '~') { michael@0: type = eCSSTokenSerialization_Symbol_Assorted; michael@0: } else if (mToken.mSymbol == '=') { michael@0: type = eCSSTokenSerialization_Symbol_Equals; michael@0: } else if (mToken.mSymbol == '|') { michael@0: type = eCSSTokenSerialization_Symbol_Bar; michael@0: } else if (mToken.mSymbol == '/') { michael@0: type = eCSSTokenSerialization_Symbol_Slash; michael@0: } else if (mToken.mSymbol == '*') { michael@0: type = eCSSTokenSerialization_Symbol_Asterisk; michael@0: } michael@0: UPDATE_RECORDING_TOKENS(type); michael@0: break; michael@0: } michael@0: michael@0: case eCSSToken_Function: michael@0: if (mToken.mIdent.LowerCaseEqualsLiteral("var")) { michael@0: // Save the tokens before the "var(" to our resolved value. michael@0: nsString recording; michael@0: mScanner->StopRecording(recording); michael@0: recording.Truncate(lengthBeforeVar); michael@0: AppendTokens(value, valueFirstToken, valueLastToken, michael@0: recFirstToken, recLastToken, recording); michael@0: recFirstToken = eCSSTokenSerialization_Nothing; michael@0: recLastToken = eCSSTokenSerialization_Nothing; michael@0: michael@0: if (!GetToken(true) || michael@0: mToken.mType != eCSSToken_Ident || michael@0: !nsCSSProps::IsCustomPropertyName(mToken.mIdent)) { michael@0: // "var(" must be followed by an identifier, and it must be a michael@0: // custom property name. michael@0: return false; michael@0: } michael@0: michael@0: // Turn the custom property name into a variable name by removing the michael@0: // '--' prefix. michael@0: MOZ_ASSERT(Substring(mToken.mIdent, 0, michael@0: CSS_CUSTOM_NAME_PREFIX_LENGTH). michael@0: EqualsLiteral("--")); michael@0: nsDependentString variableName(mToken.mIdent, michael@0: CSS_CUSTOM_NAME_PREFIX_LENGTH); michael@0: michael@0: // Get the value of the identified variable. Note that we michael@0: // check if the variable value is the empty string, as that means michael@0: // that the variable was invalid at computed value time due to michael@0: // unresolveable variable references or cycles. michael@0: nsString variableValue; michael@0: nsCSSTokenSerializationType varFirstToken, varLastToken; michael@0: bool valid = aVariables->Get(variableName, variableValue, michael@0: varFirstToken, varLastToken) && michael@0: !variableValue.IsEmpty(); michael@0: michael@0: if (!GetToken(true) || michael@0: mToken.IsSymbol(')')) { michael@0: mScanner->StartRecording(); michael@0: if (!valid) { michael@0: // Invalid variable with no fallback. michael@0: return false; michael@0: } michael@0: // Valid variable with no fallback. michael@0: AppendTokens(value, valueFirstToken, valueLastToken, michael@0: varFirstToken, varLastToken, variableValue); michael@0: } else if (mToken.IsSymbol(',')) { michael@0: mScanner->StartRecording(); michael@0: if (!GetToken(false) || michael@0: mToken.IsSymbol(')')) { michael@0: // Comma must be followed by at least one fallback token. michael@0: return false; michael@0: } michael@0: UngetToken(); michael@0: if (valid) { michael@0: // Valid variable with ignored fallback. michael@0: mScanner->StopRecording(); michael@0: AppendTokens(value, valueFirstToken, valueLastToken, michael@0: varFirstToken, varLastToken, variableValue); michael@0: bool ok = SkipBalancedContentUntil(')'); michael@0: mScanner->StartRecording(); michael@0: if (!ok) { michael@0: return false; michael@0: } michael@0: } else { michael@0: nsString fallback; michael@0: if (!ResolveValueWithVariableReferencesRec(fallback, michael@0: varFirstToken, michael@0: varLastToken, michael@0: aVariables)) { michael@0: // Fallback value had invalid tokens or an invalid variable reference michael@0: // that itself had no fallback. michael@0: return false; michael@0: } michael@0: AppendTokens(value, valueFirstToken, valueLastToken, michael@0: varFirstToken, varLastToken, fallback); michael@0: // Now we're either at the pushed back ')' that finished the michael@0: // fallback or at EOF. michael@0: DebugOnly gotToken = GetToken(false); michael@0: MOZ_ASSERT(!gotToken || mToken.IsSymbol(')')); michael@0: } michael@0: } else { michael@0: // Expected ',' or ')' after the variable name. michael@0: mScanner->StartRecording(); michael@0: return false; michael@0: } michael@0: } else { michael@0: stack.AppendElement(')'); michael@0: UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Function); michael@0: } michael@0: break; michael@0: michael@0: case eCSSToken_Bad_String: michael@0: case eCSSToken_Bad_URL: michael@0: return false; michael@0: michael@0: case eCSSToken_Whitespace: michael@0: UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Whitespace); michael@0: break; michael@0: michael@0: case eCSSToken_AtKeyword: michael@0: case eCSSToken_Hash: michael@0: UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_AtKeyword_or_Hash); michael@0: break; michael@0: michael@0: case eCSSToken_Number: michael@0: UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Number); michael@0: break; michael@0: michael@0: case eCSSToken_Dimension: michael@0: UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Dimension); michael@0: break; michael@0: michael@0: case eCSSToken_Ident: michael@0: UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Ident); michael@0: break; michael@0: michael@0: case eCSSToken_Percentage: michael@0: UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Percentage); michael@0: break; michael@0: michael@0: case eCSSToken_URange: michael@0: UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_URange); michael@0: break; michael@0: michael@0: case eCSSToken_URL: michael@0: UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_URL_or_BadURL); michael@0: break; michael@0: michael@0: case eCSSToken_HTMLComment: michael@0: if (mToken.mIdent[0] == '-') { michael@0: UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_CDC); michael@0: } else { michael@0: UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Other); michael@0: } michael@0: break; michael@0: michael@0: case eCSSToken_Dashmatch: michael@0: UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_DashMatch); michael@0: break; michael@0: michael@0: case eCSSToken_Containsmatch: michael@0: UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_ContainsMatch); michael@0: break; michael@0: michael@0: default: michael@0: NS_NOTREACHED("unexpected token type"); michael@0: // fall through michael@0: case eCSSToken_ID: michael@0: case eCSSToken_String: michael@0: case eCSSToken_Includes: michael@0: case eCSSToken_Beginsmatch: michael@0: case eCSSToken_Endsmatch: michael@0: UPDATE_RECORDING_TOKENS(eCSSTokenSerialization_Other); michael@0: break; michael@0: } michael@0: michael@0: lengthBeforeVar = mScanner->RecordingLength(); michael@0: } michael@0: michael@0: #undef UPDATE_RECORDING_TOKENS michael@0: michael@0: aResult.Append(value); michael@0: StopRecordingAndAppendTokens(aResult, valueFirstToken, valueLastToken, michael@0: recFirstToken, recLastToken, mScanner); michael@0: michael@0: // Append any implicitly closed brackets. michael@0: if (!stack.IsEmpty()) { michael@0: do { michael@0: aResult.Append(stack.LastElement()); michael@0: stack.TruncateLength(stack.Length() - 1); michael@0: } while (!stack.IsEmpty()); michael@0: valueLastToken = eCSSTokenSerialization_Other; michael@0: } michael@0: michael@0: mScanner->StartRecording(); michael@0: aResultFirstToken = valueFirstToken; michael@0: aResultLastToken = valueLastToken; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ResolveValueWithVariableReferences( michael@0: const CSSVariableValues* aVariables, michael@0: nsString& aResult, michael@0: nsCSSTokenSerializationType& aFirstToken, michael@0: nsCSSTokenSerializationType& aLastToken) michael@0: { michael@0: aResult.Truncate(0); michael@0: michael@0: // Start recording before we read the first token. michael@0: mScanner->StartRecording(); michael@0: michael@0: if (!GetToken(false)) { michael@0: // Value was empty since we reached EOF. michael@0: mScanner->StopRecording(); michael@0: return false; michael@0: } michael@0: michael@0: UngetToken(); michael@0: michael@0: nsString value; michael@0: nsCSSTokenSerializationType firstToken, lastToken; michael@0: bool ok = ResolveValueWithVariableReferencesRec(value, firstToken, lastToken, aVariables) && michael@0: !GetToken(true); michael@0: michael@0: mScanner->StopRecording(); michael@0: michael@0: if (ok) { michael@0: aResult = value; michael@0: aFirstToken = firstToken; michael@0: aLastToken = lastToken; michael@0: } michael@0: return ok; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ResolveVariableValue(const nsAString& aPropertyValue, michael@0: const CSSVariableValues* aVariables, michael@0: nsString& aResult, michael@0: nsCSSTokenSerializationType& aFirstToken, michael@0: nsCSSTokenSerializationType& aLastToken) michael@0: { michael@0: nsCSSScanner scanner(aPropertyValue, 0); michael@0: michael@0: // At this point, we know that aPropertyValue is syntactically correct michael@0: // for a token stream that has variable references. We also won't be michael@0: // interpreting any of the stream as we parse it, apart from expanding michael@0: // var() references, so we don't need a base URL etc. or any useful michael@0: // error reporting. michael@0: css::ErrorReporter reporter(scanner, nullptr, nullptr, nullptr); michael@0: InitScanner(scanner, reporter, nullptr, nullptr, nullptr); michael@0: michael@0: bool valid = ResolveValueWithVariableReferences(aVariables, aResult, michael@0: aFirstToken, aLastToken); michael@0: michael@0: ReleaseScanner(); michael@0: return valid; michael@0: } michael@0: michael@0: void michael@0: CSSParserImpl::ParsePropertyWithVariableReferences( michael@0: nsCSSProperty aPropertyID, michael@0: nsCSSProperty aShorthandPropertyID, michael@0: const nsAString& aValue, michael@0: const CSSVariableValues* aVariables, michael@0: nsRuleData* aRuleData, michael@0: nsIURI* aDocURL, michael@0: nsIURI* aBaseURL, michael@0: nsIPrincipal* aDocPrincipal, michael@0: nsCSSStyleSheet* aSheet, michael@0: uint32_t aLineNumber, michael@0: uint32_t aLineOffset) michael@0: { michael@0: mTempData.AssertInitialState(); michael@0: michael@0: bool valid; michael@0: nsString expandedValue; michael@0: michael@0: // Resolve any variable references in the property value. michael@0: { michael@0: nsCSSScanner scanner(aValue, 0); michael@0: css::ErrorReporter reporter(scanner, aSheet, mChildLoader, aDocURL); michael@0: InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal); michael@0: michael@0: nsCSSTokenSerializationType firstToken, lastToken; michael@0: valid = ResolveValueWithVariableReferences(aVariables, expandedValue, michael@0: firstToken, lastToken); michael@0: if (!valid) { michael@0: NS_ConvertASCIItoUTF16 propName(nsCSSProps::GetStringValue(aPropertyID)); michael@0: REPORT_UNEXPECTED(PEInvalidVariableReference); michael@0: REPORT_UNEXPECTED_P(PEValueParsingError, propName); michael@0: if (nsCSSProps::IsInherited(aPropertyID)) { michael@0: REPORT_UNEXPECTED(PEValueWithVariablesFallbackInherit); michael@0: } else { michael@0: REPORT_UNEXPECTED(PEValueWithVariablesFallbackInitial); michael@0: } michael@0: OUTPUT_ERROR_WITH_POSITION(aLineNumber, aLineOffset); michael@0: } michael@0: ReleaseScanner(); michael@0: } michael@0: michael@0: nsCSSProperty propertyToParse = michael@0: aShorthandPropertyID != eCSSProperty_UNKNOWN ? aShorthandPropertyID : michael@0: aPropertyID; michael@0: michael@0: // Parse the property with that resolved value. michael@0: if (valid) { michael@0: nsCSSScanner scanner(expandedValue, 0); michael@0: css::ErrorReporter reporter(scanner, aSheet, mChildLoader, aDocURL); michael@0: InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal); michael@0: valid = ParseProperty(propertyToParse); michael@0: if (valid && GetToken(true)) { michael@0: REPORT_UNEXPECTED_TOKEN(PEExpectEndValue); michael@0: valid = false; michael@0: } michael@0: if (!valid) { michael@0: NS_ConvertASCIItoUTF16 propName(nsCSSProps::GetStringValue( michael@0: propertyToParse)); michael@0: REPORT_UNEXPECTED_P(PEValueWithVariablesParsingError, propName); michael@0: if (nsCSSProps::IsInherited(aPropertyID)) { michael@0: REPORT_UNEXPECTED(PEValueWithVariablesFallbackInherit); michael@0: } else { michael@0: REPORT_UNEXPECTED(PEValueWithVariablesFallbackInitial); michael@0: } michael@0: OUTPUT_ERROR_WITH_POSITION(aLineNumber, aLineOffset); michael@0: } michael@0: ReleaseScanner(); michael@0: } michael@0: michael@0: // If the property could not be parsed with the resolved value, then we michael@0: // treat it as if the value were 'initial' or 'inherit', depending on whether michael@0: // the property is an inherited property. michael@0: if (!valid) { michael@0: nsCSSValue defaultValue; michael@0: if (nsCSSProps::IsInherited(aPropertyID)) { michael@0: defaultValue.SetInheritValue(); michael@0: } else { michael@0: defaultValue.SetInitialValue(); michael@0: } michael@0: mTempData.AddLonghandProperty(aPropertyID, defaultValue); michael@0: } michael@0: michael@0: // Copy the property value into the rule data. michael@0: mTempData.MapRuleInfoInto(aPropertyID, aRuleData); michael@0: michael@0: mTempData.ClearProperty(propertyToParse); michael@0: mTempData.AssertInitialState(); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: bool michael@0: CSSParserImpl::GetToken(bool aSkipWS) michael@0: { michael@0: if (mHavePushBack) { michael@0: mHavePushBack = false; michael@0: if (!aSkipWS || mToken.mType != eCSSToken_Whitespace) { michael@0: return true; michael@0: } michael@0: } michael@0: return mScanner->Next(mToken, aSkipWS); michael@0: } michael@0: michael@0: void michael@0: CSSParserImpl::UngetToken() michael@0: { michael@0: NS_PRECONDITION(!mHavePushBack, "double pushback"); michael@0: mHavePushBack = true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::GetNextTokenLocation(bool aSkipWS, uint32_t *linenum, uint32_t *colnum) michael@0: { michael@0: // Peek at next token so that mScanner updates line and column vals michael@0: if (!GetToken(aSkipWS)) { michael@0: return false; michael@0: } michael@0: UngetToken(); michael@0: // The scanner uses one-indexing for line numbers but zero-indexing michael@0: // for column numbers. michael@0: *linenum = mScanner->GetLineNumber(); michael@0: *colnum = 1 + mScanner->GetColumnNumber(); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ExpectSymbol(char16_t aSymbol, michael@0: bool aSkipWS) michael@0: { michael@0: if (!GetToken(aSkipWS)) { michael@0: // CSS2.1 specifies that all "open constructs" are to be closed at michael@0: // EOF. It simplifies higher layers if we claim to have found an michael@0: // ), ], }, or ; if we encounter EOF while looking for one of them. michael@0: // Do still issue a diagnostic, to aid debugging. michael@0: if (aSymbol == ')' || aSymbol == ']' || michael@0: aSymbol == '}' || aSymbol == ';') { michael@0: REPORT_UNEXPECTED_EOF_CHAR(aSymbol); michael@0: return true; michael@0: } michael@0: else michael@0: return false; michael@0: } michael@0: if (mToken.IsSymbol(aSymbol)) { michael@0: return true; michael@0: } michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: michael@0: // Checks to see if we're at the end of a property. If an error occurs during michael@0: // the check, does not signal a parse error. michael@0: bool michael@0: CSSParserImpl::CheckEndProperty() michael@0: { michael@0: if (!GetToken(true)) { michael@0: return true; // properties may end with eof michael@0: } michael@0: if ((eCSSToken_Symbol == mToken.mType) && michael@0: ((';' == mToken.mSymbol) || michael@0: ('!' == mToken.mSymbol) || michael@0: ('}' == mToken.mSymbol) || michael@0: (')' == mToken.mSymbol))) { michael@0: // XXX need to verify that ! is only followed by "important [;|}] michael@0: // XXX this requires a multi-token pushback buffer michael@0: UngetToken(); michael@0: return true; michael@0: } michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: michael@0: // Checks if we're at the end of a property, raising an error if we're not. michael@0: bool michael@0: CSSParserImpl::ExpectEndProperty() michael@0: { michael@0: if (CheckEndProperty()) michael@0: return true; michael@0: michael@0: // If we're here, we read something incorrect, so we should report it. michael@0: REPORT_UNEXPECTED_TOKEN(PEExpectEndValue); michael@0: return false; michael@0: } michael@0: michael@0: // Parses the priority suffix on a property, which at present may be michael@0: // either '!important' or nothing. michael@0: CSSParserImpl::PriorityParsingStatus michael@0: CSSParserImpl::ParsePriority() michael@0: { michael@0: if (!GetToken(true)) { michael@0: return ePriority_None; // properties may end with EOF michael@0: } michael@0: if (!mToken.IsSymbol('!')) { michael@0: UngetToken(); michael@0: return ePriority_None; // dunno what it is, but it's not a priority michael@0: } michael@0: michael@0: if (!GetToken(true)) { michael@0: // EOF is not ok after ! michael@0: REPORT_UNEXPECTED_EOF(PEImportantEOF); michael@0: return ePriority_Error; michael@0: } michael@0: michael@0: if (mToken.mType != eCSSToken_Ident || michael@0: !mToken.mIdent.LowerCaseEqualsLiteral("important")) { michael@0: REPORT_UNEXPECTED_TOKEN(PEExpectedImportant); michael@0: UngetToken(); michael@0: return ePriority_Error; michael@0: } michael@0: michael@0: return ePriority_Important; michael@0: } michael@0: michael@0: nsSubstring* michael@0: CSSParserImpl::NextIdent() michael@0: { michael@0: // XXX Error reporting? michael@0: if (!GetToken(true)) { michael@0: return nullptr; michael@0: } michael@0: if (eCSSToken_Ident != mToken.mType) { michael@0: UngetToken(); michael@0: return nullptr; michael@0: } michael@0: return &mToken.mIdent; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::SkipAtRule(bool aInsideBlock) michael@0: { michael@0: for (;;) { michael@0: if (!GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PESkipAtRuleEOF2); michael@0: return false; michael@0: } michael@0: if (eCSSToken_Symbol == mToken.mType) { michael@0: char16_t symbol = mToken.mSymbol; michael@0: if (symbol == ';') { michael@0: break; michael@0: } michael@0: if (aInsideBlock && symbol == '}') { michael@0: // The closing } doesn't belong to us. michael@0: UngetToken(); michael@0: break; michael@0: } michael@0: if (symbol == '{') { michael@0: SkipUntil('}'); michael@0: break; michael@0: } else if (symbol == '(') { michael@0: SkipUntil(')'); michael@0: } else if (symbol == '[') { michael@0: SkipUntil(']'); michael@0: } michael@0: } else if (eCSSToken_Function == mToken.mType || michael@0: eCSSToken_Bad_URL == mToken.mType) { michael@0: SkipUntil(')'); michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseAtRule(RuleAppendFunc aAppendFunc, michael@0: void* aData, michael@0: bool aInAtRule) michael@0: { michael@0: michael@0: nsCSSSection newSection; michael@0: bool (CSSParserImpl::*parseFunc)(RuleAppendFunc, void*); michael@0: michael@0: if ((mSection <= eCSSSection_Charset) && michael@0: (mToken.mIdent.LowerCaseEqualsLiteral("charset"))) { michael@0: parseFunc = &CSSParserImpl::ParseCharsetRule; michael@0: newSection = eCSSSection_Import; // only one charset allowed michael@0: michael@0: } else if ((mSection <= eCSSSection_Import) && michael@0: mToken.mIdent.LowerCaseEqualsLiteral("import")) { michael@0: parseFunc = &CSSParserImpl::ParseImportRule; michael@0: newSection = eCSSSection_Import; michael@0: michael@0: } else if ((mSection <= eCSSSection_NameSpace) && michael@0: mToken.mIdent.LowerCaseEqualsLiteral("namespace")) { michael@0: parseFunc = &CSSParserImpl::ParseNameSpaceRule; michael@0: newSection = eCSSSection_NameSpace; michael@0: michael@0: } else if (mToken.mIdent.LowerCaseEqualsLiteral("media")) { michael@0: parseFunc = &CSSParserImpl::ParseMediaRule; michael@0: newSection = eCSSSection_General; michael@0: michael@0: } else if (mToken.mIdent.LowerCaseEqualsLiteral("-moz-document")) { michael@0: parseFunc = &CSSParserImpl::ParseMozDocumentRule; michael@0: newSection = eCSSSection_General; michael@0: michael@0: } else if (mToken.mIdent.LowerCaseEqualsLiteral("font-face")) { michael@0: parseFunc = &CSSParserImpl::ParseFontFaceRule; michael@0: newSection = eCSSSection_General; michael@0: michael@0: } else if (mToken.mIdent.LowerCaseEqualsLiteral("font-feature-values") && michael@0: nsCSSFontFeatureValuesRule::PrefEnabled()) { michael@0: parseFunc = &CSSParserImpl::ParseFontFeatureValuesRule; michael@0: newSection = eCSSSection_General; michael@0: michael@0: } else if (mToken.mIdent.LowerCaseEqualsLiteral("page")) { michael@0: parseFunc = &CSSParserImpl::ParsePageRule; michael@0: newSection = eCSSSection_General; michael@0: michael@0: } else if ((nsCSSProps::IsEnabled(eCSSPropertyAlias_MozAnimation) && michael@0: mToken.mIdent.LowerCaseEqualsLiteral("-moz-keyframes")) || michael@0: mToken.mIdent.LowerCaseEqualsLiteral("keyframes")) { michael@0: parseFunc = &CSSParserImpl::ParseKeyframesRule; michael@0: newSection = eCSSSection_General; michael@0: michael@0: } else if (mToken.mIdent.LowerCaseEqualsLiteral("supports") && michael@0: CSSSupportsRule::PrefEnabled()) { michael@0: parseFunc = &CSSParserImpl::ParseSupportsRule; michael@0: newSection = eCSSSection_General; michael@0: michael@0: } else { michael@0: if (!NonMozillaVendorIdentifier(mToken.mIdent)) { michael@0: REPORT_UNEXPECTED_TOKEN(PEUnknownAtRule); michael@0: OUTPUT_ERROR(); michael@0: } michael@0: // Skip over unsupported at rule, don't advance section michael@0: return SkipAtRule(aInAtRule); michael@0: } michael@0: michael@0: // Inside of @-rules, only the rules that can occur anywhere michael@0: // are allowed. michael@0: bool unnestable = aInAtRule && newSection != eCSSSection_General; michael@0: if (unnestable) { michael@0: REPORT_UNEXPECTED_TOKEN(PEGroupRuleNestedAtRule); michael@0: } michael@0: michael@0: if (unnestable || !(this->*parseFunc)(aAppendFunc, aData)) { michael@0: // Skip over invalid at rule, don't advance section michael@0: OUTPUT_ERROR(); michael@0: return SkipAtRule(aInAtRule); michael@0: } michael@0: michael@0: // Nested @-rules don't affect the top-level rule chain requirement michael@0: if (!aInAtRule) { michael@0: mSection = newSection; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseCharsetRule(RuleAppendFunc aAppendFunc, michael@0: void* aData) michael@0: { michael@0: if (!GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PECharsetRuleEOF); michael@0: return false; michael@0: } michael@0: michael@0: if (eCSSToken_String != mToken.mType) { michael@0: UngetToken(); michael@0: REPORT_UNEXPECTED_TOKEN(PECharsetRuleNotString); michael@0: return false; michael@0: } michael@0: michael@0: nsAutoString charset = mToken.mIdent; michael@0: michael@0: if (!ExpectSymbol(';', true)) { michael@0: return false; michael@0: } michael@0: michael@0: nsRefPtr rule = new css::CharsetRule(charset); michael@0: (*aAppendFunc)(rule, aData); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseURLOrString(nsString& aURL) michael@0: { michael@0: if (!GetToken(true)) { michael@0: return false; michael@0: } michael@0: if (eCSSToken_String == mToken.mType || eCSSToken_URL == mToken.mType) { michael@0: aURL = mToken.mIdent; michael@0: return true; michael@0: } michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseMediaQuery(bool aInAtRule, michael@0: nsMediaQuery **aQuery, michael@0: bool *aHitStop) michael@0: { michael@0: *aQuery = nullptr; michael@0: *aHitStop = false; michael@0: michael@0: // "If the comma-separated list is the empty list it is assumed to michael@0: // specify the media query 'all'." (css3-mediaqueries, section michael@0: // "Media Queries") michael@0: if (!GetToken(true)) { michael@0: *aHitStop = true; michael@0: // expected termination by EOF michael@0: if (!aInAtRule) michael@0: return true; michael@0: michael@0: // unexpected termination by EOF michael@0: REPORT_UNEXPECTED_EOF(PEGatherMediaEOF); michael@0: return true; michael@0: } michael@0: michael@0: if (eCSSToken_Symbol == mToken.mType && aInAtRule && michael@0: (mToken.mSymbol == ';' || mToken.mSymbol == '{' || mToken.mSymbol == '}' )) { michael@0: *aHitStop = true; michael@0: UngetToken(); michael@0: return true; michael@0: } michael@0: UngetToken(); michael@0: michael@0: nsMediaQuery* query = new nsMediaQuery; michael@0: *aQuery = query; michael@0: michael@0: if (ExpectSymbol('(', true)) { michael@0: // we got an expression without a media type michael@0: UngetToken(); // so ParseMediaQueryExpression can handle it michael@0: query->SetType(nsGkAtoms::all); michael@0: query->SetTypeOmitted(); michael@0: // Just parse the first expression here. michael@0: if (!ParseMediaQueryExpression(query)) { michael@0: OUTPUT_ERROR(); michael@0: query->SetHadUnknownExpression(); michael@0: } michael@0: } else { michael@0: nsCOMPtr mediaType; michael@0: bool gotNotOrOnly = false; michael@0: for (;;) { michael@0: if (!GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PEGatherMediaEOF); michael@0: return false; michael@0: } michael@0: if (eCSSToken_Ident != mToken.mType) { michael@0: REPORT_UNEXPECTED_TOKEN(PEGatherMediaNotIdent); michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: // case insensitive from CSS - must be lower cased michael@0: nsContentUtils::ASCIIToLower(mToken.mIdent); michael@0: mediaType = do_GetAtom(mToken.mIdent); michael@0: if (!mediaType) { michael@0: NS_RUNTIMEABORT("do_GetAtom failed - out of memory?"); michael@0: } michael@0: if (!gotNotOrOnly && mediaType == nsGkAtoms::_not) { michael@0: gotNotOrOnly = true; michael@0: query->SetNegated(); michael@0: } else if (!gotNotOrOnly && mediaType == nsGkAtoms::only) { michael@0: gotNotOrOnly = true; michael@0: query->SetHasOnly(); michael@0: } else if (mediaType == nsGkAtoms::_not || michael@0: mediaType == nsGkAtoms::only || michael@0: mediaType == nsGkAtoms::_and || michael@0: mediaType == nsGkAtoms::_or) { michael@0: REPORT_UNEXPECTED_TOKEN(PEGatherMediaReservedMediaType); michael@0: UngetToken(); michael@0: return false; michael@0: } else { michael@0: // valid media type michael@0: break; michael@0: } michael@0: } michael@0: query->SetType(mediaType); michael@0: } michael@0: michael@0: for (;;) { michael@0: if (!GetToken(true)) { michael@0: *aHitStop = true; michael@0: // expected termination by EOF michael@0: if (!aInAtRule) michael@0: break; michael@0: michael@0: // unexpected termination by EOF michael@0: REPORT_UNEXPECTED_EOF(PEGatherMediaEOF); michael@0: break; michael@0: } michael@0: michael@0: if (eCSSToken_Symbol == mToken.mType && aInAtRule && michael@0: (mToken.mSymbol == ';' || mToken.mSymbol == '{' || mToken.mSymbol == '}')) { michael@0: *aHitStop = true; michael@0: UngetToken(); michael@0: break; michael@0: } michael@0: if (eCSSToken_Symbol == mToken.mType && mToken.mSymbol == ',') { michael@0: // Done with the expressions for this query michael@0: break; michael@0: } michael@0: if (eCSSToken_Ident != mToken.mType || michael@0: !mToken.mIdent.LowerCaseEqualsLiteral("and")) { michael@0: REPORT_UNEXPECTED_TOKEN(PEGatherMediaNotComma); michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: if (!ParseMediaQueryExpression(query)) { michael@0: OUTPUT_ERROR(); michael@0: query->SetHadUnknownExpression(); michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: // Returns false only when there is a low-level error in the scanner michael@0: // (out-of-memory). michael@0: bool michael@0: CSSParserImpl::GatherMedia(nsMediaList* aMedia, michael@0: bool aInAtRule) michael@0: { michael@0: for (;;) { michael@0: nsAutoPtr query; michael@0: bool hitStop; michael@0: if (!ParseMediaQuery(aInAtRule, getter_Transfers(query), michael@0: &hitStop)) { michael@0: NS_ASSERTION(!hitStop, "should return true when hit stop"); michael@0: OUTPUT_ERROR(); michael@0: if (query) { michael@0: query->SetHadUnknownExpression(); michael@0: } michael@0: if (aInAtRule) { michael@0: const char16_t stopChars[] = michael@0: { char16_t(','), char16_t('{'), char16_t(';'), char16_t('}'), char16_t(0) }; michael@0: SkipUntilOneOf(stopChars); michael@0: } else { michael@0: SkipUntil(','); michael@0: } michael@0: // Rely on SkipUntilOneOf leaving mToken around as the last token read. michael@0: if (mToken.mType == eCSSToken_Symbol && aInAtRule && michael@0: (mToken.mSymbol == '{' || mToken.mSymbol == ';' || mToken.mSymbol == '}')) { michael@0: UngetToken(); michael@0: hitStop = true; michael@0: } michael@0: } michael@0: if (query) { michael@0: aMedia->AppendQuery(query); michael@0: } michael@0: if (hitStop) { michael@0: break; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseMediaQueryExpression(nsMediaQuery* aQuery) michael@0: { michael@0: if (!ExpectSymbol('(', true)) { michael@0: REPORT_UNEXPECTED_TOKEN(PEMQExpectedExpressionStart); michael@0: return false; michael@0: } michael@0: if (! GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PEMQExpressionEOF); michael@0: return false; michael@0: } michael@0: if (eCSSToken_Ident != mToken.mType) { michael@0: REPORT_UNEXPECTED_TOKEN(PEMQExpectedFeatureName); michael@0: UngetToken(); michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: michael@0: nsMediaExpression *expr = aQuery->NewExpression(); michael@0: michael@0: // case insensitive from CSS - must be lower cased michael@0: nsContentUtils::ASCIIToLower(mToken.mIdent); michael@0: const char16_t *featureString; michael@0: if (StringBeginsWith(mToken.mIdent, NS_LITERAL_STRING("min-"))) { michael@0: expr->mRange = nsMediaExpression::eMin; michael@0: featureString = mToken.mIdent.get() + 4; michael@0: } else if (StringBeginsWith(mToken.mIdent, NS_LITERAL_STRING("max-"))) { michael@0: expr->mRange = nsMediaExpression::eMax; michael@0: featureString = mToken.mIdent.get() + 4; michael@0: } else { michael@0: expr->mRange = nsMediaExpression::eEqual; michael@0: featureString = mToken.mIdent.get(); michael@0: } michael@0: michael@0: nsCOMPtr mediaFeatureAtom = do_GetAtom(featureString); michael@0: if (!mediaFeatureAtom) { michael@0: NS_RUNTIMEABORT("do_GetAtom failed - out of memory?"); michael@0: } michael@0: const nsMediaFeature *feature = nsMediaFeatures::features; michael@0: for (; feature->mName; ++feature) { michael@0: if (*(feature->mName) == mediaFeatureAtom) { michael@0: break; michael@0: } michael@0: } michael@0: if (!feature->mName || michael@0: (expr->mRange != nsMediaExpression::eEqual && michael@0: feature->mRangeType != nsMediaFeature::eMinMaxAllowed)) { michael@0: REPORT_UNEXPECTED_TOKEN(PEMQExpectedFeatureName); michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: expr->mFeature = feature; michael@0: michael@0: if (!GetToken(true) || mToken.IsSymbol(')')) { michael@0: // Query expressions for any feature can be given without a value. michael@0: // However, min/max prefixes are not allowed. michael@0: if (expr->mRange != nsMediaExpression::eEqual) { michael@0: REPORT_UNEXPECTED(PEMQNoMinMaxWithoutValue); michael@0: return false; michael@0: } michael@0: expr->mValue.Reset(); michael@0: return true; michael@0: } michael@0: michael@0: if (!mToken.IsSymbol(':')) { michael@0: REPORT_UNEXPECTED_TOKEN(PEMQExpectedFeatureNameEnd); michael@0: UngetToken(); michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: michael@0: bool rv = false; michael@0: switch (feature->mValueType) { michael@0: case nsMediaFeature::eLength: michael@0: rv = ParseNonNegativeVariant(expr->mValue, VARIANT_LENGTH, nullptr); michael@0: break; michael@0: case nsMediaFeature::eInteger: michael@0: case nsMediaFeature::eBoolInteger: michael@0: rv = ParseNonNegativeVariant(expr->mValue, VARIANT_INTEGER, nullptr); michael@0: // Enforce extra restrictions for eBoolInteger michael@0: if (rv && michael@0: feature->mValueType == nsMediaFeature::eBoolInteger && michael@0: expr->mValue.GetIntValue() > 1) michael@0: rv = false; michael@0: break; michael@0: case nsMediaFeature::eFloat: michael@0: rv = ParseNonNegativeVariant(expr->mValue, VARIANT_NUMBER, nullptr); michael@0: break; michael@0: case nsMediaFeature::eIntRatio: michael@0: { michael@0: // Two integers separated by '/', with optional whitespace on michael@0: // either side of the '/'. michael@0: nsRefPtr a = nsCSSValue::Array::Create(2); michael@0: expr->mValue.SetArrayValue(a, eCSSUnit_Array); michael@0: // We don't bother with ParseNonNegativeVariant since we have to michael@0: // check for != 0 as well; no need to worry about the UngetToken michael@0: // since we're throwing out up to the next ')' anyway. michael@0: rv = ParseVariant(a->Item(0), VARIANT_INTEGER, nullptr) && michael@0: a->Item(0).GetIntValue() > 0 && michael@0: ExpectSymbol('/', true) && michael@0: ParseVariant(a->Item(1), VARIANT_INTEGER, nullptr) && michael@0: a->Item(1).GetIntValue() > 0; michael@0: } michael@0: break; michael@0: case nsMediaFeature::eResolution: michael@0: rv = GetToken(true); michael@0: if (!rv) michael@0: break; michael@0: rv = mToken.mType == eCSSToken_Dimension && mToken.mNumber > 0.0f; michael@0: if (!rv) { michael@0: UngetToken(); michael@0: break; michael@0: } michael@0: // No worries about whether unitless zero is allowed, since the michael@0: // value must be positive (and we checked that above). michael@0: NS_ASSERTION(!mToken.mIdent.IsEmpty(), "unit lied"); michael@0: if (mToken.mIdent.LowerCaseEqualsLiteral("dpi")) { michael@0: expr->mValue.SetFloatValue(mToken.mNumber, eCSSUnit_Inch); michael@0: } else if (mToken.mIdent.LowerCaseEqualsLiteral("dppx")) { michael@0: expr->mValue.SetFloatValue(mToken.mNumber, eCSSUnit_Pixel); michael@0: } else if (mToken.mIdent.LowerCaseEqualsLiteral("dpcm")) { michael@0: expr->mValue.SetFloatValue(mToken.mNumber, eCSSUnit_Centimeter); michael@0: } else { michael@0: rv = false; michael@0: } michael@0: break; michael@0: case nsMediaFeature::eEnumerated: michael@0: rv = ParseVariant(expr->mValue, VARIANT_KEYWORD, michael@0: feature->mData.mKeywordTable); michael@0: break; michael@0: case nsMediaFeature::eIdent: michael@0: rv = ParseVariant(expr->mValue, VARIANT_IDENTIFIER, nullptr); michael@0: break; michael@0: } michael@0: if (!rv || !ExpectSymbol(')', true)) { michael@0: REPORT_UNEXPECTED(PEMQExpectedFeatureValue); michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // Parse a CSS2 import rule: "@import STRING | URL [medium [, medium]]" michael@0: bool michael@0: CSSParserImpl::ParseImportRule(RuleAppendFunc aAppendFunc, void* aData) michael@0: { michael@0: nsRefPtr media = new nsMediaList(); michael@0: michael@0: nsAutoString url; michael@0: if (!ParseURLOrString(url)) { michael@0: REPORT_UNEXPECTED_TOKEN(PEImportNotURI); michael@0: return false; michael@0: } michael@0: michael@0: if (!ExpectSymbol(';', true)) { michael@0: if (!GatherMedia(media, true) || michael@0: !ExpectSymbol(';', true)) { michael@0: REPORT_UNEXPECTED_TOKEN(PEImportUnexpected); michael@0: // don't advance section, simply ignore invalid @import michael@0: return false; michael@0: } michael@0: michael@0: // Safe to assert this, since we ensured that there is something michael@0: // other than the ';' coming after the @import's url() token. michael@0: NS_ASSERTION(media->Length() != 0, "media list must be nonempty"); michael@0: } michael@0: michael@0: ProcessImport(url, media, aAppendFunc, aData); michael@0: return true; michael@0: } michael@0: michael@0: michael@0: void michael@0: CSSParserImpl::ProcessImport(const nsString& aURLSpec, michael@0: nsMediaList* aMedia, michael@0: RuleAppendFunc aAppendFunc, michael@0: void* aData) michael@0: { michael@0: nsRefPtr rule = new css::ImportRule(aMedia, aURLSpec); michael@0: (*aAppendFunc)(rule, aData); michael@0: michael@0: // Diagnose bad URIs even if we don't have a child loader. michael@0: nsCOMPtr url; michael@0: // Charset will be deduced from mBaseURI, which is more or less correct. michael@0: nsresult rv = NS_NewURI(getter_AddRefs(url), aURLSpec, nullptr, mBaseURI); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: if (rv == NS_ERROR_MALFORMED_URI) { michael@0: // import url is bad michael@0: REPORT_UNEXPECTED_P(PEImportBadURI, aURLSpec); michael@0: OUTPUT_ERROR(); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: if (mChildLoader) { michael@0: mChildLoader->LoadChildSheet(mSheet, url, aMedia, rule); michael@0: } michael@0: } michael@0: michael@0: // Parse the {} part of an @media or @-moz-document rule. michael@0: bool michael@0: CSSParserImpl::ParseGroupRule(css::GroupRule* aRule, michael@0: RuleAppendFunc aAppendFunc, michael@0: void* aData) michael@0: { michael@0: // XXXbz this could use better error reporting throughout the method michael@0: if (!ExpectSymbol('{', true)) { michael@0: return false; michael@0: } michael@0: michael@0: // push rule on stack, loop over children michael@0: PushGroup(aRule); michael@0: nsCSSSection holdSection = mSection; michael@0: mSection = eCSSSection_General; michael@0: michael@0: for (;;) { michael@0: // Get next non-whitespace token michael@0: if (! GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PEGroupRuleEOF2); michael@0: break; michael@0: } michael@0: if (mToken.IsSymbol('}')) { // done! michael@0: UngetToken(); michael@0: break; michael@0: } michael@0: if (eCSSToken_AtKeyword == mToken.mType) { michael@0: // Parse for nested rules michael@0: ParseAtRule(aAppendFunc, aData, true); michael@0: continue; michael@0: } michael@0: UngetToken(); michael@0: ParseRuleSet(AppendRuleToSheet, this, true); michael@0: } michael@0: PopGroup(); michael@0: michael@0: if (!ExpectSymbol('}', true)) { michael@0: mSection = holdSection; michael@0: return false; michael@0: } michael@0: (*aAppendFunc)(aRule, aData); michael@0: return true; michael@0: } michael@0: michael@0: // Parse a CSS2 media rule: "@media medium [, medium] { ... }" michael@0: bool michael@0: CSSParserImpl::ParseMediaRule(RuleAppendFunc aAppendFunc, void* aData) michael@0: { michael@0: nsRefPtr media = new nsMediaList(); michael@0: michael@0: if (GatherMedia(media, true)) { michael@0: // XXXbz this could use better error reporting throughout the method michael@0: nsRefPtr rule = new css::MediaRule(); michael@0: // Append first, so when we do SetMedia() the rule michael@0: // knows what its stylesheet is. michael@0: if (ParseGroupRule(rule, aAppendFunc, aData)) { michael@0: rule->SetMedia(media); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: // Parse a @-moz-document rule. This is like an @media rule, but instead michael@0: // of a medium it has a nonempty list of items where each item is either michael@0: // url(), url-prefix(), or domain(). michael@0: bool michael@0: CSSParserImpl::ParseMozDocumentRule(RuleAppendFunc aAppendFunc, void* aData) michael@0: { michael@0: css::DocumentRule::URL *urls = nullptr; michael@0: css::DocumentRule::URL **next = &urls; michael@0: do { michael@0: if (!GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PEMozDocRuleEOF); michael@0: delete urls; michael@0: return false; michael@0: } michael@0: michael@0: if (!(eCSSToken_URL == mToken.mType || michael@0: (eCSSToken_Function == mToken.mType && michael@0: (mToken.mIdent.LowerCaseEqualsLiteral("url-prefix") || michael@0: mToken.mIdent.LowerCaseEqualsLiteral("domain") || michael@0: mToken.mIdent.LowerCaseEqualsLiteral("regexp"))))) { michael@0: REPORT_UNEXPECTED_TOKEN(PEMozDocRuleBadFunc2); michael@0: UngetToken(); michael@0: delete urls; michael@0: return false; michael@0: } michael@0: css::DocumentRule::URL *cur = *next = new css::DocumentRule::URL; michael@0: next = &cur->next; michael@0: if (mToken.mType == eCSSToken_URL) { michael@0: cur->func = css::DocumentRule::eURL; michael@0: CopyUTF16toUTF8(mToken.mIdent, cur->url); michael@0: } else if (mToken.mIdent.LowerCaseEqualsLiteral("regexp")) { michael@0: // regexp() is different from url-prefix() and domain() (but michael@0: // probably the way they *should* have been* in that it requires a michael@0: // string argument, and doesn't try to behave like url(). michael@0: cur->func = css::DocumentRule::eRegExp; michael@0: GetToken(true); michael@0: // copy before we know it's valid (but before ExpectSymbol changes michael@0: // mToken.mIdent) michael@0: CopyUTF16toUTF8(mToken.mIdent, cur->url); michael@0: if (eCSSToken_String != mToken.mType || !ExpectSymbol(')', true)) { michael@0: REPORT_UNEXPECTED_TOKEN(PEMozDocRuleNotString); michael@0: SkipUntil(')'); michael@0: delete urls; michael@0: return false; michael@0: } michael@0: } else { michael@0: if (mToken.mIdent.LowerCaseEqualsLiteral("url-prefix")) { michael@0: cur->func = css::DocumentRule::eURLPrefix; michael@0: } else if (mToken.mIdent.LowerCaseEqualsLiteral("domain")) { michael@0: cur->func = css::DocumentRule::eDomain; michael@0: } michael@0: michael@0: NS_ASSERTION(!mHavePushBack, "mustn't have pushback at this point"); michael@0: if (!mScanner->NextURL(mToken) || mToken.mType != eCSSToken_URL) { michael@0: REPORT_UNEXPECTED_TOKEN(PEMozDocRuleNotURI); michael@0: SkipUntil(')'); michael@0: delete urls; michael@0: return false; michael@0: } michael@0: michael@0: // We could try to make the URL (as long as it's not domain()) michael@0: // canonical and absolute with NS_NewURI and GetSpec, but I'm michael@0: // inclined to think we shouldn't. michael@0: CopyUTF16toUTF8(mToken.mIdent, cur->url); michael@0: } michael@0: } while (ExpectSymbol(',', true)); michael@0: michael@0: nsRefPtr rule = new css::DocumentRule(); michael@0: rule->SetURLs(urls); michael@0: michael@0: return ParseGroupRule(rule, aAppendFunc, aData); michael@0: } michael@0: michael@0: // Parse a CSS3 namespace rule: "@namespace [prefix] STRING | URL;" michael@0: bool michael@0: CSSParserImpl::ParseNameSpaceRule(RuleAppendFunc aAppendFunc, void* aData) michael@0: { michael@0: if (!GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PEAtNSPrefixEOF); michael@0: return false; michael@0: } michael@0: michael@0: nsAutoString prefix; michael@0: nsAutoString url; michael@0: michael@0: if (eCSSToken_Ident == mToken.mType) { michael@0: prefix = mToken.mIdent; michael@0: // user-specified identifiers are case-sensitive (bug 416106) michael@0: } else { michael@0: UngetToken(); michael@0: } michael@0: michael@0: if (!ParseURLOrString(url) || !ExpectSymbol(';', true)) { michael@0: if (mHavePushBack) { michael@0: REPORT_UNEXPECTED_TOKEN(PEAtNSUnexpected); michael@0: } else { michael@0: REPORT_UNEXPECTED_EOF(PEAtNSURIEOF); michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: ProcessNameSpace(prefix, url, aAppendFunc, aData); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: CSSParserImpl::ProcessNameSpace(const nsString& aPrefix, michael@0: const nsString& aURLSpec, michael@0: RuleAppendFunc aAppendFunc, michael@0: void* aData) michael@0: { michael@0: nsCOMPtr prefix; michael@0: michael@0: if (!aPrefix.IsEmpty()) { michael@0: prefix = do_GetAtom(aPrefix); michael@0: if (!prefix) { michael@0: NS_RUNTIMEABORT("do_GetAtom failed - out of memory?"); michael@0: } michael@0: } michael@0: michael@0: nsRefPtr rule = new css::NameSpaceRule(prefix, aURLSpec); michael@0: (*aAppendFunc)(rule, aData); michael@0: michael@0: // If this was the first namespace rule encountered, it will trigger michael@0: // creation of a namespace map. michael@0: if (!mNameSpaceMap) { michael@0: mNameSpaceMap = mSheet->GetNameSpaceMap(); michael@0: } michael@0: } michael@0: michael@0: // font-face-rule: '@font-face' '{' font-description '}' michael@0: // font-description: font-descriptor+ michael@0: bool michael@0: CSSParserImpl::ParseFontFaceRule(RuleAppendFunc aAppendFunc, void* aData) michael@0: { michael@0: if (!ExpectSymbol('{', true)) { michael@0: REPORT_UNEXPECTED_TOKEN(PEBadFontBlockStart); michael@0: return false; michael@0: } michael@0: michael@0: nsRefPtr rule(new nsCSSFontFaceRule()); michael@0: michael@0: for (;;) { michael@0: if (!GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PEFontFaceEOF); michael@0: break; michael@0: } michael@0: if (mToken.IsSymbol('}')) { // done! michael@0: UngetToken(); michael@0: break; michael@0: } michael@0: michael@0: // ignore extra semicolons michael@0: if (mToken.IsSymbol(';')) michael@0: continue; michael@0: michael@0: if (!ParseFontDescriptor(rule)) { michael@0: REPORT_UNEXPECTED(PEDeclSkipped); michael@0: OUTPUT_ERROR(); michael@0: if (!SkipDeclaration(true)) michael@0: break; michael@0: } michael@0: } michael@0: if (!ExpectSymbol('}', true)) { michael@0: REPORT_UNEXPECTED_TOKEN(PEBadFontBlockEnd); michael@0: return false; michael@0: } michael@0: (*aAppendFunc)(rule, aData); michael@0: return true; michael@0: } michael@0: michael@0: // font-descriptor: font-family-desc michael@0: // | font-style-desc michael@0: // | font-weight-desc michael@0: // | font-stretch-desc michael@0: // | font-src-desc michael@0: // | unicode-range-desc michael@0: // michael@0: // All font-*-desc productions follow the pattern michael@0: // IDENT ':' value ';' michael@0: // michael@0: // On entry to this function, mToken is the IDENT. michael@0: michael@0: bool michael@0: CSSParserImpl::ParseFontDescriptor(nsCSSFontFaceRule* aRule) michael@0: { michael@0: if (eCSSToken_Ident != mToken.mType) { michael@0: REPORT_UNEXPECTED_TOKEN(PEFontDescExpected); michael@0: return false; michael@0: } michael@0: michael@0: nsString descName = mToken.mIdent; michael@0: if (!ExpectSymbol(':', true)) { michael@0: REPORT_UNEXPECTED_TOKEN(PEParseDeclarationNoColon); michael@0: OUTPUT_ERROR(); michael@0: return false; michael@0: } michael@0: michael@0: nsCSSFontDesc descID = nsCSSProps::LookupFontDesc(descName); michael@0: nsCSSValue value; michael@0: michael@0: if (descID == eCSSFontDesc_UNKNOWN) { michael@0: if (NonMozillaVendorIdentifier(descName)) { michael@0: // silently skip other vendors' extensions michael@0: SkipDeclaration(true); michael@0: return true; michael@0: } else { michael@0: REPORT_UNEXPECTED_P(PEUnknownFontDesc, descName); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (!ParseFontDescriptorValue(descID, value)) { michael@0: REPORT_UNEXPECTED_P(PEValueParsingError, descName); michael@0: return false; michael@0: } michael@0: michael@0: if (!ExpectEndProperty()) michael@0: return false; michael@0: michael@0: aRule->SetDesc(descID, value); michael@0: return true; michael@0: } michael@0: michael@0: // @font-feature-values # { michael@0: // @ { michael@0: // : +; michael@0: // : +; michael@0: // ... michael@0: // } michael@0: // ... michael@0: // } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseFontFeatureValuesRule(RuleAppendFunc aAppendFunc, michael@0: void* aData) michael@0: { michael@0: nsRefPtr michael@0: valuesRule(new nsCSSFontFeatureValuesRule()); michael@0: michael@0: // parse family list michael@0: nsCSSValue familyValue; michael@0: michael@0: if (!ParseFamily(familyValue) || michael@0: familyValue.GetUnit() != eCSSUnit_Families) michael@0: { michael@0: REPORT_UNEXPECTED_TOKEN(PEFFVNoFamily); michael@0: return false; michael@0: } michael@0: michael@0: // add family to rule michael@0: nsAutoString familyList; michael@0: bool hasGeneric; michael@0: familyValue.GetStringValue(familyList); michael@0: valuesRule->SetFamilyList(familyList, hasGeneric); michael@0: michael@0: // family list has generic ==> parse error michael@0: if (hasGeneric) { michael@0: REPORT_UNEXPECTED_TOKEN(PEFFVGenericInFamilyList); michael@0: return false; michael@0: } michael@0: michael@0: // open brace michael@0: if (!ExpectSymbol('{', true)) { michael@0: REPORT_UNEXPECTED_TOKEN(PEFFVBlockStart); michael@0: return false; michael@0: } michael@0: michael@0: // list of sets of feature values, each set bound to a specific michael@0: // feature-type (e.g. swash, annotation) michael@0: for (;;) { michael@0: if (!GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PEFFVUnexpectedEOF); michael@0: break; michael@0: } michael@0: if (mToken.IsSymbol('}')) { // done! michael@0: UngetToken(); michael@0: break; michael@0: } michael@0: michael@0: if (!ParseFontFeatureValueSet(valuesRule)) { michael@0: if (!SkipAtRule(false)) { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: if (!ExpectSymbol('}', true)) { michael@0: REPORT_UNEXPECTED_TOKEN(PEFFVUnexpectedBlockEnd); michael@0: SkipUntil('}'); michael@0: return false; michael@0: } michael@0: michael@0: (*aAppendFunc)(valuesRule, aData); michael@0: return true; michael@0: } michael@0: michael@0: #define NUMVALUES_NO_LIMIT 0xFFFF michael@0: michael@0: // parse a single value set containing name-value pairs for a single feature type michael@0: // @ { [ : + ; ]* } michael@0: // Ex: @swash { flowing: 1; delicate: 2; } michael@0: bool michael@0: CSSParserImpl::ParseFontFeatureValueSet(nsCSSFontFeatureValuesRule michael@0: *aFeatureValuesRule) michael@0: { michael@0: // -- @keyword (e.g. swash, styleset) michael@0: if (eCSSToken_AtKeyword != mToken.mType) { michael@0: REPORT_UNEXPECTED_TOKEN(PEFontFeatureValuesNoAt); michael@0: OUTPUT_ERROR(); michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: michael@0: // which font-specific variant of font-variant-alternates michael@0: int32_t whichVariant; michael@0: nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent); michael@0: if (keyword == eCSSKeyword_UNKNOWN || michael@0: !nsCSSProps::FindKeyword(keyword, michael@0: nsCSSProps::kFontVariantAlternatesFuncsKTable, michael@0: whichVariant)) michael@0: { michael@0: if (!NonMozillaVendorIdentifier(mToken.mIdent)) { michael@0: REPORT_UNEXPECTED_TOKEN(PEFFVUnknownFontVariantPropValue); michael@0: OUTPUT_ERROR(); michael@0: } michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: michael@0: nsAutoString featureType(mToken.mIdent); michael@0: michael@0: // open brace michael@0: if (!ExpectSymbol('{', true)) { michael@0: REPORT_UNEXPECTED_TOKEN(PEFFVValueSetStart); michael@0: return false; michael@0: } michael@0: michael@0: // styleset and character-variant can be multi-valued, otherwise single value michael@0: int32_t limitNumValues; michael@0: michael@0: switch (keyword) { michael@0: case eCSSKeyword_styleset: michael@0: limitNumValues = NUMVALUES_NO_LIMIT; michael@0: break; michael@0: case eCSSKeyword_character_variant: michael@0: limitNumValues = 2; michael@0: break; michael@0: default: michael@0: limitNumValues = 1; michael@0: break; michael@0: } michael@0: michael@0: // -- ident integer+ [, ident integer+] michael@0: nsAutoTArray values; michael@0: michael@0: // list of font-feature-values-declaration's michael@0: for (;;) { michael@0: nsAutoString valueId; michael@0: michael@0: if (!GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PEFFVUnexpectedEOF); michael@0: break; michael@0: } michael@0: michael@0: // ignore extra semicolons michael@0: if (mToken.IsSymbol(';')) { michael@0: continue; michael@0: } michael@0: michael@0: // close brace ==> done michael@0: if (mToken.IsSymbol('}')) { michael@0: break; michael@0: } michael@0: michael@0: // ident michael@0: if (eCSSToken_Ident != mToken.mType) { michael@0: REPORT_UNEXPECTED_TOKEN(PEFFVExpectedIdent); michael@0: if (!SkipDeclaration(true)) { michael@0: break; michael@0: } michael@0: continue; michael@0: } michael@0: michael@0: valueId.Assign(mToken.mIdent); michael@0: michael@0: // colon michael@0: if (!ExpectSymbol(':', true)) { michael@0: REPORT_UNEXPECTED_TOKEN(PEParseDeclarationNoColon); michael@0: OUTPUT_ERROR(); michael@0: if (!SkipDeclaration(true)) { michael@0: break; michael@0: } michael@0: continue; michael@0: } michael@0: michael@0: // value list michael@0: nsAutoTArray featureSelectors; michael@0: michael@0: nsCSSValue intValue; michael@0: while (ParseNonNegativeVariant(intValue, VARIANT_INTEGER, nullptr)) { michael@0: featureSelectors.AppendElement(uint32_t(intValue.GetIntValue())); michael@0: } michael@0: michael@0: int32_t numValues = featureSelectors.Length(); michael@0: michael@0: if (numValues == 0) { michael@0: REPORT_UNEXPECTED_TOKEN(PEFFVExpectedValue); michael@0: OUTPUT_ERROR(); michael@0: if (!SkipDeclaration(true)) { michael@0: break; michael@0: } michael@0: continue; michael@0: } michael@0: michael@0: if (numValues > limitNumValues) { michael@0: REPORT_UNEXPECTED_P(PEFFVTooManyValues, featureType); michael@0: OUTPUT_ERROR(); michael@0: if (!SkipDeclaration(true)) { michael@0: break; michael@0: } michael@0: continue; michael@0: } michael@0: michael@0: if (!GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PEFFVUnexpectedEOF); michael@0: gfxFontFeatureValueSet::ValueList v(valueId, featureSelectors); michael@0: values.AppendElement(v); michael@0: break; michael@0: } michael@0: michael@0: // ';' or '}' to end definition michael@0: if (!mToken.IsSymbol(';') && !mToken.IsSymbol('}')) { michael@0: REPORT_UNEXPECTED_TOKEN(PEFFVValueDefinitionTrailing); michael@0: OUTPUT_ERROR(); michael@0: if (!SkipDeclaration(true)) { michael@0: break; michael@0: } michael@0: continue; michael@0: } michael@0: michael@0: gfxFontFeatureValueSet::ValueList v(valueId, featureSelectors); michael@0: values.AppendElement(v); michael@0: michael@0: if (mToken.IsSymbol('}')) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: aFeatureValuesRule->AddValueList(whichVariant, values); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseKeyframesRule(RuleAppendFunc aAppendFunc, void* aData) michael@0: { michael@0: if (!GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PEKeyframeNameEOF); michael@0: return false; michael@0: } michael@0: michael@0: if (mToken.mType != eCSSToken_Ident) { michael@0: REPORT_UNEXPECTED_TOKEN(PEKeyframeBadName); michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: nsString name(mToken.mIdent); michael@0: michael@0: if (!ExpectSymbol('{', true)) { michael@0: REPORT_UNEXPECTED_TOKEN(PEKeyframeBrace); michael@0: return false; michael@0: } michael@0: michael@0: nsRefPtr rule = new nsCSSKeyframesRule(name); michael@0: michael@0: while (!ExpectSymbol('}', true)) { michael@0: nsRefPtr kid = ParseKeyframeRule(); michael@0: if (kid) { michael@0: rule->AppendStyleRule(kid); michael@0: } else { michael@0: OUTPUT_ERROR(); michael@0: SkipRuleSet(true); michael@0: } michael@0: } michael@0: michael@0: (*aAppendFunc)(rule, aData); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParsePageRule(RuleAppendFunc aAppendFunc, void* aData) michael@0: { michael@0: // TODO: There can be page selectors after @page such as ":first", ":left". michael@0: uint32_t parseFlags = eParseDeclaration_InBraces | michael@0: eParseDeclaration_AllowImportant; michael@0: michael@0: // Forbid viewport units in @page rules. See bug 811391. michael@0: NS_ABORT_IF_FALSE(mViewportUnitsEnabled, michael@0: "Viewport units should be enabled outside of @page rules."); michael@0: mViewportUnitsEnabled = false; michael@0: nsAutoPtr declaration( michael@0: ParseDeclarationBlock(parseFlags, michael@0: eCSSContext_Page)); michael@0: mViewportUnitsEnabled = true; michael@0: michael@0: if (!declaration) { michael@0: return false; michael@0: } michael@0: michael@0: // Takes ownership of declaration. michael@0: nsRefPtr rule = new nsCSSPageRule(declaration); michael@0: michael@0: (*aAppendFunc)(rule, aData); michael@0: return true; michael@0: } michael@0: michael@0: already_AddRefed michael@0: CSSParserImpl::ParseKeyframeRule() michael@0: { michael@0: InfallibleTArray selectorList; michael@0: if (!ParseKeyframeSelectorList(selectorList)) { michael@0: REPORT_UNEXPECTED(PEBadSelectorKeyframeRuleIgnored); michael@0: return nullptr; michael@0: } michael@0: michael@0: // Ignore !important in keyframe rules michael@0: uint32_t parseFlags = eParseDeclaration_InBraces; michael@0: nsAutoPtr declaration(ParseDeclarationBlock(parseFlags)); michael@0: if (!declaration) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // Takes ownership of declaration, and steals contents of selectorList. michael@0: nsRefPtr rule = michael@0: new nsCSSKeyframeRule(selectorList, declaration); michael@0: michael@0: return rule.forget(); michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseKeyframeSelectorList(InfallibleTArray& aSelectorList) michael@0: { michael@0: for (;;) { michael@0: if (!GetToken(true)) { michael@0: // The first time through the loop, this means we got an empty michael@0: // list. Otherwise, it means we have a trailing comma. michael@0: return false; michael@0: } michael@0: float value; michael@0: switch (mToken.mType) { michael@0: case eCSSToken_Percentage: michael@0: value = mToken.mNumber; michael@0: break; michael@0: case eCSSToken_Ident: michael@0: if (mToken.mIdent.LowerCaseEqualsLiteral("from")) { michael@0: value = 0.0f; michael@0: break; michael@0: } michael@0: if (mToken.mIdent.LowerCaseEqualsLiteral("to")) { michael@0: value = 1.0f; michael@0: break; michael@0: } michael@0: // fall through michael@0: default: michael@0: UngetToken(); michael@0: // The first time through the loop, this means we got an empty michael@0: // list. Otherwise, it means we have a trailing comma. michael@0: return false; michael@0: } michael@0: aSelectorList.AppendElement(value); michael@0: if (!ExpectSymbol(',', true)) { michael@0: return true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // supports_rule michael@0: // : "@supports" supports_condition group_rule_body michael@0: // ; michael@0: bool michael@0: CSSParserImpl::ParseSupportsRule(RuleAppendFunc aAppendFunc, void* aProcessData) michael@0: { michael@0: bool conditionMet = false; michael@0: nsString condition; michael@0: michael@0: mScanner->StartRecording(); michael@0: bool parsed = ParseSupportsCondition(conditionMet); michael@0: michael@0: if (!parsed) { michael@0: mScanner->StopRecording(); michael@0: return false; michael@0: } michael@0: michael@0: if (!ExpectSymbol('{', true)) { michael@0: REPORT_UNEXPECTED_TOKEN(PESupportsGroupRuleStart); michael@0: mScanner->StopRecording(); michael@0: return false; michael@0: } michael@0: michael@0: UngetToken(); michael@0: mScanner->StopRecording(condition); michael@0: michael@0: // Remove the "{" that would follow the condition. michael@0: if (condition.Length() != 0) { michael@0: condition.Truncate(condition.Length() - 1); michael@0: } michael@0: michael@0: // Remove spaces from the start and end of the recorded supports condition. michael@0: condition.Trim(" ", true, true, false); michael@0: michael@0: // Record whether we are in a failing @supports, so that property parse michael@0: // errors don't get reported. michael@0: nsAutoFailingSupportsRule failing(this, conditionMet); michael@0: michael@0: nsRefPtr rule = new CSSSupportsRule(conditionMet, condition); michael@0: return ParseGroupRule(rule, aAppendFunc, aProcessData); michael@0: } michael@0: michael@0: // supports_condition michael@0: // : supports_condition_in_parens supports_condition_terms michael@0: // | supports_condition_negation michael@0: // ; michael@0: bool michael@0: CSSParserImpl::ParseSupportsCondition(bool& aConditionMet) michael@0: { michael@0: mInSupportsCondition = true; michael@0: michael@0: if (!GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PESupportsConditionStartEOF2); michael@0: return false; michael@0: } michael@0: michael@0: UngetToken(); michael@0: michael@0: mScanner->ClearSeenBadToken(); michael@0: michael@0: if (mToken.IsSymbol('(') || michael@0: mToken.mType == eCSSToken_Function || michael@0: mToken.mType == eCSSToken_URL || michael@0: mToken.mType == eCSSToken_Bad_URL) { michael@0: bool result = ParseSupportsConditionInParens(aConditionMet) && michael@0: ParseSupportsConditionTerms(aConditionMet) && michael@0: !mScanner->SeenBadToken(); michael@0: mInSupportsCondition = false; michael@0: return result; michael@0: } michael@0: michael@0: if (mToken.mType == eCSSToken_Ident && michael@0: mToken.mIdent.LowerCaseEqualsLiteral("not")) { michael@0: bool result = ParseSupportsConditionNegation(aConditionMet) && michael@0: !mScanner->SeenBadToken(); michael@0: mInSupportsCondition = false; michael@0: return result; michael@0: } michael@0: michael@0: REPORT_UNEXPECTED_TOKEN(PESupportsConditionExpectedStart); michael@0: mInSupportsCondition = false; michael@0: return false; michael@0: } michael@0: michael@0: // supports_condition_negation michael@0: // : 'not' S+ supports_condition_in_parens michael@0: // ; michael@0: bool michael@0: CSSParserImpl::ParseSupportsConditionNegation(bool& aConditionMet) michael@0: { michael@0: if (!GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PESupportsConditionNotEOF); michael@0: return false; michael@0: } michael@0: michael@0: if (mToken.mType != eCSSToken_Ident || michael@0: !mToken.mIdent.LowerCaseEqualsLiteral("not")) { michael@0: REPORT_UNEXPECTED_TOKEN(PESupportsConditionExpectedNot); michael@0: return false; michael@0: } michael@0: michael@0: if (!RequireWhitespace()) { michael@0: REPORT_UNEXPECTED(PESupportsWhitespaceRequired); michael@0: return false; michael@0: } michael@0: michael@0: if (ParseSupportsConditionInParens(aConditionMet)) { michael@0: aConditionMet = !aConditionMet; michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: // supports_condition_in_parens michael@0: // : '(' S* supports_condition_in_parens_inside_parens ')' S* michael@0: // | general_enclosed michael@0: // ; michael@0: bool michael@0: CSSParserImpl::ParseSupportsConditionInParens(bool& aConditionMet) michael@0: { michael@0: if (!GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PESupportsConditionInParensStartEOF); michael@0: return false; michael@0: } michael@0: michael@0: if (mToken.mType == eCSSToken_URL) { michael@0: aConditionMet = false; michael@0: return true; michael@0: } michael@0: michael@0: if (mToken.mType == eCSSToken_Function || michael@0: mToken.mType == eCSSToken_Bad_URL) { michael@0: if (!SkipUntil(')')) { michael@0: REPORT_UNEXPECTED_EOF(PESupportsConditionInParensEOF); michael@0: return false; michael@0: } michael@0: aConditionMet = false; michael@0: return true; michael@0: } michael@0: michael@0: if (!mToken.IsSymbol('(')) { michael@0: REPORT_UNEXPECTED_TOKEN(PESupportsConditionExpectedOpenParenOrFunction); michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: michael@0: if (!ParseSupportsConditionInParensInsideParens(aConditionMet)) { michael@0: if (!SkipUntil(')')) { michael@0: REPORT_UNEXPECTED_EOF(PESupportsConditionInParensEOF); michael@0: return false; michael@0: } michael@0: aConditionMet = false; michael@0: return true; michael@0: } michael@0: michael@0: if (!(ExpectSymbol(')', true))) { michael@0: SkipUntil(')'); michael@0: aConditionMet = false; michael@0: return true; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // supports_condition_in_parens_inside_parens michael@0: // : core_declaration michael@0: // | supports_condition_negation michael@0: // | supports_condition_in_parens supports_condition_terms michael@0: // ; michael@0: bool michael@0: CSSParserImpl::ParseSupportsConditionInParensInsideParens(bool& aConditionMet) michael@0: { michael@0: if (!GetToken(true)) { michael@0: return false; michael@0: } michael@0: michael@0: if (mToken.mType == eCSSToken_Ident) { michael@0: if (!mToken.mIdent.LowerCaseEqualsLiteral("not")) { michael@0: nsAutoString propertyName = mToken.mIdent; michael@0: if (!ExpectSymbol(':', true)) { michael@0: return false; michael@0: } michael@0: michael@0: nsCSSProperty propID = LookupEnabledProperty(propertyName); michael@0: if (propID == eCSSProperty_UNKNOWN) { michael@0: if (ExpectSymbol(')', true)) { michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: aConditionMet = false; michael@0: SkipUntil(')'); michael@0: UngetToken(); michael@0: } else if (propID == eCSSPropertyExtra_variable) { michael@0: if (ExpectSymbol(')', false)) { michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: CSSVariableDeclarations::Type variableType; michael@0: nsString variableValue; michael@0: aConditionMet = michael@0: ParseVariableDeclaration(&variableType, variableValue) && michael@0: ParsePriority() != ePriority_Error; michael@0: if (!aConditionMet) { michael@0: SkipUntil(')'); michael@0: UngetToken(); michael@0: } michael@0: } else { michael@0: if (ExpectSymbol(')', true)) { michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: aConditionMet = ParseProperty(propID) && michael@0: ParsePriority() != ePriority_Error; michael@0: if (!aConditionMet) { michael@0: SkipUntil(')'); michael@0: UngetToken(); michael@0: } michael@0: mTempData.ClearProperty(propID); michael@0: mTempData.AssertInitialState(); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: UngetToken(); michael@0: return ParseSupportsConditionNegation(aConditionMet); michael@0: } michael@0: michael@0: UngetToken(); michael@0: return ParseSupportsConditionInParens(aConditionMet) && michael@0: ParseSupportsConditionTerms(aConditionMet); michael@0: } michael@0: michael@0: // supports_condition_terms michael@0: // : S+ 'and' supports_condition_terms_after_operator('and') michael@0: // | S+ 'or' supports_condition_terms_after_operator('or') michael@0: // | michael@0: // ; michael@0: bool michael@0: CSSParserImpl::ParseSupportsConditionTerms(bool& aConditionMet) michael@0: { michael@0: if (!RequireWhitespace() || !GetToken(false)) { michael@0: return true; michael@0: } michael@0: michael@0: if (mToken.mType != eCSSToken_Ident) { michael@0: UngetToken(); michael@0: return true; michael@0: } michael@0: michael@0: if (mToken.mIdent.LowerCaseEqualsLiteral("and")) { michael@0: return ParseSupportsConditionTermsAfterOperator(aConditionMet, eAnd); michael@0: } michael@0: michael@0: if (mToken.mIdent.LowerCaseEqualsLiteral("or")) { michael@0: return ParseSupportsConditionTermsAfterOperator(aConditionMet, eOr); michael@0: } michael@0: michael@0: UngetToken(); michael@0: return true; michael@0: } michael@0: michael@0: // supports_condition_terms_after_operator(operator) michael@0: // : S+ supports_condition_in_parens ( supports_condition_in_parens )* michael@0: // ; michael@0: bool michael@0: CSSParserImpl::ParseSupportsConditionTermsAfterOperator( michael@0: bool& aConditionMet, michael@0: CSSParserImpl::SupportsConditionTermOperator aOperator) michael@0: { michael@0: if (!RequireWhitespace()) { michael@0: REPORT_UNEXPECTED(PESupportsWhitespaceRequired); michael@0: return false; michael@0: } michael@0: michael@0: const char* token = aOperator == eAnd ? "and" : "or"; michael@0: for (;;) { michael@0: bool termConditionMet = false; michael@0: if (!ParseSupportsConditionInParens(termConditionMet)) { michael@0: return false; michael@0: } michael@0: aConditionMet = aOperator == eAnd ? aConditionMet && termConditionMet : michael@0: aConditionMet || termConditionMet; michael@0: michael@0: if (!GetToken(true)) { michael@0: return true; michael@0: } michael@0: michael@0: if (mToken.mType != eCSSToken_Ident || michael@0: !mToken.mIdent.LowerCaseEqualsASCII(token)) { michael@0: UngetToken(); michael@0: return true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::SkipUntil(char16_t aStopSymbol) michael@0: { michael@0: nsCSSToken* tk = &mToken; michael@0: nsAutoTArray stack; michael@0: stack.AppendElement(aStopSymbol); michael@0: for (;;) { michael@0: if (!GetToken(true)) { michael@0: return false; michael@0: } michael@0: if (eCSSToken_Symbol == tk->mType) { michael@0: char16_t symbol = tk->mSymbol; michael@0: uint32_t stackTopIndex = stack.Length() - 1; michael@0: if (symbol == stack.ElementAt(stackTopIndex)) { michael@0: stack.RemoveElementAt(stackTopIndex); michael@0: if (stackTopIndex == 0) { michael@0: return true; michael@0: } michael@0: michael@0: // Just handle out-of-memory by parsing incorrectly. It's michael@0: // highly unlikely we're dealing with a legitimate style sheet michael@0: // anyway. michael@0: } else if ('{' == symbol) { michael@0: stack.AppendElement('}'); michael@0: } else if ('[' == symbol) { michael@0: stack.AppendElement(']'); michael@0: } else if ('(' == symbol) { michael@0: stack.AppendElement(')'); michael@0: } michael@0: } else if (eCSSToken_Function == tk->mType || michael@0: eCSSToken_Bad_URL == tk->mType) { michael@0: stack.AppendElement(')'); michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::SkipBalancedContentUntil(char16_t aStopSymbol) michael@0: { michael@0: nsCSSToken* tk = &mToken; michael@0: nsAutoTArray stack; michael@0: stack.AppendElement(aStopSymbol); michael@0: for (;;) { michael@0: if (!GetToken(true)) { michael@0: return true; michael@0: } michael@0: if (eCSSToken_Symbol == tk->mType) { michael@0: char16_t symbol = tk->mSymbol; michael@0: uint32_t stackTopIndex = stack.Length() - 1; michael@0: if (symbol == stack.ElementAt(stackTopIndex)) { michael@0: stack.RemoveElementAt(stackTopIndex); michael@0: if (stackTopIndex == 0) { michael@0: return true; michael@0: } michael@0: michael@0: // Just handle out-of-memory by parsing incorrectly. It's michael@0: // highly unlikely we're dealing with a legitimate style sheet michael@0: // anyway. michael@0: } else if ('{' == symbol) { michael@0: stack.AppendElement('}'); michael@0: } else if ('[' == symbol) { michael@0: stack.AppendElement(']'); michael@0: } else if ('(' == symbol) { michael@0: stack.AppendElement(')'); michael@0: } else if (')' == symbol || michael@0: ']' == symbol || michael@0: '}' == symbol) { michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: } else if (eCSSToken_Function == tk->mType || michael@0: eCSSToken_Bad_URL == tk->mType) { michael@0: stack.AppendElement(')'); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: CSSParserImpl::SkipUntilOneOf(const char16_t* aStopSymbolChars) michael@0: { michael@0: nsCSSToken* tk = &mToken; michael@0: nsDependentString stopSymbolChars(aStopSymbolChars); michael@0: for (;;) { michael@0: if (!GetToken(true)) { michael@0: break; michael@0: } michael@0: if (eCSSToken_Symbol == tk->mType) { michael@0: char16_t symbol = tk->mSymbol; michael@0: if (stopSymbolChars.FindChar(symbol) != -1) { michael@0: break; michael@0: } else if ('{' == symbol) { michael@0: SkipUntil('}'); michael@0: } else if ('[' == symbol) { michael@0: SkipUntil(']'); michael@0: } else if ('(' == symbol) { michael@0: SkipUntil(')'); michael@0: } michael@0: } else if (eCSSToken_Function == tk->mType || michael@0: eCSSToken_Bad_URL == tk->mType) { michael@0: SkipUntil(')'); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: CSSParserImpl::SkipUntilAllOf(const StopSymbolCharStack& aStopSymbolChars) michael@0: { michael@0: uint32_t i = aStopSymbolChars.Length(); michael@0: while (i--) { michael@0: SkipUntil(aStopSymbolChars[i]); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::SkipDeclaration(bool aCheckForBraces) michael@0: { michael@0: nsCSSToken* tk = &mToken; michael@0: for (;;) { michael@0: if (!GetToken(true)) { michael@0: if (aCheckForBraces) { michael@0: REPORT_UNEXPECTED_EOF(PESkipDeclBraceEOF); michael@0: } michael@0: return false; michael@0: } michael@0: if (eCSSToken_Symbol == tk->mType) { michael@0: char16_t symbol = tk->mSymbol; michael@0: if (';' == symbol) { michael@0: break; michael@0: } michael@0: if (aCheckForBraces) { michael@0: if ('}' == symbol) { michael@0: UngetToken(); michael@0: break; michael@0: } michael@0: } michael@0: if ('{' == symbol) { michael@0: SkipUntil('}'); michael@0: } else if ('(' == symbol) { michael@0: SkipUntil(')'); michael@0: } else if ('[' == symbol) { michael@0: SkipUntil(']'); michael@0: } michael@0: } else if (eCSSToken_Function == tk->mType || michael@0: eCSSToken_Bad_URL == tk->mType) { michael@0: SkipUntil(')'); michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: CSSParserImpl::SkipRuleSet(bool aInsideBraces) michael@0: { michael@0: nsCSSToken* tk = &mToken; michael@0: for (;;) { michael@0: if (!GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PESkipRSBraceEOF); michael@0: break; michael@0: } michael@0: if (eCSSToken_Symbol == tk->mType) { michael@0: char16_t symbol = tk->mSymbol; michael@0: if ('}' == symbol && aInsideBraces) { michael@0: // leave block closer for higher-level grammar to consume michael@0: UngetToken(); michael@0: break; michael@0: } else if ('{' == symbol) { michael@0: SkipUntil('}'); michael@0: break; michael@0: } else if ('(' == symbol) { michael@0: SkipUntil(')'); michael@0: } else if ('[' == symbol) { michael@0: SkipUntil(']'); michael@0: } michael@0: } else if (eCSSToken_Function == tk->mType || michael@0: eCSSToken_Bad_URL == tk->mType) { michael@0: SkipUntil(')'); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: CSSParserImpl::PushGroup(css::GroupRule* aRule) michael@0: { michael@0: mGroupStack.AppendElement(aRule); michael@0: } michael@0: michael@0: void michael@0: CSSParserImpl::PopGroup() michael@0: { michael@0: uint32_t count = mGroupStack.Length(); michael@0: if (0 < count) { michael@0: mGroupStack.RemoveElementAt(count - 1); michael@0: } michael@0: } michael@0: michael@0: void michael@0: CSSParserImpl::AppendRule(css::Rule* aRule) michael@0: { michael@0: uint32_t count = mGroupStack.Length(); michael@0: if (0 < count) { michael@0: mGroupStack[count - 1]->AppendStyleRule(aRule); michael@0: } michael@0: else { michael@0: mSheet->AppendStyleRule(aRule); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseRuleSet(RuleAppendFunc aAppendFunc, void* aData, michael@0: bool aInsideBraces) michael@0: { michael@0: // First get the list of selectors for the rule michael@0: nsCSSSelectorList* slist = nullptr; michael@0: uint32_t linenum, colnum; michael@0: if (!GetNextTokenLocation(true, &linenum, &colnum) || michael@0: !ParseSelectorList(slist, char16_t('{'))) { michael@0: REPORT_UNEXPECTED(PEBadSelectorRSIgnored); michael@0: OUTPUT_ERROR(); michael@0: SkipRuleSet(aInsideBraces); michael@0: return false; michael@0: } michael@0: NS_ASSERTION(nullptr != slist, "null selector list"); michael@0: CLEAR_ERROR(); michael@0: michael@0: // Next parse the declaration block michael@0: uint32_t parseFlags = eParseDeclaration_InBraces | michael@0: eParseDeclaration_AllowImportant; michael@0: css::Declaration* declaration = ParseDeclarationBlock(parseFlags); michael@0: if (nullptr == declaration) { michael@0: delete slist; michael@0: return false; michael@0: } michael@0: michael@0: #if 0 michael@0: slist->Dump(); michael@0: fputs("{\n", stdout); michael@0: declaration->List(); michael@0: fputs("}\n", stdout); michael@0: #endif michael@0: michael@0: // Translate the selector list and declaration block into style data michael@0: michael@0: nsRefPtr rule = new css::StyleRule(slist, declaration); michael@0: rule->SetLineNumberAndColumnNumber(linenum, colnum); michael@0: (*aAppendFunc)(rule, aData); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseSelectorList(nsCSSSelectorList*& aListHead, michael@0: char16_t aStopChar) michael@0: { michael@0: nsCSSSelectorList* list = nullptr; michael@0: if (! ParseSelectorGroup(list)) { michael@0: // must have at least one selector group michael@0: aListHead = nullptr; michael@0: return false; michael@0: } michael@0: NS_ASSERTION(nullptr != list, "no selector list"); michael@0: aListHead = list; michael@0: michael@0: // After that there must either be a "," or a "{" (the latter if michael@0: // StopChar is nonzero) michael@0: nsCSSToken* tk = &mToken; michael@0: for (;;) { michael@0: if (! GetToken(true)) { michael@0: if (aStopChar == char16_t(0)) { michael@0: return true; michael@0: } michael@0: michael@0: REPORT_UNEXPECTED_EOF(PESelectorListExtraEOF); michael@0: break; michael@0: } michael@0: michael@0: if (eCSSToken_Symbol == tk->mType) { michael@0: if (',' == tk->mSymbol) { michael@0: nsCSSSelectorList* newList = nullptr; michael@0: // Another selector group must follow michael@0: if (! ParseSelectorGroup(newList)) { michael@0: break; michael@0: } michael@0: // add new list to the end of the selector list michael@0: list->mNext = newList; michael@0: list = newList; michael@0: continue; michael@0: } else if (aStopChar == tk->mSymbol && aStopChar != char16_t(0)) { michael@0: UngetToken(); michael@0: return true; michael@0: } michael@0: } michael@0: REPORT_UNEXPECTED_TOKEN(PESelectorListExtra); michael@0: UngetToken(); michael@0: break; michael@0: } michael@0: michael@0: delete aListHead; michael@0: aListHead = nullptr; michael@0: return false; michael@0: } michael@0: michael@0: static bool IsUniversalSelector(const nsCSSSelector& aSelector) michael@0: { michael@0: return bool((aSelector.mNameSpace == kNameSpaceID_Unknown) && michael@0: (aSelector.mLowercaseTag == nullptr) && michael@0: (aSelector.mIDList == nullptr) && michael@0: (aSelector.mClassList == nullptr) && michael@0: (aSelector.mAttrList == nullptr) && michael@0: (aSelector.mNegations == nullptr) && michael@0: (aSelector.mPseudoClassList == nullptr)); michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseSelectorGroup(nsCSSSelectorList*& aList) michael@0: { michael@0: char16_t combinator = 0; michael@0: nsAutoPtr list(new nsCSSSelectorList()); michael@0: michael@0: for (;;) { michael@0: if (!ParseSelector(list, combinator)) { michael@0: return false; michael@0: } michael@0: michael@0: // Look for a combinator. michael@0: if (!GetToken(false)) { michael@0: break; // EOF ok here michael@0: } michael@0: michael@0: combinator = char16_t(0); michael@0: if (mToken.mType == eCSSToken_Whitespace) { michael@0: if (!GetToken(true)) { michael@0: break; // EOF ok here michael@0: } michael@0: combinator = char16_t(' '); michael@0: } michael@0: michael@0: if (mToken.mType != eCSSToken_Symbol) { michael@0: UngetToken(); // not a combinator michael@0: } else { michael@0: char16_t symbol = mToken.mSymbol; michael@0: if (symbol == '+' || symbol == '>' || symbol == '~') { michael@0: combinator = mToken.mSymbol; michael@0: } else { michael@0: UngetToken(); // not a combinator michael@0: if (symbol == ',' || symbol == '{' || symbol == ')') { michael@0: break; // end of selector group michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!combinator) { michael@0: REPORT_UNEXPECTED_TOKEN(PESelectorListExtra); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: aList = list.forget(); michael@0: return true; michael@0: } michael@0: michael@0: #define SEL_MASK_NSPACE 0x01 michael@0: #define SEL_MASK_ELEM 0x02 michael@0: #define SEL_MASK_ID 0x04 michael@0: #define SEL_MASK_CLASS 0x08 michael@0: #define SEL_MASK_ATTRIB 0x10 michael@0: #define SEL_MASK_PCLASS 0x20 michael@0: #define SEL_MASK_PELEM 0x40 michael@0: michael@0: // michael@0: // Parses an ID selector #name michael@0: // michael@0: CSSParserImpl::nsSelectorParsingStatus michael@0: CSSParserImpl::ParseIDSelector(int32_t& aDataMask, michael@0: nsCSSSelector& aSelector) michael@0: { michael@0: NS_ASSERTION(!mToken.mIdent.IsEmpty(), michael@0: "Empty mIdent in eCSSToken_ID token?"); michael@0: aDataMask |= SEL_MASK_ID; michael@0: aSelector.AddID(mToken.mIdent); michael@0: return eSelectorParsingStatus_Continue; michael@0: } michael@0: michael@0: // michael@0: // Parses a class selector .name michael@0: // michael@0: CSSParserImpl::nsSelectorParsingStatus michael@0: CSSParserImpl::ParseClassSelector(int32_t& aDataMask, michael@0: nsCSSSelector& aSelector) michael@0: { michael@0: if (! GetToken(false)) { // get ident michael@0: REPORT_UNEXPECTED_EOF(PEClassSelEOF); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: if (eCSSToken_Ident != mToken.mType) { // malformed selector michael@0: REPORT_UNEXPECTED_TOKEN(PEClassSelNotIdent); michael@0: UngetToken(); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: aDataMask |= SEL_MASK_CLASS; michael@0: michael@0: aSelector.AddClass(mToken.mIdent); michael@0: michael@0: return eSelectorParsingStatus_Continue; michael@0: } michael@0: michael@0: // michael@0: // Parse a type element selector or a universal selector michael@0: // namespace|type or namespace|* or *|* or * michael@0: // michael@0: CSSParserImpl::nsSelectorParsingStatus michael@0: CSSParserImpl::ParseTypeOrUniversalSelector(int32_t& aDataMask, michael@0: nsCSSSelector& aSelector, michael@0: bool aIsNegated) michael@0: { michael@0: nsAutoString buffer; michael@0: if (mToken.IsSymbol('*')) { // universal element selector, or universal namespace michael@0: if (ExpectSymbol('|', false)) { // was namespace michael@0: aDataMask |= SEL_MASK_NSPACE; michael@0: aSelector.SetNameSpace(kNameSpaceID_Unknown); // namespace wildcard michael@0: michael@0: if (! GetToken(false)) { michael@0: REPORT_UNEXPECTED_EOF(PETypeSelEOF); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: if (eCSSToken_Ident == mToken.mType) { // element name michael@0: aDataMask |= SEL_MASK_ELEM; michael@0: michael@0: aSelector.SetTag(mToken.mIdent); michael@0: } michael@0: else if (mToken.IsSymbol('*')) { // universal selector michael@0: aDataMask |= SEL_MASK_ELEM; michael@0: // don't set tag michael@0: } michael@0: else { michael@0: REPORT_UNEXPECTED_TOKEN(PETypeSelNotType); michael@0: UngetToken(); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: } michael@0: else { // was universal element selector michael@0: SetDefaultNamespaceOnSelector(aSelector); michael@0: aDataMask |= SEL_MASK_ELEM; michael@0: // don't set any tag in the selector michael@0: } michael@0: if (! GetToken(false)) { // premature eof is ok (here!) michael@0: return eSelectorParsingStatus_Done; michael@0: } michael@0: } michael@0: else if (eCSSToken_Ident == mToken.mType) { // element name or namespace name michael@0: buffer = mToken.mIdent; // hang on to ident michael@0: michael@0: if (ExpectSymbol('|', false)) { // was namespace michael@0: aDataMask |= SEL_MASK_NSPACE; michael@0: int32_t nameSpaceID = GetNamespaceIdForPrefix(buffer); michael@0: if (nameSpaceID == kNameSpaceID_Unknown) { michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: aSelector.SetNameSpace(nameSpaceID); michael@0: michael@0: if (! GetToken(false)) { michael@0: REPORT_UNEXPECTED_EOF(PETypeSelEOF); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: if (eCSSToken_Ident == mToken.mType) { // element name michael@0: aDataMask |= SEL_MASK_ELEM; michael@0: aSelector.SetTag(mToken.mIdent); michael@0: } michael@0: else if (mToken.IsSymbol('*')) { // universal selector michael@0: aDataMask |= SEL_MASK_ELEM; michael@0: // don't set tag michael@0: } michael@0: else { michael@0: REPORT_UNEXPECTED_TOKEN(PETypeSelNotType); michael@0: UngetToken(); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: } michael@0: else { // was element name michael@0: SetDefaultNamespaceOnSelector(aSelector); michael@0: aSelector.SetTag(buffer); michael@0: michael@0: aDataMask |= SEL_MASK_ELEM; michael@0: } michael@0: if (! GetToken(false)) { // premature eof is ok (here!) michael@0: return eSelectorParsingStatus_Done; michael@0: } michael@0: } michael@0: else if (mToken.IsSymbol('|')) { // No namespace michael@0: aDataMask |= SEL_MASK_NSPACE; michael@0: aSelector.SetNameSpace(kNameSpaceID_None); // explicit NO namespace michael@0: michael@0: // get mandatory tag michael@0: if (! GetToken(false)) { michael@0: REPORT_UNEXPECTED_EOF(PETypeSelEOF); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: if (eCSSToken_Ident == mToken.mType) { // element name michael@0: aDataMask |= SEL_MASK_ELEM; michael@0: aSelector.SetTag(mToken.mIdent); michael@0: } michael@0: else if (mToken.IsSymbol('*')) { // universal selector michael@0: aDataMask |= SEL_MASK_ELEM; michael@0: // don't set tag michael@0: } michael@0: else { michael@0: REPORT_UNEXPECTED_TOKEN(PETypeSelNotType); michael@0: UngetToken(); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: if (! GetToken(false)) { // premature eof is ok (here!) michael@0: return eSelectorParsingStatus_Done; michael@0: } michael@0: } michael@0: else { michael@0: SetDefaultNamespaceOnSelector(aSelector); michael@0: } michael@0: michael@0: if (aIsNegated) { michael@0: // restore last token read in case of a negated type selector michael@0: UngetToken(); michael@0: } michael@0: return eSelectorParsingStatus_Continue; michael@0: } michael@0: michael@0: // michael@0: // Parse attribute selectors [attr], [attr=value], [attr|=value], michael@0: // [attr~=value], [attr^=value], [attr$=value] and [attr*=value] michael@0: // michael@0: CSSParserImpl::nsSelectorParsingStatus michael@0: CSSParserImpl::ParseAttributeSelector(int32_t& aDataMask, michael@0: nsCSSSelector& aSelector) michael@0: { michael@0: if (! GetToken(true)) { // premature EOF michael@0: REPORT_UNEXPECTED_EOF(PEAttributeNameEOF); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: michael@0: int32_t nameSpaceID = kNameSpaceID_None; michael@0: nsAutoString attr; michael@0: if (mToken.IsSymbol('*')) { // wildcard namespace michael@0: nameSpaceID = kNameSpaceID_Unknown; michael@0: if (ExpectSymbol('|', false)) { michael@0: if (! GetToken(false)) { // premature EOF michael@0: REPORT_UNEXPECTED_EOF(PEAttributeNameEOF); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: if (eCSSToken_Ident == mToken.mType) { // attr name michael@0: attr = mToken.mIdent; michael@0: } michael@0: else { michael@0: REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected); michael@0: UngetToken(); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: } michael@0: else { michael@0: REPORT_UNEXPECTED_TOKEN(PEAttSelNoBar); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: } michael@0: else if (mToken.IsSymbol('|')) { // NO namespace michael@0: if (! GetToken(false)) { // premature EOF michael@0: REPORT_UNEXPECTED_EOF(PEAttributeNameEOF); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: if (eCSSToken_Ident == mToken.mType) { // attr name michael@0: attr = mToken.mIdent; michael@0: } michael@0: else { michael@0: REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected); michael@0: UngetToken(); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: } michael@0: else if (eCSSToken_Ident == mToken.mType) { // attr name or namespace michael@0: attr = mToken.mIdent; // hang on to it michael@0: if (ExpectSymbol('|', false)) { // was a namespace michael@0: nameSpaceID = GetNamespaceIdForPrefix(attr); michael@0: if (nameSpaceID == kNameSpaceID_Unknown) { michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: if (! GetToken(false)) { // premature EOF michael@0: REPORT_UNEXPECTED_EOF(PEAttributeNameEOF); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: if (eCSSToken_Ident == mToken.mType) { // attr name michael@0: attr = mToken.mIdent; michael@0: } michael@0: else { michael@0: REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected); michael@0: UngetToken(); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: } michael@0: } michael@0: else { // malformed michael@0: REPORT_UNEXPECTED_TOKEN(PEAttributeNameOrNamespaceExpected); michael@0: UngetToken(); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: michael@0: if (! GetToken(true)) { // premature EOF michael@0: REPORT_UNEXPECTED_EOF(PEAttSelInnerEOF); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: if ((eCSSToken_Symbol == mToken.mType) || michael@0: (eCSSToken_Includes == mToken.mType) || michael@0: (eCSSToken_Dashmatch == mToken.mType) || michael@0: (eCSSToken_Beginsmatch == mToken.mType) || michael@0: (eCSSToken_Endsmatch == mToken.mType) || michael@0: (eCSSToken_Containsmatch == mToken.mType)) { michael@0: uint8_t func; michael@0: if (eCSSToken_Includes == mToken.mType) { michael@0: func = NS_ATTR_FUNC_INCLUDES; michael@0: } michael@0: else if (eCSSToken_Dashmatch == mToken.mType) { michael@0: func = NS_ATTR_FUNC_DASHMATCH; michael@0: } michael@0: else if (eCSSToken_Beginsmatch == mToken.mType) { michael@0: func = NS_ATTR_FUNC_BEGINSMATCH; michael@0: } michael@0: else if (eCSSToken_Endsmatch == mToken.mType) { michael@0: func = NS_ATTR_FUNC_ENDSMATCH; michael@0: } michael@0: else if (eCSSToken_Containsmatch == mToken.mType) { michael@0: func = NS_ATTR_FUNC_CONTAINSMATCH; michael@0: } michael@0: else if (']' == mToken.mSymbol) { michael@0: aDataMask |= SEL_MASK_ATTRIB; michael@0: aSelector.AddAttribute(nameSpaceID, attr); michael@0: func = NS_ATTR_FUNC_SET; michael@0: } michael@0: else if ('=' == mToken.mSymbol) { michael@0: func = NS_ATTR_FUNC_EQUALS; michael@0: } michael@0: else { michael@0: REPORT_UNEXPECTED_TOKEN(PEAttSelUnexpected); michael@0: UngetToken(); // bad function michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: if (NS_ATTR_FUNC_SET != func) { // get value michael@0: if (! GetToken(true)) { // premature EOF michael@0: REPORT_UNEXPECTED_EOF(PEAttSelValueEOF); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: if ((eCSSToken_Ident == mToken.mType) || (eCSSToken_String == mToken.mType)) { michael@0: nsAutoString value(mToken.mIdent); michael@0: if (! GetToken(true)) { // premature EOF michael@0: REPORT_UNEXPECTED_EOF(PEAttSelCloseEOF); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: if (mToken.IsSymbol(']')) { michael@0: bool isCaseSensitive = true; michael@0: michael@0: // For cases when this style sheet is applied to an HTML michael@0: // element in an HTML document, and the attribute selector is michael@0: // for a non-namespaced attribute, then check to see if it's michael@0: // one of the known attributes whose VALUE is michael@0: // case-insensitive. michael@0: if (nameSpaceID == kNameSpaceID_None) { michael@0: static const char* caseInsensitiveHTMLAttribute[] = { michael@0: // list based on http://www.w3.org/TR/html4/ michael@0: "lang", michael@0: "dir", michael@0: "http-equiv", michael@0: "text", michael@0: "link", michael@0: "vlink", michael@0: "alink", michael@0: "compact", michael@0: "align", michael@0: "frame", michael@0: "rules", michael@0: "valign", michael@0: "scope", michael@0: "axis", michael@0: "nowrap", michael@0: "hreflang", michael@0: "rel", michael@0: "rev", michael@0: "charset", michael@0: "codetype", michael@0: "declare", michael@0: "valuetype", michael@0: "shape", michael@0: "nohref", michael@0: "media", michael@0: "bgcolor", michael@0: "clear", michael@0: "color", michael@0: "face", michael@0: "noshade", michael@0: "noresize", michael@0: "scrolling", michael@0: "target", michael@0: "method", michael@0: "enctype", michael@0: "accept-charset", michael@0: "accept", michael@0: "checked", michael@0: "multiple", michael@0: "selected", michael@0: "disabled", michael@0: "readonly", michael@0: "language", michael@0: "defer", michael@0: "type", michael@0: // additional attributes not in HTML4 michael@0: "direction", // marquee michael@0: nullptr michael@0: }; michael@0: short i = 0; michael@0: const char* htmlAttr; michael@0: while ((htmlAttr = caseInsensitiveHTMLAttribute[i++])) { michael@0: if (attr.LowerCaseEqualsASCII(htmlAttr)) { michael@0: isCaseSensitive = false; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: aDataMask |= SEL_MASK_ATTRIB; michael@0: aSelector.AddAttribute(nameSpaceID, attr, func, value, isCaseSensitive); michael@0: } michael@0: else { michael@0: REPORT_UNEXPECTED_TOKEN(PEAttSelNoClose); michael@0: UngetToken(); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: } michael@0: else { michael@0: REPORT_UNEXPECTED_TOKEN(PEAttSelBadValue); michael@0: UngetToken(); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: } michael@0: } michael@0: else { michael@0: REPORT_UNEXPECTED_TOKEN(PEAttSelUnexpected); michael@0: UngetToken(); // bad dog, no biscut! michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: return eSelectorParsingStatus_Continue; michael@0: } michael@0: michael@0: // michael@0: // Parse pseudo-classes and pseudo-elements michael@0: // michael@0: CSSParserImpl::nsSelectorParsingStatus michael@0: CSSParserImpl::ParsePseudoSelector(int32_t& aDataMask, michael@0: nsCSSSelector& aSelector, michael@0: bool aIsNegated, michael@0: nsIAtom** aPseudoElement, michael@0: nsAtomList** aPseudoElementArgs, michael@0: nsCSSPseudoElements::Type* aPseudoElementType) michael@0: { michael@0: NS_ASSERTION(aIsNegated || (aPseudoElement && aPseudoElementArgs), michael@0: "expected location to store pseudo element"); michael@0: NS_ASSERTION(!aIsNegated || (!aPseudoElement && !aPseudoElementArgs), michael@0: "negated selectors shouldn't have a place to store " michael@0: "pseudo elements"); michael@0: if (! GetToken(false)) { // premature eof michael@0: REPORT_UNEXPECTED_EOF(PEPseudoSelEOF); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: michael@0: // First, find out whether we are parsing a CSS3 pseudo-element michael@0: bool parsingPseudoElement = false; michael@0: if (mToken.IsSymbol(':')) { michael@0: parsingPseudoElement = true; michael@0: if (! GetToken(false)) { // premature eof michael@0: REPORT_UNEXPECTED_EOF(PEPseudoSelEOF); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: } michael@0: michael@0: // Do some sanity-checking on the token michael@0: if (eCSSToken_Ident != mToken.mType && eCSSToken_Function != mToken.mType) { michael@0: // malformed selector michael@0: REPORT_UNEXPECTED_TOKEN(PEPseudoSelBadName); michael@0: UngetToken(); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: michael@0: // OK, now we know we have an mIdent. Atomize it. All the atoms, for michael@0: // pseudo-classes as well as pseudo-elements, start with a single ':'. michael@0: nsAutoString buffer; michael@0: buffer.Append(char16_t(':')); michael@0: buffer.Append(mToken.mIdent); michael@0: nsContentUtils::ASCIIToLower(buffer); michael@0: nsCOMPtr pseudo = do_GetAtom(buffer); michael@0: if (!pseudo) { michael@0: NS_RUNTIMEABORT("do_GetAtom failed - out of memory?"); michael@0: } michael@0: michael@0: // stash away some info about this pseudo so we only have to get it once. michael@0: bool isTreePseudo = false; michael@0: nsCSSPseudoElements::Type pseudoElementType = michael@0: nsCSSPseudoElements::GetPseudoType(pseudo); michael@0: nsCSSPseudoClasses::Type pseudoClassType = michael@0: nsCSSPseudoClasses::GetPseudoType(pseudo); michael@0: bool pseudoClassIsUserAction = michael@0: nsCSSPseudoClasses::IsUserActionPseudoClass(pseudoClassType); michael@0: michael@0: if (!mUnsafeRulesEnabled && michael@0: pseudoElementType < nsCSSPseudoElements::ePseudo_PseudoElementCount && michael@0: nsCSSPseudoElements::PseudoElementIsChromeOnly(pseudoElementType)) { michael@0: // This pseudo-element is not exposed to content. michael@0: REPORT_UNEXPECTED_TOKEN(PEPseudoSelUnknown); michael@0: UngetToken(); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: michael@0: // We currently allow :-moz-placeholder and ::-moz-placeholder. We have to michael@0: // be a bit stricter regarding the pseudo-element parsing rules. michael@0: if (pseudoElementType == nsCSSPseudoElements::ePseudo_mozPlaceholder && michael@0: pseudoClassType == nsCSSPseudoClasses::ePseudoClass_mozPlaceholder) { michael@0: if (parsingPseudoElement) { michael@0: pseudoClassType = nsCSSPseudoClasses::ePseudoClass_NotPseudoClass; michael@0: } else { michael@0: pseudoElementType = nsCSSPseudoElements::ePseudo_NotPseudoElement; michael@0: } michael@0: } michael@0: michael@0: #ifdef MOZ_XUL michael@0: isTreePseudo = (pseudoElementType == nsCSSPseudoElements::ePseudo_XULTree); michael@0: // If a tree pseudo-element is using the function syntax, it will michael@0: // get isTree set here and will pass the check below that only michael@0: // allows functions if they are in our list of things allowed to be michael@0: // functions. If it is _not_ using the function syntax, isTree will michael@0: // be false, and it will still pass that check. So the tree michael@0: // pseudo-elements are allowed to be either functions or not, as michael@0: // desired. michael@0: bool isTree = (eCSSToken_Function == mToken.mType) && isTreePseudo; michael@0: #endif michael@0: bool isPseudoElement = michael@0: (pseudoElementType < nsCSSPseudoElements::ePseudo_PseudoElementCount); michael@0: // anonymous boxes are only allowed if they're the tree boxes or we have michael@0: // enabled unsafe rules michael@0: bool isAnonBox = isTreePseudo || michael@0: (pseudoElementType == nsCSSPseudoElements::ePseudo_AnonBox && michael@0: mUnsafeRulesEnabled); michael@0: bool isPseudoClass = michael@0: (pseudoClassType != nsCSSPseudoClasses::ePseudoClass_NotPseudoClass); michael@0: michael@0: NS_ASSERTION(!isPseudoClass || michael@0: pseudoElementType == nsCSSPseudoElements::ePseudo_NotPseudoElement, michael@0: "Why is this atom both a pseudo-class and a pseudo-element?"); michael@0: NS_ASSERTION(isPseudoClass + isPseudoElement + isAnonBox <= 1, michael@0: "Shouldn't be more than one of these"); michael@0: michael@0: if (!isPseudoClass && !isPseudoElement && !isAnonBox) { michael@0: // Not a pseudo-class, not a pseudo-element.... forget it michael@0: REPORT_UNEXPECTED_TOKEN(PEPseudoSelUnknown); michael@0: UngetToken(); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: michael@0: // If it's a function token, it better be on our "ok" list, and if the name michael@0: // is that of a function pseudo it better be a function token michael@0: if ((eCSSToken_Function == mToken.mType) != michael@0: ( michael@0: #ifdef MOZ_XUL michael@0: isTree || michael@0: #endif michael@0: nsCSSPseudoClasses::ePseudoClass_notPseudo == pseudoClassType || michael@0: nsCSSPseudoClasses::HasStringArg(pseudoClassType) || michael@0: nsCSSPseudoClasses::HasNthPairArg(pseudoClassType) || michael@0: nsCSSPseudoClasses::HasSelectorListArg(pseudoClassType))) { michael@0: // There are no other function pseudos michael@0: REPORT_UNEXPECTED_TOKEN(PEPseudoSelNonFunc); michael@0: UngetToken(); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: michael@0: // If it starts with "::", it better be a pseudo-element michael@0: if (parsingPseudoElement && michael@0: !isPseudoElement && michael@0: !isAnonBox) { michael@0: REPORT_UNEXPECTED_TOKEN(PEPseudoSelNotPE); michael@0: UngetToken(); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: michael@0: if (!parsingPseudoElement && michael@0: nsCSSPseudoClasses::ePseudoClass_notPseudo == pseudoClassType) { michael@0: if (aIsNegated) { // :not() can't be itself negated michael@0: REPORT_UNEXPECTED_TOKEN(PEPseudoSelDoubleNot); michael@0: UngetToken(); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: // CSS 3 Negation pseudo-class takes one simple selector as argument michael@0: nsSelectorParsingStatus parsingStatus = michael@0: ParseNegatedSimpleSelector(aDataMask, aSelector); michael@0: if (eSelectorParsingStatus_Continue != parsingStatus) { michael@0: return parsingStatus; michael@0: } michael@0: } michael@0: else if (!parsingPseudoElement && isPseudoClass) { michael@0: if (aSelector.IsPseudoElement()) { michael@0: nsCSSPseudoElements::Type type = aSelector.PseudoType(); michael@0: if (!nsCSSPseudoElements::PseudoElementSupportsUserActionState(type)) { michael@0: // We only allow user action pseudo-classes on certain pseudo-elements. michael@0: REPORT_UNEXPECTED_TOKEN(PEPseudoSelNoUserActionPC); michael@0: UngetToken(); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: if (!pseudoClassIsUserAction) { michael@0: // CSS 4 Selectors says that pseudo-elements can only be followed by michael@0: // a user action pseudo-class. michael@0: REPORT_UNEXPECTED_TOKEN(PEPseudoClassNotUserAction); michael@0: UngetToken(); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: } michael@0: aDataMask |= SEL_MASK_PCLASS; michael@0: if (eCSSToken_Function == mToken.mType) { michael@0: nsSelectorParsingStatus parsingStatus; michael@0: if (nsCSSPseudoClasses::HasStringArg(pseudoClassType)) { michael@0: parsingStatus = michael@0: ParsePseudoClassWithIdentArg(aSelector, pseudoClassType); michael@0: } michael@0: else if (nsCSSPseudoClasses::HasNthPairArg(pseudoClassType)) { michael@0: parsingStatus = michael@0: ParsePseudoClassWithNthPairArg(aSelector, pseudoClassType); michael@0: } michael@0: else { michael@0: NS_ABORT_IF_FALSE(nsCSSPseudoClasses::HasSelectorListArg(pseudoClassType), michael@0: "unexpected pseudo with function token"); michael@0: parsingStatus = ParsePseudoClassWithSelectorListArg(aSelector, michael@0: pseudoClassType); michael@0: } michael@0: if (eSelectorParsingStatus_Continue != parsingStatus) { michael@0: if (eSelectorParsingStatus_Error == parsingStatus) { michael@0: SkipUntil(')'); michael@0: } michael@0: return parsingStatus; michael@0: } michael@0: } michael@0: else { michael@0: aSelector.AddPseudoClass(pseudoClassType); michael@0: } michael@0: } michael@0: else if (isPseudoElement || isAnonBox) { michael@0: // Pseudo-element. Make some more sanity checks. michael@0: michael@0: if (aIsNegated) { // pseudo-elements can't be negated michael@0: REPORT_UNEXPECTED_TOKEN(PEPseudoSelPEInNot); michael@0: UngetToken(); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: // CSS2 pseudo-elements and -moz-tree-* pseudo-elements are allowed michael@0: // to have a single ':' on them. Others (CSS3+ pseudo-elements and michael@0: // various -moz-* pseudo-elements) must have |parsingPseudoElement| michael@0: // set. michael@0: if (!parsingPseudoElement && michael@0: !nsCSSPseudoElements::IsCSS2PseudoElement(pseudo) michael@0: #ifdef MOZ_XUL michael@0: && !isTreePseudo michael@0: #endif michael@0: ) { michael@0: REPORT_UNEXPECTED_TOKEN(PEPseudoSelNewStyleOnly); michael@0: UngetToken(); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: michael@0: if (0 == (aDataMask & SEL_MASK_PELEM)) { michael@0: aDataMask |= SEL_MASK_PELEM; michael@0: NS_ADDREF(*aPseudoElement = pseudo); michael@0: *aPseudoElementType = pseudoElementType; michael@0: michael@0: #ifdef MOZ_XUL michael@0: if (isTree) { michael@0: // We have encountered a pseudoelement of the form michael@0: // -moz-tree-xxxx(a,b,c). We parse (a,b,c) and add each michael@0: // item in the list to the pseudoclass list. They will be pulled michael@0: // from the list later along with the pseudo-element. michael@0: if (!ParseTreePseudoElement(aPseudoElementArgs)) { michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: // Pseudo-elements can only be followed by user action pseudo-classes michael@0: // or be the end of the selector. So the next non-whitespace token must michael@0: // be ':', '{' or ',' or EOF. michael@0: if (!GetToken(true)) { // premature eof is ok (here!) michael@0: return eSelectorParsingStatus_Done; michael@0: } michael@0: if (parsingPseudoElement && mToken.IsSymbol(':')) { michael@0: UngetToken(); michael@0: return eSelectorParsingStatus_Continue; michael@0: } michael@0: if ((mToken.IsSymbol('{') || mToken.IsSymbol(','))) { michael@0: UngetToken(); michael@0: return eSelectorParsingStatus_Done; michael@0: } michael@0: REPORT_UNEXPECTED_TOKEN(PEPseudoSelEndOrUserActionPC); michael@0: UngetToken(); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: else { // multiple pseudo elements, not legal michael@0: REPORT_UNEXPECTED_TOKEN(PEPseudoSelMultiplePE); michael@0: UngetToken(); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: } michael@0: #ifdef DEBUG michael@0: else { michael@0: // We should never end up here. Indeed, if we ended up here, we know (from michael@0: // the current if/else cascade) that !isPseudoElement and !isAnonBox. But michael@0: // then due to our earlier check we know that isPseudoClass. Since we michael@0: // didn't fall into the isPseudoClass case in this cascade, we must have michael@0: // parsingPseudoElement. But we've already checked the michael@0: // parsingPseudoElement && !isPseudoClass && !isAnonBox case and bailed if michael@0: // it's happened. michael@0: NS_NOTREACHED("How did this happen?"); michael@0: } michael@0: #endif michael@0: return eSelectorParsingStatus_Continue; michael@0: } michael@0: michael@0: // michael@0: // Parse the argument of a negation pseudo-class :not() michael@0: // michael@0: CSSParserImpl::nsSelectorParsingStatus michael@0: CSSParserImpl::ParseNegatedSimpleSelector(int32_t& aDataMask, michael@0: nsCSSSelector& aSelector) michael@0: { michael@0: if (! GetToken(true)) { // premature eof michael@0: REPORT_UNEXPECTED_EOF(PENegationEOF); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: michael@0: if (mToken.IsSymbol(')')) { michael@0: REPORT_UNEXPECTED_TOKEN(PENegationBadArg); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: michael@0: // Create a new nsCSSSelector and add it to the end of michael@0: // aSelector.mNegations. michael@0: // Given the current parsing rules, every selector in mNegations michael@0: // contains only one simple selector (css3 definition) within it. michael@0: // This could easily change in future versions of CSS, and the only michael@0: // thing we need to change to support that is this parsing code and the michael@0: // serialization code for nsCSSSelector. michael@0: nsCSSSelector *newSel = new nsCSSSelector(); michael@0: nsCSSSelector* negations = &aSelector; michael@0: while (negations->mNegations) { michael@0: negations = negations->mNegations; michael@0: } michael@0: negations->mNegations = newSel; michael@0: michael@0: nsSelectorParsingStatus parsingStatus; michael@0: if (eCSSToken_ID == mToken.mType) { // #id michael@0: parsingStatus = ParseIDSelector(aDataMask, *newSel); michael@0: } michael@0: else if (mToken.IsSymbol('.')) { // .class michael@0: parsingStatus = ParseClassSelector(aDataMask, *newSel); michael@0: } michael@0: else if (mToken.IsSymbol(':')) { // :pseudo michael@0: parsingStatus = ParsePseudoSelector(aDataMask, *newSel, true, michael@0: nullptr, nullptr, nullptr); michael@0: } michael@0: else if (mToken.IsSymbol('[')) { // [attribute michael@0: parsingStatus = ParseAttributeSelector(aDataMask, *newSel); michael@0: if (eSelectorParsingStatus_Error == parsingStatus) { michael@0: // Skip forward to the matching ']' michael@0: SkipUntil(']'); michael@0: } michael@0: } michael@0: else { michael@0: // then it should be a type element or universal selector michael@0: parsingStatus = ParseTypeOrUniversalSelector(aDataMask, *newSel, true); michael@0: } michael@0: if (eSelectorParsingStatus_Error == parsingStatus) { michael@0: REPORT_UNEXPECTED_TOKEN(PENegationBadInner); michael@0: SkipUntil(')'); michael@0: return parsingStatus; michael@0: } michael@0: // close the parenthesis michael@0: if (!ExpectSymbol(')', true)) { michael@0: REPORT_UNEXPECTED_TOKEN(PENegationNoClose); michael@0: SkipUntil(')'); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: michael@0: NS_ASSERTION(newSel->mNameSpace == kNameSpaceID_Unknown || michael@0: (!newSel->mIDList && !newSel->mClassList && michael@0: !newSel->mPseudoClassList && !newSel->mAttrList), michael@0: "Need to fix the serialization code to deal with this"); michael@0: michael@0: return eSelectorParsingStatus_Continue; michael@0: } michael@0: michael@0: // michael@0: // Parse the argument of a pseudo-class that has an ident arg michael@0: // michael@0: CSSParserImpl::nsSelectorParsingStatus michael@0: CSSParserImpl::ParsePseudoClassWithIdentArg(nsCSSSelector& aSelector, michael@0: nsCSSPseudoClasses::Type aType) michael@0: { michael@0: if (! GetToken(true)) { // premature eof michael@0: REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: // We expect an identifier with a language abbreviation michael@0: if (eCSSToken_Ident != mToken.mType) { michael@0: REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotIdent); michael@0: UngetToken(); michael@0: return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') michael@0: } michael@0: michael@0: // -moz-locale-dir and -moz-dir can only have values of 'ltr' or 'rtl'. michael@0: if (aType == nsCSSPseudoClasses::ePseudoClass_mozLocaleDir || michael@0: aType == nsCSSPseudoClasses::ePseudoClass_dir) { michael@0: nsContentUtils::ASCIIToLower(mToken.mIdent); // case insensitive michael@0: if (!mToken.mIdent.EqualsLiteral("ltr") && michael@0: !mToken.mIdent.EqualsLiteral("rtl")) { michael@0: REPORT_UNEXPECTED_TOKEN(PEBadDirValue); michael@0: return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') michael@0: } michael@0: } michael@0: michael@0: // Add the pseudo with the language parameter michael@0: aSelector.AddPseudoClass(aType, mToken.mIdent.get()); michael@0: michael@0: // close the parenthesis michael@0: if (!ExpectSymbol(')', true)) { michael@0: REPORT_UNEXPECTED_TOKEN(PEPseudoClassNoClose); michael@0: return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') michael@0: } michael@0: michael@0: return eSelectorParsingStatus_Continue; michael@0: } michael@0: michael@0: CSSParserImpl::nsSelectorParsingStatus michael@0: CSSParserImpl::ParsePseudoClassWithNthPairArg(nsCSSSelector& aSelector, michael@0: nsCSSPseudoClasses::Type aType) michael@0: { michael@0: int32_t numbers[2] = { 0, 0 }; michael@0: int32_t sign[2] = { 1, 1 }; michael@0: bool hasSign[2] = { false, false }; michael@0: bool lookForB = true; michael@0: michael@0: // Follow the whitespace rules as proposed in michael@0: // http://lists.w3.org/Archives/Public/www-style/2008Mar/0121.html michael@0: michael@0: if (! GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: michael@0: if (mToken.IsSymbol('+') || mToken.IsSymbol('-')) { michael@0: hasSign[0] = true; michael@0: if (mToken.IsSymbol('-')) { michael@0: sign[0] = -1; michael@0: } michael@0: if (! GetToken(false)) { michael@0: REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: } michael@0: michael@0: if (eCSSToken_Ident == mToken.mType || eCSSToken_Dimension == mToken.mType) { michael@0: // The CSS tokenization doesn't handle :nth-child() containing - well: michael@0: // 2n-1 is a dimension michael@0: // n-1 is an identifier michael@0: // The easiest way to deal with that is to push everything from the michael@0: // minus on back onto the scanner's pushback buffer. michael@0: uint32_t truncAt = 0; michael@0: if (StringBeginsWith(mToken.mIdent, NS_LITERAL_STRING("n-"))) { michael@0: truncAt = 1; michael@0: } else if (StringBeginsWith(mToken.mIdent, NS_LITERAL_STRING("-n-")) && !hasSign[0]) { michael@0: truncAt = 2; michael@0: } michael@0: if (truncAt != 0) { michael@0: mScanner->Backup(mToken.mIdent.Length() - truncAt); michael@0: mToken.mIdent.Truncate(truncAt); michael@0: } michael@0: } michael@0: michael@0: if (eCSSToken_Ident == mToken.mType) { michael@0: if (mToken.mIdent.LowerCaseEqualsLiteral("odd") && !hasSign[0]) { michael@0: numbers[0] = 2; michael@0: numbers[1] = 1; michael@0: lookForB = false; michael@0: } michael@0: else if (mToken.mIdent.LowerCaseEqualsLiteral("even") && !hasSign[0]) { michael@0: numbers[0] = 2; michael@0: numbers[1] = 0; michael@0: lookForB = false; michael@0: } michael@0: else if (mToken.mIdent.LowerCaseEqualsLiteral("n")) { michael@0: numbers[0] = sign[0]; michael@0: } michael@0: else if (mToken.mIdent.LowerCaseEqualsLiteral("-n") && !hasSign[0]) { michael@0: numbers[0] = -1; michael@0: } michael@0: else { michael@0: REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth); michael@0: return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') michael@0: } michael@0: } michael@0: else if (eCSSToken_Number == mToken.mType) { michael@0: if (!mToken.mIntegerValid) { michael@0: REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth); michael@0: return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') michael@0: } michael@0: // for +-an case michael@0: if (mToken.mHasSign && hasSign[0]) { michael@0: REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth); michael@0: return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') michael@0: } michael@0: int32_t intValue = mToken.mInteger * sign[0]; michael@0: // for -a/**/n case michael@0: if (! GetToken(false)) { michael@0: numbers[1] = intValue; michael@0: lookForB = false; michael@0: } michael@0: else { michael@0: if (eCSSToken_Ident == mToken.mType && mToken.mIdent.LowerCaseEqualsLiteral("n")) { michael@0: numbers[0] = intValue; michael@0: } michael@0: else if (eCSSToken_Ident == mToken.mType && StringBeginsWith(mToken.mIdent, NS_LITERAL_STRING("n-"))) { michael@0: numbers[0] = intValue; michael@0: mScanner->Backup(mToken.mIdent.Length() - 1); michael@0: } michael@0: else { michael@0: UngetToken(); michael@0: numbers[1] = intValue; michael@0: lookForB = false; michael@0: } michael@0: } michael@0: } michael@0: else if (eCSSToken_Dimension == mToken.mType) { michael@0: if (!mToken.mIntegerValid || !mToken.mIdent.LowerCaseEqualsLiteral("n")) { michael@0: REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth); michael@0: return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') michael@0: } michael@0: // for +-an case michael@0: if ( mToken.mHasSign && hasSign[0] ) { michael@0: REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth); michael@0: return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') michael@0: } michael@0: numbers[0] = mToken.mInteger * sign[0]; michael@0: } michael@0: // XXX If it's a ')', is that valid? (as 0n+0) michael@0: else { michael@0: REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth); michael@0: UngetToken(); michael@0: return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') michael@0: } michael@0: michael@0: if (! GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: if (lookForB && !mToken.IsSymbol(')')) { michael@0: // The '+' or '-' sign can optionally be separated by whitespace. michael@0: // If it is separated by whitespace from what follows it, it appears michael@0: // as a separate token rather than part of the number token. michael@0: if (mToken.IsSymbol('+') || mToken.IsSymbol('-')) { michael@0: hasSign[1] = true; michael@0: if (mToken.IsSymbol('-')) { michael@0: sign[1] = -1; michael@0: } michael@0: if (! GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: } michael@0: if (eCSSToken_Number != mToken.mType || michael@0: !mToken.mIntegerValid || mToken.mHasSign == hasSign[1]) { michael@0: REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth); michael@0: UngetToken(); michael@0: return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') michael@0: } michael@0: numbers[1] = mToken.mInteger * sign[1]; michael@0: if (! GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF); michael@0: return eSelectorParsingStatus_Error; michael@0: } michael@0: } michael@0: if (!mToken.IsSymbol(')')) { michael@0: REPORT_UNEXPECTED_TOKEN(PEPseudoClassNoClose); michael@0: return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') michael@0: } michael@0: aSelector.AddPseudoClass(aType, numbers); michael@0: return eSelectorParsingStatus_Continue; michael@0: } michael@0: michael@0: // michael@0: // Parse the argument of a pseudo-class that has a selector list argument. michael@0: // Such selector lists cannot contain combinators, but can contain michael@0: // anything that goes between a pair of combinators. michael@0: // michael@0: CSSParserImpl::nsSelectorParsingStatus michael@0: CSSParserImpl::ParsePseudoClassWithSelectorListArg(nsCSSSelector& aSelector, michael@0: nsCSSPseudoClasses::Type aType) michael@0: { michael@0: nsAutoPtr slist; michael@0: if (! ParseSelectorList(*getter_Transfers(slist), char16_t(')'))) { michael@0: return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') michael@0: } michael@0: michael@0: // Check that none of the selectors in the list have combinators or michael@0: // pseudo-elements. michael@0: for (nsCSSSelectorList *l = slist; l; l = l->mNext) { michael@0: nsCSSSelector *s = l->mSelectors; michael@0: if (s->mNext || s->IsPseudoElement()) { michael@0: return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') michael@0: } michael@0: } michael@0: michael@0: // Add the pseudo with the selector list parameter michael@0: aSelector.AddPseudoClass(aType, slist.forget()); michael@0: michael@0: // close the parenthesis michael@0: if (!ExpectSymbol(')', true)) { michael@0: REPORT_UNEXPECTED_TOKEN(PEPseudoClassNoClose); michael@0: return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') michael@0: } michael@0: michael@0: return eSelectorParsingStatus_Continue; michael@0: } michael@0: michael@0: michael@0: /** michael@0: * This is the format for selectors: michael@0: * operator? [[namespace |]? element_name]? [ ID | class | attrib | pseudo ]* michael@0: */ michael@0: bool michael@0: CSSParserImpl::ParseSelector(nsCSSSelectorList* aList, michael@0: char16_t aPrevCombinator) michael@0: { michael@0: if (! GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PESelectorEOF); michael@0: return false; michael@0: } michael@0: michael@0: nsCSSSelector* selector = aList->AddSelector(aPrevCombinator); michael@0: nsCOMPtr pseudoElement; michael@0: nsAutoPtr pseudoElementArgs; michael@0: nsCSSPseudoElements::Type pseudoElementType = michael@0: nsCSSPseudoElements::ePseudo_NotPseudoElement; michael@0: michael@0: int32_t dataMask = 0; michael@0: nsSelectorParsingStatus parsingStatus = michael@0: ParseTypeOrUniversalSelector(dataMask, *selector, false); michael@0: michael@0: while (parsingStatus == eSelectorParsingStatus_Continue) { michael@0: if (eCSSToken_ID == mToken.mType) { // #id michael@0: parsingStatus = ParseIDSelector(dataMask, *selector); michael@0: } michael@0: else if (mToken.IsSymbol('.')) { // .class michael@0: parsingStatus = ParseClassSelector(dataMask, *selector); michael@0: } michael@0: else if (mToken.IsSymbol(':')) { // :pseudo michael@0: parsingStatus = ParsePseudoSelector(dataMask, *selector, false, michael@0: getter_AddRefs(pseudoElement), michael@0: getter_Transfers(pseudoElementArgs), michael@0: &pseudoElementType); michael@0: if (pseudoElement && michael@0: pseudoElementType != nsCSSPseudoElements::ePseudo_AnonBox) { michael@0: // Pseudo-elements other than anonymous boxes are represented with michael@0: // a special ':' combinator. michael@0: michael@0: aList->mWeight += selector->CalcWeight(); michael@0: michael@0: selector = aList->AddSelector(':'); michael@0: michael@0: selector->mLowercaseTag.swap(pseudoElement); michael@0: selector->mClassList = pseudoElementArgs.forget(); michael@0: selector->SetPseudoType(pseudoElementType); michael@0: } michael@0: } michael@0: else if (mToken.IsSymbol('[')) { // [attribute michael@0: parsingStatus = ParseAttributeSelector(dataMask, *selector); michael@0: if (eSelectorParsingStatus_Error == parsingStatus) { michael@0: SkipUntil(']'); michael@0: } michael@0: } michael@0: else { // not a selector token, we're done michael@0: parsingStatus = eSelectorParsingStatus_Done; michael@0: UngetToken(); michael@0: break; michael@0: } michael@0: michael@0: if (parsingStatus != eSelectorParsingStatus_Continue) { michael@0: break; michael@0: } michael@0: michael@0: if (! GetToken(false)) { // premature eof is ok (here!) michael@0: parsingStatus = eSelectorParsingStatus_Done; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (parsingStatus == eSelectorParsingStatus_Error) { michael@0: return false; michael@0: } michael@0: michael@0: if (!dataMask) { michael@0: if (selector->mNext) { michael@0: REPORT_UNEXPECTED(PESelectorGroupExtraCombinator); michael@0: } else { michael@0: REPORT_UNEXPECTED(PESelectorGroupNoSelector); michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: if (pseudoElementType == nsCSSPseudoElements::ePseudo_AnonBox) { michael@0: // We got an anonymous box pseudo-element; it must be the only michael@0: // thing in this selector group. michael@0: if (selector->mNext || !IsUniversalSelector(*selector)) { michael@0: REPORT_UNEXPECTED(PEAnonBoxNotAlone); michael@0: return false; michael@0: } michael@0: michael@0: // Rewrite the current selector as this pseudo-element. michael@0: // It does not contribute to selector weight. michael@0: selector->mLowercaseTag.swap(pseudoElement); michael@0: selector->mClassList = pseudoElementArgs.forget(); michael@0: selector->SetPseudoType(pseudoElementType); michael@0: return true; michael@0: } michael@0: michael@0: aList->mWeight += selector->CalcWeight(); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: css::Declaration* michael@0: CSSParserImpl::ParseDeclarationBlock(uint32_t aFlags, nsCSSContextType aContext) michael@0: { michael@0: bool checkForBraces = (aFlags & eParseDeclaration_InBraces) != 0; michael@0: michael@0: if (checkForBraces) { michael@0: if (!ExpectSymbol('{', true)) { michael@0: REPORT_UNEXPECTED_TOKEN(PEBadDeclBlockStart); michael@0: OUTPUT_ERROR(); michael@0: return nullptr; michael@0: } michael@0: } michael@0: css::Declaration* declaration = new css::Declaration(); michael@0: mData.AssertInitialState(); michael@0: if (declaration) { michael@0: for (;;) { michael@0: bool changed; michael@0: if (!ParseDeclaration(declaration, aFlags, true, &changed, aContext)) { michael@0: if (!SkipDeclaration(checkForBraces)) { michael@0: break; michael@0: } michael@0: if (checkForBraces) { michael@0: if (ExpectSymbol('}', true)) { michael@0: break; michael@0: } michael@0: } michael@0: // Since the skipped declaration didn't end the block we parse michael@0: // the next declaration. michael@0: } michael@0: } michael@0: declaration->CompressFrom(&mData); michael@0: } michael@0: return declaration; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseColor(nsCSSValue& aValue) michael@0: { michael@0: if (!GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PEColorEOF); michael@0: return false; michael@0: } michael@0: michael@0: nsCSSToken* tk = &mToken; michael@0: nscolor rgba; michael@0: switch (tk->mType) { michael@0: case eCSSToken_ID: michael@0: case eCSSToken_Hash: michael@0: // #xxyyzz michael@0: if (NS_HexToRGB(tk->mIdent, &rgba)) { michael@0: MOZ_ASSERT(tk->mIdent.Length() == 3 || tk->mIdent.Length() == 6, michael@0: "unexpected hex color length"); michael@0: nsCSSUnit unit = tk->mIdent.Length() == 3 ? michael@0: eCSSUnit_ShortHexColor : michael@0: eCSSUnit_HexColor; michael@0: aValue.SetIntegerColorValue(rgba, unit); michael@0: return true; michael@0: } michael@0: break; michael@0: michael@0: case eCSSToken_Ident: michael@0: if (NS_ColorNameToRGB(tk->mIdent, &rgba)) { michael@0: aValue.SetStringValue(tk->mIdent, eCSSUnit_Ident); michael@0: return true; michael@0: } michael@0: else { michael@0: nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(tk->mIdent); michael@0: if (eCSSKeyword_UNKNOWN < keyword) { // known keyword michael@0: int32_t value; michael@0: if (nsCSSProps::FindKeyword(keyword, nsCSSProps::kColorKTable, value)) { michael@0: aValue.SetIntValue(value, eCSSUnit_EnumColor); michael@0: return true; michael@0: } michael@0: } michael@0: } michael@0: break; michael@0: case eCSSToken_Function: michael@0: if (mToken.mIdent.LowerCaseEqualsLiteral("rgb")) { michael@0: // rgb ( component , component , component ) michael@0: if (GetToken(true)) { michael@0: UngetToken(); michael@0: } michael@0: if (mToken.mType == eCSSToken_Number) { michael@0: uint8_t r, g, b; michael@0: if (ParseNumberColorComponent(r, ',') && michael@0: ParseNumberColorComponent(g, ',') && michael@0: ParseNumberColorComponent(b, ')')) { michael@0: aValue.SetIntegerColorValue(NS_RGB(r, g, b), eCSSUnit_RGBColor); michael@0: return true; michael@0: } michael@0: } else { michael@0: float r, g, b; michael@0: if (ParsePercentageColorComponent(r, ',') && michael@0: ParsePercentageColorComponent(g, ',') && michael@0: ParsePercentageColorComponent(b, ')')) { michael@0: aValue.SetFloatColorValue(r, g, b, 1.0f, michael@0: eCSSUnit_PercentageRGBColor); michael@0: return true; michael@0: } michael@0: } michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: else if (mToken.mIdent.LowerCaseEqualsLiteral("rgba")) { michael@0: // rgba ( component , component , component , opacity ) michael@0: if (GetToken(true)) { michael@0: UngetToken(); michael@0: } michael@0: if (mToken.mType == eCSSToken_Number) { michael@0: uint8_t r, g, b, a; michael@0: if (ParseNumberColorComponent(r, ',') && michael@0: ParseNumberColorComponent(g, ',') && michael@0: ParseNumberColorComponent(b, ',') && michael@0: ParseColorOpacity(a)) { michael@0: aValue.SetIntegerColorValue(NS_RGBA(r, g, b, a), michael@0: eCSSUnit_RGBAColor); michael@0: return true; michael@0: } michael@0: } else { michael@0: float r, g, b, a; michael@0: if (ParsePercentageColorComponent(r, ',') && michael@0: ParsePercentageColorComponent(g, ',') && michael@0: ParsePercentageColorComponent(b, ',') && michael@0: ParseColorOpacity(a)) { michael@0: aValue.SetFloatColorValue(r, g, b, a, eCSSUnit_PercentageRGBAColor); michael@0: return true; michael@0: } michael@0: } michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: else if (mToken.mIdent.LowerCaseEqualsLiteral("hsl")) { michael@0: // hsl ( hue , saturation , lightness ) michael@0: // "hue" is a number, "saturation" and "lightness" are percentages. michael@0: float h, s, l; michael@0: if (ParseHSLColor(h, s, l, ')')) { michael@0: aValue.SetFloatColorValue(h, s, l, 1.0f, eCSSUnit_HSLColor); michael@0: return true; michael@0: } michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: else if (mToken.mIdent.LowerCaseEqualsLiteral("hsla")) { michael@0: // hsla ( hue , saturation , lightness , opacity ) michael@0: // "hue" is a number, "saturation" and "lightness" are percentages, michael@0: // "opacity" is a number. michael@0: float h, s, l, a; michael@0: if (ParseHSLColor(h, s, l, ',') && michael@0: ParseColorOpacity(a)) { michael@0: aValue.SetFloatColorValue(h, s, l, a, eCSSUnit_HSLAColor); michael@0: return true; michael@0: } michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: michael@0: // try 'xxyyzz' without '#' prefix for compatibility with IE and Nav4x (bug 23236 and 45804) michael@0: if (mHashlessColorQuirk) { michael@0: // - If the string starts with 'a-f', the nsCSSScanner builds the michael@0: // token as a eCSSToken_Ident and we can parse the string as a michael@0: // 'xxyyzz' RGB color. michael@0: // - If it only contains '0-9' digits, the token is a michael@0: // eCSSToken_Number and it must be converted back to a 6 michael@0: // characters string to be parsed as a RGB color. michael@0: // - If it starts with '0-9' and contains any 'a-f', the token is a michael@0: // eCSSToken_Dimension, the mNumber part must be converted back to michael@0: // a string and the mIdent part must be appended to that string so michael@0: // that the resulting string has 6 characters. michael@0: // Note: This is a hack for Nav compatibility. Do not attempt to michael@0: // simplify it by hacking into the ncCSSScanner. This would be very michael@0: // bad. michael@0: nsAutoString str; michael@0: char buffer[20]; michael@0: switch (tk->mType) { michael@0: case eCSSToken_Ident: michael@0: str.Assign(tk->mIdent); michael@0: break; michael@0: michael@0: case eCSSToken_Number: michael@0: if (tk->mIntegerValid) { michael@0: PR_snprintf(buffer, sizeof(buffer), "%06d", tk->mInteger); michael@0: str.AssignWithConversion(buffer); michael@0: } michael@0: break; michael@0: michael@0: case eCSSToken_Dimension: michael@0: if (tk->mIdent.Length() <= 6) { michael@0: PR_snprintf(buffer, sizeof(buffer), "%06.0f", tk->mNumber); michael@0: nsAutoString temp; michael@0: temp.AssignWithConversion(buffer); michael@0: temp.Right(str, 6 - tk->mIdent.Length()); michael@0: str.Append(tk->mIdent); michael@0: } michael@0: break; michael@0: default: michael@0: // There is a whole bunch of cases that are michael@0: // not handled by this switch. Ignore them. michael@0: break; michael@0: } michael@0: if (NS_HexToRGB(str, &rgba)) { michael@0: aValue.SetIntegerColorValue(rgba, eCSSUnit_HexColor); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: // It's not a color michael@0: REPORT_UNEXPECTED_TOKEN(PEColorNotColor); michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseNumberColorComponent(uint8_t& aComponent, char aStop) michael@0: { michael@0: if (!GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PEColorComponentEOF); michael@0: return false; michael@0: } michael@0: michael@0: if (mToken.mType != eCSSToken_Number || !mToken.mIntegerValid) { michael@0: REPORT_UNEXPECTED_TOKEN(PEExpectedInt); michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: michael@0: float value = mToken.mNumber; michael@0: if (value < 0.0f) value = 0.0f; michael@0: if (value > 255.0f) value = 255.0f; michael@0: michael@0: if (ExpectSymbol(aStop, true)) { michael@0: aComponent = NSToIntRound(value); michael@0: return true; michael@0: } michael@0: REPORT_UNEXPECTED_TOKEN_CHAR(PEColorComponentBadTerm, aStop); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParsePercentageColorComponent(float& aComponent, char aStop) michael@0: { michael@0: if (!GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PEColorComponentEOF); michael@0: return false; michael@0: } michael@0: michael@0: if (mToken.mType != eCSSToken_Percentage) { michael@0: REPORT_UNEXPECTED_TOKEN(PEExpectedPercent); michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: michael@0: float value = mToken.mNumber; michael@0: if (value < 0.0f) value = 0.0f; michael@0: if (value > 1.0f) value = 1.0f; michael@0: michael@0: if (ExpectSymbol(aStop, true)) { michael@0: aComponent = value; michael@0: return true; michael@0: } michael@0: REPORT_UNEXPECTED_TOKEN_CHAR(PEColorComponentBadTerm, aStop); michael@0: return false; michael@0: } michael@0: michael@0: michael@0: bool michael@0: CSSParserImpl::ParseHSLColor(float& aHue, float& aSaturation, float& aLightness, michael@0: char aStop) michael@0: { michael@0: float h, s, l; michael@0: michael@0: // Get the hue michael@0: if (!GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PEColorHueEOF); michael@0: return false; michael@0: } michael@0: if (mToken.mType != eCSSToken_Number) { michael@0: REPORT_UNEXPECTED_TOKEN(PEExpectedNumber); michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: h = mToken.mNumber; michael@0: h /= 360.0f; michael@0: // hue values are wraparound michael@0: h = h - floor(h); michael@0: michael@0: if (!ExpectSymbol(',', true)) { michael@0: REPORT_UNEXPECTED_TOKEN(PEExpectedComma); michael@0: return false; michael@0: } michael@0: michael@0: // Get the saturation michael@0: if (!GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PEColorSaturationEOF); michael@0: return false; michael@0: } michael@0: if (mToken.mType != eCSSToken_Percentage) { michael@0: REPORT_UNEXPECTED_TOKEN(PEExpectedPercent); michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: s = mToken.mNumber; michael@0: if (s < 0.0f) s = 0.0f; michael@0: if (s > 1.0f) s = 1.0f; michael@0: michael@0: if (!ExpectSymbol(',', true)) { michael@0: REPORT_UNEXPECTED_TOKEN(PEExpectedComma); michael@0: return false; michael@0: } michael@0: michael@0: // Get the lightness michael@0: if (!GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PEColorLightnessEOF); michael@0: return false; michael@0: } michael@0: if (mToken.mType != eCSSToken_Percentage) { michael@0: REPORT_UNEXPECTED_TOKEN(PEExpectedPercent); michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: l = mToken.mNumber; michael@0: if (l < 0.0f) l = 0.0f; michael@0: if (l > 1.0f) l = 1.0f; michael@0: michael@0: if (ExpectSymbol(aStop, true)) { michael@0: aHue = h; michael@0: aSaturation = s; michael@0: aLightness = l; michael@0: return true; michael@0: } michael@0: michael@0: REPORT_UNEXPECTED_TOKEN_CHAR(PEColorComponentBadTerm, aStop); michael@0: return false; michael@0: } michael@0: michael@0: michael@0: bool michael@0: CSSParserImpl::ParseColorOpacity(uint8_t& aOpacity) michael@0: { michael@0: float floatOpacity; michael@0: if (!ParseColorOpacity(floatOpacity)) { michael@0: return false; michael@0: } michael@0: michael@0: uint8_t value = nsStyleUtil::FloatToColorComponent(floatOpacity); michael@0: // Need to compare to something slightly larger michael@0: // than 0.5 due to floating point inaccuracies. michael@0: NS_ASSERTION(fabs(255.0f*mToken.mNumber - value) <= 0.51f, michael@0: "FloatToColorComponent did something weird"); michael@0: michael@0: aOpacity = value; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseColorOpacity(float& aOpacity) michael@0: { michael@0: if (!GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PEColorOpacityEOF); michael@0: return false; michael@0: } michael@0: michael@0: if (mToken.mType != eCSSToken_Number) { michael@0: REPORT_UNEXPECTED_TOKEN(PEExpectedNumber); michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: michael@0: if (!ExpectSymbol(')', true)) { michael@0: REPORT_UNEXPECTED_TOKEN(PEExpectedCloseParen); michael@0: return false; michael@0: } michael@0: michael@0: if (mToken.mNumber < 0.0f) { michael@0: mToken.mNumber = 0.0f; michael@0: } else if (mToken.mNumber > 1.0f) { michael@0: mToken.mNumber = 1.0f; michael@0: } michael@0: michael@0: aOpacity = mToken.mNumber; michael@0: return true; michael@0: } michael@0: michael@0: #ifdef MOZ_XUL michael@0: bool michael@0: CSSParserImpl::ParseTreePseudoElement(nsAtomList **aPseudoElementArgs) michael@0: { michael@0: // The argument to a tree pseudo-element is a sequence of identifiers michael@0: // that are either space- or comma-separated. (Was the intent to michael@0: // allow only comma-separated? That's not what was done.) michael@0: nsCSSSelector fakeSelector; // so we can reuse AddPseudoClass michael@0: michael@0: while (!ExpectSymbol(')', true)) { michael@0: if (!GetToken(true)) { michael@0: return false; michael@0: } michael@0: if (eCSSToken_Ident == mToken.mType) { michael@0: fakeSelector.AddClass(mToken.mIdent); michael@0: } michael@0: else if (!mToken.IsSymbol(',')) { michael@0: UngetToken(); michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: } michael@0: *aPseudoElementArgs = fakeSelector.mClassList; michael@0: fakeSelector.mClassList = nullptr; michael@0: return true; michael@0: } michael@0: #endif michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: bool michael@0: CSSParserImpl::ParseDeclaration(css::Declaration* aDeclaration, michael@0: uint32_t aFlags, michael@0: bool aMustCallValueAppended, michael@0: bool* aChanged, michael@0: nsCSSContextType aContext) michael@0: { michael@0: NS_PRECONDITION(aContext == eCSSContext_General || michael@0: aContext == eCSSContext_Page, michael@0: "Must be page or general context"); michael@0: michael@0: bool checkForBraces = (aFlags & eParseDeclaration_InBraces) != 0; michael@0: michael@0: mTempData.AssertInitialState(); michael@0: michael@0: // Get property name michael@0: nsCSSToken* tk = &mToken; michael@0: nsAutoString propertyName; michael@0: for (;;) { michael@0: if (!GetToken(true)) { michael@0: if (checkForBraces) { michael@0: REPORT_UNEXPECTED_EOF(PEDeclEndEOF); michael@0: } michael@0: return false; michael@0: } michael@0: if (eCSSToken_Ident == tk->mType) { michael@0: propertyName = tk->mIdent; michael@0: // grab the ident before the ExpectSymbol trashes the token michael@0: if (!ExpectSymbol(':', true)) { michael@0: REPORT_UNEXPECTED_TOKEN(PEParseDeclarationNoColon); michael@0: REPORT_UNEXPECTED(PEDeclDropped); michael@0: OUTPUT_ERROR(); michael@0: return false; michael@0: } michael@0: break; michael@0: } michael@0: if (tk->IsSymbol(';')) { michael@0: // dangling semicolons are skipped michael@0: continue; michael@0: } michael@0: michael@0: if (!tk->IsSymbol('}')) { michael@0: REPORT_UNEXPECTED_TOKEN(PEParseDeclarationDeclExpected); michael@0: REPORT_UNEXPECTED(PEDeclSkipped); michael@0: OUTPUT_ERROR(); michael@0: michael@0: if (eCSSToken_AtKeyword == tk->mType) { michael@0: SkipAtRule(checkForBraces); michael@0: return true; // Not a declaration, but don't skip until ';' michael@0: } michael@0: } michael@0: // Not a declaration... michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: michael@0: // Don't report property parse errors if we're inside a failing @supports michael@0: // rule. michael@0: nsAutoSuppressErrors suppressErrors(this, mInFailingSupportsRule); michael@0: michael@0: // Information about a parsed non-custom property. michael@0: nsCSSProperty propID; michael@0: michael@0: // Information about a parsed custom property. michael@0: CSSVariableDeclarations::Type variableType; michael@0: nsString variableValue; michael@0: michael@0: // Check if the property name is a custom property. michael@0: bool customProperty = nsLayoutUtils::CSSVariablesEnabled() && michael@0: nsCSSProps::IsCustomPropertyName(propertyName) && michael@0: aContext == eCSSContext_General; michael@0: michael@0: if (customProperty) { michael@0: if (!ParseVariableDeclaration(&variableType, variableValue)) { michael@0: REPORT_UNEXPECTED_P(PEValueParsingError, propertyName); michael@0: REPORT_UNEXPECTED(PEDeclDropped); michael@0: OUTPUT_ERROR(); michael@0: return false; michael@0: } michael@0: } else { michael@0: // Map property name to its ID. michael@0: propID = LookupEnabledProperty(propertyName); michael@0: if (eCSSProperty_UNKNOWN == propID || michael@0: (aContext == eCSSContext_Page && michael@0: !nsCSSProps::PropHasFlags(propID, michael@0: CSS_PROPERTY_APPLIES_TO_PAGE_RULE))) { // unknown property michael@0: if (!NonMozillaVendorIdentifier(propertyName)) { michael@0: REPORT_UNEXPECTED_P(PEUnknownProperty, propertyName); michael@0: REPORT_UNEXPECTED(PEDeclDropped); michael@0: OUTPUT_ERROR(); michael@0: } michael@0: return false; michael@0: } michael@0: // Then parse the property. michael@0: if (!ParseProperty(propID)) { michael@0: // XXX Much better to put stuff in the value parsers instead... michael@0: REPORT_UNEXPECTED_P(PEValueParsingError, propertyName); michael@0: REPORT_UNEXPECTED(PEDeclDropped); michael@0: OUTPUT_ERROR(); michael@0: mTempData.ClearProperty(propID); michael@0: mTempData.AssertInitialState(); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: CLEAR_ERROR(); michael@0: michael@0: // Look for "!important". michael@0: PriorityParsingStatus status; michael@0: if ((aFlags & eParseDeclaration_AllowImportant) != 0) { michael@0: status = ParsePriority(); michael@0: } else { michael@0: status = ePriority_None; michael@0: } michael@0: michael@0: // Look for a semicolon or close brace. michael@0: if (status != ePriority_Error) { michael@0: if (!GetToken(true)) { michael@0: // EOF is always ok michael@0: } else if (mToken.IsSymbol(';')) { michael@0: // semicolon is always ok michael@0: } else if (mToken.IsSymbol('}')) { michael@0: // brace is ok if checkForBraces, but don't eat it michael@0: UngetToken(); michael@0: if (!checkForBraces) { michael@0: status = ePriority_Error; michael@0: } michael@0: } else { michael@0: UngetToken(); michael@0: status = ePriority_Error; michael@0: } michael@0: } michael@0: michael@0: if (status == ePriority_Error) { michael@0: if (checkForBraces) { michael@0: REPORT_UNEXPECTED_TOKEN(PEBadDeclOrRuleEnd2); michael@0: } else { michael@0: REPORT_UNEXPECTED_TOKEN(PEBadDeclEnd); michael@0: } michael@0: REPORT_UNEXPECTED(PEDeclDropped); michael@0: OUTPUT_ERROR(); michael@0: if (!customProperty) { michael@0: mTempData.ClearProperty(propID); michael@0: } michael@0: mTempData.AssertInitialState(); michael@0: return false; michael@0: } michael@0: michael@0: if (customProperty) { michael@0: MOZ_ASSERT(Substring(propertyName, 0, michael@0: CSS_CUSTOM_NAME_PREFIX_LENGTH).EqualsLiteral("--")); michael@0: // remove '--' michael@0: nsDependentString varName(propertyName, CSS_CUSTOM_NAME_PREFIX_LENGTH); michael@0: aDeclaration->AddVariableDeclaration(varName, variableType, variableValue, michael@0: status == ePriority_Important, false); michael@0: } else { michael@0: *aChanged |= mData.TransferFromBlock(mTempData, propID, michael@0: status == ePriority_Important, michael@0: false, aMustCallValueAppended, michael@0: aDeclaration); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static const nsCSSProperty kBorderTopIDs[] = { michael@0: eCSSProperty_border_top_width, michael@0: eCSSProperty_border_top_style, michael@0: eCSSProperty_border_top_color michael@0: }; michael@0: static const nsCSSProperty kBorderRightIDs[] = { michael@0: eCSSProperty_border_right_width_value, michael@0: eCSSProperty_border_right_style_value, michael@0: eCSSProperty_border_right_color_value, michael@0: eCSSProperty_border_right_width, michael@0: eCSSProperty_border_right_style, michael@0: eCSSProperty_border_right_color michael@0: }; michael@0: static const nsCSSProperty kBorderBottomIDs[] = { michael@0: eCSSProperty_border_bottom_width, michael@0: eCSSProperty_border_bottom_style, michael@0: eCSSProperty_border_bottom_color michael@0: }; michael@0: static const nsCSSProperty kBorderLeftIDs[] = { michael@0: eCSSProperty_border_left_width_value, michael@0: eCSSProperty_border_left_style_value, michael@0: eCSSProperty_border_left_color_value, michael@0: eCSSProperty_border_left_width, michael@0: eCSSProperty_border_left_style, michael@0: eCSSProperty_border_left_color michael@0: }; michael@0: static const nsCSSProperty kBorderStartIDs[] = { michael@0: eCSSProperty_border_start_width_value, michael@0: eCSSProperty_border_start_style_value, michael@0: eCSSProperty_border_start_color_value, michael@0: eCSSProperty_border_start_width, michael@0: eCSSProperty_border_start_style, michael@0: eCSSProperty_border_start_color michael@0: }; michael@0: static const nsCSSProperty kBorderEndIDs[] = { michael@0: eCSSProperty_border_end_width_value, michael@0: eCSSProperty_border_end_style_value, michael@0: eCSSProperty_border_end_color_value, michael@0: eCSSProperty_border_end_width, michael@0: eCSSProperty_border_end_style, michael@0: eCSSProperty_border_end_color michael@0: }; michael@0: static const nsCSSProperty kColumnRuleIDs[] = { michael@0: eCSSProperty__moz_column_rule_width, michael@0: eCSSProperty__moz_column_rule_style, michael@0: eCSSProperty__moz_column_rule_color michael@0: }; michael@0: michael@0: bool michael@0: CSSParserImpl::ParseEnum(nsCSSValue& aValue, michael@0: const KTableValue aKeywordTable[]) michael@0: { michael@0: nsSubstring* ident = NextIdent(); michael@0: if (nullptr == ident) { michael@0: return false; michael@0: } michael@0: nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(*ident); michael@0: if (eCSSKeyword_UNKNOWN < keyword) { michael@0: int32_t value; michael@0: if (nsCSSProps::FindKeyword(keyword, aKeywordTable, value)) { michael@0: aValue.SetIntValue(value, eCSSUnit_Enumerated); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: // Put the unknown identifier back and return michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: michael@0: michael@0: struct UnitInfo { michael@0: char name[6]; // needs to be long enough for the longest unit, with michael@0: // terminating null. michael@0: uint32_t length; michael@0: nsCSSUnit unit; michael@0: int32_t type; michael@0: }; michael@0: michael@0: #define STR_WITH_LEN(_str) \ michael@0: _str, sizeof(_str) - 1 michael@0: michael@0: const UnitInfo UnitData[] = { michael@0: { STR_WITH_LEN("px"), eCSSUnit_Pixel, VARIANT_LENGTH }, michael@0: { STR_WITH_LEN("em"), eCSSUnit_EM, VARIANT_LENGTH }, michael@0: { STR_WITH_LEN("ex"), eCSSUnit_XHeight, VARIANT_LENGTH }, michael@0: { STR_WITH_LEN("pt"), eCSSUnit_Point, VARIANT_LENGTH }, michael@0: { STR_WITH_LEN("in"), eCSSUnit_Inch, VARIANT_LENGTH }, michael@0: { STR_WITH_LEN("cm"), eCSSUnit_Centimeter, VARIANT_LENGTH }, michael@0: { STR_WITH_LEN("ch"), eCSSUnit_Char, VARIANT_LENGTH }, michael@0: { STR_WITH_LEN("rem"), eCSSUnit_RootEM, VARIANT_LENGTH }, michael@0: { STR_WITH_LEN("mm"), eCSSUnit_Millimeter, VARIANT_LENGTH }, michael@0: { STR_WITH_LEN("mozmm"), eCSSUnit_PhysicalMillimeter, VARIANT_LENGTH }, michael@0: { STR_WITH_LEN("vw"), eCSSUnit_ViewportWidth, VARIANT_LENGTH }, michael@0: { STR_WITH_LEN("vh"), eCSSUnit_ViewportHeight, VARIANT_LENGTH }, michael@0: { STR_WITH_LEN("vmin"), eCSSUnit_ViewportMin, VARIANT_LENGTH }, michael@0: { STR_WITH_LEN("vmax"), eCSSUnit_ViewportMax, VARIANT_LENGTH }, michael@0: { STR_WITH_LEN("pc"), eCSSUnit_Pica, VARIANT_LENGTH }, michael@0: { STR_WITH_LEN("deg"), eCSSUnit_Degree, VARIANT_ANGLE }, michael@0: { STR_WITH_LEN("grad"), eCSSUnit_Grad, VARIANT_ANGLE }, michael@0: { STR_WITH_LEN("rad"), eCSSUnit_Radian, VARIANT_ANGLE }, michael@0: { STR_WITH_LEN("turn"), eCSSUnit_Turn, VARIANT_ANGLE }, michael@0: { STR_WITH_LEN("hz"), eCSSUnit_Hertz, VARIANT_FREQUENCY }, michael@0: { STR_WITH_LEN("khz"), eCSSUnit_Kilohertz, VARIANT_FREQUENCY }, michael@0: { STR_WITH_LEN("s"), eCSSUnit_Seconds, VARIANT_TIME }, michael@0: { STR_WITH_LEN("ms"), eCSSUnit_Milliseconds, VARIANT_TIME } michael@0: }; michael@0: michael@0: #undef STR_WITH_LEN michael@0: michael@0: bool michael@0: CSSParserImpl::TranslateDimension(nsCSSValue& aValue, michael@0: int32_t aVariantMask, michael@0: float aNumber, michael@0: const nsString& aUnit) michael@0: { michael@0: nsCSSUnit units; michael@0: int32_t type = 0; michael@0: if (!aUnit.IsEmpty()) { michael@0: uint32_t i; michael@0: for (i = 0; i < ArrayLength(UnitData); ++i) { michael@0: if (aUnit.LowerCaseEqualsASCII(UnitData[i].name, michael@0: UnitData[i].length)) { michael@0: units = UnitData[i].unit; michael@0: type = UnitData[i].type; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (i == ArrayLength(UnitData)) { michael@0: // Unknown unit michael@0: return false; michael@0: } michael@0: michael@0: if (!mViewportUnitsEnabled && michael@0: (eCSSUnit_ViewportWidth == units || michael@0: eCSSUnit_ViewportHeight == units || michael@0: eCSSUnit_ViewportMin == units || michael@0: eCSSUnit_ViewportMax == units)) { michael@0: // Viewport units aren't allowed right now, probably because we're michael@0: // inside an @page declaration. Fail. michael@0: return false; michael@0: } michael@0: } else { michael@0: // Must be a zero number... michael@0: NS_ASSERTION(0 == aNumber, "numbers without units must be 0"); michael@0: if ((VARIANT_LENGTH & aVariantMask) != 0) { michael@0: units = eCSSUnit_Pixel; michael@0: type = VARIANT_LENGTH; michael@0: } michael@0: else if ((VARIANT_ANGLE & aVariantMask) != 0) { michael@0: NS_ASSERTION(aVariantMask & VARIANT_ZERO_ANGLE, michael@0: "must have allowed zero angle"); michael@0: units = eCSSUnit_Degree; michael@0: type = VARIANT_ANGLE; michael@0: } michael@0: else { michael@0: NS_ERROR("Variant mask does not include dimension; why were we called?"); michael@0: return false; michael@0: } michael@0: } michael@0: if ((type & aVariantMask) != 0) { michael@0: aValue.SetFloatValue(aNumber, units); michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: // Note that this does include VARIANT_CALC, which is numeric. This is michael@0: // because calc() parsing, as proposed, drops range restrictions inside michael@0: // the calc() expression and clamps the result of the calculation to the michael@0: // range. michael@0: #define VARIANT_ALL_NONNUMERIC \ michael@0: VARIANT_KEYWORD | \ michael@0: VARIANT_COLOR | \ michael@0: VARIANT_URL | \ michael@0: VARIANT_STRING | \ michael@0: VARIANT_COUNTER | \ michael@0: VARIANT_ATTR | \ michael@0: VARIANT_IDENTIFIER | \ michael@0: VARIANT_IDENTIFIER_NO_INHERIT | \ michael@0: VARIANT_AUTO | \ michael@0: VARIANT_INHERIT | \ michael@0: VARIANT_NONE | \ michael@0: VARIANT_NORMAL | \ michael@0: VARIANT_SYSFONT | \ michael@0: VARIANT_GRADIENT | \ michael@0: VARIANT_TIMING_FUNCTION | \ michael@0: VARIANT_ALL | \ michael@0: VARIANT_CALC | \ michael@0: VARIANT_OPENTYPE_SVG_KEYWORD michael@0: michael@0: // Note that callers passing VARIANT_CALC in aVariantMask will get michael@0: // full-range parsing inside the calc() expression, and the code that michael@0: // computes the calc will be required to clamp the resulting value to an michael@0: // appropriate range. michael@0: bool michael@0: CSSParserImpl::ParseNonNegativeVariant(nsCSSValue& aValue, michael@0: int32_t aVariantMask, michael@0: const KTableValue aKeywordTable[]) michael@0: { michael@0: // The variant mask must only contain non-numeric variants or the ones michael@0: // that we specifically handle. michael@0: NS_ABORT_IF_FALSE((aVariantMask & ~(VARIANT_ALL_NONNUMERIC | michael@0: VARIANT_NUMBER | michael@0: VARIANT_LENGTH | michael@0: VARIANT_PERCENT | michael@0: VARIANT_INTEGER)) == 0, michael@0: "need to update code below to handle additional variants"); michael@0: michael@0: if (ParseVariant(aValue, aVariantMask, aKeywordTable)) { michael@0: if (eCSSUnit_Number == aValue.GetUnit() || michael@0: aValue.IsLengthUnit()){ michael@0: if (aValue.GetFloatValue() < 0) { michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: } michael@0: else if (aValue.GetUnit() == eCSSUnit_Percent) { michael@0: if (aValue.GetPercentValue() < 0) { michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: } else if (aValue.GetUnit() == eCSSUnit_Integer) { michael@0: if (aValue.GetIntValue() < 0) { michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: // Note that callers passing VARIANT_CALC in aVariantMask will get michael@0: // full-range parsing inside the calc() expression, and the code that michael@0: // computes the calc will be required to clamp the resulting value to an michael@0: // appropriate range. michael@0: bool michael@0: CSSParserImpl::ParseOneOrLargerVariant(nsCSSValue& aValue, michael@0: int32_t aVariantMask, michael@0: const KTableValue aKeywordTable[]) michael@0: { michael@0: // The variant mask must only contain non-numeric variants or the ones michael@0: // that we specifically handle. michael@0: NS_ABORT_IF_FALSE((aVariantMask & ~(VARIANT_ALL_NONNUMERIC | michael@0: VARIANT_NUMBER | michael@0: VARIANT_INTEGER)) == 0, michael@0: "need to update code below to handle additional variants"); michael@0: michael@0: if (ParseVariant(aValue, aVariantMask, aKeywordTable)) { michael@0: if (aValue.GetUnit() == eCSSUnit_Integer) { michael@0: if (aValue.GetIntValue() < 1) { michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: } else if (eCSSUnit_Number == aValue.GetUnit()) { michael@0: if (aValue.GetFloatValue() < 1.0f) { michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: // Assigns to aValue iff it returns true. michael@0: bool michael@0: CSSParserImpl::ParseVariant(nsCSSValue& aValue, michael@0: int32_t aVariantMask, michael@0: const KTableValue aKeywordTable[]) michael@0: { michael@0: NS_ASSERTION(!(mHashlessColorQuirk && (aVariantMask & VARIANT_COLOR)) || michael@0: !(aVariantMask & VARIANT_NUMBER), michael@0: "can't distinguish colors from numbers"); michael@0: NS_ASSERTION(!(mHashlessColorQuirk && (aVariantMask & VARIANT_COLOR)) || michael@0: !(mUnitlessLengthQuirk && (aVariantMask & VARIANT_LENGTH)), michael@0: "can't distinguish colors from lengths"); michael@0: NS_ASSERTION(!(mUnitlessLengthQuirk && (aVariantMask & VARIANT_LENGTH)) || michael@0: !(aVariantMask & VARIANT_NUMBER), michael@0: "can't distinguish lengths from numbers"); michael@0: NS_ABORT_IF_FALSE(!(aVariantMask & VARIANT_IDENTIFIER) || michael@0: !(aVariantMask & VARIANT_IDENTIFIER_NO_INHERIT), michael@0: "must not set both VARIANT_IDENTIFIER and " michael@0: "VARIANT_IDENTIFIER_NO_INHERIT"); michael@0: michael@0: if (!GetToken(true)) { michael@0: return false; michael@0: } michael@0: nsCSSToken* tk = &mToken; michael@0: if (((aVariantMask & (VARIANT_AHK | VARIANT_NORMAL | VARIANT_NONE | VARIANT_ALL)) != 0) && michael@0: (eCSSToken_Ident == tk->mType)) { michael@0: nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(tk->mIdent); michael@0: if (eCSSKeyword_UNKNOWN < keyword) { // known keyword michael@0: if ((aVariantMask & VARIANT_AUTO) != 0) { michael@0: if (eCSSKeyword_auto == keyword) { michael@0: aValue.SetAutoValue(); michael@0: return true; michael@0: } michael@0: } michael@0: if ((aVariantMask & VARIANT_INHERIT) != 0) { michael@0: // XXX Should we check IsParsingCompoundProperty, or do all michael@0: // callers handle it? (Not all callers set it, though, since michael@0: // they want the quirks that are disabled by setting it.) michael@0: michael@0: // IMPORTANT: If new keywords are added here, michael@0: // they probably need to be added in ParseCustomIdent as well. michael@0: if (eCSSKeyword_inherit == keyword) { michael@0: aValue.SetInheritValue(); michael@0: return true; michael@0: } michael@0: else if (eCSSKeyword_initial == keyword) { michael@0: aValue.SetInitialValue(); michael@0: return true; michael@0: } michael@0: else if (eCSSKeyword_unset == keyword && michael@0: nsLayoutUtils::UnsetValueEnabled()) { michael@0: aValue.SetUnsetValue(); michael@0: return true; michael@0: } michael@0: } michael@0: if ((aVariantMask & VARIANT_NONE) != 0) { michael@0: if (eCSSKeyword_none == keyword) { michael@0: aValue.SetNoneValue(); michael@0: return true; michael@0: } michael@0: } michael@0: if ((aVariantMask & VARIANT_ALL) != 0) { michael@0: if (eCSSKeyword_all == keyword) { michael@0: aValue.SetAllValue(); michael@0: return true; michael@0: } michael@0: } michael@0: if ((aVariantMask & VARIANT_NORMAL) != 0) { michael@0: if (eCSSKeyword_normal == keyword) { michael@0: aValue.SetNormalValue(); michael@0: return true; michael@0: } michael@0: } michael@0: if ((aVariantMask & VARIANT_SYSFONT) != 0) { michael@0: if (eCSSKeyword__moz_use_system_font == keyword && michael@0: !IsParsingCompoundProperty()) { michael@0: aValue.SetSystemFontValue(); michael@0: return true; michael@0: } michael@0: } michael@0: if ((aVariantMask & VARIANT_OPENTYPE_SVG_KEYWORD) != 0) { michael@0: static bool sOpentypeSVGEnabled; michael@0: static bool sOpentypeSVGEnabledCached = false; michael@0: if (!sOpentypeSVGEnabledCached) { michael@0: sOpentypeSVGEnabledCached = true; michael@0: Preferences::AddBoolVarCache(&sOpentypeSVGEnabled, michael@0: "gfx.font_rendering.opentype_svg.enabled"); michael@0: } michael@0: if (sOpentypeSVGEnabled) { michael@0: aVariantMask |= VARIANT_KEYWORD; michael@0: } michael@0: } michael@0: if ((aVariantMask & VARIANT_KEYWORD) != 0) { michael@0: int32_t value; michael@0: if (nsCSSProps::FindKeyword(keyword, aKeywordTable, value)) { michael@0: aValue.SetIntValue(value, eCSSUnit_Enumerated); michael@0: return true; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: // Check VARIANT_NUMBER and VARIANT_INTEGER before VARIANT_LENGTH or michael@0: // VARIANT_ZERO_ANGLE. michael@0: if (((aVariantMask & VARIANT_NUMBER) != 0) && michael@0: (eCSSToken_Number == tk->mType)) { michael@0: aValue.SetFloatValue(tk->mNumber, eCSSUnit_Number); michael@0: return true; michael@0: } michael@0: if (((aVariantMask & VARIANT_INTEGER) != 0) && michael@0: (eCSSToken_Number == tk->mType) && tk->mIntegerValid) { michael@0: aValue.SetIntValue(tk->mInteger, eCSSUnit_Integer); michael@0: return true; michael@0: } michael@0: if (((aVariantMask & (VARIANT_LENGTH | VARIANT_ANGLE | michael@0: VARIANT_FREQUENCY | VARIANT_TIME)) != 0 && michael@0: eCSSToken_Dimension == tk->mType) || michael@0: ((aVariantMask & (VARIANT_LENGTH | VARIANT_ZERO_ANGLE)) != 0 && michael@0: eCSSToken_Number == tk->mType && michael@0: tk->mNumber == 0.0f)) { michael@0: if (((aVariantMask & VARIANT_POSITIVE_DIMENSION) != 0 && michael@0: tk->mNumber <= 0.0) || michael@0: ((aVariantMask & VARIANT_NONNEGATIVE_DIMENSION) != 0 && michael@0: tk->mNumber < 0.0)) { michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: if (TranslateDimension(aValue, aVariantMask, tk->mNumber, tk->mIdent)) { michael@0: return true; michael@0: } michael@0: // Put the token back; we didn't parse it, so we shouldn't consume it michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: if (((aVariantMask & VARIANT_PERCENT) != 0) && michael@0: (eCSSToken_Percentage == tk->mType)) { michael@0: aValue.SetPercentValue(tk->mNumber); michael@0: return true; michael@0: } michael@0: if (mUnitlessLengthQuirk) { // NONSTANDARD: Nav interprets unitless numbers as px michael@0: if (((aVariantMask & VARIANT_LENGTH) != 0) && michael@0: (eCSSToken_Number == tk->mType)) { michael@0: aValue.SetFloatValue(tk->mNumber, eCSSUnit_Pixel); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: if (IsSVGMode() && !IsParsingCompoundProperty()) { michael@0: // STANDARD: SVG Spec states that lengths and coordinates can be unitless michael@0: // in which case they default to user-units (1 px = 1 user unit) michael@0: if (((aVariantMask & VARIANT_LENGTH) != 0) && michael@0: (eCSSToken_Number == tk->mType)) { michael@0: aValue.SetFloatValue(tk->mNumber, eCSSUnit_Pixel); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: if (((aVariantMask & VARIANT_URL) != 0) && michael@0: eCSSToken_URL == tk->mType) { michael@0: SetValueToURL(aValue, tk->mIdent); michael@0: return true; michael@0: } michael@0: if ((aVariantMask & VARIANT_GRADIENT) != 0 && michael@0: eCSSToken_Function == tk->mType) { michael@0: // a generated gradient michael@0: nsDependentString tmp(tk->mIdent, 0); michael@0: bool isLegacy = false; michael@0: if (StringBeginsWith(tmp, NS_LITERAL_STRING("-moz-"))) { michael@0: tmp.Rebind(tmp, 5); michael@0: isLegacy = true; michael@0: } michael@0: bool isRepeating = false; michael@0: if (StringBeginsWith(tmp, NS_LITERAL_STRING("repeating-"))) { michael@0: tmp.Rebind(tmp, 10); michael@0: isRepeating = true; michael@0: } michael@0: michael@0: if (tmp.LowerCaseEqualsLiteral("linear-gradient")) { michael@0: return ParseLinearGradient(aValue, isRepeating, isLegacy); michael@0: } michael@0: if (tmp.LowerCaseEqualsLiteral("radial-gradient")) { michael@0: return ParseRadialGradient(aValue, isRepeating, isLegacy); michael@0: } michael@0: } michael@0: if ((aVariantMask & VARIANT_IMAGE_RECT) != 0 && michael@0: eCSSToken_Function == tk->mType && michael@0: tk->mIdent.LowerCaseEqualsLiteral("-moz-image-rect")) { michael@0: return ParseImageRect(aValue); michael@0: } michael@0: if ((aVariantMask & VARIANT_ELEMENT) != 0 && michael@0: eCSSToken_Function == tk->mType && michael@0: tk->mIdent.LowerCaseEqualsLiteral("-moz-element")) { michael@0: return ParseElement(aValue); michael@0: } michael@0: if ((aVariantMask & VARIANT_COLOR) != 0) { michael@0: if (mHashlessColorQuirk || // NONSTANDARD: Nav interprets 'xxyyzz' values even without '#' prefix michael@0: (eCSSToken_ID == tk->mType) || michael@0: (eCSSToken_Hash == tk->mType) || michael@0: (eCSSToken_Ident == tk->mType) || michael@0: ((eCSSToken_Function == tk->mType) && michael@0: (tk->mIdent.LowerCaseEqualsLiteral("rgb") || michael@0: tk->mIdent.LowerCaseEqualsLiteral("hsl") || michael@0: tk->mIdent.LowerCaseEqualsLiteral("rgba") || michael@0: tk->mIdent.LowerCaseEqualsLiteral("hsla")))) michael@0: { michael@0: // Put token back so that parse color can get it michael@0: UngetToken(); michael@0: if (ParseColor(aValue)) { michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: } michael@0: if (((aVariantMask & VARIANT_STRING) != 0) && michael@0: (eCSSToken_String == tk->mType)) { michael@0: nsAutoString buffer; michael@0: buffer.Append(tk->mIdent); michael@0: aValue.SetStringValue(buffer, eCSSUnit_String); michael@0: return true; michael@0: } michael@0: if (((aVariantMask & michael@0: (VARIANT_IDENTIFIER | VARIANT_IDENTIFIER_NO_INHERIT)) != 0) && michael@0: (eCSSToken_Ident == tk->mType) && michael@0: ((aVariantMask & VARIANT_IDENTIFIER) != 0 || michael@0: !(tk->mIdent.LowerCaseEqualsLiteral("inherit") || michael@0: tk->mIdent.LowerCaseEqualsLiteral("initial") || michael@0: (tk->mIdent.LowerCaseEqualsLiteral("unset") && michael@0: nsLayoutUtils::UnsetValueEnabled())))) { michael@0: aValue.SetStringValue(tk->mIdent, eCSSUnit_Ident); michael@0: return true; michael@0: } michael@0: if (((aVariantMask & VARIANT_COUNTER) != 0) && michael@0: (eCSSToken_Function == tk->mType) && michael@0: (tk->mIdent.LowerCaseEqualsLiteral("counter") || michael@0: tk->mIdent.LowerCaseEqualsLiteral("counters"))) { michael@0: return ParseCounter(aValue); michael@0: } michael@0: if (((aVariantMask & VARIANT_ATTR) != 0) && michael@0: (eCSSToken_Function == tk->mType) && michael@0: tk->mIdent.LowerCaseEqualsLiteral("attr")) { michael@0: if (!ParseAttr(aValue)) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: if (((aVariantMask & VARIANT_TIMING_FUNCTION) != 0) && michael@0: (eCSSToken_Function == tk->mType)) { michael@0: if (tk->mIdent.LowerCaseEqualsLiteral("cubic-bezier")) { michael@0: if (!ParseTransitionTimingFunctionValues(aValue)) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: if (tk->mIdent.LowerCaseEqualsLiteral("steps")) { michael@0: if (!ParseTransitionStepTimingFunctionValues(aValue)) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: } michael@0: if ((aVariantMask & VARIANT_CALC) && michael@0: (eCSSToken_Function == tk->mType) && michael@0: (tk->mIdent.LowerCaseEqualsLiteral("calc") || michael@0: tk->mIdent.LowerCaseEqualsLiteral("-moz-calc"))) { michael@0: // calc() currently allows only lengths and percents inside it. michael@0: return ParseCalc(aValue, aVariantMask & VARIANT_LP); michael@0: } michael@0: michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseCustomIdent(nsCSSValue& aValue, michael@0: const nsAutoString& aIdentValue, michael@0: const nsCSSKeyword aExcludedKeywords[], michael@0: const nsCSSProps::KTableValue aPropertyKTable[]) michael@0: { michael@0: nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(aIdentValue); michael@0: if (keyword == eCSSKeyword_UNKNOWN) { michael@0: // Fast path for identifiers that are not known CSS keywords: michael@0: aValue.SetStringValue(mToken.mIdent, eCSSUnit_Ident); michael@0: return true; michael@0: } michael@0: if (keyword == eCSSKeyword_inherit || michael@0: keyword == eCSSKeyword_initial || michael@0: keyword == eCSSKeyword_unset || michael@0: keyword == eCSSKeyword_default || michael@0: (aPropertyKTable && michael@0: nsCSSProps::FindIndexOfKeyword(keyword, aPropertyKTable) >= 0)) { michael@0: return false; michael@0: } michael@0: if (aExcludedKeywords) { michael@0: for (uint32_t i = 0;; i++) { michael@0: nsCSSKeyword excludedKeyword = aExcludedKeywords[i]; michael@0: if (excludedKeyword == eCSSKeyword_UNKNOWN) { michael@0: break; michael@0: } michael@0: if (excludedKeyword == keyword) { michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: aValue.SetStringValue(mToken.mIdent, eCSSUnit_Ident); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseCounter(nsCSSValue& aValue) michael@0: { michael@0: nsCSSUnit unit = (mToken.mIdent.LowerCaseEqualsLiteral("counter") ? michael@0: eCSSUnit_Counter : eCSSUnit_Counters); michael@0: michael@0: // A non-iterative for loop to break out when an error occurs. michael@0: for (;;) { michael@0: if (!GetToken(true)) { michael@0: break; michael@0: } michael@0: if (eCSSToken_Ident != mToken.mType) { michael@0: UngetToken(); michael@0: break; michael@0: } michael@0: michael@0: nsRefPtr val = michael@0: nsCSSValue::Array::Create(unit == eCSSUnit_Counter ? 2 : 3); michael@0: michael@0: val->Item(0).SetStringValue(mToken.mIdent, eCSSUnit_Ident); michael@0: michael@0: if (eCSSUnit_Counters == unit) { michael@0: // must have a comma and then a separator string michael@0: if (!ExpectSymbol(',', true) || !GetToken(true)) { michael@0: break; michael@0: } michael@0: if (eCSSToken_String != mToken.mType) { michael@0: UngetToken(); michael@0: break; michael@0: } michael@0: val->Item(1).SetStringValue(mToken.mIdent, eCSSUnit_String); michael@0: } michael@0: michael@0: // get optional type michael@0: int32_t type = NS_STYLE_LIST_STYLE_DECIMAL; michael@0: if (ExpectSymbol(',', true)) { michael@0: if (!GetToken(true)) { michael@0: break; michael@0: } michael@0: nsCSSKeyword keyword; michael@0: if (eCSSToken_Ident != mToken.mType || michael@0: (keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent)) == michael@0: eCSSKeyword_UNKNOWN || michael@0: !nsCSSProps::FindKeyword(keyword, nsCSSProps::kListStyleKTable, michael@0: type)) { michael@0: UngetToken(); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: int32_t typeItem = eCSSUnit_Counters == unit ? 2 : 1; michael@0: val->Item(typeItem).SetIntValue(type, eCSSUnit_Enumerated); michael@0: michael@0: if (!ExpectSymbol(')', true)) { michael@0: break; michael@0: } michael@0: michael@0: aValue.SetArrayValue(val, unit); michael@0: return true; michael@0: } michael@0: michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseAttr(nsCSSValue& aValue) michael@0: { michael@0: if (!GetToken(true)) { michael@0: return false; michael@0: } michael@0: michael@0: nsAutoString attr; michael@0: if (eCSSToken_Ident == mToken.mType) { // attr name or namespace michael@0: nsAutoString holdIdent(mToken.mIdent); michael@0: if (ExpectSymbol('|', false)) { // namespace michael@0: int32_t nameSpaceID = GetNamespaceIdForPrefix(holdIdent); michael@0: if (nameSpaceID == kNameSpaceID_Unknown) { michael@0: return false; michael@0: } michael@0: attr.AppendInt(nameSpaceID, 10); michael@0: attr.Append(char16_t('|')); michael@0: if (! GetToken(false)) { michael@0: REPORT_UNEXPECTED_EOF(PEAttributeNameEOF); michael@0: return false; michael@0: } michael@0: if (eCSSToken_Ident == mToken.mType) { michael@0: attr.Append(mToken.mIdent); michael@0: } michael@0: else { michael@0: REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected); michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: } michael@0: else { // no namespace michael@0: attr = holdIdent; michael@0: } michael@0: } michael@0: else if (mToken.IsSymbol('*')) { // namespace wildcard michael@0: // Wildcard namespace makes no sense here and is not allowed michael@0: REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected); michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: else if (mToken.IsSymbol('|')) { // explicit NO namespace michael@0: if (! GetToken(false)) { michael@0: REPORT_UNEXPECTED_EOF(PEAttributeNameEOF); michael@0: return false; michael@0: } michael@0: if (eCSSToken_Ident == mToken.mType) { michael@0: attr.Append(mToken.mIdent); michael@0: } michael@0: else { michael@0: REPORT_UNEXPECTED_TOKEN(PEAttributeNameExpected); michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: } michael@0: else { michael@0: REPORT_UNEXPECTED_TOKEN(PEAttributeNameOrNamespaceExpected); michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: if (!ExpectSymbol(')', true)) { michael@0: return false; michael@0: } michael@0: aValue.SetStringValue(attr, eCSSUnit_Attr); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::SetValueToURL(nsCSSValue& aValue, const nsString& aURL) michael@0: { michael@0: if (!mSheetPrincipal) { michael@0: NS_NOTREACHED("Codepaths that expect to parse URLs MUST pass in an " michael@0: "origin principal"); michael@0: return false; michael@0: } michael@0: michael@0: nsRefPtr buffer(nsCSSValue::BufferFromString(aURL)); michael@0: michael@0: // Note: urlVal retains its own reference to |buffer|. michael@0: mozilla::css::URLValue *urlVal = michael@0: new mozilla::css::URLValue(buffer, mBaseURI, mSheetURI, mSheetPrincipal); michael@0: aValue.SetURLValue(urlVal); michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Parse the image-orientation property, which has the grammar: michael@0: * flip? | flip | from-image michael@0: */ michael@0: bool michael@0: CSSParserImpl::ParseImageOrientation(nsCSSValue& aValue) michael@0: { michael@0: if (ParseVariant(aValue, VARIANT_INHERIT, nullptr)) { michael@0: // 'inherit', 'initial' and 'unset' must be alone michael@0: return true; michael@0: } michael@0: michael@0: // Check for an angle with optional 'flip'. michael@0: nsCSSValue angle; michael@0: if (ParseVariant(angle, VARIANT_ANGLE, nullptr)) { michael@0: nsCSSValue flip; michael@0: michael@0: if (ParseVariant(flip, VARIANT_KEYWORD, nsCSSProps::kImageOrientationFlipKTable)) { michael@0: nsRefPtr array = nsCSSValue::Array::Create(2); michael@0: array->Item(0) = angle; michael@0: array->Item(1) = flip; michael@0: aValue.SetArrayValue(array, eCSSUnit_Array); michael@0: } else { michael@0: aValue = angle; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // The remaining possibilities (bare 'flip' and 'from-image') are both michael@0: // keywords, so we can handle them at the same time. michael@0: nsCSSValue keyword; michael@0: if (ParseVariant(keyword, VARIANT_KEYWORD, nsCSSProps::kImageOrientationKTable)) { michael@0: aValue = keyword; michael@0: return true; michael@0: } michael@0: michael@0: // All possibilities failed. michael@0: return false; michael@0: } michael@0: michael@0: /** michael@0: * Parse the arguments of -moz-image-rect() function. michael@0: * -moz-image-rect(, , , , ) michael@0: */ michael@0: bool michael@0: CSSParserImpl::ParseImageRect(nsCSSValue& aImage) michael@0: { michael@0: // A non-iterative for loop to break out when an error occurs. michael@0: for (;;) { michael@0: nsCSSValue newFunction; michael@0: static const uint32_t kNumArgs = 5; michael@0: nsCSSValue::Array* func = michael@0: newFunction.InitFunction(eCSSKeyword__moz_image_rect, kNumArgs); michael@0: michael@0: // func->Item(0) is reserved for the function name. michael@0: nsCSSValue& url = func->Item(1); michael@0: nsCSSValue& top = func->Item(2); michael@0: nsCSSValue& right = func->Item(3); michael@0: nsCSSValue& bottom = func->Item(4); michael@0: nsCSSValue& left = func->Item(5); michael@0: michael@0: nsAutoString urlString; michael@0: if (!ParseURLOrString(urlString) || michael@0: !SetValueToURL(url, urlString) || michael@0: !ExpectSymbol(',', true)) { michael@0: break; michael@0: } michael@0: michael@0: static const int32_t VARIANT_SIDE = VARIANT_NUMBER | VARIANT_PERCENT; michael@0: if (!ParseNonNegativeVariant(top, VARIANT_SIDE, nullptr) || michael@0: !ExpectSymbol(',', true) || michael@0: !ParseNonNegativeVariant(right, VARIANT_SIDE, nullptr) || michael@0: !ExpectSymbol(',', true) || michael@0: !ParseNonNegativeVariant(bottom, VARIANT_SIDE, nullptr) || michael@0: !ExpectSymbol(',', true) || michael@0: !ParseNonNegativeVariant(left, VARIANT_SIDE, nullptr) || michael@0: !ExpectSymbol(')', true)) michael@0: break; michael@0: michael@0: aImage = newFunction; michael@0: return true; michael@0: } michael@0: michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: michael@0: // : -moz-element(# ) michael@0: bool michael@0: CSSParserImpl::ParseElement(nsCSSValue& aValue) michael@0: { michael@0: // A non-iterative for loop to break out when an error occurs. michael@0: for (;;) { michael@0: if (!GetToken(true)) michael@0: break; michael@0: michael@0: if (mToken.mType == eCSSToken_ID) { michael@0: aValue.SetStringValue(mToken.mIdent, eCSSUnit_Element); michael@0: } else { michael@0: UngetToken(); michael@0: break; michael@0: } michael@0: michael@0: if (!ExpectSymbol(')', true)) michael@0: break; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // If we detect a syntax error, we must match the opening parenthesis of the michael@0: // function with the closing parenthesis and skip all the tokens in between. michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: michael@0: // flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ] michael@0: bool michael@0: CSSParserImpl::ParseFlex() michael@0: { michael@0: // First check for inherit / initial / unset michael@0: nsCSSValue tmpVal; michael@0: if (ParseVariant(tmpVal, VARIANT_INHERIT, nullptr)) { michael@0: AppendValue(eCSSProperty_flex_grow, tmpVal); michael@0: AppendValue(eCSSProperty_flex_shrink, tmpVal); michael@0: AppendValue(eCSSProperty_flex_basis, tmpVal); michael@0: return true; michael@0: } michael@0: michael@0: // Next, check for 'none' == '0 0 auto' michael@0: if (ParseVariant(tmpVal, VARIANT_NONE, nullptr)) { michael@0: AppendValue(eCSSProperty_flex_grow, nsCSSValue(0.0f, eCSSUnit_Number)); michael@0: AppendValue(eCSSProperty_flex_shrink, nsCSSValue(0.0f, eCSSUnit_Number)); michael@0: AppendValue(eCSSProperty_flex_basis, nsCSSValue(eCSSUnit_Auto)); michael@0: return true; michael@0: } michael@0: michael@0: // OK, try parsing our value as individual per-subproperty components: michael@0: // [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ] michael@0: michael@0: // Each subproperty has a default value that it takes when it's omitted in a michael@0: // "flex" shorthand value. These default values are *only* for the shorthand michael@0: // syntax -- they're distinct from the subproperties' own initial values. We michael@0: // start with each subproperty at its default, as if we had "flex: 1 1 0%". michael@0: nsCSSValue flexGrow(1.0f, eCSSUnit_Number); michael@0: nsCSSValue flexShrink(1.0f, eCSSUnit_Number); michael@0: nsCSSValue flexBasis(0.0f, eCSSUnit_Percent); michael@0: michael@0: // OVERVIEW OF PARSING STRATEGY: michael@0: // ============================= michael@0: // a) Parse the first component as either flex-basis or flex-grow. michael@0: // b) If it wasn't flex-grow, parse the _next_ component as flex-grow. michael@0: // c) Now we've just parsed flex-grow -- so try parsing the next thing as michael@0: // flex-shrink. michael@0: // d) Finally: If we didn't get flex-basis at the beginning, try to parse michael@0: // it now, at the end. michael@0: // michael@0: // More details in each section below. michael@0: michael@0: uint32_t flexBasisVariantMask = michael@0: (nsCSSProps::ParserVariant(eCSSProperty_flex_basis) & ~(VARIANT_INHERIT)); michael@0: michael@0: // (a) Parse first component. It can be either be a 'flex-basis' value or a michael@0: // 'flex-grow' value, so we use the flex-basis-specific variant mask, along michael@0: // with VARIANT_NUMBER to accept 'flex-grow' values. michael@0: // michael@0: // NOTE: if we encounter unitless 0 here, we *must* interpret it as a michael@0: // 'flex-grow' value (a number), *not* as a 'flex-basis' value (a length). michael@0: // Conveniently, that's the behavior this combined variant-mask gives us -- michael@0: // it'll treat unitless 0 as a number. The flexbox spec requires this: michael@0: // "a unitless zero that is not already preceded by two flex factors must be michael@0: // interpreted as a flex factor. michael@0: if (!ParseNonNegativeVariant(tmpVal, flexBasisVariantMask | VARIANT_NUMBER, michael@0: nsCSSProps::kWidthKTable)) { michael@0: // First component was not a valid flex-basis or flex-grow value. Fail. michael@0: return false; michael@0: } michael@0: michael@0: // Record what we just parsed as either flex-basis or flex-grow: michael@0: bool wasFirstComponentFlexBasis = (tmpVal.GetUnit() != eCSSUnit_Number); michael@0: (wasFirstComponentFlexBasis ? flexBasis : flexGrow) = tmpVal; michael@0: michael@0: // (b) If we didn't get flex-grow yet, parse _next_ component as flex-grow. michael@0: bool doneParsing = false; michael@0: if (wasFirstComponentFlexBasis) { michael@0: if (ParseNonNegativeVariant(tmpVal, VARIANT_NUMBER, nullptr)) { michael@0: flexGrow = tmpVal; michael@0: } else { michael@0: // Failed to parse anything after our flex-basis -- that's fine. We can michael@0: // skip the remaining parsing. michael@0: doneParsing = true; michael@0: } michael@0: } michael@0: michael@0: if (!doneParsing) { michael@0: // (c) OK -- the last thing we parsed was flex-grow, so look for a michael@0: // flex-shrink in the next position. michael@0: if (ParseNonNegativeVariant(tmpVal, VARIANT_NUMBER, nullptr)) { michael@0: flexShrink = tmpVal; michael@0: } michael@0: michael@0: // d) Finally: If we didn't get flex-basis at the beginning, try to parse michael@0: // it now, at the end. michael@0: // michael@0: // NOTE: If we encounter unitless 0 in this final position, we'll parse it michael@0: // as a 'flex-basis' value. That's OK, because we know it must have michael@0: // been "preceded by 2 flex factors" (justification below), which gets us michael@0: // out of the spec's requirement of otherwise having to treat unitless 0 michael@0: // as a flex factor. michael@0: // michael@0: // JUSTIFICATION: How do we know that a unitless 0 here must have been michael@0: // preceded by 2 flex factors? Well, suppose we had a unitless 0 that michael@0: // was preceded by only 1 flex factor. Then, we would have already michael@0: // accepted this unitless 0 as the 'flex-shrink' value, up above (since michael@0: // it's a valid flex-shrink value), and we'd have moved on to the next michael@0: // token (if any). And of course, if we instead had a unitless 0 preceded michael@0: // by *no* flex factors (if it were the first token), we would've already michael@0: // parsed it in our very first call to ParseNonNegativeVariant(). So, any michael@0: // unitless 0 encountered here *must* have been preceded by 2 flex factors. michael@0: if (!wasFirstComponentFlexBasis && michael@0: ParseNonNegativeVariant(tmpVal, flexBasisVariantMask, michael@0: nsCSSProps::kWidthKTable)) { michael@0: flexBasis = tmpVal; michael@0: } michael@0: } michael@0: michael@0: AppendValue(eCSSProperty_flex_grow, flexGrow); michael@0: AppendValue(eCSSProperty_flex_shrink, flexShrink); michael@0: AppendValue(eCSSProperty_flex_basis, flexBasis); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // flex-flow: || michael@0: bool michael@0: CSSParserImpl::ParseFlexFlow() michael@0: { michael@0: static const nsCSSProperty kFlexFlowSubprops[] = { michael@0: eCSSProperty_flex_direction, michael@0: eCSSProperty_flex_wrap michael@0: }; michael@0: const size_t numProps = MOZ_ARRAY_LENGTH(kFlexFlowSubprops); michael@0: nsCSSValue values[numProps]; michael@0: michael@0: int32_t found = ParseChoice(values, kFlexFlowSubprops, numProps); michael@0: michael@0: // Bail if we didn't successfully parse anything michael@0: if (found < 1) { michael@0: return false; michael@0: } michael@0: michael@0: // If either property didn't get an explicit value, use its initial value. michael@0: if ((found & 1) == 0) { michael@0: values[0].SetIntValue(NS_STYLE_FLEX_DIRECTION_ROW, eCSSUnit_Enumerated); michael@0: } michael@0: if ((found & 2) == 0) { michael@0: values[1].SetIntValue(NS_STYLE_FLEX_WRAP_NOWRAP, eCSSUnit_Enumerated); michael@0: } michael@0: michael@0: // Store these values and declare success! michael@0: for (size_t i = 0; i < numProps; i++) { michael@0: AppendValue(kFlexFlowSubprops[i], values[i]); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseGridAutoFlow() michael@0: { michael@0: nsCSSValue value; michael@0: if (ParseVariant(value, VARIANT_INHERIT, nullptr)) { michael@0: AppendValue(eCSSProperty_grid_auto_flow, value); michael@0: return true; michael@0: } michael@0: michael@0: static const int32_t mask[] = { michael@0: NS_STYLE_GRID_AUTO_FLOW_COLUMN | NS_STYLE_GRID_AUTO_FLOW_ROW, michael@0: MASK_END_VALUE michael@0: }; michael@0: if (!ParseBitmaskValues(value, nsCSSProps::kGridAutoFlowKTable, mask)) { michael@0: return false; michael@0: } michael@0: int32_t bitField = value.GetIntValue(); michael@0: michael@0: // Requires one of these michael@0: if (!(bitField & NS_STYLE_GRID_AUTO_FLOW_NONE || michael@0: bitField & NS_STYLE_GRID_AUTO_FLOW_COLUMN || michael@0: bitField & NS_STYLE_GRID_AUTO_FLOW_ROW)) { michael@0: return false; michael@0: } michael@0: michael@0: // 'none' is only valid if it occurs alone: michael@0: if (bitField & NS_STYLE_GRID_AUTO_FLOW_NONE && michael@0: bitField != NS_STYLE_GRID_AUTO_FLOW_NONE) { michael@0: return false; michael@0: } michael@0: michael@0: AppendValue(eCSSProperty_grid_auto_flow, value); michael@0: return true; michael@0: } michael@0: michael@0: CSSParseResult michael@0: CSSParserImpl::ParseGridLineNames(nsCSSValue& aValue) michael@0: { michael@0: if (!ExpectSymbol('(', true)) { michael@0: return CSSParseResult::NotFound; michael@0: } michael@0: if (!GetToken(true) || mToken.IsSymbol(')')) { michael@0: return CSSParseResult::Ok; michael@0: } michael@0: // 'return' so far leaves aValue untouched, to represent an empty list. michael@0: michael@0: nsCSSValueList* item; michael@0: if (aValue.GetUnit() == eCSSUnit_List) { michael@0: // Find the end of an existing list. michael@0: // The grid-template shorthand uses this, at most once for a given list. michael@0: michael@0: // NOTE: we could avoid this traversal by somehow keeping around michael@0: // a pointer to the last item from the previous call. michael@0: // It's not yet clear if this is worth the additional code complexity. michael@0: item = aValue.GetListValue(); michael@0: while (item->mNext) { michael@0: item = item->mNext; michael@0: } michael@0: item->mNext = new nsCSSValueList; michael@0: item = item->mNext; michael@0: } else { michael@0: MOZ_ASSERT(aValue.GetUnit() == eCSSUnit_Null, "Unexpected unit"); michael@0: item = aValue.SetListValue(); michael@0: } michael@0: for (;;) { michael@0: if (!(eCSSToken_Ident == mToken.mType && michael@0: ParseCustomIdent(item->mValue, mToken.mIdent))) { michael@0: UngetToken(); michael@0: SkipUntil(')'); michael@0: return CSSParseResult::Error; michael@0: } michael@0: if (!GetToken(true) || mToken.IsSymbol(')')) { michael@0: return CSSParseResult::Ok; michael@0: } michael@0: item->mNext = new nsCSSValueList; michael@0: item = item->mNext; michael@0: } michael@0: } michael@0: michael@0: // Assuming the 'repeat(' function token has already been consumed, michael@0: // parse the rest of repeat(, +) michael@0: // Append to the linked list whose end is given by |aTailPtr|, michael@0: // and updated |aTailPtr| to point to the new end of the list. michael@0: bool michael@0: CSSParserImpl::ParseGridLineNameListRepeat(nsCSSValueList** aTailPtr) michael@0: { michael@0: if (!(GetToken(true) && michael@0: mToken.mType == eCSSToken_Number && michael@0: mToken.mIntegerValid && michael@0: mToken.mInteger > 0)) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: int32_t repetitions = std::min(mToken.mInteger, michael@0: GRID_TEMPLATE_MAX_REPETITIONS); michael@0: if (!ExpectSymbol(',', true)) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: michael@0: // Parse at least one michael@0: nsCSSValueList* tail = *aTailPtr; michael@0: do { michael@0: tail->mNext = new nsCSSValueList; michael@0: tail = tail->mNext; michael@0: if (ParseGridLineNames(tail->mValue) != CSSParseResult::Ok) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: } while (!ExpectSymbol(')', true)); michael@0: nsCSSValueList* firstRepeatedItem = (*aTailPtr)->mNext; michael@0: nsCSSValueList* lastRepeatedItem = tail; michael@0: michael@0: // Our repeated items are already in the target list once, michael@0: // so they need to be repeated |repetitions - 1| more times. michael@0: MOZ_ASSERT(repetitions > 0, "Expected positive repetitions"); michael@0: while (--repetitions) { michael@0: nsCSSValueList* repeatedItem = firstRepeatedItem; michael@0: for (;;) { michael@0: tail->mNext = new nsCSSValueList; michael@0: tail = tail->mNext; michael@0: tail->mValue = repeatedItem->mValue; michael@0: if (repeatedItem == lastRepeatedItem) { michael@0: break; michael@0: } michael@0: repeatedItem = repeatedItem->mNext; michael@0: } michael@0: } michael@0: *aTailPtr = tail; michael@0: return true; michael@0: } michael@0: michael@0: // Assuming a 'subgrid' keyword was already consumed, parse ? michael@0: bool michael@0: CSSParserImpl::ParseOptionalLineNameListAfterSubgrid(nsCSSValue& aValue) michael@0: { michael@0: nsCSSValueList* item = aValue.SetListValue(); michael@0: // This marker distinguishes the value from a . michael@0: item->mValue.SetIntValue(NS_STYLE_GRID_TEMPLATE_SUBGRID, michael@0: eCSSUnit_Enumerated); michael@0: for (;;) { michael@0: // First try to parse repeat(, +) michael@0: if (!GetToken(true)) { michael@0: return true; michael@0: } michael@0: if (mToken.mType == eCSSToken_Function && michael@0: mToken.mIdent.LowerCaseEqualsLiteral("repeat")) { michael@0: if (!ParseGridLineNameListRepeat(&item)) { michael@0: return false; michael@0: } michael@0: } else { michael@0: UngetToken(); michael@0: michael@0: // This was not a repeat() function. Try to parse . michael@0: nsCSSValue lineNames; michael@0: CSSParseResult result = ParseGridLineNames(lineNames); michael@0: if (result == CSSParseResult::NotFound) { michael@0: return true; michael@0: } michael@0: if (result == CSSParseResult::Error) { michael@0: return false; michael@0: } michael@0: item->mNext = new nsCSSValueList; michael@0: item = item->mNext; michael@0: item->mValue = lineNames; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Parse a michael@0: bool michael@0: CSSParserImpl::ParseGridTrackBreadth(nsCSSValue& aValue) michael@0: { michael@0: if (ParseNonNegativeVariant(aValue, michael@0: VARIANT_LPCALC | VARIANT_KEYWORD, michael@0: nsCSSProps::kGridTrackBreadthKTable)) { michael@0: return true; michael@0: } michael@0: michael@0: // Attempt to parse (a dimension with the "fr" unit) michael@0: if (!GetToken(true)) { michael@0: return false; michael@0: } michael@0: if (!(eCSSToken_Dimension == mToken.mType && michael@0: mToken.mIdent.LowerCaseEqualsLiteral("fr") && michael@0: mToken.mNumber >= 0)) { michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: aValue.SetFloatValue(mToken.mNumber, eCSSUnit_FlexFraction); michael@0: return true; michael@0: } michael@0: michael@0: // Parse a michael@0: CSSParseResult michael@0: CSSParserImpl::ParseGridTrackSize(nsCSSValue& aValue) michael@0: { michael@0: // Attempt to parse 'auto' or a single michael@0: if (ParseGridTrackBreadth(aValue) || michael@0: ParseVariant(aValue, VARIANT_AUTO, nullptr)) { michael@0: return CSSParseResult::Ok; michael@0: } michael@0: michael@0: // Attempt to parse a minmax() function michael@0: if (!GetToken(true)) { michael@0: return CSSParseResult::NotFound; michael@0: } michael@0: if (!(eCSSToken_Function == mToken.mType && michael@0: mToken.mIdent.LowerCaseEqualsLiteral("minmax"))) { michael@0: UngetToken(); michael@0: return CSSParseResult::NotFound; michael@0: } michael@0: nsCSSValue::Array* func = aValue.InitFunction(eCSSKeyword_minmax, 2); michael@0: if (ParseGridTrackBreadth(func->Item(1)) && michael@0: ExpectSymbol(',', true) && michael@0: ParseGridTrackBreadth(func->Item(2)) && michael@0: ExpectSymbol(')', true)) { michael@0: return CSSParseResult::Ok; michael@0: } michael@0: SkipUntil(')'); michael@0: return CSSParseResult::Error; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseGridAutoColumnsRows(nsCSSProperty aPropID) michael@0: { michael@0: nsCSSValue value; michael@0: if (ParseVariant(value, VARIANT_INHERIT, nullptr) || michael@0: ParseGridTrackSize(value) == CSSParseResult::Ok) { michael@0: AppendValue(aPropID, value); michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseGridTrackListWithFirstLineNames(nsCSSValue& aValue, michael@0: const nsCSSValue& aFirstLineNames) michael@0: { michael@0: nsCSSValueList* firstLineNamesItem = aValue.SetListValue(); michael@0: firstLineNamesItem->mValue = aFirstLineNames; michael@0: michael@0: // This function is trying to parse , which is michael@0: // [ ? [ | ] ]+ ? michael@0: // and we're already past the first "?". michael@0: // michael@0: // Each iteration of the following loop attempts to parse either a michael@0: // repeat() or a expression, and then an (optional) michael@0: // expression. michael@0: // michael@0: // The only successful exit point from this loop is the ::NotFound michael@0: // case after ParseGridTrackSize(); i.e. we'll greedily parse michael@0: // repeat()/ until we can't find one. michael@0: nsCSSValueList* item = firstLineNamesItem; michael@0: for (;;) { michael@0: // First try to parse repeat() michael@0: if (!GetToken(true)) { michael@0: break; michael@0: } michael@0: if (mToken.mType == eCSSToken_Function && michael@0: mToken.mIdent.LowerCaseEqualsLiteral("repeat")) { michael@0: if (!ParseGridTrackListRepeat(&item)) { michael@0: return false; michael@0: } michael@0: } else { michael@0: UngetToken(); michael@0: michael@0: // This was not a repeat() function. Try to parse . michael@0: nsCSSValue trackSize; michael@0: CSSParseResult result = ParseGridTrackSize(trackSize); michael@0: if (result == CSSParseResult::Error) { michael@0: return false; michael@0: } michael@0: if (result == CSSParseResult::NotFound) { michael@0: // What we've parsed so far is a valid michael@0: // (modulo the "at least one " check below.) michael@0: // Stop here. michael@0: break; michael@0: } michael@0: item->mNext = new nsCSSValueList; michael@0: item = item->mNext; michael@0: item->mValue = trackSize; michael@0: michael@0: item->mNext = new nsCSSValueList; michael@0: item = item->mNext; michael@0: } michael@0: if (ParseGridLineNames(item->mValue) == CSSParseResult::Error) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // Require at least one . michael@0: if (item == firstLineNamesItem) { michael@0: return false; michael@0: } michael@0: michael@0: MOZ_ASSERT(aValue.GetListValue() && michael@0: aValue.GetListValue()->mNext && michael@0: aValue.GetListValue()->mNext->mNext, michael@0: " should have a minimum length of 3"); michael@0: return true; michael@0: } michael@0: michael@0: // Takes ownership of |aSecond| michael@0: static void michael@0: ConcatLineNames(nsCSSValue& aFirst, nsCSSValue& aSecond) michael@0: { michael@0: if (aSecond.GetUnit() == eCSSUnit_Null) { michael@0: // Nothing to do. michael@0: return; michael@0: } michael@0: if (aFirst.GetUnit() == eCSSUnit_Null) { michael@0: // Empty or omitted . Replace it. michael@0: aFirst = aSecond; michael@0: return; michael@0: } michael@0: michael@0: // Join the two lists. michael@0: nsCSSValueList* source = aSecond.GetListValue(); michael@0: nsCSSValueList* target = aFirst.GetListValue(); michael@0: // Find the end: michael@0: while (target->mNext) { michael@0: target = target->mNext; michael@0: } michael@0: // Copy the first name. We can't take ownership of it michael@0: // as it'll be destroyed when |aSecond| goes out of scope. michael@0: target->mNext = new nsCSSValueList; michael@0: target = target->mNext; michael@0: target->mValue = source->mValue; michael@0: // Move the rest of the linked list. michael@0: target->mNext = source->mNext; michael@0: source->mNext = nullptr; michael@0: } michael@0: michael@0: // Assuming the 'repeat(' function token has already been consumed, michael@0: // parse the rest of michael@0: // repeat( , michael@0: // [ ? ]+ ? ) michael@0: // Append to the linked list whose end is given by |aTailPtr|, michael@0: // and updated |aTailPtr| to point to the new end of the list. michael@0: bool michael@0: CSSParserImpl::ParseGridTrackListRepeat(nsCSSValueList** aTailPtr) michael@0: { michael@0: if (!(GetToken(true) && michael@0: mToken.mType == eCSSToken_Number && michael@0: mToken.mIntegerValid && michael@0: mToken.mInteger > 0)) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: int32_t repetitions = std::min(mToken.mInteger, michael@0: GRID_TEMPLATE_MAX_REPETITIONS); michael@0: if (!ExpectSymbol(',', true)) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: michael@0: // Parse [ ? ]+ ? michael@0: // but keep the first and last separate michael@0: // because they'll need to be joined. michael@0: // http://dev.w3.org/csswg/css-grid/#repeat-notation michael@0: nsCSSValue firstLineNames; michael@0: nsCSSValue trackSize; michael@0: nsCSSValue lastLineNames; michael@0: // Optional michael@0: if (ParseGridLineNames(firstLineNames) == CSSParseResult::Error) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: // Required michael@0: if (ParseGridTrackSize(trackSize) != CSSParseResult::Ok) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: // Use nsAutoPtr to free the list in case of early return. michael@0: nsAutoPtr firstTrackSizeItemAuto(new nsCSSValueList); michael@0: firstTrackSizeItemAuto->mValue = trackSize; michael@0: michael@0: nsCSSValueList* item = firstTrackSizeItemAuto; michael@0: for (;;) { michael@0: // Optional michael@0: if (ParseGridLineNames(lastLineNames) == CSSParseResult::Error) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: michael@0: if (ExpectSymbol(')', true)) { michael@0: break; michael@0: } michael@0: michael@0: // Required michael@0: if (ParseGridTrackSize(trackSize) != CSSParseResult::Ok) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: michael@0: item->mNext = new nsCSSValueList; michael@0: item = item->mNext; michael@0: item->mValue = lastLineNames; michael@0: // Do not append to this list at the next iteration. michael@0: lastLineNames.Reset(); michael@0: michael@0: item->mNext = new nsCSSValueList; michael@0: item = item->mNext; michael@0: item->mValue = trackSize; michael@0: } michael@0: nsCSSValueList* lastTrackSizeItem = item; michael@0: michael@0: // [ ? ]+ ? is now parsed into: michael@0: // * firstLineNames: the first michael@0: // * a linked list of odd length >= 1, from firstTrackSizeItem michael@0: // (the first ) to lastTrackSizeItem (the last), michael@0: // with the sublists in between michael@0: // * lastLineNames: the last michael@0: michael@0: michael@0: // Join the last and first (in that order.) michael@0: // For example, repeat(3, (a) 100px (b) 200px (c)) results in michael@0: // (a) 100px (b) 200px (c a) 100px (b) 200px (c a) 100px (b) 200px (c) michael@0: // This is (c a). michael@0: // Make deep copies: the originals will be moved. michael@0: nsCSSValue joinerLineNames; michael@0: { michael@0: nsCSSValueList* target = nullptr; michael@0: if (lastLineNames.GetUnit() != eCSSUnit_Null) { michael@0: target = joinerLineNames.SetListValue(); michael@0: nsCSSValueList* source = lastLineNames.GetListValue(); michael@0: for (;;) { michael@0: target->mValue = source->mValue; michael@0: source = source->mNext; michael@0: if (!source) { michael@0: break; michael@0: } michael@0: target->mNext = new nsCSSValueList; michael@0: target = target->mNext; michael@0: } michael@0: } michael@0: michael@0: if (firstLineNames.GetUnit() != eCSSUnit_Null) { michael@0: if (target) { michael@0: target->mNext = new nsCSSValueList; michael@0: target = target->mNext; michael@0: } else { michael@0: target = joinerLineNames.SetListValue(); michael@0: } michael@0: nsCSSValueList* source = firstLineNames.GetListValue(); michael@0: for (;;) { michael@0: target->mValue = source->mValue; michael@0: source = source->mNext; michael@0: if (!source) { michael@0: break; michael@0: } michael@0: target->mNext = new nsCSSValueList; michael@0: target = target->mNext; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Join our first with the one before repeat(). michael@0: // (a) repeat(1, (b) 20px) expands to (a b) 20px michael@0: nsCSSValueList* previousItemBeforeRepeat = *aTailPtr; michael@0: ConcatLineNames(previousItemBeforeRepeat->mValue, firstLineNames); michael@0: michael@0: // Move our linked list michael@0: // (first to last , with the sublists in between). michael@0: // This is the first repetition. michael@0: NS_ASSERTION(previousItemBeforeRepeat->mNext == nullptr, michael@0: "Expected the end of a linked list"); michael@0: previousItemBeforeRepeat->mNext = firstTrackSizeItemAuto.forget(); michael@0: nsCSSValueList* firstTrackSizeItem = previousItemBeforeRepeat->mNext; michael@0: nsCSSValueList* tail = lastTrackSizeItem; michael@0: michael@0: // Repeat |repetitions - 1| more times: michael@0: // * the joiner michael@0: // * the linked list michael@0: // (first to last , with the sublists in between) michael@0: MOZ_ASSERT(repetitions > 0, "Expected positive repetitions"); michael@0: while (--repetitions) { michael@0: tail->mNext = new nsCSSValueList; michael@0: tail = tail->mNext; michael@0: tail->mValue = joinerLineNames; michael@0: michael@0: nsCSSValueList* repeatedItem = firstTrackSizeItem; michael@0: for (;;) { michael@0: tail->mNext = new nsCSSValueList; michael@0: tail = tail->mNext; michael@0: tail->mValue = repeatedItem->mValue; michael@0: if (repeatedItem == lastTrackSizeItem) { michael@0: break; michael@0: } michael@0: repeatedItem = repeatedItem->mNext; michael@0: } michael@0: } michael@0: michael@0: // Finally, move our last . michael@0: // Any immediately after repeat() will append to it. michael@0: tail->mNext = new nsCSSValueList; michael@0: tail = tail->mNext; michael@0: tail->mValue = lastLineNames; michael@0: michael@0: *aTailPtr = tail; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseGridTemplateColumnsRows(nsCSSProperty aPropID) michael@0: { michael@0: nsCSSValue value; michael@0: if (ParseVariant(value, VARIANT_INHERIT | VARIANT_NONE, nullptr)) { michael@0: AppendValue(aPropID, value); michael@0: return true; michael@0: } michael@0: michael@0: nsSubstring* ident = NextIdent(); michael@0: if (ident) { michael@0: if (ident->LowerCaseEqualsLiteral("subgrid")) { michael@0: if (!ParseOptionalLineNameListAfterSubgrid(value)) { michael@0: return false; michael@0: } michael@0: AppendValue(aPropID, value); michael@0: return true; michael@0: } michael@0: UngetToken(); michael@0: } michael@0: michael@0: nsCSSValue firstLineNames; michael@0: if (ParseGridLineNames(firstLineNames) == CSSParseResult::Error || michael@0: !ParseGridTrackListWithFirstLineNames(value, firstLineNames)) { michael@0: return false; michael@0: } michael@0: AppendValue(aPropID, value); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseGridTemplateAreasLine(const nsAutoString& aInput, michael@0: css::GridTemplateAreasValue* aAreas, michael@0: nsDataHashtable& aAreaIndices) michael@0: { michael@0: aAreas->mTemplates.AppendElement(mToken.mIdent); michael@0: michael@0: nsCSSGridTemplateAreaScanner scanner(aInput); michael@0: nsCSSGridTemplateAreaToken token; michael@0: css::GridNamedArea* currentArea = nullptr; michael@0: uint32_t row = aAreas->NRows(); michael@0: uint32_t column; michael@0: for (column = 1; scanner.Next(token); column++) { michael@0: if (token.isTrash) { michael@0: return false; michael@0: } michael@0: if (currentArea) { michael@0: if (token.mName == currentArea->mName) { michael@0: if (currentArea->mRowStart == row) { michael@0: // Next column in the first row of this named area. michael@0: currentArea->mColumnEnd++; michael@0: } michael@0: continue; michael@0: } michael@0: // We're exiting |currentArea|, so currentArea is ending at |column|. michael@0: // Make sure that this is consistent with currentArea on previous rows: michael@0: if (currentArea->mColumnEnd != column) { michael@0: NS_ASSERTION(currentArea->mRowStart != row, michael@0: "Inconsistent column end for the first row of a named area."); michael@0: // Not a rectangle michael@0: return false; michael@0: } michael@0: currentArea = nullptr; michael@0: } michael@0: if (!token.mName.IsEmpty()) { michael@0: // Named cell that doesn't have a cell with the same name on its left. michael@0: michael@0: // Check if this is the continuation of an existing named area: michael@0: uint32_t index; michael@0: if (aAreaIndices.Get(token.mName, &index)) { michael@0: MOZ_ASSERT(index < aAreas->mNamedAreas.Length(), michael@0: "Invalid aAreaIndices hash table"); michael@0: currentArea = &aAreas->mNamedAreas[index]; michael@0: if (currentArea->mColumnStart != column || michael@0: currentArea->mRowEnd != row) { michael@0: // Existing named area, but not forming a rectangle michael@0: return false; michael@0: } michael@0: // Next row of an existing named area michael@0: currentArea->mRowEnd++; michael@0: } else { michael@0: // New named area michael@0: aAreaIndices.Put(token.mName, aAreas->mNamedAreas.Length()); michael@0: currentArea = aAreas->mNamedAreas.AppendElement(); michael@0: currentArea->mName = token.mName; michael@0: // For column or row N (starting at 1), michael@0: // the start line is N, the end line is N + 1 michael@0: currentArea->mColumnStart = column; michael@0: currentArea->mColumnEnd = column + 1; michael@0: currentArea->mRowStart = row; michael@0: currentArea->mRowEnd = row + 1; michael@0: } michael@0: } michael@0: } michael@0: if (currentArea && currentArea->mColumnEnd != column) { michael@0: NS_ASSERTION(currentArea->mRowStart != row, michael@0: "Inconsistent column end for the first row of a named area."); michael@0: // Not a rectangle michael@0: return false; michael@0: } michael@0: michael@0: // On the first row, set the number of columns michael@0: // that grid-template-areas contributes to the explicit grid. michael@0: // On other rows, check that the number of columns is consistent michael@0: // between rows. michael@0: if (row == 1) { michael@0: aAreas->mNColumns = column; michael@0: } else if (aAreas->mNColumns != column) { michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseGridTemplateAreas() michael@0: { michael@0: nsCSSValue value; michael@0: if (ParseVariant(value, VARIANT_INHERIT | VARIANT_NONE, nullptr)) { michael@0: AppendValue(eCSSProperty_grid_template_areas, value); michael@0: return true; michael@0: } michael@0: michael@0: nsRefPtr areas = michael@0: new css::GridTemplateAreasValue(); michael@0: nsDataHashtable areaIndices; michael@0: for (;;) { michael@0: if (!GetToken(true)) { michael@0: break; michael@0: } michael@0: if (eCSSToken_String != mToken.mType) { michael@0: UngetToken(); michael@0: break; michael@0: } michael@0: if (!ParseGridTemplateAreasLine(mToken.mIdent, areas, areaIndices)) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (areas->NRows() == 0) { michael@0: return false; michael@0: } michael@0: michael@0: AppendValue(eCSSProperty_grid_template_areas, nsCSSValue(areas)); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseGridTemplate() michael@0: { michael@0: // none | michael@0: // subgrid | michael@0: // <'grid-template-columns'> / <'grid-template-rows'> | michael@0: // [ / ]? [ ? ? ? ]+ michael@0: nsCSSValue value; michael@0: if (ParseVariant(value, VARIANT_INHERIT, nullptr)) { michael@0: AppendValue(eCSSProperty_grid_template_areas, value); michael@0: AppendValue(eCSSProperty_grid_template_columns, value); michael@0: AppendValue(eCSSProperty_grid_template_rows, value); michael@0: return true; michael@0: } michael@0: michael@0: // TODO (bug 983175): add parsing for 'subgrid' by itself michael@0: michael@0: // 'none' can appear either by itself, michael@0: // or as the beginning of <'grid-template-columns'> / <'grid-template-rows'> michael@0: if (ParseVariant(value, VARIANT_NONE, nullptr)) { michael@0: AppendValue(eCSSProperty_grid_template_columns, value); michael@0: if (ExpectSymbol('/', true)) { michael@0: return ParseGridTemplateAfterSlash(/* aColumnsIsTrackList = */ false); michael@0: } michael@0: AppendValue(eCSSProperty_grid_template_areas, value); michael@0: AppendValue(eCSSProperty_grid_template_rows, value); michael@0: return true; michael@0: } michael@0: michael@0: // 'subgrid' can appear either by itself, michael@0: // or as the beginning of <'grid-template-columns'> / <'grid-template-rows'> michael@0: nsSubstring* ident = NextIdent(); michael@0: if (ident) { michael@0: if (ident->LowerCaseEqualsLiteral("subgrid")) { michael@0: if (!ParseOptionalLineNameListAfterSubgrid(value)) { michael@0: return false; michael@0: } michael@0: AppendValue(eCSSProperty_grid_template_columns, value); michael@0: if (ExpectSymbol('/', true)) { michael@0: return ParseGridTemplateAfterSlash(/* aColumnsIsTrackList = */ false); michael@0: } michael@0: if (value.GetListValue()->mNext) { michael@0: // Non-empty after 'subgrid'. michael@0: // This is only valid as part of <'grid-template-columns'>, michael@0: // which must be followed by a slash. michael@0: return false; michael@0: } michael@0: // 'subgrid' by itself sets both grid-template-columns michael@0: // and grid-template-rows. michael@0: AppendValue(eCSSProperty_grid_template_rows, value); michael@0: value.SetNoneValue(); michael@0: AppendValue(eCSSProperty_grid_template_areas, value); michael@0: return true; michael@0: } michael@0: UngetToken(); michael@0: } michael@0: michael@0: // [ ? ] here is ambiguous: michael@0: // it can be either the start of a , michael@0: // or the start of [ ? ? ? ]+ michael@0: nsCSSValue firstLineNames; michael@0: if (ParseGridLineNames(firstLineNames) == CSSParseResult::Error || michael@0: !GetToken(true)) { michael@0: return false; michael@0: } michael@0: if (mToken.mType == eCSSToken_String) { michael@0: // [ / ]? was omitted michael@0: // Parse [ ? ? ? ]+ michael@0: value.SetNoneValue(); michael@0: AppendValue(eCSSProperty_grid_template_columns, value); michael@0: return ParseGridTemplateAfterString(firstLineNames); michael@0: } michael@0: UngetToken(); michael@0: michael@0: if (!(ParseGridTrackListWithFirstLineNames(value, firstLineNames) && michael@0: ExpectSymbol('/', true))) { michael@0: return false; michael@0: } michael@0: AppendValue(eCSSProperty_grid_template_columns, value); michael@0: return ParseGridTemplateAfterSlash(/* aColumnsIsTrackList = */ true); michael@0: } michael@0: michael@0: // Helper for parsing the 'grid-template' shorthand michael@0: // michael@0: // NOTE: This parses the portion after the slash, for *one* of the michael@0: // following types of expressions: michael@0: // - <'grid-template-columns'> / <'grid-template-rows'> michael@0: // - / [ ? ? ? ]+ michael@0: // michael@0: // We don't know which type of expression we've got until we've parsed the michael@0: // second half, since the pre-slash part is ambiguous. The various return michael@0: // clauses below are labeled with the type of expression they're completing. michael@0: bool michael@0: CSSParserImpl::ParseGridTemplateAfterSlash(bool aColumnsIsTrackList) michael@0: { michael@0: nsCSSValue rowsValue; michael@0: if (ParseVariant(rowsValue, VARIANT_NONE, nullptr)) { michael@0: // <'grid-template-columns'> / <'grid-template-rows'> michael@0: AppendValue(eCSSProperty_grid_template_rows, rowsValue); michael@0: nsCSSValue areasValue(eCSSUnit_None); // implied michael@0: AppendValue(eCSSProperty_grid_template_areas, areasValue); michael@0: return true; michael@0: } michael@0: michael@0: nsSubstring* ident = NextIdent(); michael@0: if (ident) { michael@0: if (ident->LowerCaseEqualsLiteral("subgrid")) { michael@0: if (!ParseOptionalLineNameListAfterSubgrid(rowsValue)) { michael@0: return false; michael@0: } michael@0: // <'grid-template-columns'> / <'grid-template-rows'> michael@0: AppendValue(eCSSProperty_grid_template_rows, rowsValue); michael@0: nsCSSValue areasValue(eCSSUnit_None); // implied michael@0: AppendValue(eCSSProperty_grid_template_areas, areasValue); michael@0: return true; michael@0: } michael@0: UngetToken(); michael@0: } michael@0: michael@0: nsCSSValue firstLineNames; michael@0: if (ParseGridLineNames(firstLineNames) == CSSParseResult::Error || michael@0: !GetToken(true)) { michael@0: return false; michael@0: } michael@0: if (aColumnsIsTrackList && mToken.mType == eCSSToken_String) { michael@0: // [ / ]? [ ? ? ? ]+ michael@0: return ParseGridTemplateAfterString(firstLineNames); michael@0: } michael@0: UngetToken(); michael@0: michael@0: if (!ParseGridTrackListWithFirstLineNames(rowsValue, firstLineNames)) { michael@0: return false; michael@0: } michael@0: michael@0: // <'grid-template-columns'> / <'grid-template-rows'> michael@0: AppendValue(eCSSProperty_grid_template_rows, rowsValue); michael@0: nsCSSValue areasValue(eCSSUnit_None); // implied michael@0: AppendValue(eCSSProperty_grid_template_areas, areasValue); michael@0: return true; michael@0: } michael@0: michael@0: // Helper for parsing the 'grid-template' shorthand: michael@0: // Parse [ ? ? ? ]+ michael@0: // with a ? already consumed, stored in |aFirstLineNames|, michael@0: // and the current token a michael@0: bool michael@0: CSSParserImpl::ParseGridTemplateAfterString(const nsCSSValue& aFirstLineNames) michael@0: { michael@0: MOZ_ASSERT(mToken.mType == eCSSToken_String, michael@0: "ParseGridTemplateAfterString called with a non-string token"); michael@0: michael@0: nsCSSValue rowsValue; michael@0: nsRefPtr areas = michael@0: new css::GridTemplateAreasValue(); michael@0: nsDataHashtable areaIndices; michael@0: nsCSSValueList* rowsItem = rowsValue.SetListValue(); michael@0: rowsItem->mValue = aFirstLineNames; michael@0: michael@0: for (;;) { michael@0: if (!ParseGridTemplateAreasLine(mToken.mIdent, areas, areaIndices)) { michael@0: return false; michael@0: } michael@0: michael@0: rowsItem->mNext = new nsCSSValueList; michael@0: rowsItem = rowsItem->mNext; michael@0: CSSParseResult result = ParseGridTrackSize(rowsItem->mValue); michael@0: if (result == CSSParseResult::Error) { michael@0: return false; michael@0: } michael@0: if (result == CSSParseResult::NotFound) { michael@0: rowsItem->mValue.SetAutoValue(); michael@0: } michael@0: michael@0: rowsItem->mNext = new nsCSSValueList; michael@0: rowsItem = rowsItem->mNext; michael@0: result = ParseGridLineNames(rowsItem->mValue); michael@0: if (result == CSSParseResult::Error) { michael@0: return false; michael@0: } michael@0: if (result == CSSParseResult::Ok) { michael@0: // Append to the same list as the previous call to ParseGridLineNames. michael@0: result = ParseGridLineNames(rowsItem->mValue); michael@0: if (result == CSSParseResult::Error) { michael@0: return false; michael@0: } michael@0: if (result == CSSParseResult::Ok) { michael@0: // Parsed twice. michael@0: // The property value can not end here, we expect a string next. michael@0: if (!GetToken(true)) { michael@0: return false; michael@0: } michael@0: if (eCSSToken_String != mToken.mType) { michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: // Did not find a . michael@0: // Next, we expect either a string or the end of the property value. michael@0: if (!GetToken(true)) { michael@0: break; michael@0: } michael@0: if (eCSSToken_String != mToken.mType) { michael@0: UngetToken(); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: AppendValue(eCSSProperty_grid_template_areas, nsCSSValue(areas)); michael@0: AppendValue(eCSSProperty_grid_template_rows, rowsValue); michael@0: return true; michael@0: } michael@0: michael@0: // <'grid-template'> | michael@0: // [ <'grid-auto-flow'> [ <'grid-auto-columns'> [ / <'grid-auto-rows'> ]? ]? ] michael@0: bool michael@0: CSSParserImpl::ParseGrid() michael@0: { michael@0: nsCSSValue value; michael@0: if (ParseVariant(value, VARIANT_INHERIT, nullptr)) { michael@0: for (const nsCSSProperty* subprops = michael@0: nsCSSProps::SubpropertyEntryFor(eCSSProperty_grid); michael@0: *subprops != eCSSProperty_UNKNOWN; ++subprops) { michael@0: AppendValue(*subprops, value); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: // 'none' at the beginning could be a <'grid-auto-flow'> michael@0: // (which also covers 'none' by itself) michael@0: // or a <'grid-template-columns'> (as part of <'grid-template'>) michael@0: if (ParseVariant(value, VARIANT_NONE, nullptr)) { michael@0: if (ExpectSymbol('/', true)) { michael@0: AppendValue(eCSSProperty_grid_template_columns, value); michael@0: michael@0: // Set grid-auto-* subproperties to their initial values. michael@0: value.SetIntValue(NS_STYLE_GRID_AUTO_FLOW_NONE, eCSSUnit_Enumerated); michael@0: AppendValue(eCSSProperty_grid_auto_flow, value); michael@0: value.SetAutoValue(); michael@0: AppendValue(eCSSProperty_grid_auto_columns, value); michael@0: AppendValue(eCSSProperty_grid_auto_rows, value); michael@0: michael@0: return ParseGridTemplateAfterSlash(/* aColumnsIsTrackList = */ false); michael@0: } michael@0: value.SetIntValue(NS_STYLE_GRID_AUTO_FLOW_NONE, eCSSUnit_Enumerated); michael@0: AppendValue(eCSSProperty_grid_auto_flow, value); michael@0: return ParseGridShorthandAutoProps(); michael@0: } michael@0: michael@0: // An empty value is always invalid. michael@0: if (!GetToken(true)) { michael@0: return false; michael@0: } michael@0: michael@0: // If the value starts with a 'dense', 'column' or 'row' keyword, michael@0: // it can only start with a <'grid-auto-flow'> michael@0: if (mToken.mType == eCSSToken_Ident) { michael@0: nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent); michael@0: if (keyword == eCSSKeyword_dense || michael@0: keyword == eCSSKeyword_column || michael@0: keyword == eCSSKeyword_row) { michael@0: UngetToken(); michael@0: return ParseGridAutoFlow() && ParseGridShorthandAutoProps(); michael@0: } michael@0: } michael@0: UngetToken(); michael@0: michael@0: // Set other subproperties to their initial values michael@0: // and parse <'grid-template'>. michael@0: value.SetIntValue(NS_STYLE_GRID_AUTO_FLOW_NONE, eCSSUnit_Enumerated); michael@0: AppendValue(eCSSProperty_grid_auto_flow, value); michael@0: value.SetAutoValue(); michael@0: AppendValue(eCSSProperty_grid_auto_columns, value); michael@0: AppendValue(eCSSProperty_grid_auto_rows, value); michael@0: return ParseGridTemplate(); michael@0: } michael@0: michael@0: // Parse [ <'grid-auto-columns'> [ / <'grid-auto-rows'> ]? ]? michael@0: // for the 'grid' shorthand. michael@0: // Assumes that <'grid-auto-flow'> was already parsed by the caller. michael@0: bool michael@0: CSSParserImpl::ParseGridShorthandAutoProps() michael@0: { michael@0: nsCSSValue autoColumnsValue; michael@0: nsCSSValue autoRowsValue; michael@0: CSSParseResult result = ParseGridTrackSize(autoColumnsValue); michael@0: if (result == CSSParseResult::Error) { michael@0: return false; michael@0: } michael@0: if (result == CSSParseResult::NotFound) { michael@0: autoColumnsValue.SetAutoValue(); michael@0: autoRowsValue.SetAutoValue(); michael@0: } else { michael@0: if (!ExpectSymbol('/', true)) { michael@0: autoRowsValue.SetAutoValue(); michael@0: } else if (ParseGridTrackSize(autoRowsValue) != CSSParseResult::Ok) { michael@0: return false; michael@0: } michael@0: } michael@0: AppendValue(eCSSProperty_grid_auto_columns, autoColumnsValue); michael@0: AppendValue(eCSSProperty_grid_auto_rows, autoRowsValue); michael@0: nsCSSValue templateValue(eCSSUnit_None); // Initial values michael@0: AppendValue(eCSSProperty_grid_template_areas, templateValue); michael@0: AppendValue(eCSSProperty_grid_template_columns, templateValue); michael@0: AppendValue(eCSSProperty_grid_template_rows, templateValue); michael@0: return true; michael@0: } michael@0: michael@0: // Parse a . michael@0: // If successful, set aValue to eCSSUnit_Auto, michael@0: // or a eCSSUnit_List containing, in that order: michael@0: // michael@0: // * An optional eCSSUnit_Enumerated marking a "span" keyword. michael@0: // * An optional eCSSUnit_Integer michael@0: // * An optional eCSSUnit_Ident michael@0: // michael@0: // At least one of eCSSUnit_Integer or eCSSUnit_Ident is present. michael@0: bool michael@0: CSSParserImpl::ParseGridLine(nsCSSValue& aValue) michael@0: { michael@0: // = michael@0: // auto | michael@0: // | michael@0: // [ && ? ] | michael@0: // [ span && [ || ] ] michael@0: // michael@0: // Syntactically, this simplifies to: michael@0: // michael@0: // = michael@0: // auto | michael@0: // [ span? && [ || ] ] michael@0: michael@0: if (ParseVariant(aValue, VARIANT_AUTO, nullptr)) { michael@0: return true; michael@0: } michael@0: michael@0: static const nsCSSKeyword kGridLineKeywords[] = { michael@0: eCSSKeyword_span, michael@0: eCSSKeyword_UNKNOWN // End-of-array marker michael@0: }; michael@0: bool hasSpan = false; michael@0: bool hasInteger = false; michael@0: bool hasIdent = false; michael@0: int32_t integer; michael@0: nsCSSValue ident; michael@0: michael@0: if (!GetToken(true)) { michael@0: return false; michael@0: } michael@0: if (mToken.mType == eCSSToken_Ident && michael@0: mToken.mIdent.LowerCaseEqualsLiteral("span")) { michael@0: hasSpan = true; michael@0: if (!GetToken(true)) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: do { michael@0: if (!hasIdent && michael@0: mToken.mType == eCSSToken_Ident && michael@0: ParseCustomIdent(ident, mToken.mIdent, kGridLineKeywords)) { michael@0: hasIdent = true; michael@0: } else if (!hasInteger && michael@0: mToken.mType == eCSSToken_Number && michael@0: mToken.mIntegerValid && michael@0: mToken.mInteger != 0) { michael@0: hasInteger = true; michael@0: integer = mToken.mInteger; michael@0: } else { michael@0: UngetToken(); michael@0: break; michael@0: } michael@0: } while (!(hasInteger && hasIdent) && GetToken(true)); michael@0: michael@0: // Require at least one of or michael@0: if (!(hasInteger || hasIdent)) { michael@0: return false; michael@0: } michael@0: michael@0: if (!hasSpan && GetToken(true)) { michael@0: if (mToken.mType == eCSSToken_Ident && michael@0: mToken.mIdent.LowerCaseEqualsLiteral("span")) { michael@0: hasSpan = true; michael@0: } else { michael@0: UngetToken(); michael@0: } michael@0: } michael@0: michael@0: nsCSSValueList* item = aValue.SetListValue(); michael@0: if (hasSpan) { michael@0: // Given "span", a negative is invalid. michael@0: if (hasInteger && integer < 0) { michael@0: return false; michael@0: } michael@0: // '1' here is a dummy value. michael@0: // The mere presence of eCSSUnit_Enumerated indicates a "span" keyword. michael@0: item->mValue.SetIntValue(1, eCSSUnit_Enumerated); michael@0: item->mNext = new nsCSSValueList; michael@0: item = item->mNext; michael@0: } michael@0: if (hasInteger) { michael@0: item->mValue.SetIntValue(integer, eCSSUnit_Integer); michael@0: if (hasIdent) { michael@0: item->mNext = new nsCSSValueList; michael@0: item = item->mNext; michael@0: } michael@0: } michael@0: if (hasIdent) { michael@0: item->mValue = ident; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseGridAutoPosition() michael@0: { michael@0: nsCSSValue value; michael@0: if (ParseVariant(value, VARIANT_INHERIT, nullptr)) { michael@0: AppendValue(eCSSProperty_grid_auto_position, value); michael@0: return true; michael@0: } michael@0: nsCSSValue columnStartValue; michael@0: nsCSSValue rowStartValue; michael@0: if (ParseGridLine(columnStartValue) && michael@0: ExpectSymbol('/', true) && michael@0: ParseGridLine(rowStartValue)) { michael@0: value.SetPairValue(columnStartValue, rowStartValue); michael@0: AppendValue(eCSSProperty_grid_auto_position, value); michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseGridColumnRowStartEnd(nsCSSProperty aPropID) michael@0: { michael@0: nsCSSValue value; michael@0: if (ParseVariant(value, VARIANT_INHERIT, nullptr) || michael@0: ParseGridLine(value)) { michael@0: AppendValue(aPropID, value); michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: // If |aFallback| is a List containing a single Ident, set |aValue| to that. michael@0: // Otherwise, set |aValue| to Auto. michael@0: // Used with |aFallback| from ParseGridLine() michael@0: static void michael@0: HandleGridLineFallback(const nsCSSValue& aFallback, nsCSSValue& aValue) michael@0: { michael@0: if (aFallback.GetUnit() == eCSSUnit_List && michael@0: aFallback.GetListValue()->mValue.GetUnit() == eCSSUnit_Ident && michael@0: !aFallback.GetListValue()->mNext) { michael@0: aValue = aFallback; michael@0: } else { michael@0: aValue.SetAutoValue(); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseGridColumnRow(nsCSSProperty aStartPropID, michael@0: nsCSSProperty aEndPropID) michael@0: { michael@0: nsCSSValue value; michael@0: nsCSSValue secondValue; michael@0: if (ParseVariant(value, VARIANT_INHERIT, nullptr)) { michael@0: AppendValue(aStartPropID, value); michael@0: AppendValue(aEndPropID, value); michael@0: return true; michael@0: } michael@0: michael@0: if (!ParseGridLine(value)) { michael@0: return false; michael@0: } michael@0: if (GetToken(true)) { michael@0: if (mToken.IsSymbol('/')) { michael@0: if (ParseGridLine(secondValue)) { michael@0: AppendValue(aStartPropID, value); michael@0: AppendValue(aEndPropID, secondValue); michael@0: return true; michael@0: } else { michael@0: return false; michael@0: } michael@0: } michael@0: UngetToken(); michael@0: } michael@0: michael@0: // A single is repeated to both properties, michael@0: // anything else sets the grid-{column,row}-end property to 'auto'. michael@0: HandleGridLineFallback(value, secondValue); michael@0: michael@0: AppendValue(aStartPropID, value); michael@0: AppendValue(aEndPropID, secondValue); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseGridArea() michael@0: { michael@0: nsCSSValue values[4]; michael@0: if (ParseVariant(values[0], VARIANT_INHERIT, nullptr)) { michael@0: AppendValue(eCSSProperty_grid_row_start, values[0]); michael@0: AppendValue(eCSSProperty_grid_column_start, values[0]); michael@0: AppendValue(eCSSProperty_grid_row_end, values[0]); michael@0: AppendValue(eCSSProperty_grid_column_end, values[0]); michael@0: return true; michael@0: } michael@0: michael@0: int32_t i = 0; michael@0: for (;;) { michael@0: if (!ParseGridLine(values[i])) { michael@0: return false; michael@0: } michael@0: if (++i == 4 || !GetToken(true)) { michael@0: break; michael@0: } michael@0: if (!mToken.IsSymbol('/')) { michael@0: UngetToken(); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: MOZ_ASSERT(i >= 1, "should have parsed at least one grid-line (or returned)"); michael@0: if (i < 2) { michael@0: HandleGridLineFallback(values[0], values[1]); michael@0: } michael@0: if (i < 3) { michael@0: HandleGridLineFallback(values[0], values[2]); michael@0: } michael@0: if (i < 4) { michael@0: HandleGridLineFallback(values[1], values[3]); michael@0: } michael@0: michael@0: AppendValue(eCSSProperty_grid_row_start, values[0]); michael@0: AppendValue(eCSSProperty_grid_column_start, values[1]); michael@0: AppendValue(eCSSProperty_grid_row_end, values[2]); michael@0: AppendValue(eCSSProperty_grid_column_end, values[3]); michael@0: return true; michael@0: } michael@0: michael@0: // : [ | ]? michael@0: bool michael@0: CSSParserImpl::ParseColorStop(nsCSSValueGradient* aGradient) michael@0: { michael@0: nsCSSValueGradientStop* stop = aGradient->mStops.AppendElement(); michael@0: if (!ParseVariant(stop->mColor, VARIANT_COLOR, nullptr)) { michael@0: return false; michael@0: } michael@0: michael@0: // Stop positions do not have to fall between the starting-point and michael@0: // ending-point, so we don't use ParseNonNegativeVariant. michael@0: if (!ParseVariant(stop->mLocation, VARIANT_LP | VARIANT_CALC, nullptr)) { michael@0: stop->mLocation.SetNoneValue(); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: // michael@0: // : linear-gradient( ? ')' michael@0: // | radial-gradient( ? ')' michael@0: // michael@0: // : [ to [left | right] || [top | bottom] ] , michael@0: // | michael@0: // : [ || ] [ at ]? , michael@0: // | [ at ] , michael@0: // | ? ? michael@0: // : circle | ellipse michael@0: // : closest-side | closest-corner | farthest-side | farthest-corner michael@0: // | | [ | ]{2} michael@0: // michael@0: // : [ || ] , michael@0: // michael@0: // : [ || ] , michael@0: // : closest-side | closest-corner | farthest-side michael@0: // | farthest-corner | contain | cover michael@0: // michael@0: // : , [, ]* michael@0: bool michael@0: CSSParserImpl::ParseLinearGradient(nsCSSValue& aValue, bool aIsRepeating, michael@0: bool aIsLegacy) michael@0: { michael@0: nsRefPtr cssGradient michael@0: = new nsCSSValueGradient(false, aIsRepeating); michael@0: michael@0: if (!GetToken(true)) { michael@0: return false; michael@0: } michael@0: michael@0: if (mToken.mType == eCSSToken_Ident && michael@0: mToken.mIdent.LowerCaseEqualsLiteral("to")) { michael@0: michael@0: // "to" syntax doesn't allow explicit "center" michael@0: if (!ParseBoxPositionValues(cssGradient->mBgPos, false, false)) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: michael@0: // [ to [left | right] || [top | bottom] ] , michael@0: const nsCSSValue& xValue = cssGradient->mBgPos.mXValue; michael@0: const nsCSSValue& yValue = cssGradient->mBgPos.mYValue; michael@0: if (xValue.GetUnit() != eCSSUnit_Enumerated || michael@0: !(xValue.GetIntValue() & (NS_STYLE_BG_POSITION_LEFT | michael@0: NS_STYLE_BG_POSITION_CENTER | michael@0: NS_STYLE_BG_POSITION_RIGHT)) || michael@0: yValue.GetUnit() != eCSSUnit_Enumerated || michael@0: !(yValue.GetIntValue() & (NS_STYLE_BG_POSITION_TOP | michael@0: NS_STYLE_BG_POSITION_CENTER | michael@0: NS_STYLE_BG_POSITION_BOTTOM))) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: michael@0: if (!ExpectSymbol(',', true)) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: michael@0: return ParseGradientColorStops(cssGradient, aValue); michael@0: } michael@0: michael@0: if (!aIsLegacy) { michael@0: UngetToken(); michael@0: michael@0: // , michael@0: if (ParseVariant(cssGradient->mAngle, VARIANT_ANGLE, nullptr) && michael@0: !ExpectSymbol(',', true)) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: michael@0: return ParseGradientColorStops(cssGradient, aValue); michael@0: } michael@0: michael@0: nsCSSTokenType ty = mToken.mType; michael@0: nsString id = mToken.mIdent; michael@0: UngetToken(); michael@0: michael@0: // michael@0: bool haveGradientLine = IsLegacyGradientLine(ty, id); michael@0: if (haveGradientLine) { michael@0: cssGradient->mIsLegacySyntax = true; michael@0: bool haveAngle = michael@0: ParseVariant(cssGradient->mAngle, VARIANT_ANGLE, nullptr); michael@0: michael@0: // if we got an angle, we might now have a comma, ending the gradient-line michael@0: if (!haveAngle || !ExpectSymbol(',', true)) { michael@0: if (!ParseBoxPositionValues(cssGradient->mBgPos, false)) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: michael@0: if (!ExpectSymbol(',', true) && michael@0: // if we didn't already get an angle, we might have one now, michael@0: // otherwise it's an error michael@0: (haveAngle || michael@0: !ParseVariant(cssGradient->mAngle, VARIANT_ANGLE, nullptr) || michael@0: // now we better have a comma michael@0: !ExpectSymbol(',', true))) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return ParseGradientColorStops(cssGradient, aValue); michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseRadialGradient(nsCSSValue& aValue, bool aIsRepeating, michael@0: bool aIsLegacy) michael@0: { michael@0: nsRefPtr cssGradient michael@0: = new nsCSSValueGradient(true, aIsRepeating); michael@0: michael@0: // [ || ] michael@0: bool haveShape = michael@0: ParseVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD, michael@0: nsCSSProps::kRadialGradientShapeKTable); michael@0: michael@0: bool haveSize = ParseVariant(cssGradient->GetRadialSize(), VARIANT_KEYWORD, michael@0: aIsLegacy ? michael@0: nsCSSProps::kRadialGradientLegacySizeKTable : michael@0: nsCSSProps::kRadialGradientSizeKTable); michael@0: if (haveSize) { michael@0: if (!haveShape) { michael@0: // michael@0: haveShape = ParseVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD, michael@0: nsCSSProps::kRadialGradientShapeKTable); michael@0: } michael@0: } else if (!aIsLegacy) { michael@0: // Save RadialShape before parsing RadiusX because RadialShape and michael@0: // RadiusX share the storage. michael@0: int32_t shape = michael@0: cssGradient->GetRadialShape().GetUnit() == eCSSUnit_Enumerated ? michael@0: cssGradient->GetRadialShape().GetIntValue() : -1; michael@0: // | [ | ]{2} michael@0: cssGradient->mIsExplicitSize = true; michael@0: haveSize = michael@0: ParseNonNegativeVariant(cssGradient->GetRadiusX(), VARIANT_LP, nullptr); michael@0: if (!haveSize) { michael@0: // It was not an explicit size after all. michael@0: // Note that ParseNonNegativeVariant may have put something michael@0: // invalid into our storage, but only in the case where it was michael@0: // rejected only for being negative. Since this means the token michael@0: // was a length or a percentage, we know it's not valid syntax michael@0: // (which must be a comma, the 'at' keyword, or a color), so we michael@0: // know this value will be dropped. This means it doesn't matter michael@0: // that we have something invalid in our storage. michael@0: cssGradient->mIsExplicitSize = false; michael@0: } else { michael@0: // vertical extent is optional michael@0: bool haveYSize = michael@0: ParseNonNegativeVariant(cssGradient->GetRadiusY(), VARIANT_LP, nullptr); michael@0: if (!haveShape) { michael@0: nsCSSValue shapeValue; michael@0: haveShape = ParseVariant(shapeValue, VARIANT_KEYWORD, michael@0: nsCSSProps::kRadialGradientShapeKTable); michael@0: if (haveShape) { michael@0: shape = shapeValue.GetIntValue(); michael@0: } michael@0: } michael@0: if (haveYSize michael@0: ? shape == NS_STYLE_GRADIENT_SHAPE_CIRCULAR michael@0: : cssGradient->GetRadiusX().GetUnit() == eCSSUnit_Percent || michael@0: shape == NS_STYLE_GRADIENT_SHAPE_ELLIPTICAL) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if ((haveShape || haveSize) && ExpectSymbol(',', true)) { michael@0: // [ || ] , michael@0: return ParseGradientColorStops(cssGradient, aValue); michael@0: } michael@0: michael@0: if (!GetToken(true)) { michael@0: return false; michael@0: } michael@0: michael@0: if (!aIsLegacy) { michael@0: if (mToken.mType == eCSSToken_Ident && michael@0: mToken.mIdent.LowerCaseEqualsLiteral("at")) { michael@0: // [ || ]? at , michael@0: if (!ParseBoxPositionValues(cssGradient->mBgPos, false) || michael@0: !ExpectSymbol(',', true)) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: michael@0: return ParseGradientColorStops(cssGradient, aValue); michael@0: } michael@0: michael@0: // only michael@0: UngetToken(); michael@0: return ParseGradientColorStops(cssGradient, aValue); michael@0: } michael@0: MOZ_ASSERT(!cssGradient->mIsExplicitSize); michael@0: michael@0: nsCSSTokenType ty = mToken.mType; michael@0: nsString id = mToken.mIdent; michael@0: UngetToken(); michael@0: michael@0: // michael@0: bool haveGradientLine = false; michael@0: // if we already encountered a shape or size, michael@0: // we can not have a gradient-line in legacy syntax michael@0: if (!haveShape && !haveSize) { michael@0: haveGradientLine = IsLegacyGradientLine(ty, id); michael@0: } michael@0: if (haveGradientLine) { michael@0: bool haveAngle = michael@0: ParseVariant(cssGradient->mAngle, VARIANT_ANGLE, nullptr); michael@0: michael@0: // if we got an angle, we might now have a comma, ending the gradient-line michael@0: if (!haveAngle || !ExpectSymbol(',', true)) { michael@0: if (!ParseBoxPositionValues(cssGradient->mBgPos, false)) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: michael@0: if (!ExpectSymbol(',', true) && michael@0: // if we didn't already get an angle, we might have one now, michael@0: // otherwise it's an error michael@0: (haveAngle || michael@0: !ParseVariant(cssGradient->mAngle, VARIANT_ANGLE, nullptr) || michael@0: // now we better have a comma michael@0: !ExpectSymbol(',', true))) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (cssGradient->mAngle.GetUnit() != eCSSUnit_None) { michael@0: cssGradient->mIsLegacySyntax = true; michael@0: } michael@0: } michael@0: michael@0: // radial gradients might have a shape and size here for legacy syntax michael@0: if (!haveShape && !haveSize) { michael@0: haveShape = michael@0: ParseVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD, michael@0: nsCSSProps::kRadialGradientShapeKTable); michael@0: haveSize = michael@0: ParseVariant(cssGradient->GetRadialSize(), VARIANT_KEYWORD, michael@0: nsCSSProps::kRadialGradientLegacySizeKTable); michael@0: michael@0: // could be in either order michael@0: if (!haveShape) { michael@0: haveShape = michael@0: ParseVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD, michael@0: nsCSSProps::kRadialGradientShapeKTable); michael@0: } michael@0: } michael@0: michael@0: if ((haveShape || haveSize) && !ExpectSymbol(',', true)) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: michael@0: return ParseGradientColorStops(cssGradient, aValue); michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::IsLegacyGradientLine(const nsCSSTokenType& aType, michael@0: const nsString& aId) michael@0: { michael@0: // N.B. ParseBoxPositionValues is not guaranteed to put back michael@0: // everything it scanned if it fails, so we must only call it michael@0: // if there is no alternative to consuming a . michael@0: // ParseVariant, as used here, will either succeed and consume michael@0: // a single token, or fail and consume none, so we can be more michael@0: // cavalier about calling it. michael@0: michael@0: bool haveGradientLine = false; michael@0: switch (aType) { michael@0: case eCSSToken_Percentage: michael@0: case eCSSToken_Number: michael@0: case eCSSToken_Dimension: michael@0: haveGradientLine = true; michael@0: break; michael@0: michael@0: case eCSSToken_Function: michael@0: if (aId.LowerCaseEqualsLiteral("calc") || michael@0: aId.LowerCaseEqualsLiteral("-moz-calc")) { michael@0: haveGradientLine = true; michael@0: break; michael@0: } michael@0: // fall through michael@0: case eCSSToken_ID: michael@0: case eCSSToken_Hash: michael@0: // this is a color michael@0: break; michael@0: michael@0: case eCSSToken_Ident: { michael@0: // This is only a gradient line if it's a box position keyword. michael@0: nsCSSKeyword kw = nsCSSKeywords::LookupKeyword(aId); michael@0: int32_t junk; michael@0: if (kw != eCSSKeyword_UNKNOWN && michael@0: nsCSSProps::FindKeyword(kw, nsCSSProps::kBackgroundPositionKTable, michael@0: junk)) { michael@0: haveGradientLine = true; michael@0: } michael@0: break; michael@0: } michael@0: michael@0: default: michael@0: // error michael@0: break; michael@0: } michael@0: michael@0: return haveGradientLine; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseGradientColorStops(nsCSSValueGradient* aGradient, michael@0: nsCSSValue& aValue) michael@0: { michael@0: // At least two color stops are required michael@0: if (!ParseColorStop(aGradient) || michael@0: !ExpectSymbol(',', true) || michael@0: !ParseColorStop(aGradient)) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: michael@0: // Additional color stops michael@0: while (ExpectSymbol(',', true)) { michael@0: if (!ParseColorStop(aGradient)) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (!ExpectSymbol(')', true)) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: michael@0: aValue.SetGradientValue(aGradient); michael@0: return true; michael@0: } michael@0: michael@0: int32_t michael@0: CSSParserImpl::ParseChoice(nsCSSValue aValues[], michael@0: const nsCSSProperty aPropIDs[], int32_t aNumIDs) michael@0: { michael@0: int32_t found = 0; michael@0: nsAutoParseCompoundProperty compound(this); michael@0: michael@0: int32_t loop; michael@0: for (loop = 0; loop < aNumIDs; loop++) { michael@0: // Try each property parser in order michael@0: int32_t hadFound = found; michael@0: int32_t index; michael@0: for (index = 0; index < aNumIDs; index++) { michael@0: int32_t bit = 1 << index; michael@0: if ((found & bit) == 0) { michael@0: if (ParseSingleValueProperty(aValues[index], aPropIDs[index])) { michael@0: found |= bit; michael@0: // It's more efficient to break since it will reset |hadFound| michael@0: // to |found|. Furthermore, ParseListStyle depends on our going michael@0: // through the properties in order for each value.. michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: if (found == hadFound) { // found nothing new michael@0: break; michael@0: } michael@0: } michael@0: if (0 < found) { michael@0: if (1 == found) { // only first property michael@0: if (eCSSUnit_Inherit == aValues[0].GetUnit()) { // one inherit, all inherit michael@0: for (loop = 1; loop < aNumIDs; loop++) { michael@0: aValues[loop].SetInheritValue(); michael@0: } michael@0: found = ((1 << aNumIDs) - 1); michael@0: } michael@0: else if (eCSSUnit_Initial == aValues[0].GetUnit()) { // one initial, all initial michael@0: for (loop = 1; loop < aNumIDs; loop++) { michael@0: aValues[loop].SetInitialValue(); michael@0: } michael@0: found = ((1 << aNumIDs) - 1); michael@0: } michael@0: else if (eCSSUnit_Unset == aValues[0].GetUnit()) { // one unset, all unset michael@0: for (loop = 1; loop < aNumIDs; loop++) { michael@0: aValues[loop].SetUnsetValue(); michael@0: } michael@0: found = ((1 << aNumIDs) - 1); michael@0: } michael@0: } michael@0: else { // more than one value, verify no inherits, initials or unsets michael@0: for (loop = 0; loop < aNumIDs; loop++) { michael@0: if (eCSSUnit_Inherit == aValues[loop].GetUnit()) { michael@0: found = -1; michael@0: break; michael@0: } michael@0: else if (eCSSUnit_Initial == aValues[loop].GetUnit()) { michael@0: found = -1; michael@0: break; michael@0: } michael@0: else if (eCSSUnit_Unset == aValues[loop].GetUnit()) { michael@0: found = -1; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: return found; michael@0: } michael@0: michael@0: void michael@0: CSSParserImpl::AppendValue(nsCSSProperty aPropID, const nsCSSValue& aValue) michael@0: { michael@0: mTempData.AddLonghandProperty(aPropID, aValue); michael@0: } michael@0: michael@0: /** michael@0: * Parse a "box" property. Box properties have 1 to 4 values. When less michael@0: * than 4 values are provided a standard mapping is used to replicate michael@0: * existing values. michael@0: */ michael@0: bool michael@0: CSSParserImpl::ParseBoxProperties(const nsCSSProperty aPropIDs[]) michael@0: { michael@0: // Get up to four values for the property michael@0: int32_t count = 0; michael@0: nsCSSRect result; michael@0: NS_FOR_CSS_SIDES (index) { michael@0: if (! ParseSingleValueProperty(result.*(nsCSSRect::sides[index]), michael@0: aPropIDs[index])) { michael@0: break; michael@0: } michael@0: count++; michael@0: } michael@0: if (count == 0) { michael@0: return false; michael@0: } michael@0: michael@0: if (1 < count) { // verify no more than single inherit, initial or unset michael@0: NS_FOR_CSS_SIDES (index) { michael@0: nsCSSUnit unit = (result.*(nsCSSRect::sides[index])).GetUnit(); michael@0: if (eCSSUnit_Inherit == unit || michael@0: eCSSUnit_Initial == unit || michael@0: eCSSUnit_Unset == unit) { michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Provide missing values by replicating some of the values found michael@0: switch (count) { michael@0: case 1: // Make right == top michael@0: result.mRight = result.mTop; michael@0: case 2: // Make bottom == top michael@0: result.mBottom = result.mTop; michael@0: case 3: // Make left == right michael@0: result.mLeft = result.mRight; michael@0: } michael@0: michael@0: NS_FOR_CSS_SIDES (index) { michael@0: AppendValue(aPropIDs[index], result.*(nsCSSRect::sides[index])); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: // Similar to ParseBoxProperties, except there is only one property michael@0: // with the result as its value, not four. Requires values be nonnegative. michael@0: bool michael@0: CSSParserImpl::ParseGroupedBoxProperty(int32_t aVariantMask, michael@0: /** outparam */ nsCSSValue& aValue) michael@0: { michael@0: nsCSSRect& result = aValue.SetRectValue(); michael@0: michael@0: int32_t count = 0; michael@0: NS_FOR_CSS_SIDES (index) { michael@0: if (!ParseNonNegativeVariant(result.*(nsCSSRect::sides[index]), michael@0: aVariantMask, nullptr)) { michael@0: break; michael@0: } michael@0: count++; michael@0: } michael@0: michael@0: if (count == 0) { michael@0: return false; michael@0: } michael@0: michael@0: // Provide missing values by replicating some of the values found michael@0: switch (count) { michael@0: case 1: // Make right == top michael@0: result.mRight = result.mTop; michael@0: case 2: // Make bottom == top michael@0: result.mBottom = result.mTop; michael@0: case 3: // Make left == right michael@0: result.mLeft = result.mRight; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseDirectionalBoxProperty(nsCSSProperty aProperty, michael@0: int32_t aSourceType) michael@0: { michael@0: const nsCSSProperty* subprops = nsCSSProps::SubpropertyEntryFor(aProperty); michael@0: NS_ASSERTION(subprops[3] == eCSSProperty_UNKNOWN, michael@0: "not box property with physical vs. logical cascading"); michael@0: nsCSSValue value; michael@0: if (!ParseSingleValueProperty(value, subprops[0])) { michael@0: return false; michael@0: } michael@0: michael@0: AppendValue(subprops[0], value); michael@0: nsCSSValue typeVal(aSourceType, eCSSUnit_Enumerated); michael@0: AppendValue(subprops[1], typeVal); michael@0: AppendValue(subprops[2], typeVal); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseBoxCornerRadius(nsCSSProperty aPropID) michael@0: { michael@0: nsCSSValue dimenX, dimenY; michael@0: // required first value michael@0: if (! ParseNonNegativeVariant(dimenX, VARIANT_HLP | VARIANT_CALC, nullptr)) michael@0: return false; michael@0: michael@0: // optional second value (forbidden if first value is inherit/initial/unset) michael@0: if (dimenX.GetUnit() != eCSSUnit_Inherit && michael@0: dimenX.GetUnit() != eCSSUnit_Initial && michael@0: dimenX.GetUnit() != eCSSUnit_Unset) { michael@0: ParseNonNegativeVariant(dimenY, VARIANT_LP | VARIANT_CALC, nullptr); michael@0: } michael@0: michael@0: if (dimenX == dimenY || dimenY.GetUnit() == eCSSUnit_Null) { michael@0: AppendValue(aPropID, dimenX); michael@0: } else { michael@0: nsCSSValue value; michael@0: value.SetPairValue(dimenX, dimenY); michael@0: AppendValue(aPropID, value); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseBoxCornerRadii(const nsCSSProperty aPropIDs[]) michael@0: { michael@0: // Rectangles are used as scratch storage. michael@0: // top => top-left, right => top-right, michael@0: // bottom => bottom-right, left => bottom-left. michael@0: nsCSSRect dimenX, dimenY; michael@0: int32_t countX = 0, countY = 0; michael@0: michael@0: NS_FOR_CSS_SIDES (side) { michael@0: if (! ParseNonNegativeVariant(dimenX.*nsCSSRect::sides[side], michael@0: (side > 0 ? 0 : VARIANT_INHERIT) | michael@0: VARIANT_LP | VARIANT_CALC, michael@0: nullptr)) michael@0: break; michael@0: countX++; michael@0: } michael@0: if (countX == 0) michael@0: return false; michael@0: michael@0: if (ExpectSymbol('/', true)) { michael@0: NS_FOR_CSS_SIDES (side) { michael@0: if (! ParseNonNegativeVariant(dimenY.*nsCSSRect::sides[side], michael@0: VARIANT_LP | VARIANT_CALC, nullptr)) michael@0: break; michael@0: countY++; michael@0: } michael@0: if (countY == 0) michael@0: return false; michael@0: } michael@0: michael@0: // if 'initial', 'inherit' or 'unset' was used, it must be the only value michael@0: if (countX > 1 || countY > 0) { michael@0: nsCSSUnit unit = dimenX.mTop.GetUnit(); michael@0: if (eCSSUnit_Inherit == unit || michael@0: eCSSUnit_Initial == unit || michael@0: eCSSUnit_Unset == unit) michael@0: return false; michael@0: } michael@0: michael@0: // if we have no Y-values, use the X-values michael@0: if (countY == 0) { michael@0: dimenY = dimenX; michael@0: countY = countX; michael@0: } michael@0: michael@0: // Provide missing values by replicating some of the values found michael@0: switch (countX) { michael@0: case 1: dimenX.mRight = dimenX.mTop; // top-right same as top-left, and michael@0: case 2: dimenX.mBottom = dimenX.mTop; // bottom-right same as top-left, and michael@0: case 3: dimenX.mLeft = dimenX.mRight; // bottom-left same as top-right michael@0: } michael@0: michael@0: switch (countY) { michael@0: case 1: dimenY.mRight = dimenY.mTop; // top-right same as top-left, and michael@0: case 2: dimenY.mBottom = dimenY.mTop; // bottom-right same as top-left, and michael@0: case 3: dimenY.mLeft = dimenY.mRight; // bottom-left same as top-right michael@0: } michael@0: michael@0: NS_FOR_CSS_SIDES(side) { michael@0: nsCSSValue& x = dimenX.*nsCSSRect::sides[side]; michael@0: nsCSSValue& y = dimenY.*nsCSSRect::sides[side]; michael@0: michael@0: if (x == y) { michael@0: AppendValue(aPropIDs[side], x); michael@0: } else { michael@0: nsCSSValue pair; michael@0: pair.SetPairValue(x, y); michael@0: AppendValue(aPropIDs[side], pair); michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: // These must be in CSS order (top,right,bottom,left) for indexing to work michael@0: static const nsCSSProperty kBorderStyleIDs[] = { michael@0: eCSSProperty_border_top_style, michael@0: eCSSProperty_border_right_style_value, michael@0: eCSSProperty_border_bottom_style, michael@0: eCSSProperty_border_left_style_value michael@0: }; michael@0: static const nsCSSProperty kBorderWidthIDs[] = { michael@0: eCSSProperty_border_top_width, michael@0: eCSSProperty_border_right_width_value, michael@0: eCSSProperty_border_bottom_width, michael@0: eCSSProperty_border_left_width_value michael@0: }; michael@0: static const nsCSSProperty kBorderColorIDs[] = { michael@0: eCSSProperty_border_top_color, michael@0: eCSSProperty_border_right_color_value, michael@0: eCSSProperty_border_bottom_color, michael@0: eCSSProperty_border_left_color_value michael@0: }; michael@0: static const nsCSSProperty kBorderRadiusIDs[] = { michael@0: eCSSProperty_border_top_left_radius, michael@0: eCSSProperty_border_top_right_radius, michael@0: eCSSProperty_border_bottom_right_radius, michael@0: eCSSProperty_border_bottom_left_radius michael@0: }; michael@0: static const nsCSSProperty kOutlineRadiusIDs[] = { michael@0: eCSSProperty__moz_outline_radius_topLeft, michael@0: eCSSProperty__moz_outline_radius_topRight, michael@0: eCSSProperty__moz_outline_radius_bottomRight, michael@0: eCSSProperty__moz_outline_radius_bottomLeft michael@0: }; michael@0: michael@0: void michael@0: CSSParserImpl::SaveInputState(CSSParserInputState& aState) michael@0: { michael@0: aState.mToken = mToken; michael@0: aState.mHavePushBack = mHavePushBack; michael@0: mScanner->SavePosition(aState.mPosition); michael@0: } michael@0: michael@0: void michael@0: CSSParserImpl::RestoreSavedInputState(const CSSParserInputState& aState) michael@0: { michael@0: mToken = aState.mToken; michael@0: mHavePushBack = aState.mHavePushBack; michael@0: mScanner->RestoreSavedPosition(aState.mPosition); michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseProperty(nsCSSProperty aPropID) michael@0: { michael@0: // Can't use AutoRestore because it's a bitfield. michael@0: NS_ABORT_IF_FALSE(!mHashlessColorQuirk, michael@0: "hashless color quirk should not be set"); michael@0: NS_ABORT_IF_FALSE(!mUnitlessLengthQuirk, michael@0: "unitless length quirk should not be set"); michael@0: michael@0: if (mNavQuirkMode) { michael@0: mHashlessColorQuirk = michael@0: nsCSSProps::PropHasFlags(aPropID, CSS_PROPERTY_HASHLESS_COLOR_QUIRK); michael@0: mUnitlessLengthQuirk = michael@0: nsCSSProps::PropHasFlags(aPropID, CSS_PROPERTY_UNITLESS_LENGTH_QUIRK); michael@0: } michael@0: michael@0: // Save the current input state so that we can restore it later if we michael@0: // have to re-parse the property value as a variable-reference-containing michael@0: // token stream. michael@0: CSSParserInputState stateBeforeProperty; michael@0: SaveInputState(stateBeforeProperty); michael@0: mScanner->ClearSeenVariableReference(); michael@0: michael@0: NS_ASSERTION(aPropID < eCSSProperty_COUNT, "index out of range"); michael@0: bool allowVariables = true; michael@0: bool result; michael@0: switch (nsCSSProps::PropertyParseType(aPropID)) { michael@0: case CSS_PROPERTY_PARSE_INACCESSIBLE: { michael@0: // The user can't use these michael@0: REPORT_UNEXPECTED(PEInaccessibleProperty2); michael@0: allowVariables = false; michael@0: result = false; michael@0: break; michael@0: } michael@0: case CSS_PROPERTY_PARSE_FUNCTION: { michael@0: result = ParsePropertyByFunction(aPropID); michael@0: break; michael@0: } michael@0: case CSS_PROPERTY_PARSE_VALUE: { michael@0: result = false; michael@0: nsCSSValue value; michael@0: if (ParseSingleValueProperty(value, aPropID)) { michael@0: AppendValue(aPropID, value); michael@0: result = true; michael@0: } michael@0: // XXX Report errors? michael@0: break; michael@0: } michael@0: case CSS_PROPERTY_PARSE_VALUE_LIST: { michael@0: result = ParseValueList(aPropID); michael@0: break; michael@0: } michael@0: default: { michael@0: result = false; michael@0: allowVariables = false; michael@0: NS_ABORT_IF_FALSE(false, michael@0: "Property's flags field in nsCSSPropList.h is missing " michael@0: "one of the CSS_PROPERTY_PARSE_* constants"); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (result) { michael@0: // We need to call ExpectEndProperty() to decide whether to reparse michael@0: // with variables. This is needed because the property parsing may michael@0: // have stopped upon finding a variable (e.g., 'margin: 1px var(a)') michael@0: // in a way that future variable substitutions will be valid, or michael@0: // because it parsed everything that's possible but we still want to michael@0: // act as though the property contains variables even though we know michael@0: // the substitution will never work (e.g., for 'margin: 1px 2px 3px michael@0: // 4px 5px var(a)'). michael@0: // michael@0: // It would be nice to find a better solution here michael@0: // (and for the SkipUntilOneOf below), though, that doesn't depend michael@0: // on using what we don't accept for doing parsing correctly. michael@0: if (!ExpectEndProperty()) { michael@0: result = false; michael@0: } michael@0: } michael@0: michael@0: bool seenVariable = mScanner->SeenVariableReference() || michael@0: (stateBeforeProperty.mHavePushBack && michael@0: stateBeforeProperty.mToken.mType == eCSSToken_Function && michael@0: stateBeforeProperty.mToken.mIdent.LowerCaseEqualsLiteral("var")); michael@0: bool parseAsTokenStream; michael@0: michael@0: if (!result && allowVariables) { michael@0: parseAsTokenStream = true; michael@0: if (!seenVariable) { michael@0: // We might have stopped parsing the property before its end and before michael@0: // finding a variable reference. Keep checking until the end of the michael@0: // property. michael@0: CSSParserInputState stateAtError; michael@0: SaveInputState(stateAtError); michael@0: michael@0: const char16_t stopChars[] = { ';', '!', '}', ')', 0 }; michael@0: SkipUntilOneOf(stopChars); michael@0: UngetToken(); michael@0: parseAsTokenStream = mScanner->SeenVariableReference(); michael@0: michael@0: if (!parseAsTokenStream) { michael@0: // If we parsed to the end of the propery and didn't find any variable michael@0: // references, then the real position we want to report the error at michael@0: // is |stateAtError|. michael@0: RestoreSavedInputState(stateAtError); michael@0: } michael@0: } michael@0: } else { michael@0: parseAsTokenStream = false; michael@0: } michael@0: michael@0: if (parseAsTokenStream) { michael@0: // Go back to the start of the property value and parse it to make sure michael@0: // its variable references are syntactically valid and is otherwise michael@0: // balanced. michael@0: RestoreSavedInputState(stateBeforeProperty); michael@0: michael@0: if (!mInSupportsCondition) { michael@0: mScanner->StartRecording(); michael@0: } michael@0: michael@0: CSSVariableDeclarations::Type type; michael@0: bool dropBackslash; michael@0: nsString impliedCharacters; michael@0: nsCSSValue value; michael@0: if (ParseValueWithVariables(&type, &dropBackslash, impliedCharacters, michael@0: nullptr, nullptr)) { michael@0: MOZ_ASSERT(type == CSSVariableDeclarations::eTokenStream, michael@0: "a non-custom property reparsed since it contained variable " michael@0: "references should not have been 'initial' or 'inherit'"); michael@0: michael@0: nsString propertyValue; michael@0: michael@0: if (!mInSupportsCondition) { michael@0: // If we are in an @supports condition, we don't need to store the michael@0: // actual token stream on the nsCSSValue. michael@0: mScanner->StopRecording(propertyValue); michael@0: if (dropBackslash) { michael@0: MOZ_ASSERT(!propertyValue.IsEmpty() && michael@0: propertyValue[propertyValue.Length() - 1] == '\\'); michael@0: propertyValue.Truncate(propertyValue.Length() - 1); michael@0: } michael@0: propertyValue.Append(impliedCharacters); michael@0: } michael@0: michael@0: if (mHavePushBack) { michael@0: // If we came to the end of a property value that had a variable michael@0: // reference and a token was pushed back, then it would have been michael@0: // ended by '!', ')', ';', ']' or '}'. We should remove it from the michael@0: // recorded property value. michael@0: MOZ_ASSERT(mToken.IsSymbol('!') || michael@0: mToken.IsSymbol(')') || michael@0: mToken.IsSymbol(';') || michael@0: mToken.IsSymbol(']') || michael@0: mToken.IsSymbol('}')); michael@0: if (!mInSupportsCondition) { michael@0: MOZ_ASSERT(!propertyValue.IsEmpty()); michael@0: MOZ_ASSERT(propertyValue[propertyValue.Length() - 1] == michael@0: mToken.mSymbol); michael@0: propertyValue.Truncate(propertyValue.Length() - 1); michael@0: } michael@0: } michael@0: michael@0: if (!mInSupportsCondition) { michael@0: if (nsCSSProps::IsShorthand(aPropID)) { michael@0: // If this is a shorthand property, we store the token stream on each michael@0: // of its corresponding longhand properties. michael@0: CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aPropID) { michael@0: nsCSSValueTokenStream* tokenStream = new nsCSSValueTokenStream; michael@0: tokenStream->mPropertyID = *p; michael@0: tokenStream->mShorthandPropertyID = aPropID; michael@0: tokenStream->mTokenStream = propertyValue; michael@0: tokenStream->mBaseURI = mBaseURI; michael@0: tokenStream->mSheetURI = mSheetURI; michael@0: tokenStream->mSheetPrincipal = mSheetPrincipal; michael@0: tokenStream->mSheet = mSheet; michael@0: tokenStream->mLineNumber = stateBeforeProperty.mPosition.LineNumber(); michael@0: tokenStream->mLineOffset = stateBeforeProperty.mPosition.LineOffset(); michael@0: value.SetTokenStreamValue(tokenStream); michael@0: AppendValue(*p, value); michael@0: } michael@0: } else { michael@0: nsCSSValueTokenStream* tokenStream = new nsCSSValueTokenStream; michael@0: tokenStream->mPropertyID = aPropID; michael@0: tokenStream->mTokenStream = propertyValue; michael@0: tokenStream->mBaseURI = mBaseURI; michael@0: tokenStream->mSheetURI = mSheetURI; michael@0: tokenStream->mSheetPrincipal = mSheetPrincipal; michael@0: tokenStream->mSheet = mSheet; michael@0: tokenStream->mLineNumber = stateBeforeProperty.mPosition.LineNumber(); michael@0: tokenStream->mLineOffset = stateBeforeProperty.mPosition.LineOffset(); michael@0: value.SetTokenStreamValue(tokenStream); michael@0: AppendValue(aPropID, value); michael@0: } michael@0: } michael@0: result = true; michael@0: } else { michael@0: if (!mInSupportsCondition) { michael@0: mScanner->StopRecording(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (mNavQuirkMode) { michael@0: mHashlessColorQuirk = false; michael@0: mUnitlessLengthQuirk = false; michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParsePropertyByFunction(nsCSSProperty aPropID) michael@0: { michael@0: switch (aPropID) { // handle shorthand or multiple properties michael@0: case eCSSProperty_background: michael@0: return ParseBackground(); michael@0: case eCSSProperty_background_repeat: michael@0: return ParseBackgroundRepeat(); michael@0: case eCSSProperty_background_position: michael@0: return ParseBackgroundPosition(); michael@0: case eCSSProperty_background_size: michael@0: return ParseBackgroundSize(); michael@0: case eCSSProperty_border: michael@0: return ParseBorderSide(kBorderTopIDs, true); michael@0: case eCSSProperty_border_color: michael@0: return ParseBorderColor(); michael@0: case eCSSProperty_border_spacing: michael@0: return ParseBorderSpacing(); michael@0: case eCSSProperty_border_style: michael@0: return ParseBorderStyle(); michael@0: case eCSSProperty_border_bottom: michael@0: return ParseBorderSide(kBorderBottomIDs, false); michael@0: case eCSSProperty_border_end: michael@0: return ParseDirectionalBorderSide(kBorderEndIDs, michael@0: NS_BOXPROP_SOURCE_LOGICAL); michael@0: case eCSSProperty_border_left: michael@0: return ParseDirectionalBorderSide(kBorderLeftIDs, michael@0: NS_BOXPROP_SOURCE_PHYSICAL); michael@0: case eCSSProperty_border_right: michael@0: return ParseDirectionalBorderSide(kBorderRightIDs, michael@0: NS_BOXPROP_SOURCE_PHYSICAL); michael@0: case eCSSProperty_border_start: michael@0: return ParseDirectionalBorderSide(kBorderStartIDs, michael@0: NS_BOXPROP_SOURCE_LOGICAL); michael@0: case eCSSProperty_border_top: michael@0: return ParseBorderSide(kBorderTopIDs, false); michael@0: case eCSSProperty_border_bottom_colors: michael@0: case eCSSProperty_border_left_colors: michael@0: case eCSSProperty_border_right_colors: michael@0: case eCSSProperty_border_top_colors: michael@0: return ParseBorderColors(aPropID); michael@0: case eCSSProperty_border_image_slice: michael@0: return ParseBorderImageSlice(true, nullptr); michael@0: case eCSSProperty_border_image_width: michael@0: return ParseBorderImageWidth(true); michael@0: case eCSSProperty_border_image_outset: michael@0: return ParseBorderImageOutset(true); michael@0: case eCSSProperty_border_image_repeat: michael@0: return ParseBorderImageRepeat(true); michael@0: case eCSSProperty_border_image: michael@0: return ParseBorderImage(); michael@0: case eCSSProperty_border_width: michael@0: return ParseBorderWidth(); michael@0: case eCSSProperty_border_end_color: michael@0: return ParseDirectionalBoxProperty(eCSSProperty_border_end_color, michael@0: NS_BOXPROP_SOURCE_LOGICAL); michael@0: case eCSSProperty_border_left_color: michael@0: return ParseDirectionalBoxProperty(eCSSProperty_border_left_color, michael@0: NS_BOXPROP_SOURCE_PHYSICAL); michael@0: case eCSSProperty_border_right_color: michael@0: return ParseDirectionalBoxProperty(eCSSProperty_border_right_color, michael@0: NS_BOXPROP_SOURCE_PHYSICAL); michael@0: case eCSSProperty_border_start_color: michael@0: return ParseDirectionalBoxProperty(eCSSProperty_border_start_color, michael@0: NS_BOXPROP_SOURCE_LOGICAL); michael@0: case eCSSProperty_border_end_width: michael@0: return ParseDirectionalBoxProperty(eCSSProperty_border_end_width, michael@0: NS_BOXPROP_SOURCE_LOGICAL); michael@0: case eCSSProperty_border_left_width: michael@0: return ParseDirectionalBoxProperty(eCSSProperty_border_left_width, michael@0: NS_BOXPROP_SOURCE_PHYSICAL); michael@0: case eCSSProperty_border_right_width: michael@0: return ParseDirectionalBoxProperty(eCSSProperty_border_right_width, michael@0: NS_BOXPROP_SOURCE_PHYSICAL); michael@0: case eCSSProperty_border_start_width: michael@0: return ParseDirectionalBoxProperty(eCSSProperty_border_start_width, michael@0: NS_BOXPROP_SOURCE_LOGICAL); michael@0: case eCSSProperty_border_end_style: michael@0: return ParseDirectionalBoxProperty(eCSSProperty_border_end_style, michael@0: NS_BOXPROP_SOURCE_LOGICAL); michael@0: case eCSSProperty_border_left_style: michael@0: return ParseDirectionalBoxProperty(eCSSProperty_border_left_style, michael@0: NS_BOXPROP_SOURCE_PHYSICAL); michael@0: case eCSSProperty_border_right_style: michael@0: return ParseDirectionalBoxProperty(eCSSProperty_border_right_style, michael@0: NS_BOXPROP_SOURCE_PHYSICAL); michael@0: case eCSSProperty_border_start_style: michael@0: return ParseDirectionalBoxProperty(eCSSProperty_border_start_style, michael@0: NS_BOXPROP_SOURCE_LOGICAL); michael@0: case eCSSProperty_border_radius: michael@0: return ParseBoxCornerRadii(kBorderRadiusIDs); michael@0: case eCSSProperty__moz_outline_radius: michael@0: return ParseBoxCornerRadii(kOutlineRadiusIDs); michael@0: michael@0: case eCSSProperty_border_top_left_radius: michael@0: case eCSSProperty_border_top_right_radius: michael@0: case eCSSProperty_border_bottom_right_radius: michael@0: case eCSSProperty_border_bottom_left_radius: michael@0: case eCSSProperty__moz_outline_radius_topLeft: michael@0: case eCSSProperty__moz_outline_radius_topRight: michael@0: case eCSSProperty__moz_outline_radius_bottomRight: michael@0: case eCSSProperty__moz_outline_radius_bottomLeft: michael@0: return ParseBoxCornerRadius(aPropID); michael@0: michael@0: case eCSSProperty_box_shadow: michael@0: case eCSSProperty_text_shadow: michael@0: return ParseShadowList(aPropID); michael@0: michael@0: case eCSSProperty_clip: michael@0: return ParseRect(eCSSProperty_clip); michael@0: case eCSSProperty__moz_columns: michael@0: return ParseColumns(); michael@0: case eCSSProperty__moz_column_rule: michael@0: return ParseBorderSide(kColumnRuleIDs, false); michael@0: case eCSSProperty_content: michael@0: return ParseContent(); michael@0: case eCSSProperty_counter_increment: michael@0: case eCSSProperty_counter_reset: michael@0: return ParseCounterData(aPropID); michael@0: case eCSSProperty_cursor: michael@0: return ParseCursor(); michael@0: case eCSSProperty_filter: michael@0: return ParseFilter(); michael@0: case eCSSProperty_flex: michael@0: return ParseFlex(); michael@0: case eCSSProperty_flex_flow: michael@0: return ParseFlexFlow(); michael@0: case eCSSProperty_font: michael@0: return ParseFont(); michael@0: case eCSSProperty_grid_auto_flow: michael@0: return ParseGridAutoFlow(); michael@0: case eCSSProperty_grid_auto_columns: michael@0: case eCSSProperty_grid_auto_rows: michael@0: return ParseGridAutoColumnsRows(aPropID); michael@0: case eCSSProperty_grid_template_areas: michael@0: return ParseGridTemplateAreas(); michael@0: case eCSSProperty_grid_template_columns: michael@0: case eCSSProperty_grid_template_rows: michael@0: return ParseGridTemplateColumnsRows(aPropID); michael@0: case eCSSProperty_grid_template: michael@0: return ParseGridTemplate(); michael@0: case eCSSProperty_grid: michael@0: return ParseGrid(); michael@0: case eCSSProperty_grid_auto_position: michael@0: return ParseGridAutoPosition(); michael@0: case eCSSProperty_grid_column_start: michael@0: case eCSSProperty_grid_column_end: michael@0: case eCSSProperty_grid_row_start: michael@0: case eCSSProperty_grid_row_end: michael@0: return ParseGridColumnRowStartEnd(aPropID); michael@0: case eCSSProperty_grid_column: michael@0: return ParseGridColumnRow(eCSSProperty_grid_column_start, michael@0: eCSSProperty_grid_column_end); michael@0: case eCSSProperty_grid_row: michael@0: return ParseGridColumnRow(eCSSProperty_grid_row_start, michael@0: eCSSProperty_grid_row_end); michael@0: case eCSSProperty_grid_area: michael@0: return ParseGridArea(); michael@0: case eCSSProperty_image_region: michael@0: return ParseRect(eCSSProperty_image_region); michael@0: case eCSSProperty_list_style: michael@0: return ParseListStyle(); michael@0: case eCSSProperty_margin: michael@0: return ParseMargin(); michael@0: case eCSSProperty_margin_end: michael@0: return ParseDirectionalBoxProperty(eCSSProperty_margin_end, michael@0: NS_BOXPROP_SOURCE_LOGICAL); michael@0: case eCSSProperty_margin_left: michael@0: return ParseDirectionalBoxProperty(eCSSProperty_margin_left, michael@0: NS_BOXPROP_SOURCE_PHYSICAL); michael@0: case eCSSProperty_margin_right: michael@0: return ParseDirectionalBoxProperty(eCSSProperty_margin_right, michael@0: NS_BOXPROP_SOURCE_PHYSICAL); michael@0: case eCSSProperty_margin_start: michael@0: return ParseDirectionalBoxProperty(eCSSProperty_margin_start, michael@0: NS_BOXPROP_SOURCE_LOGICAL); michael@0: case eCSSProperty_outline: michael@0: return ParseOutline(); michael@0: case eCSSProperty_overflow: michael@0: return ParseOverflow(); michael@0: case eCSSProperty_padding: michael@0: return ParsePadding(); michael@0: case eCSSProperty_padding_end: michael@0: return ParseDirectionalBoxProperty(eCSSProperty_padding_end, michael@0: NS_BOXPROP_SOURCE_LOGICAL); michael@0: case eCSSProperty_padding_left: michael@0: return ParseDirectionalBoxProperty(eCSSProperty_padding_left, michael@0: NS_BOXPROP_SOURCE_PHYSICAL); michael@0: case eCSSProperty_padding_right: michael@0: return ParseDirectionalBoxProperty(eCSSProperty_padding_right, michael@0: NS_BOXPROP_SOURCE_PHYSICAL); michael@0: case eCSSProperty_padding_start: michael@0: return ParseDirectionalBoxProperty(eCSSProperty_padding_start, michael@0: NS_BOXPROP_SOURCE_LOGICAL); michael@0: case eCSSProperty_quotes: michael@0: return ParseQuotes(); michael@0: case eCSSProperty_size: michael@0: return ParseSize(); michael@0: case eCSSProperty_text_decoration: michael@0: return ParseTextDecoration(); michael@0: case eCSSProperty_will_change: michael@0: return ParseWillChange(); michael@0: case eCSSProperty_transform: michael@0: return ParseTransform(false); michael@0: case eCSSProperty__moz_transform: michael@0: return ParseTransform(true); michael@0: case eCSSProperty_transform_origin: michael@0: return ParseTransformOrigin(false); michael@0: case eCSSProperty_perspective_origin: michael@0: return ParseTransformOrigin(true); michael@0: case eCSSProperty_transition: michael@0: return ParseTransition(); michael@0: case eCSSProperty_animation: michael@0: return ParseAnimation(); michael@0: case eCSSProperty_transition_property: michael@0: return ParseTransitionProperty(); michael@0: case eCSSProperty_fill: michael@0: case eCSSProperty_stroke: michael@0: return ParsePaint(aPropID); michael@0: case eCSSProperty_stroke_dasharray: michael@0: return ParseDasharray(); michael@0: case eCSSProperty_marker: michael@0: return ParseMarker(); michael@0: case eCSSProperty_paint_order: michael@0: return ParsePaintOrder(); michael@0: case eCSSProperty_all: michael@0: return ParseAll(); michael@0: default: michael@0: NS_ABORT_IF_FALSE(false, "should not be called"); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // Bits used in determining which background position info we have michael@0: #define BG_CENTER NS_STYLE_BG_POSITION_CENTER michael@0: #define BG_TOP NS_STYLE_BG_POSITION_TOP michael@0: #define BG_BOTTOM NS_STYLE_BG_POSITION_BOTTOM michael@0: #define BG_LEFT NS_STYLE_BG_POSITION_LEFT michael@0: #define BG_RIGHT NS_STYLE_BG_POSITION_RIGHT michael@0: #define BG_CTB (BG_CENTER | BG_TOP | BG_BOTTOM) michael@0: #define BG_TB (BG_TOP | BG_BOTTOM) michael@0: #define BG_CLR (BG_CENTER | BG_LEFT | BG_RIGHT) michael@0: #define BG_LR (BG_LEFT | BG_RIGHT) michael@0: michael@0: bool michael@0: CSSParserImpl::ParseSingleValueProperty(nsCSSValue& aValue, michael@0: nsCSSProperty aPropID) michael@0: { michael@0: if (aPropID == eCSSPropertyExtra_x_none_value) { michael@0: return ParseVariant(aValue, VARIANT_NONE | VARIANT_INHERIT, nullptr); michael@0: } michael@0: michael@0: if (aPropID == eCSSPropertyExtra_x_auto_value) { michael@0: return ParseVariant(aValue, VARIANT_AUTO | VARIANT_INHERIT, nullptr); michael@0: } michael@0: michael@0: if (aPropID < 0 || aPropID >= eCSSProperty_COUNT_no_shorthands) { michael@0: NS_ABORT_IF_FALSE(false, "not a single value property"); michael@0: return false; michael@0: } michael@0: michael@0: if (nsCSSProps::PropHasFlags(aPropID, CSS_PROPERTY_VALUE_PARSER_FUNCTION)) { michael@0: switch (aPropID) { michael@0: case eCSSProperty_font_family: michael@0: return ParseFamily(aValue); michael@0: case eCSSProperty_font_synthesis: michael@0: return ParseFontSynthesis(aValue); michael@0: case eCSSProperty_font_variant_alternates: michael@0: return ParseFontVariantAlternates(aValue); michael@0: case eCSSProperty_font_variant_east_asian: michael@0: return ParseFontVariantEastAsian(aValue); michael@0: case eCSSProperty_font_variant_ligatures: michael@0: return ParseFontVariantLigatures(aValue); michael@0: case eCSSProperty_font_variant_numeric: michael@0: return ParseFontVariantNumeric(aValue); michael@0: case eCSSProperty_font_feature_settings: michael@0: return ParseFontFeatureSettings(aValue); michael@0: case eCSSProperty_font_weight: michael@0: return ParseFontWeight(aValue); michael@0: case eCSSProperty_image_orientation: michael@0: return ParseImageOrientation(aValue); michael@0: case eCSSProperty_marks: michael@0: return ParseMarks(aValue); michael@0: case eCSSProperty_text_align: michael@0: return ParseTextAlign(aValue); michael@0: case eCSSProperty_text_align_last: michael@0: return ParseTextAlignLast(aValue); michael@0: case eCSSProperty_text_decoration_line: michael@0: return ParseTextDecorationLine(aValue); michael@0: case eCSSProperty_text_combine_upright: michael@0: return ParseTextCombineUpright(aValue); michael@0: case eCSSProperty_text_overflow: michael@0: return ParseTextOverflow(aValue); michael@0: case eCSSProperty_touch_action: michael@0: return ParseTouchAction(aValue); michael@0: default: michael@0: NS_ABORT_IF_FALSE(false, "should not reach here"); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: uint32_t variant = nsCSSProps::ParserVariant(aPropID); michael@0: if (variant == 0) { michael@0: NS_ABORT_IF_FALSE(false, "not a single value property"); michael@0: return false; michael@0: } michael@0: michael@0: // We only allow 'script-level' when unsafe rules are enabled, because michael@0: // otherwise it could interfere with rulenode optimizations if used in michael@0: // a non-MathML-enabled document. We also only allow math-display when michael@0: // unsafe rules are enabled. michael@0: if (!mUnsafeRulesEnabled && michael@0: (aPropID == eCSSProperty_script_level || michael@0: aPropID == eCSSProperty_math_display)) michael@0: return false; michael@0: michael@0: const KTableValue *kwtable = nsCSSProps::kKeywordTableTable[aPropID]; michael@0: switch (nsCSSProps::ValueRestrictions(aPropID)) { michael@0: default: michael@0: NS_ABORT_IF_FALSE(false, "should not be reached"); michael@0: case 0: michael@0: return ParseVariant(aValue, variant, kwtable); michael@0: case CSS_PROPERTY_VALUE_NONNEGATIVE: michael@0: return ParseNonNegativeVariant(aValue, variant, kwtable); michael@0: case CSS_PROPERTY_VALUE_AT_LEAST_ONE: michael@0: return ParseOneOrLargerVariant(aValue, variant, kwtable); michael@0: } michael@0: } michael@0: michael@0: // nsFont::EnumerateFamilies callback for ParseFontDescriptorValue michael@0: struct MOZ_STACK_CLASS ExtractFirstFamilyData { michael@0: nsAutoString mFamilyName; michael@0: bool mGood; michael@0: ExtractFirstFamilyData() : mFamilyName(), mGood(false) {} michael@0: }; michael@0: michael@0: static bool michael@0: ExtractFirstFamily(const nsString& aFamily, michael@0: bool aGeneric, michael@0: void* aData) michael@0: { michael@0: ExtractFirstFamilyData* realData = (ExtractFirstFamilyData*) aData; michael@0: if (aGeneric || realData->mFamilyName.Length() > 0) { michael@0: realData->mGood = false; michael@0: return false; michael@0: } michael@0: realData->mFamilyName.Assign(aFamily); michael@0: realData->mGood = true; michael@0: return true; michael@0: } michael@0: michael@0: // font-descriptor: descriptor ':' value ';' michael@0: // caller has advanced mToken to point at the descriptor michael@0: bool michael@0: CSSParserImpl::ParseFontDescriptorValue(nsCSSFontDesc aDescID, michael@0: nsCSSValue& aValue) michael@0: { michael@0: switch (aDescID) { michael@0: // These four are similar to the properties of the same name, michael@0: // possibly with more restrictions on the values they can take. michael@0: case eCSSFontDesc_Family: { michael@0: if (!ParseFamily(aValue) || michael@0: aValue.GetUnit() != eCSSUnit_Families) michael@0: return false; michael@0: michael@0: // the style parameters to the nsFont constructor are ignored, michael@0: // because it's only being used to call EnumerateFamilies michael@0: nsAutoString valueStr; michael@0: aValue.GetStringValue(valueStr); michael@0: nsFont font(valueStr, 0, 0, 0, 0, 0, 0); michael@0: ExtractFirstFamilyData dat; michael@0: michael@0: font.EnumerateFamilies(ExtractFirstFamily, (void*) &dat); michael@0: if (!dat.mGood) michael@0: return false; michael@0: michael@0: aValue.SetStringValue(dat.mFamilyName, eCSSUnit_String); michael@0: return true; michael@0: } michael@0: michael@0: case eCSSFontDesc_Style: michael@0: // property is VARIANT_HMK|VARIANT_SYSFONT michael@0: return ParseVariant(aValue, VARIANT_KEYWORD | VARIANT_NORMAL, michael@0: nsCSSProps::kFontStyleKTable); michael@0: michael@0: case eCSSFontDesc_Weight: michael@0: return (ParseFontWeight(aValue) && michael@0: aValue.GetUnit() != eCSSUnit_Inherit && michael@0: aValue.GetUnit() != eCSSUnit_Initial && michael@0: aValue.GetUnit() != eCSSUnit_Unset && michael@0: (aValue.GetUnit() != eCSSUnit_Enumerated || michael@0: (aValue.GetIntValue() != NS_STYLE_FONT_WEIGHT_BOLDER && michael@0: aValue.GetIntValue() != NS_STYLE_FONT_WEIGHT_LIGHTER))); michael@0: michael@0: case eCSSFontDesc_Stretch: michael@0: // property is VARIANT_HK|VARIANT_SYSFONT michael@0: return ParseVariant(aValue, VARIANT_KEYWORD, michael@0: nsCSSProps::kFontStretchKTable); michael@0: michael@0: // These two are unique to @font-face and have their own special grammar. michael@0: case eCSSFontDesc_Src: michael@0: return ParseFontSrc(aValue); michael@0: michael@0: case eCSSFontDesc_UnicodeRange: michael@0: return ParseFontRanges(aValue); michael@0: michael@0: case eCSSFontDesc_FontFeatureSettings: michael@0: return ParseFontFeatureSettings(aValue); michael@0: michael@0: case eCSSFontDesc_FontLanguageOverride: michael@0: return ParseVariant(aValue, VARIANT_NORMAL | VARIANT_STRING, nullptr); michael@0: michael@0: case eCSSFontDesc_UNKNOWN: michael@0: case eCSSFontDesc_COUNT: michael@0: NS_NOTREACHED("bad nsCSSFontDesc code"); michael@0: } michael@0: // explicitly do NOT have a default case to let the compiler michael@0: // help find missing descriptors michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: CSSParserImpl::InitBoxPropsAsPhysical(const nsCSSProperty *aSourceProperties) michael@0: { michael@0: nsCSSValue physical(NS_BOXPROP_SOURCE_PHYSICAL, eCSSUnit_Enumerated); michael@0: for (const nsCSSProperty *prop = aSourceProperties; michael@0: *prop != eCSSProperty_UNKNOWN; ++prop) { michael@0: AppendValue(*prop, physical); michael@0: } michael@0: } michael@0: michael@0: static nsCSSValue michael@0: BoxPositionMaskToCSSValue(int32_t aMask, bool isX) michael@0: { michael@0: int32_t val = NS_STYLE_BG_POSITION_CENTER; michael@0: if (isX) { michael@0: if (aMask & BG_LEFT) { michael@0: val = NS_STYLE_BG_POSITION_LEFT; michael@0: } michael@0: else if (aMask & BG_RIGHT) { michael@0: val = NS_STYLE_BG_POSITION_RIGHT; michael@0: } michael@0: } michael@0: else { michael@0: if (aMask & BG_TOP) { michael@0: val = NS_STYLE_BG_POSITION_TOP; michael@0: } michael@0: else if (aMask & BG_BOTTOM) { michael@0: val = NS_STYLE_BG_POSITION_BOTTOM; michael@0: } michael@0: } michael@0: michael@0: return nsCSSValue(val, eCSSUnit_Enumerated); michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseBackground() michael@0: { michael@0: nsAutoParseCompoundProperty compound(this); michael@0: michael@0: // background-color can only be set once, so it's not a list. michael@0: nsCSSValue color; michael@0: michael@0: // Check first for inherit/initial/unset. michael@0: if (ParseVariant(color, VARIANT_INHERIT, nullptr)) { michael@0: // must be alone michael@0: for (const nsCSSProperty* subprops = michael@0: nsCSSProps::SubpropertyEntryFor(eCSSProperty_background); michael@0: *subprops != eCSSProperty_UNKNOWN; ++subprops) { michael@0: AppendValue(*subprops, color); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: nsCSSValue image, repeat, attachment, clip, origin, position, size; michael@0: BackgroundParseState state(color, image.SetListValue(), michael@0: repeat.SetPairListValue(), michael@0: attachment.SetListValue(), clip.SetListValue(), michael@0: origin.SetListValue(), position.SetListValue(), michael@0: size.SetPairListValue()); michael@0: michael@0: for (;;) { michael@0: if (!ParseBackgroundItem(state)) { michael@0: return false; michael@0: } michael@0: // If we saw a color, this must be the last item. michael@0: if (color.GetUnit() != eCSSUnit_Null) { michael@0: break; michael@0: } michael@0: // If there's a comma, expect another item. michael@0: if (!ExpectSymbol(',', true)) { michael@0: break; michael@0: } michael@0: // Chain another entry on all the lists. michael@0: state.mImage->mNext = new nsCSSValueList; michael@0: state.mImage = state.mImage->mNext; michael@0: state.mRepeat->mNext = new nsCSSValuePairList; michael@0: state.mRepeat = state.mRepeat->mNext; michael@0: state.mAttachment->mNext = new nsCSSValueList; michael@0: state.mAttachment = state.mAttachment->mNext; michael@0: state.mClip->mNext = new nsCSSValueList; michael@0: state.mClip = state.mClip->mNext; michael@0: state.mOrigin->mNext = new nsCSSValueList; michael@0: state.mOrigin = state.mOrigin->mNext; michael@0: state.mPosition->mNext = new nsCSSValueList; michael@0: state.mPosition = state.mPosition->mNext; michael@0: state.mSize->mNext = new nsCSSValuePairList; michael@0: state.mSize = state.mSize->mNext; michael@0: } michael@0: michael@0: // If we get to this point without seeing a color, provide a default. michael@0: if (color.GetUnit() == eCSSUnit_Null) { michael@0: color.SetIntegerColorValue(NS_RGBA(0,0,0,0), eCSSUnit_RGBAColor); michael@0: } michael@0: michael@0: AppendValue(eCSSProperty_background_image, image); michael@0: AppendValue(eCSSProperty_background_repeat, repeat); michael@0: AppendValue(eCSSProperty_background_attachment, attachment); michael@0: AppendValue(eCSSProperty_background_clip, clip); michael@0: AppendValue(eCSSProperty_background_origin, origin); michael@0: AppendValue(eCSSProperty_background_position, position); michael@0: AppendValue(eCSSProperty_background_size, size); michael@0: AppendValue(eCSSProperty_background_color, color); michael@0: return true; michael@0: } michael@0: michael@0: // Parse one item of the background shorthand property. michael@0: bool michael@0: CSSParserImpl::ParseBackgroundItem(CSSParserImpl::BackgroundParseState& aState) michael@0: michael@0: { michael@0: // Fill in the values that the shorthand will set if we don't find michael@0: // other values. michael@0: aState.mImage->mValue.SetNoneValue(); michael@0: aState.mRepeat->mXValue.SetIntValue(NS_STYLE_BG_REPEAT_REPEAT, michael@0: eCSSUnit_Enumerated); michael@0: aState.mRepeat->mYValue.Reset(); michael@0: aState.mAttachment->mValue.SetIntValue(NS_STYLE_BG_ATTACHMENT_SCROLL, michael@0: eCSSUnit_Enumerated); michael@0: aState.mClip->mValue.SetIntValue(NS_STYLE_BG_CLIP_BORDER, michael@0: eCSSUnit_Enumerated); michael@0: aState.mOrigin->mValue.SetIntValue(NS_STYLE_BG_ORIGIN_PADDING, michael@0: eCSSUnit_Enumerated); michael@0: nsRefPtr positionArr = nsCSSValue::Array::Create(4); michael@0: aState.mPosition->mValue.SetArrayValue(positionArr, eCSSUnit_Array); michael@0: positionArr->Item(1).SetPercentValue(0.0f); michael@0: positionArr->Item(3).SetPercentValue(0.0f); michael@0: aState.mSize->mXValue.SetAutoValue(); michael@0: aState.mSize->mYValue.SetAutoValue(); michael@0: michael@0: bool haveColor = false, michael@0: haveImage = false, michael@0: haveRepeat = false, michael@0: haveAttach = false, michael@0: havePositionAndSize = false, michael@0: haveOrigin = false, michael@0: haveSomething = false; michael@0: michael@0: while (GetToken(true)) { michael@0: nsCSSTokenType tt = mToken.mType; michael@0: UngetToken(); // ...but we'll still cheat and use mToken michael@0: if (tt == eCSSToken_Symbol) { michael@0: // ExpectEndProperty only looks for symbols, and nothing else will michael@0: // show up as one. michael@0: break; michael@0: } michael@0: michael@0: if (tt == eCSSToken_Ident) { michael@0: nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent); michael@0: int32_t dummy; michael@0: if (keyword == eCSSKeyword_inherit || michael@0: keyword == eCSSKeyword_initial || michael@0: keyword == eCSSKeyword_unset) { michael@0: return false; michael@0: } else if (keyword == eCSSKeyword_none) { michael@0: if (haveImage) michael@0: return false; michael@0: haveImage = true; michael@0: if (!ParseSingleValueProperty(aState.mImage->mValue, michael@0: eCSSProperty_background_image)) { michael@0: NS_NOTREACHED("should be able to parse"); michael@0: return false; michael@0: } michael@0: } else if (nsCSSProps::FindKeyword(keyword, michael@0: nsCSSProps::kBackgroundAttachmentKTable, dummy)) { michael@0: if (haveAttach) michael@0: return false; michael@0: haveAttach = true; michael@0: if (!ParseSingleValueProperty(aState.mAttachment->mValue, michael@0: eCSSProperty_background_attachment)) { michael@0: NS_NOTREACHED("should be able to parse"); michael@0: return false; michael@0: } michael@0: } else if (nsCSSProps::FindKeyword(keyword, michael@0: nsCSSProps::kBackgroundRepeatKTable, dummy)) { michael@0: if (haveRepeat) michael@0: return false; michael@0: haveRepeat = true; michael@0: nsCSSValuePair scratch; michael@0: if (!ParseBackgroundRepeatValues(scratch)) { michael@0: NS_NOTREACHED("should be able to parse"); michael@0: return false; michael@0: } michael@0: aState.mRepeat->mXValue = scratch.mXValue; michael@0: aState.mRepeat->mYValue = scratch.mYValue; michael@0: } else if (nsCSSProps::FindKeyword(keyword, michael@0: nsCSSProps::kBackgroundPositionKTable, dummy)) { michael@0: if (havePositionAndSize) michael@0: return false; michael@0: havePositionAndSize = true; michael@0: if (!ParseBackgroundPositionValues(aState.mPosition->mValue, false)) { michael@0: return false; michael@0: } michael@0: if (ExpectSymbol('/', true)) { michael@0: nsCSSValuePair scratch; michael@0: if (!ParseBackgroundSizeValues(scratch)) { michael@0: return false; michael@0: } michael@0: aState.mSize->mXValue = scratch.mXValue; michael@0: aState.mSize->mYValue = scratch.mYValue; michael@0: } michael@0: } else if (nsCSSProps::FindKeyword(keyword, michael@0: nsCSSProps::kBackgroundOriginKTable, dummy)) { michael@0: if (haveOrigin) michael@0: return false; michael@0: haveOrigin = true; michael@0: if (!ParseSingleValueProperty(aState.mOrigin->mValue, michael@0: eCSSProperty_background_origin)) { michael@0: NS_NOTREACHED("should be able to parse"); michael@0: return false; michael@0: } michael@0: michael@0: // The spec allows a second box value (for background-clip), michael@0: // immediately following the first one (for background-origin). michael@0: michael@0: // 'background-clip' and 'background-origin' use the same keyword table michael@0: MOZ_ASSERT(nsCSSProps::kKeywordTableTable[ michael@0: eCSSProperty_background_origin] == michael@0: nsCSSProps::kBackgroundOriginKTable); michael@0: MOZ_ASSERT(nsCSSProps::kKeywordTableTable[ michael@0: eCSSProperty_background_clip] == michael@0: nsCSSProps::kBackgroundOriginKTable); michael@0: static_assert(NS_STYLE_BG_CLIP_BORDER == michael@0: NS_STYLE_BG_ORIGIN_BORDER && michael@0: NS_STYLE_BG_CLIP_PADDING == michael@0: NS_STYLE_BG_ORIGIN_PADDING && michael@0: NS_STYLE_BG_CLIP_CONTENT == michael@0: NS_STYLE_BG_ORIGIN_CONTENT, michael@0: "bg-clip and bg-origin style constants must agree"); michael@0: michael@0: if (!ParseSingleValueProperty(aState.mClip->mValue, michael@0: eCSSProperty_background_clip)) { michael@0: // When exactly one value is set, it is used for both michael@0: // 'background-origin' and 'background-clip'. michael@0: // See assertions above showing these values are compatible. michael@0: aState.mClip->mValue = aState.mOrigin->mValue; michael@0: } michael@0: } else { michael@0: if (haveColor) michael@0: return false; michael@0: haveColor = true; michael@0: if (!ParseSingleValueProperty(aState.mColor, michael@0: eCSSProperty_background_color)) { michael@0: return false; michael@0: } michael@0: } michael@0: } else if (tt == eCSSToken_URL || michael@0: (tt == eCSSToken_Function && michael@0: (mToken.mIdent.LowerCaseEqualsLiteral("linear-gradient") || michael@0: mToken.mIdent.LowerCaseEqualsLiteral("radial-gradient") || michael@0: mToken.mIdent.LowerCaseEqualsLiteral("repeating-linear-gradient") || michael@0: mToken.mIdent.LowerCaseEqualsLiteral("repeating-radial-gradient") || michael@0: mToken.mIdent.LowerCaseEqualsLiteral("-moz-linear-gradient") || michael@0: mToken.mIdent.LowerCaseEqualsLiteral("-moz-radial-gradient") || michael@0: mToken.mIdent.LowerCaseEqualsLiteral("-moz-repeating-linear-gradient") || michael@0: mToken.mIdent.LowerCaseEqualsLiteral("-moz-repeating-radial-gradient") || michael@0: mToken.mIdent.LowerCaseEqualsLiteral("-moz-image-rect") || michael@0: mToken.mIdent.LowerCaseEqualsLiteral("-moz-element")))) { michael@0: if (haveImage) michael@0: return false; michael@0: haveImage = true; michael@0: if (!ParseSingleValueProperty(aState.mImage->mValue, michael@0: eCSSProperty_background_image)) { michael@0: return false; michael@0: } michael@0: } else if (tt == eCSSToken_Dimension || michael@0: tt == eCSSToken_Number || michael@0: tt == eCSSToken_Percentage || michael@0: (tt == eCSSToken_Function && michael@0: (mToken.mIdent.LowerCaseEqualsLiteral("calc") || michael@0: mToken.mIdent.LowerCaseEqualsLiteral("-moz-calc")))) { michael@0: if (havePositionAndSize) michael@0: return false; michael@0: havePositionAndSize = true; michael@0: if (!ParseBackgroundPositionValues(aState.mPosition->mValue, false)) { michael@0: return false; michael@0: } michael@0: if (ExpectSymbol('/', true)) { michael@0: nsCSSValuePair scratch; michael@0: if (!ParseBackgroundSizeValues(scratch)) { michael@0: return false; michael@0: } michael@0: aState.mSize->mXValue = scratch.mXValue; michael@0: aState.mSize->mYValue = scratch.mYValue; michael@0: } michael@0: } else { michael@0: if (haveColor) michael@0: return false; michael@0: haveColor = true; michael@0: // Note: This parses 'inherit', 'initial' and 'unset', but michael@0: // we've already checked for them, so it's ok. michael@0: if (!ParseSingleValueProperty(aState.mColor, michael@0: eCSSProperty_background_color)) { michael@0: return false; michael@0: } michael@0: } michael@0: haveSomething = true; michael@0: } michael@0: michael@0: return haveSomething; michael@0: } michael@0: michael@0: // This function is very similar to ParseBackgroundPosition and michael@0: // ParseBackgroundSize. michael@0: bool michael@0: CSSParserImpl::ParseValueList(nsCSSProperty aPropID) michael@0: { michael@0: // aPropID is a single value prop-id michael@0: nsCSSValue value; michael@0: // 'initial', 'inherit' and 'unset' stand alone, no list permitted. michael@0: if (!ParseVariant(value, VARIANT_INHERIT, nullptr)) { michael@0: nsCSSValueList* item = value.SetListValue(); michael@0: for (;;) { michael@0: if (!ParseSingleValueProperty(item->mValue, aPropID)) { michael@0: return false; michael@0: } michael@0: if (!ExpectSymbol(',', true)) { michael@0: break; michael@0: } michael@0: item->mNext = new nsCSSValueList; michael@0: item = item->mNext; michael@0: } michael@0: } michael@0: AppendValue(aPropID, value); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseBackgroundRepeat() michael@0: { michael@0: nsCSSValue value; michael@0: // 'initial', 'inherit' and 'unset' stand alone, no list permitted. michael@0: if (!ParseVariant(value, VARIANT_INHERIT, nullptr)) { michael@0: nsCSSValuePair valuePair; michael@0: if (!ParseBackgroundRepeatValues(valuePair)) { michael@0: return false; michael@0: } michael@0: nsCSSValuePairList* item = value.SetPairListValue(); michael@0: for (;;) { michael@0: item->mXValue = valuePair.mXValue; michael@0: item->mYValue = valuePair.mYValue; michael@0: if (!ExpectSymbol(',', true)) { michael@0: break; michael@0: } michael@0: if (!ParseBackgroundRepeatValues(valuePair)) { michael@0: return false; michael@0: } michael@0: item->mNext = new nsCSSValuePairList; michael@0: item = item->mNext; michael@0: } michael@0: } michael@0: michael@0: AppendValue(eCSSProperty_background_repeat, value); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseBackgroundRepeatValues(nsCSSValuePair& aValue) michael@0: { michael@0: nsCSSValue& xValue = aValue.mXValue; michael@0: nsCSSValue& yValue = aValue.mYValue; michael@0: michael@0: if (ParseEnum(xValue, nsCSSProps::kBackgroundRepeatKTable)) { michael@0: int32_t value = xValue.GetIntValue(); michael@0: // For single values set yValue as eCSSUnit_Null. michael@0: if (value == NS_STYLE_BG_REPEAT_REPEAT_X || michael@0: value == NS_STYLE_BG_REPEAT_REPEAT_Y || michael@0: !ParseEnum(yValue, nsCSSProps::kBackgroundRepeatPartKTable)) { michael@0: // the caller will fail cases like "repeat-x no-repeat" michael@0: // by expecting a list separator or an end property. michael@0: yValue.Reset(); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: // This function is very similar to ParseBackgroundList and ParseBackgroundSize. michael@0: bool michael@0: CSSParserImpl::ParseBackgroundPosition() michael@0: { michael@0: nsCSSValue value; michael@0: // 'initial', 'inherit' and 'unset' stand alone, no list permitted. michael@0: if (!ParseVariant(value, VARIANT_INHERIT, nullptr)) { michael@0: nsCSSValue itemValue; michael@0: if (!ParseBackgroundPositionValues(itemValue, false)) { michael@0: return false; michael@0: } michael@0: nsCSSValueList* item = value.SetListValue(); michael@0: for (;;) { michael@0: item->mValue = itemValue; michael@0: if (!ExpectSymbol(',', true)) { michael@0: break; michael@0: } michael@0: if (!ParseBackgroundPositionValues(itemValue, false)) { michael@0: return false; michael@0: } michael@0: item->mNext = new nsCSSValueList; michael@0: item = item->mNext; michael@0: } michael@0: } michael@0: AppendValue(eCSSProperty_background_position, value); michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * BoxPositionMaskToCSSValue and ParseBoxPositionValues are used michael@0: * for parsing the CSS 2.1 background-position syntax (which has at michael@0: * most two values). (Compare to the css3-background syntax which michael@0: * takes up to four values.) Some current CSS specifications that michael@0: * use background-position-like syntax still use this old syntax. michael@0: ** michael@0: * Parses two values that correspond to positions in a box. These can be michael@0: * values corresponding to percentages of the box, raw offsets, or keywords michael@0: * like "top," "left center," etc. michael@0: * michael@0: * @param aOut The nsCSSValuePair in which to place the result. michael@0: * @param aAcceptsInherit If true, 'inherit', 'initial' and 'unset' are michael@0: * legal values michael@0: * @param aAllowExplicitCenter If true, 'center' is a legal value michael@0: * @return Whether or not the operation succeeded. michael@0: */ michael@0: bool CSSParserImpl::ParseBoxPositionValues(nsCSSValuePair &aOut, michael@0: bool aAcceptsInherit, michael@0: bool aAllowExplicitCenter) michael@0: { michael@0: // First try a percentage or a length value michael@0: nsCSSValue &xValue = aOut.mXValue, michael@0: &yValue = aOut.mYValue; michael@0: int32_t variantMask = michael@0: (aAcceptsInherit ? VARIANT_INHERIT : 0) | VARIANT_LP | VARIANT_CALC; michael@0: if (ParseVariant(xValue, variantMask, nullptr)) { michael@0: if (eCSSUnit_Inherit == xValue.GetUnit() || michael@0: eCSSUnit_Initial == xValue.GetUnit() || michael@0: eCSSUnit_Unset == xValue.GetUnit()) { // both are inherit, initial or unset michael@0: yValue = xValue; michael@0: return true; michael@0: } michael@0: // We have one percentage/length/calc. Get the optional second michael@0: // percentage/length/calc/keyword. michael@0: if (ParseVariant(yValue, VARIANT_LP | VARIANT_CALC, nullptr)) { michael@0: // We have two numbers michael@0: return true; michael@0: } michael@0: michael@0: if (ParseEnum(yValue, nsCSSProps::kBackgroundPositionKTable)) { michael@0: int32_t yVal = yValue.GetIntValue(); michael@0: if (!(yVal & BG_CTB)) { michael@0: // The second keyword can only be 'center', 'top', or 'bottom' michael@0: return false; michael@0: } michael@0: yValue = BoxPositionMaskToCSSValue(yVal, false); michael@0: return true; michael@0: } michael@0: michael@0: // If only one percentage or length value is given, it sets the michael@0: // horizontal position only, and the vertical position will be 50%. michael@0: yValue.SetPercentValue(0.5f); michael@0: return true; michael@0: } michael@0: michael@0: // Now try keywords. We do this manually to allow for the first michael@0: // appearance of "center" to apply to the either the x or y michael@0: // position (it's ambiguous so we have to disambiguate). Each michael@0: // allowed keyword value is assigned it's own bit. We don't allow michael@0: // any duplicate keywords other than center. We try to get two michael@0: // keywords but it's okay if there is only one. michael@0: int32_t mask = 0; michael@0: if (ParseEnum(xValue, nsCSSProps::kBackgroundPositionKTable)) { michael@0: int32_t bit = xValue.GetIntValue(); michael@0: mask |= bit; michael@0: if (ParseEnum(xValue, nsCSSProps::kBackgroundPositionKTable)) { michael@0: bit = xValue.GetIntValue(); michael@0: if (mask & (bit & ~BG_CENTER)) { michael@0: // Only the 'center' keyword can be duplicated. michael@0: return false; michael@0: } michael@0: mask |= bit; michael@0: } michael@0: else { michael@0: // Only one keyword. See if we have a length, percentage, or calc. michael@0: if (ParseVariant(yValue, VARIANT_LP | VARIANT_CALC, nullptr)) { michael@0: if (!(mask & BG_CLR)) { michael@0: // The first keyword can only be 'center', 'left', or 'right' michael@0: return false; michael@0: } michael@0: michael@0: xValue = BoxPositionMaskToCSSValue(mask, true); michael@0: return true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Check for bad input. Bad input consists of no matching keywords, michael@0: // or pairs of x keywords or pairs of y keywords. michael@0: if ((mask == 0) || (mask == (BG_TOP | BG_BOTTOM)) || michael@0: (mask == (BG_LEFT | BG_RIGHT)) || michael@0: (!aAllowExplicitCenter && (mask & BG_CENTER))) { michael@0: return false; michael@0: } michael@0: michael@0: // Create style values michael@0: xValue = BoxPositionMaskToCSSValue(mask, true); michael@0: yValue = BoxPositionMaskToCSSValue(mask, false); michael@0: return true; michael@0: } michael@0: michael@0: bool CSSParserImpl::ParseBackgroundPositionValues(nsCSSValue& aOut, michael@0: bool aAcceptsInherit) michael@0: { michael@0: // css3-background allows positions to be defined as offsets michael@0: // from an edge. There can be 2 keywords and 2 offsets given. These michael@0: // four 'values' are stored in an array in the following order: michael@0: // [keyword offset keyword offset]. If a keyword or offset isn't michael@0: // parsed the value of the corresponding array element is set michael@0: // to eCSSUnit_Null by a call to nsCSSValue::Reset(). michael@0: if (aAcceptsInherit && ParseVariant(aOut, VARIANT_INHERIT, nullptr)) { michael@0: return true; michael@0: } michael@0: michael@0: nsRefPtr value = nsCSSValue::Array::Create(4); michael@0: aOut.SetArrayValue(value, eCSSUnit_Array); michael@0: michael@0: // The following clarifies organisation of the array. michael@0: nsCSSValue &xEdge = value->Item(0), michael@0: &xOffset = value->Item(1), michael@0: &yEdge = value->Item(2), michael@0: &yOffset = value->Item(3); michael@0: michael@0: // Parse all the values into the array. michael@0: uint32_t valueCount = 0; michael@0: for (int32_t i = 0; i < 4; i++) { michael@0: if (!ParseVariant(value->Item(i), VARIANT_LPCALC | VARIANT_KEYWORD, michael@0: nsCSSProps::kBackgroundPositionKTable)) { michael@0: break; michael@0: } michael@0: ++valueCount; michael@0: } michael@0: michael@0: switch (valueCount) { michael@0: case 4: michael@0: // "If three or four values are given, then each or michael@0: // represents an offset and must be preceded by a keyword, which specifies michael@0: // from which edge the offset is given." michael@0: if (eCSSUnit_Enumerated != xEdge.GetUnit() || michael@0: BG_CENTER == xEdge.GetIntValue() || michael@0: eCSSUnit_Enumerated == xOffset.GetUnit() || michael@0: eCSSUnit_Enumerated != yEdge.GetUnit() || michael@0: BG_CENTER == yEdge.GetIntValue() || michael@0: eCSSUnit_Enumerated == yOffset.GetUnit()) { michael@0: return false; michael@0: } michael@0: break; michael@0: case 3: michael@0: // "If three or four values are given, then each or michael@0: // represents an offset and must be preceded by a keyword, which specifies michael@0: // from which edge the offset is given." ... "If three values are given, michael@0: // the missing offset is assumed to be zero." michael@0: if (eCSSUnit_Enumerated != value->Item(1).GetUnit()) { michael@0: // keyword offset keyword michael@0: // Second value is non-keyword, thus first value must be a non-center michael@0: // keyword. michael@0: if (eCSSUnit_Enumerated != value->Item(0).GetUnit() || michael@0: BG_CENTER == value->Item(0).GetIntValue()) { michael@0: return false; michael@0: } michael@0: michael@0: // Remaining value must be a keyword. michael@0: if (eCSSUnit_Enumerated != value->Item(2).GetUnit()) { michael@0: return false; michael@0: } michael@0: michael@0: yOffset.Reset(); // Everything else is in the correct position. michael@0: } else if (eCSSUnit_Enumerated != value->Item(2).GetUnit()) { michael@0: // keyword keyword offset michael@0: // Third value is non-keyword, thus second value must be non-center michael@0: // keyword. michael@0: if (BG_CENTER == value->Item(1).GetIntValue()) { michael@0: return false; michael@0: } michael@0: michael@0: // Remaining value must be a keyword. michael@0: if (eCSSUnit_Enumerated != value->Item(0).GetUnit()) { michael@0: return false; michael@0: } michael@0: michael@0: // Move the values to the correct position in the array. michael@0: value->Item(3) = value->Item(2); // yOffset michael@0: value->Item(2) = value->Item(1); // yEdge michael@0: value->Item(1).Reset(); // xOffset michael@0: } else { michael@0: return false; michael@0: } michael@0: break; michael@0: case 2: michael@0: // "If two values are given and at least one value is not a keyword, then michael@0: // the first value represents the horizontal position (or offset) and the michael@0: // second represents the vertical position (or offset)" michael@0: if (eCSSUnit_Enumerated == value->Item(0).GetUnit()) { michael@0: if (eCSSUnit_Enumerated == value->Item(1).GetUnit()) { michael@0: // keyword keyword michael@0: value->Item(2) = value->Item(1); // move yEdge to correct position michael@0: xOffset.Reset(); michael@0: yOffset.Reset(); michael@0: } else { michael@0: // keyword offset michael@0: // First value must represent horizontal position. michael@0: if ((BG_TOP | BG_BOTTOM) & value->Item(0).GetIntValue()) { michael@0: return false; michael@0: } michael@0: value->Item(3) = value->Item(1); // move yOffset to correct position michael@0: xOffset.Reset(); michael@0: yEdge.Reset(); michael@0: } michael@0: } else { michael@0: if (eCSSUnit_Enumerated == value->Item(1).GetUnit()) { michael@0: // offset keyword michael@0: // Second value must represent vertical position. michael@0: if ((BG_LEFT | BG_RIGHT) & value->Item(1).GetIntValue()) { michael@0: return false; michael@0: } michael@0: value->Item(2) = value->Item(1); // move yEdge to correct position michael@0: value->Item(1) = value->Item(0); // move xOffset to correct position michael@0: xEdge.Reset(); michael@0: yOffset.Reset(); michael@0: } else { michael@0: // offset offset michael@0: value->Item(3) = value->Item(1); // move yOffset to correct position michael@0: value->Item(1) = value->Item(0); // move xOffset to correct position michael@0: xEdge.Reset(); michael@0: yEdge.Reset(); michael@0: } michael@0: } michael@0: break; michael@0: case 1: michael@0: // "If only one value is specified, the second value is assumed to be michael@0: // center." michael@0: if (eCSSUnit_Enumerated == value->Item(0).GetUnit()) { michael@0: xOffset.Reset(); michael@0: } else { michael@0: value->Item(1) = value->Item(0); // move xOffset to correct position michael@0: xEdge.Reset(); michael@0: } michael@0: yEdge.SetIntValue(NS_STYLE_BG_POSITION_CENTER, eCSSUnit_Enumerated); michael@0: yOffset.Reset(); michael@0: break; michael@0: default: michael@0: return false; michael@0: } michael@0: michael@0: // For compatibility with CSS2.1 code the edges can be unspecified. michael@0: // Unspecified edges are recorded as nullptr. michael@0: NS_ASSERTION((eCSSUnit_Enumerated == xEdge.GetUnit() || michael@0: eCSSUnit_Null == xEdge.GetUnit()) && michael@0: (eCSSUnit_Enumerated == yEdge.GetUnit() || michael@0: eCSSUnit_Null == yEdge.GetUnit()) && michael@0: eCSSUnit_Enumerated != xOffset.GetUnit() && michael@0: eCSSUnit_Enumerated != yOffset.GetUnit(), michael@0: "Unexpected units"); michael@0: michael@0: // Keywords in first and second pairs can not both be vertical or michael@0: // horizontal keywords. (eg. left right, bottom top). Additionally, michael@0: // non-center keyword can not be duplicated (eg. left left). michael@0: int32_t xEdgeEnum = michael@0: xEdge.GetUnit() == eCSSUnit_Enumerated ? xEdge.GetIntValue() : 0; michael@0: int32_t yEdgeEnum = michael@0: yEdge.GetUnit() == eCSSUnit_Enumerated ? yEdge.GetIntValue() : 0; michael@0: if ((xEdgeEnum | yEdgeEnum) == (BG_LEFT | BG_RIGHT) || michael@0: (xEdgeEnum | yEdgeEnum) == (BG_TOP | BG_BOTTOM) || michael@0: (xEdgeEnum & yEdgeEnum & ~BG_CENTER)) { michael@0: return false; michael@0: } michael@0: michael@0: // The values could be in an order that is different than expected. michael@0: // eg. x contains vertical information, y contains horizontal information. michael@0: // Swap if incorrect order. michael@0: if (xEdgeEnum & (BG_TOP | BG_BOTTOM) || michael@0: yEdgeEnum & (BG_LEFT | BG_RIGHT)) { michael@0: nsCSSValue swapEdge = xEdge; michael@0: nsCSSValue swapOffset = xOffset; michael@0: xEdge = yEdge; michael@0: xOffset = yOffset; michael@0: yEdge = swapEdge; michael@0: yOffset = swapOffset; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // This function is very similar to ParseBackgroundList and michael@0: // ParseBackgroundPosition. michael@0: bool michael@0: CSSParserImpl::ParseBackgroundSize() michael@0: { michael@0: nsCSSValue value; michael@0: // 'initial', 'inherit' and 'unset' stand alone, no list permitted. michael@0: if (!ParseVariant(value, VARIANT_INHERIT, nullptr)) { michael@0: nsCSSValuePair valuePair; michael@0: if (!ParseBackgroundSizeValues(valuePair)) { michael@0: return false; michael@0: } michael@0: nsCSSValuePairList* item = value.SetPairListValue(); michael@0: for (;;) { michael@0: item->mXValue = valuePair.mXValue; michael@0: item->mYValue = valuePair.mYValue; michael@0: if (!ExpectSymbol(',', true)) { michael@0: break; michael@0: } michael@0: if (!ParseBackgroundSizeValues(valuePair)) { michael@0: return false; michael@0: } michael@0: item->mNext = new nsCSSValuePairList; michael@0: item = item->mNext; michael@0: } michael@0: } michael@0: AppendValue(eCSSProperty_background_size, value); michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Parses two values that correspond to lengths for the background-size michael@0: * property. These can be one or two lengths (or the 'auto' keyword) or michael@0: * percentages corresponding to the element's dimensions or the single keywords michael@0: * 'contain' or 'cover'. 'initial', 'inherit' and 'unset' must be handled by michael@0: * the caller if desired. michael@0: * michael@0: * @param aOut The nsCSSValuePair in which to place the result. michael@0: * @return Whether or not the operation succeeded. michael@0: */ michael@0: #define BG_SIZE_VARIANT (VARIANT_LP | VARIANT_AUTO | VARIANT_CALC) michael@0: bool CSSParserImpl::ParseBackgroundSizeValues(nsCSSValuePair &aOut) michael@0: { michael@0: // First try a percentage or a length value michael@0: nsCSSValue &xValue = aOut.mXValue, michael@0: &yValue = aOut.mYValue; michael@0: if (ParseNonNegativeVariant(xValue, BG_SIZE_VARIANT, nullptr)) { michael@0: // We have one percentage/length/calc/auto. Get the optional second michael@0: // percentage/length/calc/keyword. michael@0: if (ParseNonNegativeVariant(yValue, BG_SIZE_VARIANT, nullptr)) { michael@0: // We have a second percentage/length/calc/auto. michael@0: return true; michael@0: } michael@0: michael@0: // If only one percentage or length value is given, it sets the michael@0: // horizontal size only, and the vertical size will be as if by 'auto'. michael@0: yValue.SetAutoValue(); michael@0: return true; michael@0: } michael@0: michael@0: // Now address 'contain' and 'cover'. michael@0: if (!ParseEnum(xValue, nsCSSProps::kBackgroundSizeKTable)) michael@0: return false; michael@0: yValue.Reset(); michael@0: return true; michael@0: } michael@0: #undef BG_SIZE_VARIANT michael@0: michael@0: bool michael@0: CSSParserImpl::ParseBorderColor() michael@0: { michael@0: static const nsCSSProperty kBorderColorSources[] = { michael@0: eCSSProperty_border_left_color_ltr_source, michael@0: eCSSProperty_border_left_color_rtl_source, michael@0: eCSSProperty_border_right_color_ltr_source, michael@0: eCSSProperty_border_right_color_rtl_source, michael@0: eCSSProperty_UNKNOWN michael@0: }; michael@0: michael@0: // do this now, in case 4 values weren't specified michael@0: InitBoxPropsAsPhysical(kBorderColorSources); michael@0: return ParseBoxProperties(kBorderColorIDs); michael@0: } michael@0: michael@0: void michael@0: CSSParserImpl::SetBorderImageInitialValues() michael@0: { michael@0: // border-image-source: none michael@0: nsCSSValue source; michael@0: source.SetNoneValue(); michael@0: AppendValue(eCSSProperty_border_image_source, source); michael@0: michael@0: // border-image-slice: 100% michael@0: nsCSSValue sliceBoxValue; michael@0: nsCSSRect& sliceBox = sliceBoxValue.SetRectValue(); michael@0: sliceBox.SetAllSidesTo(nsCSSValue(1.0f, eCSSUnit_Percent)); michael@0: nsCSSValue slice; michael@0: nsCSSValueList* sliceList = slice.SetListValue(); michael@0: sliceList->mValue = sliceBoxValue; michael@0: AppendValue(eCSSProperty_border_image_slice, slice); michael@0: michael@0: // border-image-width: 1 michael@0: nsCSSValue width; michael@0: nsCSSRect& widthBox = width.SetRectValue(); michael@0: widthBox.SetAllSidesTo(nsCSSValue(1.0f, eCSSUnit_Number)); michael@0: AppendValue(eCSSProperty_border_image_width, width); michael@0: michael@0: // border-image-outset: 0 michael@0: nsCSSValue outset; michael@0: nsCSSRect& outsetBox = outset.SetRectValue(); michael@0: outsetBox.SetAllSidesTo(nsCSSValue(0.0f, eCSSUnit_Number)); michael@0: AppendValue(eCSSProperty_border_image_outset, outset); michael@0: michael@0: // border-image-repeat: repeat michael@0: nsCSSValue repeat; michael@0: nsCSSValuePair repeatPair; michael@0: repeatPair.SetBothValuesTo(nsCSSValue(NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH, michael@0: eCSSUnit_Enumerated)); michael@0: repeat.SetPairValue(&repeatPair); michael@0: AppendValue(eCSSProperty_border_image_repeat, repeat); michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseBorderImageSlice(bool aAcceptsInherit, michael@0: bool* aConsumedTokens) michael@0: { michael@0: // border-image-slice: initial | [|]{1,4} && fill? michael@0: nsCSSValue value; michael@0: michael@0: if (aConsumedTokens) { michael@0: *aConsumedTokens = true; michael@0: } michael@0: michael@0: if (aAcceptsInherit && ParseVariant(value, VARIANT_INHERIT, nullptr)) { michael@0: // Keywords "inherit", "initial" and "unset" can not be mixed, so we michael@0: // are done. michael@0: AppendValue(eCSSProperty_border_image_slice, value); michael@0: return true; michael@0: } michael@0: michael@0: // Try parsing "fill" value. michael@0: nsCSSValue imageSliceFillValue; michael@0: bool hasFill = ParseEnum(imageSliceFillValue, michael@0: nsCSSProps::kBorderImageSliceKTable); michael@0: michael@0: // Parse the box dimensions. michael@0: nsCSSValue imageSliceBoxValue; michael@0: if (!ParseGroupedBoxProperty(VARIANT_PN, imageSliceBoxValue)) { michael@0: if (!hasFill && aConsumedTokens) { michael@0: *aConsumedTokens = false; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: // Try parsing "fill" keyword again if the first time failed because keyword michael@0: // and slice dimensions can be in any order. michael@0: if (!hasFill) { michael@0: hasFill = ParseEnum(imageSliceFillValue, michael@0: nsCSSProps::kBorderImageSliceKTable); michael@0: } michael@0: michael@0: nsCSSValueList* borderImageSlice = value.SetListValue(); michael@0: // Put the box value into the list. michael@0: borderImageSlice->mValue = imageSliceBoxValue; michael@0: michael@0: if (hasFill) { michael@0: // Put the "fill" value into the list. michael@0: borderImageSlice->mNext = new nsCSSValueList; michael@0: borderImageSlice->mNext->mValue = imageSliceFillValue; michael@0: } michael@0: michael@0: AppendValue(eCSSProperty_border_image_slice, value); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseBorderImageWidth(bool aAcceptsInherit) michael@0: { michael@0: // border-image-width: initial | [|||auto]{1,4} michael@0: nsCSSValue value; michael@0: michael@0: if (aAcceptsInherit && ParseVariant(value, VARIANT_INHERIT, nullptr)) { michael@0: // Keywords "inherit", "initial" and "unset" can not be mixed, so we michael@0: // are done. michael@0: AppendValue(eCSSProperty_border_image_width, value); michael@0: return true; michael@0: } michael@0: michael@0: // Parse the box dimensions. michael@0: if (!ParseGroupedBoxProperty(VARIANT_ALPN, value)) { michael@0: return false; michael@0: } michael@0: michael@0: AppendValue(eCSSProperty_border_image_width, value); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseBorderImageOutset(bool aAcceptsInherit) michael@0: { michael@0: // border-image-outset: initial | [|]{1,4} michael@0: nsCSSValue value; michael@0: michael@0: if (aAcceptsInherit && ParseVariant(value, VARIANT_INHERIT, nullptr)) { michael@0: // Keywords "inherit", "initial" and "unset" can not be mixed, so we michael@0: // are done. michael@0: AppendValue(eCSSProperty_border_image_outset, value); michael@0: return true; michael@0: } michael@0: michael@0: // Parse the box dimensions. michael@0: if (!ParseGroupedBoxProperty(VARIANT_LN, value)) { michael@0: return false; michael@0: } michael@0: michael@0: AppendValue(eCSSProperty_border_image_outset, value); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseBorderImageRepeat(bool aAcceptsInherit) michael@0: { michael@0: nsCSSValue value; michael@0: if (aAcceptsInherit && ParseVariant(value, VARIANT_INHERIT, nullptr)) { michael@0: // Keywords "inherit", "initial" and "unset" can not be mixed, so we michael@0: // are done. michael@0: AppendValue(eCSSProperty_border_image_repeat, value); michael@0: return true; michael@0: } michael@0: michael@0: nsCSSValuePair result; michael@0: if (!ParseEnum(result.mXValue, nsCSSProps::kBorderImageRepeatKTable)) { michael@0: return false; michael@0: } michael@0: michael@0: // optional second keyword, defaults to first michael@0: if (!ParseEnum(result.mYValue, nsCSSProps::kBorderImageRepeatKTable)) { michael@0: result.mYValue = result.mXValue; michael@0: } michael@0: michael@0: value.SetPairValue(&result); michael@0: AppendValue(eCSSProperty_border_image_repeat, value); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseBorderImage() michael@0: { michael@0: nsAutoParseCompoundProperty compound(this); michael@0: michael@0: // border-image: inherit | initial | michael@0: // || michael@0: // michael@0: // [ / | michael@0: // / ? / ]? || michael@0: // michael@0: michael@0: nsCSSValue value; michael@0: if (ParseVariant(value, VARIANT_INHERIT, nullptr)) { michael@0: AppendValue(eCSSProperty_border_image_source, value); michael@0: AppendValue(eCSSProperty_border_image_slice, value); michael@0: AppendValue(eCSSProperty_border_image_width, value); michael@0: AppendValue(eCSSProperty_border_image_outset, value); michael@0: AppendValue(eCSSProperty_border_image_repeat, value); michael@0: // Keywords "inherit", "initial" and "unset" can't be mixed, so we are done. michael@0: return true; michael@0: } michael@0: michael@0: // No empty property. michael@0: if (CheckEndProperty()) { michael@0: return false; michael@0: } michael@0: michael@0: // Shorthand properties are required to set everything they can. michael@0: SetBorderImageInitialValues(); michael@0: michael@0: bool foundSource = false; michael@0: bool foundSliceWidthOutset = false; michael@0: bool foundRepeat = false; michael@0: michael@0: // This loop is used to handle the parsing of border-image properties which michael@0: // can appear in any order. michael@0: nsCSSValue imageSourceValue; michael@0: while (!CheckEndProperty()) { michael@0: // michael@0: if (!foundSource && ParseVariant(imageSourceValue, VARIANT_IMAGE, nullptr)) { michael@0: AppendValue(eCSSProperty_border_image_source, imageSourceValue); michael@0: foundSource = true; michael@0: continue; michael@0: } michael@0: michael@0: // michael@0: // ParseBorderImageSlice is weird. It may consume tokens and then return michael@0: // false, because it parses a property with two required components that michael@0: // can appear in either order. Since the tokens that were consumed cannot michael@0: // parse as anything else we care about, this isn't a problem. michael@0: if (!foundSliceWidthOutset) { michael@0: bool sliceConsumedTokens = false; michael@0: if (ParseBorderImageSlice(false, &sliceConsumedTokens)) { michael@0: foundSliceWidthOutset = true; michael@0: michael@0: // [ / ? michael@0: if (ExpectSymbol('/', true)) { michael@0: bool foundBorderImageWidth = ParseBorderImageWidth(false); michael@0: michael@0: // [ / michael@0: if (ExpectSymbol('/', true)) { michael@0: if (!ParseBorderImageOutset(false)) { michael@0: return false; michael@0: } michael@0: } else if (!foundBorderImageWidth) { michael@0: // If this part has an trailing slash, the whole declaration is michael@0: // invalid. michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: continue; michael@0: } else { michael@0: // If we consumed some tokens for but did not michael@0: // successfully parse it, we have an error. michael@0: if (sliceConsumedTokens) { michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // michael@0: if (!foundRepeat && ParseBorderImageRepeat(false)) { michael@0: foundRepeat = true; michael@0: continue; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseBorderSpacing() michael@0: { michael@0: nsCSSValue xValue, yValue; michael@0: if (!ParseNonNegativeVariant(xValue, VARIANT_HL | VARIANT_CALC, nullptr)) { michael@0: return false; michael@0: } michael@0: michael@0: // If we have one length, get the optional second length. michael@0: // set the second value equal to the first. michael@0: if (xValue.IsLengthUnit() || xValue.IsCalcUnit()) { michael@0: ParseNonNegativeVariant(yValue, VARIANT_LENGTH | VARIANT_CALC, nullptr); michael@0: } michael@0: michael@0: if (yValue == xValue || yValue.GetUnit() == eCSSUnit_Null) { michael@0: AppendValue(eCSSProperty_border_spacing, xValue); michael@0: } else { michael@0: nsCSSValue pair; michael@0: pair.SetPairValue(xValue, yValue); michael@0: AppendValue(eCSSProperty_border_spacing, pair); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseBorderSide(const nsCSSProperty aPropIDs[], michael@0: bool aSetAllSides) michael@0: { michael@0: const int32_t numProps = 3; michael@0: nsCSSValue values[numProps]; michael@0: michael@0: int32_t found = ParseChoice(values, aPropIDs, numProps); michael@0: if (found < 1) { michael@0: return false; michael@0: } michael@0: michael@0: if ((found & 1) == 0) { // Provide default border-width michael@0: values[0].SetIntValue(NS_STYLE_BORDER_WIDTH_MEDIUM, eCSSUnit_Enumerated); michael@0: } michael@0: if ((found & 2) == 0) { // Provide default border-style michael@0: values[1].SetIntValue(NS_STYLE_BORDER_STYLE_NONE, eCSSUnit_Enumerated); michael@0: } michael@0: if ((found & 4) == 0) { // text color will be used michael@0: values[2].SetIntValue(NS_STYLE_COLOR_MOZ_USE_TEXT_COLOR, eCSSUnit_Enumerated); michael@0: } michael@0: michael@0: if (aSetAllSides) { michael@0: static const nsCSSProperty kBorderSources[] = { michael@0: eCSSProperty_border_left_color_ltr_source, michael@0: eCSSProperty_border_left_color_rtl_source, michael@0: eCSSProperty_border_right_color_ltr_source, michael@0: eCSSProperty_border_right_color_rtl_source, michael@0: eCSSProperty_border_left_style_ltr_source, michael@0: eCSSProperty_border_left_style_rtl_source, michael@0: eCSSProperty_border_right_style_ltr_source, michael@0: eCSSProperty_border_right_style_rtl_source, michael@0: eCSSProperty_border_left_width_ltr_source, michael@0: eCSSProperty_border_left_width_rtl_source, michael@0: eCSSProperty_border_right_width_ltr_source, michael@0: eCSSProperty_border_right_width_rtl_source, michael@0: eCSSProperty_UNKNOWN michael@0: }; michael@0: michael@0: InitBoxPropsAsPhysical(kBorderSources); michael@0: michael@0: // Parsing "border" shorthand; set all four sides to the same thing michael@0: for (int32_t index = 0; index < 4; index++) { michael@0: NS_ASSERTION(numProps == 3, "This code needs updating"); michael@0: AppendValue(kBorderWidthIDs[index], values[0]); michael@0: AppendValue(kBorderStyleIDs[index], values[1]); michael@0: AppendValue(kBorderColorIDs[index], values[2]); michael@0: } michael@0: michael@0: static const nsCSSProperty kBorderColorsProps[] = { michael@0: eCSSProperty_border_top_colors, michael@0: eCSSProperty_border_right_colors, michael@0: eCSSProperty_border_bottom_colors, michael@0: eCSSProperty_border_left_colors michael@0: }; michael@0: michael@0: // Set the other properties that the border shorthand sets to their michael@0: // initial values. michael@0: nsCSSValue extraValue; michael@0: switch (values[0].GetUnit()) { michael@0: case eCSSUnit_Inherit: michael@0: case eCSSUnit_Initial: michael@0: case eCSSUnit_Unset: michael@0: extraValue = values[0]; michael@0: // Set value of border-image properties to initial/inherit/unset michael@0: AppendValue(eCSSProperty_border_image_source, extraValue); michael@0: AppendValue(eCSSProperty_border_image_slice, extraValue); michael@0: AppendValue(eCSSProperty_border_image_width, extraValue); michael@0: AppendValue(eCSSProperty_border_image_outset, extraValue); michael@0: AppendValue(eCSSProperty_border_image_repeat, extraValue); michael@0: break; michael@0: default: michael@0: extraValue.SetNoneValue(); michael@0: SetBorderImageInitialValues(); michael@0: break; michael@0: } michael@0: NS_FOR_CSS_SIDES(side) { michael@0: AppendValue(kBorderColorsProps[side], extraValue); michael@0: } michael@0: } michael@0: else { michael@0: // Just set our one side michael@0: for (int32_t index = 0; index < numProps; index++) { michael@0: AppendValue(aPropIDs[index], values[index]); michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseDirectionalBorderSide(const nsCSSProperty aPropIDs[], michael@0: int32_t aSourceType) michael@0: { michael@0: const int32_t numProps = 3; michael@0: nsCSSValue values[numProps]; michael@0: michael@0: int32_t found = ParseChoice(values, aPropIDs, numProps); michael@0: if (found < 1) { michael@0: return false; michael@0: } michael@0: michael@0: if ((found & 1) == 0) { // Provide default border-width michael@0: values[0].SetIntValue(NS_STYLE_BORDER_WIDTH_MEDIUM, eCSSUnit_Enumerated); michael@0: } michael@0: if ((found & 2) == 0) { // Provide default border-style michael@0: values[1].SetIntValue(NS_STYLE_BORDER_STYLE_NONE, eCSSUnit_Enumerated); michael@0: } michael@0: if ((found & 4) == 0) { // text color will be used michael@0: values[2].SetIntValue(NS_STYLE_COLOR_MOZ_USE_TEXT_COLOR, eCSSUnit_Enumerated); michael@0: } michael@0: for (int32_t index = 0; index < numProps; index++) { michael@0: const nsCSSProperty* subprops = michael@0: nsCSSProps::SubpropertyEntryFor(aPropIDs[index + numProps]); michael@0: NS_ASSERTION(subprops[3] == eCSSProperty_UNKNOWN, michael@0: "not box property with physical vs. logical cascading"); michael@0: AppendValue(subprops[0], values[index]); michael@0: nsCSSValue typeVal(aSourceType, eCSSUnit_Enumerated); michael@0: AppendValue(subprops[1], typeVal); michael@0: AppendValue(subprops[2], typeVal); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseBorderStyle() michael@0: { michael@0: static const nsCSSProperty kBorderStyleSources[] = { michael@0: eCSSProperty_border_left_style_ltr_source, michael@0: eCSSProperty_border_left_style_rtl_source, michael@0: eCSSProperty_border_right_style_ltr_source, michael@0: eCSSProperty_border_right_style_rtl_source, michael@0: eCSSProperty_UNKNOWN michael@0: }; michael@0: michael@0: // do this now, in case 4 values weren't specified michael@0: InitBoxPropsAsPhysical(kBorderStyleSources); michael@0: return ParseBoxProperties(kBorderStyleIDs); michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseBorderWidth() michael@0: { michael@0: static const nsCSSProperty kBorderWidthSources[] = { michael@0: eCSSProperty_border_left_width_ltr_source, michael@0: eCSSProperty_border_left_width_rtl_source, michael@0: eCSSProperty_border_right_width_ltr_source, michael@0: eCSSProperty_border_right_width_rtl_source, michael@0: eCSSProperty_UNKNOWN michael@0: }; michael@0: michael@0: // do this now, in case 4 values weren't specified michael@0: InitBoxPropsAsPhysical(kBorderWidthSources); michael@0: return ParseBoxProperties(kBorderWidthIDs); michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseBorderColors(nsCSSProperty aProperty) michael@0: { michael@0: nsCSSValue value; michael@0: // 'inherit', 'initial', 'unset' and 'none' are only allowed on their own michael@0: if (!ParseVariant(value, VARIANT_INHERIT | VARIANT_NONE, nullptr)) { michael@0: nsCSSValueList *cur = value.SetListValue(); michael@0: for (;;) { michael@0: if (!ParseVariant(cur->mValue, VARIANT_COLOR | VARIANT_KEYWORD, michael@0: nsCSSProps::kBorderColorKTable)) { michael@0: return false; michael@0: } michael@0: if (CheckEndProperty()) { michael@0: break; michael@0: } michael@0: cur->mNext = new nsCSSValueList; michael@0: cur = cur->mNext; michael@0: } michael@0: } michael@0: AppendValue(aProperty, value); michael@0: return true; michael@0: } michael@0: michael@0: // Parse the top level of a calc() expression. michael@0: bool michael@0: CSSParserImpl::ParseCalc(nsCSSValue &aValue, int32_t aVariantMask) michael@0: { michael@0: // Parsing calc expressions requires, in a number of cases, looking michael@0: // for a token that is *either* a value of the property or a number. michael@0: // This can be done without lookahead when we assume that the property michael@0: // values cannot themselves be numbers. michael@0: NS_ASSERTION(!(aVariantMask & VARIANT_NUMBER), "unexpected variant mask"); michael@0: NS_ABORT_IF_FALSE(aVariantMask != 0, "unexpected variant mask"); michael@0: michael@0: bool oldUnitlessLengthQuirk = mUnitlessLengthQuirk; michael@0: mUnitlessLengthQuirk = false; michael@0: michael@0: // One-iteration loop so we can break to the error-handling case. michael@0: do { michael@0: // The toplevel of a calc() is always an nsCSSValue::Array of length 1. michael@0: nsRefPtr arr = nsCSSValue::Array::Create(1); michael@0: michael@0: if (!ParseCalcAdditiveExpression(arr->Item(0), aVariantMask)) michael@0: break; michael@0: michael@0: if (!ExpectSymbol(')', true)) michael@0: break; michael@0: michael@0: aValue.SetArrayValue(arr, eCSSUnit_Calc); michael@0: mUnitlessLengthQuirk = oldUnitlessLengthQuirk; michael@0: return true; michael@0: } while (false); michael@0: michael@0: SkipUntil(')'); michael@0: mUnitlessLengthQuirk = oldUnitlessLengthQuirk; michael@0: return false; michael@0: } michael@0: michael@0: // We optimize away the production given that michael@0: // ParseVariant consumes initial whitespace and we call michael@0: // ExpectSymbol(')') with true for aSkipWS. michael@0: // * If aVariantMask is VARIANT_NUMBER, this function parses the michael@0: // production. michael@0: // * If aVariantMask does not contain VARIANT_NUMBER, this function michael@0: // parses the production. michael@0: // * Otherwise (VARIANT_NUMBER and other bits) this function parses michael@0: // whichever one of the productions matches ***and modifies michael@0: // aVariantMask*** to reflect which one it has parsed by either michael@0: // removing VARIANT_NUMBER or removing all other bits. michael@0: // It does so iteratively, but builds the correct recursive michael@0: // data structure. michael@0: bool michael@0: CSSParserImpl::ParseCalcAdditiveExpression(nsCSSValue& aValue, michael@0: int32_t& aVariantMask) michael@0: { michael@0: NS_ABORT_IF_FALSE(aVariantMask != 0, "unexpected variant mask"); michael@0: nsCSSValue *storage = &aValue; michael@0: for (;;) { michael@0: bool haveWS; michael@0: if (!ParseCalcMultiplicativeExpression(*storage, aVariantMask, &haveWS)) michael@0: return false; michael@0: michael@0: if (!haveWS || !GetToken(false)) michael@0: return true; michael@0: nsCSSUnit unit; michael@0: if (mToken.IsSymbol('+')) { michael@0: unit = eCSSUnit_Calc_Plus; michael@0: } else if (mToken.IsSymbol('-')) { michael@0: unit = eCSSUnit_Calc_Minus; michael@0: } else { michael@0: UngetToken(); michael@0: return true; michael@0: } michael@0: if (!RequireWhitespace()) michael@0: return false; michael@0: michael@0: nsRefPtr arr = nsCSSValue::Array::Create(2); michael@0: arr->Item(0) = aValue; michael@0: storage = &arr->Item(1); michael@0: aValue.SetArrayValue(arr, unit); michael@0: } michael@0: } michael@0: michael@0: struct ReduceNumberCalcOps : public mozilla::css::BasicFloatCalcOps, michael@0: public mozilla::css::CSSValueInputCalcOps michael@0: { michael@0: result_type ComputeLeafValue(const nsCSSValue& aValue) michael@0: { michael@0: NS_ABORT_IF_FALSE(aValue.GetUnit() == eCSSUnit_Number, "unexpected unit"); michael@0: return aValue.GetFloatValue(); michael@0: } michael@0: michael@0: float ComputeNumber(const nsCSSValue& aValue) michael@0: { michael@0: return mozilla::css::ComputeCalc(aValue, *this); michael@0: } michael@0: }; michael@0: michael@0: // * If aVariantMask is VARIANT_NUMBER, this function parses the michael@0: // production. michael@0: // * If aVariantMask does not contain VARIANT_NUMBER, this function michael@0: // parses the production. michael@0: // * Otherwise (VARIANT_NUMBER and other bits) this function parses michael@0: // whichever one of the productions matches ***and modifies michael@0: // aVariantMask*** to reflect which one it has parsed by either michael@0: // removing VARIANT_NUMBER or removing all other bits. michael@0: // It does so iteratively, but builds the correct recursive data michael@0: // structure. michael@0: // This function always consumes *trailing* whitespace when it returns michael@0: // true; whether there was any such whitespace is returned in the michael@0: // aHadFinalWS parameter. michael@0: bool michael@0: CSSParserImpl::ParseCalcMultiplicativeExpression(nsCSSValue& aValue, michael@0: int32_t& aVariantMask, michael@0: bool *aHadFinalWS) michael@0: { michael@0: NS_ABORT_IF_FALSE(aVariantMask != 0, "unexpected variant mask"); michael@0: bool gotValue = false; // already got the part with the unit michael@0: bool afterDivision = false; michael@0: michael@0: nsCSSValue *storage = &aValue; michael@0: for (;;) { michael@0: int32_t variantMask; michael@0: if (afterDivision || gotValue) { michael@0: variantMask = VARIANT_NUMBER; michael@0: } else { michael@0: variantMask = aVariantMask | VARIANT_NUMBER; michael@0: } michael@0: if (!ParseCalcTerm(*storage, variantMask)) michael@0: return false; michael@0: NS_ABORT_IF_FALSE(variantMask != 0, michael@0: "ParseCalcTerm did not set variantMask appropriately"); michael@0: NS_ABORT_IF_FALSE(!(variantMask & VARIANT_NUMBER) || michael@0: !(variantMask & ~int32_t(VARIANT_NUMBER)), michael@0: "ParseCalcTerm did not set variantMask appropriately"); michael@0: michael@0: if (variantMask & VARIANT_NUMBER) { michael@0: // Simplify the value immediately so we can check for division by michael@0: // zero. michael@0: ReduceNumberCalcOps ops; michael@0: float number = mozilla::css::ComputeCalc(*storage, ops); michael@0: if (number == 0.0 && afterDivision) michael@0: return false; michael@0: storage->SetFloatValue(number, eCSSUnit_Number); michael@0: } else { michael@0: gotValue = true; michael@0: michael@0: if (storage != &aValue) { michael@0: // Simplify any numbers in the Times_L position (which are michael@0: // not simplified by the check above). michael@0: NS_ABORT_IF_FALSE(storage == &aValue.GetArrayValue()->Item(1), michael@0: "unexpected relationship to current storage"); michael@0: nsCSSValue &leftValue = aValue.GetArrayValue()->Item(0); michael@0: ReduceNumberCalcOps ops; michael@0: float number = mozilla::css::ComputeCalc(leftValue, ops); michael@0: leftValue.SetFloatValue(number, eCSSUnit_Number); michael@0: } michael@0: } michael@0: michael@0: bool hadWS = RequireWhitespace(); michael@0: if (!GetToken(false)) { michael@0: *aHadFinalWS = hadWS; michael@0: break; michael@0: } michael@0: nsCSSUnit unit; michael@0: if (mToken.IsSymbol('*')) { michael@0: unit = gotValue ? eCSSUnit_Calc_Times_R : eCSSUnit_Calc_Times_L; michael@0: afterDivision = false; michael@0: } else if (mToken.IsSymbol('/')) { michael@0: unit = eCSSUnit_Calc_Divided; michael@0: afterDivision = true; michael@0: } else { michael@0: UngetToken(); michael@0: *aHadFinalWS = hadWS; michael@0: break; michael@0: } michael@0: michael@0: nsRefPtr arr = nsCSSValue::Array::Create(2); michael@0: arr->Item(0) = aValue; michael@0: storage = &arr->Item(1); michael@0: aValue.SetArrayValue(arr, unit); michael@0: } michael@0: michael@0: // Adjust aVariantMask (see comments above function) to reflect which michael@0: // option we took. michael@0: if (aVariantMask & VARIANT_NUMBER) { michael@0: if (gotValue) { michael@0: aVariantMask &= ~int32_t(VARIANT_NUMBER); michael@0: } else { michael@0: aVariantMask = VARIANT_NUMBER; michael@0: } michael@0: } else { michael@0: if (!gotValue) { michael@0: // We had to find a value, but we didn't. michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // * If aVariantMask is VARIANT_NUMBER, this function parses the michael@0: // production. michael@0: // * If aVariantMask does not contain VARIANT_NUMBER, this function michael@0: // parses the production. michael@0: // * Otherwise (VARIANT_NUMBER and other bits) this function parses michael@0: // whichever one of the productions matches ***and modifies michael@0: // aVariantMask*** to reflect which one it has parsed by either michael@0: // removing VARIANT_NUMBER or removing all other bits. michael@0: bool michael@0: CSSParserImpl::ParseCalcTerm(nsCSSValue& aValue, int32_t& aVariantMask) michael@0: { michael@0: NS_ABORT_IF_FALSE(aVariantMask != 0, "unexpected variant mask"); michael@0: if (!GetToken(true)) michael@0: return false; michael@0: // Either an additive expression in parentheses... michael@0: if (mToken.IsSymbol('(')) { michael@0: if (!ParseCalcAdditiveExpression(aValue, aVariantMask) || michael@0: !ExpectSymbol(')', true)) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: // ... or just a value michael@0: UngetToken(); michael@0: // Always pass VARIANT_NUMBER to ParseVariant so that unitless zero michael@0: // always gets picked up michael@0: if (!ParseVariant(aValue, aVariantMask | VARIANT_NUMBER, nullptr)) { michael@0: return false; michael@0: } michael@0: // ...and do the VARIANT_NUMBER check ourselves. michael@0: if (!(aVariantMask & VARIANT_NUMBER) && aValue.GetUnit() == eCSSUnit_Number) { michael@0: return false; michael@0: } michael@0: // If we did the value parsing, we need to adjust aVariantMask to michael@0: // reflect which option we took (see above). michael@0: if (aVariantMask & VARIANT_NUMBER) { michael@0: if (aValue.GetUnit() == eCSSUnit_Number) { michael@0: aVariantMask = VARIANT_NUMBER; michael@0: } else { michael@0: aVariantMask &= ~int32_t(VARIANT_NUMBER); michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: // This function consumes all consecutive whitespace and returns whether michael@0: // there was any. michael@0: bool michael@0: CSSParserImpl::RequireWhitespace() michael@0: { michael@0: if (!GetToken(false)) michael@0: return false; michael@0: if (mToken.mType != eCSSToken_Whitespace) { michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: // Skip any additional whitespace tokens. michael@0: if (GetToken(true)) { michael@0: UngetToken(); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseRect(nsCSSProperty aPropID) michael@0: { michael@0: nsCSSValue val; michael@0: if (ParseVariant(val, VARIANT_INHERIT | VARIANT_AUTO, nullptr)) { michael@0: AppendValue(aPropID, val); michael@0: return true; michael@0: } michael@0: michael@0: if (! GetToken(true)) { michael@0: return false; michael@0: } michael@0: michael@0: if (mToken.mType == eCSSToken_Function && michael@0: mToken.mIdent.LowerCaseEqualsLiteral("rect")) { michael@0: nsCSSRect& rect = val.SetRectValue(); michael@0: bool useCommas; michael@0: NS_FOR_CSS_SIDES(side) { michael@0: if (! ParseVariant(rect.*(nsCSSRect::sides[side]), michael@0: VARIANT_AL, nullptr)) { michael@0: return false; michael@0: } michael@0: if (side == 0) { michael@0: useCommas = ExpectSymbol(',', true); michael@0: } else if (useCommas && side < 3) { michael@0: // Skip optional commas between elements, but only if the first michael@0: // separator was a comma. michael@0: if (!ExpectSymbol(',', true)) { michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: if (!ExpectSymbol(')', true)) { michael@0: return false; michael@0: } michael@0: } else { michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: michael@0: AppendValue(aPropID, val); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseColumns() michael@0: { michael@0: // We use a similar "fake value" hack to ParseListStyle, because michael@0: // "auto" is acceptable for both column-count and column-width. michael@0: // If the fake "auto" value is found, and one of the real values isn't, michael@0: // that means the fake auto value is meant for the real value we didn't michael@0: // find. michael@0: static const nsCSSProperty columnIDs[] = { michael@0: eCSSPropertyExtra_x_auto_value, michael@0: eCSSProperty__moz_column_count, michael@0: eCSSProperty__moz_column_width michael@0: }; michael@0: const int32_t numProps = MOZ_ARRAY_LENGTH(columnIDs); michael@0: michael@0: nsCSSValue values[numProps]; michael@0: int32_t found = ParseChoice(values, columnIDs, numProps); michael@0: if (found < 1) { michael@0: return false; michael@0: } michael@0: if ((found & (1|2|4)) == (1|2|4) && michael@0: values[0].GetUnit() == eCSSUnit_Auto) { michael@0: // We filled all 3 values, which is invalid michael@0: return false; michael@0: } michael@0: michael@0: if ((found & 2) == 0) { michael@0: // Provide auto column-count michael@0: values[1].SetAutoValue(); michael@0: } michael@0: if ((found & 4) == 0) { michael@0: // Provide auto column-width michael@0: values[2].SetAutoValue(); michael@0: } michael@0: michael@0: // Start at index 1 to skip the fake auto value. michael@0: for (int32_t index = 1; index < numProps; index++) { michael@0: AppendValue(columnIDs[index], values[index]); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: #define VARIANT_CONTENT (VARIANT_STRING | VARIANT_URL | VARIANT_COUNTER | VARIANT_ATTR | \ michael@0: VARIANT_KEYWORD) michael@0: bool michael@0: CSSParserImpl::ParseContent() michael@0: { michael@0: // We need to divide the 'content' keywords into two classes for michael@0: // ParseVariant's sake, so we can't just use nsCSSProps::kContentKTable. michael@0: static const KTableValue kContentListKWs[] = { michael@0: eCSSKeyword_open_quote, NS_STYLE_CONTENT_OPEN_QUOTE, michael@0: eCSSKeyword_close_quote, NS_STYLE_CONTENT_CLOSE_QUOTE, michael@0: eCSSKeyword_no_open_quote, NS_STYLE_CONTENT_NO_OPEN_QUOTE, michael@0: eCSSKeyword_no_close_quote, NS_STYLE_CONTENT_NO_CLOSE_QUOTE, michael@0: eCSSKeyword_UNKNOWN,-1 michael@0: }; michael@0: michael@0: static const KTableValue kContentSolitaryKWs[] = { michael@0: eCSSKeyword__moz_alt_content, NS_STYLE_CONTENT_ALT_CONTENT, michael@0: eCSSKeyword_UNKNOWN,-1 michael@0: }; michael@0: michael@0: // Verify that these two lists add up to the size of michael@0: // nsCSSProps::kContentKTable. michael@0: NS_ABORT_IF_FALSE(nsCSSProps::kContentKTable[ michael@0: ArrayLength(kContentListKWs) + michael@0: ArrayLength(kContentSolitaryKWs) - 4] == michael@0: eCSSKeyword_UNKNOWN && michael@0: nsCSSProps::kContentKTable[ michael@0: ArrayLength(kContentListKWs) + michael@0: ArrayLength(kContentSolitaryKWs) - 3] == -1, michael@0: "content keyword tables out of sync"); michael@0: michael@0: nsCSSValue value; michael@0: // 'inherit', 'initial', 'unset', 'normal', 'none', and 'alt-content' must michael@0: // be alone michael@0: if (!ParseVariant(value, VARIANT_HMK | VARIANT_NONE, michael@0: kContentSolitaryKWs)) { michael@0: nsCSSValueList* cur = value.SetListValue(); michael@0: for (;;) { michael@0: if (!ParseVariant(cur->mValue, VARIANT_CONTENT, kContentListKWs)) { michael@0: return false; michael@0: } michael@0: if (CheckEndProperty()) { michael@0: break; michael@0: } michael@0: cur->mNext = new nsCSSValueList; michael@0: cur = cur->mNext; michael@0: } michael@0: } michael@0: AppendValue(eCSSProperty_content, value); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseCounterData(nsCSSProperty aPropID) michael@0: { michael@0: static const nsCSSKeyword kCounterDataKTable[] = { michael@0: eCSSKeyword_none, michael@0: eCSSKeyword_UNKNOWN michael@0: }; michael@0: nsCSSValue value; michael@0: if (!ParseVariant(value, VARIANT_INHERIT | VARIANT_NONE, nullptr)) { michael@0: if (!GetToken(true)) { michael@0: return false; michael@0: } michael@0: if (mToken.mType != eCSSToken_Ident) { michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: michael@0: nsCSSValuePairList *cur = value.SetPairListValue(); michael@0: for (;;) { michael@0: if (!ParseCustomIdent(cur->mXValue, mToken.mIdent, kCounterDataKTable)) { michael@0: return false; michael@0: } michael@0: if (!GetToken(true)) { michael@0: break; michael@0: } michael@0: if (mToken.mType == eCSSToken_Number && mToken.mIntegerValid) { michael@0: cur->mYValue.SetIntValue(mToken.mInteger, eCSSUnit_Integer); michael@0: } else { michael@0: UngetToken(); michael@0: } michael@0: if (!GetToken(true)) { michael@0: break; michael@0: } michael@0: if (mToken.mType != eCSSToken_Ident) { michael@0: UngetToken(); michael@0: break; michael@0: } michael@0: cur->mNext = new nsCSSValuePairList; michael@0: cur = cur->mNext; michael@0: } michael@0: } michael@0: AppendValue(aPropID, value); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseCursor() michael@0: { michael@0: nsCSSValue value; michael@0: // 'inherit', 'initial' and 'unset' must be alone michael@0: if (!ParseVariant(value, VARIANT_INHERIT, nullptr)) { michael@0: nsCSSValueList* cur = value.SetListValue(); michael@0: for (;;) { michael@0: if (!ParseVariant(cur->mValue, VARIANT_UK, nsCSSProps::kCursorKTable)) { michael@0: return false; michael@0: } michael@0: if (cur->mValue.GetUnit() != eCSSUnit_URL) { // keyword must be last michael@0: break; michael@0: } michael@0: michael@0: // We have a URL, so make a value array with three values. michael@0: nsRefPtr val = nsCSSValue::Array::Create(3); michael@0: val->Item(0) = cur->mValue; michael@0: michael@0: // Parse optional x and y position of cursor hotspot (css3-ui). michael@0: if (ParseVariant(val->Item(1), VARIANT_NUMBER, nullptr)) { michael@0: // If we have one number, we must have two. michael@0: if (!ParseVariant(val->Item(2), VARIANT_NUMBER, nullptr)) { michael@0: return false; michael@0: } michael@0: } michael@0: cur->mValue.SetArrayValue(val, eCSSUnit_Array); michael@0: michael@0: if (!ExpectSymbol(',', true)) { // url must not be last michael@0: return false; michael@0: } michael@0: cur->mNext = new nsCSSValueList; michael@0: cur = cur->mNext; michael@0: } michael@0: } michael@0: AppendValue(eCSSProperty_cursor, value); michael@0: return true; michael@0: } michael@0: michael@0: michael@0: bool michael@0: CSSParserImpl::ParseFont() michael@0: { michael@0: static const nsCSSProperty fontIDs[] = { michael@0: eCSSProperty_font_style, michael@0: eCSSProperty_font_variant, michael@0: eCSSProperty_font_weight michael@0: }; michael@0: michael@0: // font-variant-alternates enabled ==> layout.css.font-features.enabled is true michael@0: bool featuresEnabled = michael@0: nsCSSProps::IsEnabled(eCSSProperty_font_variant_alternates); michael@0: nsCSSValue family; michael@0: if (ParseVariant(family, VARIANT_HK, nsCSSProps::kFontKTable)) { michael@0: if (eCSSUnit_Inherit == family.GetUnit() || michael@0: eCSSUnit_Initial == family.GetUnit() || michael@0: eCSSUnit_Unset == family.GetUnit()) { michael@0: AppendValue(eCSSProperty__x_system_font, nsCSSValue(eCSSUnit_None)); michael@0: AppendValue(eCSSProperty_font_family, family); michael@0: AppendValue(eCSSProperty_font_style, family); michael@0: AppendValue(eCSSProperty_font_variant, family); michael@0: AppendValue(eCSSProperty_font_weight, family); michael@0: AppendValue(eCSSProperty_font_size, family); michael@0: AppendValue(eCSSProperty_line_height, family); michael@0: AppendValue(eCSSProperty_font_stretch, family); michael@0: AppendValue(eCSSProperty_font_size_adjust, family); michael@0: AppendValue(eCSSProperty_font_feature_settings, family); michael@0: AppendValue(eCSSProperty_font_language_override, family); michael@0: if (featuresEnabled) { michael@0: AppendValue(eCSSProperty_font_kerning, family); michael@0: AppendValue(eCSSProperty_font_synthesis, family); michael@0: AppendValue(eCSSProperty_font_variant_alternates, family); michael@0: AppendValue(eCSSProperty_font_variant_caps, family); michael@0: AppendValue(eCSSProperty_font_variant_east_asian, family); michael@0: AppendValue(eCSSProperty_font_variant_ligatures, family); michael@0: AppendValue(eCSSProperty_font_variant_numeric, family); michael@0: AppendValue(eCSSProperty_font_variant_position, family); michael@0: } michael@0: } michael@0: else { michael@0: AppendValue(eCSSProperty__x_system_font, family); michael@0: nsCSSValue systemFont(eCSSUnit_System_Font); michael@0: AppendValue(eCSSProperty_font_family, systemFont); michael@0: AppendValue(eCSSProperty_font_style, systemFont); michael@0: AppendValue(eCSSProperty_font_variant, systemFont); michael@0: AppendValue(eCSSProperty_font_weight, systemFont); michael@0: AppendValue(eCSSProperty_font_size, systemFont); michael@0: AppendValue(eCSSProperty_line_height, systemFont); michael@0: AppendValue(eCSSProperty_font_stretch, systemFont); michael@0: AppendValue(eCSSProperty_font_size_adjust, systemFont); michael@0: AppendValue(eCSSProperty_font_feature_settings, systemFont); michael@0: AppendValue(eCSSProperty_font_language_override, systemFont); michael@0: if (featuresEnabled) { michael@0: AppendValue(eCSSProperty_font_kerning, systemFont); michael@0: AppendValue(eCSSProperty_font_synthesis, systemFont); michael@0: AppendValue(eCSSProperty_font_variant_alternates, systemFont); michael@0: AppendValue(eCSSProperty_font_variant_caps, systemFont); michael@0: AppendValue(eCSSProperty_font_variant_east_asian, systemFont); michael@0: AppendValue(eCSSProperty_font_variant_ligatures, systemFont); michael@0: AppendValue(eCSSProperty_font_variant_numeric, systemFont); michael@0: AppendValue(eCSSProperty_font_variant_position, systemFont); michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: // Get optional font-style, font-variant and font-weight (in any order) michael@0: const int32_t numProps = 3; michael@0: nsCSSValue values[numProps]; michael@0: int32_t found = ParseChoice(values, fontIDs, numProps); michael@0: if (found < 0 || michael@0: eCSSUnit_Inherit == values[0].GetUnit() || michael@0: eCSSUnit_Initial == values[0].GetUnit() || michael@0: eCSSUnit_Unset == values[0].GetUnit()) { // illegal data michael@0: return false; michael@0: } michael@0: if ((found & 1) == 0) { michael@0: // Provide default font-style michael@0: values[0].SetIntValue(NS_FONT_STYLE_NORMAL, eCSSUnit_Enumerated); michael@0: } michael@0: if ((found & 2) == 0) { michael@0: // Provide default font-variant michael@0: values[1].SetIntValue(NS_FONT_VARIANT_NORMAL, eCSSUnit_Enumerated); michael@0: } michael@0: if ((found & 4) == 0) { michael@0: // Provide default font-weight michael@0: values[2].SetIntValue(NS_FONT_WEIGHT_NORMAL, eCSSUnit_Enumerated); michael@0: } michael@0: michael@0: // Get mandatory font-size michael@0: nsCSSValue size; michael@0: if (! ParseNonNegativeVariant(size, VARIANT_KEYWORD | VARIANT_LP, michael@0: nsCSSProps::kFontSizeKTable)) { michael@0: return false; michael@0: } michael@0: michael@0: // Get optional "/" line-height michael@0: nsCSSValue lineHeight; michael@0: if (ExpectSymbol('/', true)) { michael@0: if (! ParseNonNegativeVariant(lineHeight, michael@0: VARIANT_NUMBER | VARIANT_LP | VARIANT_NORMAL, michael@0: nullptr)) { michael@0: return false; michael@0: } michael@0: } michael@0: else { michael@0: lineHeight.SetNormalValue(); michael@0: } michael@0: michael@0: // Get final mandatory font-family michael@0: nsAutoParseCompoundProperty compound(this); michael@0: if (ParseFamily(family)) { michael@0: if (eCSSUnit_Inherit != family.GetUnit() && michael@0: eCSSUnit_Initial != family.GetUnit() && michael@0: eCSSUnit_Unset != family.GetUnit()) { michael@0: AppendValue(eCSSProperty__x_system_font, nsCSSValue(eCSSUnit_None)); michael@0: AppendValue(eCSSProperty_font_family, family); michael@0: AppendValue(eCSSProperty_font_style, values[0]); michael@0: AppendValue(eCSSProperty_font_variant, values[1]); michael@0: AppendValue(eCSSProperty_font_weight, values[2]); michael@0: AppendValue(eCSSProperty_font_size, size); michael@0: AppendValue(eCSSProperty_line_height, lineHeight); michael@0: AppendValue(eCSSProperty_font_stretch, michael@0: nsCSSValue(NS_FONT_STRETCH_NORMAL, eCSSUnit_Enumerated)); michael@0: AppendValue(eCSSProperty_font_size_adjust, nsCSSValue(eCSSUnit_None)); michael@0: AppendValue(eCSSProperty_font_feature_settings, nsCSSValue(eCSSUnit_Normal)); michael@0: AppendValue(eCSSProperty_font_language_override, nsCSSValue(eCSSUnit_Normal)); michael@0: if (featuresEnabled) { michael@0: AppendValue(eCSSProperty_font_kerning, michael@0: nsCSSValue(NS_FONT_KERNING_AUTO, eCSSUnit_Enumerated)); michael@0: AppendValue(eCSSProperty_font_synthesis, michael@0: nsCSSValue(NS_FONT_SYNTHESIS_WEIGHT | NS_FONT_SYNTHESIS_STYLE, michael@0: eCSSUnit_Enumerated)); michael@0: AppendValue(eCSSProperty_font_variant_alternates, michael@0: nsCSSValue(eCSSUnit_Normal)); michael@0: AppendValue(eCSSProperty_font_variant_caps, nsCSSValue(eCSSUnit_Normal)); michael@0: AppendValue(eCSSProperty_font_variant_east_asian, michael@0: nsCSSValue(eCSSUnit_Normal)); michael@0: AppendValue(eCSSProperty_font_variant_ligatures, michael@0: nsCSSValue(eCSSUnit_Normal)); michael@0: AppendValue(eCSSProperty_font_variant_numeric, michael@0: nsCSSValue(eCSSUnit_Normal)); michael@0: AppendValue(eCSSProperty_font_variant_position, michael@0: nsCSSValue(eCSSUnit_Normal)); michael@0: } michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseFontSynthesis(nsCSSValue& aValue) michael@0: { michael@0: if (!ParseVariant(aValue, VARIANT_HK | VARIANT_NONE, michael@0: nsCSSProps::kFontSynthesisKTable)) { michael@0: return false; michael@0: } michael@0: michael@0: // first value 'none' ==> done michael@0: if (eCSSUnit_None == aValue.GetUnit() || michael@0: eCSSUnit_Initial == aValue.GetUnit() || michael@0: eCSSUnit_Inherit == aValue.GetUnit() || michael@0: eCSSUnit_Unset == aValue.GetUnit()) michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: // look for a second value michael@0: int32_t intValue = aValue.GetIntValue(); michael@0: nsCSSValue nextValue; michael@0: michael@0: if (ParseEnum(nextValue, nsCSSProps::kFontSynthesisKTable)) { michael@0: int32_t nextIntValue = nextValue.GetIntValue(); michael@0: if (nextIntValue & intValue) { michael@0: return false; michael@0: } michael@0: aValue.SetIntValue(nextIntValue | intValue, eCSSUnit_Enumerated); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // font-variant-alternates allows for a combination of multiple michael@0: // simple enumerated values and functional values. Functional values have michael@0: // parameter lists with one or more idents which are later resolved michael@0: // based on values defined in @font-feature-value rules. michael@0: // michael@0: // font-variant-alternates: swash(flowing), historical-forms, styleset(alt-g, alt-m); michael@0: // michael@0: // So for this the nsCSSValue is set to a pair value, with one michael@0: // value for a bitmask of both simple and functional property values michael@0: // and another value containing a ValuePairList with lists of idents michael@0: // for each functional property value. michael@0: // michael@0: // pairValue michael@0: // o intValue michael@0: // NS_FONT_VARIANT_ALTERNATES_SWASH | michael@0: // NS_FONT_VARIANT_ALTERNATES_STYLESET michael@0: // o valuePairList, each element with michael@0: // - intValue - indicates which alternate michael@0: // - string or valueList of strings michael@0: // michael@0: // Note: when only 'historical-forms' is specified, there are no michael@0: // functional values to store, in which case the valuePairList is a michael@0: // single element dummy list. In all other cases, the length of the michael@0: // list will match the number of functional values. michael@0: michael@0: #define MAX_ALLOWED_FEATURES 512 michael@0: michael@0: bool michael@0: CSSParserImpl::ParseSingleAlternate(int32_t& aWhichFeature, michael@0: nsCSSValue& aValue) michael@0: { michael@0: if (!GetToken(true)) { michael@0: return false; michael@0: } michael@0: michael@0: bool isIdent = (mToken.mType == eCSSToken_Ident); michael@0: if (mToken.mType != eCSSToken_Function && !isIdent) { michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: michael@0: // ident ==> simple enumerated prop val (e.g. historical-forms) michael@0: // function ==> e.g. swash(flowing) styleset(alt-g, alt-m) michael@0: michael@0: nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent); michael@0: if (!(eCSSKeyword_UNKNOWN < keyword && michael@0: nsCSSProps::FindKeyword(keyword, michael@0: (isIdent ? michael@0: nsCSSProps::kFontVariantAlternatesKTable : michael@0: nsCSSProps::kFontVariantAlternatesFuncsKTable), michael@0: aWhichFeature))) michael@0: { michael@0: // failed, pop token michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: michael@0: if (isIdent) { michael@0: aValue.SetIntValue(aWhichFeature, eCSSUnit_Enumerated); michael@0: return true; michael@0: } michael@0: michael@0: uint16_t maxElems = 1; michael@0: if (keyword == eCSSKeyword_styleset || michael@0: keyword == eCSSKeyword_character_variant) { michael@0: maxElems = MAX_ALLOWED_FEATURES; michael@0: } michael@0: return ParseFunction(keyword, nullptr, VARIANT_IDENTIFIER, michael@0: 1, maxElems, aValue); michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseFontVariantAlternates(nsCSSValue& aValue) michael@0: { michael@0: if (ParseVariant(aValue, VARIANT_INHERIT | VARIANT_NORMAL, nullptr)) { michael@0: return true; michael@0: } michael@0: michael@0: // iterate through parameters michael@0: nsCSSValue listValue; michael@0: int32_t feature, featureFlags = 0; michael@0: michael@0: // if no functional values, this may be a list with a single, unused element michael@0: listValue.SetListValue(); michael@0: michael@0: nsCSSValueList* list = nullptr; michael@0: nsCSSValue value; michael@0: while (ParseSingleAlternate(feature, value)) { michael@0: michael@0: // check to make sure value not already set michael@0: if (feature == 0 || michael@0: feature & featureFlags) { michael@0: return false; michael@0: } michael@0: michael@0: featureFlags |= feature; michael@0: michael@0: // if function, need to add to the list of functions michael@0: if (value.GetUnit() == eCSSUnit_Function) { michael@0: if (!list) { michael@0: list = listValue.GetListValue(); michael@0: } else { michael@0: list->mNext = new nsCSSValueList; michael@0: list = list->mNext; michael@0: } michael@0: list->mValue = value; michael@0: } michael@0: } michael@0: michael@0: if (featureFlags == 0) { michael@0: // ParseSingleAlternate failed the first time through the loop. michael@0: return false; michael@0: } michael@0: michael@0: nsCSSValue featureValue; michael@0: featureValue.SetIntValue(featureFlags, eCSSUnit_Enumerated); michael@0: aValue.SetPairValue(featureValue, listValue); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // aMasks - array of masks for mutually-exclusive property values, michael@0: // e.g. proportial-nums, tabular-nums michael@0: michael@0: bool michael@0: CSSParserImpl::ParseBitmaskValues(nsCSSValue& aValue, michael@0: const KTableValue aKeywordTable[], michael@0: const int32_t aMasks[]) michael@0: { michael@0: // Parse at least one keyword michael@0: if (!ParseEnum(aValue, aKeywordTable)) { michael@0: return false; michael@0: } michael@0: michael@0: // look for more values michael@0: nsCSSValue nextValue; michael@0: int32_t mergedValue = aValue.GetIntValue(); michael@0: michael@0: while (ParseEnum(nextValue, aKeywordTable)) michael@0: { michael@0: int32_t nextIntValue = nextValue.GetIntValue(); michael@0: michael@0: // check to make sure value not already set michael@0: if (nextIntValue & mergedValue) { michael@0: return false; michael@0: } michael@0: michael@0: const int32_t *m = aMasks; michael@0: int32_t c = 0; michael@0: michael@0: while (*m != MASK_END_VALUE) { michael@0: if (*m & nextIntValue) { michael@0: c = mergedValue & *m; michael@0: break; michael@0: } michael@0: m++; michael@0: } michael@0: michael@0: if (c) { michael@0: return false; michael@0: } michael@0: michael@0: mergedValue |= nextIntValue; michael@0: } michael@0: michael@0: aValue.SetIntValue(mergedValue, eCSSUnit_Enumerated); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static const int32_t maskEastAsian[] = { michael@0: NS_FONT_VARIANT_EAST_ASIAN_VARIANT_MASK, michael@0: NS_FONT_VARIANT_EAST_ASIAN_WIDTH_MASK, michael@0: MASK_END_VALUE michael@0: }; michael@0: michael@0: bool michael@0: CSSParserImpl::ParseFontVariantEastAsian(nsCSSValue& aValue) michael@0: { michael@0: if (ParseVariant(aValue, VARIANT_INHERIT | VARIANT_NORMAL, nullptr)) { michael@0: return true; michael@0: } michael@0: michael@0: NS_ASSERTION(maskEastAsian[ArrayLength(maskEastAsian) - 1] == michael@0: MASK_END_VALUE, michael@0: "incorrectly terminated array"); michael@0: michael@0: return ParseBitmaskValues(aValue, nsCSSProps::kFontVariantEastAsianKTable, michael@0: maskEastAsian); michael@0: } michael@0: michael@0: static const int32_t maskLigatures[] = { michael@0: NS_FONT_VARIANT_LIGATURES_COMMON_MASK, michael@0: NS_FONT_VARIANT_LIGATURES_DISCRETIONARY_MASK, michael@0: NS_FONT_VARIANT_LIGATURES_HISTORICAL_MASK, michael@0: NS_FONT_VARIANT_LIGATURES_CONTEXTUAL_MASK, michael@0: MASK_END_VALUE michael@0: }; michael@0: michael@0: bool michael@0: CSSParserImpl::ParseFontVariantLigatures(nsCSSValue& aValue) michael@0: { michael@0: if (ParseVariant(aValue, VARIANT_INHERIT | VARIANT_NORMAL, nullptr)) { michael@0: return true; michael@0: } michael@0: michael@0: NS_ASSERTION(maskLigatures[ArrayLength(maskLigatures) - 1] == michael@0: MASK_END_VALUE, michael@0: "incorrectly terminated array"); michael@0: michael@0: bool parsed = michael@0: ParseBitmaskValues(aValue, nsCSSProps::kFontVariantLigaturesKTable, michael@0: maskLigatures); michael@0: michael@0: // if none value included, no other values are possible michael@0: if (parsed && eCSSUnit_Enumerated == aValue.GetUnit()) { michael@0: int32_t val = aValue.GetIntValue(); michael@0: if ((val & NS_FONT_VARIANT_LIGATURES_NONE) && michael@0: (val & ~int32_t(NS_FONT_VARIANT_LIGATURES_NONE))) { michael@0: parsed = false; michael@0: } michael@0: } michael@0: michael@0: return parsed; michael@0: } michael@0: michael@0: static const int32_t maskNumeric[] = { michael@0: NS_FONT_VARIANT_NUMERIC_FIGURE_MASK, michael@0: NS_FONT_VARIANT_NUMERIC_SPACING_MASK, michael@0: NS_FONT_VARIANT_NUMERIC_FRACTION_MASK, michael@0: MASK_END_VALUE michael@0: }; michael@0: michael@0: bool michael@0: CSSParserImpl::ParseFontVariantNumeric(nsCSSValue& aValue) michael@0: { michael@0: if (ParseVariant(aValue, VARIANT_INHERIT | VARIANT_NORMAL, nullptr)) { michael@0: return true; michael@0: } michael@0: michael@0: NS_ASSERTION(maskNumeric[ArrayLength(maskNumeric) - 1] == michael@0: MASK_END_VALUE, michael@0: "incorrectly terminated array"); michael@0: michael@0: return ParseBitmaskValues(aValue, nsCSSProps::kFontVariantNumericKTable, michael@0: maskNumeric); michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseFontWeight(nsCSSValue& aValue) michael@0: { michael@0: if (ParseVariant(aValue, VARIANT_HKI | VARIANT_SYSFONT, michael@0: nsCSSProps::kFontWeightKTable)) { michael@0: if (eCSSUnit_Integer == aValue.GetUnit()) { // ensure unit value michael@0: int32_t intValue = aValue.GetIntValue(); michael@0: if ((100 <= intValue) && michael@0: (intValue <= 900) && michael@0: (0 == (intValue % 100))) { michael@0: return true; michael@0: } else { michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseOneFamily(nsAString& aFamily, bool& aOneKeyword) michael@0: { michael@0: if (!GetToken(true)) michael@0: return false; michael@0: michael@0: nsCSSToken* tk = &mToken; michael@0: michael@0: aOneKeyword = false; michael@0: if (eCSSToken_Ident == tk->mType) { michael@0: aOneKeyword = true; michael@0: aFamily.Append(tk->mIdent); michael@0: for (;;) { michael@0: if (!GetToken(false)) michael@0: break; michael@0: michael@0: if (eCSSToken_Ident == tk->mType) { michael@0: aOneKeyword = false; michael@0: // We had at least another keyword before. michael@0: // "If a sequence of identifiers is given as a font family name, michael@0: // the computed value is the name converted to a string by joining michael@0: // all the identifiers in the sequence by single spaces." michael@0: // -- CSS 2.1, section 15.3 michael@0: // Whitespace tokens do not actually matter, michael@0: // identifier tokens can be separated by comments. michael@0: aFamily.Append(char16_t(' ')); michael@0: aFamily.Append(tk->mIdent); michael@0: } else if (eCSSToken_Whitespace != tk->mType) { michael@0: UngetToken(); michael@0: break; michael@0: } michael@0: } michael@0: return true; michael@0: michael@0: } else if (eCSSToken_String == tk->mType) { michael@0: aFamily.Append(tk->mSymbol); // replace the quotes michael@0: aFamily.Append(tk->mIdent); // XXX What if it had escaped quotes? michael@0: aFamily.Append(tk->mSymbol); michael@0: return true; michael@0: michael@0: } else { michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseFamily(nsCSSValue& aValue) michael@0: { michael@0: nsAutoString family; michael@0: bool single; michael@0: michael@0: // keywords only have meaning in the first position michael@0: if (!ParseOneFamily(family, single)) michael@0: return false; michael@0: michael@0: // check for keywords, but only when keywords appear by themselves michael@0: // i.e. not in compounds such as font-family: default blah; michael@0: if (single) { michael@0: nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(family); michael@0: if (keyword == eCSSKeyword_inherit) { michael@0: aValue.SetInheritValue(); michael@0: return true; michael@0: } michael@0: // 605231 - don't parse unquoted 'default' reserved keyword michael@0: if (keyword == eCSSKeyword_default) { michael@0: return false; michael@0: } michael@0: if (keyword == eCSSKeyword_initial) { michael@0: aValue.SetInitialValue(); michael@0: return true; michael@0: } michael@0: if (keyword == eCSSKeyword_unset && michael@0: nsLayoutUtils::UnsetValueEnabled()) { michael@0: aValue.SetUnsetValue(); michael@0: return true; michael@0: } michael@0: if (keyword == eCSSKeyword__moz_use_system_font && michael@0: !IsParsingCompoundProperty()) { michael@0: aValue.SetSystemFontValue(); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: for (;;) { michael@0: if (!ExpectSymbol(',', true)) michael@0: break; michael@0: michael@0: family.Append(char16_t(',')); michael@0: michael@0: nsAutoString nextFamily; michael@0: if (!ParseOneFamily(nextFamily, single)) michael@0: return false; michael@0: michael@0: // at this point unquoted keywords are not allowed michael@0: // as font family names but can appear within names michael@0: if (single) { michael@0: nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(nextFamily); michael@0: switch (keyword) { michael@0: case eCSSKeyword_inherit: michael@0: case eCSSKeyword_initial: michael@0: case eCSSKeyword_default: michael@0: case eCSSKeyword__moz_use_system_font: michael@0: return false; michael@0: case eCSSKeyword_unset: michael@0: if (nsLayoutUtils::UnsetValueEnabled()) { michael@0: return false; michael@0: } michael@0: // fall through michael@0: default: michael@0: break; michael@0: } michael@0: } michael@0: michael@0: family.Append(nextFamily); michael@0: } michael@0: michael@0: if (family.IsEmpty()) { michael@0: return false; michael@0: } michael@0: aValue.SetStringValue(family, eCSSUnit_Families); michael@0: return true; michael@0: } michael@0: michael@0: // src: ( uri-src | local-src ) (',' ( uri-src | local-src ) )* michael@0: // uri-src: uri [ 'format(' string ( ',' string )* ')' ] michael@0: // local-src: 'local(' ( string | ident ) ')' michael@0: michael@0: bool michael@0: CSSParserImpl::ParseFontSrc(nsCSSValue& aValue) michael@0: { michael@0: // could we maybe turn nsCSSValue::Array into InfallibleTArray? michael@0: InfallibleTArray values; michael@0: nsCSSValue cur; michael@0: for (;;) { michael@0: if (!GetToken(true)) michael@0: break; michael@0: michael@0: if (mToken.mType == eCSSToken_URL) { michael@0: SetValueToURL(cur, mToken.mIdent); michael@0: values.AppendElement(cur); michael@0: if (!ParseFontSrcFormat(values)) michael@0: return false; michael@0: michael@0: } else if (mToken.mType == eCSSToken_Function && michael@0: mToken.mIdent.LowerCaseEqualsLiteral("local")) { michael@0: // css3-fonts does not specify a formal grammar for local(). michael@0: // The text permits both unquoted identifiers and quoted michael@0: // strings. We resolve this ambiguity in the spec by michael@0: // assuming that the appropriate production is a single michael@0: // , possibly surrounded by whitespace. michael@0: michael@0: nsAutoString family; michael@0: bool single; michael@0: if (!ParseOneFamily(family, single)) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: if (!ExpectSymbol(')', true)) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: michael@0: // XXX: Getting closer... michael@0: // the style parameters to the nsFont constructor are ignored, michael@0: // because it's only being used to call EnumerateFamilies michael@0: nsFont font(family, 0, 0, 0, 0, 0, 0); michael@0: ExtractFirstFamilyData dat; michael@0: michael@0: font.EnumerateFamilies(ExtractFirstFamily, (void*) &dat); michael@0: if (!dat.mGood) michael@0: return false; michael@0: michael@0: cur.SetStringValue(dat.mFamilyName, eCSSUnit_Local_Font); michael@0: values.AppendElement(cur); michael@0: } else { michael@0: // We don't know what to do with this token; unget it and error out michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: michael@0: if (!ExpectSymbol(',', true)) michael@0: break; michael@0: } michael@0: michael@0: if (values.Length() == 0) michael@0: return false; michael@0: michael@0: nsRefPtr srcVals michael@0: = nsCSSValue::Array::Create(values.Length()); michael@0: michael@0: uint32_t i; michael@0: for (i = 0; i < values.Length(); i++) michael@0: srcVals->Item(i) = values[i]; michael@0: aValue.SetArrayValue(srcVals, eCSSUnit_Array); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseFontSrcFormat(InfallibleTArray & values) michael@0: { michael@0: if (!GetToken(true)) michael@0: return true; // EOF harmless here michael@0: if (mToken.mType != eCSSToken_Function || michael@0: !mToken.mIdent.LowerCaseEqualsLiteral("format")) { michael@0: UngetToken(); michael@0: return true; michael@0: } michael@0: michael@0: do { michael@0: if (!GetToken(true)) michael@0: return false; // EOF - no need for SkipUntil michael@0: michael@0: if (mToken.mType != eCSSToken_String) { michael@0: UngetToken(); michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: michael@0: nsCSSValue cur(mToken.mIdent, eCSSUnit_Font_Format); michael@0: values.AppendElement(cur); michael@0: } while (ExpectSymbol(',', true)); michael@0: michael@0: if (!ExpectSymbol(')', true)) { michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // font-ranges: urange ( ',' urange )* michael@0: bool michael@0: CSSParserImpl::ParseFontRanges(nsCSSValue& aValue) michael@0: { michael@0: InfallibleTArray ranges; michael@0: for (;;) { michael@0: if (!GetToken(true)) michael@0: break; michael@0: michael@0: if (mToken.mType != eCSSToken_URange) { michael@0: UngetToken(); michael@0: break; michael@0: } michael@0: michael@0: // An invalid range token is a parsing error, causing the entire michael@0: // descriptor to be ignored. michael@0: if (!mToken.mIntegerValid) michael@0: return false; michael@0: michael@0: uint32_t low = mToken.mInteger; michael@0: uint32_t high = mToken.mInteger2; michael@0: michael@0: // A range that descends, or a range that is entirely outside the michael@0: // current range of Unicode (U+0-10FFFF) is ignored, but does not michael@0: // invalidate the descriptor. A range that straddles the high end michael@0: // is clipped. michael@0: if (low <= 0x10FFFF && low <= high) { michael@0: if (high > 0x10FFFF) michael@0: high = 0x10FFFF; michael@0: michael@0: ranges.AppendElement(low); michael@0: ranges.AppendElement(high); michael@0: } michael@0: if (!ExpectSymbol(',', true)) michael@0: break; michael@0: } michael@0: michael@0: if (ranges.Length() == 0) michael@0: return false; michael@0: michael@0: nsRefPtr srcVals michael@0: = nsCSSValue::Array::Create(ranges.Length()); michael@0: michael@0: for (uint32_t i = 0; i < ranges.Length(); i++) michael@0: srcVals->Item(i).SetIntValue(ranges[i], eCSSUnit_Integer); michael@0: aValue.SetArrayValue(srcVals, eCSSUnit_Array); michael@0: return true; michael@0: } michael@0: michael@0: // font-feature-settings: normal | [, ]* michael@0: // = [ | on | off ]? michael@0: michael@0: // minimum - "tagx", "tagy", "tagz" michael@0: // edge error case - "tagx" on 1, "tagx" "tagy", "tagx" -1, "tagx" big michael@0: michael@0: // pair value is always x = string, y = int michael@0: michael@0: // font feature tags must be four ASCII characters michael@0: #define FEATURE_TAG_LENGTH 4 michael@0: michael@0: static bool michael@0: ValidFontFeatureTag(const nsString& aTag) michael@0: { michael@0: if (aTag.Length() != FEATURE_TAG_LENGTH) { michael@0: return false; michael@0: } michael@0: uint32_t i; michael@0: for (i = 0; i < FEATURE_TAG_LENGTH; i++) { michael@0: uint32_t ch = aTag[i]; michael@0: if (ch < 0x20 || ch > 0x7e) { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseFontFeatureSettings(nsCSSValue& aValue) michael@0: { michael@0: if (ParseVariant(aValue, VARIANT_INHERIT | VARIANT_NORMAL, nullptr)) { michael@0: return true; michael@0: } michael@0: michael@0: nsCSSValuePairList *cur = aValue.SetPairListValue(); michael@0: for (;;) { michael@0: // feature tag michael@0: if (!GetToken(true)) { michael@0: return false; michael@0: } michael@0: michael@0: if (mToken.mType != eCSSToken_String || michael@0: !ValidFontFeatureTag(mToken.mIdent)) { michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: cur->mXValue.SetStringValue(mToken.mIdent, eCSSUnit_String); michael@0: michael@0: if (!GetToken(true)) { michael@0: cur->mYValue.SetIntValue(1, eCSSUnit_Integer); michael@0: break; michael@0: } michael@0: michael@0: // optional value or on/off keyword michael@0: if (mToken.mType == eCSSToken_Number && mToken.mIntegerValid && michael@0: mToken.mInteger >= 0) { michael@0: cur->mYValue.SetIntValue(mToken.mInteger, eCSSUnit_Integer); michael@0: } else if (mToken.mType == eCSSToken_Ident && michael@0: mToken.mIdent.LowerCaseEqualsLiteral("on")) { michael@0: cur->mYValue.SetIntValue(1, eCSSUnit_Integer); michael@0: } else if (mToken.mType == eCSSToken_Ident && michael@0: mToken.mIdent.LowerCaseEqualsLiteral("off")) { michael@0: cur->mYValue.SetIntValue(0, eCSSUnit_Integer); michael@0: } else { michael@0: // something other than value/on/off, set default value michael@0: cur->mYValue.SetIntValue(1, eCSSUnit_Integer); michael@0: UngetToken(); michael@0: } michael@0: michael@0: if (!ExpectSymbol(',', true)) { michael@0: break; michael@0: } michael@0: michael@0: cur->mNext = new nsCSSValuePairList; michael@0: cur = cur->mNext; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseListStyle() michael@0: { michael@0: // 'list-style' can accept 'none' for two different subproperties, michael@0: // 'list-style-type' and 'list-style-position'. In order to accept michael@0: // 'none' as the value of either but still allow another value for michael@0: // either, we need to ensure that the first 'none' we find gets michael@0: // allocated to a dummy property instead. michael@0: static const nsCSSProperty listStyleIDs[] = { michael@0: eCSSPropertyExtra_x_none_value, michael@0: eCSSProperty_list_style_type, michael@0: eCSSProperty_list_style_position, michael@0: eCSSProperty_list_style_image michael@0: }; michael@0: michael@0: nsCSSValue values[MOZ_ARRAY_LENGTH(listStyleIDs)]; michael@0: int32_t found = michael@0: ParseChoice(values, listStyleIDs, ArrayLength(listStyleIDs)); michael@0: if (found < 1) { michael@0: return false; michael@0: } michael@0: michael@0: if ((found & (1|2|8)) == (1|2|8)) { michael@0: if (values[0].GetUnit() == eCSSUnit_None) { michael@0: // We found a 'none' plus another value for both of michael@0: // 'list-style-type' and 'list-style-image'. This is a parse michael@0: // error, since the 'none' has to count for at least one of them. michael@0: return false; michael@0: } else { michael@0: NS_ASSERTION(found == (1|2|4|8) && values[0] == values[1] && michael@0: values[0] == values[2] && values[0] == values[3], michael@0: "should be a special value"); michael@0: } michael@0: } michael@0: michael@0: // Provide default values michael@0: if ((found & 2) == 0) { michael@0: if (found & 1) { michael@0: values[1].SetIntValue(NS_STYLE_LIST_STYLE_NONE, eCSSUnit_Enumerated); michael@0: } else { michael@0: values[1].SetIntValue(NS_STYLE_LIST_STYLE_DISC, eCSSUnit_Enumerated); michael@0: } michael@0: } michael@0: if ((found & 4) == 0) { michael@0: values[2].SetIntValue(NS_STYLE_LIST_STYLE_POSITION_OUTSIDE, michael@0: eCSSUnit_Enumerated); michael@0: } michael@0: if ((found & 8) == 0) { michael@0: values[3].SetNoneValue(); michael@0: } michael@0: michael@0: // Start at 1 to avoid appending fake value. michael@0: for (uint32_t index = 1; index < ArrayLength(listStyleIDs); ++index) { michael@0: AppendValue(listStyleIDs[index], values[index]); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseMargin() michael@0: { michael@0: static const nsCSSProperty kMarginSideIDs[] = { michael@0: eCSSProperty_margin_top, michael@0: eCSSProperty_margin_right_value, michael@0: eCSSProperty_margin_bottom, michael@0: eCSSProperty_margin_left_value michael@0: }; michael@0: static const nsCSSProperty kMarginSources[] = { michael@0: eCSSProperty_margin_left_ltr_source, michael@0: eCSSProperty_margin_left_rtl_source, michael@0: eCSSProperty_margin_right_ltr_source, michael@0: eCSSProperty_margin_right_rtl_source, michael@0: eCSSProperty_UNKNOWN michael@0: }; michael@0: michael@0: // do this now, in case 4 values weren't specified michael@0: InitBoxPropsAsPhysical(kMarginSources); michael@0: return ParseBoxProperties(kMarginSideIDs); michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseMarks(nsCSSValue& aValue) michael@0: { michael@0: if (ParseVariant(aValue, VARIANT_HK, nsCSSProps::kPageMarksKTable)) { michael@0: if (eCSSUnit_Enumerated == aValue.GetUnit()) { michael@0: if (NS_STYLE_PAGE_MARKS_NONE != aValue.GetIntValue() && michael@0: false == CheckEndProperty()) { michael@0: nsCSSValue second; michael@0: if (ParseEnum(second, nsCSSProps::kPageMarksKTable)) { michael@0: // 'none' keyword in conjuction with others is not allowed michael@0: if (NS_STYLE_PAGE_MARKS_NONE != second.GetIntValue()) { michael@0: aValue.SetIntValue(aValue.GetIntValue() | second.GetIntValue(), michael@0: eCSSUnit_Enumerated); michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseOutline() michael@0: { michael@0: const int32_t numProps = 3; michael@0: static const nsCSSProperty kOutlineIDs[] = { michael@0: eCSSProperty_outline_color, michael@0: eCSSProperty_outline_style, michael@0: eCSSProperty_outline_width michael@0: }; michael@0: michael@0: nsCSSValue values[numProps]; michael@0: int32_t found = ParseChoice(values, kOutlineIDs, numProps); michael@0: if (found < 1) { michael@0: return false; michael@0: } michael@0: michael@0: // Provide default values michael@0: if ((found & 1) == 0) { // Provide default outline-color michael@0: values[0].SetIntValue(NS_STYLE_COLOR_MOZ_USE_TEXT_COLOR, eCSSUnit_Enumerated); michael@0: } michael@0: if ((found & 2) == 0) { // Provide default outline-style michael@0: values[1].SetIntValue(NS_STYLE_BORDER_STYLE_NONE, eCSSUnit_Enumerated); michael@0: } michael@0: if ((found & 4) == 0) { // Provide default outline-width michael@0: values[2].SetIntValue(NS_STYLE_BORDER_WIDTH_MEDIUM, eCSSUnit_Enumerated); michael@0: } michael@0: michael@0: int32_t index; michael@0: for (index = 0; index < numProps; index++) { michael@0: AppendValue(kOutlineIDs[index], values[index]); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseOverflow() michael@0: { michael@0: nsCSSValue overflow; michael@0: if (!ParseVariant(overflow, VARIANT_HK, nsCSSProps::kOverflowKTable)) { michael@0: return false; michael@0: } michael@0: michael@0: nsCSSValue overflowX(overflow); michael@0: nsCSSValue overflowY(overflow); michael@0: if (eCSSUnit_Enumerated == overflow.GetUnit()) michael@0: switch(overflow.GetIntValue()) { michael@0: case NS_STYLE_OVERFLOW_SCROLLBARS_HORIZONTAL: michael@0: overflowX.SetIntValue(NS_STYLE_OVERFLOW_SCROLL, eCSSUnit_Enumerated); michael@0: overflowY.SetIntValue(NS_STYLE_OVERFLOW_HIDDEN, eCSSUnit_Enumerated); michael@0: break; michael@0: case NS_STYLE_OVERFLOW_SCROLLBARS_VERTICAL: michael@0: overflowX.SetIntValue(NS_STYLE_OVERFLOW_HIDDEN, eCSSUnit_Enumerated); michael@0: overflowY.SetIntValue(NS_STYLE_OVERFLOW_SCROLL, eCSSUnit_Enumerated); michael@0: break; michael@0: } michael@0: AppendValue(eCSSProperty_overflow_x, overflowX); michael@0: AppendValue(eCSSProperty_overflow_y, overflowY); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParsePadding() michael@0: { michael@0: static const nsCSSProperty kPaddingSideIDs[] = { michael@0: eCSSProperty_padding_top, michael@0: eCSSProperty_padding_right_value, michael@0: eCSSProperty_padding_bottom, michael@0: eCSSProperty_padding_left_value michael@0: }; michael@0: static const nsCSSProperty kPaddingSources[] = { michael@0: eCSSProperty_padding_left_ltr_source, michael@0: eCSSProperty_padding_left_rtl_source, michael@0: eCSSProperty_padding_right_ltr_source, michael@0: eCSSProperty_padding_right_rtl_source, michael@0: eCSSProperty_UNKNOWN michael@0: }; michael@0: michael@0: // do this now, in case 4 values weren't specified michael@0: InitBoxPropsAsPhysical(kPaddingSources); michael@0: return ParseBoxProperties(kPaddingSideIDs); michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseQuotes() michael@0: { michael@0: nsCSSValue value; michael@0: if (!ParseVariant(value, VARIANT_HOS, nullptr)) { michael@0: return false; michael@0: } michael@0: if (value.GetUnit() == eCSSUnit_String) { michael@0: nsCSSValue open = value; michael@0: nsCSSValuePairList* quotes = value.SetPairListValue(); michael@0: for (;;) { michael@0: quotes->mXValue = open; michael@0: // get mandatory close michael@0: if (!ParseVariant(quotes->mYValue, VARIANT_STRING, nullptr)) { michael@0: return false; michael@0: } michael@0: // look for another open michael@0: if (!ParseVariant(open, VARIANT_STRING, nullptr)) { michael@0: break; michael@0: } michael@0: quotes->mNext = new nsCSSValuePairList; michael@0: quotes = quotes->mNext; michael@0: } michael@0: } michael@0: AppendValue(eCSSProperty_quotes, value); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseSize() michael@0: { michael@0: nsCSSValue width, height; michael@0: if (!ParseVariant(width, VARIANT_AHKL, nsCSSProps::kPageSizeKTable)) { michael@0: return false; michael@0: } michael@0: if (width.IsLengthUnit()) { michael@0: ParseVariant(height, VARIANT_LENGTH, nullptr); michael@0: } michael@0: michael@0: if (width == height || height.GetUnit() == eCSSUnit_Null) { michael@0: AppendValue(eCSSProperty_size, width); michael@0: } else { michael@0: nsCSSValue pair; michael@0: pair.SetPairValue(width, height); michael@0: AppendValue(eCSSProperty_size, pair); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseTextDecoration() michael@0: { michael@0: enum { michael@0: eDecorationNone = NS_STYLE_TEXT_DECORATION_LINE_NONE, michael@0: eDecorationUnderline = NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, michael@0: eDecorationOverline = NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, michael@0: eDecorationLineThrough = NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH, michael@0: eDecorationBlink = NS_STYLE_TEXT_DECORATION_LINE_BLINK, michael@0: eDecorationPrefAnchors = NS_STYLE_TEXT_DECORATION_LINE_PREF_ANCHORS michael@0: }; michael@0: static_assert((eDecorationNone ^ eDecorationUnderline ^ michael@0: eDecorationOverline ^ eDecorationLineThrough ^ michael@0: eDecorationBlink ^ eDecorationPrefAnchors) == michael@0: (eDecorationNone | eDecorationUnderline | michael@0: eDecorationOverline | eDecorationLineThrough | michael@0: eDecorationBlink | eDecorationPrefAnchors), michael@0: "text decoration constants need to be bitmasks"); michael@0: michael@0: static const KTableValue kTextDecorationKTable[] = { michael@0: eCSSKeyword_none, eDecorationNone, michael@0: eCSSKeyword_underline, eDecorationUnderline, michael@0: eCSSKeyword_overline, eDecorationOverline, michael@0: eCSSKeyword_line_through, eDecorationLineThrough, michael@0: eCSSKeyword_blink, eDecorationBlink, michael@0: eCSSKeyword__moz_anchor_decoration, eDecorationPrefAnchors, michael@0: eCSSKeyword_UNKNOWN,-1 michael@0: }; michael@0: michael@0: nsCSSValue value; michael@0: if (!ParseVariant(value, VARIANT_HK, kTextDecorationKTable)) { michael@0: return false; michael@0: } michael@0: michael@0: nsCSSValue line, style, color; michael@0: switch (value.GetUnit()) { michael@0: case eCSSUnit_Enumerated: { michael@0: // We shouldn't accept decoration line style and color via michael@0: // text-decoration. michael@0: color.SetIntValue(NS_STYLE_COLOR_MOZ_USE_TEXT_COLOR, michael@0: eCSSUnit_Enumerated); michael@0: style.SetIntValue(NS_STYLE_TEXT_DECORATION_STYLE_SOLID, michael@0: eCSSUnit_Enumerated); michael@0: michael@0: int32_t intValue = value.GetIntValue(); michael@0: if (intValue == eDecorationNone) { michael@0: line.SetIntValue(NS_STYLE_TEXT_DECORATION_LINE_NONE, michael@0: eCSSUnit_Enumerated); michael@0: break; michael@0: } michael@0: michael@0: // look for more keywords michael@0: nsCSSValue keyword; michael@0: int32_t index; michael@0: for (index = 0; index < 3; index++) { michael@0: if (!ParseEnum(keyword, kTextDecorationKTable)) { michael@0: break; michael@0: } michael@0: int32_t newValue = keyword.GetIntValue(); michael@0: if (newValue == eDecorationNone || newValue & intValue) { michael@0: // 'none' keyword in conjuction with others is not allowed, and michael@0: // duplicate keyword is not allowed. michael@0: return false; michael@0: } michael@0: intValue |= newValue; michael@0: } michael@0: michael@0: line.SetIntValue(intValue, eCSSUnit_Enumerated); michael@0: break; michael@0: } michael@0: default: michael@0: line = color = style = value; michael@0: break; michael@0: } michael@0: michael@0: AppendValue(eCSSProperty_text_decoration_line, line); michael@0: AppendValue(eCSSProperty_text_decoration_color, color); michael@0: AppendValue(eCSSProperty_text_decoration_style, style); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseTextAlign(nsCSSValue& aValue, const KTableValue aTable[]) michael@0: { michael@0: if (ParseVariant(aValue, VARIANT_INHERIT, nullptr)) { michael@0: // 'inherit', 'initial' and 'unset' must be alone michael@0: return true; michael@0: } michael@0: michael@0: nsCSSValue left; michael@0: if (!ParseVariant(left, VARIANT_KEYWORD, aTable)) { michael@0: return false; michael@0: } michael@0: michael@0: if (!nsLayoutUtils::IsTextAlignTrueValueEnabled()) { michael@0: aValue = left; michael@0: return true; michael@0: } michael@0: michael@0: nsCSSValue right; michael@0: if (ParseVariant(right, VARIANT_KEYWORD, aTable)) { michael@0: // 'true' must be combined with some other value than 'true'. michael@0: if (left.GetIntValue() == NS_STYLE_TEXT_ALIGN_TRUE && michael@0: right.GetIntValue() == NS_STYLE_TEXT_ALIGN_TRUE) { michael@0: return false; michael@0: } michael@0: aValue.SetPairValue(left, right); michael@0: } else { michael@0: // Single value 'true' is not allowed. michael@0: if (left.GetIntValue() == NS_STYLE_TEXT_ALIGN_TRUE) { michael@0: return false; michael@0: } michael@0: aValue = left; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseTextAlign(nsCSSValue& aValue) michael@0: { michael@0: return ParseTextAlign(aValue, nsCSSProps::kTextAlignKTable); michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseTextAlignLast(nsCSSValue& aValue) michael@0: { michael@0: return ParseTextAlign(aValue, nsCSSProps::kTextAlignLastKTable); michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseTextDecorationLine(nsCSSValue& aValue) michael@0: { michael@0: if (ParseVariant(aValue, VARIANT_HK, nsCSSProps::kTextDecorationLineKTable)) { michael@0: if (eCSSUnit_Enumerated == aValue.GetUnit()) { michael@0: int32_t intValue = aValue.GetIntValue(); michael@0: if (intValue != NS_STYLE_TEXT_DECORATION_LINE_NONE) { michael@0: // look for more keywords michael@0: nsCSSValue keyword; michael@0: int32_t index; michael@0: for (index = 0; index < 3; index++) { michael@0: if (ParseEnum(keyword, nsCSSProps::kTextDecorationLineKTable)) { michael@0: int32_t newValue = keyword.GetIntValue(); michael@0: if (newValue == NS_STYLE_TEXT_DECORATION_LINE_NONE || michael@0: newValue & intValue) { michael@0: // 'none' keyword in conjuction with others is not allowed, and michael@0: // duplicate keyword is not allowed. michael@0: return false; michael@0: } michael@0: intValue |= newValue; michael@0: } michael@0: else { michael@0: break; michael@0: } michael@0: } michael@0: aValue.SetIntValue(intValue, eCSSUnit_Enumerated); michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseTextOverflow(nsCSSValue& aValue) michael@0: { michael@0: if (ParseVariant(aValue, VARIANT_INHERIT, nullptr)) { michael@0: // 'inherit', 'initial' and 'unset' must be alone michael@0: return true; michael@0: } michael@0: michael@0: nsCSSValue left; michael@0: if (!ParseVariant(left, VARIANT_KEYWORD | VARIANT_STRING, michael@0: nsCSSProps::kTextOverflowKTable)) michael@0: return false; michael@0: michael@0: nsCSSValue right; michael@0: if (ParseVariant(right, VARIANT_KEYWORD | VARIANT_STRING, michael@0: nsCSSProps::kTextOverflowKTable)) michael@0: aValue.SetPairValue(left, right); michael@0: else { michael@0: aValue = left; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseTouchAction(nsCSSValue& aValue) michael@0: { michael@0: // Avaliable values of property touch-action: michael@0: // auto | none | [pan-x || pan-y] | manipulation michael@0: michael@0: if (!ParseVariant(aValue, VARIANT_HK, nsCSSProps::kTouchActionKTable)) { michael@0: return false; michael@0: } michael@0: michael@0: // Auto and None keywords aren't allowed in conjunction with others. michael@0: // Also inherit, initial and unset values are available. michael@0: if (eCSSUnit_Enumerated != aValue.GetUnit()) { michael@0: return true; michael@0: } michael@0: michael@0: int32_t intValue = aValue.GetIntValue(); michael@0: nsCSSValue nextValue; michael@0: if (ParseEnum(nextValue, nsCSSProps::kTouchActionKTable)) { michael@0: int32_t nextIntValue = nextValue.GetIntValue(); michael@0: michael@0: // duplicates aren't allowed. michael@0: if (nextIntValue & intValue) { michael@0: return false; michael@0: } michael@0: michael@0: // Auto and None and Manipulation is not allowed in conjunction with others. michael@0: if ((intValue | nextIntValue) & (NS_STYLE_TOUCH_ACTION_NONE | michael@0: NS_STYLE_TOUCH_ACTION_AUTO | michael@0: NS_STYLE_TOUCH_ACTION_MANIPULATION)) { michael@0: return false; michael@0: } michael@0: michael@0: aValue.SetIntValue(nextIntValue | intValue, eCSSUnit_Enumerated); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseTextCombineUpright(nsCSSValue& aValue) michael@0: { michael@0: if (!ParseVariant(aValue, VARIANT_HK, michael@0: nsCSSProps::kTextCombineUprightKTable)) { michael@0: return false; michael@0: } michael@0: michael@0: // if 'digits', need to check for an explicit number [2, 3, 4] michael@0: if (eCSSUnit_Enumerated == aValue.GetUnit() && michael@0: aValue.GetIntValue() == NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_2) { michael@0: if (!GetToken(true)) { michael@0: return true; michael@0: } michael@0: if (mToken.mType == eCSSToken_Number && mToken.mIntegerValid) { michael@0: switch (mToken.mInteger) { michael@0: case 2: // already set, nothing to do michael@0: break; michael@0: case 3: michael@0: aValue.SetIntValue(NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_3, michael@0: eCSSUnit_Enumerated); michael@0: break; michael@0: case 4: michael@0: aValue.SetIntValue(NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_4, michael@0: eCSSUnit_Enumerated); michael@0: break; michael@0: default: michael@0: // invalid digits value michael@0: return false; michael@0: } michael@0: } else { michael@0: UngetToken(); michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////// michael@0: // transform Parsing Implementation michael@0: michael@0: /* Reads a function list of arguments and consumes the closing parenthesis. michael@0: * Do not call this function directly; it's meant to be called from michael@0: * ParseFunction. michael@0: */ michael@0: bool michael@0: CSSParserImpl::ParseFunctionInternals(const int32_t aVariantMask[], michael@0: int32_t aVariantMaskAll, michael@0: uint16_t aMinElems, michael@0: uint16_t aMaxElems, michael@0: InfallibleTArray &aOutput) michael@0: { michael@0: NS_ASSERTION((aVariantMask && !aVariantMaskAll) || michael@0: (!aVariantMask && aVariantMaskAll), michael@0: "only one of the two variant mask parameters can be set"); michael@0: michael@0: for (uint16_t index = 0; index < aMaxElems; ++index) { michael@0: nsCSSValue newValue; michael@0: int32_t m = aVariantMaskAll ? aVariantMaskAll : aVariantMask[index]; michael@0: if (!ParseVariant(newValue, m, nullptr)) { michael@0: break; michael@0: } michael@0: michael@0: aOutput.AppendElement(newValue); michael@0: michael@0: if (ExpectSymbol(',', true)) { michael@0: // Move on to the next argument if we see a comma. michael@0: continue; michael@0: } michael@0: michael@0: if (ExpectSymbol(')', true)) { michael@0: // Make sure we've read enough symbols if we see a closing parenthesis. michael@0: return (index + 1) >= aMinElems; michael@0: } michael@0: michael@0: // Only a comma or a closing parenthesis is valid after an argument. michael@0: break; michael@0: } michael@0: michael@0: // If we're here, we've hit an error without seeing a closing parenthesis or michael@0: // we've read too many elements without seeing a closing parenthesis. michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: michael@0: /* Parses a function [ input of the form (a [, b]*) ] and stores it michael@0: * as an nsCSSValue that holds a function of the form michael@0: * function-name arg1 arg2 ... argN michael@0: * michael@0: * On error, the return value is false. michael@0: * michael@0: * @param aFunction The name of the function that we're reading. michael@0: * @param aAllowedTypes An array of values corresponding to the legal michael@0: * types for each element in the function. The zeroth element in the michael@0: * array corresponds to the first function parameter, etc. The length michael@0: * of this array _must_ be greater than or equal to aMaxElems or the michael@0: * behavior is undefined. If not null, aAllowTypesAll must be 0. michael@0: * @param aAllowedTypesAll If set, every element tested for these types michael@0: * @param aMinElems Minimum number of elements to read. Reading fewer than michael@0: * this many elements will result in the function failing. michael@0: * @param aMaxElems Maximum number of elements to read. Reading more than michael@0: * this many elements will result in the function failing. michael@0: * @param aValue (out) The value that was parsed. michael@0: */ michael@0: bool michael@0: CSSParserImpl::ParseFunction(nsCSSKeyword aFunction, michael@0: const int32_t aAllowedTypes[], michael@0: int32_t aAllowedTypesAll, michael@0: uint16_t aMinElems, uint16_t aMaxElems, michael@0: nsCSSValue &aValue) michael@0: { michael@0: NS_ASSERTION((aAllowedTypes && !aAllowedTypesAll) || michael@0: (!aAllowedTypes && aAllowedTypesAll), michael@0: "only one of the two allowed type parameter can be set"); michael@0: typedef InfallibleTArray::size_type arrlen_t; michael@0: michael@0: /* 2^16 - 2, so that if we have 2^16 - 2 transforms, we have 2^16 - 1 michael@0: * elements stored in the the nsCSSValue::Array. michael@0: */ michael@0: static const arrlen_t MAX_ALLOWED_ELEMS = 0xFFFE; michael@0: michael@0: /* Read in a list of values as an array, failing if we can't or if michael@0: * it's out of bounds. michael@0: * michael@0: * We reserve 16 entries in the foundValues array in order to avoid michael@0: * having to resize the array dynamically when parsing some well-formed michael@0: * functions. The number 16 is coming from the number of arguments that michael@0: * matrix3d() accepts. michael@0: */ michael@0: AutoInfallibleTArray foundValues; michael@0: if (!ParseFunctionInternals(aAllowedTypes, aAllowedTypesAll, aMinElems, michael@0: aMaxElems, foundValues)) { michael@0: return false; michael@0: } michael@0: michael@0: /* michael@0: * In case the user has given us more than 2^16 - 2 arguments, michael@0: * we'll truncate them at 2^16 - 2 arguments. michael@0: */ michael@0: uint16_t numArgs = std::min(foundValues.Length(), MAX_ALLOWED_ELEMS); michael@0: nsRefPtr convertedArray = michael@0: aValue.InitFunction(aFunction, numArgs); michael@0: michael@0: /* Copy things over. */ michael@0: for (uint16_t index = 0; index < numArgs; ++index) michael@0: convertedArray->Item(index + 1) = foundValues[static_cast(index)]; michael@0: michael@0: /* Return it! */ michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Given a token, determines the minimum and maximum number of function michael@0: * parameters to read, along with the mask that should be used to read michael@0: * those function parameters. If the token isn't a transform function, michael@0: * returns an error. michael@0: * michael@0: * @param aToken The token identifying the function. michael@0: * @param aMinElems [out] The minimum number of elements to read. michael@0: * @param aMaxElems [out] The maximum number of elements to read michael@0: * @param aVariantMask [out] The variant mask to use during parsing michael@0: * @return Whether the information was loaded successfully. michael@0: */ michael@0: static bool GetFunctionParseInformation(nsCSSKeyword aToken, michael@0: bool aIsPrefixed, michael@0: uint16_t &aMinElems, michael@0: uint16_t &aMaxElems, michael@0: const int32_t *& aVariantMask) michael@0: { michael@0: /* These types represent the common variant masks that will be used to michael@0: * parse out the individual functions. The order in the enumeration michael@0: * must match the order in which the masks are declared. michael@0: */ michael@0: enum { eLengthPercentCalc, michael@0: eLengthCalc, michael@0: eTwoLengthPercentCalcs, michael@0: eTwoLengthPercentCalcsOneLengthCalc, michael@0: eAngle, michael@0: eTwoAngles, michael@0: eNumber, michael@0: ePositiveLength, michael@0: eTwoNumbers, michael@0: eThreeNumbers, michael@0: eThreeNumbersOneAngle, michael@0: eMatrix, michael@0: eMatrixPrefixed, michael@0: eMatrix3d, michael@0: eMatrix3dPrefixed, michael@0: eNumVariantMasks }; michael@0: static const int32_t kMaxElemsPerFunction = 16; michael@0: static const int32_t kVariantMasks[eNumVariantMasks][kMaxElemsPerFunction] = { michael@0: {VARIANT_LPCALC}, michael@0: {VARIANT_LENGTH|VARIANT_CALC}, michael@0: {VARIANT_LPCALC, VARIANT_LPCALC}, michael@0: {VARIANT_LPCALC, VARIANT_LPCALC, VARIANT_LENGTH|VARIANT_CALC}, michael@0: {VARIANT_ANGLE_OR_ZERO}, michael@0: {VARIANT_ANGLE_OR_ZERO, VARIANT_ANGLE_OR_ZERO}, michael@0: {VARIANT_NUMBER}, michael@0: {VARIANT_LENGTH|VARIANT_POSITIVE_DIMENSION}, michael@0: {VARIANT_NUMBER, VARIANT_NUMBER}, michael@0: {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER}, michael@0: {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_ANGLE_OR_ZERO}, michael@0: {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, michael@0: VARIANT_NUMBER, VARIANT_NUMBER}, michael@0: {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, michael@0: VARIANT_LPNCALC, VARIANT_LPNCALC}, michael@0: {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, michael@0: VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, michael@0: VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, michael@0: VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER}, michael@0: {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, michael@0: VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, michael@0: VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, michael@0: VARIANT_LPNCALC, VARIANT_LPNCALC, VARIANT_LNCALC, VARIANT_NUMBER}}; michael@0: michael@0: #ifdef DEBUG michael@0: static const uint8_t kVariantMaskLengths[eNumVariantMasks] = michael@0: {1, 1, 2, 3, 1, 2, 1, 1, 2, 3, 4, 6, 6, 16, 16}; michael@0: #endif michael@0: michael@0: int32_t variantIndex = eNumVariantMasks; michael@0: michael@0: switch (aToken) { michael@0: case eCSSKeyword_translatex: michael@0: case eCSSKeyword_translatey: michael@0: /* Exactly one length or percent. */ michael@0: variantIndex = eLengthPercentCalc; michael@0: aMinElems = 1U; michael@0: aMaxElems = 1U; michael@0: break; michael@0: case eCSSKeyword_translatez: michael@0: /* Exactly one length */ michael@0: variantIndex = eLengthCalc; michael@0: aMinElems = 1U; michael@0: aMaxElems = 1U; michael@0: break; michael@0: case eCSSKeyword_translate3d: michael@0: /* Exactly two lengthds or percents and a number */ michael@0: variantIndex = eTwoLengthPercentCalcsOneLengthCalc; michael@0: aMinElems = 3U; michael@0: aMaxElems = 3U; michael@0: break; michael@0: case eCSSKeyword_scalez: michael@0: case eCSSKeyword_scalex: michael@0: case eCSSKeyword_scaley: michael@0: /* Exactly one scale factor. */ michael@0: variantIndex = eNumber; michael@0: aMinElems = 1U; michael@0: aMaxElems = 1U; michael@0: break; michael@0: case eCSSKeyword_scale3d: michael@0: /* Exactly three scale factors. */ michael@0: variantIndex = eThreeNumbers; michael@0: aMinElems = 3U; michael@0: aMaxElems = 3U; michael@0: break; michael@0: case eCSSKeyword_rotatex: michael@0: case eCSSKeyword_rotatey: michael@0: case eCSSKeyword_rotate: michael@0: case eCSSKeyword_rotatez: michael@0: /* Exactly one angle. */ michael@0: variantIndex = eAngle; michael@0: aMinElems = 1U; michael@0: aMaxElems = 1U; michael@0: break; michael@0: case eCSSKeyword_rotate3d: michael@0: variantIndex = eThreeNumbersOneAngle; michael@0: aMinElems = 4U; michael@0: aMaxElems = 4U; michael@0: break; michael@0: case eCSSKeyword_translate: michael@0: /* One or two lengths or percents. */ michael@0: variantIndex = eTwoLengthPercentCalcs; michael@0: aMinElems = 1U; michael@0: aMaxElems = 2U; michael@0: break; michael@0: case eCSSKeyword_skew: michael@0: /* Exactly one or two angles. */ michael@0: variantIndex = eTwoAngles; michael@0: aMinElems = 1U; michael@0: aMaxElems = 2U; michael@0: break; michael@0: case eCSSKeyword_scale: michael@0: /* One or two scale factors. */ michael@0: variantIndex = eTwoNumbers; michael@0: aMinElems = 1U; michael@0: aMaxElems = 2U; michael@0: break; michael@0: case eCSSKeyword_skewx: michael@0: /* Exactly one angle. */ michael@0: variantIndex = eAngle; michael@0: aMinElems = 1U; michael@0: aMaxElems = 1U; michael@0: break; michael@0: case eCSSKeyword_skewy: michael@0: /* Exactly one angle. */ michael@0: variantIndex = eAngle; michael@0: aMinElems = 1U; michael@0: aMaxElems = 1U; michael@0: break; michael@0: case eCSSKeyword_matrix: michael@0: /* Six values, all numbers. */ michael@0: variantIndex = aIsPrefixed ? eMatrixPrefixed : eMatrix; michael@0: aMinElems = 6U; michael@0: aMaxElems = 6U; michael@0: break; michael@0: case eCSSKeyword_matrix3d: michael@0: /* 16 matrix values, all numbers */ michael@0: variantIndex = aIsPrefixed ? eMatrix3dPrefixed : eMatrix3d; michael@0: aMinElems = 16U; michael@0: aMaxElems = 16U; michael@0: break; michael@0: case eCSSKeyword_perspective: michael@0: /* Exactly one scale number. */ michael@0: variantIndex = ePositiveLength; michael@0: aMinElems = 1U; michael@0: aMaxElems = 1U; michael@0: break; michael@0: default: michael@0: /* Oh dear, we didn't match. Report an error. */ michael@0: return false; michael@0: } michael@0: michael@0: NS_ASSERTION(aMinElems > 0, "Didn't update minimum elements!"); michael@0: NS_ASSERTION(aMaxElems > 0, "Didn't update maximum elements!"); michael@0: NS_ASSERTION(aMinElems <= aMaxElems, "aMinElems > aMaxElems!"); michael@0: NS_ASSERTION(variantIndex >= 0, "Invalid variant mask!"); michael@0: NS_ASSERTION(variantIndex < eNumVariantMasks, "Invalid variant mask!"); michael@0: #ifdef DEBUG michael@0: NS_ASSERTION(aMaxElems <= kVariantMaskLengths[variantIndex], michael@0: "Invalid aMaxElems for this variant mask."); michael@0: #endif michael@0: michael@0: // Convert the index into a mask. michael@0: aVariantMask = kVariantMasks[variantIndex]; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool CSSParserImpl::ParseWillChange() michael@0: { michael@0: nsCSSValue listValue; michael@0: nsCSSValueList* currentListValue = listValue.SetListValue(); michael@0: bool first = true; michael@0: for (;;) { michael@0: const uint32_t variantMask = VARIANT_IDENTIFIER | michael@0: VARIANT_INHERIT | michael@0: VARIANT_NONE | michael@0: VARIANT_ALL | michael@0: VARIANT_AUTO; michael@0: nsCSSValue value; michael@0: if (!ParseVariant(value, variantMask, nullptr)) { michael@0: return false; michael@0: } michael@0: michael@0: if (value.GetUnit() == eCSSUnit_None || michael@0: value.GetUnit() == eCSSUnit_All) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: if (value.GetUnit() != eCSSUnit_Ident) { michael@0: if (first) { michael@0: AppendValue(eCSSProperty_will_change, value); michael@0: return true; michael@0: } else { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: nsString str; michael@0: value.GetStringValue(str); michael@0: if (str.LowerCaseEqualsLiteral("default")) { michael@0: return false; michael@0: } michael@0: michael@0: currentListValue->mValue = value; michael@0: michael@0: if (!ExpectSymbol(',', true)) { michael@0: break; michael@0: } michael@0: currentListValue->mNext = new nsCSSValueList; michael@0: currentListValue = currentListValue->mNext; michael@0: first = false; michael@0: } michael@0: michael@0: AppendValue(eCSSProperty_will_change, listValue); michael@0: return true; michael@0: } michael@0: michael@0: /* Reads a single transform function from the tokenizer stream, reporting an michael@0: * error if something goes wrong. michael@0: */ michael@0: bool michael@0: CSSParserImpl::ParseSingleTransform(bool aIsPrefixed, nsCSSValue& aValue) michael@0: { michael@0: if (!GetToken(true)) michael@0: return false; michael@0: michael@0: if (mToken.mType != eCSSToken_Function) { michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: michael@0: const int32_t* variantMask; michael@0: uint16_t minElems, maxElems; michael@0: nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent); michael@0: michael@0: if (!GetFunctionParseInformation(keyword, aIsPrefixed, michael@0: minElems, maxElems, variantMask)) michael@0: return false; michael@0: michael@0: return ParseFunction(keyword, variantMask, 0, minElems, maxElems, aValue); michael@0: } michael@0: michael@0: /* Parses a transform property list by continuously reading in properties michael@0: * and constructing a matrix from it. michael@0: */ michael@0: bool CSSParserImpl::ParseTransform(bool aIsPrefixed) michael@0: { michael@0: nsCSSValue value; michael@0: // 'inherit', 'initial', 'unset' and 'none' must be alone michael@0: if (!ParseVariant(value, VARIANT_INHERIT | VARIANT_NONE, nullptr)) { michael@0: nsCSSValueSharedList* list = new nsCSSValueSharedList; michael@0: value.SetSharedListValue(list); michael@0: list->mHead = new nsCSSValueList; michael@0: nsCSSValueList* cur = list->mHead; michael@0: for (;;) { michael@0: if (!ParseSingleTransform(aIsPrefixed, cur->mValue)) { michael@0: return false; michael@0: } michael@0: if (CheckEndProperty()) { michael@0: break; michael@0: } michael@0: cur->mNext = new nsCSSValueList; michael@0: cur = cur->mNext; michael@0: } michael@0: } michael@0: AppendValue(eCSSProperty_transform, value); michael@0: return true; michael@0: } michael@0: michael@0: bool CSSParserImpl::ParseTransformOrigin(bool aPerspective) michael@0: { michael@0: nsCSSValuePair position; michael@0: if (!ParseBoxPositionValues(position, true)) michael@0: return false; michael@0: michael@0: nsCSSProperty prop = eCSSProperty_transform_origin; michael@0: if (aPerspective) { michael@0: prop = eCSSProperty_perspective_origin; michael@0: } michael@0: michael@0: // Unlike many other uses of pairs, this position should always be stored michael@0: // as a pair, even if the values are the same, so it always serializes as michael@0: // a pair, and to keep the computation code simple. michael@0: if (position.mXValue.GetUnit() == eCSSUnit_Inherit || michael@0: position.mXValue.GetUnit() == eCSSUnit_Initial || michael@0: position.mXValue.GetUnit() == eCSSUnit_Unset) { michael@0: NS_ABORT_IF_FALSE(position.mXValue == position.mYValue, michael@0: "inherit/initial/unset only half?"); michael@0: AppendValue(prop, position.mXValue); michael@0: } else { michael@0: nsCSSValue value; michael@0: if (aPerspective) { michael@0: value.SetPairValue(position.mXValue, position.mYValue); michael@0: } else { michael@0: nsCSSValue depth; michael@0: if (!ParseVariant(depth, VARIANT_LENGTH | VARIANT_CALC, nullptr)) { michael@0: depth.SetFloatValue(0.0f, eCSSUnit_Pixel); michael@0: } michael@0: value.SetTripletValue(position.mXValue, position.mYValue, depth); michael@0: } michael@0: michael@0: AppendValue(prop, value); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Reads a drop-shadow value. At the moment the Filter Effects specification michael@0: * just expects one shadow item. Should this ever change to a list of shadow michael@0: * items, use ParseShadowList instead. michael@0: */ michael@0: bool michael@0: CSSParserImpl::ParseDropShadow(nsCSSValue* aValue) michael@0: { michael@0: // Use nsCSSValueList to reuse the shadow resolving code in michael@0: // nsRuleNode and nsComputedDOMStyle. michael@0: nsCSSValue shadow; michael@0: nsCSSValueList* cur = shadow.SetListValue(); michael@0: if (!ParseShadowItem(cur->mValue, false)) michael@0: return false; michael@0: michael@0: if (!ExpectSymbol(')', true)) michael@0: return false; michael@0: michael@0: nsCSSValue::Array* dropShadow = aValue->InitFunction(eCSSKeyword_drop_shadow, 1); michael@0: michael@0: // Copy things over. michael@0: dropShadow->Item(1) = shadow; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Reads a single url or filter function from the tokenizer stream, reporting an michael@0: * error if something goes wrong. michael@0: */ michael@0: bool michael@0: CSSParserImpl::ParseSingleFilter(nsCSSValue* aValue) michael@0: { michael@0: if (ParseVariant(*aValue, VARIANT_URL, nullptr)) { michael@0: return true; michael@0: } michael@0: michael@0: if (!nsLayoutUtils::CSSFiltersEnabled()) { michael@0: // With CSS Filters disabled, we should only accept an SVG reference filter. michael@0: REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURL); michael@0: return false; michael@0: } michael@0: michael@0: if (!GetToken(true)) { michael@0: REPORT_UNEXPECTED_EOF(PEFilterEOF); michael@0: return false; michael@0: } michael@0: michael@0: if (mToken.mType != eCSSToken_Function) { michael@0: REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURLOrFilterFunction); michael@0: return false; michael@0: } michael@0: michael@0: nsCSSKeyword functionName = nsCSSKeywords::LookupKeyword(mToken.mIdent); michael@0: // Parse drop-shadow independently of the other filter functions michael@0: // because of its more complex characteristics. michael@0: if (functionName == eCSSKeyword_drop_shadow) { michael@0: if (ParseDropShadow(aValue)) { michael@0: return true; michael@0: } else { michael@0: // Unrecognized filter function. michael@0: REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURLOrFilterFunction); michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // Set up the parsing rules based on the filter function. michael@0: int32_t variantMask = VARIANT_PN; michael@0: bool rejectNegativeArgument = true; michael@0: bool clampArgumentToOne = false; michael@0: switch (functionName) { michael@0: case eCSSKeyword_blur: michael@0: variantMask = VARIANT_LCALC | VARIANT_NONNEGATIVE_DIMENSION; michael@0: // VARIANT_NONNEGATIVE_DIMENSION will already reject negative lengths. michael@0: rejectNegativeArgument = false; michael@0: break; michael@0: case eCSSKeyword_brightness: michael@0: case eCSSKeyword_contrast: michael@0: case eCSSKeyword_saturate: michael@0: break; michael@0: case eCSSKeyword_grayscale: michael@0: case eCSSKeyword_invert: michael@0: case eCSSKeyword_sepia: michael@0: case eCSSKeyword_opacity: michael@0: clampArgumentToOne = true; michael@0: break; michael@0: case eCSSKeyword_hue_rotate: michael@0: variantMask = VARIANT_ANGLE; michael@0: rejectNegativeArgument = false; michael@0: break; michael@0: default: michael@0: // Unrecognized filter function. michael@0: REPORT_UNEXPECTED_TOKEN(PEExpectedNoneOrURLOrFilterFunction); michael@0: SkipUntil(')'); michael@0: return false; michael@0: } michael@0: michael@0: // Parse the function. michael@0: uint16_t minElems = 1U; michael@0: uint16_t maxElems = 1U; michael@0: uint32_t allVariants = 0; michael@0: if (!ParseFunction(functionName, &variantMask, allVariants, michael@0: minElems, maxElems, *aValue)) { michael@0: REPORT_UNEXPECTED(PEFilterFunctionArgumentsParsingError); michael@0: return false; michael@0: } michael@0: michael@0: // Get the first and only argument to the filter function. michael@0: NS_ABORT_IF_FALSE(aValue->GetUnit() == eCSSUnit_Function, michael@0: "expected a filter function"); michael@0: NS_ABORT_IF_FALSE(aValue->UnitHasArrayValue(), michael@0: "filter function should be an array"); michael@0: NS_ABORT_IF_FALSE(aValue->GetArrayValue()->Count() == 2, michael@0: "filter function should have exactly one argument"); michael@0: nsCSSValue& arg = aValue->GetArrayValue()->Item(1); michael@0: michael@0: if (rejectNegativeArgument && michael@0: ((arg.GetUnit() == eCSSUnit_Percent && arg.GetPercentValue() < 0.0f) || michael@0: (arg.GetUnit() == eCSSUnit_Number && arg.GetFloatValue() < 0.0f))) { michael@0: REPORT_UNEXPECTED(PEExpectedNonnegativeNP); michael@0: return false; michael@0: } michael@0: michael@0: if (clampArgumentToOne) { michael@0: if (arg.GetUnit() == eCSSUnit_Number && michael@0: arg.GetFloatValue() > 1.0f) { michael@0: arg.SetFloatValue(1.0f, arg.GetUnit()); michael@0: } else if (arg.GetUnit() == eCSSUnit_Percent && michael@0: arg.GetPercentValue() > 1.0f) { michael@0: arg.SetPercentValue(1.0f); michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Parses a filter property value by continuously reading in urls and/or filter michael@0: * functions and constructing a list. michael@0: * michael@0: * When CSS Filters are enabled, the filter property accepts one or more SVG michael@0: * reference filters and/or CSS filter functions. michael@0: * e.g. filter: url(#my-filter-1) blur(3px) url(#my-filter-2) grayscale(50%); michael@0: * michael@0: * When CSS Filters are disabled, the filter property only accepts one SVG michael@0: * reference filter. michael@0: * e.g. filter: url(#my-filter); michael@0: */ michael@0: bool michael@0: CSSParserImpl::ParseFilter() michael@0: { michael@0: nsCSSValue value; michael@0: // 'inherit', 'initial', 'unset' and 'none' must be alone michael@0: if (!ParseVariant(value, VARIANT_INHERIT | VARIANT_NONE, nullptr)) { michael@0: nsCSSValueList* cur = value.SetListValue(); michael@0: while (cur) { michael@0: if (!ParseSingleFilter(&cur->mValue)) { michael@0: return false; michael@0: } michael@0: if (CheckEndProperty()) { michael@0: break; michael@0: } michael@0: if (!nsLayoutUtils::CSSFiltersEnabled()) { michael@0: // With CSS Filters disabled, we should only accept one SVG reference michael@0: // filter. michael@0: REPORT_UNEXPECTED_TOKEN(PEExpectEndValue); michael@0: return false; michael@0: } michael@0: cur->mNext = new nsCSSValueList; michael@0: cur = cur->mNext; michael@0: } michael@0: } michael@0: AppendValue(eCSSProperty_filter, value); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseTransitionProperty() michael@0: { michael@0: nsCSSValue value; michael@0: // 'inherit', 'initial', 'unset' and 'none' must be alone michael@0: if (!ParseVariant(value, VARIANT_INHERIT | VARIANT_NONE, nullptr)) { michael@0: // Accept a list of arbitrary identifiers. They should be michael@0: // CSS properties, but we want to accept any so that we michael@0: // accept properties that we don't know about yet, e.g. michael@0: // transition-property: invalid-property, left, opacity; michael@0: nsCSSValueList* cur = value.SetListValue(); michael@0: for (;;) { michael@0: if (!ParseVariant(cur->mValue, VARIANT_IDENTIFIER | VARIANT_ALL, nullptr)) { michael@0: return false; michael@0: } michael@0: if (cur->mValue.GetUnit() == eCSSUnit_Ident) { michael@0: nsDependentString str(cur->mValue.GetStringBufferValue()); michael@0: // Exclude 'none', 'inherit', 'initial' and 'unset' according to the michael@0: // same rules as for 'counter-reset' in CSS 2.1. michael@0: if (str.LowerCaseEqualsLiteral("none") || michael@0: str.LowerCaseEqualsLiteral("inherit") || michael@0: str.LowerCaseEqualsLiteral("initial") || michael@0: (str.LowerCaseEqualsLiteral("unset") && michael@0: nsLayoutUtils::UnsetValueEnabled())) { michael@0: return false; michael@0: } michael@0: } michael@0: if (!ExpectSymbol(',', true)) { michael@0: break; michael@0: } michael@0: cur->mNext = new nsCSSValueList; michael@0: cur = cur->mNext; michael@0: } michael@0: } michael@0: AppendValue(eCSSProperty_transition_property, value); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseTransitionTimingFunctionValues(nsCSSValue& aValue) michael@0: { michael@0: NS_ASSERTION(!mHavePushBack && michael@0: mToken.mType == eCSSToken_Function && michael@0: mToken.mIdent.LowerCaseEqualsLiteral("cubic-bezier"), michael@0: "unexpected initial state"); michael@0: michael@0: nsRefPtr val = nsCSSValue::Array::Create(4); michael@0: michael@0: float x1, x2, y1, y2; michael@0: if (!ParseTransitionTimingFunctionValueComponent(x1, ',', true) || michael@0: !ParseTransitionTimingFunctionValueComponent(y1, ',', false) || michael@0: !ParseTransitionTimingFunctionValueComponent(x2, ',', true) || michael@0: !ParseTransitionTimingFunctionValueComponent(y2, ')', false)) { michael@0: return false; michael@0: } michael@0: michael@0: val->Item(0).SetFloatValue(x1, eCSSUnit_Number); michael@0: val->Item(1).SetFloatValue(y1, eCSSUnit_Number); michael@0: val->Item(2).SetFloatValue(x2, eCSSUnit_Number); michael@0: val->Item(3).SetFloatValue(y2, eCSSUnit_Number); michael@0: michael@0: aValue.SetArrayValue(val, eCSSUnit_Cubic_Bezier); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseTransitionTimingFunctionValueComponent(float& aComponent, michael@0: char aStop, michael@0: bool aCheckRange) michael@0: { michael@0: if (!GetToken(true)) { michael@0: return false; michael@0: } michael@0: nsCSSToken* tk = &mToken; michael@0: if (tk->mType == eCSSToken_Number) { michael@0: float num = tk->mNumber; michael@0: if (aCheckRange && (num < 0.0 || num > 1.0)) { michael@0: return false; michael@0: } michael@0: aComponent = num; michael@0: if (ExpectSymbol(aStop, true)) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseTransitionStepTimingFunctionValues(nsCSSValue& aValue) michael@0: { michael@0: NS_ASSERTION(!mHavePushBack && michael@0: mToken.mType == eCSSToken_Function && michael@0: mToken.mIdent.LowerCaseEqualsLiteral("steps"), michael@0: "unexpected initial state"); michael@0: michael@0: nsRefPtr val = nsCSSValue::Array::Create(2); michael@0: michael@0: if (!ParseOneOrLargerVariant(val->Item(0), VARIANT_INTEGER, nullptr)) { michael@0: return false; michael@0: } michael@0: michael@0: int32_t type = NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_END; michael@0: if (ExpectSymbol(',', true)) { michael@0: if (!GetToken(true)) { michael@0: return false; michael@0: } michael@0: type = -1; michael@0: if (mToken.mType == eCSSToken_Ident) { michael@0: if (mToken.mIdent.LowerCaseEqualsLiteral("start")) { michael@0: type = NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_START; michael@0: } else if (mToken.mIdent.LowerCaseEqualsLiteral("end")) { michael@0: type = NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_END; michael@0: } michael@0: } michael@0: if (type == -1) { michael@0: UngetToken(); michael@0: return false; michael@0: } michael@0: } michael@0: val->Item(1).SetIntValue(type, eCSSUnit_Enumerated); michael@0: michael@0: if (!ExpectSymbol(')', true)) { michael@0: return false; michael@0: } michael@0: michael@0: aValue.SetArrayValue(val, eCSSUnit_Steps); michael@0: return true; michael@0: } michael@0: michael@0: static nsCSSValueList* michael@0: AppendValueToList(nsCSSValue& aContainer, michael@0: nsCSSValueList* aTail, michael@0: const nsCSSValue& aValue) michael@0: { michael@0: nsCSSValueList* entry; michael@0: if (aContainer.GetUnit() == eCSSUnit_Null) { michael@0: NS_ABORT_IF_FALSE(!aTail, "should not have an entry"); michael@0: entry = aContainer.SetListValue(); michael@0: } else { michael@0: NS_ABORT_IF_FALSE(!aTail->mNext, "should not have a next entry"); michael@0: NS_ABORT_IF_FALSE(aContainer.GetUnit() == eCSSUnit_List, "not a list"); michael@0: entry = new nsCSSValueList; michael@0: aTail->mNext = entry; michael@0: } michael@0: entry->mValue = aValue; michael@0: return entry; michael@0: } michael@0: michael@0: CSSParserImpl::ParseAnimationOrTransitionShorthandResult michael@0: CSSParserImpl::ParseAnimationOrTransitionShorthand( michael@0: const nsCSSProperty* aProperties, michael@0: const nsCSSValue* aInitialValues, michael@0: nsCSSValue* aValues, michael@0: size_t aNumProperties) michael@0: { michael@0: nsCSSValue tempValue; michael@0: // first see if 'inherit', 'initial' or 'unset' is specified. If one is, michael@0: // it can be the only thing specified, so don't attempt to parse any michael@0: // additional properties michael@0: if (ParseVariant(tempValue, VARIANT_INHERIT, nullptr)) { michael@0: for (uint32_t i = 0; i < aNumProperties; ++i) { michael@0: AppendValue(aProperties[i], tempValue); michael@0: } michael@0: return eParseAnimationOrTransitionShorthand_Inherit; michael@0: } michael@0: michael@0: static const size_t maxNumProperties = 7; michael@0: NS_ABORT_IF_FALSE(aNumProperties <= maxNumProperties, michael@0: "can't handle this many properties"); michael@0: nsCSSValueList *cur[maxNumProperties]; michael@0: bool parsedProperty[maxNumProperties]; michael@0: michael@0: for (size_t i = 0; i < aNumProperties; ++i) { michael@0: cur[i] = nullptr; michael@0: } michael@0: bool atEOP = false; // at end of property? michael@0: for (;;) { // loop over comma-separated transitions or animations michael@0: // whether a particular subproperty was specified for this michael@0: // transition or animation michael@0: bool haveAnyProperty = false; michael@0: for (size_t i = 0; i < aNumProperties; ++i) { michael@0: parsedProperty[i] = false; michael@0: } michael@0: for (;;) { // loop over values within a transition or animation michael@0: bool foundProperty = false; michael@0: // check to see if we're at the end of one full transition or michael@0: // animation definition (either because we hit a comma or because michael@0: // we hit the end of the property definition) michael@0: if (ExpectSymbol(',', true)) michael@0: break; michael@0: if (CheckEndProperty()) { michael@0: atEOP = true; michael@0: break; michael@0: } michael@0: michael@0: // else, try to parse the next transition or animation sub-property michael@0: for (uint32_t i = 0; !foundProperty && i < aNumProperties; ++i) { michael@0: if (!parsedProperty[i]) { michael@0: // if we haven't found this property yet, try to parse it michael@0: if (ParseSingleValueProperty(tempValue, aProperties[i])) { michael@0: parsedProperty[i] = true; michael@0: cur[i] = AppendValueToList(aValues[i], cur[i], tempValue); michael@0: foundProperty = true; michael@0: haveAnyProperty = true; michael@0: break; // out of inner loop; continue looking for next sub-property michael@0: } michael@0: } michael@0: } michael@0: if (!foundProperty) { michael@0: // We're not at a ',' or at the end of the property, but we couldn't michael@0: // parse any of the sub-properties, so the declaration is invalid. michael@0: return eParseAnimationOrTransitionShorthand_Error; michael@0: } michael@0: } michael@0: michael@0: if (!haveAnyProperty) { michael@0: // Got an empty item. michael@0: return eParseAnimationOrTransitionShorthand_Error; michael@0: } michael@0: michael@0: // We hit the end of the property or the end of one transition michael@0: // or animation definition, add its components to the list. michael@0: for (uint32_t i = 0; i < aNumProperties; ++i) { michael@0: // If all of the subproperties were not explicitly specified, fill michael@0: // in the missing ones with initial values. michael@0: if (!parsedProperty[i]) { michael@0: cur[i] = AppendValueToList(aValues[i], cur[i], aInitialValues[i]); michael@0: } michael@0: } michael@0: michael@0: if (atEOP) michael@0: break; michael@0: // else we just hit a ',' so continue parsing the next compound transition michael@0: } michael@0: michael@0: return eParseAnimationOrTransitionShorthand_Values; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseTransition() michael@0: { michael@0: static const nsCSSProperty kTransitionProperties[] = { michael@0: eCSSProperty_transition_duration, michael@0: eCSSProperty_transition_timing_function, michael@0: // Must check 'transition-delay' after 'transition-duration', since michael@0: // that's our assumption about what the spec means for the shorthand michael@0: // syntax (the first time given is the duration, and the second michael@0: // given is the delay). michael@0: eCSSProperty_transition_delay, michael@0: // Must check 'transition-property' after michael@0: // 'transition-timing-function' since 'transition-property' accepts michael@0: // any keyword. michael@0: eCSSProperty_transition_property michael@0: }; michael@0: static const uint32_t numProps = MOZ_ARRAY_LENGTH(kTransitionProperties); michael@0: // this is a shorthand property that accepts -property, -delay, michael@0: // -duration, and -timing-function with some components missing. michael@0: // there can be multiple transitions, separated with commas michael@0: michael@0: nsCSSValue initialValues[numProps]; michael@0: initialValues[0].SetFloatValue(0.0, eCSSUnit_Seconds); michael@0: initialValues[1].SetIntValue(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE, michael@0: eCSSUnit_Enumerated); michael@0: initialValues[2].SetFloatValue(0.0, eCSSUnit_Seconds); michael@0: initialValues[3].SetAllValue(); michael@0: michael@0: nsCSSValue values[numProps]; michael@0: michael@0: ParseAnimationOrTransitionShorthandResult spres = michael@0: ParseAnimationOrTransitionShorthand(kTransitionProperties, michael@0: initialValues, values, numProps); michael@0: if (spres != eParseAnimationOrTransitionShorthand_Values) { michael@0: return spres != eParseAnimationOrTransitionShorthand_Error; michael@0: } michael@0: michael@0: // Make two checks on the list for 'transition-property': michael@0: // + If there is more than one item, then none of the items can be michael@0: // 'none'. michael@0: // + None of the items can be 'inherit', 'initial' or 'unset'. michael@0: { michael@0: NS_ABORT_IF_FALSE(kTransitionProperties[3] == michael@0: eCSSProperty_transition_property, michael@0: "array index mismatch"); michael@0: nsCSSValueList *l = values[3].GetListValue(); michael@0: bool multipleItems = !!l->mNext; michael@0: do { michael@0: const nsCSSValue& val = l->mValue; michael@0: if (val.GetUnit() == eCSSUnit_None) { michael@0: if (multipleItems) { michael@0: // This is a syntax error. michael@0: return false; michael@0: } michael@0: michael@0: // Unbox a solitary 'none'. michael@0: values[3].SetNoneValue(); michael@0: break; michael@0: } michael@0: if (val.GetUnit() == eCSSUnit_Ident) { michael@0: nsDependentString str(val.GetStringBufferValue()); michael@0: if (str.EqualsLiteral("inherit") || michael@0: str.EqualsLiteral("initial") || michael@0: (str.EqualsLiteral("unset") && michael@0: nsLayoutUtils::UnsetValueEnabled())) { michael@0: return false; michael@0: } michael@0: } michael@0: } while ((l = l->mNext)); michael@0: } michael@0: michael@0: // Save all parsed transition sub-properties in mTempData michael@0: for (uint32_t i = 0; i < numProps; ++i) { michael@0: AppendValue(kTransitionProperties[i], values[i]); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseAnimation() michael@0: { michael@0: static const nsCSSProperty kAnimationProperties[] = { michael@0: eCSSProperty_animation_duration, michael@0: eCSSProperty_animation_timing_function, michael@0: // Must check 'animation-delay' after 'animation-duration', since michael@0: // that's our assumption about what the spec means for the shorthand michael@0: // syntax (the first time given is the duration, and the second michael@0: // given is the delay). michael@0: eCSSProperty_animation_delay, michael@0: eCSSProperty_animation_direction, michael@0: eCSSProperty_animation_fill_mode, michael@0: eCSSProperty_animation_iteration_count, michael@0: // Must check 'animation-name' after 'animation-timing-function', michael@0: // 'animation-direction', 'animation-fill-mode', michael@0: // 'animation-iteration-count', and 'animation-play-state' since michael@0: // 'animation-name' accepts any keyword. michael@0: eCSSProperty_animation_name michael@0: }; michael@0: static const uint32_t numProps = MOZ_ARRAY_LENGTH(kAnimationProperties); michael@0: // this is a shorthand property that accepts -property, -delay, michael@0: // -duration, and -timing-function with some components missing. michael@0: // there can be multiple animations, separated with commas michael@0: michael@0: nsCSSValue initialValues[numProps]; michael@0: initialValues[0].SetFloatValue(0.0, eCSSUnit_Seconds); michael@0: initialValues[1].SetIntValue(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE, michael@0: eCSSUnit_Enumerated); michael@0: initialValues[2].SetFloatValue(0.0, eCSSUnit_Seconds); michael@0: initialValues[3].SetIntValue(NS_STYLE_ANIMATION_DIRECTION_NORMAL, eCSSUnit_Enumerated); michael@0: initialValues[4].SetIntValue(NS_STYLE_ANIMATION_FILL_MODE_NONE, eCSSUnit_Enumerated); michael@0: initialValues[5].SetFloatValue(1.0f, eCSSUnit_Number); michael@0: initialValues[6].SetNoneValue(); michael@0: michael@0: nsCSSValue values[numProps]; michael@0: michael@0: ParseAnimationOrTransitionShorthandResult spres = michael@0: ParseAnimationOrTransitionShorthand(kAnimationProperties, michael@0: initialValues, values, numProps); michael@0: if (spres != eParseAnimationOrTransitionShorthand_Values) { michael@0: return spres != eParseAnimationOrTransitionShorthand_Error; michael@0: } michael@0: michael@0: // Save all parsed animation sub-properties in mTempData michael@0: for (uint32_t i = 0; i < numProps; ++i) { michael@0: AppendValue(kAnimationProperties[i], values[i]); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseShadowItem(nsCSSValue& aValue, bool aIsBoxShadow) michael@0: { michael@0: // A shadow list item is an array, with entries in this sequence: michael@0: enum { michael@0: IndexX, michael@0: IndexY, michael@0: IndexRadius, michael@0: IndexSpread, // only for box-shadow michael@0: IndexColor, michael@0: IndexInset // only for box-shadow michael@0: }; michael@0: michael@0: nsRefPtr val = nsCSSValue::Array::Create(6); michael@0: michael@0: if (aIsBoxShadow) { michael@0: // Optional inset keyword (ignore errors) michael@0: ParseVariant(val->Item(IndexInset), VARIANT_KEYWORD, michael@0: nsCSSProps::kBoxShadowTypeKTable); michael@0: } michael@0: michael@0: nsCSSValue xOrColor; michael@0: bool haveColor = false; michael@0: if (!ParseVariant(xOrColor, VARIANT_COLOR | VARIANT_LENGTH | VARIANT_CALC, michael@0: nullptr)) { michael@0: return false; michael@0: } michael@0: if (xOrColor.IsLengthUnit() || xOrColor.IsCalcUnit()) { michael@0: val->Item(IndexX) = xOrColor; michael@0: } else { michael@0: // Must be a color (as string or color value) michael@0: NS_ASSERTION(xOrColor.GetUnit() == eCSSUnit_Ident || michael@0: xOrColor.GetUnit() == eCSSUnit_EnumColor || michael@0: xOrColor.IsNumericColorUnit(), michael@0: "Must be a color value"); michael@0: val->Item(IndexColor) = xOrColor; michael@0: haveColor = true; michael@0: michael@0: // X coordinate mandatory after color michael@0: if (!ParseVariant(val->Item(IndexX), VARIANT_LENGTH | VARIANT_CALC, michael@0: nullptr)) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // Y coordinate; mandatory michael@0: if (!ParseVariant(val->Item(IndexY), VARIANT_LENGTH | VARIANT_CALC, michael@0: nullptr)) { michael@0: return false; michael@0: } michael@0: michael@0: // Optional radius. Ignore errors except if they pass a negative michael@0: // value which we must reject. If we use ParseNonNegativeVariant michael@0: // we can't tell the difference between an unspecified radius michael@0: // and a negative radius. michael@0: if (ParseVariant(val->Item(IndexRadius), VARIANT_LENGTH | VARIANT_CALC, michael@0: nullptr) && michael@0: val->Item(IndexRadius).IsLengthUnit() && michael@0: val->Item(IndexRadius).GetFloatValue() < 0) { michael@0: return false; michael@0: } michael@0: michael@0: if (aIsBoxShadow) { michael@0: // Optional spread michael@0: ParseVariant(val->Item(IndexSpread), VARIANT_LENGTH | VARIANT_CALC, nullptr); michael@0: } michael@0: michael@0: if (!haveColor) { michael@0: // Optional color michael@0: ParseVariant(val->Item(IndexColor), VARIANT_COLOR, nullptr); michael@0: } michael@0: michael@0: if (aIsBoxShadow && val->Item(IndexInset).GetUnit() == eCSSUnit_Null) { michael@0: // Optional inset keyword michael@0: ParseVariant(val->Item(IndexInset), VARIANT_KEYWORD, michael@0: nsCSSProps::kBoxShadowTypeKTable); michael@0: } michael@0: michael@0: aValue.SetArrayValue(val, eCSSUnit_Array); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseShadowList(nsCSSProperty aProperty) michael@0: { michael@0: nsAutoParseCompoundProperty compound(this); michael@0: bool isBoxShadow = aProperty == eCSSProperty_box_shadow; michael@0: michael@0: nsCSSValue value; michael@0: // 'inherit', 'initial', 'unset' and 'none' must be alone michael@0: if (!ParseVariant(value, VARIANT_INHERIT | VARIANT_NONE, nullptr)) { michael@0: nsCSSValueList* cur = value.SetListValue(); michael@0: for (;;) { michael@0: if (!ParseShadowItem(cur->mValue, isBoxShadow)) { michael@0: return false; michael@0: } michael@0: if (!ExpectSymbol(',', true)) { michael@0: break; michael@0: } michael@0: cur->mNext = new nsCSSValueList; michael@0: cur = cur->mNext; michael@0: } michael@0: } michael@0: AppendValue(aProperty, value); michael@0: return true; michael@0: } michael@0: michael@0: int32_t michael@0: CSSParserImpl::GetNamespaceIdForPrefix(const nsString& aPrefix) michael@0: { michael@0: NS_PRECONDITION(!aPrefix.IsEmpty(), "Must have a prefix here"); michael@0: michael@0: int32_t nameSpaceID = kNameSpaceID_Unknown; michael@0: if (mNameSpaceMap) { michael@0: // user-specified identifiers are case-sensitive (bug 416106) michael@0: nsCOMPtr prefix = do_GetAtom(aPrefix); michael@0: if (!prefix) { michael@0: NS_RUNTIMEABORT("do_GetAtom failed - out of memory?"); michael@0: } michael@0: nameSpaceID = mNameSpaceMap->FindNameSpaceID(prefix); michael@0: } michael@0: // else no declared namespaces michael@0: michael@0: if (nameSpaceID == kNameSpaceID_Unknown) { // unknown prefix, dump it michael@0: REPORT_UNEXPECTED_P(PEUnknownNamespacePrefix, aPrefix); michael@0: } michael@0: michael@0: return nameSpaceID; michael@0: } michael@0: michael@0: void michael@0: CSSParserImpl::SetDefaultNamespaceOnSelector(nsCSSSelector& aSelector) michael@0: { michael@0: if (mNameSpaceMap) { michael@0: aSelector.SetNameSpace(mNameSpaceMap->FindNameSpaceID(nullptr)); michael@0: } else { michael@0: aSelector.SetNameSpace(kNameSpaceID_Unknown); // wildcard michael@0: } michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParsePaint(nsCSSProperty aPropID) michael@0: { michael@0: nsCSSValue x, y; michael@0: michael@0: if (!ParseVariant(x, VARIANT_HC | VARIANT_NONE | VARIANT_URL | michael@0: VARIANT_OPENTYPE_SVG_KEYWORD, michael@0: nsCSSProps::kContextPatternKTable)) { michael@0: return false; michael@0: } michael@0: michael@0: bool canHaveFallback = x.GetUnit() == eCSSUnit_URL || michael@0: x.GetUnit() == eCSSUnit_Enumerated; michael@0: if (canHaveFallback) { michael@0: if (!ParseVariant(y, VARIANT_COLOR | VARIANT_NONE, nullptr)) michael@0: y.SetNoneValue(); michael@0: } michael@0: michael@0: if (!canHaveFallback) { michael@0: AppendValue(aPropID, x); michael@0: } else { michael@0: nsCSSValue val; michael@0: val.SetPairValue(x, y); michael@0: AppendValue(aPropID, val); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseDasharray() michael@0: { michael@0: nsCSSValue value; michael@0: michael@0: // 'inherit', 'initial', 'unset' and 'none' are only allowed on their own michael@0: if (!ParseVariant(value, VARIANT_INHERIT | VARIANT_NONE | michael@0: VARIANT_OPENTYPE_SVG_KEYWORD, michael@0: nsCSSProps::kStrokeContextValueKTable)) { michael@0: nsCSSValueList *cur = value.SetListValue(); michael@0: for (;;) { michael@0: if (!ParseNonNegativeVariant(cur->mValue, VARIANT_LPN, nullptr)) { michael@0: return false; michael@0: } michael@0: if (CheckEndProperty()) { michael@0: break; michael@0: } michael@0: // skip optional commas between elements michael@0: (void)ExpectSymbol(',', true); michael@0: michael@0: cur->mNext = new nsCSSValueList; michael@0: cur = cur->mNext; michael@0: } michael@0: } michael@0: AppendValue(eCSSProperty_stroke_dasharray, value); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseMarker() michael@0: { michael@0: nsCSSValue marker; michael@0: if (ParseSingleValueProperty(marker, eCSSProperty_marker_end)) { michael@0: AppendValue(eCSSProperty_marker_end, marker); michael@0: AppendValue(eCSSProperty_marker_mid, marker); michael@0: AppendValue(eCSSProperty_marker_start, marker); michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParsePaintOrder() michael@0: { michael@0: static_assert michael@0: ((1 << NS_STYLE_PAINT_ORDER_BITWIDTH) > NS_STYLE_PAINT_ORDER_LAST_VALUE, michael@0: "bitfield width insufficient for paint-order constants"); michael@0: michael@0: static const KTableValue kPaintOrderKTable[] = { michael@0: eCSSKeyword_normal, NS_STYLE_PAINT_ORDER_NORMAL, michael@0: eCSSKeyword_fill, NS_STYLE_PAINT_ORDER_FILL, michael@0: eCSSKeyword_stroke, NS_STYLE_PAINT_ORDER_STROKE, michael@0: eCSSKeyword_markers, NS_STYLE_PAINT_ORDER_MARKERS, michael@0: eCSSKeyword_UNKNOWN,-1 michael@0: }; michael@0: michael@0: static_assert(MOZ_ARRAY_LENGTH(kPaintOrderKTable) == michael@0: 2 * (NS_STYLE_PAINT_ORDER_LAST_VALUE + 2), michael@0: "missing paint-order values in kPaintOrderKTable"); michael@0: michael@0: nsCSSValue value; michael@0: if (!ParseVariant(value, VARIANT_HK, kPaintOrderKTable)) { michael@0: return false; michael@0: } michael@0: michael@0: uint32_t seen = 0; michael@0: uint32_t order = 0; michael@0: uint32_t position = 0; michael@0: michael@0: // Ensure that even cast to a signed int32_t when stored in CSSValue, michael@0: // we have enough space for the entire paint-order value. michael@0: static_assert michael@0: (NS_STYLE_PAINT_ORDER_BITWIDTH * NS_STYLE_PAINT_ORDER_LAST_VALUE < 32, michael@0: "seen and order not big enough"); michael@0: michael@0: if (value.GetUnit() == eCSSUnit_Enumerated) { michael@0: uint32_t component = static_cast(value.GetIntValue()); michael@0: if (component != NS_STYLE_PAINT_ORDER_NORMAL) { michael@0: bool parsedOK = true; michael@0: for (;;) { michael@0: if (seen & (1 << component)) { michael@0: // Already seen this component. michael@0: UngetToken(); michael@0: parsedOK = false; michael@0: break; michael@0: } michael@0: seen |= (1 << component); michael@0: order |= (component << position); michael@0: position += NS_STYLE_PAINT_ORDER_BITWIDTH; michael@0: if (!ParseEnum(value, kPaintOrderKTable)) { michael@0: break; michael@0: } michael@0: component = value.GetIntValue(); michael@0: if (component == NS_STYLE_PAINT_ORDER_NORMAL) { michael@0: // Can't have "normal" in the middle of the list of paint components. michael@0: UngetToken(); michael@0: parsedOK = false; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // Fill in the remaining paint-order components in the order of their michael@0: // constant values. michael@0: if (parsedOK) { michael@0: for (component = 1; michael@0: component <= NS_STYLE_PAINT_ORDER_LAST_VALUE; michael@0: component++) { michael@0: if (!(seen & (1 << component))) { michael@0: order |= (component << position); michael@0: position += NS_STYLE_PAINT_ORDER_BITWIDTH; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: static_assert(NS_STYLE_PAINT_ORDER_NORMAL == 0, michael@0: "unexpected value for NS_STYLE_PAINT_ORDER_NORMAL"); michael@0: value.SetIntValue(static_cast(order), eCSSUnit_Enumerated); michael@0: } michael@0: michael@0: AppendValue(eCSSProperty_paint_order, value); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::BackslashDropped() michael@0: { michael@0: return mScanner->GetEOFCharacters() & michael@0: nsCSSScanner::eEOFCharacters_DropBackslash; michael@0: } michael@0: michael@0: void michael@0: CSSParserImpl::AppendImpliedEOFCharacters(nsAString& aResult) michael@0: { michael@0: nsCSSScanner::AppendImpliedEOFCharacters(mScanner->GetEOFCharacters(), michael@0: aResult); michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseAll() michael@0: { michael@0: nsCSSValue value; michael@0: if (!ParseVariant(value, VARIANT_INHERIT, nullptr)) { michael@0: return false; michael@0: } michael@0: michael@0: CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, eCSSProperty_all) { michael@0: AppendValue(*p, value); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseVariableDeclaration(CSSVariableDeclarations::Type* aType, michael@0: nsString& aValue) michael@0: { michael@0: CSSVariableDeclarations::Type type; michael@0: nsString variableValue; michael@0: bool dropBackslash; michael@0: nsString impliedCharacters; michael@0: michael@0: // Record the token stream while parsing a variable value. michael@0: if (!mInSupportsCondition) { michael@0: mScanner->StartRecording(); michael@0: } michael@0: if (!ParseValueWithVariables(&type, &dropBackslash, impliedCharacters, michael@0: nullptr, nullptr)) { michael@0: if (!mInSupportsCondition) { michael@0: mScanner->StopRecording(); michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: if (!mInSupportsCondition) { michael@0: if (type == CSSVariableDeclarations::eTokenStream) { michael@0: // This was indeed a token stream value, so store it in variableValue. michael@0: mScanner->StopRecording(variableValue); michael@0: if (dropBackslash) { michael@0: MOZ_ASSERT(!variableValue.IsEmpty() && michael@0: variableValue[variableValue.Length() - 1] == '\\'); michael@0: variableValue.Truncate(variableValue.Length() - 1); michael@0: } michael@0: variableValue.Append(impliedCharacters); michael@0: } else { michael@0: // This was either 'inherit' or 'initial'; we don't need the recorded michael@0: // input. michael@0: mScanner->StopRecording(); michael@0: } michael@0: } michael@0: michael@0: if (mHavePushBack && type == CSSVariableDeclarations::eTokenStream) { michael@0: // If we came to the end of a valid variable declaration and a token was michael@0: // pushed back, then it would have been ended by '!', ')', ';', ']' or '}'. michael@0: // We need to remove it from the recorded variable value. michael@0: MOZ_ASSERT(mToken.IsSymbol('!') || michael@0: mToken.IsSymbol(')') || michael@0: mToken.IsSymbol(';') || michael@0: mToken.IsSymbol(']') || michael@0: mToken.IsSymbol('}')); michael@0: if (!mInSupportsCondition) { michael@0: MOZ_ASSERT(!variableValue.IsEmpty()); michael@0: MOZ_ASSERT(variableValue[variableValue.Length() - 1] == mToken.mSymbol); michael@0: variableValue.Truncate(variableValue.Length() - 1); michael@0: } michael@0: } michael@0: michael@0: *aType = type; michael@0: aValue = variableValue; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CSSParserImpl::ParseValueWithVariables(CSSVariableDeclarations::Type* aType, michael@0: bool* aDropBackslash, michael@0: nsString& aImpliedCharacters, michael@0: void (*aFunc)(const nsAString&, void*), michael@0: void* aData) michael@0: { michael@0: // A property value is invalid if it contains variable references and also: michael@0: // michael@0: // * has unbalanced parens, brackets or braces michael@0: // * has any BAD_STRING or BAD_URL tokens michael@0: // * has any ';' or '!' tokens at the top level of a variable reference's michael@0: // fallback michael@0: // michael@0: // If the property is a custom property (i.e. a variable declaration), then michael@0: // it is also invalid if it consists of no tokens, such as: michael@0: // michael@0: // --invalid:; michael@0: // michael@0: // Note that is valid for a custom property to have a value that consists michael@0: // solely of white space, such as: michael@0: // michael@0: // --valid: ; michael@0: michael@0: // Stack of closing characters for currently open constructs. michael@0: StopSymbolCharStack stack; michael@0: michael@0: // Indexes into ')' characters in |stack| that correspond to "var(". This michael@0: // is used to stop parsing when we encounter a '!' or ';' at the top level michael@0: // of a variable reference's fallback. michael@0: nsAutoTArray references; michael@0: michael@0: if (!GetToken(false)) { michael@0: // Variable value was empty since we reached EOF. michael@0: REPORT_UNEXPECTED_EOF(PEVariableEOF); michael@0: return false; michael@0: } michael@0: michael@0: if (mToken.mType == eCSSToken_Symbol && michael@0: (mToken.mSymbol == '!' || michael@0: mToken.mSymbol == ')' || michael@0: mToken.mSymbol == ';' || michael@0: mToken.mSymbol == ']' || michael@0: mToken.mSymbol == '}')) { michael@0: // Variable value was empty since we reached the end of the construct. michael@0: UngetToken(); michael@0: REPORT_UNEXPECTED_TOKEN(PEVariableEmpty); michael@0: return false; michael@0: } michael@0: michael@0: if (mToken.mType == eCSSToken_Whitespace) { michael@0: if (!GetToken(true)) { michael@0: // Variable value was white space only. This is valid. michael@0: MOZ_ASSERT(!BackslashDropped()); michael@0: *aType = CSSVariableDeclarations::eTokenStream; michael@0: *aDropBackslash = false; michael@0: AppendImpliedEOFCharacters(aImpliedCharacters); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: // Look for 'initial', 'inherit' or 'unset' as the first non-white space michael@0: // token. michael@0: CSSVariableDeclarations::Type type = CSSVariableDeclarations::eTokenStream; michael@0: if (mToken.mType == eCSSToken_Ident) { michael@0: if (mToken.mIdent.LowerCaseEqualsLiteral("initial")) { michael@0: type = CSSVariableDeclarations::eInitial; michael@0: } else if (mToken.mIdent.LowerCaseEqualsLiteral("inherit")) { michael@0: type = CSSVariableDeclarations::eInherit; michael@0: } else if (mToken.mIdent.LowerCaseEqualsLiteral("unset")) { michael@0: type = CSSVariableDeclarations::eUnset; michael@0: } michael@0: } michael@0: michael@0: if (type != CSSVariableDeclarations::eTokenStream) { michael@0: if (!GetToken(true)) { michael@0: // Variable value was 'initial' or 'inherit' followed by EOF. michael@0: MOZ_ASSERT(!BackslashDropped()); michael@0: *aType = type; michael@0: *aDropBackslash = false; michael@0: AppendImpliedEOFCharacters(aImpliedCharacters); michael@0: return true; michael@0: } michael@0: UngetToken(); michael@0: if (mToken.mType == eCSSToken_Symbol && michael@0: (mToken.mSymbol == '!' || michael@0: mToken.mSymbol == ')' || michael@0: mToken.mSymbol == ';' || michael@0: mToken.mSymbol == ']' || michael@0: mToken.mSymbol == '}')) { michael@0: // Variable value was 'initial' or 'inherit' followed by the end michael@0: // of the declaration. michael@0: MOZ_ASSERT(!BackslashDropped()); michael@0: *aType = type; michael@0: *aDropBackslash = false; michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: do { michael@0: switch (mToken.mType) { michael@0: case eCSSToken_Symbol: michael@0: if (mToken.mSymbol == '(') { michael@0: stack.AppendElement(')'); michael@0: } else if (mToken.mSymbol == '[') { michael@0: stack.AppendElement(']'); michael@0: } else if (mToken.mSymbol == '{') { michael@0: stack.AppendElement('}'); michael@0: } else if (mToken.mSymbol == ';' || michael@0: mToken.mSymbol == '!') { michael@0: if (stack.IsEmpty()) { michael@0: UngetToken(); michael@0: MOZ_ASSERT(!BackslashDropped()); michael@0: *aType = CSSVariableDeclarations::eTokenStream; michael@0: *aDropBackslash = false; michael@0: return true; michael@0: } else if (!references.IsEmpty() && michael@0: references.LastElement() == stack.Length() - 1) { michael@0: REPORT_UNEXPECTED_TOKEN(PEInvalidVariableTokenFallback); michael@0: SkipUntilAllOf(stack); michael@0: return false; michael@0: } michael@0: } else if (mToken.mSymbol == ')' || michael@0: mToken.mSymbol == ']' || michael@0: mToken.mSymbol == '}') { michael@0: for (;;) { michael@0: if (stack.IsEmpty()) { michael@0: UngetToken(); michael@0: MOZ_ASSERT(!BackslashDropped()); michael@0: *aType = CSSVariableDeclarations::eTokenStream; michael@0: *aDropBackslash = false; michael@0: return true; michael@0: } michael@0: char16_t c = stack.LastElement(); michael@0: stack.TruncateLength(stack.Length() - 1); michael@0: if (!references.IsEmpty() && michael@0: references.LastElement() == stack.Length()) { michael@0: references.TruncateLength(references.Length() - 1); michael@0: } michael@0: if (mToken.mSymbol == c) { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: break; michael@0: michael@0: case eCSSToken_Function: michael@0: if (mToken.mIdent.LowerCaseEqualsLiteral("var")) { michael@0: if (!GetToken(true)) { michael@0: // EOF directly after "var(". michael@0: REPORT_UNEXPECTED_EOF(PEExpectedVariableNameEOF); michael@0: return false; michael@0: } michael@0: if (mToken.mType != eCSSToken_Ident || michael@0: !nsCSSProps::IsCustomPropertyName(mToken.mIdent)) { michael@0: // There must be an identifier directly after the "var(" and michael@0: // it must be a custom property name. michael@0: UngetToken(); michael@0: REPORT_UNEXPECTED_TOKEN(PEExpectedVariableName); michael@0: SkipUntil(')'); michael@0: SkipUntilAllOf(stack); michael@0: return false; michael@0: } michael@0: if (aFunc) { michael@0: MOZ_ASSERT(Substring(mToken.mIdent, 0, michael@0: CSS_CUSTOM_NAME_PREFIX_LENGTH). michael@0: EqualsLiteral("--")); michael@0: // remove '--' michael@0: const nsDependentSubstring varName = michael@0: Substring(mToken.mIdent, CSS_CUSTOM_NAME_PREFIX_LENGTH); michael@0: aFunc(varName, aData); michael@0: } michael@0: if (!GetToken(true)) { michael@0: // EOF right after "var(". michael@0: stack.AppendElement(')'); michael@0: } else if (mToken.IsSymbol(',')) { michael@0: // Variable reference with fallback. michael@0: if (!GetToken(false) || mToken.IsSymbol(')')) { michael@0: // Comma must be followed by at least one fallback token. michael@0: REPORT_UNEXPECTED(PEExpectedVariableFallback); michael@0: SkipUntilAllOf(stack); michael@0: return false; michael@0: } michael@0: UngetToken(); michael@0: references.AppendElement(stack.Length()); michael@0: stack.AppendElement(')'); michael@0: } else if (mToken.IsSymbol(')')) { michael@0: // Correctly closed variable reference. michael@0: } else { michael@0: // Malformed variable reference. michael@0: REPORT_UNEXPECTED_TOKEN(PEExpectedVariableCommaOrCloseParen); michael@0: SkipUntil(')'); michael@0: SkipUntilAllOf(stack); michael@0: return false; michael@0: } michael@0: } else { michael@0: stack.AppendElement(')'); michael@0: } michael@0: break; michael@0: michael@0: case eCSSToken_Bad_String: michael@0: SkipUntilAllOf(stack); michael@0: return false; michael@0: michael@0: case eCSSToken_Bad_URL: michael@0: SkipUntil(')'); michael@0: SkipUntilAllOf(stack); michael@0: return false; michael@0: michael@0: default: michael@0: break; michael@0: } michael@0: } while (GetToken(true)); michael@0: michael@0: // Append any implied closing characters. michael@0: *aDropBackslash = BackslashDropped(); michael@0: AppendImpliedEOFCharacters(aImpliedCharacters); michael@0: uint32_t i = stack.Length(); michael@0: while (i--) { michael@0: aImpliedCharacters.Append(stack[i]); michael@0: } michael@0: michael@0: *aType = type; michael@0: return true; michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: michael@0: // Recycling of parser implementation objects michael@0: michael@0: static CSSParserImpl* gFreeList = nullptr; michael@0: michael@0: nsCSSParser::nsCSSParser(mozilla::css::Loader* aLoader, michael@0: nsCSSStyleSheet* aSheet) michael@0: { michael@0: CSSParserImpl *impl = gFreeList; michael@0: if (impl) { michael@0: gFreeList = impl->mNextFree; michael@0: impl->mNextFree = nullptr; michael@0: } else { michael@0: impl = new CSSParserImpl(); michael@0: } michael@0: michael@0: if (aLoader) { michael@0: impl->SetChildLoader(aLoader); michael@0: impl->SetQuirkMode(aLoader->GetCompatibilityMode() == michael@0: eCompatibility_NavQuirks); michael@0: } michael@0: if (aSheet) { michael@0: impl->SetStyleSheet(aSheet); michael@0: } michael@0: michael@0: mImpl = static_cast(impl); michael@0: } michael@0: michael@0: nsCSSParser::~nsCSSParser() michael@0: { michael@0: CSSParserImpl *impl = static_cast(mImpl); michael@0: impl->Reset(); michael@0: impl->mNextFree = gFreeList; michael@0: gFreeList = impl; michael@0: } michael@0: michael@0: /* static */ void michael@0: nsCSSParser::Shutdown() michael@0: { michael@0: CSSParserImpl *tofree = gFreeList; michael@0: CSSParserImpl *next; michael@0: while (tofree) michael@0: { michael@0: next = tofree->mNextFree; michael@0: delete tofree; michael@0: tofree = next; michael@0: } michael@0: } michael@0: michael@0: // Wrapper methods michael@0: michael@0: nsresult michael@0: nsCSSParser::SetStyleSheet(nsCSSStyleSheet* aSheet) michael@0: { michael@0: return static_cast(mImpl)-> michael@0: SetStyleSheet(aSheet); michael@0: } michael@0: michael@0: nsresult michael@0: nsCSSParser::SetQuirkMode(bool aQuirkMode) michael@0: { michael@0: return static_cast(mImpl)-> michael@0: SetQuirkMode(aQuirkMode); michael@0: } michael@0: michael@0: nsresult michael@0: nsCSSParser::SetChildLoader(mozilla::css::Loader* aChildLoader) michael@0: { michael@0: return static_cast(mImpl)-> michael@0: SetChildLoader(aChildLoader); michael@0: } michael@0: michael@0: nsresult michael@0: nsCSSParser::ParseSheet(const nsAString& aInput, michael@0: nsIURI* aSheetURI, michael@0: nsIURI* aBaseURI, michael@0: nsIPrincipal* aSheetPrincipal, michael@0: uint32_t aLineNumber, michael@0: bool aAllowUnsafeRules) michael@0: { michael@0: return static_cast(mImpl)-> michael@0: ParseSheet(aInput, aSheetURI, aBaseURI, aSheetPrincipal, aLineNumber, michael@0: aAllowUnsafeRules); michael@0: } michael@0: michael@0: nsresult michael@0: nsCSSParser::ParseStyleAttribute(const nsAString& aAttributeValue, michael@0: nsIURI* aDocURI, michael@0: nsIURI* aBaseURI, michael@0: nsIPrincipal* aNodePrincipal, michael@0: css::StyleRule** aResult) michael@0: { michael@0: return static_cast(mImpl)-> michael@0: ParseStyleAttribute(aAttributeValue, aDocURI, aBaseURI, michael@0: aNodePrincipal, aResult); michael@0: } michael@0: michael@0: nsresult michael@0: nsCSSParser::ParseDeclarations(const nsAString& aBuffer, michael@0: nsIURI* aSheetURI, michael@0: nsIURI* aBaseURI, michael@0: nsIPrincipal* aSheetPrincipal, michael@0: css::Declaration* aDeclaration, michael@0: bool* aChanged) michael@0: { michael@0: return static_cast(mImpl)-> michael@0: ParseDeclarations(aBuffer, aSheetURI, aBaseURI, aSheetPrincipal, michael@0: aDeclaration, aChanged); michael@0: } michael@0: michael@0: nsresult michael@0: nsCSSParser::ParseRule(const nsAString& aRule, michael@0: nsIURI* aSheetURI, michael@0: nsIURI* aBaseURI, michael@0: nsIPrincipal* aSheetPrincipal, michael@0: css::Rule** aResult) michael@0: { michael@0: return static_cast(mImpl)-> michael@0: ParseRule(aRule, aSheetURI, aBaseURI, aSheetPrincipal, aResult); michael@0: } michael@0: michael@0: nsresult michael@0: nsCSSParser::ParseProperty(const nsCSSProperty aPropID, michael@0: const nsAString& aPropValue, michael@0: nsIURI* aSheetURI, michael@0: nsIURI* aBaseURI, michael@0: nsIPrincipal* aSheetPrincipal, michael@0: css::Declaration* aDeclaration, michael@0: bool* aChanged, michael@0: bool aIsImportant, michael@0: bool aIsSVGMode) michael@0: { michael@0: return static_cast(mImpl)-> michael@0: ParseProperty(aPropID, aPropValue, aSheetURI, aBaseURI, michael@0: aSheetPrincipal, aDeclaration, aChanged, michael@0: aIsImportant, aIsSVGMode); michael@0: } michael@0: michael@0: nsresult michael@0: nsCSSParser::ParseVariable(const nsAString& aVariableName, michael@0: const nsAString& aPropValue, michael@0: nsIURI* aSheetURI, michael@0: nsIURI* aBaseURI, michael@0: nsIPrincipal* aSheetPrincipal, michael@0: css::Declaration* aDeclaration, michael@0: bool* aChanged, michael@0: bool aIsImportant) michael@0: { michael@0: return static_cast(mImpl)-> michael@0: ParseVariable(aVariableName, aPropValue, aSheetURI, aBaseURI, michael@0: aSheetPrincipal, aDeclaration, aChanged, aIsImportant); michael@0: } michael@0: michael@0: void michael@0: nsCSSParser::ParseMediaList(const nsSubstring& aBuffer, michael@0: nsIURI* aURI, michael@0: uint32_t aLineNumber, michael@0: nsMediaList* aMediaList, michael@0: bool aHTMLMode) michael@0: { michael@0: static_cast(mImpl)-> michael@0: ParseMediaList(aBuffer, aURI, aLineNumber, aMediaList, aHTMLMode); michael@0: } michael@0: michael@0: bool michael@0: nsCSSParser::ParseColorString(const nsSubstring& aBuffer, michael@0: nsIURI* aURI, michael@0: uint32_t aLineNumber, michael@0: nsCSSValue& aValue) michael@0: { michael@0: return static_cast(mImpl)-> michael@0: ParseColorString(aBuffer, aURI, aLineNumber, aValue); michael@0: } michael@0: michael@0: nsresult michael@0: nsCSSParser::ParseSelectorString(const nsSubstring& aSelectorString, michael@0: nsIURI* aURI, michael@0: uint32_t aLineNumber, michael@0: nsCSSSelectorList** aSelectorList) michael@0: { michael@0: return static_cast(mImpl)-> michael@0: ParseSelectorString(aSelectorString, aURI, aLineNumber, aSelectorList); michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsCSSParser::ParseKeyframeRule(const nsSubstring& aBuffer, michael@0: nsIURI* aURI, michael@0: uint32_t aLineNumber) michael@0: { michael@0: return static_cast(mImpl)-> michael@0: ParseKeyframeRule(aBuffer, aURI, aLineNumber); michael@0: } michael@0: michael@0: bool michael@0: nsCSSParser::ParseKeyframeSelectorString(const nsSubstring& aSelectorString, michael@0: nsIURI* aURI, michael@0: uint32_t aLineNumber, michael@0: InfallibleTArray& aSelectorList) michael@0: { michael@0: return static_cast(mImpl)-> michael@0: ParseKeyframeSelectorString(aSelectorString, aURI, aLineNumber, michael@0: aSelectorList); michael@0: } michael@0: michael@0: bool michael@0: nsCSSParser::EvaluateSupportsDeclaration(const nsAString& aProperty, michael@0: const nsAString& aValue, michael@0: nsIURI* aDocURL, michael@0: nsIURI* aBaseURL, michael@0: nsIPrincipal* aDocPrincipal) michael@0: { michael@0: return static_cast(mImpl)-> michael@0: EvaluateSupportsDeclaration(aProperty, aValue, aDocURL, aBaseURL, michael@0: aDocPrincipal); michael@0: } michael@0: michael@0: bool michael@0: nsCSSParser::EvaluateSupportsCondition(const nsAString& aCondition, michael@0: nsIURI* aDocURL, michael@0: nsIURI* aBaseURL, michael@0: nsIPrincipal* aDocPrincipal) michael@0: { michael@0: return static_cast(mImpl)-> michael@0: EvaluateSupportsCondition(aCondition, aDocURL, aBaseURL, aDocPrincipal); michael@0: } michael@0: michael@0: bool michael@0: nsCSSParser::EnumerateVariableReferences(const nsAString& aPropertyValue, michael@0: VariableEnumFunc aFunc, michael@0: void* aData) michael@0: { michael@0: return static_cast(mImpl)-> michael@0: EnumerateVariableReferences(aPropertyValue, aFunc, aData); michael@0: } michael@0: michael@0: bool michael@0: nsCSSParser::ResolveVariableValue(const nsAString& aPropertyValue, michael@0: const CSSVariableValues* aVariables, michael@0: nsString& aResult, michael@0: nsCSSTokenSerializationType& aFirstToken, michael@0: nsCSSTokenSerializationType& aLastToken) michael@0: { michael@0: return static_cast(mImpl)-> michael@0: ResolveVariableValue(aPropertyValue, aVariables, michael@0: aResult, aFirstToken, aLastToken); michael@0: } michael@0: michael@0: void michael@0: nsCSSParser::ParsePropertyWithVariableReferences( michael@0: nsCSSProperty aPropertyID, michael@0: nsCSSProperty aShorthandPropertyID, michael@0: const nsAString& aValue, michael@0: const CSSVariableValues* aVariables, michael@0: nsRuleData* aRuleData, michael@0: nsIURI* aDocURL, michael@0: nsIURI* aBaseURL, michael@0: nsIPrincipal* aDocPrincipal, michael@0: nsCSSStyleSheet* aSheet, michael@0: uint32_t aLineNumber, michael@0: uint32_t aLineOffset) michael@0: { michael@0: static_cast(mImpl)-> michael@0: ParsePropertyWithVariableReferences(aPropertyID, aShorthandPropertyID, michael@0: aValue, aVariables, aRuleData, aDocURL, michael@0: aBaseURL, aDocPrincipal, aSheet, michael@0: aLineNumber, aLineOffset); michael@0: }